feat(cli): Add config editor

This commit is contained in:
Daniel S. 2019-09-28 15:47:49 +02:00
parent 2c000daae1
commit 8b0b56f130

View file

@ -2,78 +2,344 @@ import sys
import multiprocessing as MP import multiprocessing as MP
import queue import queue
import ctypes import ctypes
import os
from datetime import datetime
from math import floor from math import floor
import click import click
from tqdm import tqdm
from click_default_group import DefaultGroup from click_default_group import DefaultGroup
import requests as RQ import requests as RQ
from ed_lrr_gui import Router from urllib.parse import urljoin
from ed_lrr_gui import Preprocessor from ed_lrr_gui import Router, Preprocessor, cfg
import ed_lrr_gui.gui as ED_LRR_GUI from _ed_lrr import find_sys
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
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.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
@click.pass_context @click.pass_context
@click.version_option()
def main(ctx): def main(ctx):
"Elite: Dangerous long range router, command line interface" "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: if ctx.invoked_subcommand is None:
ctx.invoke(gui) ctx.invoke(gui)
return return
return return
@main.command()
@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() @main.command()
@click.option("--debug", help="Debug print", is_flag=True) @click.option("--debug", help="Debug print", is_flag=True)
def gui(debug): def gui(debug):
"Run the ED LRR GUI (default)" "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() ctypes.windll.kernel32.FreeConsole()
sys.stdin = open("NUL", "rt") sys.stdin = open("NUL", "rt")
sys.stdout = open("NUL", "wt") sys.stdout = open("NUL", "wt")
sys.stderr = open("NUL", "wt") sys.stderr = open("NUL", "wt")
sys.exit(ED_LRR_GUI.main()) sys.exit(ED_LRR_GUI.main())
@main.command() @main.command()
@click.option("--url","-u",help="Base URL",default="https://www.edsm.net/dump/",show_default=True) @click.option(
@click.option("--systems","-s",help="Target path for systemsWithCoordinates.json",default="systemsWithCoordinates.json",show_default=True) "--url",
@click.option("--bodies","-b",help="Target path for bodies.json",default="bodies.json",show_default=True) "-u",
def download(*args,**kwargs): 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" "Download EDSM dumps"
print("Download:",args,kwargs) 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:
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() click.pause()
@main.command() @main.command()
def preprocess(*args,**kwargs): @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" "Preprocess EDSM dumps"
print("PreProcess:",ctx,args,kwargs) 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:
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.update(0)
print(state.get("result"))
print("DONE!")
click.pause() click.pause()
@main.command() @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(
@click.option("--precomp_file","-pf",metavar="<path>",help="Precomputed routing graph to use",type=click.Path(exists=True,dir_okay=False)) "--path",
@click.option("--range","-r",required=True,metavar="<float>",help="Jump range (Ly)",type=click.FloatRange(min=0)) "-i",
@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)])) required=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) metavar="<path>",
@click.option("--primary","-ps",is_flag=True,default=False,help="Only route through primary stars") help="Path to stars.csv",
@click.option("--factor","-g",metavar="<float>",default=0.5,help="Greedyness factor for A-Star",show_default=True) default=stars_path,
@click.option("--mode","-m",default="bfs",help="Search mode",type=click.Choice(["bfs","a-star","greedy"]),show_default=True) type=click.Path(exists=True, dir_okay=False),
@click.argument('systems',nargs=-1) 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): def route(**kwargs):
"Compute a route" "Compute a route"
if kwargs['prune']==(0,0): if len(kwargs["systems"]) < 2:
kwargs['prune']=None exit("Need at least two systems to plot a route")
if kwargs["prune"] == (0, 0):
kwargs["prune"] = None
def to_string(state): def to_string(state):
if state: if state:
return "[{}] {}".format(state['depth'],state['system']) return "{prc_done:.2f}% [N:{depth} Q:{queue_size} D:{d_rem:.2f} Ly] {system}".format(
**state
)
keep_first, keep_last = { keep_first, keep_last = {
"all": (False, False), "all": (False, False),
"keep_first": (True, False), "keep_first": (True, False),
"keep_last": (False, True), "keep_last": (False, True),
"keep_both": (True, True), "keep_both": (True, True),
None: (False,False) None: (False, False),
}[kwargs['permute']] }[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']] print("Resolving systems...")
with click.progressbar(length=100,label="Computing route",show_percent=True,item_show_func=to_string,width=50) as pbar: 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) router = Router(*args)
t = datetime.today()
router.start() router.start()
state = {} state = {}
pstate = {} pstate = {}
@ -84,28 +350,54 @@ def route(**kwargs):
if state != pstate: if state != pstate:
pbar.current_item = state.get("status") pbar.current_item = state.get("status")
if pbar.current_item: if pbar.current_item:
pbar.pos=floor(pbar.current_item["prc_done"]*10)/10 pbar.pos = pbar.current_item["prc_done"]
pbar.update(0) pbar.update(0)
pstate=state pstate = state.copy()
except queue.Empty: except queue.Empty:
pass pass
pbar.pos = 100 pbar.pos = 100
pbar.update(0) pbar.update(0)
print(state.get("result")) for n, jump in enumerate(state.get("return", []), 1):
print("DONE!") 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() @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(
@click.option("--precomp_file","-pc",help="Precomputed routing graph to use",type=click.Path(exists=True,dir_okay=False)) "--path",
@click.option("--range","-r",required=True,help="Jump range (Ly)",type=click.FloatRange(min=0)) "-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("--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.option(
@click.argument('systems',nargs=-1) "--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): def precompute(*args, **kwargs):
"Precompute routing graph" "Precompute routing graph"
print("PreComp:",ctx,args,kwargs) print("PreComp:", args, kwargs)
if __name__ == '__main__': if __name__ == "__main__":
MP.freeze_support()
main() main()