Source code for doot.cmds.help_cmd

  1#!/usr/bin/env python3
  2"""
  3
  4"""
  5# Imports:
  6from __future__ import annotations
  7
  8# ##-- stdlib imports
  9import datetime
 10import enum
 11import functools as ftz
 12import itertools as itz
 13import logging as logmod
 14import pathlib as pl
 15import re
 16import time
 17import types
 18from collections import defaultdict
 19from uuid import UUID, uuid1
 20
 21# ##-- end stdlib imports
 22
 23# ##-- 3rd party imports
 24from jgdv import Proto, Mixin
 25from jgdv.structs.strang import CodeReference
 26from jgdv.cli.param_spec import ParamSpec
 27from jgdv.cli._interface import NON_DEFAULT_KEY
 28# ##-- end 3rd party imports
 29
 30# ##-- 1st party imports
 31import doot
 32from ._base import BaseCommand
 33from ._interface import Command_p
 34from doot.workflow._interface import Task_p
 35
 36# ##-- end 1st party imports
 37
 38# ##-- types
 39# isort: off
 40import abc
 41import collections.abc
 42from typing import TYPE_CHECKING, cast, assert_type, assert_never
 43from typing import Generic, NewType
 44# Protocols:
 45from typing import Protocol, runtime_checkable
 46# Typing Decorators:
 47from typing import no_type_check, final, override, overload
 48
 49if TYPE_CHECKING:
 50    from jgdv import Maybe
 51    from jgdv.structs.chainguard import ChainGuard
 52    from typing import Final
 53    from typing import ClassVar, Any, LiteralString
 54    from typing import Never, Self, Literal
 55    from typing import TypeGuard
 56    from collections.abc import Iterable, Iterator, Callable, Generator
 57    from collections.abc import Sequence, Mapping, MutableMapping, Hashable
 58    from doot.workflow._interface import TaskSpec_i
 59
 60# isort: on
 61# ##-- end types
 62
 63##-- logging
 64logging = logmod.getLogger(__name__)
 65##-- end logging
 66
 67LINE_SEP        : Final[str] = "------------------------------"
 68GROUP_INDENT    : Final[str] = "----"
 69ITEM_INDENT     : Final[str] = "-"
 70
