diff --git a/docs/Tools/Framework.md b/docs/Tools/Framework.md index 5675da66..5647806f 100644 --- a/docs/Tools/Framework.md +++ b/docs/Tools/Framework.md @@ -3,6 +3,20 @@ The `framework` module contains the infrastructure to support standardized implementation of tools in GridLAB-D. +Standard options: + +The following options are processed by `read_stdargs()`: + +* `--debug`: enable debug traceback on exception + +* `--quiet`: suppress error messages + +* `--silent`: suppress all error messages + +* `--warning`: suppress warning messages + +* `--verbose`: enable verbose output, if any + Example: ~~~ @@ -10,6 +24,7 @@ import framework as app def main(argv): + # handle no options case -- typically a cry for help if len(argv) == 1: print(" @@ -17,22 +32,32 @@ def main(argv): ") if x.startswith("Syntax: ")])) return app.E_SYNTAX - args = read_stdargs(argv) + # handle stardard app arguments --debug, --warning, --verbose, --quiet, --silent + args = app.read_stdargs(argv) for key,value in args: if key in ["-h","--help","help"]: print(__doc__,file=sys.stdout) + + # TODO: add options here + else: error(f"'{key}={value}' is invalid") return app.E_INVALID + # TODO: code implementation here, if any + return app.E_OK if __name__ == "__main__": try: + # TODO: development testing -- delete when done writing code + if not sys.argv[0]: + sys.argv = ["selftest","--debug"] + rc = main(sys.argv) exit(rc) @@ -42,12 +67,12 @@ if __name__ == "__main__": except Exception as exc: - if DEBUG: + if app.DEBUG: raise exc - if not QUIET: + if not app.QUIET: e_type,e_value,e_trace = sys.exc_info() - tb = traceback.TracebackException(e_type,e_value,e_trace).stack[1] + tb = app.traceback.TracebackException(e_type,e_value,e_trace).stack[1] print(f"EXCEPTION [{app.EXEFILE}@{tb.lineno}]: ({e_type.__name__}) {e_value}",file=sys.stderr) exit(app.E_EXCEPTION) @@ -81,6 +106,21 @@ Returns: * `complex`: complex value (if form is 'conjugate' or None) +--- + +## `debug() -> None` + +Debugging message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are enabled when the `--debug` option is used. + + --- ## `double_unit() -> float` @@ -94,6 +134,32 @@ Returns: * real value +--- + +## `error() -> None` + +Error message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppressed when the `--quiet` option is used. + + +--- + +## `exception() -> None` + +Exception message output + +Arguments: + +* `exc`: exception to raise + + --- ## `gridlabd() -> Optional` @@ -153,6 +219,21 @@ Return: * File handle to JSON file after conversion from GLM +--- + +## `output() -> None` + +General message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppressed when the `--silent` option is used. + + --- ## `read_stdargs() -> list` @@ -168,6 +249,21 @@ Returns: * Remaining arguments +--- + +## `verbose() -> None` + +Verbose message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are enabled when the `--verbose` option is used. + + --- ## `version() -> str` @@ -178,3 +274,18 @@ Returns: * GridLAB-D version info + +--- + +## `warning() -> None` + +Warning message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppress when the `--warning` option is used. + diff --git a/docs/Tools/Location.md b/docs/Tools/Location.md new file mode 100644 index 00000000..f75d3dcd --- /dev/null +++ b/docs/Tools/Location.md @@ -0,0 +1,180 @@ +[[/Tools/Location]] -- Location tool + +Syntax: `gridlabd location [OPTIONS ...] [FILENAME=KEY[:VALUE][,...] ...]` + +Options: + +* `--debug`: enable debug traceback on exception + +* `--find[=KEY[:VALUE][,...]]`: get location settings + +* `--format=FORMAT[,OPTION[:VALUE[,...]]] + +* `--quiet`: suppress error messages + +* `--silent`: suppress all error messages + +* `--system[=KEY[:VALUE][,...]]`: get/set the default location + +* `--verbose`: enable verbose output, if any + +* `--warning`: suppress warning messages + +Description: + +The `location` tool allows configuration of the location of a model. + +The `location` tool `--system` option is used to setup the system's default +location for models when not location data is not specified in the model. +When values are change, the location data is returned and the new location +is stored in `GLD_ETC/location_config.glm + +The `location` tool `--find` options can identify the current location of a +system or a location based on partial information. + +Location setting on `FILENAME` will be performed in place, i.e., the file will +first be read and the it will be written with the new values. The result +output to stdout will be the previous values. + +The keys and globals handled by the `location` tools include the following: + +* `latitude`: the location's latitude + +* `longitude`: the location's longitude + +* `zipcode`: the location's postal code + +* `city`: the location's city + +* `county`: the location's county + +* `state`: the location's state + +* `region`: the location's region + +* `country`: the location's country + +Caveat: + +Although the `--find` option allows multiple addresses to be resolved, it is +certainly not efficient to do more than a few queries this way. If you need +to resolve large number of addresses then you should use the batch feature of +the `geocoder` module. + +Examples: + +Get the current location + + gridlabd location --find + +Display the default location + + gridlabd location --system + +Set the location in a model file + + gridlabd location ieee123.json=country:US,state:CA,county:Kern,city:Bakersfield + + + + +# Classes + +## LocationError + +Location exception + +# Functions + +## `find() -> dict` + +Find location data + +Arguments: + +* `kwargs`: Partial location data (see `system()`). None return IP location. + +Returns: + +* Location data + + +--- + +## `get_location() -> dict` + +Get location data in file + +Arguments: + +* `file`: file from which to get location data + +Returns: + +* Current values + + +--- + +## `main() -> int` + +Main location routine + +Arguments: + +* `argv`: command line argument list + +Returns: + +* Exit code + + +--- + +## `set_location() -> dict` + +Set location in file + +Arguments: + +* `file`: file in which to set location data + +* `**kwargs`: location data + +Returns: + +* Previous values + + +--- + +## `system() -> dict` + +Get/set system location settings + +Arguments: + +* `latitude`: new latitude + +* `longitude`: new longitude + +* `number`: new street number + +* `street`: new street name + +* `zipcode`: new zipcode + +* `city`: new city + +* `county`: new county + +* `state`: new state + +* `region`: new region + +* `country`: new country + +Returns: + +* previous location settings + diff --git a/docs/Tools/Makefile.mk b/docs/Tools/Makefile.mk index 705d73fa..9333cf4c 100644 --- a/docs/Tools/Makefile.mk +++ b/docs/Tools/Makefile.mk @@ -1,6 +1,7 @@ DOCS_UTILITIES += docs/Tools/Framework.md DOCS_UTILITIES += docs/Tools/Network.md DOCS_UTILITIES += docs/Tools/Edit.md +DOCS_UTILITIES += docs/Tools/Location.md DOCS_UTILITIES += docs/Tools/Mapping.md DOCS_UTILITIES += docs/Tools/Moutils.md DOCS_UTILITIES += docs/Tools/Unitcalc.md diff --git a/docs/Tools/Mapping.md b/docs/Tools/Mapping.md index 69b4cdd5..8d9fe305 100644 --- a/docs/Tools/Mapping.md +++ b/docs/Tools/Mapping.md @@ -202,6 +202,21 @@ Returns: * `complex`: complex value (if form is 'conjugate' or None) +--- + +## `debug() -> None` + +Debugging message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are enabled when the `--debug` option is used. + + --- ## `double_unit() -> float` @@ -215,6 +230,32 @@ Returns: * real value +--- + +## `error() -> None` + +Error message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppressed when the `--quiet` option is used. + + +--- + +## `exception() -> None` + +Exception message output + +Arguments: + +* `exc`: exception to raise + + --- ## `get_options() -> dict` @@ -306,6 +347,21 @@ Return: * File handle to JSON file after conversion from GLM +--- + +## `output() -> None` + +General message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppressed when the `--silent` option is used. + + --- ## `read_stdargs() -> list` @@ -321,6 +377,21 @@ Returns: * Remaining arguments +--- + +## `verbose() -> None` + +Verbose message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are enabled when the `--verbose` option is used. + + --- ## `version() -> str` @@ -331,3 +402,18 @@ Returns: * GridLAB-D version info + +--- + +## `warning() -> None` + +Warning message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppress when the `--warning` option is used. + diff --git a/runtime/gridlabd.conf b/runtime/gridlabd.conf index 51097fd6..7b3b91b3 100644 --- a/runtime/gridlabd.conf +++ b/runtime/gridlabd.conf @@ -8,7 +8,7 @@ #ifdef WINDOWS #setenv GRIDLABD=c:/Program Files/GridLAB-D #else // linux/mac -#setenv GRIDLABD=/usr/etc/gridlabd +#setenv GRIDLABD=/usr/local/opt/gridlabd/current #endif #endif // GRIDLABD @@ -37,13 +37,10 @@ #endif // gnuplot ///////////////////////////////////////////////////////////////////////////// -// The following are some recommended settings -// set the save default file name -// comment it out to automatic save to this file -//#set savefile=gridlabd.xml - -// comment this out to stop using the PNNL's development URL base -//#set urlbase=http://gridlabd.pnl.gov/source/VS2005 +// location configuration +#ifexist "location.glm" +#include "location.glm" +#endif // location ///////////////////////////////////////////////////////////////////////////// // get user/custom settings diff --git a/tools/Makefile.mk b/tools/Makefile.mk index fa086148..012e0cb1 100644 --- a/tools/Makefile.mk +++ b/tools/Makefile.mk @@ -22,6 +22,7 @@ dist_pkgdata_DATA += tools/group.py dist_pkgdata_DATA += tools/insights.py dist_pkgdata_DATA += tools/install.py dist_pkgdata_DATA += tools/isone.py +dist_pkgdata_DATA += tools/location.py dist_pkgdata_DATA += tools/mapping.py dist_pkgdata_DATA += tools/mapping_config.py dist_pkgdata_DATA += tools/market_data.py diff --git a/tools/autotest/test_location.glm b/tools/autotest/test_location.glm new file mode 100644 index 00000000..894e4363 --- /dev/null +++ b/tools/autotest/test_location.glm @@ -0,0 +1,6 @@ +#system rm -f ${GLD_ETC}/location.glm +#system gridlabd location --system > test_location.txt + +#ifexist "../test_location.txt" +#on_exit 0 diff ../test_location.txt test_location.txt > gridlabd.diff +#endif diff --git a/tools/autotest/test_location.txt b/tools/autotest/test_location.txt new file mode 100644 index 00000000..c4198f0b --- /dev/null +++ b/tools/autotest/test_location.txt @@ -0,0 +1 @@ +{'latitude': '', 'longitude': '', 'city': '', 'zipcode': '', 'county': '', 'state': '', 'region': 'CA', 'country': 'US'} diff --git a/tools/edit.py b/tools/edit.py index 8c9eb09b..3d0e3b56 100644 --- a/tools/edit.py +++ b/tools/edit.py @@ -163,7 +163,7 @@ class Editor: """GLM Model Editor """ def __init__(self,data:dict): - assert "application" in data, "data does not valid application data" + assert "application" in data, "data does not have valid application data" assert data["application"] == "gridlabd", "data is not a valid gridlabd model" self.data = data diff --git a/tools/framework.py b/tools/framework.py index d3b63b84..ad347ae3 100644 --- a/tools/framework.py +++ b/tools/framework.py @@ -3,6 +3,20 @@ The `framework` module contains the infrastructure to support standardized implementation of tools in GridLAB-D. +Standard options: + +The following options are processed by `read_stdargs()`: + +* `--debug`: enable debug traceback on exception + +* `--quiet`: suppress error messages + +* `--silent`: suppress all error messages + +* `--warning`: suppress warning messages + +* `--verbose`: enable verbose output, if any + Example: ~~~ @@ -10,27 +24,38 @@ def main(argv): + # handle no options case -- typically a cry for help if len(argv) == 1: print("\n".join([x for x in __doc__.split("\n") if x.startswith("Syntax: ")])) return app.E_SYNTAX - args = read_stdargs(argv) + # handle stardard app arguments --debug, --warning, --verbose, --quiet, --silent + args = app.read_stdargs(argv) for key,value in args: if key in ["-h","--help","help"]: print(__doc__,file=sys.stdout) + + # TODO: add options here + else: error(f"'{key}={value}' is invalid") return app.E_INVALID + # TODO: code implementation here, if any + return app.E_OK if __name__ == "__main__": try: + # TODO: development testing -- delete when done writing code + if not sys.argv[0]: + sys.argv = ["selftest","--debug"] + rc = main(sys.argv) exit(rc) @@ -40,12 +65,12 @@ def main(argv): except Exception as exc: - if DEBUG: + if app.DEBUG: raise exc - if not QUIET: + if not app.QUIET: e_type,e_value,e_trace = sys.exc_info() - tb = traceback.TracebackException(e_type,e_value,e_trace).stack[1] + tb = app.traceback.TracebackException(e_type,e_value,e_trace).stack[1] print(f"EXCEPTION [{app.EXEFILE}@{tb.lineno}]: ({e_type.__name__}) {e_value}",file=sys.stderr) exit(app.E_EXCEPTION) @@ -138,17 +163,43 @@ def read_stdargs(argv:list[str]) -> list[str]: return result def output(*msg:list,**kwargs): + """General message output + + Arguments: + + * `msg`: message to output + + * `**kwargs`: print options + + Messages are suppressed when the `--silent` option is used. + """ if not "file" in kwargs: kwargs["file"] = sys.stdout if not SILENT: print(*msg,**kwargs) def exception(exc:[TypeVar('Exception')|str]): + """Exception message output + + Arguments: + + * `exc`: exception to raise + """ if isinstance(exc,str): exc = MapError(exc) raise exc def error(*msg:list,code:[int|None]=None,**kwargs): + """Error message output + + Arguments: + + * `msg`: message to output + + * `**kwargs`: print options + + Messages are suppressed when the `--quiet` option is used. + """ if not QUIET: if code: print(f"ERROR [{EXENAME}]: {' '.join([str(x) for x in msg])} (code {repr(code)})",file=sys.stderr,**kwargs) @@ -160,14 +211,44 @@ def error(*msg:list,code:[int|None]=None,**kwargs): sys.exit(code) def verbose(*msg:list,**kwargs): + """Verbose message output + + Arguments: + + * `msg`: message to output + + * `**kwargs`: print options + + Messages are enabled when the `--verbose` option is used. + """ if VERBOSE: print(f"VERBOSE [{EXENAME}]: {' '.join([str(x) for x in msg])}",file=sys.stderr,**kwargs) def warning(*msg:list,**kwargs): + """Warning message output + + Arguments: + + * `msg`: message to output + + * `**kwargs`: print options + + Messages are suppress when the `--warning` option is used. + """ if WARNING: print(f"WARNING [{EXENAME}]: {' '.join([str(x) for x in msg])}",file=sys.stderr,**kwargs) def debug(*msg:list,**kwargs): + """Debugging message output + + Arguments: + + * `msg`: message to output + + * `**kwargs`: print options + + Messages are enabled when the `--debug` option is used. + """ if DEBUG: print(f"DEBUG [{EXENAME}]: {' '.join([str(x) for x in msg])}",file=sys.stderr,**kwargs) diff --git a/tools/location.py b/tools/location.py new file mode 100644 index 00000000..ff27da84 --- /dev/null +++ b/tools/location.py @@ -0,0 +1,412 @@ +"""Location tool + +Syntax: `gridlabd location [OPTIONS ...] [FILENAME=KEY[:VALUE][,...] ...]` + +Options: + +* `--debug`: enable debug traceback on exception + +* `--find[=KEY[:VALUE][,...]]`: get location settings + +* `--format=FORMAT[,OPTION[:VALUE[,...]]] + +* `--quiet`: suppress error messages + +* `--silent`: suppress all error messages + +* `--system[=KEY[:VALUE][,...]]`: get/set the default location + +* `--verbose`: enable verbose output, if any + +* `--warning`: suppress warning messages + +Description: + +The `location` tool allows configuration of the location of a model. + +The `location` tool `--system` option is used to setup the system's default +location for models when not location data is not specified in the model. +When values are change, the location data is returned and the new location +is stored in `GLD_ETC/location_config.glm + +The `location` tool `--find` options can identify the current location of a +system or a location based on partial information. + +Location setting on `FILENAME` will be performed in place, i.e., the file will +first be read and the it will be written with the new values. The result +output to stdout will be the previous values. + +The keys and globals handled by the `location` tools include the following: + +* `latitude`: the location's latitude + +* `longitude`: the location's longitude + +* `zipcode`: the location's postal code + +* `city`: the location's city + +* `county`: the location's county + +* `state`: the location's state + +* `region`: the location's region + +* `country`: the location's country + +Caveat: + +Although the `--find` option allows multiple addresses to be resolved, it is +certainly not efficient to do more than a few queries this way. If you need +to resolve large number of addresses then you should use the batch feature of +the `geocoder` module. + +Examples: + +Get the current location + + gridlabd location --find + +Display the default location + + gridlabd location --system + +Set the location in a model file + + gridlabd location ieee123.json=country:US,state:CA,county:Kern,city:Bakersfield + +""" + +import os +import sys +import json +import datetime as dt +import framework as app +import geocoder +import edit + +LOCATIONKEYS = ["latitude","longitude","city","zipcode","county","state","region","country"] +PROVIDER = "arcgis" +PROVIDERCONFIG = { + "arcgis" : { + "lat" : "latitude", + "lng" : "longitude", + "city" : "city", + "postal" : "zipcode", + "raw.address.RegionAbbr" : "state", + "raw.address.Subregion" : "county", + "country" : "country", + } +} + +class LocationError(Exception): + """Location exception""" + +class Location: + + def __init__(self,**kwargs): + + for key,value in kwargs.items(): + if not key in LOCATIONKEYS: + raise LocationError(f"'{key}' is not a valid location key") + setattr(self,key,value) + for key in LOCATIONKEYS: + if not hasattr(self,key): + setattr(self,key,"") + + def __repr__(self): + return str(dict(self.items())) + + def __getitem__(self,key): + + return getattr(self,key) + + def keys(self): + return LOCATIONKEYS + + def values(self): + return [getattr(self,x) for x in LOCATIONKEYS] + + def items(self): + return [(x,getattr(self,x)) for x in LOCATIONKEYS] + +def system(**kwargs:dict) -> dict: + """Get/set system location settings + + Arguments: + + * `latitude`: new latitude + + * `longitude`: new longitude + + * `number`: new street number + + * `street`: new street name + + * `zipcode`: new zipcode + + * `city`: new city + + * `county`: new county + + * `state`: new state + + * `region`: new region + + * `country`: new country + + Returns: + + * previous location settings + """ + glm = os.path.join(os.environ["GLD_ETC"],"location.glm") + data = json.loads(app.gridlabd(glm,"--globals=json").stdout.decode('utf-8')) + result = {} + for item in LOCATIONKEYS: + result[item] = data[item]['value'] if item in data else "" + + save = dict(result) + for x,y in kwargs.items(): + if not x in save: + raise LocationError(f"'{x}' is not a valid location key") + save[x] = y + + if save != result: + with open(glm,"w") as fh: + print(f"// generated by {' '.join(sys.argv)} on {dt.datetime.now()}",file=fh) + for x,y in save.items(): + setter = "set" if x in data else "define" + print(f'#{setter} {x}="{y}"',file=fh) + return Location(**result) + +def find(*address:list[str]) -> dict: + """Find location data + + Arguments: + + * `kwargs`: Partial location data (see `system()`). None return IP location. + + Returns: + + * Location data + """ + if len(address) == 0 or not address[0]: + + data = geocoder.ip('me') + result = {} + for item in LOCATIONKEYS: + result[item] = getattr(data,item) if hasattr(data,item) else "" + if not result: + raise LocationError("unable to find current location") + address = (f"{result['city']}, {result['state']}, {result['country']}",) + + result = [] + for item in address: + location = getattr(geocoder,PROVIDER)(address) + data = getattr(geocoder,PROVIDER)(location.latlng,method='reverse') + if data.ok: + found = {} + for key,value in PROVIDERCONFIG[PROVIDER].items(): + for subkey in key.split("."): + found[value] = (found[value] if value in found else data.json)[subkey] + for key,value in found.items(): + found[key] = str(value) + result.append(found) + else: + raise LocationError("no location found") + + return [Location(**x) for x in result] + +def set_location(file:str,**kwargs) -> dict: + """Set location in file + + Arguments: + + * `file`: file in which to set location data + + * `**kwargs`: location data + + Returns: + + * Previous values + """ + data = json.load(open(file,"r")) + result = {x:(data["globals"][x]["value"] if x in data["globals"] else "") for x in LOCATIONKEYS} + for key,value in kwargs.items(): + if not key in LOCATIONKEYS: + raise LocationError(f"'{key}' is not a valid location key") + data["globals"][key] = { + "type" : "char32", + "access" : "public", + "value" : value, + } + json.dump(data,open(file,"w"),indent=4) + return Location(**result) + +def get_location(file:str) -> dict: + """Get location data in file + + Arguments: + + * `file`: file from which to get location data + + Returns: + + * Current values + """ + data = json.load(open(file,"r")) + result = {x:(data["globals"][x]["value"] if x in data["globals"] else "") for x in LOCATIONKEYS} + return Location(**result) + +def main(argv:list) -> int: + """Main location routine + + Arguments: + + * `argv`: command line argument list + + Returns: + + * Exit code + """ + if len(argv) == 1: + + print("\n".join([x for x in __doc__.split("\n") if x.startswith("Syntax: ")])) + return app.E_SYNTAX + + args = app.read_stdargs(argv) + + def output_raw(data,**kwargs): + print(data,**kwargs) + + def output_csv(data,**kwargs): + if isinstance(data,list): + print(",".join(LOCATIONKEYS)) + for item in data: + print(",".join([str(item[x]) for x in LOCATIONKEYS])) + elif isinstance(data,Location): + print(",".join(LOCATIONKEYS)) + print(",".join([str(data[x]) for x in LOCATIONKEYS])) + else: + raise LocationError(f"unable to output '{type(data)}' as CSV") + + def output_json(data,**kwargs): + if isinstance(data,list): + print(json.dumps([dict(x.items()) for x in data],**kwargs)) + elif isinstance(data,Location): + print(json.dumps(dict(data.items()),**kwargs)) + else: + raise LocationError(f"unable to output '{type(data)}' as CSV") + + + outputter = output_raw + outputter_options = {} + + result = [] + for key,value in args: + + if key in ["-h","--help","help"]: + print(__doc__,file=sys.stdout) + + elif key in ["--format"]: + + if len(value) == 0: + + app.error("missing format") + return app.E_MISSING + + + elif value[0] == "csv": + + if len(value) > 1: + app.error(f"invalid format options '{','.join(value[1:])}'") + return app.E_INVALID + outputter = output_csv + + elif value[0] == "json": + + options = {x:y for x,y in [z.split(":",1) for z in value[1:]]} if len(value) > 1 else {} + _bool = lambda x: x=="true" if x in ["true","false"] else None, + for x,y in { + "indent": int, + "skipkeys": _bool, + "ensure_ascii": _bool, + "check_circular": _bool, + "allow_nan": _bool, + "sort_keys": _bool, + }.items(): + try: + options[x] = y(options[x]) + except: + pass + outputter = output_json + outputter_options = options + + elif key in ["--system"]: + + options = dict([x.split(":",1) for x in value]) + result = system(**options) + + elif key in ["--find"]: + + address = [",".join(value)] + result.extend(find(*address)) + + elif os.path.exists(key): + + if not value: + result = get_location(key) + else: + options = dict([x.split(":",1) for x in value]) + result = set_location(key,**options) + + else: + error(f"'{key}={value}' is invalid") + return app.E_INVALID + + if result: + outputter(result,**outputter_options) + + return app.E_OK + +if __name__ == "__main__": + + try: + + # TODO: development testing -- only needed when developing/debugging + # if not sys.argv[0]: + # options = [] + # # options = ["--format=raw"] + # # options = ["--format=csv"] + # # options = ["--format=json,indent:4"] + # # options = ["--debug","--format=raw"] + # # options = ["--debug","--format=csv"] + # # options = ["--debug","--format=json,indent:4"] + + # # sys.argv = [__file__,*options,"--system"] + # # sys.argv = [__file__,*options,"--system=city:Menlo Park,state:CA,region:west,country:US"] + # # sys.argv = [__file__,*options,"--find"] + # # sys.argv = [__file__,*options,"--find=2575 Sand Hill Rd, Menlo Park, CA, USA"] + # # sys.argv = [__file__,*options,"--find=2575 Sand Hill Rd, Menlo Park, CA","--find=7443 87th Dr NE, Marysville, WA"] + # # sys.argv = [__file__,*options,"autotest/test_moutils.json"] + # # sys.argv = [__file__,*options,"autotest/test_moutils.json=city:Seattle"] + + rc = main(sys.argv) + exit(rc) + + except KeyboardInterrupt: + + exit(app.E_INTERRUPT) + + except Exception as exc: + + if app.DEBUG: + raise exc + + if not app.QUIET: + e_type,e_value,e_trace = sys.exc_info() + tb = app.traceback.TracebackException(e_type,e_value,e_trace).stack[1] + print(f"EXCEPTION [{app.EXEFILE}@{tb.lineno}]: ({e_type.__name__}) {e_value}",file=sys.stderr) + + exit(app.E_EXCEPTION) +