Added --inventory, --door-warning to chem_db.py's command-line interface.
authorW. Trevor King <wking@drexel.edu>
Thu, 30 Apr 2009 13:23:32 +0000 (09:23 -0400)
committerW. Trevor King <wking@drexel.edu>
Thu, 30 Apr 2009 13:23:32 +0000 (09:23 -0400)
Also their associated options --valid-record, --sort-field, and
--pdf-title.  The door_warning format doesn't have an obvious title
location, so I need to think about that some more...

Also added --audit, to check for troublesome entries.

DEPENDENCIES
chem_db.py
docs/README
docs/inventory_template.tex

index c71e2c50e97be07303ec72cb6481b1a5cdbb1936..b03675b6fb58dc92f3c0b67cc5499939763867c8 100644 (file)
@@ -18,6 +18,7 @@ stat
 string
 sys
 time
+types
 
 
 EXTERNAL MODULES
index cb9af640f26e6dfecf109a10b7e0b3aecd4c0cbd..7d74ca7e5519d12ea73080ca5f7522fd476cf06a 100755 (executable)
@@ -9,6 +9,7 @@ import re
 import os
 import os.path
 import time
+import types
 
 def valid_CASno(cas_string, debug=False):
     """
@@ -71,11 +72,11 @@ class MSDS_manager (object) :
         else :
             os.mkdir(self.dir)
     def basepath(self, id) :
-        assert type(id) == type(1), 'id must be an integer, not %s (%s)' \
+        assert type(id) == types.IntType, 'id must be an integer, not %s (%s)' \
                                     % (type(id), str(id))
         return os.path.join(self.dir, "%d" % id)
     def local_basepath(self, id) : # for symbolic links
-        assert type(id) == type(1), 'id must be an integer, not %s (%s)' \
+        assert type(id) == types.IntType, 'id must be an integer, not %s (%s)' \
                                     % (type(id), str(id))
         return "./%d" % id
     def MIME_ext(self, mime) :
@@ -171,13 +172,21 @@ class docgen (object) :
         path = os.path.join('./docs/', target_file)
         os.system('cp ./docs/main.pdf %s' % path)
         return path
-    def inventory(self, namewidth='a') :
-        "Create a pdf list of all currently owned chemicals."
+    def inventory(self, title=None,
+                  namewidth='a', sort_field='db_id',
+                  valid_record=lambda r: r['Disposed'] == '') :
+        """Create a pdf list of all maching chemicals.  The default is to
+        match all currently owned chemicals.  Matching chemicals can be sorted
+        by any field (defaults to 'ID')."""
+        if title == None:
+            title == 'Inventory'
         pp = db_pretty_printer(self.db)
         active_ids = []
         for record in self.db.records() :
-            if record['Disposed'] == '' : # get ids for chemicals we still have
+            if valid_record(record) : # get ids for matching chemicals
                 active_ids.append(record['db_id'])
+        active_ids.sort(cmp=lambda a,b: cmp(self.db.record(a)[sort_field],
+                                            self.db.record(b)[sort_field]))
         active_fields = ['ID', 'Name', 'Amount',
                          'H', 'F', 'R', 'O', 'M', 'C', 'T']
         width = {}
@@ -210,6 +219,7 @@ class docgen (object) :
                          self._latex_safe(record['C']),
                          self._latex_safe(record['T']))
         string += "\\end{longtable}\n"
+        print >> file('./docs/inventory_title.tex', 'w'), title
         print >> file('./docs/inventory_data.tex', 'w'), string
         ## alter main.tex to point to the inventory template.
         self._set_main_target('inventory_template')
@@ -344,44 +354,65 @@ def close_IOfiles(ifilename=None, ifile=stdin,
         ofile.close()
 
 
-if __name__ == "__main__" :
+if __name__ == '__main__' :
     from optparse import OptionParser
 
-    parser = OptionParser(usage="usage: %prog [options]", version="%prog 0.1")
+    parser = OptionParser(usage='usage: %prog [options]', version='%prog 0.1')
 
-    parser.add_option('-f', '--input-file', dest="ifilename",
-                      help="Read input from FILE (default stdin)",
-                      type='string', metavar="FILE")
-    parser.add_option('-o', '--output-file', dest="ofilename",
-                      help="Write output to FILE (default stdout)",
-                      type='string', metavar="FILE")
-    parser.add_option('-d', '--delimiter', dest="FS", # field seperator
+    parser.add_option('-f', '--input-file', dest='ifilename',
+                      help='Read input from FILE (default stdin)',
+                      type='string', metavar='FILE')
+    parser.add_option('-o', '--output-file', dest='ofilename',
+                      help='Write output to FILE (default stdout)',
+                      type='string', metavar='FILE')
+    parser.add_option('-d', '--delimiter', dest='FS', # field seperator
                       help="Set field delimiter (default '%default')",
-                      type='string', metavar="DELIM", default='\t')
-    parser.add_option('-p', '--print-fields', dest="print_fields",
-                      help="Only print certain fields (e.g. 0,3,4,2)",
-                      type='string', metavar="FIELDS")
-    parser.add_option('-r', '--print-records', dest="print_records",
-                      help="Only print certain records (e.g. 0:3)",
-                      type='string', metavar="RECORDS")
-    parser.add_option('-w', '--column-width', dest="width",
-                      help="Set column width for short-format output.",
-                      type='string', metavar="WIDTH")
-    parser.add_option('-L', '--long-format', dest="long_format",
-                      help="Print long format (several lines per record)",
+                      type='string', metavar='DELIM', default='\t')
+    parser.add_option('-p', '--print-fields', dest='print_fields',
+                      help='Only print certain fields (e.g. 0,3,4,2)',
+                      type='string', metavar='FIELDS')
+    parser.add_option('-r', '--print-records', dest='print_records',
+                      help='Only print certain records (e.g. 0:3)',
+                      type='string', metavar='RECORDS')
+    parser.add_option('-w', '--column-width', dest='width',
+                      help='Set column width for short-format output.',
+                      type='string', metavar='WIDTH')
+    parser.add_option('-L', '--long-format', dest='long_format',
+                      help='Print long format (several lines per record)',
                       action='store_true', default=False)
-    parser.add_option('-l', '--short-format', dest="long_format",
-                      help="Print short format (default) (one lines per record)",
+    parser.add_option('-l', '--short-format', dest='long_format',
+                      help='Print short format (default) (one lines per record)',
                       action='store_false', default=False)
-    parser.add_option('-t', '--test', dest="test",
-                      help="Run docutils tests on db.py",
-                      action="store_true", default=False)
-    parser.add_option('-V', '--validate', dest="validate",
-                      help="Validate CAS#s (no other output)",
-                      action="store_true", default=False)
-    parser.add_option('-v', '--verbose', dest="verbose",
-                      help="Print lots of debugging information",
-                      action="store_true", default=False)
+    parser.add_option('--valid-record', dest='valid_record',
+                      help="Select fields where True == lambda r : eval(EXPRESSION).  default '%default'",
+                      type='string', metavar='EXPRESSION', default="r['Disposed'] == ''")
+    parser.add_option('--sort-field', dest='sort_field',
+                      help="Sort matching records by FIELD (defauly '%default')",
+                      type='string', metavar='FIELD', default='db_id')
+    parser.add_option('--pdf-title', dest='pdf_title',
+                      help='Override the default PDF title',
+                      type='string', metavar='TITLE')
+    parser.add_option('--inventory', dest='inventory',
+                      help='Output a PDF inventory of matching records',
+                      action='store_true', default=False)
+    parser.add_option('--door-warning', dest='door_warning',
+                      help='Output a PDF door warning of matching records',
+                      action='store_true', default=False)
+    parser.add_option('-t', '--test', dest='test',
+                      help='Run docutils tests on db.py',
+                      action='store_true', default=False)
+    parser.add_option('--list-locations', dest='locations',
+                      help='List all currently used locations (no other output)',
+                      action='store_true', default=False)
+    parser.add_option('-V', '--validate', dest='validate',
+                      help='Validate CAS#s (no other output)',
+                      action='store_true', default=False)
+    parser.add_option('-A', '--audit', dest='audit',
+                      help='Search for troublesome entries (no other output)',
+                      action='store_true', default=False)
+    parser.add_option('-v', '--verbose', dest='verbose',
+                      help='Print lots of debugging information',
+                      action='store_true', default=False)
 
     (options, args) = parser.parse_args()
     parser.destroy()
@@ -391,6 +422,19 @@ if __name__ == "__main__" :
 
     if options.test :
         _test()
+    elif options.locations :
+        db = text_db(filename=None)
+        pp = db_pretty_printer(db)
+
+        # read in and parse the file
+        db._parse(ifile.read())
+
+        locations = []
+        for record in db.records():
+            if len(record['Location']) > 0 and record['Location'] not in locations:
+                locations.append(record['Location'])
+        locations.sort()
+        print >> ofile, '\n'.join(locations)
     elif options.validate :
         db = text_db(filename=None)
         pp = db_pretty_printer(db)
@@ -416,7 +460,52 @@ if __name__ == "__main__" :
             if not valid :
                 print >> ofile, "in record %s: %s" % (record['ID'], record['Name'])
                 #pp.full_record_string(record)
+    elif options.audit :
+        db = text_db(filename=None)
+        pp = db_pretty_printer(db)
+
+        # read in and parse the file
+        db._parse(ifile.read())
+
+        for record in db.records():
+            # check for extra spaces
+            for key,value in record.items():
+                if type(value) in types.StringTypes and value.strip() != value:
+                    print >> ofile, "Extra whitespace for %s - %s field %s : '%s'" % (record['ID'], record['Name'], key, value)
+            # make sure we know the location of all current chemicals 
+            if len(record['Disposed']) == 0 and len(record['Location']) == 0:
+                print >> ofile, "Misplaced record: %s - %s" % (record['ID'], record['Name'])
+    elif options.inventory:
+        db = text_db(filename=None)
+        pp = db_pretty_printer(db)
 
+        # read in and parse the file
+        db._parse(ifile.read())
+        
+        dgen = docgen(db)
+        def valid_record(r) :
+            return eval(options.valid_record,  # expression
+                        {'__builtins__':None}, # globals
+                        {'r':r})               # locals
+        path = dgen.inventory(title=options.pdf_title,
+                              namewidth=40,
+                              sort_field=options.sort_field,
+                              valid_record=valid_record)
+        print >> ofile, '\n', path
+    elif options.door_warning:
+        db = text_db(filename=None)
+        pp = db_pretty_printer(db)
+
+        # read in and parse the file
+        db._parse(ifile.read())
+        
+        dgen = docgen(db)
+        def valid_record(r) :
+            return eval(options.valid_record,  # expression
+                        {'__builtins__':None}, # globals
+                        {'r':r})               # locals
+        path = dgen.door_warning(valid_record=valid_record)
+        print >> ofile, '\n', path
     else :
         db = text_db(filename=None)
 
@@ -428,10 +517,9 @@ if __name__ == "__main__" :
                 string = pp.full_record_string_id(id)
         else :
             # pythonize the width option
-            if (options.width != None
-                and options.width != 'a'
-                and len(options.width.split(':')) == 1
-                ) :
+            if options.width == None or options.width == 'a':
+                width = options.width
+            elif len(options.width.split(':')) == 1 :
                 width = int(options.width)
             elif len(options.width.split(':')) > 1 :
                 width = {}
index 338876adbb951ffa932883b317862b20f22a54fc..b4df7b0ab3167a8c7177e268c7815d71421afa76 100644 (file)
@@ -5,3 +5,6 @@ documents from the data.
 The `mp' directory contains metapost source and assorted
 infrastructure for building and previewing the graphics.
 
+Note that due to my wimpy Makefile rules, you may need to make a door
+warning before making an inventory, in order to avoid
+  make: *** No rule to make target `mp/NFPA.mp', needed by `mp/NFPA.1'.  Stop.
index 77f5e77a73dda13ef0dd0250bdd69a511b42a856..c856a194f6c5f6ba8fa9eaefe8ca662c7e5e8eeb 100644 (file)
@@ -42,7 +42,7 @@
 \begin{document}
 
 \begin{center}
-{\headfont Inventory}\\
+{\headfont \input{inventory_title}}\\
 \contfont
 Generated \today\ by chem\_db.py\\
 \vskip 10pt