update docstrings

This commit is contained in:
Soren I. Bjornstad 2021-08-27 10:06:52 -05:00
parent 0321b7d7b1
commit 97a4611d56
5 changed files with 75 additions and 7 deletions

View File

@ -13,15 +13,26 @@ from tzk.util import BuildError, fail, numerize
class CliCommand(ABC): class CliCommand(ABC):
"""
Base class for subcommands of tzk.
"""
#: The text of the subcommand to be used on the command line.
cmd = None # type: str cmd = None # type: str
#: Help string for argparse to display for this subcommand.
help = None # type: str help = None # type: str
@abstractclassmethod @abstractclassmethod
def setup_arguments(cls, parser: argparse.ArgumentParser) -> None: def setup_arguments(cls, parser: argparse.ArgumentParser) -> None:
"""
Given the :arg:`parser`, add any arguments this subcommand wants to accept.
"""
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def execute(self, parser: argparse.Namespace) -> None: def execute(self, args: argparse.Namespace) -> None:
"""
Given the :arg:`args` passed to this subcommand, do whatever the command does.
"""
raise NotImplementedError raise NotImplementedError
@ -182,21 +193,27 @@ class BuildCommand(CliCommand):
def execute(self, args: argparse.Namespace) -> None: def execute(self, args: argparse.Namespace) -> None:
self._precheck(args.product) self._precheck(args.product)
# Find the build steps for the product the user specified.
steps = cm().products[args.product] steps = cm().products[args.product]
print(f"tzk: Starting build of product '{args.product}'.") print(f"tzk: Starting build of product '{args.product}'.")
print(f"tzk: Found {len(steps)} build {numerize(len(steps), 'step')}.") print(f"tzk: Found {len(steps)} build {numerize(len(steps), 'step')}.")
# For each build step...
for idx, step in enumerate(steps, 1): for idx, step in enumerate(steps, 1):
# Explain what we're doing. Use first line of the builder's docstring
# as a summary, if present.
if hasattr(step, '__doc__'): if hasattr(step, '__doc__'):
short_description = step.__doc__.strip().split('\n')[0].rstrip('.') short_description = step.__doc__.strip().split('\n')[0].rstrip('.')
print(f"tzk: Step {idx}/{len(steps)}: {short_description}") print(f"tzk: Step {idx}/{len(steps)}: {short_description}")
else: else:
print(f"tzk: Step {idx}/{len(steps)}") print(f"tzk: Step {idx}/{len(steps)}")
# If the user asked to skip this builder on the command line, do so.
if step.__name__ in args.skip_builder: if step.__name__ in args.skip_builder:
print(f"tzk: Skipping step {idx} due to --skip-builder parameter.") print(f"tzk: Skipping step {idx} due to --skip-builder parameter.")
continue continue
# Execute step and handle any errors.
try: try:
step() step()
except BuildError as e: except BuildError as e:
@ -231,10 +248,12 @@ def launch():
args = parser.parse_args() args = parser.parse_args()
if not hasattr(args, '_cls'): if not hasattr(args, '_cls'):
# no subcommand was given
parser.print_help() parser.print_help()
sys.exit(0) sys.exit(0)
# For all operations except 'init', we start in the wiki folder. # For all operations except 'init', we start in the wiki folder.
# (For init, we're actually *creating* the wiki folder.)
if not args._cls.cmd == "init": if not args._cls.cmd == "init":
if not cm().wiki_folder: if not cm().wiki_folder:
fail("No 'wiki_folder' option found in config. Set this option to the name " fail("No 'wiki_folder' option found in config. Set this option to the name "

View File

@ -1,3 +1,6 @@
"""
config.py - read and manage the TZK config file
"""
import datetime import datetime
import functools import functools
import importlib import importlib
@ -57,6 +60,14 @@ class ConfigurationManager:
def cm(cache=[]): def cm(cache=[]):
"""
Call this function to retrieve the singleton ConfigurationManager object,
reading and initializing it if necessary.
Since so much happens when the ConfigurationManager is initialized,
this has to go in a function so that autodoc doesn't blow up
when it tries to import the module.
"""
if not cache: if not cache:
cache.append(ConfigurationManager()) cache.append(ConfigurationManager())
return cache[0] return cache[0]

View File

@ -1,11 +1,17 @@
import subprocess import subprocess
from typing import Sequence from typing import Sequence
def exec(*args: str):
return subprocess.check_call(["git", *args])
def rc(*args: str): def exec(*args: str) -> None:
"Execute a Git command, raising CalledProcessError if the exit code is nonzero."
subprocess.check_call(["git", *args])
def rc(*args: str) -> int:
"Execute a Git command, returning the exit code."
return subprocess.call(["git", *args]) return subprocess.call(["git", *args])
def read(*args: str):
def read(*args: str) -> str:
"Execute a Git command, returning the output as a string."
return subprocess.check_output(["git", *args], text=True).strip() return subprocess.check_output(["git", *args], text=True).strip()

View File

@ -20,6 +20,7 @@ def _tw_path() -> str:
@functools.lru_cache(1) @functools.lru_cache(1)
def _whoami() -> str: def _whoami() -> str:
"Try to guess the user's name."
try: try:
return subprocess.check_output(("whoami",), text=True).strip() return subprocess.check_output(("whoami",), text=True).strip()
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
@ -27,6 +28,19 @@ def _whoami() -> str:
def exec(args: Sequence[Sequence[str]], base_wiki_folder: str = None) -> int: def exec(args: Sequence[Sequence[str]], base_wiki_folder: str = None) -> int:
"""
Execute a series of TiddlyWiki commands.
:param args: A list of lists of CLI commands to send to TiddlyWiki.
The first element of each list is a TiddlyWiki CLI command,
without the ``--``, e.g., ``savewikifolder``.
The following elements of the list are arguments to that command.
:param base_wiki_folder: If the wiki to execute commands against is not the one
in the current directory, provide its path here.
The current directory is the source wiki's root directory
during the execution of builders,
unless explicitly changed.
"""
call_args = [_tw_path()] call_args = [_tw_path()]
if base_wiki_folder is not None: if base_wiki_folder is not None:
call_args.append(base_wiki_folder) call_args.append(base_wiki_folder)
@ -98,6 +112,10 @@ def _save_wikifolder_to_config(wiki_name: str) -> bool:
def _add_filesystem_plugins(wiki_name: str) -> None: def _add_filesystem_plugins(wiki_name: str) -> None:
"""
Add the "tiddlywiki/filesystem" and "tiddlywiki/tiddlyweb" plugins
required for Node.js client-server operation to the new wiki's tiddlywiki.info.
"""
print("tzk: Adding filesystem plugins to tiddlywiki.info...") print("tzk: Adding filesystem plugins to tiddlywiki.info...")
info_path = Path.cwd() / wiki_name / "tiddlywiki.info" info_path = Path.cwd() / wiki_name / "tiddlywiki.info"
with info_path.open("r") as f: with info_path.open("r") as f:
@ -108,6 +126,9 @@ def _add_filesystem_plugins(wiki_name: str) -> None:
def _init_gitignore() -> None: def _init_gitignore() -> None:
"""
Create a basic gitignore for the new wiki.
"""
print("tzk: Creating gitignore...") print("tzk: Creating gitignore...")
GITIGNORE = dedent(""" GITIGNORE = dedent("""
__pycache__/ __pycache__/
@ -122,6 +143,9 @@ def _init_gitignore() -> None:
def _initial_commit() -> None: def _initial_commit() -> None:
"""
Create a new Git repo and commit everything we've done so far.
"""
print("tzk: Initializing new Git repository for wiki...") print("tzk: Initializing new Git repository for wiki...")
git.exec("init") git.exec("init")
@ -131,6 +155,9 @@ def _initial_commit() -> None:
def install(wiki_name: str, tw_version_spec: str, author: Optional[str]): def install(wiki_name: str, tw_version_spec: str, author: Optional[str]):
"""
Install TiddlyWiki on Node.js in the current directory and set up a new wiki.
"""
# assert: caller has checked npm and git are installed # assert: caller has checked npm and git are installed
warnings = False warnings = False

View File

@ -1,3 +1,6 @@
"""
util.py - miscellaneous utility functions
"""
from contextlib import contextmanager from contextlib import contextmanager
import os import os
import sys import sys
@ -9,11 +12,13 @@ class BuildError(Exception):
def fail(msg: str, exit_code: int = 1) -> NoReturn: def fail(msg: str, exit_code: int = 1) -> NoReturn:
"Print message to stderr and quit with exit code 1."
print(msg, file=sys.stderr) print(msg, file=sys.stderr)
sys.exit(exit_code) sys.exit(exit_code)
def numerize(number: int, singular: str, plural: str = None): def numerize(number: int, singular: str, plural: str = None) -> str:
"Render a string in the singular or plural as appropriate."
if plural is None: if plural is None:
plural = singular + 's' plural = singular + 's'