feat(cli): Add config editor

This commit is contained in:
Daniel S. 2019-09-28 15:47:49 +02:00
parent 2c000daae1
commit 8b0b56f130
1 changed files with 366 additions and 74 deletions

View File

@ -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()