feisty meow concerns codebase  2.140
marks_maker.cpp
Go to the documentation of this file.
1 // 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>
23 #include <filesystem/byte_filer.h>
24 #include <filesystem/filename.h>
25 #include <loggers/file_logger.h>
26 #include <timely/time_stamp.h>
28 #include <textual/list_parsing.h>
30 
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;
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 
48 const int MAX_FILE_SIZE = 4 * MEGABYTE;
49  // the largest file we'll read.
50 
51 const int SPACING_CHUNK = 4;
52  // number of spaces per indentation level.
53 
54 const int MAX_URL_DISPLAYED = 58;
55 const int MAX_DESCRIP_DISPLAYED = 72;
56 
58 
59 class marks_maker : public application_shell
60 {
61 public:
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 
79 private:
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 
111 marks_maker::marks_maker()
113  c_current_depth(0),
114  c_style(ST_HUMAN_READABLE)
115 {}
116 
117 int marks_maker::print_instructions(const filename &program_name)
118 {
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\
131 generated.\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);
136  return 12;
137 }
138 
139 void 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>";
149  output += parser_bits::platform_eol_to_chars();
150 }
151 
152 void 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>";
162  output += parser_bits::platform_eol_to_chars();
163 }
164 
165 void 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>";
189  output += parser_bits::platform_eol_to_chars();
190 
191  increase_nesting(output);
192 }
193 
194 void marks_maker::write_category_end(int depth, astring &output)
195 {
196  FUNCDEF("write_category_end");
197  decrease_nesting(output);
198 }
199 
200 void 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>";
212  output += parser_bits::platform_eol_to_chars();
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>";
257  output += parser_bits::platform_eol_to_chars();
258 }
259 
260 astring 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 
285 void 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\
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\
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 
314 int 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 
370 int 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 
415 HOOPLE_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.
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
const char * s() const
synonym for observe. the 's' stands for "string", if that helps.
Definition: astring.h:113
bool replace(const astring &tag, const astring &replacement)
replaces the first occurrence of "tag" text with the "replacement".
Definition: astring.cpp:905
virtual void zap(int start, int end)
Deletes the characters between "start" and "end" inclusively.
Definition: astring.cpp:521
void insert(int position, const astring &to_insert)
Copies "to_insert" into "this" at the "position".
Definition: astring.cpp:892
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
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
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.
Definition: symbol_tree.cpp:78
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
#define SETUP_COMBO_LOGGER
a macro that retasks the program-wide logger as a combo_logger.
Definition: combo_logger.h:49
#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:45
#define FUNCDEF(func_in)
FUNCDEF sets the name of a function (and plugs it into the callstack).
Definition: enhance_cpp.h:57
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
Definition: marks_maker.cpp:55
#define LOG(s)
Definition: marks_maker.cpp:46
const int MAX_URL_DISPLAYED
Definition: marks_maker.cpp:54
const int SPACING_CHUNK
Definition: marks_maker.cpp:51
#define BASE_LOG(s)
Definition: marks_maker.cpp:44
const int MAX_FILE_SIZE
Definition: marks_maker.cpp:48
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.
Definition: definitions.h:135
A platform independent way to obtain the timestamp of a file.
Definition: byte_filer.cpp:37
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>
Definition: earth_time.cpp:37