feat(cli): Add config editor
This commit is contained in:
		
							parent
							
								
									2c000daae1
								
							
						
					
					
						commit
						8b0b56f130
					
				
					 1 changed files with 366 additions and 74 deletions
				
			
		|  | @ -2,110 +2,402 @@ import sys | |||
| import multiprocessing as MP | ||||
| import queue | ||||
| import ctypes | ||||
| import os | ||||
| from datetime import datetime | ||||
| from math import floor | ||||
| import click | ||||
| from tqdm import tqdm | ||||
| from click_default_group import DefaultGroup | ||||
| import requests as RQ | ||||
| from ed_lrr_gui import Router | ||||
| from ed_lrr_gui import Preprocessor | ||||
| import ed_lrr_gui.gui as ED_LRR_GUI | ||||
| CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) | ||||
| from urllib.parse import urljoin | ||||
| from ed_lrr_gui import Router, Preprocessor, cfg | ||||
| from _ed_lrr import find_sys | ||||
| 
 | ||||
| @click.group(invoke_without_command=True,context_settings=CONTEXT_SETTINGS) | ||||
| CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) | ||||
| 
 | ||||
| stars_path = os.path.join(cfg["folders.data_dir"], "stars.csv") | ||||
| for folder in cfg["history.out_path"]: | ||||
|     file = os.path.join(folder, "stars.csv") | ||||
|     if os.path.isfile(file): | ||||
|         stars_path = file | ||||
|         break | ||||
| 
 | ||||
| systems_path = os.path.join(cfg["folders.data_dir"], "systemsWithCoordinates.json") | ||||
| for file in cfg["history.systems_path"]: | ||||
|     if os.path.isfile(file): | ||||
|         systems_path = file | ||||
|         break | ||||
| 
 | ||||
| bodies_path = os.path.join(cfg["folders.data_dir"], "bodies.json") | ||||
| for file in cfg["history.bodies_path"][::-1]: | ||||
|     if os.path.isfile(file): | ||||
|         bodies_path = file | ||||
|         break | ||||
| 
 | ||||
| 
 | ||||
| @click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS) | ||||
| @click.pass_context | ||||
| @click.version_option() | ||||
| def main(ctx): | ||||
|     "Elite: Dangerous long range router, command line interface" | ||||
|     MP.freeze_support() | ||||
|     if ctx.invoked_subcommand != "config": | ||||
|         os.makedirs(cfg["folders.data_dir"], exist_ok=True) | ||||
|     if ctx.invoked_subcommand is None: | ||||
|         ctx.invoke(gui) | ||||
|         return | ||||
|     return | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| @click.option("--debug",help="Debug print",is_flag=True) | ||||
| @click.argument("option", default=None, required=False) | ||||
| @click.argument("value", default=None, required=False) | ||||
| def config(option, value): | ||||
|     """Change configuration | ||||
|      | ||||
| 
 | ||||
|     If "key" and "value" are both omitted the current configuration is printed | ||||
|     """ | ||||
| 
 | ||||
|     def print_config(key): | ||||
|         default = cfg.section(key).default() | ||||
|         comment = cfg.section(key).comment | ||||
|         value = cfg[key] | ||||
|         is_default = value == default | ||||
|         if ( | ||||
|             isinstance(value, list) | ||||
|             and all(isinstance(element, str) for element in value) | ||||
|             and len(value) != 0 | ||||
|         ): | ||||
|             value = "[{}]".format(", ".join(map("'{}'".format, value))) | ||||
|         key = click.style("{}".format(key), fg="cyan") | ||||
|         value = click.style("{}".format(value), fg="green") | ||||
|         default = click.style("{}".format(default), fg="blue") | ||||
|         comment = click.style("{}".format(comment), fg="yellow") | ||||
|         if is_default: | ||||
|             print("{}: {} # {}".format(key, default, comment)) | ||||
|         else: | ||||
|             print("{}: {} (default: {}) # {}".format(key, value, default, comment)) | ||||
| 
 | ||||
