updated from windows side and nearly working over there.
[feisty_meow.git] / nucleus / 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   filename _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
233   LOG("\
234 This utility scans a code base for outcome and filter definitions.  It will\n\
235 only scan the header files (*.h) found in the directories specified.  The\n\
236 single parameter is expected to be an INI filename that contains the scanning\n\
237 configuration.  The INI file should be formatted like this (where the $HOME\n\
238 can be any variable substitution from the environment):");
239   LOG("");
240   LOG("\
241 [manifest]\n\
242 output=$HOME/manifest.txt\n\
243 \n\
244 [searches]\n\
245 DEFINE_OUTCOME=1\n\
246 DEFINE_FILTER=1\n\
247 \n\
248 [directories]\n\
249 $HOME/source/lib_src/library/basis\n\
250 $HOME/source/lib_src/library\n\
251 $HOME/source/lib_src/communication/sockets\n\
252 $HOME/source/lib_src/communication\n\
253 $HOME/source/lib_src\n\
254 $HOME/source/app_src\n\
255 $HOME/source/test_src\n\
256 \n\
257 [DEFINE_OUTCOME]\n\
258 first=0\n\
259 increment=-1\n\
260 \n\
261 [DEFINE_FILTER]\n\
262 first=-1\n\
263 increment=1\n\
264 no_modify=1\n\
265 \n\
266 [DEFINE_API_OUTCOME]\n\
267 no_modify=1\n\
268 link=DEFINE_OUTCOME\n\
269 tag=API\n\
270 \n\
271   The \"first\" field defines the starting value that should be assigned to\n\
272 items.\n\
273   The \"increment\" field specifies what to add to a value for the next item.\n\
274   The optional \"no_modify\" flag means that the values should not be auto-\n\
275 incremented; their current value will be used.\n\
276   The optional \"link\" field defines this type of item as using the current\n\
277 values for another type of item.  In this case, API_OUTCOME will use the\n\
278 values for OUTCOME to share its integer space, but API_OUTCOME is not auto-\n\
279 incremented even though OUTCOME is.  This causes the values for OUTCOME and\n\
280 API_OUTCOME to be checked for uniqueness together, but only OUTCOME will be\n\
281 auto-incremented.  Note that only one level of linking is supported currently.\n\
282   The optional \"tag\" can be used to distinguish the entries for a particular\n\
283 search type if needed.  This is most helpful for links, so that they can be\n\
284 distinguished from their base type.\n\
285 \n\
286 ");
287
288   return 23;
289 }
290
291 astring header_string(const astring &build_number)
292 {
293   return a_sprintf("\
294 #ifndef GENERATED_VALUES_MANIFEST\n\
295 #define GENERATED_VALUES_MANIFEST\n\
296 \n\
297 // This file contains all outcomes and filters for this build.\n\
298 \n\
299 // Generated for build %s on %s\n\
300 \n\
301 ", build_number.s(), time_stamp::notarize(true).s());
302 }
303
304 astring footer_string(const byte_array &full_config_file)
305 {
306   return a_sprintf("\n\
307 // End of definitions.\n\
308 \n\
309 \n\
310 // The following is the full configuration for this build:\n\
311 \n\
312 /*\n\
313 \n\
314 %s\n\
315 */\n\
316 \n\
317 \n\
318 #endif // outer guard.\n\
319 ", (char *)full_config_file.observe());
320 }
321
322 int value_tagger::execute()
323 {
324   FUNCDEF("execute");
325   if (_global_argc < 2) {
326     return print_instructions_and_exit();
327   }
328
329   log(time_stamp::notarize(true) + "value_tagger started.", basis::ALWAYS_PRINT);
330
331   astring test_repository = environment::get("FEISTY_MEOW_DIR");
332   if (!test_repository) {
333     astring msg = "\
334 There is a problem with a required build precondition.  The following\r\n\
335 variables must be set before the build is run:\r\n\
336 \r\n\
337   FEISTY_MEOW_DIR    This should point at the root of the build tree.\r\n\
338 \r\n\
339 There are also a few variables only required for CLAM-based compilation:\r\n\
340 \r\n\
341   MAKEFLAGS         This should be set to \"-I $FEISTY_MEOW_DIR/clam\".\r\n\
342 \r\n\
343 Note that on Win32 platforms, these should be set in the System or User\r\n\
344 variables before running a build.\r\n";
345 #ifdef __WIN32__
346     ::MessageBox(0, to_unicode_temp(msg),
347         to_unicode_temp("Missing Precondition"), MB_ICONWARNING|MB_OK);
348 #endif
349     non_continuable_error(class_name(), func, msg);
350   }
351
352   astring ini_file = _global_argv[1];  // the name of our ini file.
353   _ini = new ini_configurator(ini_file, ini_configurator::RETURN_ONLY);
354
355   // read the name of the manifest file to create.
356   _manifest_filename = filename(_ini->load("manifest", "output", ""));
357   if (!_manifest_filename.raw().length()) {
358     non_continuable_error(class_name(), ini_file, "The 'output' file entry is missing");
359   }
360   _manifest_filename = parser_bits::substitute_env_vars(_manifest_filename);
361
362   LOG(astring("Sending Manifest to ") + _manifest_filename);
363   LOG("");
364
365   filename(_manifest_filename).unlink();
366     // clean out the manifest ahead of time.
367
368   // read the list of directories to scan for code.
369   string_table temp_dirs;
370   bool read_dirs = _ini->get_section("directories", temp_dirs);
371   if (!read_dirs || !temp_dirs.symbols()) {
372     non_continuable_error(class_name(), ini_file,
373         "The 'directories' section is missing");
374   }
375   for (int i = 0; i < temp_dirs.symbols(); i++) {
376 //log(astring("curr is ") + current);
377     filename current = filename(parser_bits::substitute_env_vars(temp_dirs.name(i)));
378     _dirs.add(current, "");
379   }
380
381   LOG(astring("Directories to scan..."));
382   LOG(_dirs.text_form());
383
384   astring rdir = environment::get("FEISTY_MEOW_DIR");
385   astring fname;
386   astring parmfile = environment::get("BUILD_PARAMETER_FILE");
387   if (parmfile.t()) fname = parmfile;
388   else fname = rdir + "/build.ini";
389
390   // read the list of search patterns.
391   string_table searches;
392   bool read_searches = _ini->get_section("searches", searches);
393   if (!read_searches || !searches.symbols()) {
394     non_continuable_error(class_name(), ini_file,
395         "The 'searches' section is missing");
396   }
397
398   LOG("Searching for...");
399   LOG(searches.text_form());
400
401   // now make sure that we get the configuration for each type of value.
402   for (int i = 0; i < searches.symbols(); i++) {
403     const astring &curr_name = searches.name(i);
404
405     search_record *check_search = _search_list.find(curr_name);
406     if (check_search) {
407       non_continuable_error(class_name(), ini_file,
408           astring("section ") + curr_name + " is being defined twice");
409     }
410
411     {
412       // check for whether this section is linked to another or not.
413       astring linked = _ini->load(curr_name, "link", "");
414       search_record *our_link_found = NIL;
415       if (linked.t()) {
416         // we found that this should be linked to another item.
417         our_link_found = _search_list.find(linked);
418         if (!our_link_found) {
419           non_continuable_error(class_name(), ini_file,
420               astring("linked section ") + curr_name + " is linked to missing "
421                   "section " + linked);
422         }
423         search_record new_guy(curr_name, true, our_link_found);
424         _search_list.add(curr_name, new_guy);
425       } else {
426         // this section is a stand-alone section.
427         search_record new_guy(curr_name);
428         _search_list.add(curr_name, new_guy);
429       }
430     }
431
432     // find our new search cabinet again so we can use it.
433     search_record *curr_search = _search_list.find(curr_name);
434     if (!curr_search) {
435       non_continuable_error(class_name(), ini_file,
436           astring("section ") + curr_name + " is missing from table "
437               "after addition; logic error");
438     }
439
440     // specify some defaults first.
441     int start = 0;
442     int increm = 1;
443     if (!curr_search->is_link()) {
444       // a linked object doesn't get to specify starting value or increment.
445       start = _ini->load(curr_name, "first", start);
446       curr_search->current_value() = start;
447       increm = _ini->load(curr_name, "increment", increm);
448       curr_search->value_increment() = increm;
449     } else {
450       start = curr_search->our_link()->current_value();
451       increm = curr_search->our_link()->value_increment();
452     }
453
454     int no_modify = _ini->load(curr_name, "no_modify", 0);
455     if (no_modify) {
456       curr_search->_no_modify = true;
457     }
458
459     astring tag = _ini->load(curr_name, "tag", "");
460     if (tag.t()) {
461       curr_search->_tag = tag;
462     }
463
464     a_sprintf to_show("%s: no_modify=%s", curr_name.s(),
465          no_modify? "true" : "false");
466
467     if (curr_search->is_link()) {
468       // links show who they're hooked to.
469       to_show += astring(" link=") + curr_search->our_link()->_search;
470     } else {
471       // non-links get to show off their start value and increment.
472       to_show += a_sprintf(" start=%d increment=%d", start, increm);
473     }
474     if (tag.t()) {
475       to_show += astring(" tag=") + curr_search->_tag;
476     }
477     LOG(to_show);
478   }
479   LOG("");
480
481   // now gather some info about the build that we can plug into the manifest.
482
483   byte_filer build_file(fname, "r");
484   if (!build_file.good()) {
485     non_continuable_error(class_name(), build_file.name(),
486         "Could not find the build configuration; is FEISTY_MEOW_DIR set?");
487   }
488   byte_array full_config;
489   build_file.read(full_config, 100000);  // a good chance to be big enough.
490   build_file.close();
491
492 //log("got config info:");
493 //log((char *)full_config.observe());
494
495   astring build_number;
496   ini_configurator temp_ini(fname, configurator::RETURN_ONLY);
497   build_number += temp_ini.load("version", "major", "");
498   build_number += ".";
499   build_number += temp_ini.load("version", "minor", "");
500   build_number += ".";
501   build_number += temp_ini.load("version", "revision", "");
502   build_number += ".";
503   build_number += temp_ini.load("version", "build", "");
504   if (build_number.equal_to("...")) {
505     non_continuable_error(class_name(), build_file.name(),
506         "Could not read the build number; is build parameter file malformed?");
507   }
508
509 //log(astring("got build num: ") + build_number);
510
511   // now that we know what file to create, write the header blob for it.
512   _manifest.open(_manifest_filename, "wb");
513   if (!_manifest.good()) {
514     non_continuable_error(class_name(), _manifest_filename,
515         "Could not write to the manifest file!");
516   }
517   _manifest.write(header_string(build_number));
518
519   // make sure we have the right ordering for our terms.  items that are
520   // non-modify types must come before the modifying types.
521   for (int i = 0; i < _search_list.symbols(); i++) {
522     search_record &curr_reco = _search_list[i];
523     if (curr_reco._no_modify)
524       _search_ordering += i;
525     else
526       _postponed_searches += i;
527   }
528
529   // scan across each directory specified for our first pass.
530   LOG("First pass...");
531   for (int i = 0; i < _dirs.symbols(); i++) {
532     if (_dirs.name(i).begins("#") || _dirs.name(i).begins(";")) continue;  // skip comment.
533     LOG(astring("  Processing: ") + _dirs.name(i));
534     bool ret = process_tree(_dirs.name(i));
535     if (!ret) {
536       LOG(astring("Problem encountered in directory ") + _dirs.name(i));
537     }
538   }
539   LOG("");
540
541   // second pass now.
542   LOG("Second pass...");
543   _search_ordering = _postponed_searches;  // recharge the list for 2nd pass.
544   _dirs_seen.reset();  // drop any directories we saw before.
545   for (int i = 0; i < _dirs.symbols(); i++) {
546     if (_dirs.name(i).begins("#") || _dirs.name(i).begins(";")) continue;  // skip comment.
547     LOG(astring("  Processing: ") + _dirs.name(i));
548     bool ret = process_tree(_dirs.name(i));
549     if (!ret) {
550       LOG(astring("Problem encountered in directory ") + _dirs.name(i));
551     }
552   }
553   LOG("");
554
555   const astring quote = "\"";
556   const astring comma = ",";
557
558   // scoot across all the completed searches and dump results.
559   for (int i = 0; i < _search_list.symbols(); i++) {
560     search_record &curr_reco = _search_list[i];
561     const astring &pattern = curr_reco._search;
562
563     _manifest.write(astring("/* START ") + pattern + "\n");
564     _manifest.write(astring("[") + pattern + "]\n");
565
566     if (!curr_reco.is_link()) {
567       // scoot across all definitions and print them out.
568
569       // do the print out in order, as dictated by the sign of the increment.
570       sorting_array sortie;
571       for (int j = 0; j < curr_reco.definitions().symbols(); j++) {
572         const item_record &rec = curr_reco.definitions().get(j);
573         sortie += simple_sorter(j, rec._value);
574       }
575       shell_sort(sortie.access(), sortie.length(),
576           negative(curr_reco.value_increment()));
577
578       for (int j = 0; j < sortie.length(); j++) {
579         int indy = sortie[j]._index;
580         const item_record &rec = curr_reco.definitions().get(indy);
581         astring to_write = "  ";
582         if (rec._extra_tag.t()) {
583           to_write += astring("(") + rec._extra_tag + ") ";
584         }
585         to_write += quote + rec._name + quote + comma + " ";
586         to_write += quote + a_sprintf("%d", rec._value) + quote + comma + " ";
587         to_write += quote + rec._description + quote + comma + " ";
588         to_write += quote + rec._path + quote;
589         to_write += "\n";
590         _manifest.write(to_write);
591       }
592     } else {
593       // this is just a link.
594       astring to_write = "  Linked to search item ";
595       to_write += curr_reco.our_link()->_search;
596       to_write += "\n";
597       _manifest.write(to_write);
598     }
599
600     _manifest.write(astring("END ") + pattern + " */\n\n");
601   }
602
603   _manifest.write(footer_string(full_config));
604
605   // show all the modified files.
606   if (_modified_files.symbols()) {
607     const int syms = _modified_files.symbols();
608     LOG("Modified Files:");
609     LOG("===============");
610     for (int i = 0; i < syms; i++) {
611       LOG(_modified_files.name(i));
612     }
613   } else {
614     LOG("No files needed modification for generated values.");
615   }
616   LOG("");
617
618   log(time_stamp::notarize(true) + "value_tagger finished.", ALWAYS_PRINT);
619
620   return 0;
621 }
622
623 #define INBO (indy < scanning.length())
624   // a macro that makes length checking less verbose.
625
626 // make sure we drop any spaces in between important bits.
627 #define SKIP_SPACES \
628   while (INBO && parser_bits::white_space(scanning[indy])) indy++;
629
630 // return with a failure but say why it happened.
631 #define FAIL_PARSE(why) { \
632   log(astring("failed to parse the string because ") + why + ".", ALWAYS_PRINT); \
633   return false; \
634 }
635
636 bool value_tagger::parse_define(const astring &scanning, int indy,
637     astring &name, int &value, astring &description, int &num_start,
638     int &num_end)
639 {
640   // prepare our result objects.
641   name = ""; value = -1; description = ""; num_start = -1; num_end = -1;
642
643   SKIP_SPACES;
644
645   // look for starting parenthesis.
646   if (!INBO || (scanning[indy] != '(') )
647     FAIL_PARSE("the first parenthesis is missing");
648
649   indy++;  // skip paren.
650   SKIP_SPACES;
651
652   // find the name of the item being defined.
653   while (INBO && (scanning[indy] != ',') ) {
654     name += scanning[indy];
655     indy++;
656   }
657
658   indy++;  // skip the comma.
659   SKIP_SPACES;
660
661   astring num_string;
662   num_start = indy;
663   while (INBO && parser_bits::is_numeric(scanning[indy])) {
664     num_string += scanning[indy];
665     indy++;
666   }
667   num_end = indy - 1;
668   value = num_string.convert(0);
669
670   SKIP_SPACES;
671
672   if (!INBO || (scanning[indy] != ',') )
673     FAIL_PARSE("the post-value comma is missing");
674
675   indy++;
676   SKIP_SPACES;
677
678   if (!INBO || (scanning[indy] != '"') )
679     FAIL_PARSE("the opening quote for the description is missing");
680
681   indy++;  // now we should be at raw text.
682
683   // scan through the full description, taking into account that it might
684   // be broken across multiple lines as several quoted bits.
685   bool in_quote = true;  // we're inside a quote now.
686   while (INBO && (scanning[indy] != ')') ) {
687     const char curr = scanning[indy];
688 //hmmm: escaped quotes are not currently handled.
689     if (curr == '"') in_quote = !in_quote;  // switch quoting state.
690     else if (in_quote) description += curr;
691     indy++;
692   }
693
694   return scanning[indy] == ')';
695 }
696
697 bool value_tagger::process_file(const astring &path)
698 {
699   byte_filer examining(path, "rb");
700   if (!examining.good()) {
701     log(astring("Error reading file: ") + path, ALWAYS_PRINT);
702     return false;
703   }
704   examining.seek(0, byte_filer::FROM_END);
705   int fsize = int(examining.tell());
706   examining.seek(0, byte_filer::FROM_START);
707
708   astring contents('\0', fsize + 20);
709   int bytes_read = examining.read((abyte *)contents.access(), fsize);
710     // read the file directly into a big astring.
711   examining.close();
712   contents[bytes_read] = '\0';
713   contents.shrink();  // drop any extra stuff at end.
714
715   bool modified = false;  // set to true if we need to write the file back.
716
717   // check if the file matches our phrases of interest.
718   bool matched = false;
719   for (int q = 0; q < _search_list.symbols(); q++) {
720     search_record &curr_reco = _search_list[q];
721     if (contents.contains(curr_reco._search)) {
722 //_manifest.write(astring("MATCH-") + curr_pattern + ": " + path + "\n" ); //temp
723       matched = true;
724       break;
725     }
726   }
727
728   if (!matched) return true;
729
730   // now we have verified that there's something interesting in this file.
731   // go through to find the interesting bits.
732
733   // we do this in the search ordering that we established earlier, so we
734   // will tag the values in the proper order.
735   for (int x = 0; x < _search_ordering.length(); x++) {
736     int q = _search_ordering[x];  // get our real index.
737     search_record &curr_reco = _search_list[q];
738     const astring &curr_pattern = curr_reco._search;
739 ///log(astring("now seeking ") + curr_pattern);
740     int start_from = 0;  // where searches will start from.
741
742     while (true) {
743       // search forward for next match.
744       int indy = contents.find(curr_pattern, start_from);
745       if (negative(indy)) break;  // no more matches.
746       start_from = indy + 5;  // ensure we'll skip past the last match.
747
748       // make sure our deadly pattern isn't in front; we don't want to
749       // process the actual definition of the macro in question.
750 //log(a_sprintf("indy=%d [indy-1]=%c [indy-2]=%c", indy, contents[indy-1], contents[indy-2]));
751       if ( (indy > 3) && (contents[indy-1] == ' ')
752           && (contents[indy-2] == 'e') ) {
753         int def_indy = contents.find("#define", indy, true);
754 //log(astring("checking ") + curr_pattern + a_sprintf(": defindy %d, ", def_indy) + path + "\n" );
755
756         if (non_negative(def_indy) && (absolute_value(indy - def_indy) < 12) ) {
757           // they're close enough that we probably need to skip this
758           // occurrence of our search term.
759 //_manifest.write(astring("DEMATCH-") + curr_pattern + ": had the #define! " + path + "\n" );
760           continue;
761         }
762       }
763
764       // make sure we don't include commented lines in consideration.
765       int comm_indy = contents.find("//", indy, true);
766       if (non_negative(comm_indy)) {
767 //log("found a comment marker");
768         // we found a comment before the definition, but we're not sure how
769         // far before.
770         if (absolute_value(comm_indy - indy) < LONGEST_SEPARATION) {
771 //log("comment is close enough...");
772           // they could be on the same line...  unless lines are longer than
773           // our constant.
774           bool found_cr = false;
775           for (int q = comm_indy; q < indy; q++) {
776             if (parser_bits::is_eol(contents[q])) {
777               found_cr = true;
778               break;
779             }
780           }
781           if (!found_cr) {
782             // if there's a comment before the definition and no carriage
783             // returns in between, then this is just a comment.
784 //log(astring("DEMATCH-") + curr_pattern + ": had the comment! " + path + "\n" );
785             continue;
786           }
787         }
788       }
789
790       // now we are pretty sure this is a righteous definition of an outcome,
791       // and not the definition of the macro itself.
792       int value, num_start, num_end;
793       astring name, description;
794       bool found_it = parse_define(contents, indy + curr_pattern.length(),
795           name, value, description, num_start, num_end);
796       if (!found_it) {
797         log(astring("there was a problem parsing ") + curr_pattern + " in " + path, ALWAYS_PRINT);
798         continue;
799       }
800
801       // handle the special keyword for changing the value.  this is useful
802       // if you want a set of outcomes to start at a specific range.
803       if (name.equal_to(SKIP_VALUE_PHRASE)) {
804         LOG(astring("\tSkipping value for ") + curr_pattern
805             + a_sprintf(" to %d because of request in\n\t", value) + path);
806         curr_reco.current_value() = value;
807       }
808       while (true) {
809         // make sure that the current value is not already in use.
810         if (!curr_reco.out_of_band().member(curr_reco.current_value()))
811           break;
812         // if we had a match above, we need to adjust the current value.
813         curr_reco.current_value() += curr_reco.value_increment();
814       }
815       if (name.equal_to(SKIP_VALUE_PHRASE)) {
816         continue;  // keep going now that we vetted the current value.
817       }
818
819 //must catch some conditions here for values:
820 //  for incrementing types, we can always just try to use the next value
821 //  once we know it wasn't already defined out of band?
822 //  for non-incrementing types, we need to ensure we haven't already seen
823 //  the thing.  do we just always add a value seen to out of band?
824 //  for mixed types, the incrementing side needs to not reuse out of band
825 //  values.  
826
827       astring other_place;  // the other place it was defined.
828       if (curr_reco.out_of_band().member(value) && curr_reco._no_modify) {
829         // this is bad; we have already seen this value elsewhere...
830         for (int x = 0; x < curr_reco.definitions().symbols(); x++) {
831           // see if we can find the previous definition in our list.
832           if (value == curr_reco.definitions()[x]._value)
833             other_place = curr_reco.definitions()[x]._path;
834         }
835         non_continuable_error(class_name(), path,
836             a_sprintf("There is a duplicate value here for %s=%d !  "
837                 "Also defined in %s.", name.s(), value, other_place.s()));
838       }
839
840       // we care sometimes that this value is different than the next
841       // sequential one we'd assign.  if it's a non-modifying type of
842       // search, then we can't change the assigned value anyway--we can
843       // only report the error in re-using a value (above).
844       if (!curr_reco._no_modify) {
845         // check that the defined value matches the next one we'd assign.
846         if (value != curr_reco.current_value()) {
847           // patch the value with the appropriate one we've been tracking.
848           modified = true;
849           value = curr_reco.current_value();
850           contents.zap(num_start, num_end);  // remove old fusty value.
851           contents.insert(num_start, a_sprintf("%d", value));
852           _modified_files.add(path, "");
853         }
854         // move the current value up (or down).
855         curr_reco.current_value() += curr_reco.value_increment();
856       } else {
857         // non-modifying type of value here.
858 //anything to do?
859       }
860
861       curr_reco.out_of_band() += value;
862         // we've vetted the value, and now we're definitely using it.
863
864       // make sure they aren't trying to reuse the name for this item.
865       item_record rec;
866       bool found_name = false;  // we don't want to find name already there.
867       if (curr_reco.definitions().find(name)) {
868         rec = *curr_reco.definitions().find(name);
869         found_name = true;
870       }
871       if (found_name) {
872         // this is bad.  this means we are not unique.  remove the manifest
873         // file due to this error.
874         _manifest.close();  // close the file since we want to whack it.
875         filename(_manifest_filename).unlink();
876         non_continuable_error(class_name(), path,
877             a_sprintf("There is a duplicate name here (%s)!  "
878                 "Also defined in %s.", name.s(), rec._path.s()));
879       }
880
881       // record the definition in the appropriate table.
882       curr_reco.definitions().add(name, item_record(name, value,
883           description, path, curr_reco._tag));
884
885 //log(curr_pattern + a_sprintf(": name=%s value=%d desc=[%s]\n", name.s(), value, description.s()));
886
887     }
888   }
889
890   if (modified) {
891     // rewrite the file, since we modified its contents.
892     bool chmod_result = filename(path).chmod(filename::ALLOW_BOTH,
893         filename::USER_RIGHTS);
894 /*
895     int chmod_value;
896 #ifdef __UNIX__
897     chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
898 #elif defined(__WIN32__)
899     chmod_value = _S_IREAD | _S_IWRITE;
900 #else
901     //unknown.  let's try unix...
902     chmod_value = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
903 #endif
904     int chmod_result = chmod(path.s(), chmod_value);
905 */
906     if (!chmod_result) {
907       log(astring("there was a problem changing permissions on ") + path
908           + "; writing the new version might fail.", ALWAYS_PRINT);
909     }
910
911     byte_filer rewriting(path, "wb");
912     rewriting.write(contents);
913     rewriting.close();
914   }
915
916   return true;
917 }
918
919 bool value_tagger::process_tree(const astring &path)
920 {
921   directory_tree dir(path, "*.h");
922   if (!dir.good()) return false;
923
924   dir_tree_iterator *ted = dir.start(directory_tree::prefix);
925     // create our iterator to perform a prefix traversal.
926
927   filename curr_dir;  // the current path the iterator is at.
928   string_array files;  // the filenames held at the iterator.
929
930   while (directory_tree::current(*ted, curr_dir, files)) {
931     // we have a good directory to process.
932
933     // omit any subdirectories that exactly match directories we've already
934     // scanned.  necessary to avoid redoing whole areas.
935     if (!_dirs_seen.find(curr_dir)) {
936       // deal with each matching header file we've found.
937       for (int i = 0; i < files.length(); i++) {
938         bool file_ret = process_file(filename(curr_dir.raw(), files[i]));
939         if (!file_ret) {
940           log(astring("There was an error while processing ") + files[i], ALWAYS_PRINT);
941         }
942       }
943
944       _dirs_seen.add(curr_dir, "");
945     }
946
947     // go to the next place.
948     directory_tree::next(*ted);
949   }
950
951   directory_tree::throw_out(ted);
952   return true;
953 }
954
955 HOOPLE_MAIN(value_tagger, )
956
957 #ifdef __BUILD_STATIC_APPLICATION__
958   // static dependencies found by buildor_gen_deps.sh:
959   #include <application/application_shell.cpp>
960   #include <application/command_line.cpp>
961   #include <application/windoze_helper.cpp>
962   #include <basis/astring.cpp>
963   #include <basis/common_outcomes.cpp>
964   #include <basis/environment.cpp>
965   #include <basis/guards.cpp>
966   #include <basis/mutex.cpp>
967   #include <basis/utf_conversion.cpp>
968   #include <configuration/application_configuration.cpp>
969   #include <configuration/configurator.cpp>
970   #include <configuration/ini_configurator.cpp>
971   #include <configuration/ini_parser.cpp>
972   #include <configuration/table_configurator.cpp>
973   #include <configuration/variable_tokenizer.cpp>
974   #include <filesystem/byte_filer.cpp>
975   #include <filesystem/directory.cpp>
976   #include <filesystem/directory_tree.cpp>
977   #include <filesystem/file_info.cpp>
978   #include <filesystem/file_time.cpp>
979   #include <filesystem/filename.cpp>
980   #include <filesystem/filename_list.cpp>
981   #include <filesystem/filename_tree.cpp>
982   #include <filesystem/huge_file.cpp>
983   #include <loggers/combo_logger.cpp>
984   #include <loggers/console_logger.cpp>
985   #include <loggers/critical_events.cpp>
986   #include <loggers/file_logger.cpp>
987   #include <loggers/program_wide_logger.cpp>
988   #include <nodes/node.cpp>
989   #include <nodes/packable_tree.cpp>
990   #include <nodes/path.cpp>
991   #include <nodes/tree.cpp>
992   #include <structures/bit_vector.cpp>
993   #include <structures/checksums.cpp>
994   #include <structures/object_packers.cpp>
995   #include <structures/static_memory_gremlin.cpp>
996   #include <structures/string_hasher.cpp>
997   #include <structures/string_table.cpp>
998   #include <structures/version_record.cpp>
999   #include <textual/byte_formatter.cpp>
1000   #include <textual/parser_bits.cpp>
1001   #include <textual/string_manipulation.cpp>
1002   #include <timely/earth_time.cpp>
1003   #include <timely/time_stamp.cpp>
1004 #endif // __BUILD_STATIC_APPLICATION__
1005