1+ #!/usr/bin/python3
2+
3+ import argparse , os , urllib .request , ast , sys
4+ from string import Template
5+
6+ SCRIPT_DIR = os .path .dirname (__file__ )
7+ DEFAULT_URL = "https://raw.githubusercontent.com/WebODM/ODM/master/opendm/config.py"
8+
9+ parser = argparse .ArgumentParser (description = 'Extract ODM arguments and generate a Markdown reference page.' )
10+ parser .add_argument ('input' , type = str , nargs = '?' ,
11+ default = DEFAULT_URL ,
12+ help = 'URL to ODM\' s config.py' )
13+ args = parser .parse_args ()
14+
15+ url = args .input
16+ outfile = os .path .join (SCRIPT_DIR , ".." , "src" , "content" , "docs" , "options-flags.md" )
17+ tmplfile = os .path .join (SCRIPT_DIR , "options-flags.template.md" )
18+
19+ print ("Fetching %s ..." % url )
20+ res = urllib .request .urlopen (url )
21+ config_file = res .read ().decode ('utf-8' )
22+
23+ options = {}
24+ args_map = {}
25+
26+ class ArgumentParserStub (argparse .ArgumentParser ):
27+ def add_argument (self , * args , ** kwargs ):
28+ argparse .ArgumentParser .add_argument (self , * args , ** kwargs )
29+ options [args [0 ]] = {}
30+ args_map [args [0 ]] = args [1 :]
31+
32+ for name , value in kwargs .items ():
33+ options [args [0 ]][str (name )] = str (value )
34+
35+ def add_mutually_exclusive_group (self ):
36+ return ArgumentParserStub ()
37+
38+ # Voodoo! :)
39+ # Parse AST, extract assignments and function definitions,
40+ # execute in current scope to populate options via the stub parser.
41+ root = ast .parse (config_file )
42+ new_body = []
43+ for stmt in root .body :
44+ if hasattr (stmt , 'targets' ): # Assignments
45+ new_body .append (stmt )
46+ elif hasattr (stmt , 'name' ): # Functions
47+ new_body .append (stmt )
48+
49+ root .body = new_body
50+ exec (compile (root , filename = "<ast>" , mode = "exec" ))
51+
52+ # Misc variables needed to get config() to run
53+ __version__ = '?'
54+ class context :
55+ root_path = ''
56+ num_cores = 4
57+ settings_path = ''
58+ class io :
59+ def path_or_json_string_to_dict (s ):
60+ return s
61+ def path_or_json_string (s ):
62+ return s
63+ class log :
64+ def ODM_ERROR (s ):
65+ pass
66+ def ODM_WARNING (s ):
67+ pass
68+ def ODM_INFO (s ):
69+ pass
70+
71+ config (["--project-path" , "/bogus" , "name" ], parser = ArgumentParserStub ())
72+
73+ # --- helpers ---
74+
75+ def get_opt_name (opt ):
76+ opt_name = opt
77+ arg_map = args_map [opt ]
78+ if len (arg_map ) > 0 :
79+ opt_name = max (arg_map + (opt_name , ), key = len )
80+ return opt_name .replace ("--" , "" )
81+
82+ def get_opt_descr (opt ):
83+ h = options [opt ].get ('help' , '' )
84+ if not h :
85+ return ''
86+ h = h .replace ("\n " , "\n \n " )
87+ # Remove default/choices template markers — we show them separately
88+ h = h .replace ("Can be one of: %(choices)s." , "" )
89+ h = h .replace ("Can be one of: %(choices)s" , "" )
90+ h = h .replace ('%(choices)s' , options [opt ].get ('choices' , '' ))
91+
92+ # Remove some tags
93+ # Escape HTML tags
94+ import re
95+ h = re .sub (r'<[^>]+>' , lambda m : '`' + m .group (0 ) + '`' , h )
96+
97+ # Strip trailing "Default: %(default)s" (with optional period) so we
98+ # don't duplicate the separate **Default:** line we emit below.
99+
100+ h = re .sub (r'\s*Default:\s*%\(default\)s\.?\s*$' , '' , h )
101+ h = h .replace ('%(default)s' , '`' + options [opt ].get ('default' , '' ) + '`' )
102+
103+ # Collapse multiple spaces
104+ h = re .sub (r' +' , ' ' , h ).strip ()
105+ return h
106+
107+ def get_opt_default (opt ):
108+ res = options [opt ].get ('default' , '' )
109+ if res == "==SUPPRESS==" :
110+ res = ""
111+ return res
112+
113+ def get_opt_choices (opt ):
114+ raw = options [opt ].get ('choices' , options [opt ].get ('metavar' , '' ))
115+ return raw .replace ('[' , '' ).replace (']' , '' ).replace (',' , ' | ' ).replace ('\' ' , '' )
116+
117+ # --- build markdown ---
118+
119+ print ("Found %s ODM options" % len (options ))
120+
121+ if len (options ) == 0 :
122+ print ("No options found" )
123+ sys .exit (1 )
124+
125+ keys = list (options .keys ())
126+ keys .sort (key = lambda a : a .replace ("-" , "" ))
127+
128+ sections = ""
129+ for opt in keys :
130+ name = get_opt_name (opt )
131+ descr = get_opt_descr (opt )
132+ default = get_opt_default (opt )
133+ choices = get_opt_choices (opt )
134+
135+ section = "## %s\n \n " % name
136+ if descr :
137+ section += "%s\n \n " % descr
138+ if choices :
139+ section += "**Options:** `%s`\n \n " % choices
140+ if default :
141+ section += "**Default:** `%s`\n \n " % default
142+
143+ sections += section
144+
145+ with open (tmplfile ) as f :
146+ tmpl = Template (f .read ())
147+
148+ with open (outfile , "w" ) as f :
149+ f .write (tmpl .substitute (arguments = sections ))
150+
151+ print ("Wrote %s" % outfile )
0 commit comments