|     if option is None and value is None: | ||||
|         click.secho("Config path: {}".format(cfg.sources[0]), bold=True) | ||||
|         print() | ||||
|         for key in cfg: | ||||
|             print_config(key) | ||||
|         return | ||||
|     if value is None: | ||||
|         if option in cfg: | ||||
|             print_config(option) | ||||
|         else: | ||||
|             print("Invalid option:", option) | ||||
|         return | ||||
|     cfg[option] = value | ||||
|     cfg.sync() | ||||
|     return | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| def explore(): | ||||
|     "Open file manager in data folder" | ||||
|     click.launch(cfg["folders.data_dir"], locate=True) | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| @click.option("--debug", help="Debug print", is_flag=True) | ||||
| def gui(debug): | ||||
|     "Run the ED LRR GUI (default)" | ||||
|     if not debug: | ||||
|     import ed_lrr_gui.gui as ED_LRR_GUI | ||||
| 
 | ||||
|     if (not debug) and os.name == "nt": | ||||
|         ctypes.windll.kernel32.FreeConsole() | ||||
|         sys.stdin=open("NUL","rt") | ||||
|         sys.stdout=open("NUL","wt") | ||||
|         sys.stderr=open("NUL","wt") | ||||
|         sys.stdin = open("NUL", "rt") | ||||
|         sys.stdout = open("NUL", "wt") | ||||
|         sys.stderr = open("NUL", "wt") | ||||
|     sys.exit(ED_LRR_GUI.main()) | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| @click.option("--url","-u",help="Base URL",default="https://www.edsm.net/dump/",show_default=True) | ||||
| @click.option("--systems","-s",help="Target path for systemsWithCoordinates.json",default="systemsWithCoordinates.json",show_default=True) | ||||
| @click.option("--bodies","-b",help="Target path for bodies.json",default="bodies.json",show_default=True) | ||||
| def download(*args,**kwargs): | ||||
| @click.option( | ||||
|     "--url", | ||||
|     "-u", | ||||
|     help="Base URL", | ||||
|     default="https://www.edsm.net/dump/", | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--folder", | ||||
|     "-f", | ||||
|     help="Target folder for downloads", | ||||
|     default=cfg["folders.data_dir"], | ||||
|     type=click.Path(exists=True, dir_okay=True, file_okay=False), | ||||
|     show_default=True, | ||||
| ) | ||||
| def download(url, folder): | ||||
|     "Download EDSM dumps" | ||||
|     print("Download:",args,kwargs) | ||||
|     click.pause() | ||||
| 
 | ||||
