version 1.21, 2008/01/09 14:43:50
|
version 1.35, 2012/02/15 08:41:01
|
Line 6 import logging
|
Line 6 import logging
|
import sys |
import sys |
import types |
import types |
import time |
import time |
|
import re |
|
|
from xml import sax |
from xml import sax |
from amara import saxtools |
from xml.sax.handler import ContentHandler |
|
#from amara import saxtools |
|
|
try: |
try: |
import psycopg2 as psycopg |
import psycopg2 as psycopg |
|
import psycopg2.extensions |
|
# switch to unicode |
|
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) |
psyco = 2 |
psyco = 2 |
except: |
except: |
import psycopg |
import psycopg |
Line 19 except:
|
Line 24 except:
|
|
|
fm_ns = 'http://www.filemaker.com/fmpxmlresult' |
fm_ns = 'http://www.filemaker.com/fmpxmlresult' |
|
|
version_string = "V0.5.1 ROC 9.1.2008" |
version_string = "V0.6.7 ROC 21.6.2011" |
|
|
def unicodify(text, withNone=False): |
def unicodify(text, withNone=False): |
"""decode str (utf-8 or latin-1 representation) into unicode object""" |
"""decode str (utf-8 or latin-1 representation) into unicode object""" |
Line 41 def utf8ify(text, withNone=False):
|
Line 46 def utf8ify(text, withNone=False):
|
return None |
return None |
if not text: |
if not text: |
return "" |
return "" |
if isinstance(text, str): |
if isinstance(text, unicode): |
return text |
|
else: |
|
return text.encode('utf-8') |
return text.encode('utf-8') |
|
else: |
|
return text |
|
|
def getTextFromNode(nodename): |
def getTextFromNode(nodename): |
"""get the cdata content of a node""" |
"""get the cdata content of a node""" |
Line 65 def sql_quote(v):
|
Line 70 def sql_quote(v):
|
v=string.join(string.split(v,dkey),quote_dict[dkey]) |
v=string.join(string.split(v,dkey),quote_dict[dkey]) |
return "'%s'"%v |
return "'%s'"%v |
|
|
|
def sqlName(s, lc=True, more=''): |
|
"""returns restricted ASCII-only version of string""" |
|
if s is None: |
|
return "" |
|
|
|
# remove ' |
|
s = s.replace("'","") |
|
# all else -> "_" |
|
s = re.sub('[^A-Za-z0-9_'+more+']','_',s) |
|
if lc: |
|
return s.lower() |
|
|
|
return s |
|
|
def SimpleSearch(curs,query, args=None, ascii=False): |
def SimpleSearch(curs,query, args=None, ascii=False): |
"""execute sql query and return data""" |
"""execute sql query and return data""" |
#logger.debug("executing: "+query) |
#logger.debug("executing: "+query) |
Line 107 class TableColumn:
|
Line 126 class TableColumn:
|
return self.name |
return self.name |
|
|
|
|
class xml_handler: |
class xml_handler(ContentHandler): |
def __init__(self,options): |
def __init__(self,options): |
"""SAX handler to import FileMaker XML file (FMPXMLRESULT format) into the table. |
"""SAX handler to import FileMaker XML file (FMPXMLRESULT format) into the table. |
@param options: dict of options |
@param options: dict of options |
Line 133 class xml_handler:
|
Line 152 class xml_handler:
|
|
|
|
|
# set up parser |
# set up parser |
|
self.result={} |
self.event = None |
self.event = None |
self.top_dispatcher = { |
|
(saxtools.START_ELEMENT, fm_ns, u'METADATA'): |
# self.top_dispatcher = { |
self.handle_meta_fields, |
# (saxtools.START_ELEMENT, fm_ns, u'METADATA'): |
(saxtools.START_ELEMENT, fm_ns, u'RESULTSET'): |
# self.handle_meta_fields, |
self.handle_data_fields, |
# (saxtools.START_ELEMENT, fm_ns, u'RESULTSET'): |
} |
# self.handle_data_fields, |
|
# } |
|
|
# connect database |
# connect database |
self.dbCon = psycopg.connect(options.dsn) |
self.dbCon = psycopg.connect(options.dsn) |
|
logging.debug("DB encoding: %s"%getattr(self.dbCon, 'encoding', 'UNKNOWN')) |
self.db = self.dbCon.cursor() |
self.db = self.dbCon.cursor() |
assert self.db, "AIIEE no db cursor for %s!!"%options.dsn |
assert self.db, "AIIEE no db cursor for %s!!"%options.dsn |
|
|
Line 155 class xml_handler:
|
Line 177 class xml_handler:
|
self.ascii_db = getattr(options,"ascii_db",None) |
self.ascii_db = getattr(options,"ascii_db",None) |
self.replace_table = getattr(options,"replace_table",None) |
self.replace_table = getattr(options,"replace_table",None) |
self.backup_table = getattr(options,"backup_table",None) |
self.backup_table = getattr(options,"backup_table",None) |
|
self.read_before_update = getattr(options,"read_before_update",None) |
|
self.debug_data = getattr(options,"debug_data",None) |
|
|
self.logger.debug("dsn: "+repr(getattr(options,"dsn",None))) |
self.logger.debug("dsn: "+repr(getattr(options,"dsn",None))) |
self.logger.debug("table: "+repr(self.table)) |
self.logger.debug("table: "+repr(self.table)) |
Line 166 class xml_handler:
|
Line 190 class xml_handler:
|
self.logger.debug("ascii_db: "+repr(self.ascii_db)) |
self.logger.debug("ascii_db: "+repr(self.ascii_db)) |
self.logger.debug("replace_table: "+repr(self.replace_table)) |
self.logger.debug("replace_table: "+repr(self.replace_table)) |
self.logger.debug("backup_table: "+repr(self.backup_table)) |
self.logger.debug("backup_table: "+repr(self.backup_table)) |
|
self.logger.debug("read_before_update: "+repr(self.read_before_update)) |
|
self.logger.debug("debug_data: "+repr(self.debug_data)) |
|
|
self.dbIDs = {} |
self.dbIDs = {} |
self.rowcnt = 0 |
self.rowcnt = 0 |
|
|
|
self.currentName = None |
|
|
if self.id_field is not None: |
if self.id_field is not None: |
# prepare a list of ids for sync mode |
# prepare a list of ids for sync mode |
qstr="select %s from %s"%(self.id_field,self.table) |
qstr="select %s from %s"%(self.id_field,self.table) |
Line 189 class xml_handler:
|
Line 217 class xml_handler:
|
|
|
return |
return |
|
|
def handle_meta_fields(self, end_condition): |
def startElement(self, name, attrs): |
dispatcher = { |
logging.debug(name) |
(saxtools.START_ELEMENT, fm_ns, u'FIELD'): |
if (name.lower() == "field") : |
self.handle_meta_field, |
self.handle_meta_field(attrs) |
} |
if (name.lower() == "row") : |
|
logging.debug("handleROW") |
|
self.handle_row(attrs) |
|
if (name.lower()=="resultset"): |
|
self.handle_data_fields(attrs) |
|
|
|
if (name.lower()=="data"): |
|
self.handle_data_tag(attrs); |
|
|
|
def endElement(self,name): |
|
if (name.lower() == "resultset") : |
|
self.currentTag="" |
|
self.handle_end_data_fields() |
|
if (name.lower() == "field") : |
|
self.handle_end_meta_field() |
|
if (name.lower() == "metadata"): |
|
self.handle_end_meta_fields() |
|
if (name.lower() == "row") : |
|
logging.debug("handleROW") |
|
self.handle_end_row() |
|
|
|
if (name.lower() == "col") : |
|
self.handle_end_col() |
|
def characters(self,content): |
|
|
|
try: |
|
fn = self.xml_field_names[self.colIdx] |
|
|
|
contentTmp = self.xml_data.get(fn,'') #gibt es schon einen Inhalt, dann dieses hinzufuegen (in einem Tag kann u.U. der characters handler mehrfach aufgerufen werden.) |
|
self.xml_data[fn] = contentTmp+content |
|
except: |
|
logging.debug(content) |
|
pass |
|
|
|
# if self.currentName is not None: |
|
# logging.debug(self.currentName+" "+content) |
|
# self.currentRow[self.currentName]=content; |
|
# |
|
def handle_end_meta_fields(self): |
|
# dispatcher = { |
|
# (saxtools.START_ELEMENT, fm_ns, u'FIELD'): |
|
# self.handle_meta_field, |
|
# } |
#First round through the generator corresponds to the |
#First round through the generator corresponds to the |
#start element event |
#start element event |
self.logger.info("reading metadata...") |
# self.logger.info("reading metadata...") |
self.logger.debug("START METADATA") |
# if self.debug_data: |
yield None |
# self.logger.debug("START METADATA") |
|
# #yield None |
|
|
#delegate is a generator that handles all the events "within" |
#delegate is a generator that handles all the events "within" |
#this element |
#this element |
delegate = None |
# delegate = None |
while not self.event == end_condition: |
# while not self.event == end_condition: |
delegate = saxtools.tenorsax.event_loop_body( |
# delegate = saxtools.tenorsax.event_loop_body( |
dispatcher, delegate, self.event) |
# dispatcher, delegate, self.event) |
yield None |
# yield None |
|
# |
#Element closed. Wrap up |
# #Element closed. Wrap up |
|
if self.debug_data: |
self.logger.debug("END METADATA") |
self.logger.debug("END METADATA") |
|
|
# rename table for backup |
# rename table for backup |
Line 255 class xml_handler:
|
Line 327 class xml_handler:
|
qstr="select attname, format_type(pg_attribute.atttypid, pg_attribute.atttypmod) from pg_attribute, pg_class where attrelid = pg_class.oid and pg_attribute.attnum > 0 and relname = '%s'" |
qstr="select attname, format_type(pg_attribute.atttypid, pg_attribute.atttypmod) from pg_attribute, pg_class where attrelid = pg_class.oid and pg_attribute.attnum > 0 and relname = '%s'" |
self.sql_fields={} |
self.sql_fields={} |
for f in SimpleSearch(self.db, qstr%self.table): |
for f in SimpleSearch(self.db, qstr%self.table): |
n = f[0] |
fn = f[0] |
t = f[1] |
ft = f[1] |
#print "SQL fields: %s (%s)"%(n,t) |
#print "SQL fields: %s (%s)"%(n,t) |
self.sql_fields[n] = TableColumn(n,t) |
self.sql_fields[fn] = TableColumn(fn,ft) |
|
|
# translate id_field (SQL-name) to XML-name |
# translate id_field (SQL-name) to XML-name |
self.xml_id = self.sql_field_map.get(self.id_field, None) |
self.xml_id = self.sql_field_map.get(self.id_field, None) |
Line 307 class xml_handler:
|
Line 379 class xml_handler:
|
self.logger.debug("field %s has different type (%s vs %s)"%(f,f.getType(),sf.getType())) |
self.logger.debug("field %s has different type (%s vs %s)"%(f,f.getType(),sf.getType())) |
elif uf is not None: |
elif uf is not None: |
# add field to table |
# add field to table |
qstr="alter table %s add %s %s"%(self.table,uf.getName(),uf.getType()) |
fn = uf.getName() |
|
ft = uf.getType() |
|
qstr="alter table %s add \"%s\" %s"%(self.table,fn,ft) |
self.logger.info("db add field:"+qstr) |
self.logger.info("db add field:"+qstr) |
|
|
if self.ascii_db and type(qstr)==types.UnicodeType: |
if self.ascii_db and type(qstr)==types.UnicodeType: |
Line 315 class xml_handler:
|
Line 389 class xml_handler:
|
|
|
self.db.execute(qstr) |
self.db.execute(qstr) |
self.dbCon.commit() |
self.dbCon.commit() |
|
# add field to field list |
|
self.sql_fields[fn] = TableColumn(fn, ft) |
|
|
# prepare sql statements for update (do not update id_field) |
# prepare sql statements for update (do not update id_field) |
setStr=string.join(["%s = %%s"%self.xml_field_map[f] for f in self.xml_update_list if f != self.xml_id], ', ') |
setStr=string.join(["\"%s\" = %%s"%self.xml_field_map[f] for f in self.xml_update_list if f != self.xml_id], ', ') |
self.updQuery="UPDATE %s SET %s WHERE %s = %%s"%(self.table,setStr,self.id_field) |
self.updQuery="UPDATE %s SET %s WHERE \"%s\" = %%s"%(self.table,setStr,self.id_field) |
|
# and select (for update check) |
|
selStr=string.join([self.xml_field_map[f].getName() for f in self.xml_update_list if f != self.xml_id], ', ') |
|
self.selQuery="SELECT %s FROM %s WHERE \"%s\" = %%s"%(selStr,self.table,self.id_field) |
# and insert |
# and insert |
fields=string.join([self.xml_field_map[x].getName() for x in self.xml_update_list], ',') |
fields=string.join(["\"%s\""%self.xml_field_map[x].getName() for x in self.xml_update_list], ',') |
values=string.join(['%s' for f in self.xml_update_list], ',') |
values=string.join(['%s' for f in self.xml_update_list], ',') |
self.addQuery="INSERT INTO %s (%s) VALUES (%s)"%(self.table,fields,values) |
self.addQuery="INSERT INTO %s (%s) VALUES (%s)"%(self.table,fields,values) |
self.logger.debug("update-query: "+self.updQuery) |
self.logger.debug("update-query: "+self.updQuery) |
|
self.logger.debug("sel-query: "+self.selQuery) |
self.logger.debug("add-query: "+self.addQuery) |
self.logger.debug("add-query: "+self.addQuery) |
return |
return |
|
|
def handle_meta_field(self, end_condition): |
def handle_meta_field(self, attrs): |
name = self.params.get((None, u'NAME')) |
self.currentName = attrs.get('NAME') |
yield None |
#yield None |
|
return |
|
def handle_end_meta_field(self): |
#Element closed. Wrap up |
#Element closed. Wrap up |
|
name = self.currentName |
if self.lc_names: |
if self.lc_names: |
# clean name |
# clean name |
sqlname = name.replace(" ","_").lower() |
sqlname = sqlName(name) |
else: |
else: |
sqlname = name |
sqlname = name |
self.xml_field_names.append(name) |
self.xml_field_names.append(name) |
Line 343 class xml_handler:
|
Line 426 class xml_handler:
|
self.logger.debug("FIELD name: "+name) |
self.logger.debug("FIELD name: "+name) |
return |
return |
|
|
def handle_data_fields(self, end_condition): |
def handle_data_fields(self, attrs): |
dispatcher = { |
|
(saxtools.START_ELEMENT, fm_ns, u'ROW'): |
|
self.handle_row, |
|
} |
|
#First round through the generator corresponds to the |
#First round through the generator corresponds to the |
#start element event |
#start element event |
self.logger.info("reading data...") |
self.logger.info("reading data...") |
|
if self.debug_data: |
self.logger.debug("START RESULTSET") |
self.logger.debug("START RESULTSET") |
self.rowcnt = 0 |
self.rowcnt = 0 |
yield None |
return |
|
|
|
def handle_end_data_fields(self): |
#delegate is a generator that handles all the events "within" |
#delegate is a generator that handles all the events "within" |
#this element |
#this element |
delegate = None |
|
while not self.event == end_condition: |
|
delegate = saxtools.tenorsax.event_loop_body( |
|
dispatcher, delegate, self.event) |
|
yield None |
|
|
|
#Element closed. Wrap up |
#Element closed. Wrap up |
|
if self.debug_data: |
self.logger.debug("END RESULTSET") |
self.logger.debug("END RESULTSET") |
self.dbCon.commit() |
self.dbCon.commit() |
|
|
if self.sync_mode: |
if self.sync_mode: |
# delete unmatched entries in db |
# delete unmatched entries in db |
|
if self.rowcnt > 0: |
self.logger.info("deleting unmatched rows from db") |
self.logger.info("deleting unmatched rows from db") |
delQuery = "DELETE FROM %s WHERE %s = %%s"%(self.table,self.id_field) |
delQuery = "DELETE FROM %s WHERE \"%s\" = %%s"%(self.table,self.id_field) |
for id in self.dbIDs.keys(): |
for id in self.dbIDs.keys(): |
# find all not-updated fields |
# find all not-updated fields |
if self.dbIDs[id] == 0: |
if self.dbIDs[id] == 0: |
self.logger.info(" delete:"+id) |
self.logger.info(" delete: %s"%id) |
SimpleSearch(self.db, delQuery, [id], ascii=self.ascii_db) |
SimpleSearch(self.db, delQuery, [id], ascii=self.ascii_db) |
sys.exit(1) |
|
|
|
elif self.dbIDs[id] > 1: |
elif self.dbIDs[id] > 1: |
self.logger.info(" sync: ID %s used more than once?"%id) |
self.logger.info(" sync: ID %s used more than once?"%id) |
|
|
self.dbCon.commit() |
self.dbCon.commit() |
|
|
|
else: |
|
# safety in case we had an empty file |
|
self.logger.warning("no rows read! not deleting unmatched rows!") |
|
|
# reinstate backup tables |
# reinstate backup tables |
if self.backup_table and not self.id_field: |
if self.backup_table and not self.id_field: |
backup_name = "%s_%s"%(self.orig_table,time.strftime('%Y_%m_%d_%H_%M_%S')) |
backup_name = "%s_%s"%(self.orig_table,time.strftime('%Y_%m_%d_%H_%M_%S')) |
Line 394 class xml_handler:
|
Line 476 class xml_handler:
|
self.db.execute(qstr) |
self.db.execute(qstr) |
self.dbCon.commit() |
self.dbCon.commit() |
|
|
|
self.logger.info("Done (%s rows)"%self.rowcnt) |
return |
return |
|
|
def handle_row(self, end_condition): |
def handle_row(self, end_condition): |
dispatcher = { |
|
(saxtools.START_ELEMENT, fm_ns, u'COL'): |
if self.debug_data: |
self.handle_col, |
|
} |
|
self.logger.debug("START ROW") |
self.logger.debug("START ROW") |
self.xml_data = {} |
self.xml_data = {} |
self.colIdx = 0 |
self.colIdx = 0 |
yield None |
|
|
|
|
return |
|
|
|
def handle_end_row(self): |
#delegate is a generator that handles all the events "within" |
#delegate is a generator that handles all the events "within" |
#this element |
#this element |
delegate = None |
|
while not self.event == end_condition: |
|
delegate = saxtools.tenorsax.event_loop_body( |
|
dispatcher, delegate, self.event) |
|
yield None |
|
|
|
#Element closed. Wrap up |
#Element closed. Wrap up |
|
if self.debug_data: |
self.logger.debug("END ROW") |
self.logger.debug("END ROW") |
self.rowcnt += 1 |
self.rowcnt += 1 |
# process collected row data |
# process collected row data |
Line 423 class xml_handler:
|
Line 502 class xml_handler:
|
# synchronize by id_field |
# synchronize by id_field |
if self.id_field: |
if self.id_field: |
if self.id_type == 'integer': |
if self.id_type == 'integer': |
|
try: |
id_val = int(self.xml_data[self.xml_id]) |
id_val = int(self.xml_data[self.xml_id]) |
|
except: |
|
pass |
else: |
else: |
id_val = self.xml_data[self.xml_id] |
id_val = self.xml_data[self.xml_id] |
|
|
|
if not id_val: |
|
# abort update |
|
self.logger.error("ERROR: unable to sync! emtpy id in row %s"%self.rowcnt) |
|
return |
|
|
if id_val in self.dbIDs: |
if id_val in self.dbIDs: |
self.dbIDs[id_val] += 1 |
self.dbIDs[id_val] += 1 |
update=True |
update=True |
Line 439 class xml_handler:
|
Line 526 class xml_handler:
|
continue |
continue |
|
|
f = self.xml_field_map[fn] |
f = self.xml_field_map[fn] |
val = self.xml_data[fn] |
val = self.xml_data.get(fn,None) |
type = self.sql_fields[f.getName()].getType() |
type = self.sql_fields[f.getName()].getType() |
if type == "date" and len(val) == 0: |
if type == "date" and len(val.strip()) == 0: |
# empty date field |
# empty date field |
val = None |
val = None |
|
|
Line 453 class xml_handler:
|
Line 540 class xml_handler:
|
|
|
if update: |
if update: |
# update existing row (by id_field) |
# update existing row (by id_field) |
# last argument is ID match |
if self.read_before_update: |
args.append(id_val) |
# read data |
|
if self.debug_data: |
|
self.logger.debug("update check: %s = %s"%(id_val, args)) |
|
oldrow = SimpleSearch(self.db, self.selQuery, [id_val], ascii=self.ascii_db) |
|
#i = 0 |
|
#for v in oldrow[0]: |
|
# logging.debug("v: %s = %s (%s)"%(v,args[i],v==args[i])) |
|
# i += 1 |
|
if tuple(oldrow[0]) != tuple(args): |
|
# data has changed -- update |
|
if self.debug_data: |
|
self.logger.debug("really update: %s = %s"%(id_val, args)) |
|
args.append(id_val) # last arg is id |
|
SimpleSearch(self.db, self.updQuery, args, ascii=self.ascii_db) |
|
|
|
else: |
|
# always update |
|
if self.debug_data: |
self.logger.debug("update: %s = %s"%(id_val, args)) |
self.logger.debug("update: %s = %s"%(id_val, args)) |
|
args.append(id_val) # last arg is id |
SimpleSearch(self.db, self.updQuery, args, ascii=self.ascii_db) |
SimpleSearch(self.db, self.updQuery, args, ascii=self.ascii_db) |
|
|
else: |
else: |
# create new row |
# create new row |
|
if self.debug_data: |
self.logger.debug("insert: %s"%args) |
self.logger.debug("insert: %s"%args) |
SimpleSearch(self.db, self.addQuery, args, ascii=self.ascii_db) |
SimpleSearch(self.db, self.addQuery, args, ascii=self.ascii_db) |
|
|
Line 470 class xml_handler:
|
Line 576 class xml_handler:
|
|
|
return |
return |
|
|
def handle_col(self, end_condition): |
def handle_end_col(self): |
dispatcher = { |
|
(saxtools.START_ELEMENT, fm_ns, u'DATA'): |
|
self.handle_data_tag, |
|
} |
|
#print "START COL" |
|
yield None |
|
#delegate is a generator that handles all the events "within" |
|
#this element |
|
delegate = None |
|
while not self.event == end_condition: |
|
delegate = saxtools.tenorsax.event_loop_body( |
|
dispatcher, delegate, self.event) |
|
yield None |
|
#Element closed. Wrap up |
|
#print "END COL" |
|
self.colIdx += 1 |
self.colIdx += 1 |
return |
return |
|
|
def handle_data_tag(self, end_condition): |
|
|
def handle_data_tag(self, attrs): |
#print "START DATA" |
#print "START DATA" |
content = u'' |
self.content = u'' |
yield None |
# yield None |
# gather child elements |
# # gather child elements |
while not self.event == end_condition: |
# while not self.event == end_condition: |
if self.event[0] == saxtools.CHARACTER_DATA: |
# if self.event[0] == saxtools.CHARACTER_DATA: |
content += self.params |
# content += self.params |
yield None |
# yield None |
#Element closed. Wrap up |
# #Element closed. Wrap up |
fn = self.xml_field_names[self.colIdx] |
# fn = self.xml_field_names[self.colIdx] |
self.xml_data[fn] = content |
# self.xml_data[fn] = content |
return |
return |
|
|
|
|
Line 540 def importFMPXML(options):
|
Line 634 def importFMPXML(options):
|
#The "consumer" is our own handler |
#The "consumer" is our own handler |
consumer = xml_handler(options) |
consumer = xml_handler(options) |
#Initialize Tenorsax with handler |
#Initialize Tenorsax with handler |
handler = saxtools.tenorsax(consumer) |
#handler = saxtools.tenorsax(consumer) |
#Resulting tenorsax instance is the SAX handler |
#Resulting tenorsax instance is the SAX handler |
parser.setContentHandler(handler) |
parser.setContentHandler(consumer) |
parser.setFeature(sax.handler.feature_namespaces, 1) |
#parser.setFeature(sax.handler.feature_namespaces, 1) |
parser.parse(options.filename) |
parser.parse(options.filename) |
|
|
|
|
Line 584 if __name__ == "__main__":
|
Line 678 if __name__ == "__main__":
|
opars.add_option("--backup", default=False, action="store_true", |
opars.add_option("--backup", default=False, action="store_true", |
dest="backup_table", |
dest="backup_table", |
help="create backup of old table") |
help="create backup of old table") |
|
opars.add_option("--read-before-update", default=False, action="store_true", |
|
dest="read_before_update", |
|
help="read all data to check if it really changed") |
opars.add_option("-d", "--debug", default=False, action="store_true", |
opars.add_option("-d", "--debug", default=False, action="store_true", |
dest="debug", |
dest="debug", |
help="debug mode (more output)") |
help="debug mode (more output)") |
|
opars.add_option("--debug-data", default=False, action="store_true", |
|
dest="debug_data", |
|
help="debug mode for data (even more output)") |
|
|
(options, args) = opars.parse_args() |
(options, args) = opars.parse_args() |
|
|