first check-in of feisty meow codebase. many things broken still due to recent
[feisty_meow.git] / core / tools / clam_tools / value_tagger.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : value_tagger                                                      *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *  Purpose:                                                                   *
7 *                                                                             *
8 *    Scoots through the entire known code base and builds a list of all the   *
9 *  outcome (and filter) values for that tree.  A manifest of the names is     *
10 *  produced.  Most of the behavior is driven by the ini file whose name is    *
11 *  passed on the command line.                                                *
12 *    Note that the set of items that can be searched for can be specified     *
13 *  in the ini file, although they must follow the format of:                  *
14 *      pattern(name, value, description)                                      *
15 *  where the "pattern" is the search term and the other three items specify   *
16 *  the enumerated value to be marked.                                         *
17 *                                                                             *
18 *******************************************************************************
19 * Copyright (c) 2005-$now By Author.  This program is free software; you can  *
20 * redistribute it and/or modify it under the terms of the GNU General Public  *
21 * License as published by the Free Software Foundation; either version 2 of   *
22 * the License or (at your option) any later version.  This is online at:      *
23 *     http://www.fsf.org/copyleft/gpl.html                                    *
24 * Please send any updates to: fred@gruntose.com                               *
25 \*****************************************************************************/
26
27 #include <algorithms/shell_sort.h>
28 #include <application/application_shell.h>
29 #include <application/command_line.h>
30 #include <application/hoople_main.h>
31 #include <application/windoze_helper.h>
32 #include <basis/environment.h>
33 #include <basis/functions.h>
34 #include <basis/utf_conversion.h>
35 #include <configuration/ini_configurator.h>
36 #include <filesystem/byte_filer.h>
37 #include <filesystem/directory_tree.h>
38 #include <filesystem/filename.h>
39 #include <loggers/combo_logger.h>
40 #include <loggers/critical_events.h>
41 #include <loggers/program_wide_logger.h>
42 #include <structures/set.h>
43 #include <structures/string_array.h>
44 #include <structures/string_table.h>
45 #include <timely/time_stamp.h>
46 #include <textual/parser_bits.h>
47
48 #include <sys/stat.h>
49 #ifdef __WIN32__
50   #include <io.h>
51 #endif
52
53 #undef LOG
54 #define LOG(s) EMERGENCY_LOG(program_wide_logger::get(), astring(s))
55
56 using namespace algorithms;
57 using namespace application;
58 using namespace basis;
59 using namespace configuration;
60 using namespace filesystem;
61 using namespace loggers;
62 using namespace structures;
63 using namespace textual;
64 using namespace timely;
65
66 const int LONGEST_SEPARATION = 128;
67   // the longest we expect a single line of text to be in definition blocks.
68   // if the definition of an outcome or whatever is farther away than this
69   // many characters from a comment start, we will no longer consider the
70   // line to be commented out.  this pretty much will never happen unless it's
71   // intentionally done to break this case.
72
73 const char *SKIP_VALUE_PHRASE = "SKIP_TO_VALUE";
74   // the special phrase we use to indicate that values should jump to
75   // a specific number.
76
77 ////////////////////////////////////////////////////////////////////////////
78
79 // this object records all the data that we gather for the defined items.
80 class item_record
81 {
82 public:
83   astring _name;
84   int _value;
85   astring _description;
86   astring _path;
87   astring _extra_tag;  //!< records special info for links.
88
89   item_record(const astring &name = astring::empty_string(), int value = 999,
90       const astring &description = astring::empty_string(),
91       const astring &path = astring::empty_string(),
92       const astring &extra_tag = astring::empty_string())
93   : _name(name), _value(value), _description(description), _path(path),
94     _extra_tag(extra_tag) {}
95 };
96
97 ////////////////////////////////////////////////////////////////////////////
98
99 class search_record
100 {
101 public:
102   search_record(const astring &search = astring::empty_string(),
103       bool is_link = false, search_record *link = NIL)
104   : _search(search), _no_modify(false), _is_link(is_link), _our_link(link),
105     _current_value(0), _value_increment(1) {}
106
107   // these properties are available for both real or linked records.
108   astring _search;  // our term to search for in the files.
109   bool _no_modify;  // true if values should not be automatically incremented.
110   astring _tag;  // extra information attached to this type.
111   
112   bool is_link() const { return _is_link; }
113     // returns true if this object is leeching off another object for data.
114
115   search_record *our_link() const { return _our_link; }
116     // returns the object that this object is a mere shadow of.
117
118   symbol_table<item_record> &definitions() {
119     if (is_link()) return _our_link->_definitions;
120     else return _definitions;
121   }
122   
123   int &current_value() {
124     if (is_link()) return _our_link->_current_value;
125     else return _current_value;
126   }
127
128   int &value_increment() {
129     if (is_link()) return _our_link->_value_increment;
130     else return _value_increment;
131   }
132
133   int_set &out_of_band() {
134     if (is_link()) return _our_link->_out_of_band;
135     else return _out_of_band;
136   }
137
138 private:
139   bool _is_link;  // true if this object links to another.
140   search_record *_our_link;  // the search we share for our values.
141   symbol_table<item_record> _definitions;
142     // the definitions that we found in the code.
143   int _current_value;  // the next value to use for our term.
144   int _value_increment;
145     // how much to add for each new value, if this is an incrementing search.
146   int_set _out_of_band;
147     // values we've seen that were premature.  we always want to honor this
148     // set, if it exists, but there will be nothing in it if the search has
149     // completely standard non-incrementing type.  this could be varied by
150     // a non-incrementer linking to a standard incrementer.
151 };
152
153 //! a table of terms that we will search for in the code.
154 class active_searches : public symbol_table<search_record>
155 {};
156
157 ////////////////////////////////////////////////////////////////////////////
158
159 // this class provides us a way to easily sort our items based on value.
160
161 class simple_sorter {
162 public:
163   int _index;
164   int _value;
165   simple_sorter(int index = 0, int value = 0) : _index(index), _value(value) {}
166   bool operator < (const simple_sorter &to_compare) const
167     { return _value < to_compare._value; }
168   bool operator == (const simple_sorter &to_compare) const
169     { return _value == to_compare._value; }
170 };
171
172 class sorting_array : public array<simple_sorter> {};
173
174 ////////////////////////////////////////////////////////////////////////////
175
176 class value_tagger : public application_shell
177 {
178 public:
179   value_tagger();
180   virtual ~value_tagger();
181   DEFINE_CLASS_NAME("value_tagger");
182   int execute();
183   int print_instructions_and_exit();
184
185   bool process_tree(const astring &path);
186     // called on each directory hierarchy that we need to process.
187
188   bool process_file(const astring &path);
189     // examines the file specified to see if it matches our needs.
190
191   bool parse_define(const astring &scanning, int indy, astring &name,
192           int &value, astring &description, int &num_start, int &num_end);
193     // processes the string in "scanning" to find parentheses surrounding
194     // the "name", "value" and "description".  the "description" field may
195     // occupy multiple lines, so all are gathered together to form one
196     // unbroken string.  the "num_start" and "num_end" specify where the
197     // numeric value was found, in case it needs to be patched.
198
199 private:
200   ini_configurator *_ini;  // the configuration for what we'll scan.
201   string_table _dirs;  // the list of directories.
202   string_table _dirs_seen;  // full list of already processed directories.
203   astring _manifest_filename;  // the name of the manifest we'll create.
204   byte_filer _manifest;  // the actual file we're building.
205   active_searches _search_list;  // tracks our progress in scanning files.
206   int_array _search_ordering;
207     // lists the terms in the order they should be applied.  initially this
208     // carries the first pass items, but later will be reset for second pass.
209   int_array _postponed_searches;
210     // lists the searches that must wait until the main search is done.
211   string_table _modified_files;  // the list of files that we touched.
212 };
213
214 ////////////////////////////////////////////////////////////////////////////
215
216 value_tagger::value_tagger()
217 : application_shell(),
218   _ini(NIL),
219   _dirs_seen(10)
220 {
221 }
222
223 value_tagger::~value_tagger()
224 {
225   WHACK(_ini);
226 }
227
228 int value_tagger::print_instructions_and_exit()
229 {
230   LOG(a_sprintf("%s usage:", filename(_global_argv[0]).basename().raw().s()));
231   LOG("");
232   LOG("\
233 This utility scans a code base for outcome and filter definitions.  It will\n\
234 only scan the header files (*.h) found in the directories specified.  The\n\
235 single parameter is expected to be an INI filename that contains the scanning\n\
236 configuration.  The INI file should be formatted like this (where the $HOME\n\
237 can be any variable substitution from the environment):");
238   LOG("");
239   LOG("\
240 [manifest]\n\
241 output=$HOME/manifest.txt\n\
242 \n\
243 [searches]\n\
244 DEFINE_OUTCOME=1\n\
245 DEFINE_FILTER=1\n\
246 \n\
247 [directories]\n\
248 $HOME/source/lib_src/library/basis\n\
249 $HOME/source/lib_src/library\n\
250 $HOME/source/lib_src/communication/sockets\n\
251 $HOME/source/lib_src/communication\n\
252 $HOME/source/lib_src\n\
253 $HOME/source/app_src\n\
254 $HOME/source/test_src\n\
255 \n\
256 [DEFINE_OUTCOME]\n\
257 first=0\n\
258 increment=-1\n\
259 \n\
260 [DEFINE_FILTER]\n\
261 first=-1\n\
262 increment=1\n\
263 no_modify=1\n\
264 \n\
265 [DEFINE_API_OUTCOME]\n\
266 no_modify=1\n\
267 link=DEFINE_OUTCOME\n\
268 tag=API\n\
269 \n\
270   The \"first\" field defines the starting value that should be assigned to\n\
271 items.\n\
272   The \"increment\" field specifies what to add to a value for the next item.\n\
273   The optional \"no_modify\" flag means that the values should not be auto-\n\
274 incremented; their current value will be used.\n\
275   The optional \"link\" field defines this type of item as using the current\n\
276 values for another type of item.  In this case, API_OUTCOME will use the\n\
277 values for OUTCOME to share its integer space, but API_OUTCOME is not auto-\n\
278 incremented even though OUTCOME is.  This causes the values for OUTCOME and\n\
279 API_OUTCOME to be checked for uniqueness together, but only OUTCOME will be\n\
280 auto-incremented.  Note that only one level of linking is supported currently.\n\
281   The optional \"tag\" can be used to distinguish the entries for a particular\n\
282 search type if needed.  This is most helpful for links, so that they can be\n\
283 distinguished from their base type.\n\
284 \n\
285 ");
286
287   return 23;
288 }
289
290 astring header_string(const astring &build_number)
291 {
292   return a_sprintf("\
293 #ifndef GENERATED_VALUES_MANIFEST\n\
294 #define GENERATED_VALUES_MANIFEST\n\
295 \n\
296 // This file contains all outcomes and filters for this build.\n\
297 \n\
298 // Generated for build %s on %s\n\
299 \n\
300 ", build_number.s(), time_stamp::notarize(true).s());
301 }
302
303 astring footer_string(const byte_array &full_config_file)
304 {
305   return a_sprintf("\n\
306 // End of definitions.\n\
307 \n\
308 \n\
309 // The following is the full configuration for this build:\n\
310 \n\
311 /*\n\
312 \n\
313 %s\n\
314 */\n\
315 \n\
316 \n\
317 #endif // outer guard.\n\
318 ", (char *)full_config_file.observe());
319 }
320
321 int value_tagger::execute()
322 {
323   FUNCDEF("execute");
324   if (_global_argc < 2) {
325     return print_instructions_and_exit();
326   }
327
328   log(time_stamp::notarize(true) + "value_tagger started.", basis::ALWAYS_PRINT);
329
330   astring test_repository = environment::get("REPOSITORY_DIR");
331   if (!test_repository) {
332     astring msg = "\
333 There is a problem with a required build precondition.  The following\r\n\
334 variables must be set before the build is run:\r\n\
335 \r\n\
336   REPOSITORY_DIR    This should point at the root of the build tree.\r\n\
337 \r\n\
338 There are also a few variables only required for CLAM-based compilation:\r\n\
339 \r\n\
340   MAKEFLAGS         This should be set to \"-I $REPOSITORY_DIR/clam\".\r\n\
341 \r\n\
342 Note that on Win32 platforms, these should be set in the System or User\r\n\
343 variables before running a build.\r\n";
344 #ifdef __WIN32__
345     ::MessageBox(0, to_unicode_temp(msg),
346         to_unicode_temp("Missing Precondition"), MB_ICONWARNING|MB_OK);
347 #endif
348     non_continuable_error(class_name(), func, msg);
349   }
350
351   astring ini_file = _global_argv[1];  // the name of our ini file.
352   _ini = new ini_configurator(ini_file, ini_configurator::RETURN_ONLY);
353
354   // read the name of the manifest file to create.
355   _manifest_filename = _ini->load("manifest", "output", "");
356   if (!_manifest_filename) {
357     non_continuable_error(class_name(), ini_file, "The 'output' file entry is missing");
358   }
359   _manifest_filename = parser_bits::substitute_env_vars(_manifest_filename);
360
361   LOG(astring("Sending Manifest to ") + _manifest_filename);
362   LOG("");
363
364   filename(_manifest_filename).unlink();
365     // clean out the manifest ahead of time.
366
367   // read the list of directories to scan for code.
368   string_table temp_dirs;
369   bool read_dirs = _ini->get_section("directories", temp_dirs);
370   if (!read_dirs || !temp_dirs.symbols()) {
371     non_continuable_error(class_name(), ini_file,
372         "The 'directories' section is missing");
373   }
374   for (int i = 0; i < temp_dirs.symbols(); i++) {
375 //log(astring("curr is ") + current);
376     astring current = parser_bits::substitute_env_vars(temp_dirs.name(i));
377     _dirs.add(current, "");
378   }
379
380   LOG(astring("Directories to scan..."));
381   LOG(_dirs.text_form());
382
383   astring rdir = environment::get("REPOSITORY_DIR");
384   astring fname;
385   astring parmfile = environment::get("BUILD_PARAMETER_FILE");
386   if (parmfile.t()) fname = parmfile;
387   else fname = rdir + "/build.ini";
388
389   // read the list of search patterns.
390   string_table searches;
391   bool read_searches = _ini->get_section("searches", searches);
392   if (!read_searches || !searches.symbols()) {
393     non_continuable_error(class_name(), ini_file,
394         "The 'searches' section is missing");
395   }
396
397   LOG("Searching for...");
398   LOG(searches.text_form());
399
400   // now make sure that we get the configuration for each type of value.
401   for (int i = 0; i < searches.symbols(); i++) {
402     const astring &curr_name = searches.name(i);
403
404     search_record *check_search = _search_list.find(curr_name);
405     if (check_search) {
406       non_continuable_error(class_name(), ini_file,
407           astring("section ") + curr_name + " is being defined twice");
408     }
409
410     {
411       // check for whether this section is linked to another or not.
412       astring linked = _ini->load(curr_name, "link", "");
413       search_record *our_link_found = NIL;
414       if (linked.t()) {
415         // we found that this should be linked to another item.
416         our_link_found = _search_list.find(linked);
417         if (!our_link_found) {
418           non_continuable_error(class_name(), ini_file,
419               astring("linked section ") + curr_name + " is linked to missing "
420                   "section " + linked);
421         }
422         search_record new_guy(curr_name, true, our_link_found);
423         _search_list.add(curr_name, new_guy);
424       } else {
425         // this section is a stand-alone section.
426         search_record new_guy(curr_name);
427         _search_list.add(curr_name, new_guy);
428       }
429     }
430
431     // find our new search cabinet again so we can use it.
432     search_record *curr_search = _search_list.find(curr_name);
433     if (!curr_search) {
434       non_continuable_error(class_name(), ini_file,
435           astring("section ") + curr_name + " is missing from table "
436               "after addition; logic error");
437     }
438
439     // specify some defaults first.
440     int start = 0;
441     int increm = 1;
442     if (!curr_search->is_link()) {
443       // a linked object doesn't get to specify starting value or increment.
444       start = _ini->load(curr_name, "first", start);
445       curr_search->current_value() = start;
446       increm = _ini->load(curr_name, "increment", increm);
447       curr_search->value_increment() = increm;
448     } else {
449       start = curr_search->our_link()->current_value();
450       increm = curr_search->our_link()->value_increment();
451     }
452
453     int no_modify = _ini->load(curr_name, "no_modify", 0);
454     if (no_modify) {
455       curr_search->_no_modify = true;
456     }
457
458     astring tag = _ini->load(curr_name, "tag", "");
459     if (tag.t()) {
460       curr_search->_tag = tag;
461     }
462
463     a_sprintf to_show("%s: no_modify=%s", curr_name.s(),
464          no_modify? "true" : "false");
465
466     if (curr_search->is_link()) {
467       // links show who they're hooked to.
468       to_show += astring(" link=") + curr_search->our_link()->_search;
469     } else {
470       // non-links get to show off their start value and increment.
471       to_show += a_sprintf(" start=%d increment=%d", start, increm);
472     }
473     if (tag.t()) {
474       to_show += astring(" tag=") + curr_search->_tag;
475     }
476     LOG(to_show);
477   }
478   LOG("");
479
480   // now gather some info about the build that we can plug into the manifest.
481
482   byte_filer build_file(fname, "r");
483   if (!build_file.good()) {
484     non_continuable_error(class_name(), build_file.filename(),
485         "Could not find the build configuration; is REPOSITORY_DIR set?");
486   }
487   byte_array full_config;
488   build_file.read(full_config, 100000);  // a good chance to be big enough.
489   build_file.close();
490
491 //log("got config info:");
492 //log((char *)full_config.observe());
493
494   astring build_number;
495   ini_configurator temp_ini(fname, configurator::RETURN_ONLY);
496   build_number += temp_ini.load("version", "major", "");
497   build_number += ".";
498   build_number += temp_ini.load("version", "minor", "");
499   build_number += ".";
500   build_number += temp_ini.load("version", "revision", "");
501   build_number += ".";
502   build_number += temp_ini.load("version", "build", "");
503   if (build_number.equal_to("...")) {
504     non_continuable_error(class_name(), build_file.filename(),
505         "Could not read the build number; is build parameter file malformed?");
506   }
507
508 //log(astring("got build num: ") + build_number);
509
510   // now that we know what file to create, write the header blob for it.
511   _manifest.open(_manifest_filename, "wb");
512   if (!_manifest.good()) {
513     non_continuable_error(class_name(), _manifest_filename,
514         "Could not write to the manifest file!");
515   }
516   _manifest.write(header_string(build_number));
517
518   // make sure we have the right ordering for our terms.  items that are
519   // non-modify types must come before the modifying types.
520   for (int i = 0; i < _search_list.symbols(); i++) {
521     search_record &curr_reco = _search_list[i];
522     if (curr_reco._no_modify)
523       _search_ordering += i;
524     else
525       _postponed_searches += i;
526   }
527
528   // scan across each directory specified for our first pass.
529   LOG("First pass...");
530   for (int i = 0; i < _dirs.symbols(); i++) {
531     if (_dirs.name(i).begins("#") || _dirs.name(i).begins(";")) continue;  // skip comment.
532     LOG(astring("  Processing: ") + _dirs.name(i));
533     bool ret = process_tree(_dirs.name(i));
534     if (!ret) {
535       LOG(astring("Problem encountered in directory ") + _dirs.name(i));
536     }
537   }
538   LOG("");
539
540   // second pass now.
541   LOG("Second pass...");
542   _search_ordering = _postponed_searches;  // recharge the list for 2nd pass.
543   _dirs_seen.reset();  // drop any directories we saw before.
544   for (int i = 0; i < _dirs.symbols(); i++) {
545     if (_dirs.name(i).begins("#") || _dirs.name(i).begins(";")) continue;  // skip comment.
546     LOG(astring("  Processing: ") + _dirs.name(i));
547     bool ret = process_tree(_dirs.name(i));
548     if (!ret) {
549       LOG(astring("Problem encountered in directory ") + _dirs.name(i));
550     }
551   }
552   LOG("");
553
554   const astring quote = "\"";
555   const astring comma = ",";
556
557   // scoot across all the completed searches and dump results.
558   for (int i = 0; i < _search_list.symbols(); i++) {
559     search_record &curr_reco = _search_list[i];
560     const astring &pattern = curr_reco._search;
561
562     _manifest.write(astring("/* START ") + pattern + "\n");
563     _manifest.write(astring("[") + pattern + "]\n");
564
565     if (!curr_reco.is_link()) {
566       // scoot across all definitions and print them out.
567
568       // do the print out in order, as dictated by the sign of the increment.
569       sorting_array sortie;
570       for (int j = 0; j < curr_reco.definitions().symbols(); j++) {
571         const item_record &rec = curr_reco.definitions().get(j);
572         sortie += simple_sorter(j, rec._value);
573       }
574       shell_sort(sortie.access(), sortie.length(),
575           negative(curr_reco.value_increment()));
576
577       for (int j = 0; j < sortie.length(); j++) {
578         int indy = sortie[j]._index;
579         const item_record &rec = curr_reco.definitions().get(indy);
580         astring to_write = "  ";
581         if (rec._extra_tag.t()) {
582           to_write += astring("(") + rec._extra_tag + ") ";
583         }
584         to_write += quote + rec._name + quote + comma + " ";
585         to_write += quote + a_sprintf("%d", rec._value) + quote + comma + " ";
586         to_write += quote + rec._description + quote + comma + " ";
587         to_write += quote + rec._path + quote;
588         to_write += "\n";
589         _manifest.write(to_write);
590       }
591     } else {
592       // this is just a link.
593       astring to_write = "  Linked to search item ";
594       to_write += curr_reco.our_link()->_search;
595       to_write += "\n";
596       _manifest.write(to_write);
597     }
598
599     _manifest.write(astring("END ") + pattern + " */\n\n");
600   }
601
602   _manifest.write(footer_string(full_config));
603
604   // show all the modified files.
605   if (_modified_files.symbols()) {
606     const int syms = _modified_files.symbols();
607     LOG("Modified Files:");
608     LOG("===============");
609     for (int i = 0; i < syms; i++) {
610       LOG(_modified_files.name(i));
611     }
612   } else {
613     LOG("No files needed modification for generated values.");
614   }
615   LOG("");
616
617   log(time_stamp::notarize(true) + "value_tagger finished.", ALWAYS_PRINT);
618
619   return 0;
620 }
621
622 #define INBO (indy < scanning.length())
623   // a macro that makes length checking less verbose.
624
625 // make sure we drop any spaces in between important bits.
626 #define SKIP_SPACES \
627   while (INBO && parser_bits::white_space(scanning[indy])) indy++;
628
629 // return with a failure but say why it happened.
630 #define FAIL_PARSE(why) { \
631   log(astring("failed to parse the string because ") + why + ".", ALWAYS_PRINT); \
632   return false; \
633 }
634
635 bool value_tagger::parse_define(const astring &scanning, int indy,
636     astring &name, int &value, astring &description, int &num_start,
637     int &num_end)
638 {
639   // prepare our result objects.
640   name = ""; value = -1; description = ""; num_start = -1; num_end = -1;
641
642   SKIP_SPACES;
643
644   // look for starting parenthesis.
645   if (!INBO || (scanning[indy] != '(') )
646     FAIL_PARSE("the first parenthesis is missing");
647
648   indy++;  // skip paren.
649   SKIP_SPACES;
650
651   // find the name of the item being defined.
652   while (INBO && (scanning[indy] != ',') ) {
653     name += scanning[indy];
654     indy++;
655   }
656
657   indy++;  // skip the comma.
658   SKIP_SPACES;
659
660   astring num_string;
661   num_start = indy;
662   while (INBO && parser_bits::is_numeric(scanning[indy])) {
663     num_string += scanning[indy];
664     indy++;
665   }
666   num_end = indy - 1;
667   value = num_string.convert(0);
668
669   SKIP_SPACES;
670
671   if (!INBO || (scanning[indy] != ',') )
672     FAIL_PARSE("the post-value comma is missing");
673
674   indy++;
675   SKIP_SPACES;
676
677   if (!INBO || (scanning[indy] != '"') )
678     FAIL_PARSE("the opening quote for the description is missing");
679
680   indy++;  // now we should be at raw text.
681
682   // scan through the full description, taking into account that it might
683   // be broken across multiple lines as several quoted bits.
684   bool in_quote = true;  // we're inside a quote now.
685   while (INBO && (scanning[indy] != ')') ) {
686     const char curr = scanning[indy];
687 //hmmm: escaped quotes are not currently handled.
688     if (curr == '"') in_quote = !in_quote;  // switch quoting state.
689     else if (in_quote) description += curr;
690     indy++;
691   }
692
693   return scanning[indy] == ')';
694 }
695
696 bool value_tagger::process_file(const astring &path)
697 {
698   byte_filer examining(path, "rb");
699   if (!examining.good()) {
700     log(astring("Error reading file: ") + path, ALWAYS_PRINT);
701     return false;
702   }
703   examining.seek(0, byte_filer::FROM_END);
704   int fsize = int(examining.tell());
705   examining.seek(0, byte_filer::FROM_START);
706
707   astring contents('\0', fsize + 20);
708   int bytes_read = examining.read((abyte *)contents.access(), fsize);
709     // read the file directly into a big astring.
710   examining.close();
711   contents[bytes_read] = '\0';
712   contents.shrink();  // drop any extra stuff at end.
713
714   bool modified = false;  // set to true if we need to write the file back.
715
716   // check if the file matches our phrases of interest.
717   bool matched = false;
718   for (int q = 0; q < _search_list.symbols(); q++) {
719     search_record &curr_reco = _search_list[q];
720     if (contents.contains(curr_reco._search)) {
721 //_manifest.write(astring("MATCH-") + curr_pattern + ": " + path + "\n" ); //temp
722       matched = true;
723       break;
724     }
725   }
726
727   if (!matched) return true;
728
729   // now we have verified that there's something interesting in this file.
730   // go through to find the interesting bits.
731
732   // we do this in the search ordering that we established earlier, so we
733   // will tag the values in the proper order.
734   for (int x = 0; x < _search_ordering.length(); x++) {
735     int q = _search_ordering[x];  // get our real index.
736     search_record &curr_reco = _search_list[q];
737     const astring &curr_pattern = curr_reco._search;
738 ///log(astring("now seeking ") + curr_pattern);
739     int start_from = 0;  // where searches will start from.
740
741     while (true) {
742       // search forward for next match.
743       int indy = contents.find(curr_pattern, start_from);
744       if (negative(indy)) break;  // no more matches.
745       start_from = indy + 5;  // ensure we'll skip past the last match.
746
747       // make sure our deadly pattern isn't in front; we don't want to
748       // process the actual definition of the macro in question.
749 //log(a_sprintf("indy=%d [indy-1]=%c [indy-2]=%c", indy, contents[indy-1], contents[indy-2]));
750       if ( (indy > 3) && (contents[indy-1] == ' ')
751           && (contents[indy-2] == 'e') ) {
752         int def_indy = contents.find("#define", indy, true);
753 //log(astring("checking ") + curr_pattern + a_sprintf(": defindy %d, ", def_indy) + path + "\n" );
754
755         if (non_negative(def_indy) && (absolute_value(indy - def_indy) < 12) ) {
756           // they're close enough that we probably need to skip this
757           // occurrence of our search term.
758 //_manifest.write(astring("DEMATCH-") + curr_pattern + ": had the #define! " + path + "\n" );
759           continue;
760         }
761       }
762
763       // make sure we don't include commented lines in consideration.
764       int comm_indy = contents.find("//", indy, true);
765       if (non_negative(comm_indy)) {
766 //log("found a comment marker");
767         // we found a comment before the definition, but we're not sure how
768         // far before.
769         if (absolute_value(comm_indy - indy) < LONGEST_SEPARATION) {
770 //log("comment is close enough...");
771           // they could be on the same line...  unless lines are longer than
772           // our constant.
773           bool found_cr = false;
774           for (int q = comm_indy; q < indy; q++) {
775             if (parser_bits::is_eol(contents[q])) {
776               found_cr = true;
777               break;
778             }
779           }
780           if (!found_cr) {
781             // if there's a comment before the definition and no carriage
782             // returns in between, then this is just a comment.
783 //log(astring("DEMATCH-") + curr_pattern + ": had the comment! " + path + "\n" );
784             continue;
785           }
786         }
787       }
788
789       // now we are pretty sure this is a righteous definition of an outcome,
790       // and not the definition of the macro itself.
791       int value, num_start, num_end;
792       astring name, description;
793       bool found_it = parse_define(contents, indy + curr_pattern.length(),
794           name, value, description, num_start, num_end);
795       if (!found_it) {
796         log(astring("there was a problem parsing ") + curr_pattern + " in " + path, ALWAYS_PRINT);
797         continue;
798       }
799
800       // handle the special keyword for changing the value.  this is useful
801       // if you want a set of outcomes to start at a specific range.
802       if (name.equal_to(SKIP_VALUE_PHRASE)) {
803         LOG(astring("\tSkipping value for ") + curr_pattern
804             + a_sprintf(" to %d because of request in\n\t", value) + path);
805         curr_reco.current_value() = value;
806       }
807       while (true) {
808         // make sure that the current value is not already in use.
809         if (!curr_reco.out_of_band().member(curr_reco.current_value()))
810           break;
811         // if we had a match above, we need to adjust the current value.
812         curr_reco.current_value() += curr_reco.value_increment();
813       }
814       if (name.equal_to(SKIP_VALUE_PHRASE)) {
815         continue;  // keep going now that we vetted the current value.
816       }
817
818 //must catch some conditions here for values:
819 //  for incrementing types, we can always just try to use the next value
820 //  once we know it wasn't already defined out of band?
821 //  for non-incrementing types, we need to ensure we haven't already seen
822 //  the thing.  do we just always add a value seen to out of band?
823 //  for mixed types, the incrementing side needs to not reuse out of band
824 //  values.  
825
826       astring other_place;  // the other place it was defined.
827       if (curr_reco.out_of_band().member(value) && curr_reco._no_modify) {
828         // this is bad; we have already seen this value elsewhere...
829         for (int x = 0; x < curr_reco.definitions().symbols(); x++) {
830           // see if we can find the previous definition in our list.
831           if (value == curr_reco.definitions()[x]._value)
832             other_place = curr_reco.definitions()[x]._path;
833         }
834         non_continuable_error(class_name(), path,
835             a_sprintf("There is a duplicate value here for %s=%d !  "
836                 "Also defined in %s.", name.s(), value, other_place.s()));
837       }
838
839       // we care sometimes that this value is different than the next
840       // sequential one we'd assign.  if it's a non-modifying type of
841       // search, then we can't change the assigned value anyway--we can
842       // only report the error in re-using a value (above).
843       if (!curr_reco._no_modify) {
844         // check that the defined value matches the next one we'd assign.
845         if (value != curr_reco.current_value()) {
846           // patch the value with the appropriate one we've been tracking.
847           modified = true;
848           value = curr_reco.current_value();
849           contents.zap(num_start, num_end);  // remove old fusty value.
850           contents.insert(num_start, a_sprintf("%d", value));
851           _modified_files.add(path, "");
852         }
853         // move the current value up (or down).
854         curr_reco.current_value() += curr_reco.value_increment();
855       } else {
856         // non-modifying type of value here.
857 //anything to do?
858       }
859
860       curr_reco.out_of_band() += value;
861         // we've vetted the value, and now we're definitely using it.
862
863       // make sure they aren't trying to reuse the name for this item.
864       item_record rec;
865       bool found_name = false;  // we don't want to find name already there.
866       if (curr_reco.definitions().find(name)) {
867         rec = *curr_reco.definitions().find(name);
868         found_name = true;
869       }
870       if (found_name) {
871         // this is bad.  this means we are not unique.  remove the manifest
872         // file due to this error.
873         _manifest.close();  // close the file since we want to whack it.
874         filename(_manifest_filename).unlink();
875         non_continuable_error(class_name(), path,
876             a_sprintf("There is a duplicate name here (%s)!  "
877                 "Also defined in %s.", name.s(), rec._path.s()));
878       }
879
880       // record the definition in the appropriate table.
881       curr_reco.definitions().add(name, item_record(name, value,
882           description, path, curr_reco._tag));
883
884 //log(curr_pattern + a_sprintf(": name=%s value=%d desc=[%s]\n", name.s(), value, description.s()));
885
886     }
887   }
888
889   if (modified) {
890     // rewrite the file, since we modified its contents.
891     bool chmod_result = filename(path).chmod(filename::ALLOW_BOTH,
892         filename::USER_RIGHTS);
893 /*
894     int chmod_value;
895 #ifdef __UNIX__
896     chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
897 #elif defined(__WIN32__)
898     chmod_value = _S_IREAD | _S_IWRITE;
899 #else
900     //unknown.  let's try unix...
901     chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
902 #endif
903     int chmod_result = chmod(path.s(), chmod_value);
904 */
905     if (!chmod_result) {
906       log(astring("there was a problem changing permissions on ") + path
907           + "; writing the new version might fail.", ALWAYS_PRINT);
908     }
909
910     byte_filer rewriting(path, "wb");
911     rewriting.write(contents);
912     rewriting.close();
913   }
914
915   return true;
916 }
917
918 bool value_tagger::process_tree(const astring &path)
919 {
920   directory_tree dir(path, "*.h");
921   if (!dir.good()) return false;
922
923   dir_tree_iterator *ted = dir.start(directory_tree::prefix);
924     // create our iterator to perform a prefix traversal.
925
926   filename curr_dir;  // the current path the iterator is at.
927   string_array files;  // the filenames held at the iterator.
928
929   while (directory_tree::current(*ted, curr_dir, files)) {
930     // we have a good directory to process.
931
932     // omit any subdirectories that exactly match directories we've already
933     // scanned.  necessary to avoid redoing whole areas.
934     if (!_dirs_seen.find(curr_dir)) {
935       // deal with each matching header file we've found.
936       for (int i = 0; i < files.length(); i++) {
937         bool file_ret = process_file(filename(curr_dir.raw(), files[i]));
938         if (!file_ret) {
939           log(astring("There was an error while processing ") + files[i], ALWAYS_PRINT);
940         }
941       }
942
943       _dirs_seen.add(curr_dir, "");
944     }
945
946     // go to the next place.
947     directory_tree::next(*ted);
948   }
949
950   directory_tree::throw_out(ted);
951   return true;
952 }
953
954 HOOPLE_MAIN(value_tagger, )
955
956 #ifdef __BUILD_STATIC_APPLICATION__
957   // static dependencies found by buildor_gen_deps.sh:
958   #include <application/application_shell.cpp>
959   #include <application/command_line.cpp>
960   #include <basis/astring.cpp>
961   #include <basis/common_outcomes.cpp>
962   #include <basis/environment.cpp>
963   #include <basis/guards.cpp>
964   #include <basis/mutex.cpp>
965   #include <basis/utf_conversion.cpp>
966   #include <configuration/application_configuration.cpp>
967   #include <configuration/configurator.cpp>
968   #include <configuration/ini_configurator.cpp>
969   #include <configuration/ini_parser.cpp>
970   #include <configuration/table_configurator.cpp>
971   #include <configuration/variable_tokenizer.cpp>
972   #include <filesystem/byte_filer.cpp>
973   #include <filesystem/directory.cpp>
974   #include <filesystem/directory_tree.cpp>
975   #include <filesystem/file_info.cpp>
976   #include <filesystem/filename.cpp>
977   #include <filesystem/filename_list.cpp>
978   #include <filesystem/filename_tree.cpp>
979   #include <filesystem/file_time.cpp>
980   #include <filesystem/huge_file.cpp>
981   #include <loggers/combo_logger.cpp>
982   #include <loggers/console_logger.cpp>
983   #include <loggers/critical_events.cpp>
984   #include <loggers/file_logger.cpp>
985   #include <loggers/program_wide_logger.cpp>
986   #include <nodes/node.cpp>
987   #include <nodes/packable_tree.cpp>
988   #include <nodes/path.cpp>
989   #include <nodes/tree.cpp>
990   #include <structures/bit_vector.cpp>
991   #include <structures/checksums.cpp>
992   #include <structures/object_packers.cpp>
993   #include <structures/static_memory_gremlin.cpp>
994   #include <structures/string_hasher.cpp>
995   #include <structures/string_table.cpp>
996   #include <structures/version_record.cpp>
997   #include <textual/byte_formatter.cpp>
998   #include <textual/parser_bits.cpp>
999   #include <textual/string_manipulation.cpp>
1000   #include <timely/earth_time.cpp>
1001   #include <timely/time_stamp.cpp>
1002 #endif // __BUILD_STATIC_APPLICATION__
1003