Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions novem/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@
from ..api_ref import NovemAPI
from ..utils import cl, colors, get_config_path, get_current_config
from ..version import __version__
from .common import grid, mail, plot
from .common import grid, job, mail, plot
from .config import check_if_profile_exists, update_config
from .group import group
from .invite import invite
from .setup import setup
from .vis import list_jobs

sys.tracebacklimit = 0

Expand Down Expand Up @@ -416,9 +415,7 @@ def run_cli_wrapped() -> None:
elif args and args["grid"] != "":
grid(args)
elif args and args["job"] != "":
# Job listing only (no individual job operations yet)
if args["job"] is None:
list_jobs(args)
job(args)
elif args and args["invite"] != "":
invite(args)
elif args and ("group" in args or "org" in args):
Expand Down
170 changes: 167 additions & 3 deletions novem/cli/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import sys
from typing import Any, Dict, Literal, Optional

from novem import Grid, Mail, Plot
from novem import Grid, Job, Mail, Plot
from novem.api_ref import Novem404, NovemAPI
from novem.cli.editor import edit
from novem.cli.setup import Share
from novem.cli.vis import list_vis, list_vis_shares
from novem.cli.setup import Share, Tag
from novem.cli.vis import list_job_shares, list_job_tags, list_jobs, list_vis, list_vis_shares, list_vis_tags
from novem.utils import data_on_stdin
from novem.vis import NovemVisAPI

Expand Down Expand Up @@ -215,6 +215,23 @@ def __call__(self, args: Dict[str, Any]) -> None:
list_vis_shares(name, args, self.title)
return

# check if we are changing any tags (supports multiple comma-separated tags)
tag_op, tag_targets = args["tag"]
if tag_op is Tag.CREATE:
# add tags to the vis
for tag_target in tag_targets:
vis.tags += tag_target # type: ignore

if tag_op is Tag.DELETE:
# remove tags from the vis
for tag_target in tag_targets:
vis.tags -= tag_target # type: ignore

if tag_op is Tag.LIST:
# check if we should print our tags, we will not provide other outputs
list_vis_tags(name, args, self.title)
return

# E-mail needs sending/testing
if isinstance(vis, Mail):
mail: Mail = vis
Expand Down Expand Up @@ -260,3 +277,150 @@ def grid(args: Dict[str, Any]) -> None:
def plot(args: Dict[str, Any]) -> None:
plot = VisBase("plot")
plot(args)


def job(args: Dict[str, Any]) -> None:
name = args["job"]

# List all jobs
if name is None:
list_jobs(args)
return

# Delete job
if args["delete"]:
novem = NovemAPI(**args, is_cli=True)
try:
novem.delete(f"jobs/{name}")
return
except Novem404:
print(f"Job {name} did not exist")
sys.exit(1)

# Create Job object
ignore_ssl = False
if "ignore_ssl" in args:
ignore_ssl = args["ignore_ssl"]

create = args["create"]

j = Job(
name,
ignore_ssl=ignore_ssl,
create=create,
config_path=args["config_path"],
qpr=args.get("qpr"),
debug=args.get("debug"),
config_profile=args["profile"],
is_cli=True,
)

# -R (run): trigger job execution
if args.get("run_job"):
j.run()
return

# --dump: dump entire API tree to file
if "dump" in args and args["dump"]:
path = args["dump"]
print(f'Dumping api tree structure to "{path}"')
j.api_dump(outpath=path)
return

# --tree: print API tree structure
if "tree" in args and args["tree"] != -1:
path = args["tree"]
if not path:
path = "/"
ts = j.api_tree(colors=True, relpath=path)
print(ts)
return

# -e (edit): edit a path in the editor
if "edit" in args and args["edit"]:
path = args["edit"]

# fetch target
ctnt = j.api_read(f"/{path}")

# get new content
nctnt = edit(contents=ctnt, use_tty=True)

if ctnt != nctnt:
# update content
j.api_write(f"/{path}", nctnt)

else:
# --type
ptype = args["type"]
if ptype:
j.type = ptype

found_stdin = False
stdin_data = data_on_stdin()
stdin_has_data = bool(stdin_data)

