Skip to content

Commit

Permalink
First steps towards remaking the CLI using Click
Browse files Browse the repository at this point in the history
It currently implements the "auth", "scan" and "set-default" commands from the
existing CLI, plus handles the config file centrally to make it easier for the
sub-commands to work with.

The new code is also fully typed.
  • Loading branch information
Tenzer committed Dec 12, 2023
1 parent 0ff499b commit 1796b5c
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
100 changes: 100 additions & 0 deletions LGTV/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import json
import logging
import sys
from time import sleep
from typing import Optional

import click

from LGTV.conf import read_config, write_config
from LGTV.scan import LGTVScan
from LGTV.auth import LGTVAuth


@click.group
@click.option("-d", "--debug", is_flag=True, help="Enable debug output.")
@click.option("-n", "--name", help="Name of the TV to manage.")
@click.pass_context
def cli(ctx: click.Context, debug: bool = False, name: Optional[str] = None) -> None:
"""Command line webOS remote for LG TVs."""
logging.basicConfig(level=logging.DEBUG if debug else logging.INFO)

config_path, full_config = read_config()
name = name or full_config.get("_default")

possible_tvs = list(full_config.keys())
try:
possible_tvs.remove("_default")
except ValueError:
pass

if name and name not in possible_tvs:
click.secho(
f"No entry with the name '{name}' was found in the configuration at {config_path}. Names found: {', '.join(possible_tvs)}",
fg="red",
err=True,
)
sys.exit(1)

tv_config = full_config.get(name, {})
ctx.obj = {
"config_path": config_path,
"full_config": full_config,
"tv_name": name,
"tv_config": tv_config,
}


@cli.command
def scan() -> None:
"""Scan the local network for LG TVs."""
results = LGTVScan()

if len(results) > 0:
click.echo(json.dumps({"result": "ok", "count": len(results), "list": results}))
sys.exit(0)
else:
click.echo(json.dumps({"result": "failed", "count": len(results)}))
sys.exit(1)


@cli.command
@click.argument("host")
@click.argument("name")
@click.option("-s", "--ssl", is_flag=True, help="Connect to TV using SSL.")
@click.pass_context
def auth(ctx: click.Context, host: str, name: str, ssl: bool = False) -> None:
"""Connect to a new TV."""
if name.startswith("_"):
click.secho(
"TV names are not allowed to start with an underscore", fg="red", err=True
)
sys.exit(1)

ws = LGTVAuth(name, host, ssl=ssl)
ws.connect()
ws.run_forever()
sleep(1)
config = ctx.obj["full_config"]
config[name] = ws.serialise()
write_config(ctx.obj["config_path"], config)
click.echo(f"Wrote config file: {ctx.obj['config_path']}")


@cli.command
@click.argument("name")
@click.pass_context
def set_default(ctx: click.Context, name: str) -> None:
"""Change the default TV to interact with."""
config = ctx.obj["full_config"]
if name == "_default" or name not in config:
click.secho("TV not found in config", fg="red", err=True)
sys.exit(1)

config["_default"] = name
write_config(ctx.obj["config_path"], config)
click.echo(f"Default TV set to '{name}'")


if __name__ == "__main__":
cli()
41 changes: 41 additions & 0 deletions LGTV/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import json
import sys
from pathlib import Path
from typing import Dict, Tuple

# TODO: Should this be replaced with click.get_app_dir()?
search_paths = [
"/etc/lgtv/config.json",
"~/.lgtv/config.json",
"/opt/venvs/lgtv/config/config.json",
]


def read_config() -> Tuple[Path, Dict]:
# Check for existing config files
for path in map(Path, search_paths):
path = path.expanduser()
if path.exists():
with path.open() as fp:
return path, json.load(fp)

# Attempt to find place to write new config file
for path in map(Path, search_paths):
path = path.expanduser()

try:
path.parent.mkdir(parents=True, exist_ok=True)
return path, {}
except (FileExistsError, PermissionError):
pass

print(
"Cannot find suitable config path to write, create one in",
" or ".join(search_paths),
)
sys.exit(1)


def write_config(path: Path, config: Dict) -> None:
with path.open("w") as fp:
json.dump(config, fp)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ wakeonlan==1.1.6
ws4py==0.5.1
requests==2.31.0
getmac==0.9.2
click==8.1.7
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
'ws4py',
'requests',
'getmac',
'click>=8.1.0',
],
data_files=[
('config', ['data/config.json'])
Expand Down

0 comments on commit 1796b5c

Please sign in to comment.