| @main.command() | ||||
| def preprocess(*args,**kwargs): | ||||
|     "Preprocess EDSM dumps" | ||||
|     print("PreProcess:",ctx,args,kwargs) | ||||
|     click.pause() | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| @click.option("--path","-i",required=True,metavar="<path>",help="Path to stars.csv",default="./stars.csv",type=click.Path(exists=True,dir_okay=False),show_default=True ) | ||||
| @click.option("--precomp_file","-pf",metavar="<path>",help="Precomputed routing graph to use",type=click.Path(exists=True,dir_okay=False)) | ||||
| @click.option("--range","-r",required=True,metavar="<float>",help="Jump range (Ly)",type=click.FloatRange(min=0)) | ||||
| @click.option("--prune","-d",default=(0,0),metavar="<n> <m>",help="Prune search branches",nargs=2,type=click.Tuple([click.IntRange(min=0),click.FloatRange(min=0)])) | ||||
| @click.option("--permute","-p",type=click.Choice(["all","keep_first","keep_last","keep_both"]),default=None,help="Permute hops to find shortest route",show_default=True) | ||||
| @click.option("--primary","-ps",is_flag=True,default=False,help="Only route through primary stars") | ||||
| @click.option("--factor","-g",metavar="<float>",default=0.5,help="Greedyness factor for A-Star",show_default=True) | ||||
| @click.option("--mode","-m",default="bfs",help="Search mode",type=click.Choice(["bfs","a-star","greedy"]),show_default=True) | ||||
| @click.argument('systems',nargs=-1) | ||||
| def route(**kwargs): | ||||
|     "Compute a route" | ||||
|     if kwargs['prune']==(0,0): | ||||
|         kwargs['prune']=None | ||||
|     def to_string(state): | ||||
|         if state: | ||||
|             return "[{}] {}".format(state['depth'],state['system']) | ||||
|     keep_first,keep_last={ | ||||
|         "all":(False,False), | ||||
|         "keep_first":(True,False), | ||||
|         "keep_last":(False,True), | ||||
|         "keep_both":(True,True), | ||||
|         None: (False,False) | ||||
|     }[kwargs['permute']] | ||||
|     args=[kwargs['systems'],kwargs['range'],kwargs['prune'],kwargs['mode'],kwargs['primary'],kwargs['permute']!=None,keep_first,keep_last,kwargs['factor'],None,kwargs['path']] | ||||
|     with click.progressbar(length=100,label="Computing route",show_percent=True,item_show_func=to_string,width=50) as pbar: | ||||
|         router=Router(*args) | ||||
|         router.start() | ||||
|         state={} | ||||
|         pstate={} | ||||
|         while not (router.queue.empty() and router.is_alive()==False): | ||||
|     os.makedirs(folder, exist_ok=True) | ||||
|     for file_name in ["systemsWithCoordinates.json", "bodies.json"]: | ||||
|         download_url = urljoin(url, file_name) | ||||
|         download_path = os.path.join(folder, file_name) | ||||
|         if os.path.isfile(download_path): | ||||
|             try: | ||||
|                 event = router.queue.get(True,0.1) | ||||
|                 if not click.confirm( | ||||
|                     "{} already exissts, overwrite?".format(file_name) | ||||
|                 ): | ||||
|                     continue | ||||
|             except click.Abort: | ||||
|                 exit("Canceled!") | ||||
|         size = RQ.head(download_url, headers={"Accept-Encoding": "None"}) | ||||
|         size.raise_for_status() | ||||
|         size = int(size.headers.get("Content-Length", 0)) | ||||
|         with tqdm( | ||||
|             total=size, | ||||
|             desc="{}".format(file_name), | ||||
|             unit="b", | ||||
|             unit_divisor=1024, | ||||
|             unit_scale=True, | ||||
|             ascii=True, | ||||
|         ) as pbar: | ||||
|             with open(download_path, "wb") as of: | ||||
|                 resp = RQ.get( | ||||
|                     download_url, stream=True, headers={"Accept-Encoding": "gzip"} | ||||
|                 ) | ||||
|                 for chunk in resp.iter_content(1024 * 1024): | ||||
|                     of.write(chunk) | ||||
|                     pbar.update(len(chunk)) | ||||
|     click.pause() | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| @click.option( | ||||
|     "--systems", | ||||
|     "-s", | ||||
|     default=systems_path, | ||||
|     metavar="<path>", | ||||
|     help="Path to stars.csv", | ||||
|     type=click.Path(exists=True, dir_okay=False), | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--bodies", | ||||
|     "-b", | ||||
|     default=bodies_path, | ||||
|     metavar="<path>", | ||||
|     help="Path to bodies.json", | ||||
|     type=click.Path(exists=True, dir_okay=False), | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--output", | ||||
|     "-o", | ||||
|     default=stars_path, | ||||
|     metavar="<path>", | ||||
|     help="Path to stars.csv", | ||||
|     type=click.Path(exists=False, dir_okay=False), | ||||
|     show_default=True, | ||||
| ) | ||||
| def preprocess(systems, bodies, output): | ||||
|     "Preprocess EDSM dumps" | ||||
|     with click.progressbar( | ||||
|         length=100, label="", show_percent=True, item_show_func=lambda v: v, width=50 | ||||
|     ) as pbar: | ||||
|         preproc = Preprocessor(systems, bodies, output) | ||||
|         preproc.start() | ||||
|         state = {} | ||||
|         pstate = {} | ||||
|         while not (preproc.queue.empty() and preproc.is_alive() == False): | ||||
|             try: | ||||
|                 event = preproc.queue.get(True, 0.1) | ||||
|                 state.update(event) | ||||
|                 if state!=pstate: | ||||
|                     pbar.current_item=state.get("status") | ||||
|                     if pbar.current_item: | ||||
|                         pbar.pos=floor(pbar.current_item["prc_done"]*10)/10 | ||||
|                         pbar.update(0) | ||||
|                     pstate=state | ||||
|                 if state != pstate: | ||||
|                     prc = (state["status"]["done"] / state["status"]["total"]) * 100 | ||||
|                     pbar.pos = prc | ||||
|                     pbar.update(0) | ||||
|                     pbar.current_item = state["status"]["message"] | ||||
|                     pstate = state.copy() | ||||
|             except queue.Empty: | ||||
|                 pass | ||||
|         pbar.pos=100 | ||||
|         pbar.pos = 100 | ||||
|         pbar.update(0) | ||||
|     print(state.get("result")) | ||||
|     print("DONE!") | ||||
|     click.pause() | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| @click.option("--path","-i",required=True,help="Path to stars.csv",default="./stars.csv",type=click.Path(exists=True,dir_okay=False),show_default=True ) | ||||
| @click.option("--precomp_file","-pc",help="Precomputed routing graph to use",type=click.Path(exists=True,dir_okay=False)) | ||||
| @click.option("--range","-r",required=True,help="Jump range (Ly)",type=click.FloatRange(min=0)) | ||||
| @click.option("--primary","-ps",help="Only route through primary stars") | ||||
| @click.option("--output","-o",required=True,help="Output path",default="./stars.idx",type=click.Path(exists=False,dir_okay=False),show_default=True ) | ||||
| @click.argument('systems',nargs=-1) | ||||
| def precompute(*args,**kwargs): | ||||
| @click.option( | ||||
|     "--path", | ||||
|     "-i", | ||||
|     required=True, | ||||
|     metavar="<path>", | ||||
|     help="Path to stars.csv", | ||||
|     default=stars_path, | ||||
|     type=click.Path(exists=True, dir_okay=False), | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--precomp_file", | ||||
|     "-pf", | ||||
|     metavar="<path>", | ||||
|     help="Precomputed routing graph to use", | ||||
|     type=click.Path(exists=True, dir_okay=False), | ||||
| ) | ||||
| @click.option( | ||||
|     "--range", | ||||
|     "-r", | ||||
|     default=cfg["route.range"], | ||||
|     metavar="<float>", | ||||
|     help="Jump range (Ly)", | ||||
|     type=click.FloatRange(min=0), | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--prune", | ||||
|     "-d", | ||||
|     default=(cfg["route.prune.steps"], cfg["route.prune.min_improvement"]), | ||||
|     metavar="<n> <m>", | ||||
|     help="Prune search branches", | ||||
|     nargs=2, | ||||
|     type=click.Tuple([click.IntRange(min=0), click.FloatRange(min=0)]), | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--permute", | ||||
|     "-p", | ||||
|     type=click.Choice(["all", "keep_first", "keep_last", "keep_both"]), | ||||
|     default=None, | ||||
|     help="Permute hops to find shortest route", | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--primary/--no-primary", | ||||
|     "+ps/-ps", | ||||
|     is_flag=True, | ||||
|     default=cfg["route.primary"], | ||||
|     help="Only route through primary stars", | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--factor", | ||||
|     "-g", | ||||
|     metavar="<float>", | ||||
|     default=cfg["route.greediness"], | ||||
|     help="Greedyness factor for A-Star", | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--mode", | ||||
|     "-m", | ||||
|     default=cfg["route.mode"], | ||||
|     help="Search mode", | ||||
|     type=click.Choice(["bfs", "a-star", "greedy"]), | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.argument("systems", nargs=-1) | ||||
| def route(**kwargs): | ||||
|     "Compute a route" | ||||
|     if len(kwargs["systems"]) < 2: | ||||
|         exit("Need at least two systems to plot a route") | ||||
|     if kwargs["prune"] == (0, 0): | ||||
|         kwargs["prune"] = None | ||||
| 
 | ||||
|     def to_string(state): | ||||
|         if state: | ||||
|             return "{prc_done:.2f}% [N:{depth} Q:{queue_size} D:{d_rem:.2f} Ly] {system}".format( | ||||
|                 **state | ||||
|             ) | ||||
| 
 | ||||
|     keep_first, keep_last = { | ||||
|         "all": (False, False), | ||||
|         "keep_first": (True, False), | ||||
|         "keep_last": (False, True), | ||||
|         "keep_both": (True, True), | ||||
|         None: (False, False), | ||||
|     }[kwargs["permute"]] | ||||
|     print("Resolving systems...") | ||||
|     t = datetime.today() | ||||
|     matches = find_sys(kwargs["systems"], kwargs["path"]) | ||||
|     kwargs["systems"] = [str(matches[key][1]["id"]) for key in kwargs["systems"]] | ||||
|     print("Done in", datetime.today() - t) | ||||
|     args = [ | ||||
|         kwargs["systems"], | ||||
|         kwargs["range"], | ||||
|         kwargs["prune"], | ||||
|         kwargs["mode"], | ||||
|         kwargs["primary"], | ||||
|         kwargs["permute"] != None, | ||||
|         keep_first, | ||||
|         keep_last, | ||||
|         kwargs["factor"], | ||||
|         None, | ||||
|         kwargs["path"], | ||||
|     ] | ||||
|     with click.progressbar( | ||||
|         length=100, | ||||
|         label="Computing route", | ||||
|         show_percent=False, | ||||
|         item_show_func=to_string, | ||||
|         width=50, | ||||
|     ) as pbar: | ||||
|         router = Router(*args) | ||||
|         t = datetime.today() | ||||
|         router.start() | ||||
|         state = {} | ||||
|         pstate = {} | ||||
|         while not (router.queue.empty() and router.is_alive() == False): | ||||
|             try: | ||||
|                 event = router.queue.get(True, 0.1) | ||||
|                 state.update(event) | ||||
|                 if state != pstate: | ||||
|                     pbar.current_item = state.get("status") | ||||
|                     if pbar.current_item: | ||||
|                         pbar.pos = pbar.current_item["prc_done"] | ||||
|                         pbar.update(0) | ||||
|                     pstate = state.copy() | ||||
|             except queue.Empty: | ||||
|                 pass | ||||
|         pbar.pos = 100 | ||||
|         pbar.update(0) | ||||
|     for n, jump in enumerate(state.get("return", []), 1): | ||||
|         jump["n"] = n | ||||
|         if jump["body"].index(jump["system"]) == -1: | ||||
|             jump["where"] = "[{body}] in [{system}]".format(**jump) | ||||
|         else: | ||||
|             jump["where"] = "[{body}]".format(**jump) | ||||
|         if jump["distance"] > 0: | ||||
|             print("({n}) {where}: {star_type} ({distance} Ls)".format(**jump)) | ||||
|         else: | ||||
|             print("({n}) {where}: {star_type}".format(**jump)) | ||||
|     print("Done in", datetime.today() - t) | ||||
| 
 | ||||
| 
 | ||||
| @main.command() | ||||
| @click.option( | ||||
|     "--path", | ||||
|     "-i", | ||||
|     required=True, | ||||
|     help="Path to stars.csv", | ||||
|     default=stars_path, | ||||
|     type=click.Path(exists=True, dir_okay=False), | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.option( | ||||
|     "--range", "-r", required=True, help="Jump range (Ly)", type=click.FloatRange(min=0) | ||||
| ) | ||||
| @click.option("--primary", "-ps", help="Only route through primary stars") | ||||
| @click.option( | ||||
|     "--output", | ||||
|     "-o", | ||||
|     required=True, | ||||
|     help="Output path", | ||||
|     default="./stars.idx", | ||||
|     type=click.Path(exists=False, dir_okay=False), | ||||
|     show_default=True, | ||||
| ) | ||||
| @click.argument("systems", nargs=-1) | ||||
| def precompute(*args, **kwargs): | ||||
|     "Precompute routing graph" | ||||
|     print("PreComp:",ctx,args,kwargs) | ||||
|     print("PreComp:", args, kwargs) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     MP.freeze_support() | ||||
|     main() | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue