55import re
66import getopt
77import json
8- try :
9- import yaml
10- except ImportError :
11- yaml = None
12- try :
13- import jsonlines
14- except ImportError :
15- jsonlines = None
8+
9+ my_yaml = None
10+ my_jsonlines = None
1611
1712import CloudFlare
13+ from .dump import dump_commands , dump_commands_from_web
1814from . import converters
1915
20- def dump_commands (cf ):
21- """dump a tree of all the known API commands"""
22- w = cf .api_list ()
23- sys .stdout .write ('\n ' .join (w ) + '\n ' )
24-
25- def dump_commands_from_web (cf ):
26- """dump a tree of all the known API commands - from web"""
27- w = cf .api_from_web ()
28- for r in w :
29- if r ['deprecated' ]:
30- if r ['deprecated_already' ]:
31- sys .stdout .write ('%-6s %s ; deprecated %s - expired!\n ' % (r ['action' ], r ['cmd' ], r ['deprecated_date' ]))
32- else :
33- sys .stdout .write ('%-6s %s ; deprecated %s\n ' % (r ['action' ], r ['cmd' ], r ['deprecated_date' ]))
34- else :
35- sys .stdout .write ('%-6s %s\n ' % (r ['action' ], r ['cmd' ]))
16+ def load_and_check_yaml ():
17+ """ load_and_check_yaml() """
18+ from . import myyaml
19+ global my_yaml
20+ my_yaml = myyaml .myyaml ()
21+ if not my_yaml .available ():
22+ sys .exit ('cli4: install yaml support' )
3623
3724def strip_multiline (s ):
3825 """ remove leading/trailing tabs/spaces on each line"""
3926 # This hack is needed in order to use yaml.safe_load() on JSON text - tabs are not allowed
4027 return '\n ' .join ([l .strip () for l in s .splitlines ()])
4128
29+ def process_params_content_files (method , binary_file , args ):
30+ """ process_params_content_files() """
31+
32+ digits_only = re .compile ('^-?[0-9]+$' )
33+ floats_only = re .compile ('^-?[0-9.]+$' )
34+
35+ params = None
36+ content = None
37+ files = None
38+ # next grab the params. These are in the form of tag=value or =value or @filename
39+ while len (args ) > 0 and ('=' in args [0 ] or args [0 ][0 ] == '@' ):
40+ arg = args .pop (0 )
41+ if arg [0 ] == '@' :
42+ # a file to be uploaded - used in workers/script - only via PUT
43+ filename = arg [1 :]
44+ if method not in ['PUT' ,'POST' ]:
45+ sys .exit ('cli4: %s - raw file upload only with PUT or POST' % (filename ))
46+ try :
47+ if filename == '-' :
48+ if binary_file :
49+ content = sys .stdin .buffer .read ()
50+ else :
51+ content = sys .stdin .read ()
52+ else :
53+ if binary_file :
54+ with open (filename , 'rb' ) as f :
55+ content = f .read ()
56+ else :
57+ with open (filename , 'r' ) as f :
58+ content = f .read ()
59+ except IOError :
60+ sys .exit ('cli4: %s - file open failure' % (filename ))
61+ continue
62+ tag_string , value_string = arg .split ('=' , 1 )
63+ if value_string .lower () == 'true' :
64+ value = True
65+ elif value_string .lower () == 'false' :
66+ value = False
67+ elif value_string == '' or value_string .lower () == 'none' :
68+ value = None
69+ elif value_string [0 ] == '=' and value_string [1 :] == '' :
70+ sys .exit ('cli4: %s== - no number value passed' % (tag_string ))
71+ elif value_string [0 ] == '=' and digits_only .match (value_string [1 :]):
72+ value = int (value_string [1 :])
73+ elif value_string [0 ] == '=' and floats_only .match (value_string [1 :]):
74+ value = float (value_string [1 :])
75+ elif value_string [0 ] == '=' :
76+ sys .exit ('cli4: %s== - invalid number value passed' % (tag_string ))
77+ elif value_string [0 ] in '[{' and value_string [- 1 ] in '}]' :
78+ # a json structure - used in pagerules
79+ try :
80+ #value = json.loads(value) - changed to yaml code to remove unicode string issues
81+ load_and_check_yaml ()
82+ # cleanup string before parsing so that yaml.safe.load does not complain about whitespace
83+ # >>> found character '\t' that cannot start any token <<<
84+ value_string = strip_multiline (value_string )
85+ try :
86+ value = my_yaml .safe_load (value_string )
87+ except my_yaml .parser .ParserError as e :
88+ raise ValueError
89+ except ValueError :
90+ sys .exit ('cli4: %s="%s" - can\' t parse json value' % (tag_string , value_string ))
91+ elif value_string [0 ] == '@' :
92+ # a file to be uploaded - used in dns_records/import - only via POST
93+ filename = value_string [1 :]
94+ if method != 'POST' :
95+ sys .exit ('cli4: %s=%s - file upload only with POST' % (tag_string , filename ))
96+ files = {}
97+ try :
98+ if filename == '-' :
99+ files [tag_string ] = sys .stdin
100+ else :
101+ files [tag_string ] = open (filename , 'rb' )
102+ except IOError :
103+ sys .exit ('cli4: %s=%s - file open failure' % (tag_string , filename ))
104+ # no need for param code below
105+ continue
106+ else :
107+ value = value_string
108+
109+ if tag_string == '' :
110+ # There's no tag; it's just an unnamed list
111+ if params is None :
112+ params = value
113+ else :
114+ sys .exit ('cli4: %s=%s - param error. Can\' t mix unnamed and named list' %
115+ (tag_string , value_string ))
116+ else :
117+ if params is None :
118+ params = {}
119+ tag = tag_string
120+ try :
121+ params [tag ] = value
122+ except TypeError :
123+ sys .exit ('cli4: %s=%s - param error. Can\' t mix unnamed and named list' %
124+ (tag_string , value_string ))
125+
126+ return (params , content , files )
127+
42128def run_command (cf , method , command , params = None , content = None , files = None ):
43129 """run the command line"""
44130 # remove leading and trailing /'s
@@ -232,8 +318,6 @@ def write_results(results, output):
232318 pass
233319 else :
234320 # anything more complex (dict, list, etc) should be dumped as JSON/YAML
235- if output is None :
236- results = None
237321 if output == 'json' :
238322 try :
239323 results = json .dumps (results ,
@@ -246,17 +330,20 @@ def write_results(results, output):
246330 indent = 4 ,
247331 sort_keys = True ,
248332 ensure_ascii = False )
249- if output == 'yaml' :
250- results = yaml .safe_dump (results )
251- if output == 'ndjson' :
333+ elif output == 'yaml' :
334+ results = my_yaml .safe_dump (results )
335+ elif output == 'ndjson' :
252336 # NDJSON support seems like a hack. There has to be a better way
253337 try :
254- writer = jsonlines .Writer (sys .stdout )
338+ writer = my_jsonlines .Writer (sys .stdout )
255339 writer .write_all (results )
256340 writer .close ()
257341 except (BrokenPipeError , IOError ):
258342 pass
259343 return
344+ else :
345+ # Internal error
346+ pass
260347
261348 if results :
262349 try :
@@ -274,6 +361,7 @@ def do_it(args):
274361 raw = False
275362 dump = False
276363 dump_from_web = False
364+ openapi_url = None
277365 binary_file = False
278366 profile = None
279367 method = 'GET'
@@ -284,6 +372,7 @@ def do_it(args):
284372 + '[-r|--raw] '
285373 + '[-d|--dump] '
286374 + '[-a|--api] '
375+ + '[-A|--openapi url] '
287376 + '[-b|--binary] '
288377 + '[-p|--profile profile-name] '
289378 + '[--get|--patch|--post|--put|--delete] '
@@ -292,12 +381,12 @@ def do_it(args):
292381
293382 try :
294383 opts , args = getopt .getopt (args ,
295- 'Vhvqjyrdabp :GPOUD' ,
384+ 'VhvqjyrdaA:bp :GPOUD' ,
296385 [
297386 'version' ,
298387 'help' , 'verbose' , 'quiet' , 'json' , 'yaml' , 'ndjson' ,
299388 'raw' ,
300- 'dump' , 'api' ,
389+ 'dump' , 'api' , 'openapi=' ,
301390 'binary' ,
302391 'profile=' ,
303392 'get' , 'patch' , 'post' , 'put' , 'delete'
@@ -316,11 +405,13 @@ def do_it(args):
316405 elif opt in ('-j' , '--json' ):
317406 output = 'json'
318407 elif opt in ('-y' , '--yaml' ):
319- if yaml is None :
320- sys .exit ('cli4: install yaml support' )
408+ load_and_check_yaml ()
321409 output = 'yaml'
322410 elif opt in ('-n' , '--ndjson' ):
323- if jsonlines is None :
411+ from . import myjsonlines
412+ global my_jsonlines
413+ my_jsonlines = myjsonlines .myjsonlines ()
414+ if not my_jsonlines .available ():
324415 sys .exit ('cli4: install jsonlines support' )
325416 output = 'ndjson'
326417 elif opt in ('-r' , '--raw' ):
@@ -331,6 +422,8 @@ def do_it(args):
331422 dump = True
332423 elif opt in ('-a' , '--api' ):
333424 dump_from_web = True
425+ elif opt in ('-A' , '--openapi' ):
426+ openapi_url = arg
334427 elif opt in ('-b' , '--binary' ):
335428 binary_file = True
336429 elif opt in ('-G' , '--get' ):
@@ -344,121 +437,34 @@ def do_it(args):
344437 elif opt in ('-D' , '--delete' ):
345438 method = 'DELETE'
346439
347- if dump :
440+ try :
348441 cf = CloudFlare .CloudFlare (debug = verbose , raw = raw , profile = profile )
349- dump_commands (cf )
442+ except Exception as e :
443+ sys .exit (e )
444+
445+ if dump :
446+ a = dump_commands (cf )
447+ sys .stdout .write (a )
350448 sys .exit (0 )
351449
352450 if dump_from_web :
353- cf = CloudFlare . CloudFlare ( debug = verbose , raw = raw , profile = profile )
354- dump_commands_from_web ( cf )
451+ a = dump_commands_from_web ( cf )
452+ sys . stdout . write ( a )
355453 sys .exit (0 )
356454
357- digits_only = re .compile ('^-?[0-9]+$' )
358- floats_only = re .compile ('^-?[0-9.]+$' )
455+ if openapi_url :
456+ a = dump_commands_from_web (cf , openapi_url )
457+ sys .stdout .write (a )
458+ sys .exit (0 )
359459
360460 # next grab the params. These are in the form of tag=value or =value or @filename
361- params = None
362- content = None
363- files = None
364- while len (args ) > 0 and ('=' in args [0 ] or args [0 ][0 ] == '@' ):
365- arg = args .pop (0 )
366- if arg [0 ] == '@' :
367- # a file to be uploaded - used in workers/script - only via PUT
368- filename = arg [1 :]
369- if method not in ['PUT' ,'POST' ]:
370- sys .exit ('cli4: %s - raw file upload only with PUT or POST' % (filename ))
371- try :
372- if filename == '-' :
373- if binary_file :
374- content = sys .stdin .buffer .read ()
375- else :
376- content = sys .stdin .read ()
377- else :
378- if binary_file :
379- with open (filename , 'rb' ) as f :
380- content = f .read ()
381- else :
382- with open (filename , 'r' ) as f :
383- content = f .read ()
384- except IOError :
385- sys .exit ('cli4: %s - file open failure' % (filename ))
386- continue
387- tag_string , value_string = arg .split ('=' , 1 )
388- if value_string .lower () == 'true' :
389- value = True
390- elif value_string .lower () == 'false' :
391- value = False
392- elif value_string == '' or value_string .lower () == 'none' :
393- value = None
394- elif value_string [0 ] == '=' and value_string [1 :] == '' :
395- sys .exit ('cli4: %s== - no number value passed' % (tag_string ))
396- elif value_string [0 ] == '=' and digits_only .match (value_string [1 :]):
397- value = int (value_string [1 :])
398- elif value_string [0 ] == '=' and floats_only .match (value_string [1 :]):
399- value = float (value_string [1 :])
400- elif value_string [0 ] == '=' :
401- sys .exit ('cli4: %s== - invalid number value passed' % (tag_string ))
402- elif value_string [0 ] in '[{' and value_string [- 1 ] in '}]' :
403- # a json structure - used in pagerules
404- try :
405- #value = json.loads(value) - changed to yaml code to remove unicode string issues
406- if yaml is None :
407- sys .exit ('cli4: install yaml support' )
408- # cleanup string before parsing so that yaml.safe.load does not complain about whitespace
409- # >>> found character '\t' that cannot start any token <<<
410- value_string = strip_multiline (value_string )
411- try :
412- value = yaml .safe_load (value_string )
413- except yaml .parser .ParserError as e :
414- raise ValueError
415- except ValueError :
416- sys .exit ('cli4: %s="%s" - can\' t parse json value' % (tag_string , value_string ))
417- elif value_string [0 ] == '@' :
418- # a file to be uploaded - used in dns_records/import - only via POST
419- filename = value_string [1 :]
420- if method != 'POST' :
421- sys .exit ('cli4: %s=%s - file upload only with POST' % (tag_string , filename ))
422- files = {}
423- try :
424- if filename == '-' :
425- files [tag_string ] = sys .stdin
426- else :
427- files [tag_string ] = open (filename , 'rb' )
428- except IOError :
429- sys .exit ('cli4: %s=%s - file open failure' % (tag_string , filename ))
430- # no need for param code below
431- continue
432- else :
433- value = value_string
434-
435- if tag_string == '' :
436- # There's no tag; it's just an unnamed list
437- if params is None :
438- params = value
439- else :
440- sys .exit ('cli4: %s=%s - param error. Can\' t mix unnamed and named list' %
441- (tag_string , value_string ))
442- else :
443- if params is None :
444- params = {}
445- tag = tag_string
446- try :
447- params [tag ] = value
448- except TypeError :
449- sys .exit ('cli4: %s=%s - param error. Can\' t mix unnamed and named list' %
450- (tag_string , value_string ))
461+ (params , content , files ) = process_params_content_files (method , binary_file , args )
451462
452463 # what's left is the command itself
453464 if len (args ) < 1 :
454465 sys .exit (usage )
455466 commands = args
456467
457- try :
458- cf = CloudFlare .CloudFlare (debug = verbose , raw = raw , profile = profile )
459- except Exception as e :
460- sys .exit (e )
461-
462468 exit_with_error = False
463469 for command in commands :
464470 try :
0 commit comments