[docs] 71class _HelpDoot_m: 72
[docs] 73 def _doot_help(self, plugins:ChainGuard) -> list[str]: 74 result = [] 75 # Print general help and list cmds 76 result.append("Doot Help Command: No Target Specified/Matched") 77 result.append("Available Command Targets: ") 78 for x in sorted(plugins.command, key=lambda x: x.name): 79 result.append(f"-- {x.name}") 80 else: 81 result.append("\n------------------------------") 82 result.append("Call a command by doing 'doot [cmd] [args]'. Eg: doot list --help") 83 return result
84
[docs] 85class _HelpCmd_m: 86
[docs] 87 def _cmd_help(self, *, idx:int, cmd:Any) -> list[str]: 88 result = [] 89 cmd_class = cmd.load() 90 cmd_instance = cmd_class() 91 result += cmd_instance.help 92 return result
93 94
[docs] 95 def _cmd_param_assignments(self, idx:int, cmd:Any) -> list[Maybe[str]]: 96 result : list[Maybe[str]] = [] 97 result.append(None) 98 result.append(f"{GROUP_INDENT} Parameters:") 99 100 max_param_len = 5 + ftz.reduce(max, map(len, (x.name for x in cmd.param_specs())), 0) 101 fmt_str = f"> %{max_param_len}s : (%-5s) : %s " 102 args = doot.args.cmds[self.name][idx].args # type: ignore[attr-defined] 103 last_prefix = None 104 for param in sorted(cmd.param_specs(), key=ParamSpec.key_func): 105 if last_prefix and last_prefix != param.prefix: 106 result.append(None) 107 last_prefix = param.prefix 108 match param.type_: 109 case type() as x: 110 type_ = x.__name__ 111 case x: 112 type_ = x 113 match args.get(param.name, None): 114 case _ if param.name == "help": 115 pass 116 case None: 117 result.append(fmt_str % (param.key_str, type_, f"default: {param.default}")) 118 case val if val == param.default: 119 result.append(fmt_str % (param.key_str, type_, f"default: {val}")) 120 case val: 121 result.append(fmt_str % (param.key_str, type_, val)) 122 else: 123 result.append(None) 124 return result
125
[docs] 126class _HelpTask_m: 127
[docs] 128 def _task_help(self, count:int, spec:TaskSpec_i) -> list[Maybe[str]]: 129 """ Print the help for a task spec """ 130 result : list[Maybe[str]] 131 task_name : str 132 ctor : Maybe[Task_p|type[Task_p]] 133 ##--| 134 task_name = str(spec.name) 135 result = [ 136 "", 137 LINE_SEP, 138 f"{count:4}: Task: {task_name}", 139 LINE_SEP, 140 f"ver : {spec.version}", 141 f"Group : {spec.name[0,:]}", 142 ] 143 144 sources = "; ".join([str(x) for x in spec.sources]) 145 result.append(f"Sources : {sources}") 146 147 match spec.doc: 148 case None: 149 pass 150 case str() as x: 151 result += [None, x, None] 152 case [*xs]: 153 result.append(None) 154 result += xs 155 result.append(None) 156 157 match spec.ctor: 158 case None: 159 ctor = None 160 case CodeReference(): 161 ctor = spec.ctor(raise_error=True) 162 case _: 163 ctor = spec.ctor 164 165 if ctor is not None: 166 result.append(f"{GROUP_INDENT} Ctor Class:") 167 result += ctor.class_help() 168 result.append(None) 169 170 extra_keys = set(spec.extra.keys()) - {"cli"} 171 if bool(extra_keys): 172 result.append(None) 173 result.append(f"{GROUP_INDENT} Toml Parameters:") 174 for kwarg,val in spec.extra.items(): 175 if kwarg == "cli": 176 continue 177 result.append(f"{ITEM_INDENT} {kwarg:-20} : {val}") 178 result.append(None) 179 180 if bool(spec.actions): 181 result.append(f"{GROUP_INDENT} Task Actions: ") 182 sub_indent = (1 + len(ITEM_INDENT)) * " " 183 for action in spec.actions: 184 result.append(f"{ITEM_INDENT} {action.do[1:,:]:>60} :") 185 186 result += self._task_param_assignments(spec) 187 188 return result
189
[docs] 190 def _task_param_assignments(self, spec:TaskSpec_i) -> list: 191 result : list[Maybe[str]] 192 ##--| 193 if not bool(spec.param_specs()): 194 return [] 195 196 result = [] 197 result.append(None) 198 result.append(f"{GROUP_INDENT} Parameters:") 199 200 max_param_len = 5 + ftz.reduce(max, map(len, (x.key_str for x in spec.param_specs())), 0) 201 fmt_str = f"> %{max_param_len}s : (%5s) : %s " 202 args = doot.args.subs[spec.name] 203 last_prefix = None 204 for param in sorted(spec.param_specs(), key=ParamSpec.key_func): 205 if last_prefix and last_prefix != param.prefix: 206 result.append(None) 207 match param.type_: 208 case type() as x: 209 type_ = x.__name__ 210 case x: 211 type_ = x 212 match args.get(param.name, None): 213 case _ if param.name == "help": 214 pass 215 case None: 216 result.append(fmt_str % (param.key_str, type_, f"default: {param.default}")) 217 case val if val == param.default: 218 result.append(fmt_str % (param.key_str, type_, f"default: {val}")) 219 case val: 220 result.append(fmt_str % (param.key_str, type_, val)) 221 else: 222 result.append(None) 223 return result
224 225 226##--|
[docs] 227@Proto(Command_p) 228@Mixin(_HelpDoot_m, _HelpCmd_m, _HelpTask_m) 229class HelpCmd(BaseCommand): 230 _name = "help" 231 _help = ("Print info about the specified cmd or task", 232 "Can also be triggered by passing --help to any command or task", 233 ) 234
[docs] 235 @override 236 def param_specs(self) -> list: 237 return [ 238 *super().param_specs(), 239 self.build_param(name="<1>target", type=str, default="", desc="The target to get help about. A command or task."), 240 ]
241 242 def __call__(self, *, idx:int, tasks:ChainGuard, plugins:ChainGuard): # noqa: PLR0912 243 """List task generators""" 244 task_targets : list 245 cmd_targets : list 246 self_help : bool 247 ##--| 248 task_targets = [] 249 cmd_targets = [] 250 self_help = False 251 match dict(doot.args.cmds[self.name][idx].args): 252 case {"target": ""|None} if bool(doot.args.subs): 253 # No target, generate list of all tasks 254 task_targets += [tasks[x] for x in doot.args.subs.keys()] 255 case {"target": ""|None} | {"help":True}: 256 self_help = True 257 case {"target": x} if x in doot.cmd_aliases: 258 aliased = doot.cmd_aliases[x][0] 259 cmd_targets += [x for x in plugins.command if x.name == aliased] 260 case {"target": target}: 261 # Print help of just the specified target(s) 262 task_targets += [y for x,y in tasks.items() if x in target] 263 cmd_targets += [x for x in plugins.command if x.name == target] 264 case {"help": True}: 265 self_help = True 266 267 268 logging.debug("Matched %s commands, %s tasks", len(cmd_targets), len(task_targets)) 269 doot.report.active_level(logmod.INFO) 270 match cmd_targets: 271 case []: 272 pass 273 case [x]: 274 result = self._cmd_help(idx=idx, cmd=x) # type: ignore[attr-defined] 275 self._print_text(result) 276 return 277 case [*xs]: 278 doot.report.gen.error("To print help for a command, choose 1 command at a time") 279 return 280 281 match task_targets: 282 case []: 283 pass 284 case [*xs]: 285 result = [] 286 for i, spec in enumerate(task_targets): 287 result += self._task_help(i, spec) # type: ignore[attr-defined] 288 else: 289 self._print_text(result) 290 return 291 292 match self_help: 293 case True: 294 result = self.help 295 self._print_text(result) 296 case False: 297 result = self._doot_help(plugins) # type: ignore[attr-defined] 298 self._print_text(result)