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 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'])
|
|
||||||
|
|
||||||
@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.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()
|
@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):
|
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)
|
||||||
click.pause()
|
for file_name in ["systemsWithCoordinates.json", "bodies.json"]:
|
||||||
|
download_url = urljoin(url, file_name)
|
||||||
@main.command()
|
download_path = os.path.join(folder, file_name)
|
||||||
def preprocess(*args,**kwargs):
|
if os.path.isfile(download_path):
|
||||||
"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):
|
|
||||||
try:
|
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)
|
state.update(event)
|
||||||
if state!=pstate:
|
if state != pstate:
|
||||||
pbar.current_item=state.get("status")
|
prc = (state["status"]["done"] / state["status"]["total"]) * 100
|
||||||
if pbar.current_item:
|
pbar.pos = prc
|
||||||
pbar.pos=floor(pbar.current_item["prc_done"]*10)/10
|
pbar.update(0)
|
||||||
pbar.update(0)
|
pbar.current_item = state["status"]["message"]
|
||||||
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"))
|
print(state.get("result"))
|
||||||
print("DONE!")
|
print("DONE!")
|
||||||
|
click.pause()
|
||||||
|
|
||||||
|
|
||||||
@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",
|
||||||
@click.option("--primary","-ps",help="Only route through primary stars")
|
required=True,
|
||||||
@click.option("--output","-o",required=True,help="Output path",default="./stars.idx",type=click.Path(exists=False,dir_okay=False),show_default=True )
|
metavar="<path>",
|
||||||
@click.argument('systems',nargs=-1)
|
help="Path to stars.csv",
|
||||||
def precompute(*args,**kwargs):
|
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"
|
"Precompute routing graph"
|
||||||
print("PreComp:",ctx,args,kwargs)
|
print("PreComp:", args, kwargs)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
MP.freeze_support()
|
main()
|
||||||
main()
|
|
||||||
|
|
Loading…
Reference in a new issue