# check if we have any explicit inputs [-w's]
if args["input"] and len(args["input"]):
for i in args["input"]:
path = f"/{i[0]}"

if len(i) == 1:
if stdin_has_data and not found_stdin:
assert stdin_data
ctnt = stdin_data

j.api_write(path, ctnt)
found_stdin = True
elif found_stdin:
print("stdin can only be sent to a single destination per invocation")
sys.exit(1)
else:
print(f'No data found on stdin, "-w {path}" requires data to be supplied on stdin')
sys.exit(1)

elif len(i) == 2 and i[1][0] == "@":
fn = os.path.expanduser(i[1][1:])
try:
with open(fn, "r") as f:
ctnt = f.read()
j.api_write(path, ctnt)
except FileNotFoundError:
print(f'The supplied input file "{fn}" does not exist. Please review your options')
sys.exit(1)

else:
ctnt = i[1]
j.api_write(path, ctnt)

# -s (share): manage shares
share_op, share_target = args["share"]
if share_op is Share.CREATE:
j.shared += share_target # type: ignore

if share_op is Share.DELETE:
j.shared -= share_target # type: ignore

if share_op is Share.LIST:
list_job_shares(name, args)
return

# -t (tag): manage tags (supports multiple comma-separated tags)
tag_op, tag_targets = args["tag"]
if tag_op is Tag.CREATE:
for tag_target in tag_targets:
j.tags += tag_target # type: ignore

if tag_op is Tag.DELETE:
for tag_target in tag_targets:
j.tags -= tag_target # type: ignore

if tag_op is Tag.LIST:
list_job_tags(name, args)
return

# -r (read output)
out = args["out"]
if out:
outp = j.api_read(f"/{out}")
print(outp, end="")
48 changes: 46 additions & 2 deletions novem/cli/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ class Share(Enum):
LIST = 3


class Tag(Enum):
NOT_GIVEN = 0
CREATE = 1
DELETE = 2
LIST = 3


def formatter(prog: str) -> ap.RawDescriptionHelpFormatter:
return ap.RawDescriptionHelpFormatter(prog, width=width)

Expand Down Expand Up @@ -187,6 +194,17 @@ def setup(raw_args: Any = None) -> Tuple[Any, Dict[str, str]]:
help="select a share group to operate on, no parameter will list all current shares",
)

vis.add_argument(
"-t",
dest="tag",
action="store",
required=False,
default="",
nargs="?",
help="select a tag to operate on (fav, like, ignore, wip, archived, or +usertag), "
"no parameter will list all current tags",
)

vis.add_argument(
"-l",
dest="list",
Expand Down Expand Up @@ -422,6 +440,13 @@ def setup(raw_args: Any = None) -> Tuple[Any, Dict[str, str]]:
help="select job to operate on, no parameter will list all your jobs",
)

job.add_argument(
"-R",
dest="run_job",
action="store_true",
help="run the job",
)

invite = parser.add_argument_group("invite")

invite.add_argument(
Expand Down Expand Up @@ -515,12 +540,31 @@ def setup(raw_args: Any = None) -> Tuple[Any, Dict[str, str]]:
elif share is None:
args["share"] = (Share.LIST, None)
elif args["create"]:
args["create"] = None
args["create"] = False
args["share"] = (Share.CREATE, share)
elif args["delete"]:
args["delete"] = None
args["delete"] = False
args["share"] = (Share.DELETE, share)
else:
args["share"] = None

# fix up the --tag option (supports comma-separated tags like -t fav,+demo,+test)
tag = args.pop("tag")
if tag == "":
args["tag"] = (Tag.NOT_GIVEN, None)
elif tag is None:
args["tag"] = (Tag.LIST, None)
elif args["create"]:
args["create"] = False
# Split by comma to support multiple tags
tags = [t.strip() for t in tag.split(",") if t.strip()]
args["tag"] = (Tag.CREATE, tags)
elif args["delete"]:
args["delete"] = False
# Split by comma to support multiple tags
tags = [t.strip() for t in tag.split(",") if t.strip()]
args["tag"] = (Tag.DELETE, tags)
else:
args["tag"] = None

return (parser, args)
Loading