feisty meow concerns codebase 2.140
marks_maker.cpp
Go to the documentation of this file.
1
2// Name : marks_maker
3// Author : Chris Koeritz
5// Copyright (c) 2005-$now By Author. This program is free software; you can
6// redistribute it and/or modify it under the terms of the GNU General Public
7// License as published by the Free Software Foundation:
8// http://www.gnu.org/licenses/gpl.html
9// or under the terms of the GNU Library license:
10// http://www.gnu.org/licenses/lgpl.html
11// at your preference. Those licenses describe your legal rights to this
12// software, and no other rights or warranties apply.
13// Please send updates for this code to: fred@gruntose.com -- Thanks, fred.
15
16#include "bookmark_tree.h"
17
20#include <basis/astring.h>
21#include <basis/functions.h>
22#include <basis/guards.h>
24#include <filesystem/filename.h>
25#include <loggers/file_logger.h>
26#include <timely/time_stamp.h>
30
31using namespace application;
32using namespace basis;
33using namespace filesystem;
34using namespace loggers;
35using namespace nodes;
36using namespace structures;
37using namespace textual;
38using namespace timely;
39
40//#define DEBUG_MARKS
41 // uncomment to have more debugging noise.
42
43#undef BASE_LOG
44#define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT)
45#undef LOG
46#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
47
48const int MAX_FILE_SIZE = 4 * MEGABYTE;
49 // the largest file we'll read.
50
51const int SPACING_CHUNK = 4;
52 // number of spaces per indentation level.
53
54const int MAX_URL_DISPLAYED = 58;
55const int MAX_DESCRIP_DISPLAYED = 72;
56
58
59class marks_maker : public application_shell
60{
61public:
62 marks_maker();
63
64 enum output_style {
65 ST_HUMAN_READABLE,
66 ST_MOZILLA_MARKS,
67// ST_JAVASCRIPT_BASED... separate implementation currently.
68 };
69
70 int write_marks_page(const astring &output_filename,
71 const astring &template_filename, output_style way);
72 // given a tree of links, this writes out a web page to "output_filename"
73 // using a template file "template_filename".
74
75 DEFINE_CLASS_NAME("marks_maker");
76 int print_instructions(const filename &program_name);
77 virtual int execute();
78
79private:
80 bookmark_tree c_categories; // our tree of categories.
81 int c_current_depth; // current indentation depth in list.
82 output_style c_style; // style of marks to write, set after construction.
83
84 void increase_nesting(astring &output);
85 // adds a new level of nesting to the text.
86
87 void decrease_nesting(astring &output);
88 // drops out of a level of nesting in the text.
89
90 astring recurse_on_node(inner_mark_tree *nod);
91 // the main recursive method that spiders down the tree. it is important that it builds
92 // the string through composition rather than being given a string reference, since it
93 // expands all sub-trees as it goes.
94
95 void inject_javascript_function(astring &output);
96 // replaces a special phrase in the template file with our javascript-based link opener.
97
98 void write_category_start(const astring &name, int node_depth, astring &output);
99 // outputs the text for categories and adjusts the indentation level.
100
101 void write_category_end(int depth, astring &output);
102 // closes a category appropriately for the nesting depth.
103
104 void write_link(inner_mark_tree *node, const link_record &linko,
105 astring &output, int depth);
106 // outputs the text for web links.
107};
108
110
111marks_maker::marks_maker()
113 c_current_depth(0),
114 c_style(ST_HUMAN_READABLE)
115{}
116
117int marks_maker::print_instructions(const filename &program_name)
118{
119 a_sprintf to_show("%s:\n\
120This program needs three filenames as command line parameters. The -i flag\n\
121is used to specify the input filename, the -t flag specifies a template web\n\
122page which is used as the wrapper around the links, and the -o flag specifies\n\
123the web page to be created. The input file is expected to be in the HOOPLE\n\
124link database format. The output file will be created from the template file\n\
125by finding the phrase $INSERT_LINKS_HERE in it and replacing that with html\n\
126formatted link and categories from the input file. Another tag of $TODAYS_DATE\n\
127will be replaced with the date when the output file is regenerated. A final\n\
128tag of $INSERT_JAVASCRIPT_HERE is replaced with a link opening function.\n\
129Note that an optional -s flag can specify a value of \"human\" readable\n\
130or \"mozilla\" bookmarks style to specify the style of the output file\n\
131generated.\n\
132The HOOPLE link format is documented here:\n\
133 http://feistymeow.org/guides/link_database/format_manifesto.txt\n\
134", program_name.basename().raw().s(), program_name.basename().raw().s());
135 program_wide_logger::get().log(to_show, ALWAYS_PRINT);
136 return 12;
137}
138
139void marks_maker::increase_nesting(astring &output)
140{
141 FUNCDEF("increase_nesting");
142 int spaces = SPACING_CHUNK * c_current_depth;
143 c_current_depth++;
144#ifdef DEBUG_MARKS
145 LOG(a_sprintf("++increased depth to %d...", c_current_depth));
146#endif
147 output += string_manipulation::indentation(spaces);
148 output += "<dl><p>";
150}
151
152void marks_maker::decrease_nesting(astring &output)
153{
154 FUNCDEF("decrease_nesting");
155 c_current_depth--;
156#ifdef DEBUG_MARKS
157 LOG(a_sprintf("--decreased depth to %d...", c_current_depth));
158#endif
159 int spaces = SPACING_CHUNK * c_current_depth;
160 output += string_manipulation::indentation(spaces);
161 output += "</dl><p>";
163}
164
165void marks_maker::write_category_start(const astring &name, int node_depth, astring &output)
166{
167 FUNCDEF("write_category_start");
168
169 // calculate proper heading number.
170 int heading_num = node_depth + 1;
171 astring heading = a_sprintf("%d", heading_num);
172 // force a weird requirement for mozilla bookmarks, all headings must be set at 3.
173 if (c_style == ST_MOZILLA_MARKS) heading = "3";
174
175#ifdef DEBUG_MARKS
176 LOG(astring("header [") + name + "] level " + a_sprintf("%d", node_depth));
177#endif
178
179 // output our heading.
180 output += string_manipulation::indentation(c_current_depth * SPACING_CHUNK);
181 output += "<dt><h";
182 output += heading;
183 output += ">";
184 output += name;
185 output += "</h";
186 output += heading;
187 output += ">";
188 output += "</dt>";
190
191 increase_nesting(output);
192}
193
194void marks_maker::write_category_end(int depth, astring &output)
195{
196 FUNCDEF("write_category_end");
197 decrease_nesting(output);
198}
199
200void marks_maker::write_link(inner_mark_tree *formal(node),
201 const link_record &linko, astring &output, int depth)
202{
203 FUNCDEF("write_link");
204 // write an html link definition.
205 if (!linko._url) {
206 // this just appears to be a comment line.
207
208 if (!linko._description) return; // it's a nothing line.
209
210 output += linko._description;
211 output += "<br>";
213 return;
214 }
215
216 astring chomped_url = linko._url;
217 if (c_style != ST_MOZILLA_MARKS) {
218 if (chomped_url.length() > MAX_URL_DISPLAYED) {
219 chomped_url.zap(MAX_URL_DISPLAYED / 2,
220 chomped_url.length() - MAX_URL_DISPLAYED / 2 - 1);
221 chomped_url.insert(MAX_URL_DISPLAYED / 2, "...");
222 }
223 }
224
225 astring description = linko._description;
226 if (c_style != ST_MOZILLA_MARKS) {
227 // this is chopping the tail off, which seems reasonable for a very long description.
228 if (description.length() > MAX_DESCRIP_DISPLAYED) {
229 description.zap(MAX_DESCRIP_DISPLAYED - 1, description.end());
230 description += "...";
231 }
232 }
233
234 // new output format, totally clean and simple. description is there
235 // in readable manner, and it's also a link. plus, this takes up a fraction
236 // of the space the old way used.
237 astring indentulus = string_manipulation::indentation(c_current_depth * SPACING_CHUNK);
238 output += indentulus;
239 output += "<dt><li>";
240 output += "<a href=\"";
241 output += linko._url;
242 output += "\">";
243 output += description;
244 output += "</a>";
245
246 if (c_style != ST_MOZILLA_MARKS) {
247 output += "&nbsp;&nbsp;&nbsp;";
248 output += "<a href=\"javascript:open_mark('";
249 output += linko._url;
250 output += "')\">";
251 output += "[launch]";
252 output += "</a>";
253 }
254
255 output += "</li>";
256 output += "</dt>";
258}
259
260astring marks_maker::recurse_on_node(inner_mark_tree *nod)
261{
262 FUNCDEF("recurse_on_node");
263 astring to_return;
264
265 // print out the category on this node.
266 write_category_start(nod->name(), nod->depth(), to_return);
267
268 // print the link for all of the ones stored at this node.
269 for (int i = 0; i < nod->_links.elements(); i++) {
270 link_record *lin = nod->_links.borrow(i);
271 write_link(nod, *lin, to_return, nod->depth());
272 }
273
274 // zoom down into sub-categories.
275 for (int i = 0; i < nod->branches(); i++) {
276 to_return += recurse_on_node((inner_mark_tree *)nod->branch(i));
277 }
278
279 // finish this category.
280 write_category_end(nod->depth(), to_return);
281
282 return to_return;
283}
284
285void marks_maker::inject_javascript_function(astring &output)
286{
287 FUNCDEF("inject_javascript_function");
288 astring scrip = "\n\
289<script language=\"javascript1.2\">\n\
290<!--\n\
291function open_mark(url) {\n\
292 if (typeof open_mark.next_num == 'undefined') {\n\
293 // must initialize this before use.\n\
294 open_mark.next_num = 0;\n\
295 }\n\
296 // pick the next number for our auto-generated name.\n\
297 open_mark.next_num++;\n\
298 winname = \"wingo\" + open_mark.next_num;\n\
299 // open URL they asked for and give its window all permissions.\n\
300 winner = window.open(url, winname);\n\
301 // bring that new window into focus so they can see it.\n\
302 winner.focus();\n\
303}\n\
304//-->\n\
305</script>\n\
306\n";
307
308 bool found_it = output.replace("$INSERT_JAVASCRIPT_HERE", scrip);
309 if (!found_it)
310 non_continuable_error(class_name(), func, "the template file is missing "
311 "the insertion point for '$INSERT_JAVASCRIPT_HERE'");
312}
313
314int marks_maker::write_marks_page(const astring &output_filename,
315 const astring &template_filename, output_style style)
316{
317 FUNCDEF("write_marks_page");
318 c_style = style; // set the overall output style here.
319 astring long_string;
320 // this is our accumulator of links. it is the semi-final result that will
321 // be injected into the template file.
322
323 // generate the meaty portion of the bookmarks.
324 increase_nesting(long_string);
325 inner_mark_tree *top = (inner_mark_tree *)&c_categories.access_root();
326 long_string += recurse_on_node(top);
327 decrease_nesting(long_string);
328
329 byte_filer template_file(template_filename, "r");
330 astring full_template;
331 if (!template_file.good())
332 non_continuable_error(class_name(), func, "the template file could not be opened");
333 template_file.read(full_template, MAX_FILE_SIZE);
334 template_file.close();
335
336 // spice up the boring template with a nice link opening function.
337 inject_javascript_function(full_template);
338
339 // replace the tag with the long string we created.
340 bool found_it = full_template.replace("$INSERT_LINKS_HERE", long_string);
341 if (!found_it)
342 non_continuable_error(class_name(), func, "the template file is missing "
343 "the insertion point for '$INSERT_LINKS_HERE'");
344
345 full_template.replace("$TODAYS_DATE", time_stamp::notarize(true));
346
347 filename outname(output_filename);
348 byte_filer output_file(output_filename, "w");
349 if (!output_file.good())
350 non_continuable_error(class_name(), func, "the output file could not be opened");
351 // write the newly generated web page out now.
352 output_file.write(full_template);
353 output_file.close();
354
355#ifdef DEBUG_MARKS
356 // show the tree.
357 BASE_LOG(astring());
358 BASE_LOG(astring("the tree, sir..."));
359 BASE_LOG(astring());
360 BASE_LOG(c_categories.access_root().text_form());
361#endif
362
363 BASE_LOG(a_sprintf("wrote %d links in %d categories.",
364 c_categories.link_count(), c_categories.category_count()));
365 BASE_LOG(astring(""));
366
367 return 0;
368}
369
370int marks_maker::execute()
371{
372 FUNCDEF("execute");
374
375 command_line cmds(_global_argc, _global_argv); // process the command line parameters.
376 astring input_filename; // we'll store our link database name here.
377 astring output_filename; // where the web page we're creating goes.
378 astring template_filename; // the wrapper html code that we'll stuff.
379 astring style_used; // type of output file style to create.
380 if (!cmds.get_value('i', input_filename, false))
381 return print_instructions(cmds.program_name());
382 if (!cmds.get_value('o', output_filename, false))
383 return print_instructions(cmds.program_name());
384 if (!cmds.get_value('t', template_filename, false))
385 return print_instructions(cmds.program_name());
386 cmds.get_value('s', style_used, false);
387 if (!style_used) style_used = "human";
388
389 BASE_LOG(astring("input file: ") + input_filename);
390 BASE_LOG(astring("output file: ") + output_filename);
391 BASE_LOG(astring("template file: ") + template_filename);
392 BASE_LOG(astring("style: ") + style_used);
393
394 filename outname(output_filename);
395 if (outname.exists()) {
396 non_continuable_error(class_name(), func, astring("the output file ")
397 + output_filename + " already exists. It would be over-written if "
398 "we continued.");
399 }
400
401 output_style styley = ST_HUMAN_READABLE;
402 if (style_used == astring("mozilla")) styley = ST_MOZILLA_MARKS;
403
404 int ret = c_categories.read_csv_file(input_filename);
405 if (ret) return ret;
406
407 ret = write_marks_page(output_filename, template_filename, styley);
408 if (ret) return ret;
409
410 return 0;
411}
412
414
415HOOPLE_MAIN(marks_maker, )
416
int print_instructions(bool good, const astring &program_name)
Definition checker.cpp:45
The application_shell is a base object for console programs.
virtual int execute()=0
< retrieves the command line from the /proc hierarchy on linux.
a_sprintf is a specialization of astring that provides printf style support.
Definition astring.h:440
Provides a dynamically resizable ASCII character string.
Definition astring.h:35
bool replace(const astring &tag, const astring &replacement)
replaces the first occurrence of "tag" text with the "replacement".
Definition astring.cpp:908
const char * s() const
synonym for observe. the 's' stands for "string", if that helps.
Definition astring.h:113
virtual void zap(int start, int end)
Deletes the characters between "start" and "end" inclusively.
Definition astring.cpp:524
void insert(int position, const astring &to_insert)
Copies "to_insert" into "this" at the "position".
Definition astring.cpp:895
int end() const
returns the index of the last (non-null) character in the string.
Definition astring.h:86
int length() const
Returns the current length of the string.
Definition astring.cpp:132
virtual outcome log(const base_string &info, int filter)=0
writes the information in "info" to the logger using the "filter".
Provides file managment services using the standard I/O support.
Definition byte_filer.h:32
Provides operations commonly needed on file names.
Definition filename.h:64
const basis::astring & raw() const
returns the astring that we're holding onto for the path.
Definition filename.cpp:97
filename basename() const
returns the base of the filename; no directory.
Definition filename.cpp:385
listo_links _links
static loggers::standard_log_base & get()
Provided by the startup code within each application for logging.
An object representing the interstitial cell in most linked data structures.
Definition node.h:41
const basis::astring & name() const
returns the name of this node.
symbol_tree * branch(int index) const
returns the "index"th branch.
virtual int depth() const
Returns the distance of "this" from the root. The root's depth is 0.
Definition tree.cpp:502
virtual int branches() const
Returns the number of branches currently connected to this tree.
Definition tree.cpp:431
int elements() const
the maximum number of elements currently allowed in this amorph.
Definition amorph.h:66
contents * borrow(int field)
Returns a pointer to the information at the index "field".
Definition amorph.h:448
static const char * platform_eol_to_chars()
provides the characters that make up this platform's line ending.
static basis::astring indentation(int spaces)
Returns a string made of white space that is "spaces" long.
static basis::astring notarize(bool add_space=true)
a useful method for getting a textual version of the time "right now".
#define SETUP_COMBO_LOGGER
a macro that retasks the program-wide logger as a combo_logger.
#define non_continuable_error(c, f, i)
an extra piece of information used, if available, in bounds_halt below.
#define formal(parameter)
This macro just eats what it's passed; it marks unused formal parameters.
Definition definitions.h:48
#define DEFINE_CLASS_NAME(objname)
Defines the name of a class by providing a couple standard methods.
Definition enhance_cpp.h:42
#define FUNCDEF(func_in)
FUNCDEF sets the name of a function (and plugs it into the callstack).
Definition enhance_cpp.h:54
Provides macros that implement the 'main' program of an application.
#define HOOPLE_MAIN(obj_name, obj_args)
options that should work for most unix and linux apps.
Definition hoople_main.h:61
const int MAX_DESCRIP_DISPLAYED
#define LOG(s)
const int MAX_URL_DISPLAYED
const int SPACING_CHUNK
#define BASE_LOG(s)
const int MAX_FILE_SIZE
Implements an application lock to ensure only one is running at once.
char ** _global_argv
The guards collection helps in testing preconditions and reporting errors.
Definition array.h:30
const int MEGABYTE
Number of bytes in a megabyte.
A platform independent way to obtain the timestamp of a file.
A logger that sends to the console screen using the standard output device.
A dynamic container class that holds any kind of object via pointers.
Definition amorph.h:55
#include <time.h>