first check-in of feisty meow codebase. many things broken still due to recent
[feisty_meow.git] / core / applications / bookmark_tools / js_marks_maker.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : marks_maker_javascript                                            *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *  Purpose:                                                                   *
7 *                                                                             *
8 *    Turns a link database in HOOPLE format into a web page, when given a     *
9 *  suitable template file.  The template file must have the phrase:           *
10 *        $INSERT_LINKS_HERE                                                   *
11 *  at the point where the generated links are supposed to be stored.          *
12 *                                                                             *
13 *******************************************************************************
14 * Copyright (c) 2005-$now By Author.  This program is free software; you can  *
15 * redistribute it and/or modify it under the terms of the GNU General Public  *
16 * License as published by the Free Software Foundation; either version 2 of   *
17 * the License or (at your option) any later version.  This is online at:      *
18 *     http://www.fsf.org/copyleft/gpl.html                                    *
19 * Please send any updates to: fred@gruntose.com                               *
20 \*****************************************************************************/
21
22 #include "bookmark_tree.h"
23
24 #include <application/command_line.h>
25 #include <application/hoople_main.h>
26 #include <basis/astring.h>
27 #include <basis/functions.h>
28 #include <basis/guards.h>
29 #include <filesystem/byte_filer.h>
30 #include <filesystem/filename.h>
31 #include <loggers/file_logger.h>
32 #include <structures/stack.h>
33 #include <structures/static_memory_gremlin.h>
34 #include <textual/list_parsing.h>
35 #include <timely/time_stamp.h>
36
37 using namespace application;
38 using namespace basis;
39 using namespace filesystem;
40 using namespace loggers;
41 using namespace nodes;
42 using namespace structures;
43 using namespace textual;
44 using namespace timely;
45
46 //#define DEBUG_MARKS
47   // uncomment to have more debugging noise.
48
49 #undef BASE_LOG
50 #define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT)
51 #undef LOG
52 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), \
53    a_sprintf("line %d: ", _categories._line_number) + s, ALWAYS_PRINT)
54
55 const int MAX_FILE_SIZE = 4 * MEGABYTE;
56   // the largest file we'll read.
57
58 ////////////////////////////////////////////////////////////////////////////
59
60 class marks_maker_javascript : public application_shell
61 {
62 public:
63   marks_maker_javascript() : application_shell(), _need_closure(false),
64         _loader_count(0), _link_spool(0), _functions_pending(0) {}
65   DEFINE_CLASS_NAME("marks_maker_javascript");
66   virtual int execute();
67   int print_instructions(const filename &program_name);
68
69   int write_marks_page(const astring &output_filename,
70           const astring &template_filename);
71     // given a tree of links, this writes out a web page to "output_filename"
72     // using a template file "template_filename".
73
74 private:
75   bookmark_tree _categories;  // our tree of categories.
76   bool _need_closure;  // true if our <div> needs a closure.
77   int _loader_count;  // count of the loader functions.
78   int _link_spool;  // count of which link we're writing.
79   stack<astring> _functions_pending;  // used for javascript node functions.
80
81 //this needs to gather any strings that would have gone into functions.
82 //instead, they need to be written into the current node's string.
83 //when a new function def would be seen, then we need to push a new node
84 //for accumulating the text.
85
86   // these handle outputting text for categories and links.
87   void write_category(inner_mark_tree *node, astring &output);
88   void write_link(inner_mark_tree *node, const link_record &linko,
89          astring &output);
90 };
91
92 ////////////////////////////////////////////////////////////////////////////
93
94 int marks_maker_javascript::print_instructions(const filename &program_name)
95 {
96   a_sprintf to_show("%s:\n\
97 This program needs three filenames as command line parameters.  The -i flag\n\
98 is used to specify the input filename, the -t flag specifies a template web\n\
99 page which is used as the wrapper around the links, and the -o flag specifies\n\
100 the web page to be created.  The input file is expected to be in the HOOPLE\n\
101 link database format.  The output file will be created from the template file\n\
102 by finding the phrase $INSERT_LINKS_HERE in it and replacing that with html\n\
103 formatted link and categories from the input file.  Another tag of $TODAYS_DATE\n\
104 will be replaced with the date when the output file is regenerated.\n\
105 The HOOPLE link format is documented here:\n\
106     http://hoople.org/guides/link_database/format_manifesto.txt\n\
107 ", program_name.basename().raw().s(), program_name.basename().raw().s());
108   program_wide_logger::get().log(to_show, ALWAYS_PRINT);
109   return 12;
110 }
111
112 void marks_maker_javascript::write_category(inner_mark_tree *node, astring &output)
113 {
114 //  FUNCDEF("write_category");
115   // output a javascript line for the category.
116
117   int node_num = node->_uid;
118   inner_mark_tree *parent = dynamic_cast<inner_mark_tree *>(node->parent());
119   int parent_node = parent? parent->_uid : -1;
120     // the parent node for root is a negative one.
121   astring chewed_name = node->name();
122   for (int i = chewed_name.end(); i >= 0; i--) {
123     // escape any raw single quotes that we see.
124     if (chewed_name[i] == '\'') {
125       chewed_name.zap(i, i);
126       chewed_name.insert(i, "\\'");
127     }
128   }
129   output += a_sprintf("  b.add(%d, %d, '%s');\n", node_num, parent_node,
130       chewed_name.s());
131 }
132
133 void marks_maker_javascript::write_link(inner_mark_tree *node, const link_record &linko, 
134     astring &output)
135 {
136 //  FUNCDEF("write_link");
137   // write a javascript link definition.
138   int parent_node = node->_uid;
139   astring chewed_name = linko._description;
140   for (int i = chewed_name.end(); i >= 0; i--) {
141     // escape any raw single quotes that we see.
142     if (chewed_name[i] == '\'') {
143       chewed_name.zap(i, i);
144       chewed_name.insert(i, "\\'");
145     }
146   }
147
148   if (!linko._url) {
149     // this just appears to be a comment line.
150     if (!linko._description) return;  // it's a nothing line.
151
152 /*
153 //hmmm: probably not what we want.
154 //hmmm: why not, again?
155     output += linko._description;
156     output += "<br>";
157     output += parser_bits::platform_eol_to_chars();
158 */
159     return;
160   }
161
162   // generate a function header if the number of links is a multiple of 100.
163   if (! (_link_spool % 100) ) {
164     if (_link_spool) {
165       // close out the previous function and set a timeout.
166       output += "  setTimeout('run_tree_loaders()', 0);\n";
167       output += "}\n";
168     }
169
170     output += a_sprintf("function tree_loader_%d() {\n", _loader_count++);
171   }
172   _link_spool++;
173
174   output += a_sprintf("  b.add(%d, %d, '%s', '%s');\n",
175       linko._uid, parent_node, chewed_name.s(), linko._url.s());
176 }
177
178 int marks_maker_javascript::execute()
179 {
180 //  FUNCDEF("execute");
181   SETUP_COMBO_LOGGER;
182
183   command_line cmds(_global_argc, _global_argv);  // process the command line parameters.
184   astring input_filename;  // we'll store our link database name here.
185   astring output_filename;  // where the web page we're creating goes.
186   astring template_filename;  // the wrapper html code that we'll stuff.
187   if (!cmds.get_value('i', input_filename, false))
188     return print_instructions(cmds.program_name());
189   if (!cmds.get_value('o', output_filename, false))
190     return print_instructions(cmds.program_name());
191   if (!cmds.get_value('t', template_filename, false))
192     return print_instructions(cmds.program_name());
193
194   BASE_LOG(astring("input file: ") + input_filename);
195   BASE_LOG(astring("output file: ") + output_filename);
196   BASE_LOG(astring("template file: ") + template_filename);
197
198   int ret = _categories.read_csv_file(input_filename);
199   if (ret) return ret;
200
201   ret = write_marks_page(output_filename, template_filename);
202   if (ret) return ret;
203   
204   return 0;
205 }
206
207 int marks_maker_javascript::write_marks_page(const astring &output_filename,
208     const astring &template_filename)
209 {
210   FUNCDEF("write_marks_page");
211   astring long_string;
212     // this is our accumulator of links.  it is the semi-final result that will
213     // be injected into the template file.
214
215   // add our target layer so that we can write to a useful place.
216   long_string += "<div class=\"marks_target\" id=\"martarg\">Marks Target</div>\n";
217
218   // add the tree style and creation of the tree object into the text.
219   long_string += "\n<div class=\"dtree\">\n";
220   long_string += "<script type=\"text/javascript\">\n";
221   long_string += "<!--\n";
222
223   long_string += "function open_mark(url) {\n";
224   long_string += "  window.open(url, '', '');\n";
225   long_string += "}\n";
226
227   // the list of functions is used for calling into the tree loaders
228   // without blocking.
229   long_string += "  b = new dTree('b');\n";
230 ///  long_string += "  b.config.inOrder = true;\n";
231   long_string += "  b.config.useCookies = false;\n";
232   long_string += "  b.config.folderLinks = false;\n";
233
234   // traverse the tree in prefix order.
235   tree::iterator itty = _categories.access_root().start(tree::prefix);
236   tree *curr = NIL;
237   while ( (curr = itty.next()) ) {
238     inner_mark_tree *nod = (inner_mark_tree *)curr;
239     // print out the category on this node.
240     write_category(nod, long_string);
241
242     // print the link for all of the ones stored at this node.
243     for (int i = 0; i < nod->_links.elements(); i++) {
244       link_record *lin = nod->_links.borrow(i);
245       write_link(nod, *lin, long_string);
246     }
247   }
248
249   // close the block of script in the output.
250   long_string += "  setTimeout('run_tree_loaders()', 0);\n";
251   long_string += "}\n\n";
252
253   long_string += a_sprintf("function tree_loader_%d()"
254       "{ setTimeout('run_tree_loaders()', 0); }\n", _loader_count++);
255
256   long_string += "\nconst max_funcs = 1000;\n";
257   long_string += "var loader_functions = new Array(max_funcs);\n";
258   long_string += "var curr_func = 0;\n";
259   long_string += "var done_rendering = false;\n\n";
260
261   long_string += a_sprintf("for (var i = 0; i < %d; i++) {\n", _loader_count);
262   long_string += "  loader_functions[curr_func++] "
263         "= 'tree_loader_' + i + '()';\n";
264   long_string += "}\n";
265
266   long_string += "var run_index = 0;\n";
267   long_string += "function run_tree_loaders() {\n";
268   long_string += "  if (done_rendering) return;\n";
269   long_string += "  if (run_index >= curr_func) {\n";
270
271   long_string += "    if (document.getElementById) {\n";
272   long_string += "      x = document.getElementById('martarg');\n";
273   long_string += "      x.innerHTML = '';\n";
274   long_string += "      x.innerHTML = b;\n";
275   long_string += "    } else { document.write(b); }\n";
276 //not a very graceful degradation.  we should use the other options from:
277 // http://www.quirksmode.org/js/layerwrite.html
278   long_string += "    done_rendering = true;\n";
279   long_string += "    return;\n";
280   long_string += "  }\n";
281   long_string += "  var next_func = loader_functions[run_index++];\n";
282   long_string += "  setTimeout(next_func, 0);\n";
283   long_string += "}\n";
284
285   long_string += a_sprintf("  run_tree_loaders();\n", _loader_count);
286
287   long_string += "//-->\n";
288   long_string += "</script>\n";
289   long_string += "<p><a href=\"javascript: b.openAll();\">open all</a> | "
290       "<a href=\"javascript: b.closeAll();\">close all</a></p>\n";
291   long_string += "</div>\n";
292
293   byte_filer template_file(template_filename, "r");
294   astring full_template;
295   if (!template_file.good())
296     non_continuable_error(class_name(), func, "the template file could not be opened");
297   template_file.read(full_template, MAX_FILE_SIZE);
298   template_file.close();
299
300   // javascript output needs some extra junk added to the header section.
301   int indy = full_template.ifind("</title>");
302   if (negative(indy))
303     non_continuable_error(class_name(), func, "the template file is missing "
304         "a <head> declaration");
305 //hmmm: the path here must be configurable!
306   full_template.insert(indy + 8, "\n\n"
307       "<link rel=\"StyleSheet\" href=\"/yeti/javascript/dtree/dtree.css\" "
308           "type=\"text/css\" />\n"
309       "<script type=\"text/javascript\" src=\"/yeti/javascript/"
310           "dtree/dtree.js\"></script>\n");
311
312   // replace the tag with the long string we created.
313   bool found_it = full_template.replace("$INSERT_LINKS_HERE", long_string);
314   if (!found_it)
315     non_continuable_error(class_name(), func, "the template file is missing "
316         "the insertion point");
317   full_template.replace("$TODAYS_DATE", time_stamp::notarize(true));
318
319   filename outname(output_filename);
320   if (outname.exists()) {
321     non_continuable_error(class_name(), func, astring("the output file ")
322         + output_filename + " already exists.  It would be over-written if "
323         "we continued.");
324   }
325
326   byte_filer output_file(output_filename, "w");
327   if (!output_file.good())
328     non_continuable_error(class_name(), func, "the output file could not be opened");
329   // write the newly generated web page out now.
330   output_file.write(full_template);
331   output_file.close();
332
333 // show the tree.
334 //  BASE_LOG("");
335 //  BASE_LOG(_categories.access_root().text_form());
336
337   BASE_LOG(a_sprintf("wrote %d links in %d categories.",
338       _categories.link_count(), _categories.category_count()));
339   BASE_LOG(astring(""));
340
341   return 0;
342 }
343
344 ////////////////////////////////////////////////////////////////////////////
345
346 HOOPLE_MAIN(marks_maker_javascript, )
347