diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8fe4fa --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.project diff --git a/csvtable.py b/csvtable.py new file mode 100644 index 0000000..6908bf6 --- /dev/null +++ b/csvtable.py @@ -0,0 +1,124 @@ +#!/usr/bin/python3 + +# Convert PFS:First Choice database (.FOL) to CSV format +# David Schmidt 2026 + +try: + import sys +except: + print("Please install the sys module") + print("\tit should be part of the standard python3 distribution") + raise + +try: + import csv +except: + print("Please install the csv module") + print("\tit should be part of the standard python3 distribution") + raise + +try: + import argparse +except: + print("Please install the argparse module") + print("\tit should be part of the standard python3 distribution") + raise + +try: + import signal +except: + print("Please install the signal module") + print("\tit should be part of the standard python3 distribution") + raise + +import first +import common + +def signal_handler(signal, frame): + """Signal handler for clean exit on Ctrl-C""" + sys.exit(0) + +def FOL_to_CSV(fol_handler, csv_file): + """Convert FOL data to CSV format + + Args: + fol_handler: FOL_handler instance with loaded data + csv_file: Open file object for CSV output + """ + # Get field names from form definition + field_names = [f['field'] for f in fol_handler.form['fields']] + + # Create CSV writer + writer = csv.writer(csv_file) + + # Write header row + writer.writerow(field_names) + + # Write data rows + for record in fol_handler.data: + writer.writerow(record) + +def CommandLine(): + """Setup argparser object to process the command line""" + cl = argparse.ArgumentParser( + description="Convert PFS:First Choice v3 database file (.FOL) to CSV format. 2021 by Paul H Alfille" + ) + + cl.add_argument( + "In", + help="Input database file (type .FOL)", + type=argparse.FileType('rb') + ) + + cl.add_argument( + "Out", + help="Output CSV file (default: stdout)", + nargs='?', + type=argparse.FileType('w', encoding='utf-8'), + default=sys.stdout + ) + + cl.add_argument( + "-v", "--verbose", + help="Verbose output", + action="count", + default=0 + ) + + return cl.parse_args() + +if __name__ == '__main__': + """ + First Choice FOL to CSV converter + """ + common.args = CommandLine() + + # Set up keyboard interrupt handler + signal.signal(signal.SIGINT, signal_handler) + + # Read in database (FOL file already open from command line) + try: + dbase_class = first.FOL_handler(common.args.In) + except common.User_Error as error: + print("Error parsing database file: {}".format(error), file=sys.stderr) + sys.exit(1) + except Exception as error: + print("Unexpected error: {}".format(error), file=sys.stderr) + sys.exit(1) + + # Convert to CSV + try: + FOL_to_CSV(dbase_class, common.args.Out) + if common.args.verbose: + print( + "Converted {} records with {} fields".format( + len(dbase_class.data), + len(dbase_class.form['fields']) + ), + file=sys.stderr + ) + except Exception as error: + print("Error writing CSV: {}".format(error), file=sys.stderr) + sys.exit(1) + + sys.exit(0) diff --git a/first.py b/first.py index 2b555e6..b50e280 100644 --- a/first.py +++ b/first.py @@ -160,7 +160,8 @@ def Parse( self, inputstring ): if not self.FitOld(sl): sl = self.wrap(ss) # get rid of fake indent that takes place of field name - sl[0] = ' '+sl[0].lstrip() + if len(sl) > 0: + sl[0] = ' '+sl[0].lstrip() if not self._final: self.PadLines( sl ) @@ -1043,7 +1044,7 @@ def CommandLine(): # Read in databaase (FOL file already open from command line) try: - dbase_class = FOL_handler( args.In, args.Out ) + dbase_class = FOL_handler( common.args.In, common.args.Out ) except common.User_Error as error: print("Error processing file: {}".format(error)) dbase_class = None diff --git a/readme.md b/readme.md index bae8de3..4c8edd3 100644 --- a/readme.md +++ b/readme.md @@ -83,7 +83,8 @@ While extracting data is nice, it would be nice to have a server-based multiuser ## Alternatives * [FirstOut](https://file-convert.com/fout.htm) -- Comercial, extract only * [First2html](http://first2html.sourceforge.net/) -- Open Source, extract only -* The module first.py can as standalone to parse and write a FOL file. It is used by web program for the FOL parsing. +* The module first.py can be used standalone to parse and write a FOL file. It is used by web program for the FOL parsing +* The module csvtable.py can be used standalone to parse a FOL file and write an equivalent .csv file. ## License