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.
16 #include "bookmark_tree.h"
18 #include <application/hoople_main.h>
19 #include <application/command_line.h>
20 #include <basis/astring.h>
21 #include <basis/functions.h>
22 #include <basis/guards.h>
23 #include <filesystem/byte_filer.h>
24 #include <filesystem/filename.h>
25 #include <loggers/file_logger.h>
26 #include <timely/time_stamp.h>
27 #include <structures/static_memory_gremlin.h>
28 #include <textual/list_parsing.h>
29 #include <textual/string_manipulation.h>
31 using namespace application;
32 using namespace basis;
33 using namespace filesystem;
34 using namespace loggers;
35 using namespace nodes;
36 using namespace structures;
37 using namespace textual;
38 using namespace timely;
41 // uncomment to have more debugging noise.
44 #define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT)
46 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
48 const int MAX_FILE_SIZE = 4 * MEGABYTE;
49 // the largest file we'll read.
51 const int SPACING_CHUNK = 4;
52 // number of spaces per indentation level.
54 const int MAX_URL_DISPLAYED = 58;
55 const int MAX_DESCRIP_DISPLAYED = 72;
57 ////////////////////////////////////////////////////////////////////////////
59 class marks_maker : public application_shell
67 // ST_JAVASCRIPT_BASED... separate implementation currently.
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".
75 DEFINE_CLASS_NAME("marks_maker");
76 int print_instructions(const filename &program_name);
77 virtual int execute();
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.
84 void increase_nesting(astring &output);
85 // adds a new level of nesting to the text.
87 void decrease_nesting(astring &output);
88 // drops out of a level of nesting in the text.
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.
95 void inject_javascript_function(astring &output);
96 // replaces a special phrase in the template file with our javascript-based link opener.
98 void write_category_start(const astring &name, int node_depth, astring &output);
99 // outputs the text for categories and adjusts the indentation level.
101 void write_category_end(int depth, astring &output);
102 // closes a category appropriately for the nesting depth.
104 void write_link(inner_mark_tree *node, const link_record &linko,
105 astring &output, int depth);
106 // outputs the text for web links.
109 ////////////////////////////////////////////////////////////////////////////
111 marks_maker::marks_maker()
112 : application_shell(),
114 c_style(ST_HUMAN_READABLE)
117 int marks_maker::print_instructions(const filename &program_name)
119 a_sprintf to_show("%s:\n\
120 This program needs three filenames as command line parameters. The -i flag\n\
121 is used to specify the input filename, the -t flag specifies a template web\n\
122 page which is used as the wrapper around the links, and the -o flag specifies\n\
123 the web page to be created. The input file is expected to be in the HOOPLE\n\
124 link database format. The output file will be created from the template file\n\
125 by finding the phrase $INSERT_LINKS_HERE in it and replacing that with html\n\
126 formatted link and categories from the input file. Another tag of $TODAYS_DATE\n\
127 will be replaced with the date when the output file is regenerated. A final\n\
128 tag of $INSERT_JAVASCRIPT_HERE is replaced with a link opening function.\n\
129 Note that an optional -s flag can specify a value of \"human\" readable\n\
130 or \"mozilla\" bookmarks style to specify the style of the output file\n\
132 The 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);
139 void marks_maker::increase_nesting(astring &output)
141 FUNCDEF("increase_nesting");
142 int spaces = SPACING_CHUNK * c_current_depth;
145 LOG(a_sprintf("++increased depth to %d...", c_current_depth));
147 output += string_manipulation::indentation(spaces);
149 output += parser_bits::platform_eol_to_chars();
152 void marks_maker::decrease_nesting(astring &output)
154 FUNCDEF("decrease_nesting");
157 LOG(a_sprintf("--decreased depth to %d...", c_current_depth));
159 int spaces = SPACING_CHUNK * c_current_depth;
160 output += string_manipulation::indentation(spaces);
161 output += "</dl><p>";
162 output += parser_bits::platform_eol_to_chars();
165 void marks_maker::write_category_start(const astring &name, int node_depth, astring &output)
167 FUNCDEF("write_category_start");
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";
176 LOG(astring("header [") + name + "] level " + a_sprintf("%d", node_depth));
179 // output our heading.
180 output += string_manipulation::indentation(c_current_depth * SPACING_CHUNK);
189 output += parser_bits::platform_eol_to_chars();
191 increase_nesting(output);
194 void marks_maker::write_category_end(int depth, astring &output)
196 FUNCDEF("write_category_end");
197 decrease_nesting(output);
200 void marks_maker::write_link(inner_mark_tree *formal(node),
201 const link_record &linko, astring &output, int depth)
203 FUNCDEF("write_link");
204 // write an html link definition.
206 // this just appears to be a comment line.
208 if (!linko._description) return; // it's a nothing line.
210 output += linko._description;
212 output += parser_bits::platform_eol_to_chars();
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, "...");
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 += "...";
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;
243 output += description;
246 if (c_style != ST_MOZILLA_MARKS) {
247 output += " ";
248 output += "<a href=\"javascript:open_mark('";
249 output += linko._url;
251 output += "[launch]";
257 output += parser_bits::platform_eol_to_chars();
260 astring marks_maker::recurse_on_node(inner_mark_tree *nod)
262 FUNCDEF("recurse_on_node");
265 // print out the category on this node.
266 write_category_start(nod->name(), nod->depth(), to_return);
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());
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));
279 // finish this category.
280 write_category_end(nod->depth(), to_return);
285 void marks_maker::inject_javascript_function(astring &output)
287 FUNCDEF("inject_javascript_function");
289 <script language=\"javascript1.2\">\n\
291 function 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\
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\
308 bool found_it = output.replace("$INSERT_JAVASCRIPT_HERE", scrip);
310 non_continuable_error(class_name(), func, "the template file is missing "
311 "the insertion point for '$INSERT_JAVASCRIPT_HERE'");
314 int marks_maker::write_marks_page(const astring &output_filename,
315 const astring &template_filename, output_style style)
317 FUNCDEF("write_marks_page");
318 c_style = style; // set the overall output style here.
320 // this is our accumulator of links. it is the semi-final result that will
321 // be injected into the template file.
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);
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();
336 // spice up the boring template with a nice link opening function.
337 inject_javascript_function(full_template);
339 // replace the tag with the long string we created.
340 bool found_it = full_template.replace("$INSERT_LINKS_HERE", long_string);
342 non_continuable_error(class_name(), func, "the template file is missing "
343 "the insertion point for '$INSERT_LINKS_HERE'");
345 full_template.replace("$TODAYS_DATE", time_stamp::notarize(true));
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);
358 BASE_LOG(astring("the tree, sir..."));
360 BASE_LOG(c_categories.access_root().text_form());
363 BASE_LOG(a_sprintf("wrote %d links in %d categories.",
364 c_categories.link_count(), c_categories.category_count()));
365 BASE_LOG(astring(""));
370 int marks_maker::execute()
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";
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);
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 "
401 output_style styley = ST_HUMAN_READABLE;
402 if (style_used == astring("mozilla")) styley = ST_MOZILLA_MARKS;
404 int ret = c_categories.read_csv_file(input_filename);
407 ret = write_marks_page(output_filename, template_filename, styley);
413 ////////////////////////////////////////////////////////////////////////////
415 HOOPLE_MAIN(marks_maker, )