1 /*****************************************************************************\
3 * Name : marks_sorter *
4 * Author : Chris Koeritz *
8 * Processes a link database in HOOPLE format and generates a new database *
9 * that is sorted and always uses category nicknames where defined. *
11 *******************************************************************************
12 * Copyright (c) 2006-$now By Author. This program is free software; you can *
13 * redistribute it and/or modify it under the terms of the GNU General Public *
14 * License as published by the Free Software Foundation; either version 2 of *
15 * the License or (at your option) any later version. This is online at: *
16 * http://www.fsf.org/copyleft/gpl.html *
17 * Please send any updates to: fred@gruntose.com *
18 \*****************************************************************************/
20 #include "bookmark_tree.h"
22 #include <application/hoople_main.h>
23 #include <basis/functions.h>
24 #include <basis/guards.h>
25 #include <basis/astring.h>
26 #include <filesystem/byte_filer.h>
27 #include <filesystem/filename.h>
28 #include <loggers/combo_logger.h>
29 #include <loggers/critical_events.h>
30 #include <structures/static_memory_gremlin.h>
31 #include <textual/list_parsing.h>
32 #include <textual/parser_bits.h>
34 using namespace application;
35 using namespace basis;
36 using namespace filesystem;
37 using namespace loggers;
38 using namespace nodes;
39 using namespace structures;
40 using namespace textual;
43 // uncomment to have more debugging noise.
46 #define BASE_LOG(s) program_wide_logger::get().log(s, ALWAYS_PRINT)
48 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), \
49 a_sprintf("line %d: ", _categories._line_number) + s)
51 const int MAX_FILE_SIZE = 4 * MEGABYTE;
52 // the largest file we'll read.
54 ////////////////////////////////////////////////////////////////////////////
56 class marks_sorter : public application_shell
60 : application_shell(), _loader_count(0), _link_spool(0) {}
61 DEFINE_CLASS_NAME("marks_sorter");
62 virtual int execute();
63 int print_instructions(const filename &program_name);
65 int write_new_marks(const astring &output_filename);
66 // given a tree of links, this writes out a new sorted file to the
70 bookmark_tree _categories; // our tree of categories.
71 int _loader_count; // count of the loader functions.
72 int _link_spool; // count of which link we're writing.
75 ////////////////////////////////////////////////////////////////////////////
77 int marks_sorter::print_instructions(const filename &program_name)
79 a_sprintf to_show("%s:\n\
80 This program needs two filenames as command-line parameters. The -i flag\n\
81 is used to specify the input filename, which is expected to be in the HOOPLE\n\
82 link database format. The -o flag specifies the new bookmarks file to be\n\
83 created, which will also be in the HOOPLE link format.\n\
84 The HOOPLE link format is documented here:\n\
85 http://feistymeow.org/guides/link_database/format_manifesto.txt\n\
86 ", program_name.basename().raw().s(), program_name.basename().raw().s());
87 program_wide_logger::get().log(to_show, ALWAYS_PRINT);
91 int marks_sorter::execute()
96 command_line cmds(_global_argc, _global_argv); // process the command line parameters.
97 astring input_filename; // we'll store our link database name here.
98 astring output_filename; // where the web page we're creating goes.
99 if (!cmds.get_value('i', input_filename, false))
100 return print_instructions(cmds.program_name());
101 if (!cmds.get_value('o', output_filename, false))
102 return print_instructions(cmds.program_name());
104 BASE_LOG(astring("input file: ") + input_filename);
105 BASE_LOG(astring("output file: ") + output_filename);
107 filename outname(output_filename);
108 if (outname.exists()) {
109 non_continuable_error(class_name(), func, astring("the output file ")
110 + output_filename + " already exists. It would be over-written if "
114 int ret = _categories.read_csv_file(input_filename);
117 ret = write_new_marks(output_filename);
123 int marks_sorter::write_new_marks(const astring &output_filename)
125 FUNCDEF("write_new_marks");
126 // open the output file for streaming out the new marks file.
127 filename outname(output_filename);
128 byte_filer output_file(output_filename, "w");
129 if (!output_file.good())
130 non_continuable_error(class_name(), func, "the output file could not be opened");
132 bool just_had_return = false; // did we just see a carriage return?
133 bool first_line = true; // is this the first line to be emitted?
135 // traverse the tree in prefix order.
136 tree::iterator itty = _categories.access_root().start(tree::prefix);
137 tree *curr = NULL_POINTER; // the current node.
139 while ( (curr = itty.next()) ) {
140 inner_mark_tree *nod = (inner_mark_tree *)curr;
141 // set up a category printout for this node.
142 string_array cat_list;
144 cat_list += nod->name();
145 inner_mark_tree *pare = (inner_mark_tree *)nod->parent();
147 astring name_split, nick_split;
148 _categories.break_name(pare->name(), name_split, nick_split);
149 if (!nick_split) cat_list += name_split;
150 else cat_list += nick_split;
155 // create a text line to send to the output file.
157 list_parsing::create_csv_line(cat_list, tmp);
159 if (!just_had_return && !first_line) {
160 // generate a blank line before the category name.
161 output_file.write(parser_bits::platform_eol_to_chars());
164 // reset the flags after we've checked them.
165 just_had_return = false;
168 output_file.write(tmp);
169 // write the actual category definition.
171 // print the links for all of the ones stored at this node.
172 for (int i = 0; i < nod->_links.elements(); i++) {
173 link_record *lin = nod->_links.borrow(i);
176 astring descrip = lin->_description;
177 if (descrip.contains("http:")) {
178 // we'll clean the html formatting out that we added earlier.
179 int indy = descrip.find('"');
180 if (non_negative(indy)) {
181 descrip.zap(0, indy);
182 indy = descrip.find('"');
183 if (non_negative(indy)) descrip.zap(indy, descrip.end());
185 descrip = astring(" ") + descrip;
186 // add a little spacing.
189 output_file.write(astring("#") + descrip + "\n");
190 just_had_return = false;
192 // this line's totally blank, so we'll generate a blank line.
193 // we don't want to put in more than one blank though, so we check
194 // whether we did this recently.
195 if (!just_had_return) {
196 output_file.write(parser_bits::platform_eol_to_chars());
197 just_had_return = true; // set our flag for a carriage return.
201 // should be a real link.
204 lnks += lin->_description;
205 // use just the nickname for the parent, if there is a nick.
208 _categories.break_name(nod->name(), name_split, nick_split);
209 if (!nick_split) lnks += nod->name();
210 else lnks += nick_split;
212 list_parsing::create_csv_line(lnks, tmp);
214 output_file.write(tmp);
215 just_had_return = false;
222 BASE_LOG(a_sprintf("wrote %d links in %d categories.",
223 _categories.link_count(), _categories.category_count()));
229 ////////////////////////////////////////////////////////////////////////////
231 HOOPLE_MAIN(marks_sorter, )