28
28
import re
29
29
import sys , os
30
30
from textwrap import dedent
31
- import traceback
32
31
32
+ import ast
33
33
import jsonpointer as jsonp
34
34
import jsonschema as jsons
35
35
import pandas as pd
36
36
37
- from . import (_version , DEBUG , model , json_dumps , str2bool ) # @UnusedImport
38
-
37
+ from . import (_version , DEBUG , model , str2bool ) # @UnusedImport
38
+ from .model import (json_dumps )
39
+ from .model import validate_model
39
40
40
41
logging .basicConfig (level = logging .DEBUG )
41
42
log = logging .getLogger (__file__ )
@@ -55,7 +56,7 @@ def main(argv=None):
55
56
*= : float
56
57
?= : boolean
57
58
:= : parsed as json
58
- ; = : parsed as python (with eval())
59
+ @ = : parsed as python (with eval())
59
60
60
61
EXAMPLES:
61
62
---------
@@ -65,21 +66,21 @@ def main(argv=None):
65
66
...
66
67
67
68
## Calculate and print fitted engine map's parameters
68
- # for a PETROL vehicle with the above engine-point's CSV-table:
69
- >> %(prog)s -m fuel=PETROL -I engine.csv
69
+ # for a petrol vehicle with the above engine-point's CSV-table:
70
+ >> %(prog)s -m fuel=petrol -I engine.csv
70
71
71
72
## Assume PME column contained normalized-Power in Watts,
72
73
# instead of P in kW:
73
- >> %(prog)s -m fuel=PETROL -I engine.csv -irenames X X 'Pnorm (w)'
74
+ >> %(prog)s -m fuel=petrol -I engine.csv -irenames X X 'Pnorm (w)'
74
75
75
76
## Read the same table above but without header-row and
76
- # store results into Excel file:
77
- >> %(prog)s -m fuel=PETROL -I engine.csv --icolumns CM PME PMF -I engine_map.xlsx
77
+ # store results into Excel file, 1st sheet :
78
+ >> %(prog)s -m fuel=petrol -I engine.csv --icolumns CM PME PMF -I engine_map.xlsx sheetname+=0
78
79
79
80
## Supply as inline-json more model-values required for columns [RPM, P, FC]
80
81
# read from <stdin> as json 2D-array of values (no headers).
81
82
# and store results in UTF-8 regardless of platform's default encoding:
82
- >> %(prog)s -m '/engine:={"fuel":"PETROL ", "stroke":15, "capacity":1359}' \\
83
+ >> %(prog)s -m '/engine:={"fuel":"petrol ", "stroke":15, "capacity":1359}' \\
83
84
-I - file_frmt=JSON orient=values -c RPM P FC \\
84
85
-O engine_map.txt encoding=UTF-8
85
86
@@ -93,11 +94,11 @@ def main(argv=None):
93
94
and the 2nd having 2 columns with no headers at all and
94
95
the 1st column being 'Pnorm', then it, then use the following command:
95
96
96
- >> %(prog)s -O engine_map -m fuel=PETROL \\
97
- -I=engine_1.xlsx \\
97
+ >> %(prog)s -O engine_map -m fuel=petrol \\
98
+ -I=engine_1.xlsx sheetname+=0 \\
98
99
-c X X N 'Fuel consumption' X \\
99
100
-r X X RPM 'FC(g/s)' X \\
100
- -I=engine_2.csv \\
101
+ -I=engine_2.csv header@=None \\
101
102
-c Pnorm X
102
103
"""
103
104
@@ -119,11 +120,15 @@ def main(argv=None):
119
120
120
121
DEBUG = bool (opts .debug )
121
122
122
- if (DEBUG ):
123
+ if (DEBUG or opts . verbose > 1 ):
123
124
log .setLevel (logging .DEBUG )
124
125
else :
125
- log .setLevel (logging .INFO )
126
- log .info ("Args: argv\n Opts: %s" , opts )
126
+ if opts .verbose == 1 :
127
+ log .setLevel (logging .INFO )
128
+ else :
129
+ log .setLevel (logging .WARNING )
130
+
131
+ log .debug ("Args: %s\n Opts: %s" , argv , opts )
127
132
128
133
opts = validate_file_opts (opts )
129
134
@@ -134,7 +139,7 @@ def main(argv=None):
134
139
log .info ("Output-files: %s" , outfiles )
135
140
136
141
mdl = build_model (opts , infiles )
137
- log .info ("Input Model: %s" , json_dumps (mdl ))
142
+ log .info ("Input Model: %s" , json_dumps (mdl , 'to_string' ))
138
143
mdl = validate_model (mdl )
139
144
140
145
except (SystemExit ) as ex :
@@ -148,10 +153,16 @@ def main(argv=None):
148
153
parser .exit (3 , "%s: %s\n %s for help use --help\n " % (program_name , ex , indent ))
149
154
except jsons .ValidationError as ex :
150
155
if DEBUG :
151
- log .exception ('Invalid input model!' )
156
+ log .error ('Invalid input model!' , exc_info = ex )
152
157
indent = len (program_name ) * " "
153
158
parser .exit (4 , "%s: Model validation failed due to: %s\n %s for help use --help\n " % (program_name , ex , indent ))
154
159
160
+ except jsonp .JsonPointerException as ex :
161
+ if DEBUG :
162
+ log .exception ('Invalid model operation!' )
163
+ indent = len (program_name ) * " "
164
+ parser .exit (4 , "%s: Model operation failed due to: %s\n %s for help use --help\n " % (program_name , ex , indent ))
165
+
155
166
156
167
157
168
## The value of file_frmt=VALUE to decide which pandas.read_XXX() method to use.
@@ -161,7 +172,6 @@ def main(argv=None):
161
172
('TXT' , pd .read_csv ),
162
173
('XLS' , pd .read_excel ),
163
174
('JSON' , pd .read_json ),
164
- ('CLIPBOARD' , pd .read_clipboard ),
165
175
])
166
176
_known_file_exts = {
167
177
'XLSX' :'XLS'
@@ -193,11 +203,11 @@ def get_file_format_from_extension(fname):
193
203
'*' : float ,
194
204
'?' : str2bool ,
195
205
':' : json .loads ,
196
- '; ' : eval
206
+ '@ ' : ast . literal_eval ## best-effort security: http://stackoverflow.com/questions/3513292/python-make- eval-safe
197
207
}
198
208
199
209
200
- _key_value_regex = re .compile (r'^\s*([A -Za-z]\w *)\s*([+*?:; ]?)=\s*(.+?)\s*$' )
210
+ _key_value_regex = re .compile (r'^\s*([/_A -Za-z][\w/\.] *)\s*([+*?:@ ]?)=\s*(.+?)\s*$' )
201
211
def parse_key_value_pair (arg ):
202
212
"""Argument-type for syntax like: KEY [+*?:]= VALUE."""
203
213
@@ -226,8 +236,6 @@ def parse_column_specifier(arg):
226
236
raise argparse .ArgumentTypeError ("Not a COLUMN_SPEC syntax: %s" % arg )
227
237
228
238
229
- FileSpec = collections .namedtuple ('FileSpec' , ('fname' , 'file' , 'frmt' , 'path' , 'append' , 'kws' ))
230
-
231
239
def validate_file_opts (opts ):
232
240
## Check number of input-files <--> related-opts
233
241
#
@@ -249,7 +257,10 @@ def validate_file_opts(opts):
249
257
return opts
250
258
251
259
260
+ FileSpec = collections .namedtuple ('FileSpec' , ('fname' , 'file' , 'frmt' , 'path' , 'append' , 'kws' , 'read_method' ))
261
+
252
262
def parse_many_file_args (many_file_args , filetype ):
263
+
253
264
def parse_file_args (fname , * kv_args ):
254
265
frmt = _default_pandas_format
255
266
dest = _default_df_dest
@@ -263,17 +274,25 @@ def parse_file_args(fname, *kv_args):
263
274
if (frmt not in _pandas_formats ):
264
275
raise argparse .ArgumentTypeError ("Unsupported pandas file_frmt: %s\n Set 'file_frmt=XXX' to one of %s" % (frmt , list (_pandas_formats .keys ())[1 :]))
265
276
266
- if (frmt == 'CLIPBOARD ' ):
277
+ if (fname == '+ ' ):
267
278
file = None
268
279
fname = '<CLIPBOARD>'
280
+ frmt = 'TABLE'
281
+ method = pd .read_clipboard
269
282
else :
270
283
if (frmt == _default_pandas_format ):
271
284
if ('-' == fname ):
272
- raise argparse .ArgumentTypeError ("With <stdio> a concrete file_frmt is required! \n Set 'file_frmt=XXX' to one of %s" % (list (_pandas_formats .keys ())[1 :]))
285
+ raise argparse .ArgumentTypeError ("With <stdio> and <clipboard> a concrete file_frmt is required! \n Set 'file_frmt=XXX' to one of %s" % (list (_pandas_formats .keys ())[1 :]))
273
286
frmt = get_file_format_from_extension (fname )
274
287
if (not frmt ):
275
288
raise argparse .ArgumentTypeError ("File(%s) has unknown extension, file_frmt is required! \n Set 'file_frmt=XXX' to one of %s" % (fname , list (_pandas_formats .keys ())[1 :]))
276
- file = argparse .FileType (filetype )(fname )
289
+
290
+ method = _pandas_formats [frmt ]
291
+
292
+ if (method == pd .read_excel ):
293
+ file = fname
294
+ else :
295
+ file = argparse .FileType (filetype )(fname )
277
296
278
297
try :
279
298
dest = pandas_kws .pop ('model_path' )
@@ -288,14 +307,15 @@ def parse_file_args(fname, *kv_args):
288
307
except KeyError :
289
308
pass
290
309
291
- return FileSpec (fname , file , frmt , dest , append , pandas_kws )
310
+ return FileSpec (fname , file , frmt , dest , append , pandas_kws , method )
292
311
293
312
return [parse_file_args (* file_args ) for file_args in many_file_args ]
294
313
295
314
296
315
def load_file_as_df (filespec ):
297
316
# FileSpec(fname, file, frmt, path, append, kws)
298
- method = _pandas_formats [filespec .frmt ]
317
+ method = filespec .read_method
318
+ log .debug ('Reading file with: pandas.%s(%s, %s)' , method .__name__ , filespec .file , filespec .kws )
299
319
dfin = method (filespec .file , ** filespec .kws )
300
320
301
321
return dfin
@@ -306,7 +326,7 @@ def build_model(opts, infiles):
306
326
307
327
for filespec in infiles :
308
328
dfin = load_file_as_df (filespec )
309
- log .info ( " +-input-file(%s):\n %s" , filespec .fname , dfin )
329
+ log .debug ( " +-input-file(%s):\n %s" , filespec .fname , dfin )
310
330
jsonp .set_pointer (mdl , filespec .path , dfin )
311
331
312
332
model_overrides = opts .m
@@ -320,12 +340,6 @@ def build_model(opts, infiles):
320
340
return mdl
321
341
322
342
323
- def validate_model (mdl ):
324
- validator = model .model_validator ()
325
- validator .validate (mdl )
326
-
327
- return mdl
328
-
329
343
def build_args_parser (program_name , version , desc , epilog ):
330
344
version_string = '%%prog %s' % (version )
331
345
@@ -360,7 +374,7 @@ def build_args_parser(program_name, version, desc, epilog):
360
374
must either match them, be 1 (meaning use them for all files), or be totally absent
361
375
(meaning use defaults for all files). """ ),
362
376
action = 'append' , nargs = '+' , required = True ,
363
- # default=[('- file_frmt=%s model_path=%s'%('CSV', _default_df_dest)).split()],
377
+ # default=[('- file_frmt=%s model_path=%s'%('CSV', _default_df_dest)).split()],
364
378
metavar = 'ARG' )
365
379
grp_io .add_argument ('-c' , '--icolumns' , help = dedent ("""\
366
380
describes the contents and the units of input file(s) (see --I).
@@ -443,8 +457,8 @@ def build_args_parser(program_name, version, desc, epilog):
443
457
444
458
grp_various = parser .add_argument_group ('Various' , 'Options controlling various other aspects.' )
445
459
#parser.add_argument('--gui', help='start in GUI mode', action='store_true')
446
- grp_various .add_argument ("--debug" , action = "store_true" , help = "set debug level [default: %(default)s]" , default = False )
447
- grp_various .add_argument ("--verbose" , action = "count" , default = 0 , help = "set verbosity level [default: %(default)s]" )
460
+ grp_various .add_argument ('-d' , "--debug" , action = "store_true" , help = "set debug level [default: %(default)s]" , default = False )
461
+ grp_various .add_argument ('-v' , "--verbose" , action = "count" , default = 0 , help = "set verbosity level [default: %(default)s]" )
448
462
grp_various .add_argument ("--version" , action = "version" , version = version_string , help = "prints version identifier of the program" )
449
463
grp_various .add_argument ("--help" , action = "help" , help = 'show this help message and exit' )
450
464
0 commit comments