diff --git a/.coderabbit.yaml b/.coderabbit.yaml index b0b0550f..81ce66ea 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -3,7 +3,7 @@ language: "en-US" early_access: false reviews: - profile: "chill" + profile: "assertive" poem: false review_status: true collapse_walkthrough: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c98f449a..452cba59 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -170,6 +170,7 @@ jobs: git commit -m "Initial commit" - run: dfetch init - run: dfetch environment + - run: dfetch add https://github.com/dfetch-org/test-repo - run: dfetch validate - run: dfetch check - run: dfetch update @@ -177,14 +178,14 @@ jobs: - run: | git add -A git commit -m "Fetched dependencies" - echo "An extra line" >> jsmn/README.md - git add jsmn/README.md + echo "An extra line" >> test-repo/README.md + git add test-repo/README.md git commit -m "Update README.md" - - run: dfetch diff jsmn + - run: dfetch diff test-repo - run: | - echo " patch: jsmn.patch" >> dfetch.yaml + echo " patch: test-repo.patch" >> dfetch.yaml git add -A - git commit -m "Patch jsmn" + git commit -m "Patch test-repo" - run: dfetch update-patch - run: dfetch format-patch - run: dfetch report -t sbom diff --git a/.github/workflows/run.yml b/.github/workflows/run.yml index 90391f44..3e4e69d3 100644 --- a/.github/workflows/run.yml +++ b/.github/workflows/run.yml @@ -38,6 +38,8 @@ jobs: run: pip install . - run: dfetch environment + - run: dfetch environment + - run: dfetch add https://github.com/dfetch-org/test-repo - run: dfetch validate - run: dfetch check - run: dfetch update @@ -111,6 +113,7 @@ jobs: run: pip install . - run: dfetch environment + - run: dfetch add https://github.com/dfetch-org/test-repo - run: dfetch validate - run: dfetch check - run: dfetch update diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b3391a48..db9f4ac0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Release 0.13.0 (unreleased) * Make ``dfetch report`` output more yaml-like (#1017) * Don't break when importing submodules with space in path (#1017) * Warn when ``src:`` glob pattern matches multiple directories (#1017) +* Introduce new ``add`` command with optional interactive mode (``-i``) (#25) Release 0.12.1 (released 2026-02-24) ==================================== diff --git a/dfetch/__main__.py b/dfetch/__main__.py index 8edfd8c4..0eb9984b 100644 --- a/dfetch/__main__.py +++ b/dfetch/__main__.py @@ -9,6 +9,7 @@ from rich.console import Console +import dfetch.commands.add import dfetch.commands.check import dfetch.commands.diff import dfetch.commands.environment @@ -43,6 +44,7 @@ def create_parser() -> argparse.ArgumentParser: parser.set_defaults(func=_help) subparsers = parser.add_subparsers(help="commands") + dfetch.commands.add.Add.create_menu(subparsers) dfetch.commands.check.Check.create_menu(subparsers) dfetch.commands.diff.Diff.create_menu(subparsers) dfetch.commands.environment.Environment.create_menu(subparsers) diff --git a/dfetch/commands/add.py b/dfetch/commands/add.py new file mode 100644 index 00000000..b58a478d --- /dev/null +++ b/dfetch/commands/add.py @@ -0,0 +1,667 @@ +"""*Dfetch* can add projects to the manifest through the CLI. + +Sometimes you want to add a project to your manifest, but you don't want to +edit the manifest by hand. With ``dfetch add`` you can add a project to your +manifest through the command line. + +Non-interactive mode +-------------------- +In the simplest form you just provide the URL:: + + dfetch add https://github.com/some-org/some-repo.git + +Dfetch fetches remote metadata (branches, tags), picks the default branch, +guesses a destination path from your existing projects, shows a preview, and +appends the entry to ``dfetch.yaml`` immediately without prompting. + +Override any field with explicit flags:: + + dfetch add --name mylib --dst ext/mylib --version v2.0 --src lib https://github.com/some-org/some-repo.git + +Interactive mode +---------------- +Use ``--interactive`` (``-i``) for a guided, step-by-step wizard:: + + dfetch add -i https://github.com/some-org/some-repo.git + +Pre-fill individual fields to skip specific prompts:: + + dfetch add -i --version main --src lib/core https://github.com/some-org/some-repo.git + +The wizard walks through: + +* **name** - defaults to the repository name extracted from the URL +* **dst** - local destination; defaults to a path guessed from existing projects +* **version** - scrollable list of all branches and tags (arrow keys to + navigate, Enter to select, Esc to fall back to free-text input) +* **src** - optional sub-path; browse the remote tree with arrow keys, + expand/collapse folders with Enter/Right/Left +* **ignore** - optional list of paths to exclude; same tree browser with + Space to toggle multiple selections and Enter to confirm + +After confirming the add you are offered to run ``dfetch update`` immediately +so the dependency is materialised without a separate command. + +.. scenario-include:: ../features/add-project-through-cli.feature +""" + +from __future__ import annotations + +import argparse +import contextlib +import dataclasses +from collections.abc import Generator + +from rich.prompt import Confirm, Prompt + +import dfetch.commands.command +import dfetch.manifest.project +import dfetch.project +from dfetch import terminal +from dfetch.log import get_logger +from dfetch.manifest.manifest import Manifest, append_entry_manifest_file +from dfetch.manifest.project import ProjectEntry, ProjectEntryDict +from dfetch.manifest.remote import Remote +from dfetch.manifest.version import Version +from dfetch.project import create_sub_project, create_super_project +from dfetch.project.gitsubproject import GitSubProject +from dfetch.project.subproject import SubProject +from dfetch.project.superproject import SuperProject +from dfetch.project.svnsubproject import SvnSubProject +from dfetch.terminal import Entry, LsFunction +from dfetch.terminal.tree_browser import ( + BrowserConfig, + TreeNode, + deselected_paths, + run_tree_browser, + tree_pick_from_names, + tree_single_pick, +) +from dfetch.util.purl import vcs_url_to_purl +from dfetch.util.versions import ( + is_commit_sha, + prioritise_default, + sort_tags_newest_first, +) + +logger = get_logger(__name__) + + +@dataclasses.dataclass +class _AddContext: + """Remote metadata and manifest state gathered before running any flow.""" + + url: str + default_name: str + default_dst: str + default_branch: str + subproject: SubProject + remote_to_use: Remote | None + manifest: Manifest + + +@dataclasses.dataclass +class _Overrides: + """Fields explicitly supplied on the command line (``None`` = not given).""" + + name: str | None = None + dst: str | None = None + version: str | None = None + src: str | None = None + ignore: list[str] | None = None + + +@contextlib.contextmanager +def browse_tree(subproject: SubProject, version: str = "") -> Generator[LsFunction]: + """Yield an ``LsFunction`` for interactively browsing *subproject*'s remote tree. + + Adapts the VCS-level ``(name, is_dir)`` tuples into :class:`~dfetch.terminal.Entry` + objects so the terminal tree browser has no knowledge of VCS internals. + + Adds '.' as the first entry to allow selecting the repo root (which is + treated as empty src). + """ + if isinstance(subproject, (GitSubProject, SvnSubProject)): + remote = subproject.remote_repo + with remote.browse_tree(version) as vcs_ls: + + def ls(path: str = "") -> list[Entry]: + entries = [ + Entry(display=name, has_children=is_dir) + for name, is_dir in vcs_ls(path) + ] + if not path: + # Prepend "." as a selectable leaf so Enter accepts the + # whole repo by default; no path normalization needed. + return [Entry(display=".", has_children=False)] + entries + return entries + + yield ls + else: + + def _empty(_path: str = "") -> list[Entry]: + return [] + + yield _empty + + +# --------------------------------------------------------------------------- +# Command class +# --------------------------------------------------------------------------- + + +class Add(dfetch.commands.command.Command): + """Add a new project to the manifest. + + Append a project entry to the manifest without fetching. + Use -i/--interactive to be guided through each field. + """ + + @staticmethod + def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None: + """Add the parser menu for this action.""" + parser = dfetch.commands.command.Command.parser(subparsers, Add) + + parser.add_argument( + "remote_url", + metavar="", + type=str, + nargs=1, + help="Remote URL of the repository to add.", + ) + + parser.add_argument( + "-i", + "--interactive", + action="store_true", + help=( + "Interactively guide through each manifest field. " + "Dfetch fetches the remote branch/tag list and lets " + "you pick or override every value." + ), + ) + + parser.add_argument( + "--name", + metavar="NAME", + default=None, + help="Project name (skips the name prompt in interactive mode).", + ) + + parser.add_argument( + "--dst", + metavar="PATH", + default=None, + help="Local destination path (skips the destination prompt in interactive mode).", + ) + + parser.add_argument( + "--version", + metavar="VERSION", + default=None, + help="Branch, tag, or revision (skips the version prompt in interactive mode).", + ) + + parser.add_argument( + "--src", + metavar="PATH", + default=None, + help="Sub-path or glob inside the remote repo (skips the source prompt in interactive mode).", + ) + + parser.add_argument( + "--ignore", + metavar="PATH", + nargs="+", + default=None, + help="Paths to ignore (skips the ignore prompt in interactive mode).", + ) + + def __call__(self, args: argparse.Namespace) -> None: + """Perform the add.""" + superproject = create_super_project() + + remote_url: str = args.remote_url[0] + purl = vcs_url_to_purl(remote_url) + + probe_entry = ProjectEntry(ProjectEntryDict(name=purl.name, url=remote_url)) + subproject = create_sub_project(probe_entry) + + remote_to_use = superproject.manifest.find_remote_for_url( + probe_entry.remote_url + ) + if remote_to_use: + logger.debug( + f"Remote URL {probe_entry.remote_url} matches remote {remote_to_use.name}" + ) + + ctx = _AddContext( + url=remote_url, + default_name=probe_entry.name, + default_dst=superproject.manifest.guess_destination(probe_entry.name), + default_branch=subproject.get_default_branch(), + subproject=subproject, + remote_to_use=remote_to_use, + manifest=superproject.manifest, + ) + overrides = _Overrides( + name=args.name, + dst=args.dst, + version=args.version, + src=args.src, + ignore=args.ignore, + ) + + try: + if args.interactive: + project_entry = _interactive_flow(ctx, overrides) + else: + project_entry = _non_interactive_entry(ctx, overrides) + logger.print_info_line(ctx.url, "Adding project to manifest") + logger.print_yaml(project_entry.as_yaml()) + + if project_entry is None: + return + + _finalize_add(project_entry, args, superproject) + except KeyboardInterrupt: + logger.info( + " [bold bright_yellow]> Aborting add of project[/bold bright_yellow]" + ) + + +# --------------------------------------------------------------------------- +# Entry construction +# --------------------------------------------------------------------------- + + +def _finalize_add( + project_entry: ProjectEntry, + args: argparse.Namespace, + superproject: SuperProject, +) -> None: + """Write *project_entry* to the manifest and optionally run update.""" + if args.interactive and not Confirm.ask("Add project to manifest?", default=True): + logger.info( + " [bold bright_yellow]> Aborting add of project[/bold bright_yellow]" + ) + return + + append_entry_manifest_file( + (superproject.root_directory / superproject.manifest.path).absolute(), + project_entry, + ) + logger.print_info_line( + project_entry.name, + f"Added '{project_entry.name}' to manifest '{superproject.manifest.path}'", + ) + + # Offer to run update immediately only in interactive mode. + if args.interactive and Confirm.ask( + f"Run 'dfetch update {project_entry.name}' now?", default=True + ): + # pylint: disable=import-outside-toplevel + from dfetch.commands.update import Update # local import avoids circular + + update_args = argparse.Namespace( + projects=[project_entry.name], + force=False, + no_recommendations=False, + ) + Update()(update_args) + + +def _non_interactive_entry(ctx: _AddContext, overrides: _Overrides) -> ProjectEntry: + """Build a ``ProjectEntry`` using inferred defaults (no user interaction).""" + if overrides.version: + branches = ctx.subproject.list_of_branches() + tags = ctx.subproject.list_of_tags() + choices: list[Version] = [ + *[ + Version(branch=b) + for b in prioritise_default(branches, ctx.default_branch) + ], + *[Version(tag=t) for t in sort_tags_newest_first(tags)], + ] + version = _resolve_raw_version(overrides.version, choices) or Version( + branch=ctx.default_branch + ) + else: + version = Version(branch=ctx.default_branch) + existing_names = {p.name for p in ctx.manifest.projects} + return _build_entry( + name=overrides.name or _unique_name(ctx.default_name, existing_names), + remote_url=ctx.url, + dst=overrides.dst or ctx.default_dst, + version=version, + src=overrides.src or "", + ignore=overrides.ignore or [], + remote_to_use=ctx.remote_to_use, + ) + + +def _build_entry( # pylint: disable=too-many-arguments + *, + name: str, + remote_url: str, + dst: str, + version: Version, + src: str, + ignore: list[str], + remote_to_use: Remote | None, +) -> ProjectEntry: + """Assemble a ``ProjectEntry`` from the fields collected by the wizard.""" + kind, value = version.field + entry_dict: ProjectEntryDict = ProjectEntryDict( + name=name, + url=remote_url, + dst=dst, + ) + entry_dict[kind] = value # type: ignore[literal-required] + if src and src != ".": + entry_dict["src"] = src + if ignore: + entry_dict["ignore"] = ignore + entry = ProjectEntry(entry_dict) + if remote_to_use: + entry.set_remote(remote_to_use) + return entry + + +# --------------------------------------------------------------------------- +# Interactive flow +# --------------------------------------------------------------------------- + + +def _show_url_fields( + name: str, remote_url: str, default_branch: str, remote_to_use: Remote | None +) -> None: + """Print the fields determined solely by the URL (name, remote, url, repo-path).""" + seed = _build_entry( + name=name, + remote_url=remote_url, + dst=name, + version=Version(branch=default_branch), + src="", + ignore=[], + remote_to_use=remote_to_use, + ).as_yaml() + logger.print_yaml( + {k: seed[k] for k in ("name", "remote", "url", "repo-path") if k in seed} + ) + + +def _pick_src_and_ignore( + subproject: SubProject, version_value: str, overrides: _Overrides +) -> tuple[str, list[str]]: + """Browse the remote tree (if needed) and return ``(src, ignore)``.""" + with browse_tree(subproject, version_value) as ls_function: + src = overrides.src if overrides.src is not None else _ask_src(ls_function) + if src: + logger.print_yaml_field("src", src) + ignore = ( + overrides.ignore + if overrides.ignore is not None + else _ask_ignore(ls_function, src=src) + ) + if ignore: + logger.print_yaml_field("ignore", ignore) + return src, ignore + + +def _interactive_flow(ctx: _AddContext, overrides: _Overrides) -> ProjectEntry: + """Guide the user through every manifest field and return a ``ProjectEntry``. + + A field in *overrides* that is not ``None`` skips the corresponding prompt. + """ + logger.print_info_line(ctx.url, "Adding project through interactive wizard") + + if overrides.name is not None: + ctx.manifest.validate_project_name(overrides.name) + name = overrides.name + else: + name = _ask_name(ctx.default_name, ctx.manifest) + _show_url_fields(name, ctx.url, ctx.default_branch, ctx.remote_to_use) + + if overrides.dst is not None: + Manifest.validate_destination(overrides.dst) + dst = overrides.dst + else: + dst = _ask_dst(name, ctx.default_dst) + if dst != name: + logger.print_yaml_field("dst", dst) + + branches = ctx.subproject.list_of_branches() + tags = ctx.subproject.list_of_tags() + if overrides.version is not None: + choices: list[Version] = [ + *[ + Version(branch=b) + for b in prioritise_default(branches, ctx.default_branch) + ], + *[Version(tag=t) for t in sort_tags_newest_first(tags)], + ] + version = _resolve_raw_version(overrides.version, choices) or Version( + branch=ctx.default_branch + ) + else: + version = _ask_version(ctx.default_branch, branches, tags) + version_kind, version_value = version.field + logger.print_yaml_field(version_kind, version_value) + + src, ignore = _pick_src_and_ignore(ctx.subproject, version_value, overrides) + + return _build_entry( + name=name, + remote_url=ctx.url, + dst=dst, + version=version, + src=src, + ignore=ignore, + remote_to_use=ctx.remote_to_use, + ) + + +# --------------------------------------------------------------------------- +# Individual prompt helpers +# --------------------------------------------------------------------------- + +_PROMPT_FORMAT = " [green]?[/green] [bold]{label}[/bold]" + + +def _unique_name(base: str, existing: set[str]) -> str: + """Return *base* if unused, otherwise append *-1*, *-2*, … until unique.""" + if base not in existing: + return base + i = 1 + while f"{base}-{i}" in existing: + i += 1 + return f"{base}-{i}" + + +def _ask_name(default: str, manifest: Manifest) -> str: + """Prompt for the project name, re-asking on invalid input.""" + existing_names = {p.name for p in manifest.projects} + suggested = _unique_name(default, existing_names) + while True: + name = terminal.prompt("Name", suggested) + try: + manifest.validate_project_name(name) + except ValueError as exc: + logger.warning(str(exc)) + if name in existing_names: + suggested = _unique_name(name, existing_names) + continue + terminal.erase_last_line() + return name + + +def _ask_dst(name: str, default: str) -> str: + """Prompt for the destination path, re-asking on path-traversal attempts.""" + suggested = default or name + while True: + dst = terminal.prompt("Destination", suggested) + if not dst: + dst = name # fall back to project name + try: + Manifest.validate_destination(dst) + except ValueError as exc: + logger.warning(str(exc)) + continue + terminal.erase_last_line() + return dst + + +def _ask_version( + default_branch: str, + branches: list[str], + tags: list[str], +) -> Version: + """Choose a version (branch / tag / SHA) and return it as a :class:`~dfetch.manifest.version.Version`. + + In a TTY shows a hierarchical tree browser (names split on '/'). + Outside a TTY (CI, pipe, tests) falls back to a numbered text menu. + """ + choices: list[Version] = [ + *[Version(branch=b) for b in prioritise_default(branches, default_branch)], + *[Version(tag=t) for t in sort_tags_newest_first(tags)], + ] + + if terminal.is_tty() and choices: + return _ask_version_tree(default_branch, choices) + + return _text_version_pick(choices, default_branch) + + +def _ask_src(ls_function: LsFunction) -> str: + """Optionally prompt for a ``src:`` sub-path or glob pattern. + + In a TTY opens a tree browser for single-path selection with + directory navigation (→/← to expand/collapse). + Outside a TTY falls back to a free-text prompt. + """ + if terminal.is_tty(): + src = tree_single_pick(ls_function, "Source path", dirs_selectable=True) + return "" if src == "." else src + + return Prompt.ask( + _PROMPT_FORMAT.format(label="Source path") + + " (sub-path/glob, or Enter to fetch whole repo)", + default="", + ).strip() + + +def _should_proceed_with_ignore(nodes: list[TreeNode]) -> bool: + """Warn and confirm when every visible node has been deselected.""" + if any(n.selected for n in nodes): + return True + logger.warning( + "You have deselected everything. This will ignore all files in the project." + ) + return bool(Confirm.ask("Continue with empty selection?", default=False)) + + +def _ask_ignore(ls_function: LsFunction, src: str = "") -> list[str]: + """Optionally prompt for ``ignore:`` paths. + + Opens a tree browser (TTY) or falls back to free-text. All items start + selected (= keep). Deselect items to mark them for ignoring. + + Paths are returned relative to *src* when *src* is set, otherwise relative + to the repo root. + """ + + def _scoped_ls(path: str = "") -> list[Entry]: + return ls_function(f"{src}/{path}" if path else src) + + browse_fn: LsFunction = _scoped_ls if src else ls_function + + if terminal.is_tty(): + while True: + _, all_nodes = run_tree_browser( + browse_fn, + "Ignore", + BrowserConfig(multi=True, all_selected=True), + ) + ignore = deselected_paths(all_nodes) + if not ignore: + return [] + if _should_proceed_with_ignore(all_nodes): + return ignore + + raw = Prompt.ask( + _PROMPT_FORMAT.format(label="Ignore paths") + + " (comma-separated paths to ignore, or Enter to skip)", + default="", + ).strip() + return [p.strip() for p in raw.split(",") if p.strip()] + + +# --------------------------------------------------------------------------- +# Version pickers +# --------------------------------------------------------------------------- + + +def _resolve_raw_version(raw: str, choices: list[Version]) -> Version | None: + """Return the matching :class:`Version` from *choices*, or ``None`` when *raw* is empty. + + Checks choices first (preserving branch/tag distinction), then falls back + to SHA detection, then treats the input as an unknown branch name. + """ + if not raw: + return None + for v in choices: + if v.field[1] == raw: + return v + if is_commit_sha(raw): + return Version(revision=raw) + return Version(branch=raw) + + +_MAX_LISTED = 30 + + +def _version_menu_entries(choices: list[Version], default_branch: str) -> list[Entry]: + """Build the numbered branch/tag pick list as :class:`~dfetch.terminal.Entry` objects.""" + entries: list[Entry] = [] + for ref in choices[:_MAX_LISTED]: + kind, value = ref.field + marker = ( + " [dim](default)[/dim]" + if value == default_branch and kind == "branch" + else "" + ) + display = f"{value}{marker} [dim]{kind}[/dim]" + entries.append(Entry(display=display, has_children=False, value=value)) + return entries + + +def _text_version_pick(choices: list[Version], default_branch: str) -> Version: + """Numbered text-based version picker (non-TTY fallback and Esc fallback in TTY).""" + entries = _version_menu_entries(choices, default_branch) + hidden = len(choices) - len(entries) + note = f" [dim] … and {hidden} more (type name directly)[/dim]" if hidden else "" + + raw = terminal.numbered_prompt( + entries, "Version", "number, branch, tag, or SHA", default_branch, note=note + ) + return _resolve_raw_version(raw, choices) or Version(branch=default_branch) + + +def _ask_version_tree( + default_branch: str, + choices: list[Version], +) -> Version: # pragma: no cover - interactive TTY only + """Branch/tag picker using the hierarchical tree browser. + + Splits names by '/' to build a navigable tree. Falls back to the + numbered text picker on Esc or when the selected path isn't in *choices*. + """ + labels = {v.field[1]: v.field[0] for v in choices} + selected = tree_pick_from_names( + labels, "Version", priority_path=default_branch, esc_label="list" + ) + for v in choices: + if v.field[1] == selected: + return v + return _text_version_pick(choices, default_branch) diff --git a/dfetch/commands/report.py b/dfetch/commands/report.py index aa3fcd67..18d63763 100644 --- a/dfetch/commands/report.py +++ b/dfetch/commands/report.py @@ -15,8 +15,7 @@ from dfetch.project import create_super_project from dfetch.project.metadata import Metadata from dfetch.reporting import REPORTERS, ReportTypes -from dfetch.util.license import License, guess_license_in_file -from dfetch.util.util import is_license_file +from dfetch.util.license import License, guess_license_in_file, is_license_file logger = get_logger(__name__) diff --git a/dfetch/log.py b/dfetch/log.py index 77a92061..340190bd 100644 --- a/dfetch/log.py +++ b/dfetch/log.py @@ -103,6 +103,22 @@ def print_warning_line(self, name: str, info: str) -> None: line = markup_escape(info).replace("\n", "\n ") self.info(f" [bold bright_yellow]> {line}[/bold bright_yellow]") + def print_overview(self, name: str, title: str, info: dict[str, Any]) -> None: + """Print an overview of fields.""" + self.print_info_line(name, title) + for key, value in info.items(): + safe_key = markup_escape(str(key)) + if isinstance(value, list): + self.info(f" [blue]{safe_key + ':':20s}[/blue]") + for item in value: + self.info( + f" {'':20s}[white]- {markup_escape(str(item))}[/white]" + ) + else: + self.info( + f" [blue]{safe_key + ':':20s}[/blue][white] {markup_escape(str(value))}[/white]" + ) + def print_title(self) -> None: """Print the DFetch tool title and version.""" self.info(f"[bold blue]Dfetch ({__version__})[/bold blue]") @@ -111,6 +127,33 @@ def print_info_field(self, field_name: str, field: str) -> None: """Print a field with corresponding value.""" self.print_report_line(field_name, field if field else "") + def print_yaml(self, fields: dict[str, Any]) -> None: + """Print all str and list values in *fields* in YAML style.""" + first = True + for key, value in fields.items(): + if isinstance(value, (str, list)): + self.print_yaml_field(key, value, first=first) + first = False + + def print_yaml_field( + self, key: str, value: str | list[str], *, first: bool = False + ) -> None: + """Print one manifest field in YAML style. + + When *first* is True the line is prefixed with ``- `` (YAML sequence + entry marker) and subsequent fields are indented with four spaces so + the output mirrors the manifest on disk. + """ + prefix = " - " if first else " " + if isinstance(value, list): + self.info(f"{prefix}[blue]{markup_escape(key)}:[/blue]") + for item in value: + self.info(f" - {markup_escape(item)}") + else: + self.info( + f"{prefix}[blue]{markup_escape(key)}:[/blue] {markup_escape(value)}" + ) + def warning(self, msg: object, *args: Any, **kwargs: Any) -> None: """Log warning.""" super().warning( diff --git a/dfetch/manifest/manifest.py b/dfetch/manifest/manifest.py index ec084c28..78bd0083 100644 --- a/dfetch/manifest/manifest.py +++ b/dfetch/manifest/manifest.py @@ -24,6 +24,7 @@ import re from collections.abc import Sequence from dataclasses import dataclass +from pathlib import Path from typing import IO, Any import yaml @@ -370,6 +371,70 @@ def find_name_in_manifest(self, name: str) -> ManifestEntryLocation: ) raise RuntimeError(f"{name} was not found in the manifest!") + # Characters not allowed in a project name (YAML special chars). + _UNSAFE_NAME_RE = re.compile(r"[\x00-\x1F\x7F-\x9F:#\[\]{}&*!|>'\"%@`]") + + def check_name_uniqueness(self, project_name: str) -> None: + """Raise if *project_name* is already used in the manifest.""" + if project_name in {project.name for project in self.projects}: + raise ValueError( + f"Project with name '{project_name}' already exists in manifest!" + ) + + def validate_project_name(self, name: str) -> None: + """Raise ValueError if *name* is not valid for use in this manifest.""" + if not name: + raise ValueError("Name cannot be empty.") + if self._UNSAFE_NAME_RE.search(name): + raise ValueError( + f"Name '{name}' contains characters not allowed in a manifest name. " + "Avoid: # : [ ] { } & * ! | > ' \" % @ `" + ) + self.check_name_uniqueness(name) + + @staticmethod + def validate_destination(dst: str) -> None: + """Raise ValueError if *dst* is not a safe manifest destination path.""" + if any(part == ".." for part in Path(dst).parts): + raise ValueError( + f"Destination '{dst}' contains '..'. " + "Paths must stay within the manifest directory." + ) + + def guess_destination(self, project_name: str) -> str: + """Guess the destination based on the common prefix of existing projects. + + With two or more existing projects the common parent directory is used. + With a single existing project its parent directory is used (if any). + """ + destinations = [p.destination for p in self.projects if p.destination] + if not destinations: + return "" + + try: + common_path = os.path.commonpath(destinations) + except ValueError: + return "" + if not common_path or common_path == os.path.sep: + return "" + + if len(destinations) == 1: + parent_path = Path(common_path).parent + if parent_path != Path("."): + return (parent_path / project_name).as_posix() + return "" + + return (Path(common_path) / project_name).as_posix() + + def find_remote_for_url(self, remote_url: str) -> Remote | None: + """Return the first remote whose base URL is a prefix of *remote_url*.""" + target = remote_url.rstrip("/") + for remote in self.remotes: + remote_base = remote.url.rstrip("/") + if target.startswith(remote_base): + return remote + return None + class ManifestDumper(yaml.SafeDumper): # pylint: disable=too-many-ancestors """Dump a manifest YAML. @@ -391,3 +456,21 @@ def write_line_break(self, data: Any = None) -> None: super().write_line_break() # type: ignore[unused-ignore, no-untyped-call] self._last_additional_break = len(self.indents) + + +def append_entry_manifest_file( + manifest_path: str | Path, + project_entry: ProjectEntry, +) -> None: + """Add the project entry to the manifest file.""" + with Path(manifest_path).open("a", encoding="utf-8") as manifest_file: + + new_entry = yaml.dump( + [project_entry.as_yaml()], + sort_keys=False, + line_break=os.linesep, + indent=2, + ) + manifest_file.write("\n") + for line in new_entry.splitlines(): + manifest_file.write(f" {line}\n") diff --git a/dfetch/manifest/project.py b/dfetch/manifest/project.py index ed5e8202..e4be2510 100644 --- a/dfetch/manifest/project.py +++ b/dfetch/manifest/project.py @@ -566,6 +566,7 @@ def as_yaml(self) -> dict[str, str | list[str] | dict[str, str]]: "repo-path": self._repo_path, "vcs": self._vcs, "integrity": self._integrity.as_yaml() or None, + "ignore": list(self._ignore) if self._ignore else None, } return {k: v for k, v in yamldata.items() if v} diff --git a/dfetch/manifest/version.py b/dfetch/manifest/version.py index 999ff34c..6d7299fb 100644 --- a/dfetch/manifest/version.py +++ b/dfetch/manifest/version.py @@ -24,6 +24,15 @@ def __eq__(self, other: Any) -> bool: return bool(self.branch == other.branch and self.revision == other.revision) + @property + def field(self) -> tuple[str, str]: + """Return ``(kind, value)`` for the active field: tag, revision, or branch.""" + if self.tag: + return "tag", self.tag + if self.revision: + return "revision", self.revision + return "branch", self.branch + def __repr__(self) -> str: """Get the string representing this version.""" if self.tag: diff --git a/dfetch/project/gitsubproject.py b/dfetch/project/gitsubproject.py index 21ee85d3..5a6ace16 100644 --- a/dfetch/project/gitsubproject.py +++ b/dfetch/project/gitsubproject.py @@ -8,7 +8,8 @@ from dfetch.manifest.version import Version from dfetch.project.metadata import Dependency from dfetch.project.subproject import SubProject -from dfetch.util.util import LICENSE_GLOBS, safe_rm +from dfetch.util.license import LICENSE_GLOBS +from dfetch.util.util import safe_rm from dfetch.vcs.git import CheckoutOptions, GitLocalRepo, GitRemote, get_git_version logger = get_logger(__name__) @@ -24,6 +25,11 @@ def __init__(self, project: ProjectEntry): super().__init__(project) self._remote_repo = GitRemote(self.remote) + @property + def remote_repo(self) -> GitRemote: + """Return the underlying remote repository object.""" + return self._remote_repo + def check(self) -> bool: """Check if is GIT.""" return bool(self._remote_repo.is_git()) @@ -40,6 +46,10 @@ def _list_of_tags(self) -> list[str]: """Get list of all available tags.""" return [str(tag) for tag in self._remote_repo.list_of_tags()] + def list_of_branches(self) -> list[str]: + """Get list of all available branches.""" + return [str(branch) for branch in self._remote_repo.list_of_branches()] + @staticmethod def revision_is_enough() -> bool: """See if this VCS can uniquely distinguish branch with revision only.""" diff --git a/dfetch/project/subproject.py b/dfetch/project/subproject.py index 36ccff3e..78f3facd 100644 --- a/dfetch/project/subproject.py +++ b/dfetch/project/subproject.py @@ -17,7 +17,7 @@ logger = get_logger(__name__) -class SubProject(ABC): +class SubProject(ABC): # pylint: disable=too-many-public-methods """Abstract SubProject object. This object represents one Project entry in the Manifest. @@ -248,6 +248,11 @@ def _log_project(self, msg: str) -> None: def _log_tool(name: str, msg: str) -> None: logger.print_report_line(name, msg.strip()) + @property + def name(self) -> str: + """Get the name of this project.""" + return self.__project.name + @property def local_path(self) -> str: """Get the local destination of this project.""" @@ -400,6 +405,14 @@ def _fetch_impl(self, version: Version) -> tuple[Version, list[Dependency]]: def get_default_branch(self) -> str: """Get the default branch of this repository.""" + def list_of_branches(self) -> list[str]: + """Get list of all available branches. Override in VCS-specific subclasses.""" + return [] + + def list_of_tags(self) -> list[str]: + """Get list of all available tags (public wrapper around ``_list_of_tags``).""" + return self._list_of_tags() + def freeze_project(self, project: ProjectEntry) -> str | None: """Freeze *project* to its current on-disk version. diff --git a/dfetch/project/svnsubproject.py b/dfetch/project/svnsubproject.py index 1ec167f8..e4942e82 100644 --- a/dfetch/project/svnsubproject.py +++ b/dfetch/project/svnsubproject.py @@ -9,10 +9,10 @@ from dfetch.manifest.version import Version from dfetch.project.metadata import Dependency from dfetch.project.subproject import SubProject +from dfetch.util.license import is_license_file from dfetch.util.util import ( find_matching_files, find_non_matching_files, - is_license_file, safe_rm, ) from dfetch.vcs.svn import SvnRemote, SvnRepo, get_svn_version @@ -30,6 +30,11 @@ def __init__(self, project: ProjectEntry): super().__init__(project) self._remote_repo = SvnRemote(self.remote) + @property + def remote_repo(self) -> SvnRemote: + """Return the underlying remote repository object.""" + return self._remote_repo + def check(self) -> bool: """Check if is SVN.""" return self._remote_repo.is_svn() @@ -178,3 +183,7 @@ def _get_revision(self, branch: str) -> str: def get_default_branch(self) -> str: """Get the default branch of this repository.""" return SvnRepo.DEFAULT_BRANCH + + def list_of_branches(self) -> list[str]: + """Return trunk plus any branches found under ``branches/``.""" + return [SvnRepo.DEFAULT_BRANCH, *self._remote_repo.list_of_branches()] diff --git a/dfetch/terminal/__init__.py b/dfetch/terminal/__init__.py new file mode 100644 index 00000000..8c73a4cf --- /dev/null +++ b/dfetch/terminal/__init__.py @@ -0,0 +1,50 @@ +"""Interactive terminal utilities. + +All public symbols are re-exported here for convenient access via +``from dfetch.terminal import X``. Implementation lives in the +sub-modules: :mod:`ansi`, :mod:`keys`, :mod:`screen`, :mod:`prompt`, +:mod:`pick`, :mod:`tree_browser`. +""" + +from .ansi import ( + BOLD, + CYAN, + DIM, + GREEN, + MAGENTA, + RESET, + REVERSE, + VIEWPORT, + YELLOW, + strip_ansi, +) +from .keys import is_tty, read_key +from .pick import scrollable_pick +from .prompt import ghost_prompt, numbered_prompt, prompt +from .screen import Screen, erase_last_line +from .tree_browser import tree_pick_from_names +from .types import Entry, LsFunction + +__all__ = [ + "BOLD", + "CYAN", + "DIM", + "GREEN", + "Entry", + "LsFunction", + "MAGENTA", + "RESET", + "REVERSE", + "VIEWPORT", + "YELLOW", + "Screen", + "erase_last_line", + "ghost_prompt", + "numbered_prompt", + "prompt", + "is_tty", + "read_key", + "scrollable_pick", + "strip_ansi", + "tree_pick_from_names", +] diff --git a/dfetch/terminal/ansi.py b/dfetch/terminal/ansi.py new file mode 100644 index 00000000..a3db710e --- /dev/null +++ b/dfetch/terminal/ansi.py @@ -0,0 +1,22 @@ +"""ANSI escape sequences and text-stripping utilities.""" + +import re + +RESET = "\x1b[0m" +BOLD = "\x1b[1m" +DIM = "\x1b[2m" +REVERSE = "\x1b[7m" # swap fore/background – used for cursor highlight +CYAN = "\x1b[96m" +MAGENTA = "\x1b[95m" +GREEN = "\x1b[92m" +YELLOW = "\x1b[93m" + +# Viewport height for scrollable list widgets (number of items shown at once). +VIEWPORT = 10 + +_ANSI_ESC_RE = re.compile(r"\x1b\[[0-9;]*m") + + +def strip_ansi(s: str) -> str: + """Strip ANSI colour/style escape sequences from *s*.""" + return _ANSI_ESC_RE.sub("", s) diff --git a/dfetch/terminal/keys.py b/dfetch/terminal/keys.py new file mode 100644 index 00000000..1b530a42 --- /dev/null +++ b/dfetch/terminal/keys.py @@ -0,0 +1,92 @@ +"""Cross-platform raw keypress reading and TTY detection.""" + +import os +import sys + + +def is_tty() -> bool: + """Return True when stdin is an interactive terminal (not CI, not piped).""" + return sys.stdin.isatty() and not os.environ.get("CI") + + +def read_key() -> str: # pragma: no cover – requires live terminal + """Read one keypress from stdin in raw mode; return a normalised key name. + + Possible return values: ``"UP"``, ``"DOWN"``, ``"LEFT"``, ``"RIGHT"``, + ``"PGUP"``, ``"PGDN"``, ``"ENTER"``, ``"SPACE"``, ``"ESC"``, or a + single printable character string. + + Raises ``KeyboardInterrupt`` on Ctrl-C / Ctrl-D. + """ + if sys.platform == "win32": + return _read_key_windows() + return _read_key_unix() + + +def _read_key_windows() -> str: # pragma: no cover + import msvcrt # type: ignore[import] # pylint: disable=import-outside-toplevel,import-error + + ch = msvcrt.getwch() # type: ignore[attr-defined] + if ch in ("\x00", "\xe0"): + arrow = { + "H": "UP", + "P": "DOWN", + "K": "LEFT", + "M": "RIGHT", + "I": "PGUP", + "Q": "PGDN", + } + return arrow.get(msvcrt.getwch(), "UNKNOWN") # type: ignore[attr-defined,no-any-return] + if ch in ("\r", "\n"): + return "ENTER" + if ch == "\x1b": + return "ESC" + if ch == " ": + return "SPACE" + if ch == "\x03": + raise KeyboardInterrupt + return str(ch) # ch is Any from untyped msvcrt + + +def _read_key_unix() -> str: # pragma: no cover + import select as _select # pylint: disable=import-outside-toplevel + import termios # pylint: disable=import-outside-toplevel + import tty # pylint: disable=import-outside-toplevel + + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + ch = os.read(fd, 1) + + if ch in (b"\r", b"\n"): + return "ENTER" + + if ch == b"\x1b": + readable, _, _ = _select.select([fd], [], [], 0.05) + if readable: + rest = b"" + while True: + more, _, _ = _select.select([fd], [], [], 0.01) + if not more: + break + rest += os.read(fd, 1) + escape_sequences = { + b"\x1b[A": "UP", + b"\x1b[B": "DOWN", + b"\x1b[C": "RIGHT", + b"\x1b[D": "LEFT", + b"\x1b[5~": "PGUP", + b"\x1b[6~": "PGDN", + } + return escape_sequences.get(ch + rest, "ESC") + return "ESC" + + if ch == b" ": + return "SPACE" + if ch in (b"\x03", b"\x04"): + raise KeyboardInterrupt + + return ch.decode("utf-8", errors="replace") + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) diff --git a/dfetch/terminal/pick.py b/dfetch/terminal/pick.py new file mode 100644 index 00000000..5432a260 --- /dev/null +++ b/dfetch/terminal/pick.py @@ -0,0 +1,148 @@ +"""Scrollable single/multi-select pick list widget.""" + +from dfetch.terminal.ansi import BOLD, DIM, GREEN, RESET, VIEWPORT +from dfetch.terminal.keys import read_key +from dfetch.terminal.screen import Screen + + +def _advance_pick_idx(key: str, idx: int, n: int) -> int: + """Return the new cursor position after a navigation keypress.""" + if key == "UP": + return max(0, idx - 1) + if key == "DOWN": + return min(n - 1, idx + 1) + if key == "PGUP": + return max(0, idx - VIEWPORT) + return min(n - 1, idx + VIEWPORT) # PGDN + + +def _toggle_pick_selection(idx: int, selected: set[int]) -> set[int]: + """Return a new selected set with *idx* toggled.""" + new_sel = set(selected) + if idx in new_sel: + new_sel.discard(idx) + else: + new_sel.add(idx) + return new_sel + + +def _pick_outcome( + key: str, idx: int, selected: set[int], multi: bool +) -> tuple[bool, int | list[int] | None]: + """Determine whether a keypress ends the interaction and what value to return. + + Returns ``(done, result)``. When *done* is ``False`` the loop continues. + When *done* is ``True``, *result* is the value to return to the caller. + """ + if key == "ENTER": + return True, sorted(selected) if multi else idx + if key == "ESC": + return True, None + if key not in ("UP", "DOWN", "PGUP", "PGDN", "SPACE") and not multi: + return True, None + return False, None + + +def _initial_selection( + multi: bool, all_selected: bool, n: int, default_idx: int +) -> set[int]: + """Return the initial selected-indices set for a pick list.""" + if multi and all_selected: + return set(range(n)) + if multi: + return set() + return {default_idx} + + +def _clamp_scroll(idx: int, top: int) -> int: + """Return an updated *top* offset so that *idx* is visible in the viewport.""" + if idx < top: + return idx + if idx >= top + VIEWPORT: + return idx - VIEWPORT + 1 + return top + + +def _render_pick_item( + i: int, idx: int, item: str, selected: set[int], multi: bool +) -> str: + """Format a single row for the pick widget.""" + cursor = f"{GREEN}▶{RESET}" if i == idx else " " + check = f"{GREEN}✓ {RESET}" if (multi and i in selected) else " " + is_highlighted = (i in selected) if multi else (i == idx) + styled = f"{BOLD}{item}{RESET}" if is_highlighted else item + return f" {cursor} {check}{styled}" + + +def _render_pick_lines( # pylint: disable=too-many-arguments,too-many-positional-arguments + title: str, + items: list[str], + idx: int, + top: int, + selected: set[int], + multi: bool, + n: int, +) -> list[str]: + """Build the list of lines to draw for one frame of the pick widget.""" + lines: list[str] = [f" {BOLD}{title}{RESET}"] + if top > 0: + lines.append(f" {DIM}↑ {top} more above{RESET}") + for i in range(top, min(top + VIEWPORT, n)): + lines.append(_render_pick_item(i, idx, items[i], selected, multi)) + remaining = n - (top + VIEWPORT) + if remaining > 0: + lines.append(f" {DIM}↓ {remaining} more below{RESET}") + hint = ( + "↑/↓ navigate Space toggle Enter confirm Esc skip" + if multi + else "↑/↓ navigate PgUp/PgDn jump Enter select Esc free-type" + ) + lines.append(f" {DIM}{hint}{RESET}") + return lines + + +def scrollable_pick( + title: str, + display_items: list[str], + *, + default_idx: int = 0, + multi: bool = False, + all_selected: bool = False, +) -> int | list[int] | None: # pragma: no cover – interactive TTY only + """Display a scrollable pick list; return selected index or indices. + + *display_items* are plain strings (no ANSI codes). Navigate with + ↑/↓ or PgUp/PgDn. In single-select mode (``multi=False``) confirm + with Enter; in multi-select mode (``multi=True``) toggle with Space + and confirm with Enter. Cancel with Esc (returns ``None``). + + Single-select: returns the selected index. + Multi-select: returns a list of selected indices (may be empty). + If ``all_selected=True``, starts with all items selected. + """ + screen = Screen() + idx = default_idx + top = 0 + n = len(display_items) + if n == 0: + screen.clear() + return [] if multi else None + selected = _initial_selection(multi, all_selected, n, default_idx) + + while True: + idx = max(0, min(idx, n - 1)) + top = _clamp_scroll(idx, top) + screen.draw( + _render_pick_lines(title, display_items, idx, top, selected, multi, n) + ) + key = read_key() + + if key in ("UP", "DOWN", "PGUP", "PGDN"): + idx = _advance_pick_idx(key, idx, n) + elif key == "SPACE" and multi: + selected = _toggle_pick_selection(idx, selected) + else: + done, result = _pick_outcome(key, idx, selected, multi) + if done: + screen.clear() + return result diff --git a/dfetch/terminal/prompt.py b/dfetch/terminal/prompt.py new file mode 100644 index 00000000..8a154b86 --- /dev/null +++ b/dfetch/terminal/prompt.py @@ -0,0 +1,117 @@ +"""Single-line ghost prompt.""" + +import sys + +from rich.console import Console +from rich.prompt import Prompt + +from dfetch.terminal.ansi import DIM, GREEN, RESET +from dfetch.terminal.keys import is_tty, read_key +from dfetch.terminal.types import Entry + +_console = Console() + +_PROMPT_FORMAT = " [green]?[/green] [bold]{label}[/bold]" + + +def _ghost_handle_backspace(buf: list[str], ghost_active: bool, ghost_len: int) -> bool: + """Handle backspace in a ghost prompt; returns updated *ghost_active*.""" + if buf: + buf.pop() + sys.stdout.write("\x1b[1D\x1b[K") + sys.stdout.flush() + elif ghost_active: + sys.stdout.write(f"\x1b[{ghost_len}D\x1b[K") + sys.stdout.flush() + return False + return ghost_active + + +def _ghost_handle_char( + ch: str, buf: list[str], ghost_active: bool, ghost_len: int +) -> bool: + """Append *ch* to *buf*, clearing ghost if still active; returns updated *ghost_active*.""" + if ghost_active: + sys.stdout.write(f"\x1b[{ghost_len}D\x1b[K{ch}") + ghost_active = False + else: + sys.stdout.write(ch) + sys.stdout.flush() + buf.append(ch) + return ghost_active + + +def ghost_prompt(label: str, default: str = "") -> str: # pragma: no cover + """Single-line prompt with *default* shown as dim ghost text. + + The ghost disappears the moment the user types anything. + Pressing Enter with no input accepts *default*. + """ + sys.stdout.write(f"{label}: {DIM}{default}{RESET}") + sys.stdout.flush() + + buf: list[str] = [] + ghost_active = bool(default) + + while True: + key = read_key() + if key == "ENTER": + sys.stdout.write("\n") + sys.stdout.flush() + return "".join(buf) if buf else default + if key in ("\x7f", "\x08"): + ghost_active = _ghost_handle_backspace(buf, ghost_active, len(default)) + continue + ch = " " if key == "SPACE" else key + if len(ch) == 1 and ch.isprintable(): + ghost_active = _ghost_handle_char(ch, buf, ghost_active, len(default)) + + +def numbered_prompt( + entries: list[Entry], + label: str, + hint: str, + default: str = "", + note: str = "", +) -> str: + """Display *entries* then prompt until the user picks one or types freely. + + Each entry is printed as `` N `` where *N* is its 1-based + index. An optional *note* line (e.g. a truncation message) is printed + after the entries without a number. + + If the user enters a digit in ``[1, len(entries)]`` returns the + corresponding ``entry.value``. Any other non-empty input is returned + as-is. Out-of-range numbers loop with a warning. + """ + for i, entry in enumerate(entries, start=1): + _console.print(f" [bold white]{i:>2}[/bold white] {entry.display}") + if note: + _console.print(note) + + n = len(entries) + while True: + raw = Prompt.ask( + _PROMPT_FORMAT.format(label=label) + f" ({hint})", + default=default, + ).strip() + + if raw.isdigit(): + idx = int(raw) - 1 + if 0 <= idx < n: + return entries[idx].value + _console.print(f" [dim]Pick a number between 1 and {n}.[/dim]") + continue + + return raw + + +def prompt(label: str, default: str = "") -> str: + """Single-line prompt that adapts to the terminal environment. + + In a TTY shows ghost text via :func:`ghost_prompt`. + Outside a TTY (CI, pipe, tests) uses a Rich fallback. + """ + if is_tty(): + return ghost_prompt(f" {GREEN}?{RESET} {label}", default).strip() + return Prompt.ask(_PROMPT_FORMAT.format(label=label), default=default).strip() diff --git a/dfetch/terminal/screen.py b/dfetch/terminal/screen.py new file mode 100644 index 00000000..9f2b4b23 --- /dev/null +++ b/dfetch/terminal/screen.py @@ -0,0 +1,40 @@ +"""In-place terminal screen redraw.""" + +import sys +from collections.abc import Sequence + +from dfetch.terminal.keys import is_tty + + +def erase_last_line() -> None: + """Erase the most recently printed terminal line (no-op when not a TTY).""" + if is_tty(): + sys.stdout.write("\x1b[1A\x1b[2K") + sys.stdout.flush() + + +class Screen: + """Minimal ANSI helper for in-place redraw. + + Tracks the number of lines last written so that each ``draw()`` call + moves the cursor back up and overwrites them without flicker. + """ + + def __init__(self) -> None: + """Create screen.""" + self._line_count = 0 + + def draw(self, lines: Sequence[str]) -> None: + """Overwrite the previously drawn content with *lines*.""" + if self._line_count: + sys.stdout.write(f"\x1b[{self._line_count}A\x1b[0J") + sys.stdout.write("\n".join(lines) + "\n") + sys.stdout.flush() + self._line_count = len(lines) + + def clear(self) -> None: + """Erase the previously drawn content.""" + if self._line_count: + sys.stdout.write(f"\x1b[{self._line_count}A\x1b[0J") + sys.stdout.flush() + self._line_count = 0 diff --git a/dfetch/terminal/tree_browser.py b/dfetch/terminal/tree_browser.py new file mode 100644 index 00000000..9496e779 --- /dev/null +++ b/dfetch/terminal/tree_browser.py @@ -0,0 +1,452 @@ +"""Generic scrollable tree browser for remote VCS trees. + +Provides :func:`run_tree_browser`, :func:`tree_single_pick`, and +:func:`tree_pick_from_names` — interactive terminal widgets that work with +any :data:`~dfetch.terminal.LsFunction`. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from dfetch.terminal.ansi import BOLD, DIM, GREEN, RESET, VIEWPORT +from dfetch.terminal.keys import read_key +from dfetch.terminal.screen import Screen +from dfetch.terminal.types import Entry, LsFunction + + +@dataclass +class TreeNode: + """One entry in the flattened view of a remote VCS tree.""" + + name: str + path: str # path relative to repo root / tree prefix + is_dir: bool + depth: int = 0 + expanded: bool = False + selected: bool = False + children_loaded: bool = False + + +@dataclass(frozen=True) +class BrowserConfig: + """Configuration for a :class:`TreeBrowser` session.""" + + multi: bool = False + all_selected: bool = False + dirs_selectable: bool = False + esc_label: str = "skip" + + +class TreeBrowser: + """Interactive scrollable tree browser for a remote VCS tree.""" + + def __init__( + self, + ls_function: LsFunction, + title: str, + config: BrowserConfig = BrowserConfig(), + ) -> None: + """Initialise browser configuration; call :meth:`run` to start.""" + self._ls_function = ls_function + self._title = title + self._config = config + self._nodes: list[TreeNode] = [] + self._idx = 0 + self._top = 0 + + @property + def nodes(self) -> list[TreeNode]: + """Current node list (populated after :meth:`run`).""" + return self._nodes + + # ------------------------------------------------------------------ + # Scroll + # ------------------------------------------------------------------ + + def _adjust_scroll(self) -> None: + """Ensure the cursor index is within the visible viewport.""" + if self._idx < self._top: + self._top = self._idx + elif self._idx >= self._top + VIEWPORT: + self._top = self._idx - VIEWPORT + 1 + + def _scroll_to_reveal_first_child(self) -> None: + """Scroll down one row when an expanded dir sits at the viewport bottom.""" + node = self._nodes[self._idx] + if ( + node.is_dir + and node.expanded + and self._idx == self._top + VIEWPORT - 1 + and self._idx + 1 < len(self._nodes) + ): + self._top += 1 + + # ------------------------------------------------------------------ + # Node mutation + # ------------------------------------------------------------------ + + def _expand(self, idx: int) -> None: + """Expand the directory node at *idx*, loading children if needed.""" + node = self._nodes[idx] + if not node.children_loaded: + children = [ + TreeNode( + name=entry.display, + path=f"{node.path}/{entry.value}", + is_dir=entry.has_children, + depth=node.depth + 1, + selected=node.selected, + ) + for entry in self._ls_function(node.path) + ] + self._nodes[idx + 1 : idx + 1] = children + node.children_loaded = True + node.expanded = True + + def _collapse(self, idx: int) -> None: + """Collapse the directory node at *idx* and remove all descendants.""" + parent_depth = self._nodes[idx].depth + end = idx + 1 + while end < len(self._nodes) and self._nodes[end].depth > parent_depth: + end += 1 + del self._nodes[idx + 1 : end] + self._nodes[idx].expanded = False + self._nodes[idx].children_loaded = False + + def _cascade_selection(self, parent_idx: int, selected: bool) -> None: + """Set *selected* on all loaded descendants of *parent_idx*.""" + parent_depth = self._nodes[parent_idx].depth + for i in range(parent_idx + 1, len(self._nodes)): + if self._nodes[i].depth <= parent_depth: + break + self._nodes[i].selected = selected + + # ------------------------------------------------------------------ + # Rendering + # ------------------------------------------------------------------ + + def _nav_hint(self) -> str: + """Return the bottom navigation hint line.""" + esc = self._config.esc_label + if self._config.multi: + return ( + f"↑/↓ navigate Space toggle Enter confirm" + f" →/← expand/collapse Esc {esc}" + ) + return f"↑/↓ navigate Enter select →/← expand/collapse Esc {esc}" + + def _render_node(self, i: int, node: TreeNode) -> str: + """Render a single node row for the visible viewport.""" + indent = " " * node.depth + icon = ("▾ " if node.expanded else "▸ ") if node.is_dir else " " + cursor = f"{GREEN}▶{RESET}" if i == self._idx else " " + if self._config.multi: + name = f"{DIM}{node.name}{RESET}" if not node.selected else node.name + return f" {cursor} {indent}{icon}{name}" + check = f"{GREEN}✓ {RESET}" if node.selected else " " + name = f"{BOLD}{node.name}{RESET}" if i == self._idx else node.name + return f" {cursor} {check}{indent}{icon}{name}" + + def _render_lines(self) -> list[str]: + """Build display strings for the visible viewport slice.""" + return [ + self._render_node(i, self._nodes[i]) + for i in range(self._top, min(self._top + VIEWPORT, len(self._nodes))) + ] + + def _build_frame(self) -> list[str]: + """Build all display lines for one render frame.""" + header = [f" {GREEN}?{RESET} {BOLD}{self._title}:{RESET}"] + if self._top > 0: + header.append(f" {DIM}↑ {self._top} more above{RESET}") + footer: list[str] = [] + hidden_below = len(self._nodes) - (self._top + VIEWPORT) + if hidden_below > 0: + footer.append(f" {DIM}↓ {hidden_below} more below{RESET}") + footer.append(f" {DIM}{self._nav_hint()}{RESET}") + return header + self._render_lines() + footer + + # ------------------------------------------------------------------ + # Key handlers + # ------------------------------------------------------------------ + + def _handle_nav(self, key: str) -> bool: + """Handle arrow/page keys; mutate *_idx* and return True if handled.""" + n = len(self._nodes) + if key == "UP": + self._idx = max(0, self._idx - 1) + elif key == "DOWN": + self._idx = min(n - 1, self._idx + 1) + elif key == "PGUP": + self._idx = max(0, self._idx - VIEWPORT) + elif key == "PGDN": + self._idx = min(n - 1, self._idx + VIEWPORT) + else: + return False + return True + + def _handle_left(self) -> None: + """Collapse the current dir or jump to its parent.""" + node = self._nodes[self._idx] + if node.is_dir and node.expanded: + self._collapse(self._idx) + return + if node.depth > 0: + for i in range(self._idx - 1, -1, -1): + if self._nodes[i].depth < node.depth: + self._idx = i + return + + def _handle_space(self) -> list[str] | None: + """Toggle selection (multi) or select immediately (single).""" + node = self._nodes[self._idx] + if self._config.multi: + node.selected = not node.selected + if node.is_dir: + self._cascade_selection(self._idx, node.selected) + return None + return [node.path] + + def _handle_enter(self) -> list[str] | None: + """Confirm selection (multi), pick node (dirs_selectable), expand/collapse, or pick leaf.""" + node = self._nodes[self._idx] + if self._config.multi: + return [item.path for item in self._nodes if item.selected] + if node.is_dir: + if self._config.dirs_selectable: + return [node.path] + if node.expanded: + self._collapse(self._idx) + else: + self._expand(self._idx) + return None + return [node.path] + + def _handle_right(self, node: TreeNode) -> None: + """Expand the current directory on RIGHT, if not already open.""" + if node.is_dir and not node.expanded: + self._expand(self._idx) + + def _handle_action(self, key: str) -> list[str] | None: + """Dispatch a non-navigation keypress; return a result list or None to continue.""" + node = self._nodes[self._idx] + if key == "RIGHT": + self._handle_right(node) + elif key == "LEFT": + self._handle_left() + elif key == "SPACE": + return ( + self._handle_enter() if not self._config.multi else self._handle_space() + ) + elif key == "ENTER": + return self._handle_enter() + elif key == "ESC": + return [] + return None + + # ------------------------------------------------------------------ + # Main loop + # ------------------------------------------------------------------ + + def run(self) -> list[str]: # pragma: no cover - interactive TTY only + """Run the interactive browser; return selected paths (empty list on skip).""" + root_entries = self._ls_function("") + if not root_entries: + return [] + + self._nodes = [ + TreeNode( + name=entry.display, + path=entry.value, + is_dir=entry.has_children, + depth=0, + selected=self._config.all_selected, + ) + for entry in root_entries + ] + + if len(self._nodes) == 1 and self._nodes[0].is_dir: + self._expand(0) + + screen = Screen() + + while True: + self._idx = max(0, min(self._idx, len(self._nodes) - 1)) + self._adjust_scroll() + screen.draw(self._build_frame()) + key = read_key() + + if self._handle_nav(key): + continue + + result = self._handle_action(key) + if result is not None: + screen.clear() + return result + self._scroll_to_reveal_first_child() + + +# --------------------------------------------------------------------------- +# Public API +# --------------------------------------------------------------------------- + + +def run_tree_browser( + ls_function: LsFunction, + title: str, + config: BrowserConfig, +) -> tuple[list[str], list[TreeNode]]: # pragma: no cover - interactive TTY only + """Run a tree browser and return ``(selected_paths, final_nodes)``. + + In single-select mode ``selected_paths`` has at most one item; in + multi-select mode any number. ``final_nodes`` reflects the full node + state at the time the user exits. + """ + browser = TreeBrowser(ls_function, title, config) + return browser.run(), browser.nodes + + +def tree_single_pick( + ls_function: LsFunction, + title: str, + *, + dirs_selectable: bool = False, + esc_label: str = "skip", +) -> str: # pragma: no cover - interactive TTY only + """Browse a remote tree and return a single selected path (``""`` on skip).""" + config = BrowserConfig(dirs_selectable=dirs_selectable, esc_label=esc_label) + browser = TreeBrowser(ls_function, title, config) + result = browser.run() + return result[0] if result else "" + + +# --------------------------------------------------------------------------- +# Tree analysis helpers +# --------------------------------------------------------------------------- + + +def all_descendants_deselected(nodes: list[TreeNode], parent_idx: int) -> bool: + """Return True if every loaded descendant of *parent_idx* is deselected.""" + parent_depth = nodes[parent_idx].depth + for i in range(parent_idx + 1, len(nodes)): + if nodes[i].depth <= parent_depth: + break + if nodes[i].selected: + return False + return True + + +def _is_covered_by_dir(path: str, dirs: set[str]) -> bool: + """Return True if *path* is nested under any directory in *dirs*.""" + return any(path.startswith(d + "/") for d in dirs) + + +def deselected_paths(nodes: list[TreeNode]) -> list[str]: + """Compute minimal path list covering all deselected nodes. + + A deselected directory is emitted as a single entry when all its loaded + descendants are also deselected (or it was never expanded). This keeps + the list short. Individual deselected files are listed when their + parent directory is only partially deselected. + """ + paths: list[str] = [] + dirs: set[str] = set() + + for i, node in enumerate(nodes): + if node.selected: + continue + if _is_covered_by_dir(node.path, dirs): + continue + if node.is_dir and ( + not node.children_loaded or all_descendants_deselected(nodes, i) + ): + paths.append(node.path) + dirs.add(node.path) + elif not node.is_dir: + paths.append(node.path) + + return paths + + +# --------------------------------------------------------------------------- +# Flat-name tree browser +# --------------------------------------------------------------------------- + + +class _FlatNamesLs: # pylint: disable=too-few-public-methods + """LsFunction that exposes a flat list of slash-separated names as a tree. + + Leaf nodes carry a dim annotation label so they are visually distinct from + directory segments. The tree browser uses ``Entry.value`` (the plain name) + rather than ``Entry.display`` (which may contain markup). + """ + + def __init__( + self, labels: dict[str, str], all_names: list[str], priority_path: str + ) -> None: + """Store pre-built lookup tables and the path to sort first.""" + self._labels = labels + self._all_names = all_names + self._priority_path = priority_path + + def _segments(self, prefix: str) -> dict[str, bool]: + """Return first-level path segments under *prefix* mapped to has_children.""" + seen: dict[str, bool] = {} + for name in self._all_names: + if not name.startswith(prefix): + continue + rest = name[len(prefix) :] + seg = rest.split("/")[0] + if seg and seg not in seen: + full = prefix + seg + seen[seg] = any(n.startswith(full + "/") for n in self._all_names) + return seen + + def _sort_key(self, seg: str, prefix: str) -> tuple[int, str]: + """Sort priority path first, then alphabetically.""" + full = prefix + seg + on_priority = full == self._priority_path or self._priority_path.startswith( + full + "/" + ) + return (0 if on_priority else 1, seg) + + def _leaf_entry(self, seg: str, full: str) -> Entry: + """Build a leaf Entry with dim annotation label and optional default marker.""" + label = self._labels[full] + marker = f" {DIM}(default){RESET}" if full == self._priority_path else "" + return Entry( + display=f"{seg} {DIM}{label}{RESET}{marker}", + has_children=False, + value=seg, + ) + + def __call__(self, path: str) -> list[Entry]: + """Return entries for *path*, building the tree one level at a time.""" + prefix = (path + "/") if path else "" + seen = self._segments(prefix) + result: list[Entry] = [] + for seg in sorted(seen, key=lambda s: self._sort_key(s, prefix)): + full = prefix + seg + if seen[seg]: + result.append(Entry(display=seg, has_children=True)) + else: + result.append(self._leaf_entry(seg, full)) + return result + + +def tree_pick_from_names( + labels: dict[str, str], + title: str, + *, + priority_path: str = "", + esc_label: str = "skip", +) -> str: # pragma: no cover - interactive TTY only + """Browse a flat dict of ``name → label`` as a tree and return the picked name. + + Names are split on ``/`` to form a navigable hierarchy. Leaf nodes show + their label as a dim annotation. *priority_path* is sorted to the top. + Returns ``""`` when the user presses Esc. + """ + ls = _FlatNamesLs(labels, list(labels), priority_path) + return tree_single_pick(ls, title, esc_label=esc_label) diff --git a/dfetch/terminal/types.py b/dfetch/terminal/types.py new file mode 100644 index 00000000..2520a3cb --- /dev/null +++ b/dfetch/terminal/types.py @@ -0,0 +1,35 @@ +"""Type aliases for the terminal package.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass, field + + +@dataclass +class Entry: + """One entry returned by an :data:`LsFunction`. + + *display* is the string shown in the tree browser. *value* is the + underlying path segment used when building ``node.path``; it defaults + to *display* when not supplied, which is correct for plain file trees + where the two are identical. Set *value* explicitly when the display + carries decorations (ANSI codes, kind labels, etc.) that should not + bleed into the path. + + *has_children* indicates whether the entry can be expanded further. + For file trees this corresponds to directories; for other uses (e.g. + a version picker) it marks namespace groups that contain nested items. + """ + + display: str + has_children: bool + value: str = field(default="") + + def __post_init__(self) -> None: + """Default *value* to *display* when not explicitly provided.""" + if not self.value: + self.value = self.display + + +LsFunction = Callable[[str], list[Entry]] diff --git a/dfetch/util/license.py b/dfetch/util/license.py index bf6a693b..0b3c137e 100644 --- a/dfetch/util/license.py +++ b/dfetch/util/license.py @@ -1,5 +1,6 @@ """*Dfetch* uses *Infer-License* to guess licenses from files.""" +import fnmatch from dataclasses import dataclass from os import PathLike @@ -10,6 +11,15 @@ MAX_LICENSE_FILE_SIZE = 1024 * 1024 # 1 MB +#: Glob patterns used to identify license files by filename. +LICENSE_GLOBS = ["licen[cs]e*", "copying*", "copyright*"] + + +def is_license_file(filename: str) -> bool: + """Return *True* when *filename* matches a known license file pattern.""" + return any(fnmatch.fnmatch(filename.lower(), pattern) for pattern in LICENSE_GLOBS) + + @dataclass class License: """Represents a software license with its SPDX identifiers and detection confidence. diff --git a/dfetch/util/util.py b/dfetch/util/util.py index b3a6dfdd..890a7aca 100644 --- a/dfetch/util/util.py +++ b/dfetch/util/util.py @@ -13,13 +13,7 @@ from _hashlib import HASH -#: Glob patterns used to identify license files by filename. -LICENSE_GLOBS = ["licen[cs]e*", "copying*", "copyright*"] - - -def is_license_file(filename: str) -> bool: - """Return *True* when *filename* matches a known license file pattern.""" - return any(fnmatch.fnmatch(filename.lower(), pattern) for pattern in LICENSE_GLOBS) +from dfetch.util.license import is_license_file def _copy_entry(src_entry: str, dest_entry: str, root: str) -> None: diff --git a/dfetch/util/versions.py b/dfetch/util/versions.py index cca2302e..6d7ca713 100644 --- a/dfetch/util/versions.py +++ b/dfetch/util/versions.py @@ -1,8 +1,13 @@ """Module for handling version information from strings.""" +from __future__ import annotations + import re from collections import defaultdict +from dataclasses import dataclass +from typing import Literal +import semver from semver.version import Version BASEVERSION = re.compile( @@ -93,6 +98,46 @@ def _create_available_version_dict( return parsed_tags +@dataclass(frozen=True) +class VersionRef: + """A resolved version reference: a branch name, tag, or commit SHA.""" + + kind: Literal["branch", "tag", "revision"] + value: str + + +def prioritise_default(branches: list[str], default: str) -> list[str]: + """Return *branches* with *default* moved to position 0.""" + if default in branches: + rest = [b for b in branches if b != default] + return [default, *rest] + return branches + + +def sort_tags_newest_first(tags: list[str]) -> list[str]: + """Sort *tags* newest-semver-first; non-semver tags appended as-is.""" + + def _parse_semver(tag: str) -> semver.Version | None: + try: + return semver.Version.parse(tag.lstrip("vV")) + except ValueError: + return None + + parsed = {t: _parse_semver(t) for t in tags} + semver_tags = sorted( + (t for t, v in parsed.items() if v is not None), + key=lambda t: parsed[t], # type: ignore[arg-type, return-value] + reverse=True, + ) + non_semver = [t for t, v in parsed.items() if v is None] + return semver_tags + non_semver + + +def is_commit_sha(value: str) -> bool: + """Return True when *value* looks like a Git commit SHA (7–40 hex chars).""" + return bool(re.fullmatch(r"[0-9a-fA-F]{7,40}", value)) + + if __name__ == "__main__": import doctest diff --git a/dfetch/vcs/archive.py b/dfetch/vcs/archive.py index 9cbe513b..b8ce7216 100644 --- a/dfetch/vcs/archive.py +++ b/dfetch/vcs/archive.py @@ -31,6 +31,7 @@ import sys import tarfile import tempfile +import urllib.error import urllib.parse import urllib.request import zipfile @@ -130,7 +131,10 @@ def is_accessible(self) -> bool: """ parsed = urllib.parse.urlparse(self.url) if parsed.scheme == "file": - return os.path.exists(urllib.request.url2pathname(parsed.path)) + try: + return os.path.exists(urllib.request.url2pathname(parsed.path)) + except urllib.error.URLError: + return False if parsed.scheme not in ("http", "https"): return False return self._is_http_reachable(parsed) diff --git a/dfetch/vcs/git.py b/dfetch/vcs/git.py index a9be2869..781241d2 100644 --- a/dfetch/vcs/git.py +++ b/dfetch/vcs/git.py @@ -1,19 +1,21 @@ """Git specific implementation.""" +import contextlib import functools import glob import os import re +import shutil import tempfile -from collections.abc import Generator, Sequence +from collections.abc import Callable, Generator, Sequence from dataclasses import dataclass from pathlib import Path from dfetch.log import get_logger from dfetch.util.cmdline import SubprocessCommandError, run_on_cmdline +from dfetch.util.license import is_license_file from dfetch.util.util import ( in_directory, - is_license_file, move_directory_contents, safe_rm, strip_glob_prefix, @@ -148,6 +150,16 @@ def list_of_tags(self) -> list[str]: if reference.startswith("refs/tags/") ] + def list_of_branches(self) -> list[str]: + """Get list of all available branches.""" + info = self._ls_remote(self._remote) + + return [ + reference.replace("refs/heads/", "") + for reference, _ in info.items() + if reference.startswith("refs/heads/") + ] + def get_default_branch(self) -> str: """Try to get the default branch or fallback to master.""" try: @@ -189,6 +201,83 @@ def _ls_remote(remote: str) -> dict[str, str]: info[ref] = sha return info + def fetch_for_tree_browse(self, target: str, version: str) -> None: + """Fetch just enough objects to support ``ls_tree`` on *version*. + + Uses ``--no-checkout`` and ``--filter=blob:none`` so only tree objects + are transferred — no file contents are downloaded. + """ + run_on_cmdline(logger, ["git", "-C", target, "init"]) + run_on_cmdline( + logger, + [ + "git", + "-C", + target, + "fetch", + "--depth=1", + "--filter=blob:none", + self._remote, + version, + ], + env=_extend_env_for_non_interactive_mode(), + ) + + @contextlib.contextmanager + def browse_tree( + self, version: str = "" + ) -> Generator[Callable[[str], list[tuple[str, bool]]], None, None]: + """Shallow-clone the remote and yield a tree-listing callable. + + The yielded callable accepts a path and returns ``(name, is_dir)`` pairs. + The clone is removed on context exit. + """ + tmpdir = tempfile.mkdtemp(prefix="dfetch_browse_") + cloned = False + try: + self.fetch_for_tree_browse(tmpdir, version or self.get_default_branch()) + cloned = True + except Exception as e: # pylint: disable=broad-exception-caught + logger.debug("Failed to fetch remote tree for '%s': %s", self._remote, e) + + def ls(path: str = "") -> list[tuple[str, bool]]: + return GitRemote.ls_tree(tmpdir, path=path) if cloned else [] + + try: + yield ls + finally: + shutil.rmtree(tmpdir, ignore_errors=True) + + @staticmethod + def _parse_ls_tree_entry(line: str, prefix: str) -> tuple[str, bool]: + """Parse one ``git ls-tree`` output line into a ``(name, is_dir)`` pair.""" + meta, name = line.split("\t", 1) + base = name[len(prefix) :] if prefix and name.startswith(prefix) else name + return base, meta.split()[1] == "tree" + + @staticmethod + def ls_tree(local_path: str, path: str = "") -> list[tuple[str, bool]]: + """List the contents of the HEAD tree at *path* in a local clone. + + Returns entries sorted with directories first (alphabetically), + then files (alphabetically). + """ + cmd = ["git", "-C", local_path, "ls-tree", "FETCH_HEAD"] + if path: + cmd.append(path.rstrip("/") + "/") + try: + result = run_on_cmdline(logger, cmd=cmd) + prefix = (path.rstrip("/") + "/") if path else "" + entries = [ + GitRemote._parse_ls_tree_entry(line, prefix) + for line in result.stdout.decode().splitlines() + if line.strip() + ] + entries.sort(key=lambda e: (not e[1], e[0])) # dirs first, then files + return entries + except SubprocessCommandError: + return [] + @staticmethod def _find_sha_of_branch_or_tag(info: dict[str, str], branch_or_tag: str) -> str: """Find SHA of a branch tip or tag.""" diff --git a/dfetch/vcs/svn.py b/dfetch/vcs/svn.py index e8ca6f84..89231a9d 100644 --- a/dfetch/vcs/svn.py +++ b/dfetch/vcs/svn.py @@ -1,9 +1,10 @@ """Svn repository.""" +import contextlib import os import pathlib import re -from collections.abc import Sequence +from collections.abc import Callable, Generator, Sequence from pathlib import Path from typing import NamedTuple @@ -60,6 +61,21 @@ def is_svn(self) -> bool: except RuntimeError: return False + def list_of_branches(self) -> list[str]: + """List branch names from the ``branches/`` directory.""" + try: + result = run_on_cmdline( + logger, + ["svn", "ls", "--non-interactive", f"{self._remote}/branches"], + ) + return [ + line.strip("/\r") + for line in result.stdout.decode().splitlines() + if line.strip("/\r") + ] + except (SubprocessCommandError, RuntimeError): + return [] + def list_of_tags(self) -> list[str]: """Get list of all available tags.""" result = run_on_cmdline( @@ -69,6 +85,50 @@ def list_of_tags(self) -> list[str]: str(tag).strip("/\r") for tag in result.stdout.decode().split("\n") if tag ] + @contextlib.contextmanager + def browse_tree( + self, version: str = "" + ) -> Generator[Callable[[str], list[tuple[str, bool]]], None, None]: + """Yield a callable that lists SVN tree contents for *version*. + + Resolves *version* to the correct remote path (trunk, + ``branches/``, or ``tags/``), then delegates + directory listing to ``svn ls``. The callable returns ``(name, is_dir)`` pairs. + """ + version = version or SvnRepo.DEFAULT_BRANCH + if version == SvnRepo.DEFAULT_BRANCH: + base_url = f"{self._remote}/{SvnRepo.DEFAULT_BRANCH}" + else: + branches_url = f"{self._remote}/branches/{version}" + try: + SvnRepo.get_info_from_target(branches_url) + base_url = branches_url + except RuntimeError: + base_url = f"{self._remote}/tags/{version}" + + def ls(path: str = "") -> list[tuple[str, bool]]: + url = f"{base_url}/{path}" if path else base_url + return self.ls_tree(url) + + yield ls + + def ls_tree(self, url_path: str) -> list[tuple[str, bool]]: + """List immediate children of *url_path* as ``(name, is_dir)`` pairs.""" + try: + result = run_on_cmdline( + logger, ["svn", "ls", "--non-interactive", url_path] + ) + entries: list[tuple[str, bool]] = [] + for line in result.stdout.decode().splitlines(): + line = line.strip("\r") + if not line: + continue + is_dir = line.endswith("/") + entries.append((line.rstrip("/"), is_dir)) + return entries + except (SubprocessCommandError, RuntimeError): + return [] + class SvnRepo: """An svn repository.""" diff --git a/doc/asciicasts/add.cast b/doc/asciicasts/add.cast new file mode 100644 index 00000000..a136c2c4 --- /dev/null +++ b/doc/asciicasts/add.cast @@ -0,0 +1,86 @@ +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774765, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.526136, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.529983, "o", "$ "] +[1.532978, "o", "\u001b"] +[1.713503, "o", "[1"] +[1.80361, "o", "mc"] +[1.893763, "o", "at"] +[1.983888, "o", " d"] +[2.074031, "o", "fe"] +[2.164342, "o", "tc"] +[2.254453, "o", "h."] +[2.344586, "o", "ya"] +[2.434735, "o", "ml"] +[2.614966, "o", "\u001b"] +[2.705088, "o", "[0"] +[2.795223, "o", "m"] +[3.796618, "o", "\r\n"] +[3.799921, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] +[3.806059, "o", "$ "] +[4.809269, "o", "\u001b"] +[4.989573, "o", "[1"] +[5.079884, "o", "md"] +[5.170155, "o", "fe"] +[5.260134, "o", "t"] +[5.35027, "o", "ch"] +[5.440405, "o", " a"] +[5.530529, "o", "dd"] +[5.620673, "o", " h"] +[5.710822, "o", "t"] +[5.891083, "o", "tp"] +[5.98132, "o", "s:"] +[6.071444, "o", "//"] +[6.161576, "o", "gi"] +[6.251704, "o", "t"] +[6.341821, "o", "hu"] +[6.431956, "o", "b."] +[6.52208, "o", "co"] +[6.61222, "o", "m/"] +[6.792762, "o", "d"] +[6.882949, "o", "fe"] +[6.973091, "o", "tc"] +[7.063222, "o", "h-"] +[7.153345, "o", "or"] +[7.243847, "o", "g"] +[7.333795, "o", "/d"] +[7.42393, "o", "fe"] +[7.51406, "o", "tc"] +[7.69429, "o", "h."] +[7.784584, "o", "g"] +[7.874714, "o", "it"] +[7.96483, "o", "\u001b["] +[8.054957, "o", "0m"] +[9.056615, "o", "\r\n"] +[9.52535, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[10.052605, "o", " \u001b[1;92mhttps://github.com/dfetch-org/dfetch.git:\u001b[0m\r\n"] +[10.053209, "o", " \u001b[1;34m> Adding project to manifest\u001b[0m\r\n"] +[10.053749, "o", " - \u001b[34mname:\u001b[0m dfetch\r\n"] +[10.054229, "o", " \u001b[34mremote:\u001b[0m github\r\n"] +[10.054731, "o", " \u001b[34mbranch:\u001b[0m main\r\n"] +[10.055203, "o", " \u001b[34mrepo-path:\u001b[0m dfetch-org/dfetch.git\r\n"] +[10.05624, "o", " \u001b[1;92mdfetch:\u001b[0m\r\n"] +[10.056777, "o", " \u001b[1;34m> Added 'dfetch' to manifest '/workspaces/dfetch/doc/generate-casts/add/dfetch.yaml'\u001b[0m\r\n"] +[10.114965, "o", "$ "] +[11.118103, "o", "\u001b"] +[11.298382, "o", "[1"] +[11.388507, "o", "mc"] +[11.478657, "o", "at"] +[11.568774, "o", " d"] +[11.658911, "o", "fe"] +[11.749043, "o", "tc"] +[11.839164, "o", "h."] +[11.929283, "o", "ya"] +[12.019422, "o", "ml"] +[12.199664, "o", "\u001b"] +[12.289795, "o", "[0"] +[12.379925, "o", "m"] +[13.381651, "o", "\r\n"] +[13.387827, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n\r\n - name: dfetch\r\n remote: github\r\n branch: main\r\n repo-path: dfetch-org/dfetch.git\r\n"] +[16.398412, "o", "$ "] +[16.40025, "o", "\u001b"] +[16.580666, "o", "[1"] +[16.670843, "o", "m\u001b"] +[16.76096, "o", "[0"] +[16.851064, "o", "m"] +[16.851564, "o", "\r\n"] +[16.854342, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/basic.cast b/doc/asciicasts/basic.cast index 52323e71..1e1fb36b 100644 --- a/doc/asciicasts/basic.cast +++ b/doc/asciicasts/basic.cast @@ -1,167 +1,163 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247049, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.575054, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.578772, "o", "$ "] -[1.753268, "o", "\u001b"] -[1.932344, "o", "[1"] -[2.022517, "o", "ml"] -[2.112659, "o", "s "] -[2.202799, "o", "-"] -[2.292924, "o", "l\u001b"] -[2.383263, "o", "[0"] -[2.473361, "o", "m"] -[3.475061, "o", "\r\n"] -[3.592392, "o", "total 4\r\n"] -[3.592558, "o", "-rw-rw-rw- 1 dev dev 733 Mar 23 06:24 dfetch.yaml\r\n"] -[3.597748, "o", "$ "] -[4.601017, "o", "\u001b"] -[4.78132, "o", "[1"] -[4.871477, "o", "mc"] -[4.961617, "o", "at"] -[5.05174, "o", " "] -[5.141881, "o", "df"] -[5.232023, "o", "et"] -[5.322152, "o", "ch"] -[5.41559, "o", ".y"] -[5.503826, "o", "a"] -[5.684086, "o", "ml"] -[5.77425, "o", "\u001b["] -[5.864343, "o", "0m"] -[6.865923, "o", "\r\n"] -[6.869423, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:"] -[6.869965, "o", "\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] -[6.878482, "o", "$ "] -[7.881322, "o", "\u001b"] -[8.06242, "o", "[1"] -[8.152555, "o", "md"] -[8.242702, "o", "fe"] -[8.33286, "o", "tc"] -[8.422958, "o", "h "] -[8.513105, "o", "ch"] -[8.60326, "o", "ec"] -[8.693366, "o", "k\u001b"] -[8.783543, "o", "[0"] -[8.963788, "o", "m"] -[9.965336, "o", "\r\n"] -[10.443987, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[10.461279, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n\u001b[?25l"] -[10.545844, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[10.626481, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[10.707096, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[10.768657, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[10.769949, "o", " \u001b[1;34m> wanted (v3.4), available (v4.0)\u001b[0m\r\n"] -[10.770873, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] -[10.771001, "o", "\u001b[?25l"] -[10.851827, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[10.932411, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[11.012985, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[11.093673, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[11.174356, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[11.254952, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[11.333922, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Checking\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[11.334767, "o", " \u001b[1;34m> available (master - 25647e692c7906b96ffd2b05ca54c097948e879c)\u001b[0m\r\n"] -[11.39488, "o", "$ "] -[12.398057, "o", "\u001b["] -[12.578008, "o", "1m"] -[12.668157, "o", "se"] -[12.758294, "o", "d "] -[12.848439, "o", "-i"] -[12.938573, "o", " '"] -[13.031414, "o", "s/"] -[13.119473, "o", "v3"] -[13.209611, "o", ".4"] -[13.299752, "o", "/v"] -[13.48002, "o", "4."] -[13.572312, "o", "0/"] -[13.662469, "o", "g'"] -[13.752595, "o", " d"] -[13.842732, "o", "fe"] -[13.932863, "o", "tc"] -[14.023001, "o", "h."] -[14.113131, "o", "ya"] -[14.203252, "o", "ml"] -[14.383655, "o", "\u001b["] -[14.473811, "o", "0m"] -[15.475425, "o", "\r\n"] -[15.483813, "o", "$ "] -[16.486997, "o", "\u001b["] -[16.667392, "o", "1m"] -[16.757532, "o", "ca"] -[16.847666, "o", "t "] -[16.937813, "o", "dfe"] -[17.02794, "o", "tc"] -[17.118085, "o", "h."] -[17.208242, "o", "ya"] -[17.298352, "o", "ml"] -[17.388489, "o", "\u001b[0"] -[17.568715, "o", "m"] -[18.570374, "o", "\r\n"] -[18.573178, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote"] -[18.573433, "o", "\r\n tag: v4.0 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] -[18.578342, "o", "$ "] -[19.581564, "o", "\u001b["] -[19.761822, "o", "1m"] -[19.851982, "o", "df"] -[19.942275, "o", "et"] -[20.0323, "o", "ch"] -[20.122431, "o", " u"] -[20.21255, "o", "pd"] -[20.302703, "o", "at"] -[20.393007, "o", "e\u001b"] -[20.483147, "o", "[0"] -[20.663386, "o", "m"] -[21.664696, "o", "\r\n"] -[22.170593, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[22.184259, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] -[22.184389, "o", "\u001b[?25l"] -[22.2691, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[22.349759, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[22.430361, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[22.510939, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[22.591552, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[22.672127, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[22.753055, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[22.834399, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[22.916339, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[22.996944, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[23.077558, "o", "\r\u001b[2K\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[23.160256, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[23.190028, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] -[23.190076, "o", "\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[23.190619, "o", " \u001b[1;34m> Fetched v4.0\u001b[0m\r\n"] -[23.215673, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] -[23.215814, "o", "\u001b[?25l"] -[23.296738, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[23.37734, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[23.457893, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[23.538471, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[23.619047, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[23.699634, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[23.780205, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[23.861876, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[23.942462, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[24.026863, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[24.107584, "o", "\r\u001b[2K\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[24.1881, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[24.268677, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[24.349248, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[24.400093, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching \u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[24.400965, "o", " \u001b[1;34m> Fetched master - 25647e692c7906b96ffd2b05ca54c097948e879c\u001b[0m\r\n"] -[24.474768, "o", "$ "] -[25.477969, "o", "\u001b"] -[25.658242, "o", "[1"] -[25.748365, "o", "ml"] -[25.838504, "o", "s "] -[25.928959, "o", "-"] -[26.019122, "o", "l\u001b"] -[26.109255, "o", "[0"] -[26.199373, "o", "m"] -[27.201007, "o", "\r\n"] -[27.204466, "o", "total 12\r\n"] -[27.204578, "o", "drwxrwxrwx+ 3 dev dev 4096 Mar 23 06:24 cpputest\r\n-rw-rw-rw- 1 dev dev 733 Mar 23 06:24 dfetch.yaml\r\ndrwxrwxrwx+ 4 dev dev 4096 Mar 23 06:24 jsmn\r\n"] -[30.213341, "o", "$ "] -[30.215274, "o", "\u001b["] -[30.395592, "o", "1m"] -[30.485702, "o", "\u001b["] -[30.575831, "o", "0m"] -[30.576462, "o", "\r\n"] -[30.579282, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774719, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.51454, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.518533, "o", "$ "] +[1.521734, "o", "\u001b"] +[1.702056, "o", "[1"] +[1.792431, "o", "ml"] +[1.882603, "o", "s "] +[1.972708, "o", "-l"] +[2.062839, "o", "\u001b["] +[2.15297, "o", "0m"] +[3.154465, "o", "\r\n"] +[3.157987, "o", "total 4\r\n"] +[3.158039, "o", "-rw-rw-rw- 1 dev dev 733 Mar 29 08:58 dfetch.yaml\r\n"] +[3.16297, "o", "$ "] +[4.166161, "o", "\u001b["] +[4.346484, "o", "1m"] +[4.436548, "o", "ca"] +[4.526693, "o", "t "] +[4.61682, "o", "dfe"] +[4.708656, "o", "tc"] +[4.797875, "o", "h."] +[4.888015, "o", "ya"] +[4.978137, "o", "ml"] +[5.068398, "o", "\u001b[0"] +[5.248651, "o", "m"] +[6.250173, "o", "\r\n"] +[6.25345, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] +[6.258653, "o", "$ "] +[7.261801, "o", "\u001b["] +[7.442068, "o", "1m"] +[7.532653, "o", "df"] +[7.62236, "o", "et"] +[7.712488, "o", "ch "] +[7.802632, "o", "ch"] +[7.892769, "o", "ec"] +[7.982873, "o", "k\u001b"] +[8.07301, "o", "[0"] +[8.163131, "o", "m"] +[9.164647, "o", "\r\n"] +[9.651652, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[9.678456, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n\u001b[?25l"] +[9.76056, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[9.841154, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[9.921787, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.002443, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.082956, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.152372, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Checking\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] +[10.154105, "o", " \u001b[1;34m> wanted (v3.4), available (v4.0)\u001b[0m\r\n \u001b[1;92mjsmn:\u001b[0m"] +[10.1547, "o", "\r\n\u001b[?25l"] +[10.23593, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.316234, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.396835, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.477363, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.557931, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.638501, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.718994, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.799692, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.833604, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.834039, "o", "\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] +[10.835057, "o", " \u001b[1;34m> available (master - 25647e692c7906b96ffd2b05ca54c097948e879c)\u001b[0m\r\n"] +[10.925167, "o", "$ "] +[11.927744, "o", "\u001b["] +[12.10803, "o", "1m"] +[12.198152, "o", "se"] +[12.288288, "o", "d "] +[12.378411, "o", "-i "] +[12.468571, "o", "'s"] +[12.558733, "o", "/v"] +[12.648829, "o", "3."] +[12.739059, "o", "4/"] +[12.829156, "o", "v4."] +[13.009525, "o", "0/"] +[13.099717, "o", "g'"] +[13.189856, "o", " d"] +[13.279982, "o", "fe"] +[13.370095, "o", "tch"] +[13.46024, "o", ".y"] +[13.550382, "o", "am"] +[13.640505, "o", "l\u001b"] +[13.730642, "o", "[0"] +[13.910817, "o", "m"] +[14.912399, "o", "\r\n"] +[14.921958, "o", "$ "] +[15.924897, "o", "\u001b["] +[16.105144, "o", "1m"] +[16.195287, "o", "ca"] +[16.285438, "o", "t "] +[16.375546, "o", "df"] +[16.465695, "o", "et"] +[16.555826, "o", "ch"] +[16.645988, "o", ".y"] +[16.736133, "o", "am"] +[16.826252, "o", "l\u001b"] +[17.006477, "o", "[0m"] +[18.008241, "o", "\r\n"] +[18.011006, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v4.0 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] +[18.016229, "o", "$ "] +[19.01921, "o", "\u001b["] +[19.199487, "o", "1m"] +[19.289638, "o", "df"] +[19.379751, "o", "et"] +[19.469878, "o", "ch"] +[19.560015, "o", " u"] +[19.650146, "o", "pd"] +[19.740262, "o", "at"] +[19.830399, "o", "e\u001b"] +[19.922389, "o", "[0"] +[20.102168, "o", "m"] +[21.103221, "o", "\r\n"] +[21.534191, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[21.548022, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] +[21.548169, "o", "\u001b[?25l"] +[21.62924, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[21.709844, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[21.790421, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[21.870992, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[21.951553, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[22.032133, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[22.112993, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[22.196255, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[22.276631, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[22.357193, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[22.43769, "o", "\r\u001b[2K\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[22.518241, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m"] +[22.551063, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v4.0\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] +[22.551816, "o", " \u001b[1;34m> Fetched v4.0\u001b[0m\r\n"] +[22.577375, "o", " \u001b[1;92mjsmn:\u001b[0m"] +[22.577929, "o", "\r\n\u001b[?25l"] +[22.659, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[22.739634, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[22.820175, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[22.90067, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[22.981272, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[23.061846, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[23.142397, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[23.222972, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[23.303796, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[23.385465, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[23.466016, "o", "\r\u001b[2K\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[23.546594, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[23.624793, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[23.62564, "o", "\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] +[23.626212, "o", " \u001b[1;34m> Fetched master - 25647e692c7906b96ffd2b05ca54c097948e879c\u001b[0m"] +[23.626478, "o", "\r\n"] +[23.715724, "o", "$ "] +[24.719051, "o", "\u001b"] +[24.899468, "o", "[1"] +[24.989611, "o", "ml"] +[25.079748, "o", "s "] +[25.169869, "o", "-"] +[25.259998, "o", "l\u001b"] +[25.350136, "o", "[0"] +[25.440269, "o", "m"] +[26.441785, "o", "\r\n"] +[26.44542, "o", "total 12\r\n"] +[26.445471, "o", "drwxrwxrwx+ 3 dev dev 4096 Mar 29 08:59 cpputest\r\n-rw-rw-rw- 1 dev dev 733 Mar 29 08:58 dfetch.yaml\r\ndrwxrwxrwx+ 4 dev dev 4096 Mar 29 08:59 jsmn\r\n"] +[29.454233, "o", "$ "] +[29.45604, "o", "\u001b["] +[29.636322, "o", "1m"] +[29.726466, "o", "\u001b["] +[29.816685, "o", "0m"] +[29.817111, "o", "\r\n"] +[29.820288, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/check-ci.cast b/doc/asciicasts/check-ci.cast index 6e2b6f2a..588d3d72 100644 --- a/doc/asciicasts/check-ci.cast +++ b/doc/asciicasts/check-ci.cast @@ -1,122 +1,130 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247123, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.52885, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.532733, "o", "$ "] -[1.536017, "o", "\u001b["] -[1.716296, "o", "1m"] -[1.806423, "o", "ca"] -[1.896572, "o", "t "] -[1.986691, "o", "df"] -[2.076842, "o", "et"] -[2.166966, "o", "ch"] -[2.257112, "o", ".y"] -[2.347264, "o", "am"] -[2.43738, "o", "l\u001b"] -[2.617631, "o", "[0m"] -[3.620643, "o", "\r\n"] -[3.624392, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/"] -[3.626141, "o", "\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] -[3.643676, "o", "$ "] -[4.647179, "o", "\u001b["] -[4.827498, "o", "1m"] -[4.917648, "o", "df"] -[5.007781, "o", "et"] -[5.097891, "o", "ch"] -[5.188041, "o", " c"] -[5.278357, "o", "he"] -[5.36838, "o", "ck"] -[5.458508, "o", " -"] -[5.548643, "o", "-j"] -[5.728891, "o", "en"] -[5.819135, "o", "ki"] -[5.909254, "o", "ns"] -[5.999532, "o", "-j"] -[6.08972, "o", "so"] -[6.179792, "o", "n "] -[6.270004, "o", "je"] -[6.360141, "o", "nk"] -[6.450395, "o", "in"] -[6.630659, "o", "s."] -[6.7208, "o", "js"] -[6.810917, "o", "on"] -[6.901059, "o", " -"] -[6.991186, "o", "-s"] -[7.081346, "o", "ar"] -[7.171499, "o", "if"] -[7.261628, "o", " s"] -[7.35179, "o", "ar"] -[7.532145, "o", "if"] -[7.622447, "o", ".j"] -[7.712556, "o", "so"] -[7.802691, "o", "n\u001b"] -[7.89283, "o", "[0"] -[7.982967, "o", "m"] -[8.984729, "o", "\r\n"] -[9.445039, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[9.46285, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n\u001b[?25l"] -[9.54838, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[9.629109, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[9.70974, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[9.742882, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[9.743862, "o", " \u001b[1;34m> wanted (v3.4), available (v4.0)\u001b[0m\r\n"] -[9.745104, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] -[9.745236, "o", "\u001b[?25l"] -[9.826709, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[9.907555, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[9.988104, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[10.068658, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[10.14928, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[10.229882, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[10.284178, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Checking\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[10.284909, "o", " \u001b[1;34m> available (master - 25647e692c7906b96ffd2b05ca54c097948e879c)\u001b[0m\r\n"] -[10.348052, "o", "$ "] -[11.351255, "o", "\u001b["] -[11.531556, "o", "1m"] -[11.62171, "o", "ls"] -[11.711859, "o", " -"] -[11.801989, "o", "l "] -[11.892109, "o", ".\u001b"] -[11.982263, "o", "[0"] -[12.072381, "o", "m"] -[13.073949, "o", "\r\n"] -[13.077493, "o", "total 16\r\n"] -[13.077605, "o", "-rw-rw-rw- 1 dev dev 733 Mar 23 06:25 dfetch.yaml\r\n-rw-rw-rw- 1 dev dev 1027 Mar 23 06:25 jenkins.json\r\n-rw-rw-rw- 1 dev dev 6117 Mar 23 06:25 sarif.json\r\n"] -[13.082295, "o", "$ "] -[14.085361, "o", "\u001b["] -[14.265626, "o", "1m"] -[14.35578, "o", "ca"] -[14.445943, "o", "t "] -[14.536058, "o", "je"] -[14.62619, "o", "nk"] -[14.716338, "o", "in"] -[14.806499, "o", "s."] -[14.896636, "o", "js"] -[14.987072, "o", "on"] -[15.167108, "o", "\u001b["] -[15.257235, "o", "0m"] -[16.258427, "o", "\r\n"] -[16.261557, "o", "{\r\n \"_class\": \"io.jenkins.plugins.analysis.core.restapi.ReportApi\",\r\n \"issues\": [\r\n {\r\n \"fileName\": \"dfetch.yaml\",\r\n \"severity\": \"High\",\r\n \"message\": \"cpputest : cpputest was never fetched!\",\r\n \"description\": \"The manifest requires version 'v3.4' of cpputest. it was never fetched, fetch it with 'dfetch update cpputest'. The latest version available is 'v4.0'\",\r\n \"lineStart\": 9,\r\n \"lineEnd\": 9,\r\n \"columnStart\": 11,\r\n \"columnEnd\": 18\r\n },\r\n {\r\n \"fileName\": \"dfetch.yaml\",\r\n \"severity\": \"High\",\r\n \"message\": \"jsmn : jsmn was never fetched!\",\r\n \"description\": \"The manifest requires version 'latest' of jsmn. it was never fetched, fetch it with 'dfetch update jsmn'. The latest version available is 'master - 25647e692c7906b96ffd2b05ca54c097948e879c'\",\r\n \"lineStart\": 14,\r\n \"lineEnd\": 14,\r\n \"columnStart\": 11,\r\n \"columnEnd\": 14\r\n }\r\n ]\r\n}"] -[16.267501, "o", "$ "] -[17.270649, "o", "\u001b"] -[17.450852, "o", "[1"] -[17.541025, "o", "mc"] -[17.631162, "o", "at"] -[17.721302, "o", " s"] -[17.811429, "o", "ar"] -[17.901979, "o", "if"] -[17.99255, "o", ".j"] -[18.081936, "o", "so"] -[18.172071, "o", "n\u001b"] -[18.352413, "o", "["] -[18.442553, "o", "0m"] -[19.444145, "o", "\r\n"] -[19.447374, "o", "{\r\n \"runs\": [\r\n {\r\n \"tool\": {\r\n \"driver\": {\r\n \"name\": \"DFetch\",\r\n \"informationUri\": \"https://dfetch.rtfd.io\","] -[19.447414, "o", "\r\n \"rules\": [\r\n {"] -[19.447462, "o", "\r\n \"id\": \"unfetched-project\",\r\n \"help\": {\r\n \"text\": \"The project mentioned in the manifest was never fetched, fetch it with 'dfetch update '. After fetching, commit the updated project to your repository.\"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Project was never fetched\"\r\n }\r\n },\r\n {\r\n \"id\": \"up-to-date-project\",\r\n \"help\": {\r\n \"text\": \"The project mentioned in the manifest is up-to-date, everything is ok, nothing to do.\"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Project is up-to-date\"\r\n }\r\n },\r\n {\r\n \"id\": \"unavailable-project-version\",\r\n \"help\": {\r\n \"text\": \"The project mentioned in the manifest is pinned to a specific version, For instance a branch, tag, or revision. However the specific version is not available at the upstream of the project. Check if the remote has the given version. \"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Requested project version is unavailable at the remote\"\r\n }\r\n },\r\n {\r\n \"id\": \"pinned-but-out-of-date-project\",\r\n \"help\": {\r\n \"text\": \"The project mentioned in the manifest is pinned to a specific version, For instance a branch, tag, or revision. This is currently the state of the project. However a newer version is available at the upstream of the project. Either ignore this warning or update the version to the latest and update using 'dfetch update ' and commit the result to your repository.\"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Project is pinned, but out-of-date\"\r\n }\r\n },\r\n {\r\n \"id\": \"out-of-date-project\",\r\n \"help\": {\r\n \"text\": \"The project is configured to always follow the latest version, There is a newer version available at the upstream of the project. Please update the project using 'dfetch update ' and commit the result to your repository.\"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Project is out-of-date\"\r\n }\r\n },\r\n {\r\n \"id\": \"local-changes-in-project\",\r\n \"help\": {\r\n \"text\": \"The files of this project are different then when they were added, Please create a patch using 'dfetch diff ' and add it to the manifest using the 'patch:' attribute. Or better yet, upstream the changes and update your project. When running 'dfetch check' on a platform with different line endings, then this warning is likely a false positive.\"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Project was locally changed\"\r\n }\r\n }\r\n ]\r\n }\r\n },\r\n \"artifacts\": [\r\n {\r\n \"location\": {\r\n \"uri\": \"dfetch.yaml\"\r\n },\r\n \"sourceLanguage\": \"yaml\"\r\n }\r\n ],\r\n \"results\": [\r\n {\r\n \"mess"] -[19.447485, "o", "age\": {\r\n \"text\": \"cpputest : cpputest was never fetched!\"\r\n },\r\n \"level\": \"error\",\r\n \"locations\": [\r\n {\r\n \"physicalLocation\": {\r\n \"artifactLocation\": {\r\n \"index\": 0,\r\n \"uri\": \"dfetch.yaml\"\r\n },\r\n \"region\": {\r\n \"endColumn\": 19,\r\n \"endLine\": 9,\r\n \"startColumn\": 11,\r\n \"startLine\": 9\r\n }\r\n }\r\n }\r\n ],\r\n \"ruleId\": \"unfetched-project\"\r\n },\r\n {\r\n \"message\": {\r\n \"text\": \"jsmn : jsmn was never fetched!\"\r\n },\r\n \"level\": \"error\",\r\n \"locations\": [\r\n {\r\n \"physicalLocation\": {\r\n \"artifactLocation\": {\r\n \"index\": 0,\r\n \"uri\": \"dfetch.yaml\"\r\n },\r\n \"region\": {\r\n \"endColumn\": 15,\r\n \"endLine\": 14,\r\n \"startColumn\": 11,\r\n \"startLine\": 14\r\n }\r\n }\r\n }\r\n ],\r\n \"ruleId\": \"unfetched-project\"\r\n }\r\n ]\r\n }\r\n ],\r\n \"version\": \"2.1.0\"\r\n}"] -[22.455303, "o", "$ "] -[22.457294, "o", "\u001b["] -[22.637561, "o", "1m"] -[22.727694, "o", "\u001b["] -[22.817825, "o", "0m"] -[22.818423, "o", "\r\n"] -[22.821348, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774809, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.604147, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.608004, "o", "$ "] +[1.611257, "o", "\u001b"] +[1.791504, "o", "[1"] +[1.881637, "o", "mc"] +[1.971798, "o", "at"] +[2.061926, "o", " "] +[2.152043, "o", "df"] +[2.242169, "o", "et"] +[2.332317, "o", "ch"] +[2.422458, "o", ".y"] +[2.512578, "o", "a"] +[2.692957, "o", "ml"] +[2.783078, "o", "\u001b["] +[2.873246, "o", "0m"] +[3.874904, "o", "\r\n"] +[3.878249, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] +[3.883761, "o", "$ "] +[4.887097, "o", "\u001b"] +[5.068525, "o", "[1"] +[5.158666, "o", "md"] +[5.248808, "o", "fe"] +[5.338924, "o", "tc"] +[5.429056, "o", "h "] +[5.51919, "o", "ch"] +[5.60931, "o", "ec"] +[5.699477, "o", "k "] +[5.789607, "o", "--"] +[5.970038, "o", "j"] +[6.060164, "o", "en"] +[6.15031, "o", "ki"] +[6.240431, "o", "ns"] +[6.330569, "o", "-j"] +[6.420692, "o", "so"] +[6.51082, "o", "n "] +[6.600961, "o", "je"] +[6.691224, "o", "nk"] +[6.871441, "o", "in"] +[6.961626, "o", "s"] +[7.051747, "o", ".j"] +[7.141882, "o", "so"] +[7.232011, "o", "n "] +[7.322126, "o", "--"] +[7.412261, "o", "sa"] +[7.50262, "o", "ri"] +[7.592741, "o", "f "] +[7.773026, "o", "sa"] +[7.863146, "o", "ri"] +[7.953265, "o", "f"] +[8.043408, "o", ".j"] +[8.133554, "o", "so"] +[8.233129, "o", "n\u001b"] +[8.313833, "o", "[0"] +[8.404005, "o", "m"] +[9.405803, "o", "\r\n"] +[9.849789, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[9.86444, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] +[9.864693, "o", "\u001b[?25l"] +[9.945501, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.027606, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.108102, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.18869, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.249285, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Checking\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] +[10.250143, "o", " \u001b[1;34m> wanted (v3.4), available (v4.0)\u001b[0m\r\n"] +[10.25205, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n\u001b[?25l"] +[10.3327, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.41325, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.493796, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.574356, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.654941, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.735935, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.817575, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.898131, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[10.978693, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[11.059264, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[11.085828, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Checking\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] +[11.086521, "o", " \u001b[1;34m> available (master - 25647e692c7906b96ffd2b05ca54c097948e879c)\u001b[0m\r\n"] +[11.154468, "o", "$ "] +[12.157324, "o", "\u001b["] +[12.337585, "o", "1m"] +[12.427894, "o", "ls"] +[12.51809, "o", " -"] +[12.608211, "o", "l "] +[12.698342, "o", ".\u001b"] +[12.788478, "o", "[0"] +[12.878609, "o", "m"] +[13.880167, "o", "\r\n"] +[13.883089, "o", "total 16\r\n"] +[13.883338, "o", "-rw-rw-rw- 1 dev dev 733 Mar 29 09:00 dfetch.yaml\r\n-rw-rw-rw- 1 dev dev 1027 Mar 29 09:00 jenkins.json\r\n-rw-rw-rw- 1 dev dev 6117 Mar 29 09:00 sarif.json\r\n"] +[13.888523, "o", "$ "] +[14.897366, "o", "\u001b["] +[15.077243, "o", "1m"] +[15.167373, "o", "ca"] +[15.257506, "o", "t "] +[15.347623, "o", "je"] +[15.437757, "o", "nk"] +[15.527907, "o", "in"] +[15.618015, "o", "s."] +[15.708188, "o", "js"] +[15.798304, "o", "on"] +[15.978946, "o", "\u001b["] +[16.069089, "o", "0m"] +[17.070826, "o", "\r\n"] +[17.073648, "o", "{\r\n \"_class\": \"io.jenkins.plugins.analysis.core.restapi.ReportApi\",\r\n \"issues\": [\r\n {\r\n \"fileName\": \"dfetch.yaml\",\r\n \"severity\": \"High\",\r\n \"message\": \"cpputest : cpputest was never fetched!\",\r\n \"description\": \"The manifest requires version 'v3.4' of cpputest. it was never fetched, fetch it with 'dfetch update cpputest'. The latest version available is 'v4.0'\",\r\n"] +[17.0738, "o", " \"lineStart\": 9,\r\n \"lineEnd\": 9,\r\n \"columnStart\": 11,\r\n \"columnEnd\": 18\r\n },\r\n {\r\n \"fileName\": \"dfetch.yaml\",\r\n \"severity\": \"High\",\r\n \"message\": \"jsmn : jsmn was never fetched!\",\r\n \"description\": \"The manifest requires version 'latest' of jsmn. it was never fetched, fetch it with 'dfetch update jsmn'. The latest version available is 'master - 25647e692c7906b96ffd2b05ca54c097948e879c'\",\r\n \"lineStart\": 14,\r\n \"lineEnd\": 14,\r\n \"columnStart\": 11,\r\n \"columnEnd\": 14\r\n }\r\n ]\r\n}"] +[17.079861, "o", "$ "] +[18.083186, "o", "\u001b"] +[18.263547, "o", "[1"] +[18.353676, "o", "mc"] +[18.443834, "o", "at"] +[18.533953, "o", " s"] +[18.624079, "o", "ar"] +[18.714154, "o", "if"] +[18.804326, "o", ".j"] +[18.89446, "o", "so"] +[18.986321, "o", "n\u001b"] +[19.166575, "o", "["] +[19.256707, "o", "0m"] +[20.257455, "o", "\r\n"] +[20.260628, "o", "{\r\n \"runs\": [\r\n {\r\n \"tool\": {\r\n \"driver\": {\r\n \"name\": \"DFetch\",\r\n \"informationUri\": \"https://dfetch.rtfd.io\",\r\n \"rules\": [\r\n {\r\n \"id\": \"unfetched-project\",\r\n \"help\": {\r\n \"text\": \"The project mentioned in the manifest was never fetched, fetch it with 'dfetch update '. After fetching, commit the updated project to your repository.\"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Project was never fetched\"\r\n }\r\n },\r\n {\r\n \"id\": \"up-to-date-project\",\r\n \"help\": {\r\n \"text\": \"The project mentioned in the manifest is up-to-date, everything is ok, nothing to do.\""] +[20.260771, "o", "\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Project is up-to-date\"\r\n }\r\n },\r\n {\r\n \"id\": \"unavailable-project-version\",\r\n \"help\": {\r\n \"text\": \"The project mentioned in the manifest is pinned to a specific version, For instance a branch, tag, or revision. However the specific version is not available at the upstream of the project. Check if the remote has the given version. \"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Requested project version is unavailable at the remote\"\r\n }\r\n },\r\n {\r\n \"id\": \"pinned-but-out-of-date-project\",\r\n \"help\": {\r\n \"text\": \"The project mentioned in the manifest is pinned to a specific version, For instance a branch, tag, or revision. This is currently the state of the project. However a newer version is available at the upstream of the project. Either ignore this warning or update the version to the latest and update using 'dfetch update ' and commit the result to your repository.\"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Project is pinned, but out-of-date\"\r\n }\r\n },\r\n {\r\n \"id\": \"out-of-date-project\",\r\n \"help\": {\r\n \"text\": \"The project is configured to always follow the latest version, There is a newer version available at the upstream of the project. Please update the project using 'dfetch update ' and commit the result to your repository.\"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Project is out-of-date\"\r\n }\r\n },\r\n {\r\n \"id\": \"local-changes-in-project\",\r\n \"help\": {\r\n \"text\": \"The files of this project are different then when they were added, Please create a patch using 'dfetch diff ' and add it to the manifest using the 'patch:' attribute. Or better yet, upstream the changes and update your project. When running 'dfetch check' on a platform with different line endings, then this warning is likely a false positive.\"\r\n },\r\n \"shortDescription\": {\r\n \"text\": \"Project was locally changed\"\r\n }\r\n }\r\n ]\r\n }\r\n },\r\n \"artifacts\": [\r\n {\r\n \"location\": {\r\n \"uri\": \"dfetch.yaml\"\r\n },\r\n \"sourceLanguage\": \"yaml\"\r\n }\r\n ],\r\n \"results\": [\r\n {\r\n \"message\": {\r\n \"text\": \"cpputest : cpputest was never fetched!\"\r\n },\r\n \"level\": \"error\",\r\n \"locations\": [\r\n {\r\n \"physicalLocation\": {\r\n \"artifactLocation\": {\r\n \"index\": 0,\r\n \"uri\": \"dfetch.yaml\"\r\n },\r\n \"region\": {\r\n \"endColumn\": 19,\r\n \"endLine\": 9,\r\n \"startColumn\": 11,\r\n \"startLine\": 9\r\n }\r\n "] +[20.260797, "o", " }\r\n }\r\n ],\r\n \"ruleId\": \"unfetched-project\"\r\n },\r\n {\r\n \"message\": {\r\n \"text\": \"jsmn : jsmn was never fetched!\"\r\n },\r\n \"level\": \"error\",\r\n \"locations\": [\r\n {\r\n \"physicalLocation\": {\r\n \"artifactLocation\": {\r\n \"index\": 0,\r\n \"uri\": \"dfetch.yaml\"\r\n },\r\n \"region\": {\r\n \"endColumn\": 15,\r\n \"endLine\": 14,\r\n \"startColumn\": 11,\r\n \"startLine\": 14\r\n }\r\n }\r\n }\r\n ],\r\n \"ruleId\": \"unfetched-project\"\r\n }\r\n ]\r\n }\r\n ],\r\n \"version\": \"2.1.0\"\r\n}"] +[23.269029, "o", "$ "] +[23.270962, "o", "\u001b["] +[23.451259, "o", "1m"] +[23.541401, "o", "\u001b["] +[23.631528, "o", "0m"] +[23.632112, "o", "\r\n"] +[23.634972, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/check.cast b/doc/asciicasts/check.cast index 080a4168..6192839e 100644 --- a/doc/asciicasts/check.cast +++ b/doc/asciicasts/check.cast @@ -1,59 +1,54 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247111, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.66465, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.668498, "o", "$ "] -[1.671618, "o", "\u001b"] -[1.852983, "o", "[1"] -[1.94317, "o", "mc"] -[2.034404, "o", "at"] -[2.124541, "o", " "] -[2.214675, "o", "df"] -[2.30482, "o", "et"] -[2.394943, "o", "ch"] -[2.485085, "o", ".y"] -[2.575257, "o", "a"] -[2.755682, "o", "ml"] -[2.845712, "o", "\u001b["] -[2.935834, "o", "0m"] -[3.937415, "o", "\r\n"] -[3.940467, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] -[3.945503, "o", "$ "] -[4.948613, "o", "\u001b"] -[5.129105, "o", "[1"] -[5.219231, "o", "md"] -[5.309371, "o", "fe"] -[5.399529, "o", "t"] -[5.49124, "o", "ch"] -[5.581308, "o", " c"] -[5.671443, "o", "he"] -[5.761584, "o", "ck"] -[5.851713, "o", "\u001b"] -[6.031978, "o", "[0"] -[6.122457, "o", "m"] -[7.12417, "o", "\r\n"] -[7.617044, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[7.630225, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] -[7.63045, "o", "\u001b[?25l"] -[7.715034, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[7.795646, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[7.87624, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[7.914558, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[7.915386, "o", " \u001b[1;34m> wanted (v3.4), available (v4.0)\u001b[0m\r\n"] -[7.916246, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] -[7.91638, "o", "\u001b[?25l"] -[7.99715, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[8.077644, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[8.158245, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[8.238823, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[8.319411, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[8.400061, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[8.460435, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Checking\u001b[0m"] -[8.461023, "o", "\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[8.462981, "o", " \u001b[1;34m> available (master - 25647e692c7906b96ffd2b05ca54c097948e879c)\u001b[0m\r\n"] -[11.528152, "o", "$ "] -[11.53011, "o", "\u001b"] -[11.710398, "o", "[1"] -[11.800546, "o", "m\u001b"] -[11.890677, "o", "[0"] -[11.980814, "o", "m"] -[11.981405, "o", "\r\n"] -[11.984299, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774797, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.530718, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.534834, "o", "$ "] +[1.538122, "o", "\u001b"] +[1.718464, "o", "[1"] +[1.808597, "o", "mc"] +[1.898749, "o", "at"] +[1.988967, "o", " "] +[2.078985, "o", "df"] +[2.169132, "o", "et"] +[2.25925, "o", "ch"] +[2.349401, "o", ".y"] +[2.439543, "o", "a"] +[2.619999, "o", "ml"] +[2.710156, "o", "\u001b["] +[2.800272, "o", "0m"] +[3.801824, "o", "\r\n"] +[3.804829, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] +[3.810776, "o", "$ "] +[4.813555, "o", "\u001b["] +[4.994135, "o", "1m"] +[5.084264, "o", "df"] +[5.174397, "o", "et"] +[5.264531, "o", "ch"] +[5.354661, "o", " c"] +[5.444795, "o", "he"] +[5.534956, "o", "ck"] +[5.62509, "o", "\u001b["] +[5.715221, "o", "0m"] +[6.716986, "o", "\r\n"] +[7.225892, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[7.239534, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] +[7.239665, "o", "\u001b[?25l"] +[7.320732, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[7.401339, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[7.481919, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[7.530894, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] +[7.531676, "o", " \u001b[1;34m> wanted (v3.4), available (v4.0)\u001b[0m\r\n"] +[7.532508, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] +[7.532632, "o", "\u001b[?25l"] +[7.613403, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[7.693972, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[7.774582, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[7.855307, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[7.935848, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[8.016365, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Checking\u001b[0m"] +[8.089455, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Checking\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K \u001b[1;34m> available (master - 25647e692c7906b96ffd2b05ca54c097948e879c)\u001b[0m\r\n"] +[11.204395, "o", "$ "] +[11.206315, "o", "\u001b["] +[11.386594, "o", "1m"] +[11.476728, "o", "\u001b["] +[11.566843, "o", "0m"] +[11.567355, "o", "\r\n"] +[11.570086, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/diff.cast b/doc/asciicasts/diff.cast index c70b2f49..3cd0c8d2 100644 --- a/doc/asciicasts/diff.cast +++ b/doc/asciicasts/diff.cast @@ -1,107 +1,113 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247207, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.297282, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.308402, "o", "$ "] -[1.310834, "o", "\u001b["] -[1.491147, "o", "1m"] -[1.581282, "o", "ls"] -[1.671427, "o", " -"] -[1.761551, "o", "l ."] -[1.851721, "o", "\u001b["] -[1.941822, "o", "0m"] -[2.943437, "o", "\r\n"] -[2.947021, "o", "total 12\r\n"] -[2.947132, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 23 06:26 cpputest\r\n-rw-rw-rw- 1 dev dev 733 Mar 23 06:26 dfetch.yaml\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 23 06:26 jsmn\r\n"] -[2.952751, "o", "$ "] -[3.956151, "o", "\u001b["] -[4.136429, "o", "1m"] -[4.226576, "o", "ls"] -[4.316879, "o", " -"] -[4.406952, "o", "l c"] -[4.497095, "o", "pp"] -[4.587199, "o", "ut"] -[4.677345, "o", "es"] -[4.767572, "o", "t/"] -[4.8577, "o", "src"] -[5.037956, "o", "/R"] -[5.128135, "o", "EA"] -[5.21826, "o", "DM"] -[5.308386, "o", "E."] -[5.398532, "o", "md\u001b"] -[5.489477, "o", "[0"] -[5.58001, "o", "m"] -[6.581699, "o", "\r\n"] -[6.585138, "o", "-rw-rw-rw- 1 dev dev 6777 Mar 23 06:26 cpputest/src/README.md\r\n"] -[6.590292, "o", "$ "] -[7.593436, "o", "\u001b"] -[7.773712, "o", "[1"] -[7.865028, "o", "ms"] -[7.955031, "o", "ed"] -[8.045161, "o", " "] -[8.135294, "o", "-i"] -[8.225425, "o", " '"] -[8.315576, "o", "s/"] -[8.405723, "o", "gi"] -[8.495997, "o", "t"] -[8.676235, "o", "hu"] -[8.766378, "o", "b/"] -[8.856517, "o", "gi"] -[8.946662, "o", "tl"] -[9.036777, "o", "a"] -[9.126914, "o", "b/"] -[9.217045, "o", "g'"] -[9.30718, "o", " c"] -[9.397311, "o", "pp"] -[9.577594, "o", "u"] -[9.667717, "o", "te"] -[9.757857, "o", "st"] -[9.847993, "o", "/s"] -[9.938322, "o", "rc"] -[10.028282, "o", "/"] -[10.118418, "o", "RE"] -[10.208543, "o", "AD"] -[10.298974, "o", "ME"] -[10.480288, "o", ".m"] -[10.570256, "o", "d"] -[10.660377, "o", "\u001b["] -[10.750514, "o", "0m"] -[11.752172, "o", "\r\n"] -[11.760729, "o", "$ "] -[12.76387, "o", "\u001b["] -[12.944144, "o", "1m"] -[13.034268, "o", "df"] -[13.124423, "o", "et"] -[13.214548, "o", "ch "] -[13.304689, "o", "di"] -[13.394753, "o", "ff"] -[13.484885, "o", " c"] -[13.575022, "o", "pp"] -[13.665153, "o", "ute"] -[13.845418, "o", "st"] -[13.935577, "o", "\u001b["] -[14.02676, "o", "0m"] -[15.028362, "o", "\r\n"] -[15.511793, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[15.576794, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n \u001b[1;34m> Generating patch cpputest.patch since 33706eb2aaa0bf0e6c462056aaf53a95415980e9 in /workspaces/dfetch/doc/generate-casts/diff\u001b[0m\r\n"] -[15.668988, "o", "$ "] -[16.672084, "o", "\u001b["] -[16.852563, "o", "1m"] -[16.942614, "o", "ca"] -[17.03276, "o", "t "] -[17.122906, "o", "cp"] -[17.213025, "o", "pu"] -[17.303157, "o", "te"] -[17.393376, "o", "st"] -[17.483512, "o", ".p"] -[17.57364, "o", "at"] -[17.753902, "o", "ch\u001b"] -[17.844042, "o", "[0"] -[17.934163, "o", "m"] -[18.93576, "o", "\r\n"] -[18.938914, "o", "diff --git a/README.md b/README.md\r\nindex 2655a7b..fc6084e 100644\r\n--- a/README.md\r\n+++ b/README.md\r\n@@ -3,7 +3,7 @@ CppUTest\r\n \r\n CppUTest unit testing and mocking framework for C/C++\r\n \r\n-[More information on the project page](http://cpputest.github.com)\r\n+[More information on the project page](http://cpputest.gitlab.com)\r\n \r\n [![Build Status](https://travis-ci.org/cpputest/cpputest.png?branch=master)](https://travis-ci.org/cpputest/cpputest)\r\n \r\n"] -[21.94809, "o", "$ "] -[21.949812, "o", "\u001b["] -[22.13027, "o", "1m"] -[22.220398, "o", "\u001b["] -[22.310585, "o", "0m"] -[22.311207, "o", "\r\n"] -[22.313952, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774895, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.257193, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.260643, "o", "$ "] +[1.263806, "o", "\u001b["] +[1.44411, "o", "1m"] +[1.534151, "o", "ls"] +[1.624299, "o", " -"] +[1.714416, "o", "l ."] +[1.804564, "o", "\u001b["] +[1.894672, "o", "0m"] +[2.896237, "o", "\r\n"] +[2.899708, "o", "total 12\r\n"] +[2.899763, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 29 09:01 cpputest\r\n-rw-rw-rw- 1 dev dev 733 Mar 29 09:01 dfetch.yaml\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 29 09:01 jsmn\r\n"] +[2.90486, "o", "$ "] +[3.908003, "o", "\u001b["] +[4.089189, "o", "1m"] +[4.179336, "o", "ls"] +[4.269462, "o", " -"] +[4.359673, "o", "l c"] +[4.449802, "o", "pp"] +[4.539936, "o", "ut"] +[4.63007, "o", "es"] +[4.720209, "o", "t/"] +[4.810389, "o", "src"] +[4.990638, "o", "/R"] +[5.083509, "o", "EA"] +[5.173417, "o", "DM"] +[5.263543, "o", "E."] +[5.35374, "o", "md\u001b"] +[5.443862, "o", "[0"] +[5.533977, "o", "m"] +[6.535505, "o", "\r\n"] +[6.5392, "o", "-rw-rw-rw- 1 dev dev 6777 Mar 29 09:01 cpputest/src/README.md"] +[6.539351, "o", "\r\n"] +[6.546616, "o", "$ "] +[7.549874, "o", "\u001b["] +[7.730148, "o", "1m"] +[7.820286, "o", "se"] +[7.910407, "o", "d "] +[8.000547, "o", "-i"] +[8.090682, "o", " '"] +[8.180931, "o", "s/"] +[8.27104, "o", "gi"] +[8.361195, "o", "th"] +[8.451308, "o", "ub"] +[8.631653, "o", "/gi"] +[8.72189, "o", "tl"] +[8.812015, "o", "ab"] +[8.902152, "o", "/g"] +[8.992387, "o", "' "] +[9.08343, "o", "cp"] +[9.176235, "o", "pu"] +[9.265397, "o", "te"] +[9.357204, "o", "st"] +[9.535785, "o", "/s"] +[9.625871, "o", "rc/"] +[9.716005, "o", "RE"] +[9.806145, "o", "AD"] +[9.896265, "o", "ME"] +[9.986399, "o", ".m"] +[10.076536, "o", "d\u001b"] +[10.166669, "o", "[0"] +[10.256792, "o", "m"] +[11.258358, "o", "\r\n"] +[11.267334, "o", "$ "] +[12.270475, "o", "\u001b["] +[12.450732, "o", "1m"] +[12.540883, "o", "df"] +[12.631016, "o", "et"] +[12.721141, "o", "ch"] +[12.811269, "o", " d"] +[12.9014, "o", "if"] +[12.991531, "o", "f "] +[13.081664, "o", "cp"] +[13.171794, "o", "pu"] +[13.352027, "o", "te"] +[13.442169, "o", "st"] +[13.53229, "o", "\u001b["] +[13.62243, "o", "0m"] +[14.623888, "o", "\r\n"] +[15.102582, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[15.148186, "o", " \u001b[1;92mcpputest:\u001b[0m"] +[15.148232, "o", "\r\n"] +[15.148893, "o", " \u001b[1;34m> Generating patch cpputest.patch since 28f7406a2a43ed683c0f8722ba6e410d97c6aeb8 in \u001b[0m "] +[15.148978, "o", "\r\n"] +[15.149018, "o", "\u001b[1;34m/workspaces/dfetch/doc/generate-casts/diff\u001b[0m "] +[15.149055, "o", "\r\n"] +[15.214045, "o", "$ "] +[16.217264, "o", "\u001b"] +[16.397535, "o", "[1"] +[16.487677, "o", "mc"] +[16.577839, "o", "at"] +[16.668019, "o", " "] +[16.758132, "o", "cp"] +[16.848266, "o", "pu"] +[16.938389, "o", "te"] +[17.028529, "o", "st"] +[17.118652, "o", "."] +[17.298906, "o", "pa"] +[17.389077, "o", "tc"] +[17.479212, "o", "h\u001b"] +[17.569335, "o", "[0"] +[17.659468, "o", "m"] +[18.661017, "o", "\r\n"] +[18.664368, "o", "diff --git a/README.md b/README.md\r\nindex 2655a7b..fc6084e 100644\r\n--- a/README.md\r\n+++ b/README.md\r\n@@ -3,7 +3,7 @@ CppUTest\r\n \r\n CppUTest unit testing and mocking framework for C/C++\r\n \r\n-[More information on the project page](http://cpputest.github.com)\r\n+[More information on the project page](http://cpputest.gitlab.com)\r\n \r\n [![Build Status](https://travis-ci.org/cpputest/cpputest.png?branch=master)](https://travis-ci.org/cpputest/cpputest)\r\n \r\n"] +[21.673544, "o", "$ "] +[21.675476, "o", "\u001b"] +[21.855753, "o", "[1"] +[21.945884, "o", "m\u001b"] +[22.035963, "o", "[0"] +[22.126093, "o", "m"] +[22.12661, "o", "\r\n"] +[22.129622, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/environment.cast b/doc/asciicasts/environment.cast index 5a01fbb4..33babf8c 100644 --- a/doc/asciicasts/environment.cast +++ b/doc/asciicasts/environment.cast @@ -1,27 +1,28 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247096, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.020181, "o", "$ "] -[1.02333, "o", "\u001b["] -[1.203634, "o", "1m"] -[1.293774, "o", "df"] -[1.383891, "o", "et"] -[1.474082, "o", "ch"] -[1.564168, "o", " e"] -[1.654306, "o", "nv"] -[1.744455, "o", "ir"] -[1.834587, "o", "on"] -[1.924727, "o", "me"] -[2.104972, "o", "nt"] -[2.19512, "o", "\u001b["] -[2.285232, "o", "0m"] -[3.286818, "o", "\r\n"] -[3.78698, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[3.78782, "o", " \u001b[1;92mplatform :\u001b[0m\u001b[1;34m Linux 6.8.0-1044-azure\u001b[0m\r\n"] -[3.790882, "o", " \u001b[1;92mgit :\u001b[0m\u001b[1;34m 2.52.0\u001b[0m\r\n"] -[3.80384, "o", " \u001b[1;92msvn :\u001b[0m\u001b[1;34m 1.14.5 (r1922182)\u001b[0m\r\n"] -[6.867225, "o", "$ "] -[6.869712, "o", "\u001b"] -[7.050015, "o", "[1"] -[7.140245, "o", "m\u001b"] -[7.230362, "o", "[0"] -[7.32048, "o", "m"] -[7.321059, "o", "\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774782, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.019582, "o", "$ "] +[1.022715, "o", "\u001b"] +[1.20297, "o", "[1"] +[1.293106, "o", "md"] +[1.383229, "o", "fe"] +[1.473387, "o", "t"] +[1.563508, "o", "ch"] +[1.653703, "o", " e"] +[1.743844, "o", "nv"] +[1.833997, "o", "ir"] +[1.924029, "o", "o"] +[2.104268, "o", "nm"] +[2.19442, "o", "en"] +[2.284526, "o", "t\u001b"] +[2.374675, "o", "[0"] +[2.464798, "o", "m"] +[3.466426, "o", "\r\n"] +[3.918173, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[3.918952, "o", " \u001b[1;92mplatform :\u001b[0m\u001b[1;34m Linux 6.8.0-1044-azure\u001b[0m\r\n"] +[3.921669, "o", " \u001b[1;92mgit :\u001b[0m\u001b[1;34m 2.52.0\u001b[0m\r\n"] +[3.935754, "o", " \u001b[1;92msvn :\u001b[0m\u001b[1;34m 1.14.5 (r1922182)\u001b[0m\r\n"] +[6.998085, "o", "$ "] +[6.999866, "o", "\u001b["] +[7.180177, "o", "1m"] +[7.27035, "o", "\u001b["] +[7.360493, "o", "0m"] +[7.361019, "o", "\r\n"] diff --git a/doc/asciicasts/format-patch.cast b/doc/asciicasts/format-patch.cast index 41658241..083a181a 100644 --- a/doc/asciicasts/format-patch.cast +++ b/doc/asciicasts/format-patch.cast @@ -1,127 +1,121 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247273, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.827732, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.83158, "o", "$ "] -[1.834752, "o", "\u001b["] -[2.015073, "o", "1m"] -[2.105205, "o", "ls"] -[2.195353, "o", " -"] -[2.285519, "o", "l ."] -[2.375663, "o", "\u001b["] -[2.465787, "o", "0m"] -[3.467311, "o", "\r\n"] -[3.470858, "o", "total 16\r\n"] -[3.470913, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 23 06:27 cpputest\r\n-rw-rw-rw- 1 dev dev 229 Mar 23 06:27 dfetch.yaml\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 23 06:27 jsmn\r\ndrwxrwxrwx+ 2 dev dev 4096 Mar 23 06:27 patches\r\n"] -[3.476076, "o", "$ "] -[4.479354, "o", "\u001b"] -[4.659756, "o", "[1"] -[4.749897, "o", "mc"] -[4.84002, "o", "at"] -[4.930171, "o", " "] -[5.020309, "o", "df"] -[5.110433, "o", "et"] -[5.200573, "o", "ch"] -[5.290709, "o", ".y"] -[5.380834, "o", "a"] -[5.561089, "o", "ml"] -[5.651373, "o", "\u001b["] -[5.741416, "o", "0m"] -[6.743112, "o", "\r\n"] -[6.746222, "o", "manifest:\r\n version: 0.0\r\n\r\n remotes:\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/\r\n repo-path: cpputest/cpputest.git\r\n tag: v3.4\r\n patch: patches/cpputest.patch\r\n\r\n"] -[6.751435, "o", "$ "] -[7.75466, "o", "\u001b["] -[7.934917, "o", "1m"] -[8.025052, "o", "ca"] -[8.115183, "o", "t "] -[8.205324, "o", "pa"] -[8.295449, "o", "tc"] -[8.385573, "o", "he"] -[8.476443, "o", "s/"] -[8.565912, "o", "cp"] -[8.65606, "o", "pu"] -[8.836316, "o", "te"] -[8.926555, "o", "st"] -[9.01669, "o", ".p"] -[9.106812, "o", "at"] -[9.196953, "o", "ch"] -[9.28711, "o", "\u001b["] -[9.377229, "o", "0m"] -[10.378877, "o", "\r\n"] -[10.382104, "o", "diff --git a/README.md b/README.md\r\nindex 2655a7b..fc6084e 100644\r\n--- a/README.md\r\n+++ b/README.md\r\n@@ -3,7 +3,7 @@ CppUTest\r\n \r\n CppUTest unit testing and mocking framework for C/C++\r\n \r\n-[More information on the project page](http://cpputest.github.com)\r\n+[More information on the project page](http://cpputest.gitlab.com)\r\n \r\n [![Build Status](https://travis-ci.org/cpputest/cpputest.png?branch=master)](https://travis-ci.org/cpputest/cpputest)\r\n \r\n"] -[10.387422, "o", "$ "] -[11.390553, "o", "\u001b"] -[11.570885, "o", "[1"] -[11.660973, "o", "md"] -[11.751107, "o", "fe"] -[11.841249, "o", "t"] -[11.931433, "o", "ch"] -[12.021564, "o", " f"] -[12.111694, "o", "or"] -[12.201934, "o", "ma"] -[12.292019, "o", "t"] -[12.472292, "o", "-p"] -[12.562416, "o", "at"] -[12.652558, "o", "ch"] -[12.74269, "o", " c"] -[12.832827, "o", "p"] -[12.922991, "o", "pu"] -[13.013121, "o", "te"] -[13.103282, "o", "st"] -[13.193412, "o", " -"] -[13.374738, "o", "-"] -[13.463884, "o", "ou"] -[13.554013, "o", "tp"] -[13.644135, "o", "ut"] -[13.734325, "o", "-d"] -[13.824434, "o", "i"] -[13.91459, "o", "re"] -[14.004738, "o", "ct"] -[14.094907, "o", "or"] -[14.275152, "o", "y "] -[14.365291, "o", "f"] -[14.455446, "o", "or"] -[14.545571, "o", "ma"] -[14.635706, "o", "tt"] -[14.725876, "o", "ed"] -[14.815991, "o", "-"] -[14.906125, "o", "pa"] -[14.996258, "o", "tc"] -[15.176523, "o", "he"] -[15.266679, "o", "s\u001b"] -[15.356801, "o", "["] -[15.446931, "o", "0m"] -[16.448596, "o", "\r\n"] -[16.90849, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[16.932549, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] -[16.933138, "o", " \u001b[1;34m> formatted patch written to formatted-patches/cpputest.patch\u001b[0m\r\n"] -[16.999031, "o", "$ "] -[18.001567, "o", "\u001b["] -[18.181864, "o", "1m"] -[18.271995, "o", "ca"] -[18.362127, "o", "t "] -[18.453764, "o", "fo"] -[18.54385, "o", "rm"] -[18.633955, "o", "at"] -[18.724109, "o", "te"] -[18.814244, "o", "d-"] -[18.904369, "o", "pa"] -[19.084689, "o", "tc"] -[19.1748, "o", "he"] -[19.26493, "o", "s/"] -[19.355119, "o", "cp"] -[19.445229, "o", "pu"] -[19.535371, "o", "te"] -[19.625491, "o", "st"] -[19.715644, "o", ".p"] -[19.8072, "o", "at"] -[19.986226, "o", "ch"] -[20.076346, "o", "\u001b["] -[20.166491, "o", "0m"] -[21.168195, "o", "\r\n"] -[21.171408, "o", "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\r\nFrom: John Doe \r\nDate: Mon, 23 Mar 2026 06:28:10 +0000\r\nSubject: [PATCH] Patch for cpputest\r\n\r\nPatch for cpputest\r\n\r\ndiff --git a/README.md b/README.md\r\nindex 2655a7b..fc6084e 100644\r\n--- a/README.md\r\n+++ b/README.md\r\n@@ -3,7 +3,7 @@\r\n \r\n CppUTest unit testing and mocking framework for C/C++\r\n \r\n-[More information on the project page](http://cpputest.github.com)\r\n+[More information on the project page](http://cpputest.gitlab.com)\r\n \r\n [![Build Status](https://travis-ci.org/cpputest/cpputest.png?branch=master)](https://travis-ci.org/cpputest/cpputest)\r\n \r\n"] -[24.178781, "o", "$ "] -[24.180615, "o", "\u001b["] -[24.360898, "o", "1m"] -[24.451026, "o", "\u001b["] -[24.541173, "o", "0m"] -[24.541658, "o", "\r\n"] -[24.54463, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774961, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.876993, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.880416, "o", "$ "] +[1.88357, "o", "\u001b["] +[2.063972, "o", "1m"] +[2.154052, "o", "ls"] +[2.244202, "o", " -"] +[2.33429, "o", "l "] +[2.424424, "o", ".\u001b"] +[2.514585, "o", "[0"] +[2.604698, "o", "m"] +[3.606471, "o", "\r\n"] +[3.60985, "o", "total 16\r\n"] +[3.609891, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 29 09:02 cpputest"] +[3.609918, "o", "\r\n-rw-rw-rw- 1 dev dev 229 Mar 29 09:02 dfetch.yaml\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 29 09:02 jsmn\r\ndrwxrwxrwx+ 2 dev dev 4096 Mar 29 09:02 patches\r\n"] +[3.615315, "o", "$ "] +[4.618348, "o", "\u001b["] +[4.798581, "o", "1m"] +[4.888735, "o", "ca"] +[4.978852, "o", "t "] +[5.06893, "o", "df"] +[5.159091, "o", "et"] +[5.249288, "o", "ch"] +[5.339378, "o", ".y"] +[5.429509, "o", "am"] +[5.519644, "o", "l\u001b"] +[5.699967, "o", "[0"] +[5.790096, "o", "m"] +[6.791355, "o", "\r\n"] +[6.794334, "o", "manifest:\r\n version: 0.0\r\n\r\n remotes:\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/\r\n repo-path: cpputest/cpputest.git\r\n tag: v3.4\r\n patch: patches/cpputest.patch\r\n\r\n"] +[6.799776, "o", "$ "] +[7.802827, "o", "\u001b["] +[7.98307, "o", "1m"] +[8.073224, "o", "ca"] +[8.163347, "o", "t "] +[8.253476, "o", "pat"] +[8.343586, "o", "ch"] +[8.433737, "o", "es"] +[8.523861, "o", "/c"] +[8.61399, "o", "pp"] +[8.704187, "o", "ute"] +[8.884448, "o", "st"] +[8.974601, "o", ".p"] +[9.064711, "o", "at"] +[9.154856, "o", "ch"] +[9.24499, "o", "\u001b[0"] +[9.335113, "o", "m"] +[10.336318, "o", "\r\n"] +[10.339282, "o", "diff --git a/README.md b/README.md\r\nindex 2655a7b..fc6084e 100644\r\n--- a/README.md\r\n+++ b/README.md\r\n@@ -3,7 +3,7 @@ CppUTest\r\n \r\n CppUTest unit testing and mocking framework for C/C++\r\n \r\n-[More information on the project page](http://cpputest.github.com)\r\n+[More information on the project page](http://cpputest.gitlab.com)\r\n"] +[10.339328, "o", " \r\n [![Build Status](https://travis-ci.org/cpputest/cpputest.png?branch=master)](https://travis-ci.org/cpputest/cpputest)\r\n \r\n"] +[10.344664, "o", "$ "] +[11.347695, "o", "\u001b["] +[11.534291, "o", "1m"] +[11.624273, "o", "df"] +[11.716283, "o", "et"] +[11.804451, "o", "ch"] +[11.894613, "o", " f"] +[11.98474, "o", "or"] +[12.074868, "o", "ma"] +[12.164989, "o", "t-"] +[12.255112, "o", "pa"] +[12.435499, "o", "tch"] +[12.525597, "o", " c"] +[12.615726, "o", "pp"] +[12.705871, "o", "ut"] +[12.795991, "o", "es"] +[12.888177, "o", "t "] +[12.97781, "o", "--"] +[13.068128, "o", "ou"] +[13.15849, "o", "tp"] +[13.248464, "o", "ut"] +[13.428783, "o", "-di"] +[13.518906, "o", "re"] +[13.609042, "o", "ct"] +[13.699163, "o", "or"] +[13.789923, "o", "y "] +[13.879436, "o", "fo"] +[13.969652, "o", "rm"] +[14.05977, "o", "at"] +[14.149918, "o", "te"] +[14.330177, "o", "d-"] +[14.42032, "o", "pat"] +[14.510443, "o", "ch"] +[14.600587, "o", "es"] +[14.690707, "o", "\u001b["] +[14.780889, "o", "0m"] +[15.782507, "o", "\r\n"] +[16.225638, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[16.25017, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] +[16.250779, "o", " \u001b[1;34m> formatted patch written to formatted-patches/cpputest.patch\u001b[0m\r\n"] +[16.310847, "o", "$ "] +[17.313933, "o", "\u001b["] +[17.494298, "o", "1m"] +[17.584503, "o", "ca"] +[17.674629, "o", "t "] +[17.764757, "o", "for"] +[17.854891, "o", "ma"] +[17.945027, "o", "tt"] +[18.035263, "o", "ed"] +[18.125368, "o", "-p"] +[18.215505, "o", "atc"] +[18.395887, "o", "he"] +[18.486243, "o", "s/"] +[18.576335, "o", "cp"] +[18.666461, "o", "pu"] +[18.756604, "o", "tes"] +[18.846717, "o", "t."] +[18.936854, "o", "pa"] +[19.026997, "o", "tc"] +[19.117144, "o", "h\u001b"] +[19.297386, "o", "[0m"] +[20.298934, "o", "\r\n"] +[20.301697, "o", "From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001\r\nFrom: John Doe \r\nDate: Sun, 29 Mar 2026 09:02:57 +0000\r\nSubject: [PATCH] Patch for cpputest\r\n\r\nPatch for cpputest\r\n\r\ndiff --git a/README.md b/README.md\r\nindex 2655a7b..fc6084e 100644\r\n--- a/README.md\r\n+++ b/README.md\r\n@@ -3,7 +3,7 @@\r\n \r\n CppUTest unit testing and mocking framework for C/C++\r\n \r\n-[More information on the project page](http://cpputest.github.com)\r\n+[More information on the project page](http://cpputest.gitlab.com)\r\n \r\n [![Build Status](https://travis-ci.org/cpputest/cpputest.png?branch=master)](https://travis-ci.org/cpputest/cpputest)\r\n \r\n"] +[23.309066, "o", "$ "] +[23.310979, "o", "\u001b"] +[23.491343, "o", "[1"] +[23.581693, "o", "m\u001b"] +[23.67181, "o", "[0"] +[23.761939, "o", "m"] +[23.762527, "o", "\r\n"] +[23.765631, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/freeze.cast b/doc/asciicasts/freeze.cast index affbd135..c6ad5bdf 100644 --- a/doc/asciicasts/freeze.cast +++ b/doc/asciicasts/freeze.cast @@ -1,69 +1,69 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247191, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.038216, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.043613, "o", "$ "] -[1.046885, "o", "\u001b"] -[1.227163, "o", "[1"] -[1.317301, "o", "mc"] -[1.40879, "o", "at"] -[1.497739, "o", " d"] -[1.587917, "o", "fe"] -[1.679002, "o", "tc"] -[1.76822, "o", "h."] -[1.858797, "o", "ya"] -[1.948954, "o", "ml"] -[2.129216, "o", "\u001b"] -[2.220018, "o", "[0"] -[2.310154, "o", "m"] -[3.311743, "o", "\r\n"] -[3.314993, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] -[3.320107, "o", "$ "] -[4.323283, "o", "\u001b["] -[4.503544, "o", "1m"] -[4.593725, "o", "df"] -[4.683858, "o", "et"] -[4.774025, "o", "ch"] -[4.864152, "o", " f"] -[4.954268, "o", "re"] -[5.044445, "o", "ez"] -[5.134637, "o", "e\u001b"] -[5.224693, "o", "[0"] -[5.404934, "o", "m"] -[6.406123, "o", "\r\n"] -[7.0302, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[7.051233, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] -[7.051844, "o", " \u001b[1;34m> Already pinned in manifest on version v3.4\u001b[0m\r\n"] -[7.054891, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] -[7.055456, "o", " \u001b[1;34m> Frozen on version 25647e692c7906b96ffd2b05ca54c097948e879c\u001b[0m\r\n"] -[7.058041, "o", "Updated manifest (dfetch.yaml) in /workspaces/dfetch/doc/generate-casts/freeze\r\n"] -[7.126346, "o", "$ "] -[8.129351, "o", "\u001b["] -[8.309624, "o", "1m"] -[8.399968, "o", "ca"] -[8.49038, "o", "t "] -[8.580221, "o", "df"] -[8.670353, "o", "et"] -[8.760504, "o", "ch"] -[8.850634, "o", ".y"] -[8.941702, "o", "am"] -[9.031651, "o", "l\u001b"] -[9.211915, "o", "[0m"] -[10.213679, "o", "\r\n"] -[10.216842, "o", "manifest:\r\n version: '0.0'\r\n\r\n remotes:\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/\r\n tag: v3.4\r\n repo-path: cpputest/cpputest.git\r\n\r\n - name: jsmn\r\n revision: 25647e692c7906b96ffd2b05ca54c097948e879c\r\n branch: master\r\n repo-path: zserge/jsmn.git\r\n"] -[10.22202, "o", "$ "] -[11.225568, "o", "\u001b["] -[11.405891, "o", "1m"] -[11.496048, "o", "ls"] -[11.586185, "o", " -"] -[11.676329, "o", "l ."] -[11.768024, "o", "\u001b["] -[11.857137, "o", "0m"] -[12.858752, "o", "\r\n"] -[12.863027, "o", "total 16\r\ndrwxr-xr-x+ 3 dev dev 4096 Mar 23 06:26 cpputest\r\n-rw-rw-rw- 1 dev dev 317 Mar 23 06:26 dfetch.yaml\r\n-rw-rw-rw- 1 dev dev 733 Mar 23 06:26 dfetch.yaml.backup\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 23 06:26 jsmn\r\n"] -[15.871264, "o", "$ "] -[15.873193, "o", "\u001b["] -[16.053489, "o", "1m"] -[16.143622, "o", "\u001b["] -[16.234505, "o", "0m"] -[16.235402, "o", "\r\n"] -[16.241797, "o", "/workspaces/dfetch/doc/generate-casts"] -[16.242269, "o", "\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774879, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.039774, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.044786, "o", "$ "] +[1.047955, "o", "\u001b["] +[1.228135, "o", "1m"] +[1.318265, "o", "ca"] +[1.408446, "o", "t "] +[1.50053, "o", "df"] +[1.590654, "o", "et"] +[1.680793, "o", "ch"] +[1.770956, "o", ".y"] +[1.861127, "o", "am"] +[1.951317, "o", "l\u001b"] +[2.131699, "o", "[0m"] +[3.133482, "o", "\r\n"] +[3.136605, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] +[3.141478, "o", "$ "] +[4.144533, "o", "\u001b["] +[4.324819, "o", "1m"] +[4.415703, "o", "df"] +[4.505854, "o", "et"] +[4.595985, "o", "ch"] +[4.686122, "o", " f"] +[4.776245, "o", "re"] +[4.866389, "o", "ez"] +[4.958249, "o", "e\u001b"] +[5.047587, "o", "[0"] +[5.22782, "o", "m"] +[6.22938, "o", "\r\n"] +[6.710588, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[6.72748, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] +[6.727996, "o", " \u001b[1;34m> Already pinned in manifest on version v3.4\u001b[0m\r\n"] +[6.730007, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] +[6.730543, "o", " \u001b[1;34m> Frozen on version 25647e692c7906b96ffd2b05ca54c097948e879c\u001b[0m\r\n"] +[6.732089, "o", "Updated manifest (dfetch.yaml) in /workspaces/dfetch/doc/generate-casts/freeze\r\n"] +[6.813142, "o", "$ "] +[7.816185, "o", "\u001b["] +[7.996489, "o", "1m"] +[8.086595, "o", "ca"] +[8.176712, "o", "t "] +[8.266817, "o", "dfe"] +[8.356958, "o", "tc"] +[8.447089, "o", "h."] +[8.537209, "o", "ya"] +[8.62734, "o", "ml"] +[8.717499, "o", "\u001b[0"] +[8.897793, "o", "m"] +[9.899381, "o", "\r\n"] +[9.902405, "o", "manifest:\r\n version: '0.0'\r\n\r\n remotes:\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/\r\n tag: v3.4\r\n repo-path: cpputest/cpputest.git\r\n\r\n - name: jsmn\r\n revision: 25647e692c7906b96ffd2b05ca54c097948e879c\r\n branch: master\r\n repo-path: zserge/jsmn.git\r\n"] +[9.907365, "o", "$ "] +[10.910562, "o", "\u001b"] +[11.09227, "o", "[1"] +[11.182331, "o", "ml"] +[11.272484, "o", "s "] +[11.362597, "o", "-"] +[11.452732, "o", "l "] +[11.542853, "o", ".\u001b"] +[11.632978, "o", "[0"] +[11.723118, "o", "m"] +[12.724685, "o", "\r\n"] +[12.728145, "o", "total 16\r\n"] +[12.72826, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 29 09:01 cpputest\r\n-rw-rw-rw- 1 dev dev 317 Mar 29 09:01 dfetch.yaml\r\n-rw-rw-rw- 1 dev dev 733 Mar 29 09:01 dfetch.yaml.backup\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 29 09:01 jsmn\r\n"] +[15.736845, "o", "$ "] +[15.738752, "o", "\u001b["] +[15.919011, "o", "1m"] +[16.00915, "o", "\u001b["] +[16.099282, "o", "0m"] +[16.099864, "o", "\r\n"] +[16.102552, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/import.cast b/doc/asciicasts/import.cast index 7b35ab5c..324e91b8 100644 --- a/doc/asciicasts/import.cast +++ b/doc/asciicasts/import.cast @@ -1,69 +1,67 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247301, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.021266, "o", "$ "] -[1.024256, "o", "\u001b"] -[1.204551, "o", "[1"] -[1.294674, "o", "ml"] -[1.384794, "o", "s "] -[1.474943, "o", "-"] -[1.565052, "o", "l\u001b"] -[1.655189, "o", "[0"] -[1.745348, "o", "m"] -[2.746928, "o", "\r\n"] -[2.750491, "o", "total 580\r\n"] -[2.750604, "o", "-rw-rw-rw- 1 dev dev 1137 Mar 23 06:23 CMakeLists.txt\r\n-rw-rw-rw- 1 dev dev 35147 Mar 23 06:23 LICENSE\r\n-rw-rw-rw- 1 dev dev 1796 Mar 23 06:23 README.md\r\n-rw-rw-rw- 1 dev dev 1381 Mar 23 06:23 appveyor.yml\r\n-rwxrwxrwx 1 dev dev 229 Mar 23 06:23 create_doc.sh\r\ndrwxrwxrwx+ 2 dev dev 4096 Mar 23 06:23 data\r\ndrwxrwxrwx+ 4 dev dev 4096 Mar 23 06:23 doc\r\ndrwxrwxrwx+ 4 dev dev 4096 Mar 23 06:23 docs\r\ndrwxrwxrwx+ 2 dev dev 4096 Mar 23 06:23 installer\r\ndrwxrwxrwx+ 4 dev dev 4096 Mar 23 06:23 libraries\r\n-rw-rw-rw- 1 dev dev 505101 Mar 23 06:23 modbusscope_demo.gif\r\ndrwxrwxrwx+ 5 dev dev 4096 Mar 23 06:23 resources\r\ndrwxrwxrwx+ 9 dev dev 4096 Mar 23 06:23 src\r\ndrwxrwxrwx+ 9 dev dev 4096 Mar 23 06:23 tests\r\n"] -[2.756067, "o", "$ "] -[3.75877, "o", "\u001b"] -[3.939032, "o", "[1"] -[4.029319, "o", "mc"] -[4.119468, "o", "at"] -[4.209613, "o", " ."] -[4.29975, "o", "gi"] -[4.389894, "o", "tm"] -[4.480026, "o", "od"] -[4.570187, "o", "ul"] -[4.660326, "o", "es"] -[4.840612, "o", "\u001b"] -[4.930754, "o", "[0"] -[5.020869, "o", "m"] -[6.023293, "o", "\r\n"] -[6.033538, "o", "[submodule \"tests/googletest\"]\r\n\tpath = tests/googletest\r\n\turl = https://github.com/google/googletest.git\r\n[submodule \"libraries/muparser\"]\r\n\tpath = libraries/muparser\r\n\turl = https://github.com/beltoforion/muparser.git\r\n"] -[6.034087, "o", "$ "] -[7.03736, "o", "\u001b"] -[7.217943, "o", "[1"] -[7.308089, "o", "md"] -[7.398224, "o", "fe"] -[7.488368, "o", "t"] -[7.5785, "o", "ch"] -[7.668637, "o", " i"] -[7.758753, "o", "mp"] -[7.848901, "o", "or"] -[7.939024, "o", "t"] -[8.119432, "o", "\u001b["] -[8.209577, "o", "0m"] -[9.211214, "o", "\r\n"] -[9.659333, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[15.2872, "o", "Found libraries/muparser\r\n"] -[15.287732, "o", "Found tests/googletest\r\n"] -[15.289874, "o", "Created manifest (dfetch.yaml) in /workspaces/dfetch/doc/generate-casts/ModbusScope\r\n"] -[15.350794, "o", "$ "] -[16.35396, "o", "\u001b["] -[16.534262, "o", "1m"] -[16.624409, "o", "ca"] -[16.714548, "o", "t "] -[16.804664, "o", "df"] -[16.894796, "o", "et"] -[16.984932, "o", "ch"] -[17.075079, "o", ".y"] -[17.165284, "o", "am"] -[17.255328, "o", "l\u001b"] -[17.435795, "o", "[0"] -[17.525823, "o", "m"] -[18.527427, "o", "\r\n"] -[18.530295, "o", "manifest:\r\n version: '0.0'\r\n\r\n remotes:\r\n - name: github-com-beltoforion\r\n url-base: https://github.com/beltoforion\r\n\r\n - name: github-com-google\r\n url-base: https://github.com/google\r\n\r\n projects:\r\n - name: libraries/muparser\r\n revision: 207d5b77c05c9111ff51ab91082701221220c477"] -[18.530424, "o", "\r\n remote: github-com-beltoforion\r\n tag: v2.3.2\r\n repo-path: muparser.git\r\n\r\n - name: tests/googletest\r\n revision: dcc92d0ab6c4ce022162a23566d44f673251eee4\r\n remote: github-com-google\r\n repo-path: googletest.git\r\n"] -[21.538779, "o", "$ "] -[21.540635, "o", "\u001b["] -[21.721005, "o", "1m"] -[21.811138, "o", "\u001b["] -[21.901272, "o", "0m"] -[21.90185, "o", "\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774990, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.018479, "o", "$ "] +[1.02162, "o", "\u001b"] +[1.201934, "o", "[1"] +[1.292035, "o", "ml"] +[1.382455, "o", "s "] +[1.472587, "o", "-l"] +[1.562753, "o", "\u001b["] +[1.652837, "o", "0m"] +[2.654367, "o", "\r\n"] +[2.657996, "o", "total 580\r\n"] +[2.65805, "o", "-rw-rw-rw- 1 dev dev 1137 Mar 29 09:03 CMakeLists.txt\r\n-rw-rw-rw- 1 dev dev 35147 Mar 29 09:03 LICENSE\r\n-rw-rw-rw- 1 dev dev 1796 Mar 29 09:03 README.md\r\n-rw-rw-rw- 1 dev dev 1381 Mar 29 09:03 appveyor.yml\r\n-rwxrwxrwx 1 dev dev 229 Mar 29 09:03 create_doc.sh\r\ndrwxrwxrwx+ 2 dev dev 4096 Mar 29 09:03 data\r\ndrwxrwxrwx+ 4 dev dev 4096 Mar 29 09:03 doc\r\ndrwxrwxrwx+ 4 dev dev 4096 Mar 29 09:03 docs\r\ndrwxrwxrwx+ 2 dev dev 4096 Mar 29 09:03 installer\r\ndrwxrwxrwx+ 4 dev dev 4096 Mar 29 09:03 libraries\r\n-rw-rw-rw- 1 dev dev 505101 Mar 29 09:03 modbusscope_demo.gif\r\ndrwxrwxrwx+ 5 dev dev 4096 Mar 29 09:03 resources\r\ndrwxrwxrwx+ 9 dev dev 4096 Mar 29 09:03 src\r\ndrwxrwxrwx+ 9 dev dev 4096 Mar 29 09:03 tests\r\n"] +[2.663114, "o", "$ "] +[3.666315, "o", "\u001b["] +[3.846588, "o", "1m"] +[3.936738, "o", "ca"] +[4.026855, "o", "t "] +[4.116978, "o", ".g"] +[4.207123, "o", "it"] +[4.297257, "o", "mo"] +[4.387381, "o", "du"] +[4.47751, "o", "le"] +[4.567656, "o", "s\u001b"] +[4.747897, "o", "[0"] +[4.838617, "o", "m"] +[5.83954, "o", "\r\n"] +[5.842593, "o", "[submodule \"tests/googletest\"]\r\n\tpath = tests/googletest\r\n\turl = https://github.com/google/googletest.git\r\n[submodule \"libraries/muparser\"]\r\n\tpath = libraries/muparser\r\n\turl = https://github.com/beltoforion/muparser.git\r\n"] +[5.848336, "o", "$ "] +[6.850501, "o", "\u001b["] +[7.031001, "o", "1m"] +[7.120927, "o", "df"] +[7.212149, "o", "et"] +[7.301522, "o", "ch"] +[7.391697, "o", " i"] +[7.481838, "o", "mp"] +[7.571967, "o", "or"] +[7.662181, "o", "t\u001b"] +[7.753437, "o", "[0"] +[7.933698, "o", "m"] +[8.935375, "o", "\r\n"] +[9.421828, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[10.232404, "o", "Found libraries/muparser\r\n"] +[10.23307, "o", "Found tests/googletest\r\n"] +[10.235064, "o", "Created manifest (dfetch.yaml) in /workspaces/dfetch/doc/generate-casts/ModbusScope\r\n"] +[10.298465, "o", "$ "] +[11.301376, "o", "\u001b"] +[11.481638, "o", "[1"] +[11.571784, "o", "mc"] +[11.661818, "o", "at"] +[11.751936, "o", " "] +[11.842325, "o", "df"] +[11.932471, "o", "et"] +[12.022769, "o", "ch"] +[12.112756, "o", ".y"] +[12.203183, "o", "a"] +[12.383276, "o", "ml"] +[12.473452, "o", "\u001b["] +[12.563576, "o", "0m"] +[13.565261, "o", "\r\n"] +[13.568165, "o", "manifest:\r\n version: '0.0'\r\n\r\n remotes:\r\n - name: github-com-beltoforion\r\n url-base: https://github.com/beltoforion\r\n\r\n - name: github-com-google\r\n url-base: https://github.com/google\r\n"] +[13.56836, "o", "\r\n projects:\r\n - name: libraries/muparser\r\n revision: 207d5b77c05c9111ff51ab91082701221220c477\r\n remote: github-com-beltoforion\r\n tag: v2.3.2\r\n repo-path: muparser.git\r\n\r\n - name: tests/googletest\r\n revision: dcc92d0ab6c4ce022162a23566d44f673251eee4\r\n remote: github-com-google\r\n repo-path: googletest.git\r\n"] +[16.577002, "o", "$ "] +[16.578831, "o", "\u001b["] +[16.759109, "o", "1m"] +[16.849231, "o", "\u001b["] +[16.93937, "o", "0m"] +[16.939809, "o", "\r\n"] diff --git a/doc/asciicasts/init.cast b/doc/asciicasts/init.cast index 1a874e1b..7178947a 100644 --- a/doc/asciicasts/init.cast +++ b/doc/asciicasts/init.cast @@ -1,62 +1,59 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247080, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.031816, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.038194, "o", "$ "] -[1.041604, "o", "\u001b"] -[1.221887, "o", "[1"] -[1.312005, "o", "ml"] -[1.402137, "o", "s "] -[1.492286, "o", "-l"] -[1.58241, "o", "\u001b["] -[1.672573, "o", "0m"] -[2.673483, "o", "\r\n"] -[2.678411, "o", "total 0\r\n"] -[2.684241, "o", "$ "] -[3.687919, "o", "\u001b["] -[3.86821, "o", "1m"] -[3.958369, "o", "df"] -[4.048479, "o", "et"] -[4.138623, "o", "ch"] -[4.228761, "o", " i"] -[4.318889, "o", "ni"] -[4.409004, "o", "t\u001b"] -[4.499166, "o", "[0"] -[4.589306, "o", "m"] -[5.590817, "o", "\r\n"] -[6.050037, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[6.051035, "o", "Created dfetch.yaml\r\n"] -[6.123621, "o", "$ "] -[7.126883, "o", "\u001b"] -[7.307436, "o", "[1"] -[7.39757, "o", "ml"] -[7.487715, "o", "s "] -[7.577871, "o", "-"] -[7.668007, "o", "l\u001b"] -[7.758176, "o", "[0"] -[7.848319, "o", "m"] -[8.849909, "o", "\r\n"] -[8.853427, "o", "total 4\r\n"] -[8.853712, "o", "-rw-rw-rw- 1 dev dev 733 Mar 23 06:24 dfetch.yaml\r\n"] -[8.858837, "o", "$ "] -[9.862119, "o", "\u001b"] -[10.042414, "o", "[1"] -[10.132532, "o", "mc"] -[10.22267, "o", "at"] -[10.312833, "o", " d"] -[10.403076, "o", "fe"] -[10.493247, "o", "tc"] -[10.58336, "o", "h."] -[10.673495, "o", "ya"] -[10.763623, "o", "ml"] -[10.943904, "o", "\u001b"] -[11.034041, "o", "[0"] -[11.124174, "o", "m"] -[12.125803, "o", "\r\n"] -[12.129146, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n"] -[12.129313, "o", " repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] -[15.140188, "o", "$ "] -[15.141604, "o", "\u001b["] -[15.321965, "o", "1m"] -[15.412142, "o", "\u001b["] -[15.502282, "o", "0m"] -[15.502794, "o", "\r\n"] -[15.506047, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774749, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.01997, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.025645, "o", "$ "] +[1.028232, "o", "\u001b"] +[1.208506, "o", "[1"] +[1.298633, "o", "ml"] +[1.38879, "o", "s "] +[1.478911, "o", "-l"] +[1.569025, "o", "\u001b["] +[1.659174, "o", "0m"] +[2.660665, "o", "\r\n"] +[2.664097, "o", "total 0\r\n"] +[2.669209, "o", "$ "] +[3.672702, "o", "\u001b"] +[3.852962, "o", "[1"] +[3.943099, "o", "md"] +[4.033217, "o", "fe"] +[4.123357, "o", "t"] +[4.213493, "o", "ch"] +[4.304553, "o", " i"] +[4.394237, "o", "ni"] +[4.484393, "o", "t\u001b"] +[4.574613, "o", "["] +[4.754762, "o", "0m"] +[5.756342, "o", "\r\n"] +[6.2007, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[6.201547, "o", "Created dfetch.yaml\r\n"] +[6.261313, "o", "$ "] +[7.264582, "o", "\u001b["] +[7.44485, "o", "1m"] +[7.53498, "o", "ls"] +[7.625104, "o", " -"] +[7.715249, "o", "l\u001b["] +[7.805373, "o", "0m"] +[8.805964, "o", "\r\n"] +[8.809378, "o", "total 4\r\n"] +[8.809496, "o", "-rw-rw-rw- 1 dev dev 733 Mar 29 08:59 dfetch.yaml\r\n"] +[8.815008, "o", "$ "] +[9.817529, "o", "\u001b["] +[9.997774, "o", "1m"] +[10.088313, "o", "ca"] +[10.178434, "o", "t "] +[10.268579, "o", "df"] +[10.358699, "o", "et"] +[10.450331, "o", "ch"] +[10.539015, "o", ".y"] +[10.629151, "o", "am"] +[10.719288, "o", "l\u001b"] +[10.899553, "o", "[0"] +[10.989869, "o", "m"] +[11.990998, "o", "\r\n"] +[11.994002, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] +[15.002874, "o", "$ "] +[15.004886, "o", "\u001b["] +[15.18517, "o", "1m"] +[15.275409, "o", "\u001b["] +[15.365546, "o", "0m"] +[15.366094, "o", "\r\n"] +[15.369642, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/interactive-add.cast b/doc/asciicasts/interactive-add.cast new file mode 100644 index 00000000..2bce072c --- /dev/null +++ b/doc/asciicasts/interactive-add.cast @@ -0,0 +1,115 @@ +{"version": 2, "width": 33, "height": 25, "timestamp": 1774774687, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.020763, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.025257, "o", "$ "] +[1.029119, "o", "\u001b"] +[1.209326, "o", "[1"] +[1.299481, "o", "mc"] +[1.389629, "o", "at"] +[1.47976, "o", " "] +[1.569896, "o", "df"] +[1.660035, "o", "et"] +[1.750192, "o", "ch"] +[1.840323, "o", ".y"] +[1.930438, "o", "a"] +[2.110741, "o", "ml"] +[2.200852, "o", "\u001b["] +[2.290977, "o", "0m"] +[3.292556, "o", "\r\n"] +[3.295576, "o", "manifest:\r\n version: '0.0'\r\n projects:\r\n - name: jsmn\r\n url: https://github.com/zserge/jsmn.git\r\n branch: master\r\n"] +[3.301532, "o", "$ "] +[3.596754, "r", "111x25"] +[4.304592, "o", "\u001b["] +[4.484932, "o", "1m"] +[4.57506, "o", "df"] +[4.665176, "o", "et"] +[4.755314, "o", "ch"] +[4.845442, "o", " a"] +[4.935555, "o", "dd"] +[5.025706, "o", " -"] +[5.115848, "o", "i "] +[5.205972, "o", "ht"] +[5.387564, "o", "tps"] +[5.476356, "o", ":/"] +[5.566459, "o", "/g"] +[5.656603, "o", "it"] +[5.746736, "o", "hu"] +[5.836865, "o", "b."] +[5.927003, "o", "co"] +[6.01714, "o", "m/"] +[6.107252, "o", "cp"] +[6.287544, "o", "pu"] +[6.377675, "o", "tes"] +[6.46779, "o", "t/"] +[6.558119, "o", "cp"] +[6.648201, "o", "pu"] +[6.738307, "o", "te"] +[6.828446, "o", "st"] +[6.918588, "o", ".g"] +[7.008799, "o", "it"] +[7.189192, "o", "\u001b["] +[7.279315, "o", "0m"] +[8.280907, "o", "\r\n"] +[8.85657, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[9.306349, "o", " \u001b[1;92mhttps://github.com/cpputest/cpputest.git:\u001b[0m\r\n"] +[9.306917, "o", " \u001b[1;34m> Adding project through interactive wizard\u001b[0m\r\n"] +[9.307253, "o", " \u001b[92m?\u001b[0m Name: \u001b[2mcpputest\u001b[0m"] +[13.834172, "o", "\r\n\u001b[1A\u001b[2K"] +[13.835052, "o", " - \u001b[34mname:\u001b[0m cpputest\r\n"] +[13.83617, "o", " \u001b[34murl:\u001b[0m https://github.com/cpputest/cpputest.git"] +[13.836356, "o", "\r\n \u001b[92m?\u001b[0m Destination: \u001b[2mcpputest\u001b[0m"] +[14.630908, "o", "\r\n"] +[14.63095, "o", "\u001b[1A\u001b[2K"] +[15.279036, "o", " \u001b[92m?\u001b[0m \u001b[1mVersion:\u001b[0m\r\n \u001b[92m▶\u001b[0m \u001b[1mmaster \u001b[2mbranch\u001b[0m \u001b[2m(default)\u001b[0m\u001b[0m\r\n 3.7.2 \u001b[2mtag\u001b[0m\r\n gh-pages \u001b[2mbranch\u001b[0m\r\n latest-passing-build \u001b[2mtag\u001b[0m\r\n ▸ revert-1598-fix\r\n separate_gtest \u001b[2mbranch\u001b[0m\r\n v3.3 \u001b[2mtag\u001b[0m\r\n v3.4 \u001b[2mtag\u001b[0m\r\n v3.5 \u001b[2mtag\u001b[0m\r\n v3.6 \u001b[2mtag\u001b[0m\r\n \u001b[2m↓ 4 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Enter select →/← expand/collapse Esc list\u001b[0m\r\n"] +[16.638518, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mVersion:\u001b[0m\r\n master \u001b[2mbranch\u001b[0m \u001b[2m(default)\u001b[0m\r\n \u001b[92m▶\u001b[0m \u001b[1m3.7.2 \u001b[2mtag\u001b[0m\u001b[0m\r\n gh-pages \u001b[2mbranch\u001b[0m\r\n latest-passing-build \u001b[2mtag\u001b[0m\r\n ▸ revert-1598-fix\r\n separate_gtest \u001b[2mbranch\u001b[0m\r\n v3.3 \u001b[2mtag\u001b[0m\r\n v3.4 \u001b[2mtag\u001b[0m\r\n v3.5 \u001b[2mtag\u001b[0m\r\n v3.6 \u001b[2mtag\u001b[0m\r\n \u001b[2m↓ 4 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Enter select →/← expand/collapse Esc list\u001b[0m\r\n"] +[16.864556, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mVersion:\u001b[0m\r\n master \u001b[2mbranch\u001b[0m \u001b[2m(default)\u001b[0m\r\n 3.7.2 \u001b[2mtag\u001b[0m\r\n \u001b[92m▶\u001b[0m \u001b[1mgh-pages \u001b[2mbranch\u001b[0m\u001b[0m\r\n latest-passing-build \u001b[2mtag\u001b[0m\r\n ▸ revert-1598-fix\r\n separate_gtest \u001b[2mbranch\u001b[0m\r\n v3.3 \u001b[2mtag\u001b[0m\r\n v3.4 \u001b[2mtag\u001b[0m\r\n v3.5 \u001b[2mtag\u001b[0m\r\n v3.6 \u001b[2mtag\u001b[0m\r\n \u001b[2m↓ 4 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Enter select →/← expand/collapse Esc list\u001b[0m\r\n"] +[16.998753, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mVersion:\u001b[0m\r\n master \u001b[2mbranch\u001b[0m \u001b[2m(default)\u001b[0m\r\n 3.7.2 \u001b[2mtag\u001b[0m\r\n gh-pages \u001b[2mbranch\u001b[0m\r\n \u001b[92m▶\u001b[0m \u001b[1mlatest-passing-build \u001b[2mtag\u001b[0m\u001b[0m\r\n ▸ revert-1598-fix\r\n separate_gtest \u001b[2mbranch\u001b[0m\r\n v3.3 \u001b[2mtag\u001b[0m\r\n v3.4 \u001b[2mtag\u001b[0m\r\n v3.5 \u001b[2mtag\u001b[0m\r\n v3.6 \u001b[2mtag\u001b[0m\r\n \u001b[2m↓ 4 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Enter select →/← expand/collapse Esc list\u001b[0m\r\n"] +[17.253788, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mVersion:\u001b[0m\r\n master \u001b[2mbranch\u001b[0m \u001b[2m(default)\u001b[0m\r\n 3.7.2 \u001b[2mtag\u001b[0m\r\n gh-pages \u001b[2mbranch\u001b[0m\r\n latest-passing-build \u001b[2mtag\u001b[0m\r\n \u001b[92m▶\u001b[0m ▸ \u001b[1mrevert-1598-fix\u001b[0m\r\n separate_gtest \u001b[2mbranch\u001b[0m\r\n v3.3 \u001b[2mtag\u001b[0m\r\n v3.4 \u001b[2mtag\u001b[0m\r\n v3.5 \u001b[2mtag\u001b[0m\r\n v3.6 \u001b[2mtag\u001b[0m\r\n \u001b[2m↓ 4 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Enter select →/← expand/collapse Esc list\u001b[0m\r\n"] +[17.539622, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mVersion:\u001b[0m\r\n master \u001b[2mbranch\u001b[0m \u001b[2m(default)\u001b[0m\r\n 3.7.2 \u001b[2mtag\u001b[0m\r\n gh-pages \u001b[2mbranch\u001b[0m\r\n latest-passing-build \u001b[2mtag\u001b[0m\r\n \u001b[92m▶\u001b[0m ▾ \u001b[1mrevert-1598-fix\u001b[0m\r\n junit_newline_encoding \u001b[2mbranch\u001b[0m\r\n separate_gtest \u001b[2mbranch\u001b[0m\r\n v3.3 \u001b[2mtag\u001b[0m\r\n v3.4 \u001b[2mtag\u001b[0m\r\n v3.5 \u001b[2mtag\u001b[0m\r\n \u001b[2m↓ 5 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Enter select →/← expand/collapse Esc list\u001b[0m\r\n"] +[18.318319, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mVersion:\u001b[0m\r\n master \u001b[2mbranch\u001b[0m \u001b[2m(default)\u001b[0m\r\n 3.7.2 \u001b[2mtag\u001b[0m\r\n gh-pages \u001b[2mbranch\u001b[0m\r\n latest-passing-build \u001b[2mtag\u001b[0m\r\n ▾ revert-1598-fix\r\n \u001b[92m▶\u001b[0m \u001b[1mjunit_newline_encoding \u001b[2mbranch\u001b[0m\u001b[0m\r\n separate_gtest \u001b[2mbranch\u001b[0m\r\n v3.3 \u001b[2mtag\u001b[0m\r\n v3.4 \u001b[2mtag\u001b[0m\r\n v3.5 \u001b[2mtag\u001b[0m\r\n \u001b[2m↓ 5 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Enter select →/← expand/collapse Esc list\u001b[0m\r\n"] +[18.669281, "o", "\u001b[13A\u001b[0J"] +[18.670395, "o", " \u001b[34mbranch:\u001b[0m revert-1598-fix/junit_newline_encoding\r\n"] +[19.277344, "o", " \u001b[92m?\u001b[0m \u001b[1mSource path:\u001b[0m\r\n \u001b[92m▶\u001b[0m \u001b[1m.\u001b[0m\r\n ▸ .circleci\r\n ▸ .github\r\n ▸ build\r\n ▸ builds\r\n ▸ cmake\r\n ▸ docker\r\n ▸ examples\r\n ▸ include\r\n ▸ m4\r\n \u001b[2m↓ 41 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Enter select →/← expand/collapse Esc skip\u001b[0m\r\n"] +[20.29916, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mSource path:\u001b[0m\r\n .\r\n \u001b[92m▶\u001b[0m ▸ \u001b[1m.circleci\u001b[0m\r\n ▸ .github\r\n ▸ build\r\n ▸ builds\r\n ▸ cmake\r\n ▸ docker\r\n ▸ examples\r\n ▸ include\r\n ▸ m4\r\n \u001b[2m↓ 41 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Enter select →/← expand/collapse Esc skip\u001b[0m\r\n"] +[21.650081, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mSource path:\u001b[0m\r\n \u001b[92m▶\u001b[0m \u001b[1m.\u001b[0m\r\n ▸ .circleci\r\n ▸ .github\r\n ▸ build\r\n ▸ builds\r\n ▸ cmake\r\n ▸ docker\r\n ▸ examples\r\n ▸ include\r\n ▸ m4\r\n \u001b[2m↓ 41 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Enter select →/← expand/collapse Esc skip\u001b[0m\r\n"] +[22.056607, "o", "\u001b[13A\u001b[0J"] +[22.061046, "o", " \u001b[92m?\u001b[0m \u001b[1mIgnore:\u001b[0m\r\n \u001b[92m▶\u001b[0m .\r\n ▸ .circleci\r\n ▸ .github\r\n ▸ build\r\n ▸ builds\r\n ▸ cmake\r\n ▸ docker\r\n ▸ examples\r\n ▸ include\r\n ▸ m4\r\n \u001b[2m↓ 41 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Space toggle Enter confirm →/← expand/collapse Esc skip\u001b[0m\r\n"] +[23.157548, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mIgnore:\u001b[0m\r\n .\r\n \u001b[92m▶\u001b[0m ▸ .circleci\r\n ▸ .github\r\n ▸ build\r\n ▸ builds\r\n ▸ cmake\r\n ▸ docker\r\n ▸ examples\r\n ▸ include\r\n ▸ m4\r\n \u001b[2m↓ 41 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Space toggle Enter confirm →/← expand/collapse Esc skip\u001b[0m\r\n"] +[23.460659, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mIgnore:\u001b[0m\r\n .\r\n ▸ .circleci\r\n \u001b[92m▶\u001b[0m ▸ .github\r\n ▸ build\r\n ▸ builds\r\n ▸ cmake\r\n ▸ docker\r\n ▸ examples\r\n ▸ include\r\n ▸ m4\r\n \u001b[2m↓ 41 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Space toggle Enter confirm →/← expand/collapse Esc skip\u001b[0m\r\n"] +[23.726259, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mIgnore:\u001b[0m\r\n .\r\n \u001b[92m▶\u001b[0m ▸ .circleci\r\n ▸ .github\r\n ▸ build\r\n ▸ builds\r\n ▸ cmake\r\n ▸ docker\r\n ▸ examples\r\n ▸ include\r\n ▸ m4\r\n \u001b[2m↓ 41 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Space toggle Enter confirm →/← expand/collapse Esc skip\u001b[0m\r\n"] +[23.954263, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mIgnore:\u001b[0m\r\n .\r\n \u001b[92m▶\u001b[0m ▸ \u001b[2m.circleci\u001b[0m\r\n ▸ .github\r\n ▸ build\r\n ▸ builds\r\n ▸ cmake\r\n ▸ docker"] +[23.954481, "o", "\r\n ▸ examples\r\n ▸ include\r\n ▸ m4\r\n \u001b[2m↓ 41 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Space toggle Enter confirm →/← expand/collapse Esc skip\u001b[0m\r\n"] +[24.090072, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mIgnore:\u001b[0m\r\n .\r\n ▸ \u001b[2m.circleci\u001b[0m\r\n \u001b[92m▶\u001b[0m ▸ .github\r\n ▸ build\r\n ▸ builds\r\n ▸ cmake\r\n ▸ docker\r\n ▸ examples\r\n ▸ include\r\n ▸ m4\r\n \u001b[2m↓ 41 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Space toggle Enter confirm →/← expand/collapse Esc skip\u001b[0m\r\n"] +[24.297134, "o", "\u001b[13A\u001b[0J \u001b[92m?\u001b[0m \u001b[1mIgnore:\u001b[0m\r\n .\r\n ▸ \u001b[2m.circleci\u001b[0m\r\n \u001b[92m▶\u001b[0m ▸ \u001b[2m.github\u001b[0m\r\n ▸ build\r\n ▸ builds\r\n ▸ cmake\r\n ▸ docker\r\n ▸ examples\r\n ▸ include\r\n ▸ m4\r\n \u001b[2m↓ 41 more below\u001b[0m\r\n \u001b[2m↑/↓ navigate Space toggle Enter confirm →/← expand/collapse Esc skip\u001b[0m\r\n"] +[24.702155, "o", "\u001b[13A\u001b[0J"] +[24.702928, "o", " \u001b[34mignore:\u001b[0m\r\n"] +[24.703401, "o", " - .circleci\r\n"] +[24.705431, "o", " - .github\r\n"] +[24.707902, "o", "Add project to manifest? \u001b[1m(\u001b[0my\u001b[1m)\u001b[0m: "] +[25.258062, "o", "y"] +[25.318216, "o", "\r\n"] +[25.319821, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] +[25.320446, "o", " \u001b[1;34m> Added 'cpputest' to manifest '/workspaces/dfetch/doc/generate-casts/interactive-add/dfetch.yaml'\u001b[0m\r\n"] +[25.321004, "o", "Run \u001b[32m'dfetch update cpputest'\u001b[0m now? \u001b[1m(\u001b[0my\u001b[1m)\u001b[0m: "] +[25.871142, "o", "n"] +[25.934569, "o", "\r\n"] +[26.010632, "o", "$ "] +[27.014153, "o", "\u001b["] +[27.194507, "o", "1m"] +[27.284626, "o", "ca"] +[27.374768, "o", "t "] +[27.464886, "o", "dfe"] +[27.555015, "o", "tc"] +[27.64514, "o", "h."] +[27.735274, "o", "ya"] +[27.82541, "o", "ml"] +[27.915743, "o", "\u001b[0"] +[28.095966, "o", "m"] +[29.09752, "o", "\r\n"] +[29.100401, "o", "manifest:\r\n version: '0.0'\r\n projects:\r\n - name: jsmn\r\n url: https://github.com/zserge/jsmn.git\r\n branch: master\r\n\r\n - name: cpputest\r\n url: https://github.com/cpputest/cpputest.git\r\n branch: revert-1598-fix/junit_newline_encoding\r\n ignore:\r\n - .circleci\r\n - .github\r\n"] +[32.108775, "o", "$ "] +[32.110446, "o", "\u001b["] +[32.29078, "o", "1m"] +[32.380906, "o", "\u001b["] +[32.471023, "o", "0m"] +[32.471578, "o", "\r\n"] +[32.474817, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/report.cast b/doc/asciicasts/report.cast index 986b8a02..8a62f4c7 100644 --- a/doc/asciicasts/report.cast +++ b/doc/asciicasts/report.cast @@ -1,45 +1,46 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247168, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.045612, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.049871, "o", "$ "] -[1.053174, "o", "\u001b["] -[1.233538, "o", "1m"] -[1.323672, "o", "ls"] -[1.413824, "o", " -"] -[1.503937, "o", "l\u001b"] -[1.594081, "o", "[0"] -[1.684878, "o", "m"] -[2.686286, "o", "\r\n"] -[2.690187, "o", "total 12\r\n"] -[2.690506, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 23 06:26 cpputest\r\n-rw-rw-rw- 1 dev dev 733 Mar 23 06:26 dfetch.yaml\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 23 06:26 jsmn\r\n"] -[2.697025, "o", "$ "] -[3.699485, "o", "\u001b["] -[3.879818, "o", "1m"] -[3.970058, "o", "df"] -[4.060259, "o", "et"] -[4.150603, "o", "ch"] -[4.240608, "o", " r"] -[4.330741, "o", "ep"] -[4.420922, "o", "or"] -[4.511042, "o", "t\u001b"] -[4.60117, "o", "[0"] -[4.781892, "o", "m"] -[5.782939, "o", "\r\n"] -[6.252819, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[6.293479, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] -[6.294254, "o", " \u001b[1;92m- remote :\u001b[0m\u001b[1;34m github\u001b[0m\r\n"] -[6.295702, "o", " \u001b[1;92m remote url :\u001b[0m\u001b[1;34m https://github.com/cpputest/cpputest.git\u001b[0m\r\n"] -[6.296252, "o", " \u001b[1;92m branch :\u001b[0m\u001b[1;34m master\u001b[0m\r\n"] -[6.296794, "o", " \u001b[1;92m tag :\u001b[0m\u001b[1;34m v3.4\u001b[0m\r\n"] -[6.297328, "o", " \u001b[1;92m last fetch :\u001b[0m\u001b[1;34m 23/03/2026, 06:25:57\u001b[0m\r\n"] -[6.297859, "o", " \u001b[1;92m revision :\u001b[0m\u001b[1;34m \u001b[0m\r\n"] -[6.298374, "o", " \u001b[1;92m patch :\u001b[0m\u001b[1;34m \u001b[0m\r\n"] -[6.299045, "o", " \u001b[1;92m licenses :\u001b[0m\u001b[1;34m BSD 3-Clause \"New\" or \"Revised\" License\u001b[0m\r\n"] -[6.303295, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] -[6.303927, "o", " \u001b[1;92m- remote :\u001b[0m\u001b[1;34m github\u001b[0m\r\n"] -[6.306005, "o", " \u001b[1;92m remote url :\u001b[0m\u001b[1;34m https://github.com/zserge/jsmn.git\u001b[0m\r\n \u001b[1;92m branch :\u001b[0m\u001b[1;34m master\u001b[0m\r\n"] -[6.306758, "o", " \u001b[1;92m tag :\u001b[0m\u001b[1;34m \u001b[0m\r\n"] -[6.307353, "o", " \u001b[1;92m last fetch :\u001b[0m\u001b[1;34m 23/03/2026, 06:25:58\u001b[0m\r\n"] -[6.308402, "o", " \u001b[1;92m revision :\u001b[0m\u001b[1;34m 25647e692c7906b96ffd2b05ca54c097948e879c\u001b[0m\r\n"] -[6.308825, "o", " \u001b[1;92m patch :\u001b[0m\u001b[1;34m \u001b[0m\r\n"] -[6.309627, "o", " \u001b[1;92m licenses :\u001b[0m\u001b[1;34m MIT License\u001b[0m\r\n"] -[9.382775, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774856, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.037865, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.042295, "o", "$ "] +[1.044677, "o", "\u001b["] +[1.225016, "o", "1m"] +[1.315141, "o", "ls"] +[1.4053, "o", " -"] +[1.495434, "o", "l\u001b"] +[1.585561, "o", "[0"] +[1.67568, "o", "m"] +[2.67721, "o", "\r\n"] +[2.680757, "o", "total 12\r\n"] +[2.68088, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 29 09:00 cpputest\r\n-rw-rw-rw- 1 dev dev 733 Mar 29 09:00 dfetch.yaml\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 29 09:00 jsmn\r\n"] +[2.685775, "o", "$ "] +[3.689043, "o", "\u001b"] +[3.869705, "o", "[1"] +[3.96164, "o", "md"] +[4.050768, "o", "fe"] +[4.14092, "o", "t"] +[4.23106, "o", "ch"] +[4.321181, "o", " r"] +[4.411318, "o", "ep"] +[4.501561, "o", "or"] +[4.591682, "o", "t"] +[4.772053, "o", "\u001b["] +[4.866174, "o", "0m"] +[5.865844, "o", "\r\n"] +[6.302145, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[6.331751, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] +[6.33235, "o", " \u001b[1;92m- remote :\u001b[0m\u001b[1;34m github\u001b[0m\r\n"] +[6.333651, "o", " \u001b[1;92m remote url :\u001b[0m\u001b[1;34m https://github.com/cpputest/cpputest.git\u001b[0m\r\n"] +[6.334169, "o", " \u001b[1;92m branch :\u001b[0m\u001b[1;34m master\u001b[0m\r\n"] +[6.334681, "o", " \u001b[1;92m tag :\u001b[0m\u001b[1;34m v3.4\u001b[0m\r\n"] +[6.335178, "o", " \u001b[1;92m last fetch :\u001b[0m\u001b[1;34m 29/03/2026, 09:00:44\u001b[0m\r\n"] +[6.335666, "o", " \u001b[1;92m revision :\u001b[0m\u001b[1;34m \u001b[0m\r\n"] +[6.33616, "o", " \u001b[1;92m patch :\u001b[0m\u001b[1;34m \u001b[0m\r\n"] +[6.336677, "o", " \u001b[1;92m licenses :\u001b[0m\u001b[1;34m BSD 3-Clause \"New\" or \"Revised\" License\u001b[0m\r\n"] +[6.340863, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] +[6.34139, "o", " \u001b[1;92m- remote :\u001b[0m\u001b[1;34m github\u001b[0m\r\n"] +[6.342645, "o", " \u001b[1;92m remote url :\u001b[0m\u001b[1;34m https://github.com/zserge/jsmn.git\u001b[0m\r\n"] +[6.343136, "o", " \u001b[1;92m branch :\u001b[0m\u001b[1;34m master\u001b[0m\r\n"] +[6.343719, "o", " \u001b[1;92m tag :\u001b[0m\u001b[1;34m \u001b[0m\r\n"] +[6.344382, "o", " \u001b[1;92m last fetch :\u001b[0m\u001b[1;34m 29/03/2026, 09:00:46\u001b[0m\r\n"] +[6.345512, "o", " \u001b[1;92m revision :\u001b[0m\u001b[1;34m 25647e692c7906b96ffd2b05ca54c097948e879c\u001b[0m\r\n \u001b[1;92m patch :\u001b[0m\u001b[1;34m \u001b[0m\r\n"] +[6.348514, "o", " \u001b[1;92m licenses :\u001b[0m\u001b[1;34m MIT License\u001b[0m\r\n"] +[9.422094, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/sbom.cast b/doc/asciicasts/sbom.cast index acdd57ed..b1dd4809 100644 --- a/doc/asciicasts/sbom.cast +++ b/doc/asciicasts/sbom.cast @@ -1,50 +1,52 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247178, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.043053, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.048537, "o", "$ "] -[1.051541, "o", "\u001b["] -[1.231806, "o", "1m"] -[1.321953, "o", "ls"] -[1.412097, "o", " -"] -[1.502223, "o", "l\u001b"] -[1.592519, "o", "[0"] -[1.682591, "o", "m"] -[2.684033, "o", "\r\n"] -[2.688422, "o", "total 12\r\n"] -[2.688473, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 23 06:26 cpputest\r\n-rw-rw-rw- 1 dev dev 733 Mar 23 06:26 dfetch.yaml\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 23 06:26 jsmn\r\n"] -[2.694441, "o", "$ "] -[3.699382, "o", "\u001b["] -[3.87915, "o", "1m"] -[3.969283, "o", "df"] -[4.059408, "o", "et"] -[4.150202, "o", "ch"] -[4.239706, "o", " r"] -[4.331648, "o", "ep"] -[4.421775, "o", "or"] -[4.511916, "o", "t "] -[4.602064, "o", "-t"] -[4.78232, "o", " sb"] -[4.872459, "o", "om"] -[4.962612, "o", "\u001b["] -[5.052725, "o", "0m"] -[6.054318, "o", "\r\n"] -[6.54686, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[6.586475, "o", "Generated SBoM report: report.json\r\n"] -[6.657691, "o", "$ "] -[7.660886, "o", "\u001b["] -[7.841184, "o", "1m"] -[7.931322, "o", "ca"] -[8.021464, "o", "t "] -[8.1116, "o", "re"] -[8.201738, "o", "po"] -[8.291867, "o", "rt"] -[8.382014, "o", ".j"] -[8.472119, "o", "so"] -[8.563745, "o", "n\u001b"] -[8.742502, "o", "[0"] -[8.832648, "o", "m"] -[9.834239, "o", "\r\n"] -[9.837464, "o", "{\r\n \"components\": [\r\n {\r\n \"bom-ref\": \"cpputest-v3.4\",\r\n \"evidence\": {\r\n \"identity\": [\r\n {\r\n \"concludedValue\": \"cpputest\",\r\n \"field\": \"name\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Name as used for project in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n },\r\n {\r\n \"concludedValue\": \"pkg:github/cpputest/cpputest@v3.4\",\r\n \"field\": \"purl\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\","] -[9.837667, "o", "\r\n \"value\": \"Determined from https://github.com/cpputest/cpputest.git as used for the project cpputest in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n },\r\n {\r\n \"concludedValue\": \"v3.4\",\r\n \"field\": \"version\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Version as used for project in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n }\r\n ],\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"id\": \"BSD-3-Clause\"\r\n }\r\n }\r\n ],\r\n \"occurrences\": [\r\n {\r\n \"line\": 9,\r\n \"location\": \"dfetch.yaml\",\r\n \"offset\": 11\r\n }\r\n ]\r\n },\r\n \"externalReferences\": [\r\n {\r\n \"type\": \"vcs\",\r\n \"url\": \"https://github.com/cpputest/cpputest\"\r\n }\r\n ],\r\n \"group\": \"cpputest\",\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"id\": \"BSD-3-Clause\"\r\n }\r\n }\r\n ],\r\n \"name\": \"cpputest\",\r\n \"purl\": \"pkg:github/cpputest/cpputest@v3.4\",\r\n \"type\": \"library\",\r\n \"version\": \"v3.4\"\r\n },\r\n {\r\n \"bom-ref\": \"jsmn-25647e692c7906b96ffd2b05ca54c097948e879c\",\r\n \"evidence\": {\r\n \"identity\": [\r\n {\r\n \"concludedValue\": \"jsmn\",\r\n \"field\": \"name\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Name as used for project in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n },\r\n {\r\n \"concludedValue\": \"pkg:github/zserge/jsmn@25647e692c7906b96ffd2b05ca54c097948e879c\",\r\n \"field\": \"purl\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Determined from https://github.com/zserge/jsmn.git as used for the project jsmn in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n },\r\n {\r\n \"concludedValue\": \"25647e692c7906b96ffd2b05ca54c097948e879c\",\r\n \"field\": \"version\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Version as used for project in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n }\r\n ],\r\n \"licenses\": [\r\n {\r\n "] -[9.837712, "o", " \"license\": {\r\n \"id\": \"MIT\"\r\n }\r\n }\r\n ],\r\n \"occurrences\": [\r\n {\r\n \"line\": 14,\r\n \"location\": \"dfetch.yaml\",\r\n \"offset\": 11\r\n }\r\n ]\r\n },\r\n \"externalReferences\": [\r\n {\r\n \"type\": \"vcs\",\r\n \"url\": \"https://github.com/zserge/jsmn\"\r\n }\r\n ],\r\n \"group\": \"zserge\",\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"id\": \"MIT\"\r\n }\r\n }\r\n ],\r\n \"name\": \"jsmn\",\r\n \"purl\": \"pkg:github/zserge/jsmn@25647e692c7906b96ffd2b05ca54c097948e879c\",\r\n \"type\": \"library\",\r\n \"version\": \"25647e692c7906b96ffd2b05ca54c097948e879c\"\r\n }\r\n ],\r\n \"dependencies\": [\r\n {\r\n \"ref\": \"cpputest-v3.4\"\r\n },\r\n {\r\n \"ref\": \"jsmn-25647e692c7906b96ffd2b05ca54c097948e879c\"\r\n }\r\n ],\r\n \"metadata\": {\r\n \"timestamp\": \"2026-03-23T06:26:24.650061+00:00\",\r\n \"tools\": {\r\n \"components\": [\r\n {\r\n \"bom-ref\": \"dfetch-0.12.1\",\r\n \"externalReferences\": [\r\n {\r\n \"type\": \"build-system\",\r\n \"url\": \"https://github.com/dfetch-org/dfetch/actions\"\r\n },\r\n {\r\n \"type\": \"distribution\",\r\n \"url\": \"https://pypi.org/project/dfetch/\"\r\n },\r\n {\r\n \"type\": \"documentation\",\r\n \"url\": \"https://dfetch.readthedocs.io/\"\r\n },\r\n {\r\n \"type\": \"issue-tracker\",\r\n \"url\": \"https://github.com/dfetch-org/dfetch/issues\"\r\n },\r\n {\r\n \"type\": \"license\",\r\n \"url\": \"https://github.com/dfetch-org/dfetch/blob/main/LICENSE\"\r\n },\r\n {\r\n \"type\": \"release-notes\",\r\n \"url\": \"https://github.com/dfetch-org/dfetch/blob/main/CHANGELOG.rst\"\r\n },\r\n {\r\n \"type\": \"vcs\",\r\n \"url\": \"https://github.com/dfetch-org/dfetch\"\r\n },\r\n {\r\n \"type\": \"website\",\r\n \"url\": \"https://dfetch-org.github.io/\"\r\n }\r\n ],\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"acknowledgement\": \"declared\",\r\n \"id\": \"MIT\"\r\n }\r\n }\r\n ],\r\n \"name\": \"dfetch\",\r\n \"supplier\": {\r\n \"name\": \"dfetch-org\"\r\n },\r\n \"type\": \"application\",\r\n \"version\": \"0.12.1\"\r\n },\r\n {\r\n \"description\": \"Python library for CycloneDX\",\r\n \"externalReferences\": [\r\n {\r\n \"type\": \"build-system\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib/actions\"\r\n },\r\n {\r\n \"type\": \"distribution\",\r\n \"url\": \"https://pypi.org/project/cyclonedx-python-lib/\"\r\n },\r\n {\r\n \"type\": \"documentation\",\r\n"] -[9.837736, "o", " \"url\": \"https://cyclonedx-python-library.readthedocs.io/\"\r\n },\r\n {\r\n \"type\": \"issue-tracker\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib/issues\"\r\n },\r\n {\r\n \"type\": \"license\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE\"\r\n },\r\n {\r\n \"type\": \"release-notes\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md\"\r\n },\r\n {\r\n \"type\": \"vcs\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib\"\r\n },\r\n {\r\n \"type\": \"website\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib/#readme\"\r\n }\r\n ],\r\n \"group\": \"CycloneDX\",\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"acknowledgement\": \"declared\",\r\n \"id\": \"Apache-2.0\"\r\n }\r\n }\r\n ],\r\n \"name\": \"cyclonedx-python-lib\",\r\n \"type\": \"library\",\r\n \"version\": \"11.7.0\"\r\n }\r\n ]\r\n }\r\n },\r\n \"serialNumber\": \"urn:uuid:732db7bd-d622-44c1-a4c4-57337557686b\",\r\n \"version\": 1,\r\n \"$schema\": \"http://cyclonedx.org/schema/bom-1.6.schema.json\",\r\n \"bomFormat\": \"CycloneDX\",\r\n \"specVersion\": \"1.6\"\r\n}"] -[12.842484, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774865, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.040968, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.045964, "o", "$ "] +[1.048967, "o", "\u001b"] +[1.229236, "o", "[1"] +[1.319363, "o", "ml"] +[1.409507, "o", "s "] +[1.499609, "o", "-"] +[1.589766, "o", "l\u001b"] +[1.679916, "o", "[0"] +[1.770034, "o", "m"] +[2.771549, "o", "\r\n"] +[2.774909, "o", "total 12\r\n"] +[2.774962, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 29 09:01 cpputest\r\n-rw-rw-rw- 1 dev dev 733 Mar 29 09:01 dfetch.yaml\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 29 09:01 jsmn\r\n"] +[2.779947, "o", "$ "] +[3.791667, "o", "\u001b["] +[3.971413, "o", "1m"] +[4.061562, "o", "df"] +[4.151692, "o", "et"] +[4.241816, "o", "ch "] +[4.331944, "o", "re"] +[4.422098, "o", "po"] +[4.512308, "o", "rt"] +[4.602741, "o", " -"] +[4.692843, "o", "t s"] +[4.873105, "o", "bo"] +[4.963226, "o", "m\u001b"] +[5.053361, "o", "[0"] +[5.143501, "o", "m"] +[6.145265, "o", "\r\n"] +[6.596424, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[6.632634, "o", "Generated SBoM report: report.json\r\n"] +[6.702399, "o", "$ "] +[7.705495, "o", "\u001b"] +[7.885758, "o", "[1"] +[7.975923, "o", "mc"] +[8.066057, "o", "at"] +[8.156166, "o", " r"] +[8.246316, "o", "ep"] +[8.336447, "o", "or"] +[8.426571, "o", "t."] +[8.516813, "o", "js"] +[8.606839, "o", "on"] +[8.787069, "o", "\u001b"] +[8.877364, "o", "[0"] +[8.967486, "o", "m"] +[9.968411, "o", "\r\n"] +[9.971389, "o", "{\r\n \"components\": [\r\n {\r\n \"bom-ref\": \"cpputest-v3.4\",\r\n \"evidence\": {\r\n"] +[9.97156, "o", " \"identity\": [\r\n {\r\n \"concludedValue\": \"cpputest\",\r\n \"field\": \"name\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Name as used for project in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n },\r\n {\r\n \"concludedValue\": \"pkg:github/cpputest/cpputest@v3.4\",\r\n \"field\": \"purl\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Determined from https://github.com/cpputest/cpputest.git as used for the project cpputest in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n },\r\n {\r\n \"concludedValue\": \"v3.4\",\r\n \"field\": \"version\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Version as used for project in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n }\r\n ],\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"id\": \"BSD-3-Clause\"\r\n }\r\n }\r\n ],\r\n \"occurrences\": [\r\n {\r\n \"line\": 9,\r\n \"location\": \"dfetch.yaml\",\r\n \"offset\": 11\r\n }\r\n ]\r\n },\r\n \"externalReferences\": [\r\n {\r\n \"type\": \"vcs\",\r\n \"url\": \"https://github.com/cpputest/cpputest\"\r\n }\r\n ],\r\n \"group\": \"cpputest\",\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"id\": \"BSD-3-Clause\"\r\n }\r\n }\r\n ],\r\n \"name\": \"cpputest\",\r\n \"purl\": \"pkg:github/cpputest/cpputest@v3.4\",\r\n \"type\": \"library\",\r\n \"version\": \"v3.4\"\r\n },\r\n {\r\n \"bom-ref\": \"jsmn-25647e692c7906b96ffd2b05ca54c097948e879c\",\r\n \"evidence\": {\r\n \"identity\": [\r\n {\r\n \"concludedValue\": \"jsmn\",\r\n \"field\": \"name\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Name as used for project in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n },\r\n {\r\n \"concludedValue\": \"pkg:github/zserge/jsmn@25647e692c7906b96ffd2b05ca54c097948e879c\",\r\n \"field\": \"purl\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Determined from https://github.com/zserge/jsmn."] +[9.971591, "o", "git as used for the project jsmn in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n },\r\n {\r\n \"concludedValue\": \"25647e692c7906b96ffd2b05ca54c097948e879c\",\r\n \"field\": \"version\",\r\n \"methods\": [\r\n {\r\n \"confidence\": 0.4,\r\n \"technique\": \"manifest-analysis\",\r\n \"value\": \"Version as used for project in dfetch.yaml\"\r\n }\r\n ],\r\n \"tools\": [\r\n \"dfetch-0.12.1\"\r\n ]\r\n }\r\n ],\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"id\": \"MIT\"\r\n }\r\n }\r\n ],\r\n \"occurrences\": [\r\n {\r\n \"line\": 14,\r\n \"location\": \"dfetch.yaml\",\r\n \"offset\": 11\r\n }\r\n ]\r\n },\r\n \"externalReferences\": [\r\n {\r\n \"type\": \"vcs\",\r\n \"url\": \"https://github.com/zserge/jsmn\"\r\n }\r\n ],\r\n \"group\": \"zserge\",\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"id\": \"MIT\"\r\n }\r\n }\r\n ],\r\n \"name\": \"jsmn\",\r\n \"purl\": \"pkg:github/zserge/jsmn@25647e692c7906b96ffd2b05ca54c097948e879c\",\r\n \"type\": \"library\",\r\n \"version\": \"25647e692c7906b96ffd2b05ca54c097948e879c\"\r\n }\r\n ],\r\n \"dependencies\": [\r\n {\r\n \"ref\": \"cpputest-v3.4\"\r\n },\r\n {\r\n \"ref\": \"jsmn-25647e692c7906b96ffd2b05ca54c097948e879c\"\r\n }\r\n ],\r\n \"metadata\": {\r\n \"timestamp\": \"2026-03-29T09:01:12.601922+00:00\",\r\n \"tools\": {\r\n \"components\": [\r\n {\r\n \"bom-ref\": \"dfetch-0.12.1\",\r\n \"externalReferences\": [\r\n {\r\n \"type\": \"build-system\",\r\n \"url\": \"https://github.com/dfetch-org/dfetch/actions\"\r\n },\r\n {\r\n \"type\": \"distribution\",\r\n \"url\": \"https://pypi.org/project/dfetch/\"\r\n },\r\n {\r\n \"type\": \"documentation\",\r\n \"url\": \"https://dfetch.readthedocs.io/\"\r\n },\r\n {\r\n \"type\": \"issue-tracker\",\r\n \"url\": \"https://github.com/dfetch-org/dfetch/issues\"\r\n },\r\n {\r\n \"type\": \"license\",\r\n \"url\": \"https://github.com/dfetch-org/dfetch/blob/main/LICENSE\"\r\n },\r\n {\r\n \"type\": \"release-notes\",\r\n \"url\": \"https://github.com/dfetch-org/dfetch/blob/main/CHANGELOG.rst\"\r\n },\r\n {\r\n \"type\": \"vcs\",\r\n \"url\": \"https://github.com/dfetch-org/dfetch\"\r\n },\r\n {\r\n \"type\": \"website\",\r\n \"url\": \"https://dfetch-org.github.io/\"\r\n }\r\n ],\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"acknowledgement\": \"declared\",\r\n \"id\": \"MI"] +[9.971639, "o", "T\"\r\n }\r\n }\r\n ],\r\n \"name\": \"dfetch\",\r\n \"supplier\": {\r\n \"name\": \"dfetch-org\"\r\n },\r\n \"type\": \"application\",\r\n \"version\": \"0.12.1\"\r\n },\r\n {\r\n \"description\": \"Python library for CycloneDX\",\r\n \"externalReferences\": [\r\n {\r\n \"type\": \"build-system\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib/actions\"\r\n },\r\n {\r\n \"type\": \"distribution\",\r\n \"url\": \"https://pypi.org/project/cyclonedx-python-lib/\"\r\n },\r\n {\r\n \"type\": \"documentation\",\r\n \"url\": \"https://cyclonedx-python-library.readthedocs.io/\"\r\n },\r\n {\r\n \"type\": \"issue-tracker\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib/issues\"\r\n },\r\n {\r\n \"type\": \"license\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE\"\r\n },\r\n {\r\n \"type\": \"release-notes\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md\"\r\n },\r\n {\r\n \"type\": \"vcs\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib\"\r\n },\r\n {\r\n \"type\": \"website\",\r\n \"url\": \"https://github.com/CycloneDX/cyclonedx-python-lib/#readme\"\r\n }\r\n ],\r\n \"group\": \"CycloneDX\",\r\n \"licenses\": [\r\n {\r\n \"license\": {\r\n \"acknowledgement\": \"declared\",\r\n \"id\": \"Apache-2.0\"\r\n }\r\n }\r\n ],\r\n \"name\": \"cyclonedx-python-lib\",\r\n \"type\": \"library\",\r\n \"version\": \"11.7.0\"\r\n }\r\n ]\r\n }\r\n },\r\n \"serialNumber\": \"urn:uuid:c98bbea9-4f0b-4b77-a969-ebbd1daa7785\",\r\n \"version\": 1,\r\n \"$schema\": \"http://cyclonedx.org/schema/bom-1.6.schema.json\",\r\n \"bomFormat\": \"CycloneDX\",\r\n \"specVersion\": \"1.6\"\r\n}"] +[12.976212, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/update-patch.cast b/doc/asciicasts/update-patch.cast index 805d4f02..7d9f524c 100644 --- a/doc/asciicasts/update-patch.cast +++ b/doc/asciicasts/update-patch.cast @@ -1,226 +1,234 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247230, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[2.679649, "o", "\u001b[H\u001b[2J\u001b[3J"] -[2.684885, "o", "$ "] -[3.688252, "o", "\u001b"] -[3.869801, "o", "[1"] -[3.959776, "o", "ml"] -[4.049904, "o", "s "] -[4.140044, "o", "-l"] -[4.230172, "o", " ."] -[4.320289, "o", "\u001b["] -[4.410438, "o", "0m"] -[5.41212, "o", "\r\n"] -[5.415596, "o", "total 16\r\n"] -[5.415714, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 23 06:27 cpputest\r\n-rw-rw-rw- 1 dev dev 229 Mar 23 06:27 dfetch.yaml\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 23 06:27 jsmn\r\ndrwxrwxrwx+ 2 dev dev 4096 Mar 23 06:27 patches\r\n"] -[5.421158, "o", "$ "] -[6.424276, "o", "\u001b["] -[6.604707, "o", "1m"] -[6.694767, "o", "ca"] -[6.784905, "o", "t "] -[6.875029, "o", "df"] -[6.965194, "o", "et"] -[7.055322, "o", "ch"] -[7.145468, "o", ".y"] -[7.235605, "o", "am"] -[7.32574, "o", "l\u001b"] -[7.50602, "o", "[0"] -[7.596127, "o", "m"] -[8.597737, "o", "\r\n"] -[8.600857, "o", "manifest:\r\n version: 0.0\r\n\r\n remotes:\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/\r\n repo-path: cpputest/cpputest.git\r\n tag: v3.4\r\n patch: patches/cpputest.patch\r\n\r\n"] -[8.606351, "o", "$ "] -[9.609779, "o", "\u001b"] -[9.790508, "o", "[1"] -[9.880623, "o", "mc"] -[9.970763, "o", "at"] -[10.062535, "o", " "] -[10.151057, "o", "pa"] -[10.241191, "o", "tc"] -[10.331335, "o", "he"] -[10.42146, "o", "s/"] -[10.511585, "o", "c"] -[10.691847, "o", "pp"] -[10.781986, "o", "ut"] -[10.872113, "o", "es"] -[10.962232, "o", "t."] -[11.052353, "o", "p"] -[11.142513, "o", "at"] -[11.232645, "o", "ch"] -[11.322774, "o", "\u001b["] -[11.412896, "o", "0m"] -[12.416043, "o", "\r\n"] -[12.422528, "o", "diff --git a/README.md b/README.md\r\nindex 2655a7b..fc6084e 100644\r\n--- a/README.md\r\n+++ b/README.md\r\n@@ -3,7 +3,7 @@ CppUTest\r\n \r\n CppUTest unit testing and mocking framework for C/C++\r\n \r\n-[More information on the project page](http://cpputest.github.com)\r\n+[More information on the project page](http://cpputest.gitlab.com)\r\n \r\n [![Build Status](https://travis-ci.org/cpputest/cpputest.png?branch=master)](https://travis-ci.org/cpputest/cpputest)\r\n \r\n"] -[12.43523, "o", "$ "] -[13.438487, "o", "\u001b["] -[13.618766, "o", "1m"] -[13.708896, "o", "gi"] -[13.799059, "o", "t "] -[13.889204, "o", "sta"] -[13.979331, "o", "tu"] -[14.06947, "o", "s\u001b"] -[14.160496, "o", "[0"] -[14.250296, "o", "m"] -[15.252133, "o", "\r\n"] -[15.274579, "o", "On branch main\r\n"] -[15.274685, "o", "nothing to commit, working tree clean\r\n"] -[15.279602, "o", "$ "] -[16.283037, "o", "\u001b"] -[16.463326, "o", "[1"] -[16.553447, "o", "ms"] -[16.644844, "o", "ed"] -[16.733917, "o", " "] -[16.824086, "o", "-i"] -[16.914242, "o", " '"] -[17.004394, "o", "s/"] -[17.094521, "o", "gi"] -[17.184676, "o", "t"] -[17.364908, "o", "la"] -[17.45504, "o", "b/"] -[17.546198, "o", "gi"] -[17.635283, "o", "te"] -[17.725435, "o", "a"] -[17.815549, "o", "/g"] -[17.905698, "o", "' "] -[17.997097, "o", "cp"] -[18.085979, "o", "pu"] -[18.267398, "o", "t"] -[18.357218, "o", "es"] -[18.447359, "o", "t/"] -[18.537476, "o", "sr"] -[18.627639, "o", "c/"] -[18.717766, "o", "R"] -[18.80798, "o", "EA"] -[18.898057, "o", "DM"] -[18.988199, "o", "E."] -[19.168441, "o", "md"] -[19.258608, "o", "\u001b"] -[19.348735, "o", "[0"] -[19.439033, "o", "m"] -[20.441248, "o", "\r\n"] -[20.451314, "o", "$ "] -[21.453518, "o", "\u001b"] -[21.633762, "o", "[1"] -[21.723919, "o", "mg"] -[21.814068, "o", "it"] -[21.908029, "o", " a"] -[21.997647, "o", "dd"] -[22.087783, "o", " ."] -[22.177931, "o", "\u001b["] -[22.268069, "o", "0m"] -[23.269616, "o", "\r\n"] -[23.281799, "o", "$ "] -[24.285111, "o", "\u001b["] -[24.465368, "o", "1m"] -[24.555516, "o", "gi"] -[24.645635, "o", "t "] -[24.735783, "o", "com"] -[24.825908, "o", "mi"] -[24.916287, "o", "t "] -[25.006523, "o", "-a"] -[25.096593, "o", " -"] -[25.186727, "o", "m '"] -[25.367009, "o", "Fi"] -[25.457127, "o", "x "] -[25.547264, "o", "vc"] -[25.637396, "o", "s "] -[25.727519, "o", "hos"] -[25.817651, "o", "t'"] -[25.907789, "o", "\u001b["] -[25.99792, "o", "0m"] -[26.99958, "o", "\r\n"] -[27.009597, "o", "[main d9c949e] Fix vcs host\r\n"] -[27.009792, "o", " 1 file changed, 1 insertion(+), 1 deletion(-)\r\n"] -[27.017534, "o", "$ "] -[28.020595, "o", "\u001b"] -[28.200877, "o", "[1"] -[28.291014, "o", "md"] -[28.381168, "o", "fe"] -[28.471509, "o", "tc"] -[28.561559, "o", "h "] -[28.651705, "o", "up"] -[28.74183, "o", "da"] -[28.831992, "o", "te"] -[28.922231, "o", "-p"] -[29.102482, "o", "a"] -[29.19276, "o", "tc"] -[29.282829, "o", "h "] -[29.37297, "o", "cp"] -[29.463097, "o", "pu"] -[29.553368, "o", "te"] -[29.643368, "o", "st"] -[29.733712, "o", "\u001b["] -[29.82378, "o", "0m"] -[30.825507, "o", "\r\n"] -[31.290516, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[31.330486, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] -[31.330905, "o", "\u001b[?25l"] -[31.411842, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[31.492459, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[31.57308, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[31.653675, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[31.739402, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[31.820007, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[31.902025, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[31.983676, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[32.066589, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[32.147225, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[32.22791, "o", "\r\u001b[2K\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[32.308516, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[32.389126, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[32.422286, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[32.423098, "o", " \u001b[1;34m> Fetched v3.4\u001b[0m\r\n"] -[32.508714, "o", " \u001b[1;34m> Updating patch \"patches/cpputest.patch\"\u001b[0m\r\n"] -[32.52478, "o", "\u001b[?25l"] -[32.605632, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[32.686385, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[32.766994, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[32.847569, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[32.928175, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[33.008746, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[33.091624, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[33.174219, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[33.254869, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[33.288973, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[33.289714, "o", " \u001b[1;34m> Fetched v3.4\u001b[0m\r\n"] -[33.290448, "o", " \u001b[1;34m> Applying patch \"patches/cpputest.patch\"\u001b[0m\r\n"] -[33.293167, "o", " \u001b[34msuccessfully patched 1/1: \u001b[0m\u001b[34m \u001b[0m \r\n\u001b[34mb'README.md'\u001b[0m \r\n"] -[33.376907, "o", "$ "] -[34.380162, "o", "\u001b["] -[34.560464, "o", "1m"] -[34.650612, "o", "ca"] -[34.740726, "o", "t "] -[34.830872, "o", "pat"] -[34.921012, "o", "ch"] -[35.011072, "o", "es"] -[35.101219, "o", "/c"] -[35.191398, "o", "pp"] -[35.281553, "o", "ute"] -[35.461796, "o", "st"] -[35.551928, "o", ".p"] -[35.642065, "o", "at"] -[35.732207, "o", "ch"] -[35.822371, "o", "\u001b[0"] -[35.912491, "o", "m"] -[36.914126, "o", "\r\n"] -[36.917467, "o", "diff --git a/README.md b/README.md\r\nindex 2655a7b..da133cb 100644\r\n--- a/README.md\r\n+++ b/README.md\r\n@@ -3,7 +3,7 @@ CppUTest\r\n \r\n CppUTest unit testing and mocking framework for C/C++\r\n \r\n-[More information on the project page](http://cpputest.github.com)\r\n+[More information on the project page](http://cpputest.gitea.com)\r\n \r\n [![Build Status](https://travis-ci.org/cpputest/cpputest.png?branch=master)](https://travis-ci.org/cpputest/cpputest)\r\n \r\n"] -[36.922667, "o", "$ "] -[37.925928, "o", "\u001b"] -[38.106205, "o", "[1"] -[38.196359, "o", "mg"] -[38.28649, "o", "it"] -[38.376634, "o", " "] -[38.466758, "o", "st"] -[38.55689, "o", "at"] -[38.647193, "o", "us"] -[38.737268, "o", "\u001b["] -[38.827402, "o", "0"] -[39.007657, "o", "m"] -[40.009953, "o", "\r\n"] -[40.042467, "o", "On branch main\r\n"] -[40.042507, "o", "Changes not staged for commit:\r\n (use \"git add ...\" to update what will be committed)\r\n (use \"git restore ...\" to discard changes in working directory)\r\n\t\u001b[31mmodified: cpputest/src/.dfetch_data.yaml\u001b[m\r\n\t\u001b[31mmodified: patches/cpputest.patch\u001b[m\r\n\r\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\r\n"] -[43.050156, "o", "$ "] -[43.052102, "o", "\u001b["] -[43.232375, "o", "1m"] -[43.322521, "o", "\u001b["] -[43.412646, "o", "0m"] -[43.413234, "o", "\r\n"] -[43.416154, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774917, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[2.515807, "o", "\u001b[H\u001b[2J\u001b[3J"] +[2.519555, "o", "$ "] +[3.52265, "o", "\u001b"] +[3.703098, "o", "[1"] +[3.793236, "o", "ml"] +[3.883397, "o", "s "] +[3.973506, "o", "-l"] +[4.063636, "o", " ."] +[4.153775, "o", "\u001b["] +[4.243875, "o", "0m"] +[5.245459, "o", "\r\n"] +[5.248864, "o", "total 16\r\n"] +[5.248972, "o", "drwxr-xr-x+ 3 dev dev 4096 Mar 29 09:01 cpputest\r\n-rw-rw-rw- 1 dev dev 229 Mar 29 09:01 dfetch.yaml\r\ndrwxr-xr-x+ 4 dev dev 4096 Mar 29 09:01 jsmn\r\ndrwxrwxrwx+ 2 dev dev 4096 Mar 29 09:01 patches\r\n"] +[5.254118, "o", "$ "] +[6.257455, "o", "\u001b"] +[6.437741, "o", "[1"] +[6.527858, "o", "mc"] +[6.617994, "o", "at"] +[6.708151, "o", " "] +[6.79829, "o", "df"] +[6.888417, "o", "et"] +[6.978538, "o", "ch"] +[7.068682, "o", ".y"] +[7.158805, "o", "a"] +[7.339059, "o", "ml"] +[7.429192, "o", "\u001b["] +[7.519529, "o", "0m"] +[8.521074, "o", "\r\n"] +[8.524082, "o", "manifest:\r\n version: 0.0\r\n\r\n remotes:\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/\r\n repo-path: cpputest/cpputest.git\r\n tag: v3.4\r\n patch: patches/cpputest.patch\r\n\r\n"] +[8.52964, "o", "$ "] +[9.532797, "o", "\u001b"] +[9.713039, "o", "[1"] +[9.803212, "o", "mc"] +[9.893367, "o", "at"] +[9.98349, "o", " "] +[10.073629, "o", "pa"] +[10.163769, "o", "tc"] +[10.253885, "o", "he"] +[10.344022, "o", "s/"] +[10.434282, "o", "c"] +[10.614515, "o", "pp"] +[10.704836, "o", "ut"] +[10.794955, "o", "es"] +[10.88508, "o", "t."] +[10.975212, "o", "p"] +[11.06535, "o", "at"] +[11.155495, "o", "ch"] +[11.245598, "o", "\u001b["] +[11.335724, "o", "0m"] +[12.337404, "o", "\r\n"] +[12.341146, "o", "diff --git a/README.md b/README.md\r\nindex 2655a7b..fc6084e 100644\r\n--- a/README.md\r\n+++ b/README.md\r\n@@ -3,7 +3,7 @@ CppUTest\r\n \r\n CppUTest unit testing and mocking framework for C/C++\r\n \r\n-[More information on the project page](http://cpputest.github.com)\r\n+[More information on the project page](http://cpputest.gitlab.com)\r\n \r\n [![Build Status](https://travis-ci.org/cpputest/cpputest.png?branch=master)](https://travis-ci.org/cpputest/cpputest)\r\n \r\n"] +[12.346899, "o", "$ "] +[13.349948, "o", "\u001b["] +[13.530223, "o", "1m"] +[13.62035, "o", "gi"] +[13.710483, "o", "t "] +[13.800688, "o", "sta"] +[13.89081, "o", "tu"] +[13.980933, "o", "s\u001b"] +[14.071157, "o", "[0"] +[14.161445, "o", "m"] +[15.163365, "o", "\r\n"] +[15.169465, "o", "On branch main\r\nnothing to commit, working tree clean\r\n"] +[15.175456, "o", "$ "] +[16.178158, "o", "\u001b["] +[16.35844, "o", "1m"] +[16.44858, "o", "se"] +[16.5387, "o", "d "] +[16.628949, "o", "-i"] +[16.719068, "o", " '"] +[16.809223, "o", "s/"] +[16.899362, "o", "gi"] +[16.98948, "o", "tl"] +[17.079604, "o", "ab"] +[17.259949, "o", "/g"] +[17.350071, "o", "it"] +[17.440204, "o", "ea"] +[17.530355, "o", "/g"] +[17.620465, "o", "' "] +[17.710743, "o", "cp"] +[17.803253, "o", "pu"] +[17.893412, "o", "te"] +[17.983534, "o", "st"] +[18.163891, "o", "/s"] +[18.254024, "o", "rc"] +[18.344155, "o", "/R"] +[18.434274, "o", "EA"] +[18.524399, "o", "DM"] +[18.61452, "o", "E."] +[18.70477, "o", "md"] +[18.794886, "o", "\u001b["] +[18.885006, "o", "0m"] +[19.888219, "o", "\r\n"] +[19.901796, "o", "$ "] +[20.905307, "o", "\u001b"] +[21.085562, "o", "[1"] +[21.175803, "o", "mg"] +[21.26585, "o", "it"] +[21.355979, "o", " "] +[21.446108, "o", "ad"] +[21.536237, "o", "d "] +[21.626409, "o", ".\u001b"] +[21.716717, "o", "[0"] +[21.807078, "o", "m"] +[22.808659, "o", "\r\n"] +[22.820686, "o", "$ "] +[23.824074, "o", "\u001b"] +[24.004417, "o", "[1"] +[24.094409, "o", "mg"] +[24.184533, "o", "it"] +[24.274668, "o", " "] +[24.364872, "o", "co"] +[24.455001, "o", "mm"] +[24.545129, "o", "it"] +[24.635284, "o", " -"] +[24.725406, "o", "a"] +[24.905659, "o", " -"] +[24.995785, "o", "m "] +[25.08593, "o", "'F"] +[25.176057, "o", "ix"] +[25.26627, "o", " "] +[25.356347, "o", "vc"] +[25.446469, "o", "s "] +[25.536593, "o", "ho"] +[25.62673, "o", "st"] +[25.807062, "o", "'"] +[25.897214, "o", "\u001b["] +[25.987312, "o", "0m"] +[26.988857, "o", "\r\n"] +[26.999009, "o", "[main 76c5f8d] Fix vcs host\r\n 1 file changed, 1 insertion(+), 1 deletion(-)\r\n"] +[27.004326, "o", "$ "] +[28.00725, "o", "\u001b["] +[28.187527, "o", "1m"] +[28.277665, "o", "df"] +[28.367777, "o", "et"] +[28.457917, "o", "ch "] +[28.548052, "o", "up"] +[28.638234, "o", "da"] +[28.728382, "o", "te"] +[28.818557, "o", "-p"] +[28.908727, "o", "atc"] +[29.088961, "o", "h "] +[29.1791, "o", "cp"] +[29.269257, "o", "pu"] +[29.359351, "o", "te"] +[29.449483, "o", "st\u001b"] +[29.539618, "o", "[0"] +[29.629741, "o", "m"] +[30.631287, "o", "\r\n"] +[31.090644, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[31.130398, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] +[31.131206, "o", "\u001b[?25l"] +[31.211896, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[31.292532, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[31.373102, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[31.453685, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[31.534261, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[31.61483, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[31.695412, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[31.775975, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[31.856717, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[31.937274, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.019424, "o", "\r\u001b[2K\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.098722, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.180447, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.26406, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.344759, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.426109, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.506086, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.586674, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.602969, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.603109, "o", "\r\n"] +[32.603255, "o", "\u001b[?25h\r\u001b[1A\u001b[2K"] +[32.604372, "o", " \u001b[1;34m> Fetched v3.4\u001b[0m"] +[32.604484, "o", "\r\n"] +[32.69484, "o", " \u001b[1;34m> Updating patch \"patches/cpputest.patch\"\u001b[0m\r\n"] +[32.713609, "o", "\u001b[?25l"] +[32.793872, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.87444, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[32.954968, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[33.035558, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[33.116093, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[33.196854, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[33.277426, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[33.358951, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[33.440609, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[33.521359, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[33.552934, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] +[33.553743, "o", " \u001b[1;34m> Fetched v3.4\u001b[0m\r\n"] +[33.5545, "o", " \u001b[1;34m> Applying patch \"patches/cpputest.patch\"\u001b[0m\r\n"] +[33.557634, "o", " \u001b[34msuccessfully patched 1/1: \u001b[0m\u001b[34m \u001b[0m \r\n\u001b[34mb'README.md'\u001b[0m \r\n"] +[33.646855, "o", "$ "] +[34.650021, "o", "\u001b"] +[34.830395, "o", "[1"] +[34.920518, "o", "mc"] +[35.010668, "o", "at"] +[35.10079, "o", " p"] +[35.190926, "o", "at"] +[35.281047, "o", "ch"] +[35.371224, "o", "es"] +[35.461332, "o", "/c"] +[35.551454, "o", "pp"] +[35.731702, "o", "u"] +[35.822028, "o", "te"] +[35.91216, "o", "st"] +[36.002275, "o", ".p"] +[36.092428, "o", "at"] +[36.182564, "o", "ch"] +[36.27287, "o", "\u001b["] +[36.363055, "o", "0m"] +[37.366466, "o", "\r\n"] +[37.369565, "o", "diff --git a/README.md b/README.md\r\nindex 2655a7b..da133cb 100644\r\n--- a/README.md\r\n+++ b/README.md\r\n@@ -3,7 +3,7 @@ CppUTest\r\n \r\n CppUTest unit testing and mocking framework for C/C++\r\n \r\n-[More information on the project page](http://cpputest.github.com)\r\n+[More information on the project page](http://cpputest.gitea.com)\r\n \r\n [![Build Status](https://travis-ci.org/cpputest/cpputest.png?branch=master)](https://travis-ci.org/cpputest/cpputest)\r\n \r\n"] +[37.374416, "o", "$ "] +[38.378066, "o", "\u001b"] +[38.560684, "o", "[1"] +[38.650836, "o", "mg"] +[38.74096, "o", "it"] +[38.8311, "o", " s"] +[38.921239, "o", "ta"] +[39.011385, "o", "tu"] +[39.101524, "o", "s\u001b"] +[39.191648, "o", "[0"] +[39.281771, "o", "m"] +[40.283426, "o", "\r\n"] +[40.312246, "o", "On branch main\r\nChanges not staged for commit:\r\n (use \"git add ...\" to update what will be committed)\r\n (use \"git restore ...\" to discard changes in working directory)\r\n\t\u001b[31mmodified: cpputest/src/.dfetch_data.yaml\u001b[m\r\n\t\u001b[31mmodified: patches/cpputest.patch\u001b[m\r\n\r\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\r\n"] +[43.319943, "o", "$ "] +[43.321855, "o", "\u001b"] +[43.502343, "o", "[1"] +[43.592566, "o", "m\u001b"] +[43.682698, "o", "[0"] +[43.77282, "o", "m"] +[43.773469, "o", "\r\n"] +[43.776329, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/update.cast b/doc/asciicasts/update.cast index 7eda8e96..d8cdc247 100644 --- a/doc/asciicasts/update.cast +++ b/doc/asciicasts/update.cast @@ -1,106 +1,121 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247146, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.645115, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.649085, "o", "$ "] -[1.652356, "o", "\u001b["] -[1.832675, "o", "1m"] -[1.922737, "o", "ls"] -[2.012907, "o", " -"] -[2.103024, "o", "l\u001b"] -[2.193155, "o", "[0"] -[2.283979, "o", "m"] -[3.284743, "o", "\r\n"] -[3.289257, "o", "total 4"] -[3.289298, "o", "\r\n"] -[3.289376, "o", "-rw-rw-rw- 1 dev dev 733 Mar 23 06:25 dfetch.yaml"] -[3.289392, "o", "\r\n"] -[3.313916, "o", "$ "] -[4.318926, "o", "\u001b["] -[4.498117, "o", "1m"] -[4.58831, "o", "ca"] -[4.678426, "o", "t "] -[4.768581, "o", "dfe"] -[4.858734, "o", "tc"] -[4.948853, "o", "h."] -[5.038993, "o", "ya"] -[5.12916, "o", "ml"] -[5.219305, "o", "\u001b[0"] -[5.401373, "o", "m"] -[6.402465, "o", "\r\n"] -[6.405694, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] -[6.410677, "o", "$ "] -[7.414034, "o", "\u001b"] -[7.594297, "o", "[1"] -[7.684435, "o", "md"] -[7.774577, "o", "fe"] -[7.864706, "o", "t"] -[7.954851, "o", "ch"] -[8.045005, "o", " u"] -[8.135196, "o", "pd"] -[8.225336, "o", "at"] -[8.315478, "o", "e"] -[8.495705, "o", "\u001b["] -[8.585841, "o", "0m"] -[9.587454, "o", "\r\n"] -[10.055519, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[10.069455, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] -[10.069588, "o", "\u001b[?25l"] -[10.155491, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[10.236126, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[10.316767, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[10.397341, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[10.478158, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[10.558675, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[10.639485, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[10.72157, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[10.802469, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[10.883069, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[10.96363, "o", "\r\u001b[2K\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[11.044244, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] -[11.122267, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[11.123193, "o", " \u001b[1;34m> Fetched v3.4\u001b[0m\r\n"] -[11.148347, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] -[11.148748, "o", "\u001b[?25l"] -[11.229774, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[11.310372, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[11.390937, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[11.471595, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[11.552195, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[11.633698, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[11.714266, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[11.794833, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[11.875955, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[11.956671, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[12.03725, "o", "\r\u001b[2K\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[12.117826, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[12.198699, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] -[12.208625, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching \u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] -[12.209406, "o", " \u001b[1;34m> Fetched master - 25647e692c7906b96ffd2b05ca54c097948e879c\u001b[0m\r\n"] -[12.289005, "o", "$ "] -[13.292261, "o", "\u001b["] -[13.47406, "o", "1m"] -[13.564182, "o", "ls"] -[13.654301, "o", " -"] -[13.744587, "o", "l\u001b["] -[13.834631, "o", "0m"] -[14.836654, "o", "\r\n"] -[14.840351, "o", "total 12\r\ndrwxrwxrwx+ 3 dev dev 4096 Mar 23 06:25 cpputest\r\n-rw-rw-rw- 1 dev dev 733 Mar 23 06:25 dfetch.yaml\r\n"] -[14.841518, "o", "drwxrwxrwx+ 4 dev dev 4096 Mar 23 06:25 jsmn\r\n"] -[14.850112, "o", "$ "] -[15.853294, "o", "\u001b["] -[16.035276, "o", "1m"] -[16.125329, "o", "df"] -[16.21547, "o", "et"] -[16.305751, "o", "ch"] -[16.395803, "o", " u"] -[16.485947, "o", "pd"] -[16.576075, "o", "at"] -[16.66621, "o", "e\u001b"] -[16.756337, "o", "[0"] -[16.93659, "o", "m"] -[17.938264, "o", "\r\n"] -[18.392468, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[18.411772, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] -[18.412474, "o", " \u001b[1;34m> up-to-date (v3.4)\u001b[0m\r\n"] -[18.970087, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] -[18.970777, "o", " \u001b[1;34m> up-to-date (master - 25647e692c7906b96ffd2b05ca54c097948e879c)\u001b[0m\r\n"] -[22.034113, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774833, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.554078, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.557538, "o", "$ "] +[1.560759, "o", "\u001b["] +[1.741055, "o", "1m"] +[1.831172, "o", "ls"] +[1.921317, "o", " -"] +[2.011452, "o", "l\u001b"] +[2.101571, "o", "[0"] +[2.192299, "o", "m"] +[3.193504, "o", "\r\n"] +[3.197332, "o", "total 4\r\n-rw-rw-rw- 1 dev dev 733 Mar 29 09:00 dfetch.yaml"] +[3.197736, "o", "\r\n"] +[3.206448, "o", "$ "] +[4.209017, "o", "\u001b"] +[4.389333, "o", "[1"] +[4.479458, "o", "mc"] +[4.56958, "o", "at"] +[4.659715, "o", " d"] +[4.749857, "o", "fe"] +[4.839978, "o", "tc"] +[4.930106, "o", "h."] +[5.020233, "o", "ya"] +[5.11047, "o", "ml"] +[5.290733, "o", "\u001b"] +[5.380868, "o", "[0"] +[5.471423, "o", "m"] +[6.472484, "o", "\r\n"] +[6.475594, "o", "manifest:\r\n version: 0.0 # DFetch Module syntax version\r\n\r\n remotes: # declare common sources in one place\r\n - name: github\r\n url-base: https://github.com/\r\n\r\n projects:\r\n - name: cpputest\r\n dst: cpputest/src/ # Destination of this project (relative to this file)\r\n repo-path: cpputest/cpputest.git # Use default github remote\r\n tag: v3.4 # tag\r\n\r\n - name: jsmn # without destination, defaults to project name\r\n repo-path: zserge/jsmn.git # only repo-path is enough\r\n"] +[6.480214, "o", "$ "] +[7.483852, "o", "\u001b["] +[7.66394, "o", "1m"] +[7.754033, "o", "df"] +[7.844197, "o", "et"] +[7.934333, "o", "ch"] +[8.02447, "o", " u"] +[8.114667, "o", "pd"] +[8.2048, "o", "at"] +[8.294939, "o", "e\u001b"] +[8.385074, "o", "[0"] +[8.565454, "o", "m"] +[9.567222, "o", "\r\n"] +[10.002609, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[10.016352, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] +[10.016703, "o", "\u001b[?25l"] +[10.09763, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.178237, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.259016, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.339575, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.420167, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.500731, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.581306, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.661871, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.74375, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.824315, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.905423, "o", "\r\u001b[2K\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[10.987213, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[11.067567, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[11.148152, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[11.22871, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[11.310258, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[11.390396, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[11.470625, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m"] +[11.521172, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching v3.4\u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] +[11.521838, "o", " \u001b[1;34m> Fetched v3.4\u001b[0m\r\n"] +[11.564396, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] +[11.564746, "o", "\u001b[?25l"] +[11.646333, "o", "\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[11.727044, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[11.807586, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[11.888169, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[11.969091, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.052086, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.13039, "o", "\r\u001b[2K\u001b[32m⠦\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.211428, "o", "\r\u001b[2K\u001b[32m⠧\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.292023, "o", "\r\u001b[2K\u001b[32m⠇\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.372575, "o", "\r\u001b[2K\u001b[32m⠏\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.453149, "o", "\r\u001b[2K\u001b[32m⠋\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.534467, "o", "\r\u001b[2K\u001b[32m⠙\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.615589, "o", "\r\u001b[2K\u001b[32m⠹\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.696111, "o", "\r\u001b[2K\u001b[32m⠸\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.776664, "o", "\r\u001b[2K\u001b[32m⠼\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.857247, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching \u001b[0m"] +[12.917367, "o", "\r\u001b[2K\u001b[32m⠴\u001b[0m \u001b[1;94m> Fetching \u001b[0m\r\n\u001b[?25h\r\u001b[1A\u001b[2K"] +[12.918174, "o", " \u001b[1;34m> Fetched master - 25647e692c7906b96ffd2b05ca54c097948e879c\u001b[0m\r\n"] +[12.98567, "o", "$ "] +[13.988661, "o", "\u001b"] +[14.168939, "o", "[1"] +[14.259164, "o", "ml"] +[14.34919, "o", "s "] +[14.439332, "o", "-"] +[14.529445, "o", "l\u001b"] +[14.619592, "o", "[0"] +[14.710135, "o", "m"] +[15.713043, "o", "\r\n"] +[15.721899, "o", "total 12"] +[15.722159, "o", "\r\n"] +[15.723309, "o", "drwxrwxrwx+ 3 dev dev 4096 Mar 29 09:00 cpputest"] +[15.72354, "o", "\r\n"] +[15.723707, "o", "-rw-rw-rw- 1 dev dev 733 Mar 29 09:00 dfetch.yaml\r\ndrwxrwxrwx+ 4 dev dev 4096 Mar 29 09:00 jsmn\r\n"] +[15.741241, "o", "$ "] +[16.746393, "o", "\u001b"] +[16.926855, "o", "[1"] +[17.016987, "o", "md"] +[17.107896, "o", "fe"] +[17.197224, "o", "tc"] +[17.287367, "o", "h "] +[17.377493, "o", "up"] +[17.467623, "o", "da"] +[17.557742, "o", "te"] +[17.647931, "o", "\u001b["] +[17.828288, "o", "0"] +[17.918431, "o", "m"] +[18.920037, "o", "\r\n"] +[19.350193, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[19.365799, "o", " \u001b[1;92mcpputest:\u001b[0m\r\n"] +[19.366342, "o", " \u001b[1;34m> up-to-date (v3.4)\u001b[0m\r\n"] +[20.018322, "o", " \u001b[1;92mjsmn:\u001b[0m\r\n"] +[20.018893, "o", " \u001b[1;34m> up-to-date (master - 25647e692c7906b96ffd2b05ca54c097948e879c)\u001b[0m"] +[20.019201, "o", "\r\n"] +[23.076645, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/asciicasts/validate.cast b/doc/asciicasts/validate.cast index dc1eda58..bf775e6c 100644 --- a/doc/asciicasts/validate.cast +++ b/doc/asciicasts/validate.cast @@ -1,19 +1,20 @@ -{"version": 2, "width": 165, "height": 30, "timestamp": 1774247103, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} -[0.544564, "o", "\u001b[H\u001b[2J\u001b[3J"] -[0.548222, "o", "$ "] -[1.551241, "o", "\u001b["] -[1.732758, "o", "1m"] -[1.821751, "o", "df"] -[1.911919, "o", "et"] -[2.002037, "o", "ch"] -[2.094464, "o", " v"] -[2.182521, "o", "al"] -[2.272672, "o", "id"] -[2.362794, "o", "at"] -[2.452919, "o", "e\u001b"] -[2.633569, "o", "[0"] -[2.725691, "o", "m"] -[3.726097, "o", "\r\n"] -[4.179101, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] -[4.185554, "o", " \u001b[1;92mdfetch.yaml :\u001b[0m\u001b[1;34m valid\u001b[0m\r\n"] -[7.24934, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] +{"version": 2, "width": 111, "height": 25, "timestamp": 1774774790, "env": {"SHELL": "/bin/sh", "TERM": "xterm-256color"}} +[0.545765, "o", "\u001b[H\u001b[2J\u001b[3J"] +[0.549334, "o", "$ "] +[1.552512, "o", "\u001b"] +[1.732747, "o", "[1"] +[1.822869, "o", "md"] +[1.913007, "o", "fe"] +[2.00315, "o", "t"] +[2.093254, "o", "ch"] +[2.183396, "o", " v"] +[2.273557, "o", "al"] +[2.363664, "o", "id"] +[2.453805, "o", "a"] +[2.634142, "o", "te"] +[2.724301, "o", "\u001b["] +[2.814418, "o", "0m"] +[3.815961, "o", "\r\n"] +[4.298398, "o", "\u001b[1;34mDfetch (0.12.1)\u001b[0m\r\n"] +[4.305204, "o", " \u001b[1;92mdfetch.yaml :\u001b[0m\u001b[1;34m valid\u001b[0m\r\n"] +[7.386545, "o", "/workspaces/dfetch/doc/generate-casts\r\n"] diff --git a/doc/generate-casts/add-demo.sh b/doc/generate-casts/add-demo.sh new file mode 100755 index 00000000..781648cc --- /dev/null +++ b/doc/generate-casts/add-demo.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +source "$(dirname "${BASH_SOURCE[0]}")/demo-magic/demo-magic.sh" + +PROMPT_TIMEOUT=1 + +# Copy example manifest +mkdir add +pushd add || { echo 'pushd failed' >&2; exit 1; } +dfetch init +clear + +# Run the command +pe "cat dfetch.yaml" +pe "dfetch add https://github.com/dfetch-org/dfetch.git" +pe "cat dfetch.yaml" + +PROMPT_TIMEOUT=3 +wait + +pei "" + +popd || { echo 'popd failed' >&2; exit 1; } +rm -rf add diff --git a/doc/generate-casts/generate-casts.sh b/doc/generate-casts/generate-casts.sh index 1d2d615b..e3eb28f3 100755 --- a/doc/generate-casts/generate-casts.sh +++ b/doc/generate-casts/generate-casts.sh @@ -11,8 +11,10 @@ export TZ=UTC rm -rf ../asciicasts/* +asciinema rec --overwrite -c "./interactive-add-demo.sh" ../asciicasts/interactive-add.cast asciinema rec --overwrite -c "./basic-demo.sh" ../asciicasts/basic.cast asciinema rec --overwrite -c "./init-demo.sh" ../asciicasts/init.cast +asciinema rec --overwrite -c "./add-demo.sh" ../asciicasts/add.cast asciinema rec --overwrite -c "./environment-demo.sh" ../asciicasts/environment.cast asciinema rec --overwrite -c "./validate-demo.sh" ../asciicasts/validate.cast asciinema rec --overwrite -c "./check-demo.sh" ../asciicasts/check.cast diff --git a/doc/generate-casts/interactive-add-demo.sh b/doc/generate-casts/interactive-add-demo.sh new file mode 100755 index 00000000..5a30266a --- /dev/null +++ b/doc/generate-casts/interactive-add-demo.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Demo of dfetch add -i (interactive wizard mode). +# +# Uses the real cpputest repository so the viewer sees dfetch fetching live +# branch/tag metadata and the wizard populating from it. + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$DIR/demo-magic/demo-magic.sh" + +PROMPT_TIMEOUT=1 + +mkdir interactive-add +trap 'popd 2>/dev/null; rm -rf interactive-add' EXIT +pushd interactive-add || { echo 'pushd failed' >&2; exit 1; } + +# Start with a manifest that already has one dependency so the demo shows +# adding to an existing project rather than starting from scratch. +cat > dfetch.yaml << 'MANIFEST' +manifest: + version: '0.0' + projects: + - name: jsmn + url: https://github.com/zserge/jsmn.git + branch: master +MANIFEST + +clear + +pe "cat dfetch.yaml" + +p "dfetch add -i https://github.com/cpputest/cpputest.git" +python3 ../interactive_add_helper.py https://github.com/cpputest/cpputest.git + +pe "cat dfetch.yaml" + +PROMPT_TIMEOUT=3 +wait + +pei "" + +popd || { echo 'popd failed' >&2; exit 1; } +rm -rf interactive-add diff --git a/doc/generate-casts/interactive_add_helper.py b/doc/generate-casts/interactive_add_helper.py new file mode 100644 index 00000000..fe2344e6 --- /dev/null +++ b/doc/generate-casts/interactive_add_helper.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Drive ``dfetch add -i`` with simulated typing for asciinema recordings. + +Usage:: + + python3 interactive-add-helper.py + +Every Rich ``Prompt.ask`` / ``Confirm.ask`` call is intercepted: + +1. The prompt markup is rendered to the terminal exactly as dfetch would. +2. After a short "thinking" pause each answer character is written to stdout + one at a time, mimicking natural typing speed. +3. The answer is returned to dfetch as if the user had pressed Enter. + +``is_tty`` is forced to ``False`` so that dfetch uses the text-based +fallbacks (numbered version list, plain src/ignore prompts) rather than +the raw-terminal tree browser – the text fallback looks better on a cast. + +Answers +------- +``None`` in a prompt-answer slot means "accept the default" – the default +value is typed out so the viewer can read what was chosen. An explicit +string overrides the default. +""" + +from __future__ import annotations + +import sys +import time +from collections import deque +from unittest.mock import patch + +from rich.console import Console + +# --------------------------------------------------------------------------- +# Wizard answers – customise these to change what the demo shows +# --------------------------------------------------------------------------- +_PROMPT_ANSWERS: deque[str | None] = deque( + [ + None, # Name – accept the default (derived from URL) + "ext/cpputest", # Destination – show the common ext/ convention + None, # Version – accept the default branch + None, # Source path – press Enter to fetch the whole repo + None, # Ignore paths – press Enter to skip + ] +) +_CONFIRM_ANSWERS: deque[bool] = deque( + [ + True, # "Add project to manifest?" → yes + False, # "Run 'dfetch update' now?" → no + ] +) + +# --------------------------------------------------------------------------- +# Timing (seconds) – tweak for faster/slower recording +# --------------------------------------------------------------------------- +_PRE_DELAY = 0.55 # pause before starting to type (user "thinking") +_CHAR_DELAY = 0.06 # delay between consecutive characters + +# --------------------------------------------------------------------------- +# Internal helpers +# --------------------------------------------------------------------------- +_console = Console(force_terminal=True) + + +def _type_out(text: str) -> None: + """Write *text* to stdout one character at a time, then a newline.""" + time.sleep(_PRE_DELAY) + for ch in text: + sys.stdout.write(ch) + sys.stdout.flush() + time.sleep(_CHAR_DELAY) + sys.stdout.write("\n") + sys.stdout.flush() + + +# --------------------------------------------------------------------------- +# Prompt replacements +# --------------------------------------------------------------------------- + + +def _fake_prompt_ask(prompt_markup: str, *, default: str = "", **_kw: object) -> str: + """Render the Rich-markup prompt, then simulate typing the next answer. + + ``None`` in the queue means "accept the default" – the default is + typed out (visible to the viewer) rather than silently accepted. + """ + suffix = f" [{default}]" if default else "" + _console.print(f"{prompt_markup}{suffix}: ", end="") + + raw = ( + _PROMPT_ANSWERS.popleft() if _PROMPT_ANSWERS else None + ) # IndexError if queue is exhausted + answer = raw if raw is not None else default + _type_out(answer) + return answer + + +def _fake_confirm_ask( + prompt_markup: str, *, default: bool = True, **_kw: object +) -> bool: + """Render the confirm prompt, then simulate typing y or n.""" + yn_hint = "y" if default else "n" + _console.print(f"{prompt_markup} [y/n] ({yn_hint}): ", end="") + + val = ( + _CONFIRM_ANSWERS.popleft() if _CONFIRM_ANSWERS else default + ) # IndexError if queue is exhausted + _type_out("y" if val else "n") + return val + + +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- + +if __name__ == "__main__": + if len(sys.argv) < 2: + sys.exit("Usage: interactive-add-helper.py ") + + url = sys.argv[1] + + # Force text-mode prompts so dfetch uses the numbered list + plain prompts + # instead of the raw-TTY tree browser. + import dfetch.terminal.keys as _keys + + _keys.is_tty = lambda: False # type: ignore[assignment] + + with patch("rich.prompt.Prompt.ask", side_effect=_fake_prompt_ask): + with patch("rich.prompt.Confirm.ask", side_effect=_fake_confirm_ask): + from dfetch.__main__ import run + + run(["add", "--interactive", url], _console) diff --git a/doc/manual.rst b/doc/manual.rst index 46a80ecc..0f5b3c48 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -173,6 +173,18 @@ Validate .. automodule:: dfetch.commands.validate +Add +--- +.. argparse:: + :module: dfetch.__main__ + :func: create_parser + :prog: dfetch + :path: add + +.. asciinema:: asciicasts/add.cast + +.. automodule:: dfetch.commands.add + CLI Cheatsheet -------------- @@ -187,6 +199,18 @@ Also called vendoring. More info: ` + + or non-interactively (auto-accept defaults, skip confirmation): + + .. code-block:: console + + dfetch add + - Generate a manifest from existing git submodules or svn externals: .. code-block:: console diff --git a/features/add-project-through-cli.feature b/features/add-project-through-cli.feature new file mode 100644 index 00000000..8c462852 --- /dev/null +++ b/features/add-project-through-cli.feature @@ -0,0 +1,296 @@ +Feature: Add a project to the manifest via the CLI + + *DFetch* can add a new project entry to the manifest without requiring + manual YAML editing. ``dfetch add `` inspects the remote repository, + fills in sensible defaults (name, destination, default branch), shows a + preview, and appends the entry to ``dfetch.yaml`` after confirmation. + + Pass ``--interactive`` / ``-i`` to be guided step-by-step through every + manifest field (name, destination, branch/tag/revision, optional src, + optional ignore list). + + Use ``--name``, ``--dst``, ``--version``, ``--src``, ``--ignore`` to + pre-fill individual fields (works with and without ``-i``). + + Background: + Given a git repository "MyLib.git" + + Scenario: Adding a project appends it to the manifest + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: ext/existing + url: some-remote-server/existing.git + """ + When I run "dfetch add some-remote-server/MyLib.git" + Then the manifest 'dfetch.yaml' contains entry + """ + - name: MyLib + url: some-remote-server/MyLib.git + branch: master + dst: ext/MyLib + """ + + Scenario: Duplicate project name is auto-renamed in non-interactive mode + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: MyLib + url: some-remote-server/MyLib.git + """ + When I run "dfetch add some-remote-server/MyLib.git" + Then the manifest 'dfetch.yaml' contains entry + """ + - name: MyLib-1 + url: some-remote-server/MyLib.git + branch: master + """ + + Scenario: Destination is guessed from common prefix of existing projects + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: ext/lib-a + url: some-remote-server/lib-a.git + - name: ext/lib-b + url: some-remote-server/lib-b.git + """ + When I run "dfetch add some-remote-server/MyLib.git" + Then the manifest 'dfetch.yaml' contains entry + """ + - name: MyLib + url: some-remote-server/MyLib.git + branch: master + dst: ext/MyLib + """ + + Scenario: Interactive add guides through each field + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: ext/existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | my-lib | + | Destination path | libs/my | + | Version | master | + | Source path | | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + branch: master + dst: libs/my + """ + + Scenario: Interactive add with tag version + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | my-lib | + | Destination path | my-lib | + | Version | v1 | + | Source path | | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + tag: v1 + """ + + Scenario: Interactive add with src subpath + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | my-lib | + | Destination path | my-lib | + | Version | master | + | Source path | docs/api | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + branch: master + src: docs/api + """ + + Scenario: Interactive add with ignore list + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | my-lib | + | Destination path | my-lib | + | Version | master | + | Source path | | + | Ignore paths | docs, tests | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + branch: master + ignore: + - docs + - tests + """ + + Scenario: Interactive add triggers immediate fetch when update is accepted + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | MyLib | + | Destination path | MyLib | + | Version | master | + | Source path | | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | y | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: MyLib + url: some-remote-server/MyLib.git + branch: master + """ + And 'MyLib/README.md' exists + + Scenario: Interactive add with abort does not modify manifest + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | MyLib | + | Destination path | MyLib | + | Version | master | + | Source path | | + | Ignore paths | | + | Add project to manifest? | n | + Then the manifest 'dfetch.yaml' is replaced with + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + + Scenario: Interactive add with empty src (repo root) does not add src field + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git" with inputs + | Question | Answer | + | Project name | MyLib | + | Destination path | MyLib | + | Version | master | + | Source path | | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: MyLib + url: some-remote-server/MyLib.git + branch: master + """ + And the manifest 'dfetch.yaml' does not contain 'src:' + + Scenario: Non-interactive add with field overrides + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: ext/existing + url: some-remote-server/existing.git + """ + When I run "dfetch add some-remote-server/MyLib.git --name my-lib --dst libs/my-lib" + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + branch: master + dst: libs/my-lib + """ + + Scenario: Interactive add with pre-filled fields skips those prompts + Given the manifest 'dfetch.yaml' + """ + manifest: + version: '0.0' + projects: + - name: existing + url: some-remote-server/existing.git + """ + When I run "dfetch add -i some-remote-server/MyLib.git --name my-lib --dst libs/my" with inputs + | Question | Answer | + | Version | master | + | Source path | | + | Ignore paths | | + | Add project to manifest? | y | + | Run update | n | + Then the manifest 'dfetch.yaml' contains entry + """ + - name: my-lib + url: some-remote-server/MyLib.git + branch: master + dst: libs/my + """ diff --git a/features/steps/add_steps.py b/features/steps/add_steps.py new file mode 100644 index 00000000..ba17eca0 --- /dev/null +++ b/features/steps/add_steps.py @@ -0,0 +1,107 @@ +"""Steps for the 'dfetch add' feature tests.""" + +# pylint: disable=missing-function-docstring, import-error, not-callable +# pyright: reportAttributeAccessIssue=false, reportCallIssue=false + +from collections import deque +from unittest.mock import patch + +from behave import then, when # pylint: disable=no-name-in-module + +from features.steps.generic_steps import call_command, remote_server_path +from features.steps.manifest_steps import apply_manifest_substitutions + + +def _resolve_url(url: str, context) -> str: + """Replace 'some-remote-server' with the actual temp file:// URL.""" + return url.replace("some-remote-server", f"file:///{remote_server_path(context)}") + + +def _run_interactive_add(context, cmd: list[str]) -> None: + """Run an interactive add command, driving prompts from ``context.table``.""" + # Parse the answer table into three buckets: + # • "Add project to manifest?" → add_confirm (bool) + # • "Run" + "update" in prompt → update_confirm (bool, default False) + # • anything else → Prompt.ask answers (in order) + add_confirm = True + update_confirm = False + prompt_answers: deque[str] = deque() + + for row in context.table: + question = row["Question"] + answer = row["Answer"] + if "Add project to manifest" in question: + add_confirm = answer.lower() not in ("n", "no", "false") + elif "update" in question.lower() and "run" in question.lower(): + update_confirm = answer.lower() not in ("n", "no", "false") + else: + prompt_answers.append(answer) + + # Two sequential Confirm calls: first "add?", then (if added) "update?". + # We use an iterator so each call consumes the next value. + _confirm_values = iter([add_confirm, update_confirm]) + + def _auto_confirm(_prompt: str, **kwargs) -> bool: + try: + return next(_confirm_values) + except StopIteration: + return bool(kwargs.get("default", False)) + + def _auto_prompt(_prompt: str, **kwargs) -> str: # type: ignore[return] + """Return the next pre-defined answer, ignoring the actual prompt text.""" + if prompt_answers: + return prompt_answers.popleft() + return str(kwargs.get("default", "")) + + with patch("dfetch.commands.add.Prompt.ask", side_effect=_auto_prompt): + with patch("dfetch.commands.add.Confirm.ask", side_effect=_auto_confirm): + call_command(context, cmd) + + +@when('I run "dfetch {add_args}" with inputs') +def step_interactive_add(context, add_args): + resolved = add_args.replace( + "some-remote-server", f"file:///{remote_server_path(context)}" + ) + _run_interactive_add(context, resolved.split()) + + +@then("the manifest '{name}' contains entry") +def step_manifest_contains_entry(context, name): + expected = apply_manifest_substitutions(context, context.text) + with open(name, "r", encoding="utf-8") as fh: + actual = fh.read() + + # Check that every line of the expected snippet is present somewhere in + # the manifest (order-insensitive substring check per line). + missing = [] + for line in expected.splitlines(): + stripped = line.strip() + if stripped and stripped not in actual: + missing.append(line) + + if missing: + print("Actual manifest:") + print(actual) + assert not missing, "Expected lines not found in manifest:\n" + "\n".join( + missing + ) + + +@then('the command fails with "{message}"') +def step_command_fails_with(context, message): + assert context.cmd_returncode != 0, "Expected command to fail, but it succeeded" + assert ( + message in context.cmd_output + ), f"Expected error message '{message}' not found in output:\n{context.cmd_output}" + + +@then("the manifest '{name}' does not contain '{text}'") +def step_manifest_not_contain(_, name, text): + with open(name, "r", encoding="utf-8") as fh: + actual = fh.read() + + if text in actual: + print("Actual manifest:") + print(actual) + assert False, f"Expected text '{text}' should not be in manifest" diff --git a/features/steps/generic_steps.py b/features/steps/generic_steps.py index dfdadda1..65902072 100644 --- a/features/steps/generic_steps.py +++ b/features/steps/generic_steps.py @@ -365,7 +365,10 @@ def step_impl(context, path=None): @when('I run "dfetch {args}"') def step_impl(context, args, path=None): """Call a command.""" - call_command(context, args.split(), path) + resolved = args.replace( + "some-remote-server", f"file:///{remote_server_path(context)}" + ) + call_command(context, resolved.split(), path) @given('"{path}" in {directory} is changed locally') diff --git a/pyproject.toml b/pyproject.toml index 0f8cca36..f5a2ebee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -237,9 +237,9 @@ root_packages = ["dfetch"] # ↓ # dfetch.project (core domain — knows about manifest & vcs) # ↓ -# dfetch.manifest | dfetch.vcs (independent lower-level modules) +# dfetch.manifest | dfetch.vcs (independent domain services) # ↓ -# dfetch.util | dfetch.log (foundational utilities) +# dfetch.util | dfetch.log | dfetch.terminal (foundational — siblings cannot import each other) [[tool.importlinter.contracts]] name = "C4 architecture layers" @@ -249,5 +249,5 @@ layers = [ "dfetch.reporting", "dfetch.project", "dfetch.manifest | dfetch.vcs", - "dfetch.util | dfetch.log", + "dfetch.util | dfetch.log | dfetch.terminal", ] diff --git a/script/package.py b/script/package.py index 8cec26ac..126a2db1 100644 --- a/script/package.py +++ b/script/package.py @@ -8,7 +8,7 @@ import xml.etree.ElementTree as ET # nosec (only used for XML generation, not parsing untrusted input) from pathlib import Path -from setuptools_scm import get_version +from setuptools_scm import get_version # pylint: disable=import-error from dfetch import __version__ as __digit_only_version__ # Used inside the installers diff --git a/tests/manifest_mock.py b/tests/manifest_mock.py index 7df1afea..a87e729a 100644 --- a/tests/manifest_mock.py +++ b/tests/manifest_mock.py @@ -20,4 +20,15 @@ def mock_manifest(projects, path: str = "/some/path") -> MagicMock: mocked_manifest = MagicMock(spec=Manifest, projects=project_mocks, path=path) mocked_manifest.selected_projects.return_value = project_mocks + + mocked_manifest.check_name_uniqueness.side_effect = lambda name: ( + Manifest.check_name_uniqueness(mocked_manifest, name) + ) + mocked_manifest.guess_destination.side_effect = lambda name: ( + Manifest.guess_destination(mocked_manifest, name) + ) + mocked_manifest.find_remote_for_url.side_effect = lambda url: ( + Manifest.find_remote_for_url(mocked_manifest, url) + ) + return mocked_manifest diff --git a/tests/test_add.py b/tests/test_add.py new file mode 100644 index 00000000..4b41b836 --- /dev/null +++ b/tests/test_add.py @@ -0,0 +1,898 @@ +"""Tests for the ``dfetch add`` command.""" + +# mypy: ignore-errors +# flake8: noqa + +import argparse +from pathlib import Path +from unittest.mock import MagicMock, Mock, call, patch + +import pytest + +from dfetch.commands.add import Add +from dfetch.manifest.manifest import Manifest +from dfetch.manifest.project import ProjectEntry, ProjectEntryDict +from dfetch.manifest.remote import Remote +from tests.manifest_mock import mock_manifest + +# --------------------------------------------------------------------------- +# Helper factories +# --------------------------------------------------------------------------- + + +def _make_project(name: str, destination: str = "") -> Mock: + p = Mock(spec=ProjectEntry) + p.name = name + p.destination = destination or name + return p + + +def _make_remote(name: str, url: str) -> Mock: + r = Mock(spec=Remote) + r.name = name + r.url = url + return r + + +def _make_args( + remote_url: str, + interactive: bool = False, + name: str | None = None, + dst: str | None = None, + version: str | None = None, + src: str | None = None, + ignore: list[str] | None = None, +) -> argparse.Namespace: + return argparse.Namespace( + remote_url=[remote_url], + interactive=interactive, + name=name, + dst=dst, + version=version, + src=src, + ignore=ignore, + ) + + +def _make_subproject( + default_branch: str = "main", + branches: list[str] | None = None, + tags: list[str] | None = None, +) -> Mock: + """Return a Mock SubProject with sensible defaults.""" + sp = Mock() + sp.get_default_branch.return_value = default_branch + sp.list_of_branches.return_value = ( + branches if branches is not None else [default_branch] + ) + sp.list_of_tags.return_value = tags if tags is not None else [] + # browse_tree returns an empty ls_fn by default (no remote tree available) + sp.browse_tree.return_value.__enter__ = Mock(return_value=lambda path="": []) + sp.browse_tree.return_value.__exit__ = Mock(return_value=False) + return sp + + +# --------------------------------------------------------------------------- +# Manifest.check_name_uniqueness +# --------------------------------------------------------------------------- + + +def test_check_name_uniqueness_raises_when_duplicate(): + m = Mock() + m.projects = [_make_project("foo"), _make_project("bar")] + with pytest.raises(ValueError, match="already exists"): + Manifest.check_name_uniqueness(m, "foo") + + +def test_check_name_uniqueness_passes_for_new_name(): + m = Mock() + m.projects = [_make_project("foo")] + Manifest.check_name_uniqueness(m, "bar") # should not raise + + +def test_check_name_uniqueness_passes_for_empty_manifest(): + m = Mock() + m.projects = [] + Manifest.check_name_uniqueness(m, "anything") + + +# --------------------------------------------------------------------------- +# Manifest.guess_destination +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + "project_name, existing, expected", + [ + # No existing projects → empty string + ("new", [], ""), + # Single existing project in a subdirectory → use parent dir + ("new", [("ext/a", "ext/a")], "ext/new"), + # Two projects under ext/ → ext/new + ("new", [("ext/a", "ext/a"), ("ext/b", "ext/b")], "ext/new"), + # Projects with no common prefix → empty string + ("new", [("a/x", "a/x"), ("b/y", "b/y")], ""), + ], +) +def test_guess_destination(project_name, existing, expected): + m = Mock() + m.projects = [_make_project(name, dst) for name, dst in existing] + assert Manifest.guess_destination(m, project_name) == expected + + +# --------------------------------------------------------------------------- +# Manifest.find_remote_for_url +# --------------------------------------------------------------------------- + + +def test_determine_remote_returns_matching_remote(): + m = Mock() + m.remotes = [ + _make_remote("github", "https://github.com/"), + _make_remote("gitlab", "https://gitlab.com/"), + ] + result = Manifest.find_remote_for_url(m, "https://github.com/myorg/myrepo.git") + assert result is not None + assert result.name == "github" + + +def test_determine_remote_returns_none_when_no_match(): + m = Mock() + m.remotes = [_make_remote("github", "https://github.com/")] + result = Manifest.find_remote_for_url(m, "https://bitbucket.org/myorg/myrepo.git") + assert result is None + + +def test_determine_remote_returns_none_for_empty_remotes(): + m = Mock() + m.remotes = [] + result = Manifest.find_remote_for_url(m, "https://github.com/myorg/myrepo.git") + assert result is None + + +# --------------------------------------------------------------------------- +# Add command – non-interactive +# --------------------------------------------------------------------------- + + +def test_add_command_non_interactive_appends_entry(): + """Non-interactive add appends the entry to the manifest without any prompts.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest( + [{"name": "ext/existing"}], path="/some/dfetch.yaml" + ) + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject("main", ["main"], ["v1.0"]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch("dfetch.commands.add.append_entry_manifest_file") as mock_append: + Add()(_make_args("https://github.com/org/myrepo.git")) + + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.name == "myrepo" + assert entry.branch == "main" + + +def test_add_command_non_interactive_field_overrides(): + """CLI field overrides are used directly in non-interactive mode.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject("main", ["main", "dev"], ["v1.0"]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch("dfetch.commands.add.append_entry_manifest_file") as mock_append: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + name="custom-name", + dst="ext/custom", + version="v1.0", + src="lib", + ignore=["docs", "tests"], + ) + ) + + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.name == "custom-name" + assert entry.destination == "ext/custom" + assert entry.tag == "v1.0" + assert entry.source == "lib" + assert entry.ignore == ["docs", "tests"] + + +def test_add_command_suffixes_duplicate_name(tmp_path): + """Non-interactive add with a clashing name must append a numbered suffix.""" + manifest_file = tmp_path / "dfetch.yaml" + manifest_file.write_text("") + + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest( + [{"name": "myrepo"}], path=str(manifest_file) + ) + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = tmp_path + + fake_subproject = _make_subproject() + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + Add()(_make_args("https://github.com/org/myrepo.git")) + + assert "myrepo-1" in manifest_file.read_text() + + +# --------------------------------------------------------------------------- +# Add command – interactive mode +# --------------------------------------------------------------------------- + + +def test_add_command_interactive_branch(): + """Interactive mode: typing a branch name appends entry with that branch.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject("main", ["main", "dev"], ["v1.0"]) + + # Prompts: name, dst, version, src, ignore (text fallback, non-TTY) + prompt_answers = iter(["myrepo", "libs/myrepo", "dev", "", ""]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(prompt_answers), + ): + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + interactive=True, + ) + ) + + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.name == "myrepo" + assert entry.branch == "dev" + assert entry.destination == "libs/myrepo" + + +def test_add_command_interactive_branch_by_number(): + """Interactive mode: picking a branch by number selects it correctly.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject("main", ["main", "dev"], []) + + # "2" selects the second option in the pick list (dev). + prompt_answers = iter(["myrepo", "myrepo", "2", "", ""]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(prompt_answers), + ): + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + interactive=True, + ) + ) + + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.branch == "dev" + + +def test_add_command_interactive_tag(): + """Interactive mode: typing a tag name appends entry with tag set.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject("main", ["main"], ["v1.0", "v2.0"]) + + # version prompt: type the tag name directly. + prompt_answers = iter(["myrepo", "myrepo", "v2.0", "", ""]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(prompt_answers), + ): + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + interactive=True, + ) + ) + + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.tag == "v2.0" + assert entry.branch == "" + + +def test_add_command_interactive_abort(): + """Interactive mode: declining confirmation does not append.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject() + + prompt_answers = iter(["myrepo", "myrepo", "main", "", ""]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(prompt_answers), + ): + with patch("dfetch.commands.add.Confirm.ask", return_value=False): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + interactive=True, + ) + ) + + mock_append.assert_not_called() + + +def test_add_command_interactive_with_src(): + """Interactive mode: providing a src path includes it in the entry.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject() + + # Prompts: name, dst, version, src, ignore + answers = iter(["myrepo", "myrepo", "main", "include/", ""]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(answers), + ): + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + interactive=True, + ) + ) + + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.source == "include/" + + +def test_add_command_interactive_with_ignore(): + """Interactive mode: providing ignore paths includes them in the entry.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject() + + # Prompts: name, dst, version, src (empty), ignore (comma-separated) + answers = iter(["myrepo", "myrepo", "main", "", "tests, docs"]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(answers), + ): + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + interactive=True, + ) + ) + + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + assert list(entry.ignore) == ["tests", "docs"] + + +def test_add_command_interactive_run_update(): + """Interactive mode: confirming update calls the Update command.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject() + + prompt_answers = iter(["myrepo", "myrepo", "main", "", ""]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(prompt_answers), + ): + with patch("dfetch.commands.add.Confirm.ask", side_effect=[True, True]): + with patch("dfetch.commands.add.append_entry_manifest_file"): + with patch( + "dfetch.commands.update.Update.__call__" + ) as mock_update: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + interactive=True, + ) + ) + + mock_update.assert_called_once() + + +# --------------------------------------------------------------------------- +# Add command – interactive mode with CLI overrides +# --------------------------------------------------------------------------- + + +def test_add_command_interactive_with_overrides(): + """CLI overrides skip their corresponding interactive prompts.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject("main", ["main", "dev"], ["v2.0"]) + + # Only src and ignore prompts should be reached; name, dst, version are overridden. + prompt_answers = iter(["", ""]) # src="", ignore="" + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(prompt_answers), + ): + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + interactive=True, + name="overridden-name", + dst="ext/overridden", + version="dev", + ) + ) + + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.name == "overridden-name" + assert entry.destination == "ext/overridden" + assert entry.branch == "dev" + + +def test_add_command_interactive_with_all_overrides(): + """All CLI overrides skip all interactive prompts.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject("main", ["main"], ["v2.0"]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + ) as mock_prompt: + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + interactive=True, + name="mylib", + dst="ext/mylib", + version="v2.0", + src="lib/core", + ignore=["docs"], + ) + ) + + mock_prompt.assert_not_called() + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.name == "mylib" + assert entry.destination == "ext/mylib" + assert entry.tag == "v2.0" + assert entry.source == "lib/core" + assert entry.ignore == ["docs"] + + +# --------------------------------------------------------------------------- +# Add command – interactive mode (SVN) +# --------------------------------------------------------------------------- + +_SVN_URL = "svn://example.com/myrepo" + + +def _make_svn_subproject( + branches: list[str] | None = None, + tags: list[str] | None = None, +) -> Mock: + """Return a Mock SVN SubProject with ``trunk`` as default branch.""" + all_branches = branches if branches is not None else ["trunk"] + return _make_subproject( + default_branch="trunk", + branches=all_branches, + tags=tags if tags is not None else [], + ) + + +def test_add_command_interactive_svn_trunk(): + """SVN interactive add: accepting the default trunk branch.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_svn_subproject() + + # Prompts: name, dst, version (trunk), src, ignore + answers = iter(["myrepo", "myrepo", "trunk", "", ""]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(answers), + ): + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()(_make_args(_SVN_URL, interactive=True)) + + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.name == "myrepo" + assert entry.branch == "trunk" + + +def test_add_command_interactive_svn_custom_branch(): + """SVN interactive add: selecting a branch from ``branches/``.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_svn_subproject(branches=["trunk", "feature-x"]) + + # Prompts: name, dst, version (feature-x), src, ignore + answers = iter(["myrepo", "myrepo", "feature-x", "", ""]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(answers), + ): + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()(_make_args(_SVN_URL, interactive=True)) + + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.branch == "feature-x" + + +def test_add_command_interactive_svn_tag(): + """SVN interactive add: selecting a tag.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_svn_subproject(tags=["v1.0", "v2.0"]) + + # Prompts: name, dst, version (v2.0 by name), src, ignore + answers = iter(["myrepo", "myrepo", "v2.0", "", ""]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(answers), + ): + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()(_make_args(_SVN_URL, interactive=True)) + + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.tag == "v2.0" + assert entry.branch == "" + + +def test_add_command_interactive_svn_branch_by_number(): + """SVN interactive add: selecting a branch by its number in the pick list.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + # choices order: trunk (default, index 1), feature-x (index 2) + fake_subproject = _make_svn_subproject(branches=["trunk", "feature-x"]) + + answers = iter(["myrepo", "myrepo", "2", "", ""]) + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch( + "dfetch.commands.add.Prompt.ask", + side_effect=lambda *a, **kw: next(answers), + ): + with patch( + "dfetch.commands.add.Confirm.ask", side_effect=[True, False] + ): + with patch( + "dfetch.commands.add.append_entry_manifest_file" + ) as mock_append: + Add()(_make_args(_SVN_URL, interactive=True)) + + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.branch == "feature-x" + + +def test_add_command_non_interactive_svn(): + """SVN non-interactive add defaults to trunk.""" + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_svn_subproject() + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch("dfetch.commands.add.append_entry_manifest_file") as mock_append: + Add()(_make_args(_SVN_URL)) + + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + assert entry.branch == "trunk" + + +# --------------------------------------------------------------------------- +# Add command – remote matching +# --------------------------------------------------------------------------- + + +def test_add_command_matches_existing_remote(): + """When the URL matches a known remote, the entry uses repo-path.""" + fake_remote = MagicMock() + fake_remote.name = "github" + fake_remote.url = "https://github.com/" + + fake_superproject = Mock() + fake_superproject.manifest = mock_manifest([], path="/some/dfetch.yaml") + fake_superproject.manifest.remotes = [fake_remote] + fake_superproject.root_directory = Path("/some") + + fake_subproject = _make_subproject() + + with patch( + "dfetch.commands.add.create_super_project", return_value=fake_superproject + ): + with patch( + "dfetch.commands.add.create_sub_project", return_value=fake_subproject + ): + with patch("dfetch.commands.add.append_entry_manifest_file") as mock_append: + Add()( + _make_args( + "https://github.com/org/myrepo.git", + ) + ) + + mock_append.assert_called_once() + entry: ProjectEntry = mock_append.call_args[0][1] + yaml_data = entry.as_yaml() + assert yaml_data.get("remote") == "github" + assert "org/myrepo" in yaml_data.get("repo-path", "") + assert "url" not in yaml_data + + +# --------------------------------------------------------------------------- +# CLI menu creation +# --------------------------------------------------------------------------- + + +def test_add_create_menu(): + import argparse + + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() + Add.create_menu(subparsers) + parsed = parser.parse_args(["add", "https://example.com/repo.git"]) + assert parsed.remote_url == ["https://example.com/repo.git"] + assert not hasattr(parsed, "force") + assert parsed.name is None + assert parsed.dst is None + assert parsed.version is None + assert parsed.src is None + assert parsed.ignore is None + + +def test_add_create_menu_field_overrides(): + import argparse + + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() + Add.create_menu(subparsers) + parsed = parser.parse_args( + [ + "add", + "https://example.com/repo.git", + "--name", + "mylib", + "--dst", + "ext/mylib", + "--version", + "v2.0", + "--src", + "lib", + "--ignore", + "docs", + "tests", + ] + ) + assert parsed.name == "mylib" + assert parsed.dst == "ext/mylib" + assert parsed.version == "v2.0" + assert parsed.src == "lib" + assert parsed.ignore == ["docs", "tests"] + + +def test_add_create_menu_interactive_flag(): + import argparse + + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() + Add.create_menu(subparsers) + parsed = parser.parse_args(["add", "-i", "https://example.com/repo.git"]) + assert parsed.interactive is True diff --git a/tests/test_tree_browser.py b/tests/test_tree_browser.py new file mode 100644 index 00000000..8744a9f0 --- /dev/null +++ b/tests/test_tree_browser.py @@ -0,0 +1,440 @@ +# pyright: reportCallIssue=false +"""Hypothesis tests for the TreeBrowser state machine. + +``TreeBrowser.run()`` is TTY-only and not exercised here. Tests drive the +browser through :class:`_HeadlessBrowser`, a thin subclass that seeds state, +feeds keys, and exposes internal state — keeping all test-only scaffolding out +of the production class. +""" + +from __future__ import annotations + +import os + +from hypothesis import given, settings +from hypothesis import strategies as st + +from dfetch.terminal.ansi import VIEWPORT +from dfetch.terminal.tree_browser import ( + BrowserConfig, + TreeBrowser, + TreeNode, + all_descendants_deselected, + deselected_paths, +) +from dfetch.terminal.types import Entry + +settings.register_profile("ci", max_examples=30, deadline=None) +settings.register_profile("dev", max_examples=100, deadline=None) +settings.load_profile("ci" if os.getenv("CI") else "dev") + + +# --------------------------------------------------------------------------- +# Headless test subclass — all test-only scaffolding lives here +# --------------------------------------------------------------------------- + + +class _HeadlessBrowser(TreeBrowser): + """TreeBrowser subclass for headless testing. + + Adds :meth:`seed` and :meth:`feed_key` to drive the state machine + without a TTY, and exposes :attr:`nodes`, :attr:`idx`, :attr:`top` so + tests can inspect internal state. Rendering (Screen / read_key) is + never called. + """ + + def seed(self, nodes: list[TreeNode], *, idx: int = 0, top: int = 0) -> None: + """Pre-load node state; clamp cursor and scroll to valid ranges.""" + self._nodes = list(nodes) + self._idx = max(0, min(idx, len(nodes) - 1)) if nodes else 0 + self._top = top + + def feed_key(self, key: str) -> list[str] | None: + """Process one keypress without rendering. + + Returns the selected-path list when the browser would exit, or + ``None`` to continue. + """ + if self._handle_nav(key): + return None + return self._handle_action(key) + + def adjust_scroll(self) -> None: + """Expose the private scroll-adjustment for direct testing.""" + self._adjust_scroll() + + @property + def idx(self) -> int: + """Current cursor index.""" + return self._idx + + @property + def top(self) -> int: + """Current scroll offset.""" + return self._top + + +# --------------------------------------------------------------------------- +# Strategies +# --------------------------------------------------------------------------- + +_NAME = st.text(alphabet="abcdefghijklmnopqrstuvwxyz_", min_size=1, max_size=10) + + +@st.composite +def _node_st(draw, depth: int = 0) -> TreeNode: + """Draw a single TreeNode at *depth*.""" + parts = [draw(_NAME) for _ in range(depth + 1)] + return TreeNode( + name=draw(_NAME), + path="/".join(parts), + is_dir=draw(st.booleans()), + depth=depth, + selected=draw(st.booleans()), + ) + + +@st.composite +def _node_list_st(draw, min_size: int = 1, max_size: int = 25) -> list[TreeNode]: + """Draw a flat list of TreeNodes with arbitrary depths.""" + size = draw(st.integers(min_value=min_size, max_value=max_size)) + return [draw(_node_st(depth=draw(st.integers(0, 3)))) for _ in range(size)] + + +@st.composite +def _proper_tree_st(draw, _prefix: str = "", _depth: int = 0) -> list[TreeNode]: + """Generate a pre-ordered node list with unique paths (parents before children). + + ``deselected_paths`` and ``all_descendants_deselected`` require this + ordering; arbitrary flat lists are not valid input for those functions. + """ + nodes: list[TreeNode] = [] + n = draw(st.integers(min_value=0 if _depth > 0 else 1, max_value=4)) + for i in range(n): + name = f"{chr(97 + i)}{_depth}" # a0, b0 … at depth 0; a1, b1 … at depth 1 + path = f"{_prefix}/{name}" if _prefix else name + is_dir = draw(st.booleans()) if _depth < 2 else False + selected = draw(st.booleans()) + nodes.append( + TreeNode( + name=name, path=path, is_dir=is_dir, depth=_depth, selected=selected + ) + ) + if is_dir: + nodes.extend(draw(_proper_tree_st(_prefix=path, _depth=_depth + 1))) + return nodes + + +@st.composite +def _dir_with_children_st( + draw, +) -> tuple[list[TreeNode], list[Entry]]: + """Draw an unexpanded dir node at index 0, optional siblings, and a children spec.""" + dir_name = draw(_NAME) + dir_node = TreeNode(name=dir_name, path=dir_name, is_dir=True, depth=0) + siblings = draw(st.lists(_node_st(depth=0), min_size=0, max_size=5)) + + child_names = draw(st.lists(_NAME, min_size=1, max_size=8)) + children: list[Entry] = [ + Entry(display=name, has_children=draw(st.booleans())) for name in child_names + ] + return [dir_node] + siblings, children + + +# --------------------------------------------------------------------------- +# Helper +# --------------------------------------------------------------------------- + +_NAV_KEYS = ["UP", "DOWN", "PGUP", "PGDN"] + + +def _browser( + nodes: list[TreeNode], + *, + idx: int = 0, + top: int = 0, + config: BrowserConfig = BrowserConfig(), + children: list[Entry] | None = None, +) -> _HeadlessBrowser: + """Return a seeded _HeadlessBrowser backed by *children* (or empty) for expansions.""" + dir_path = nodes[0].path if nodes else "" + ls = ( + (lambda path: children if path == dir_path else []) + if children + else (lambda _: []) + ) + browser = _HeadlessBrowser(ls, "test", config) + browser.seed(nodes, idx=idx, top=top) + return browser + + +# --------------------------------------------------------------------------- +# Navigation — index invariants +# --------------------------------------------------------------------------- + + +@given( + nodes=_node_list_st(), + keys=st.lists(st.sampled_from(_NAV_KEYS), min_size=1, max_size=100), + start=st.integers(min_value=0, max_value=24), +) +def test_navigation_index_stays_in_bounds(nodes, keys, start) -> None: + """Cursor index must remain in [0, len-1] after any sequence of nav keys.""" + browser = _browser(nodes, idx=start) + for key in keys: + browser.feed_key(key) + assert 0 <= browser.idx < len(browser.nodes) + + +@given( + nodes=_node_list_st(), + keys=st.lists(st.sampled_from(_NAV_KEYS), min_size=1, max_size=100), + start=st.integers(min_value=0, max_value=24), +) +def test_navigation_never_mutates_node_list(nodes, keys, start) -> None: + """Navigation keys must never add, remove or replace nodes.""" + browser = _browser(nodes, idx=start) + snapshot = list(browser.nodes) + for key in keys: + browser.feed_key(key) + assert browser.nodes == snapshot + + +@given(nodes=_node_list_st(min_size=2)) +def test_down_then_up_returns_to_start(nodes) -> None: + """DOWN then UP from index 0 must return to 0.""" + browser = _browser(nodes, idx=0) + browser.feed_key("DOWN") + browser.feed_key("UP") + assert browser.idx == 0 + + +@given(nodes=_node_list_st()) +def test_down_from_last_node_stays(nodes) -> None: + """DOWN from the last node must not move the cursor.""" + last = len(nodes) - 1 + browser = _browser(nodes, idx=last) + browser.feed_key("DOWN") + assert browser.idx == last + + +@given(nodes=_node_list_st()) +def test_up_from_first_node_stays(nodes) -> None: + """UP from the first node must not move the cursor.""" + browser = _browser(nodes, idx=0) + browser.feed_key("UP") + assert browser.idx == 0 + + +# --------------------------------------------------------------------------- +# Scroll invariants +# --------------------------------------------------------------------------- + + +@given( + nodes=_node_list_st(), + idx=st.integers(min_value=0, max_value=24), + top=st.integers(min_value=0, max_value=24), +) +def test_adjust_scroll_keeps_cursor_visible(nodes, idx, top) -> None: + """After adjust_scroll the cursor must fall inside the viewport.""" + browser = _browser(nodes, idx=idx, top=top) + browser.adjust_scroll() + assert browser.top <= browser.idx < browser.top + VIEWPORT + + +@given( + nodes=_node_list_st(), + idx=st.integers(min_value=0, max_value=24), + top=st.integers(min_value=0, max_value=24), +) +def test_adjust_scroll_top_is_nonnegative(nodes, idx, top) -> None: + """adjust_scroll must never produce a negative scroll offset.""" + browser = _browser(nodes, idx=idx, top=top) + browser.adjust_scroll() + assert browser.top >= 0 + + +# --------------------------------------------------------------------------- +# Expand / collapse symmetry +# --------------------------------------------------------------------------- + + +@given(spec=_dir_with_children_st()) +def test_expand_increases_count_by_children(spec) -> None: + """Expanding a dir must add exactly len(children) nodes.""" + nodes, children = spec + original = len(nodes) + browser = _browser(nodes, children=children) + browser.feed_key("RIGHT") + assert len(browser.nodes) == original + len(children) + + +@given(spec=_dir_with_children_st()) +def test_expand_sets_expanded_flag(spec) -> None: + """After RIGHT the node's expanded flag must be True.""" + nodes, children = spec + browser = _browser(nodes, children=children) + browser.feed_key("RIGHT") + assert browser.nodes[0].expanded + + +@given(spec=_dir_with_children_st()) +def test_expand_then_collapse_restores_count(spec) -> None: + """RIGHT then LEFT on the same dir must restore the original node count.""" + nodes, children = spec + original = len(nodes) + browser = _browser(nodes, children=children) + browser.feed_key("RIGHT") # expand + browser.feed_key("LEFT") # collapse (cursor stays at 0, which is an expanded dir) + assert len(browser.nodes) == original + + +@given(spec=_dir_with_children_st()) +def test_collapse_clears_expanded_flag(spec) -> None: + """After RIGHT then LEFT the node's expanded flag must be False.""" + nodes, children = spec + browser = _browser(nodes, children=children) + browser.feed_key("RIGHT") + browser.feed_key("LEFT") + assert not browser.nodes[0].expanded + + +@given(spec=_dir_with_children_st()) +def test_second_expand_does_not_duplicate_children(spec) -> None: + """RIGHT → LEFT → RIGHT must not duplicate children.""" + nodes, children = spec + original = len(nodes) + browser = _browser(nodes, children=children) + browser.feed_key("RIGHT") + browser.feed_key("LEFT") + browser.feed_key("RIGHT") + assert len(browser.nodes) == original + len(children) + + +# --------------------------------------------------------------------------- +# Cascade selection (multi mode, SPACE on dir) +# --------------------------------------------------------------------------- + + +@given(spec=_dir_with_children_st()) +def test_space_in_multi_mode_cascades_to_all_children(spec) -> None: + """SPACE on an expanded dir in multi mode must set all children to the same value.""" + nodes, children = spec + browser = _browser(nodes, children=children, config=BrowserConfig(multi=True)) + browser.feed_key("RIGHT") # expand + browser.feed_key("SPACE") # toggle + cascade + parent = browser.nodes[0] + for node in browser.nodes[1:]: + if node.depth <= parent.depth: + break + assert node.selected == parent.selected + + +@given(spec=_dir_with_children_st()) +def test_all_descendants_deselected_after_cascade_false(spec) -> None: + """all_descendants_deselected must be True after cascading False to all children.""" + nodes, children = spec + # Force the dir to start selected so SPACE sets it (and children) to False. + nodes[0].selected = True + browser = _browser(nodes, children=children, config=BrowserConfig(multi=True)) + browser.feed_key("RIGHT") # expand + browser.feed_key("SPACE") # toggle: True → False, cascade False + assert all_descendants_deselected(browser.nodes, 0) + + +@given(spec=_dir_with_children_st()) +def test_not_all_descendants_deselected_after_cascade_true(spec) -> None: + """all_descendants_deselected must be False after cascading True (children exist).""" + nodes, children = spec + # Force the dir to start deselected so SPACE sets it (and children) to True. + nodes[0].selected = False + browser = _browser(nodes, children=children, config=BrowserConfig(multi=True)) + browser.feed_key("RIGHT") # expand + browser.feed_key("SPACE") # toggle: False → True, cascade True + assert not all_descendants_deselected(browser.nodes, 0) + + +# --------------------------------------------------------------------------- +# deselected_paths (requires properly-ordered trees) +# --------------------------------------------------------------------------- + + +@given(nodes=_proper_tree_st()) +def test_deselected_paths_empty_when_all_selected(nodes) -> None: + """deselected_paths must return [] when every node is selected.""" + for node in nodes: + node.selected = True + assert not deselected_paths(nodes) + + +@given(nodes=_proper_tree_st()) +def test_deselected_paths_only_references_deselected_nodes(nodes) -> None: + """Every path returned by deselected_paths must belong to a deselected node.""" + deselected_node_paths = {n.path for n in nodes if not n.selected} + for path in deselected_paths(nodes): + assert path in deselected_node_paths + + +@given(nodes=_proper_tree_st()) +def test_deselected_paths_are_prefix_free(nodes) -> None: + """No returned path should be a directory-prefix of another returned path.""" + paths = deselected_paths(nodes) + for i, a in enumerate(paths): + for j, b in enumerate(paths): + if i != j: + assert not b.startswith(a + "/"), f"{a!r} is a prefix of {b!r}" + + +# --------------------------------------------------------------------------- +# Key action handlers +# --------------------------------------------------------------------------- + + +@given(nodes=_node_list_st()) +def test_esc_always_returns_empty_list(nodes) -> None: + """ESC must return [] regardless of cursor position or node state.""" + assert _browser(nodes).feed_key("ESC") == [] + + +@given(nodes=_node_list_st()) +def test_unknown_key_returns_none(nodes) -> None: + """An unrecognised key must return None (keep the loop running).""" + assert _browser(nodes).feed_key("__NO_SUCH_KEY__") is None + + +@given(nodes=_node_list_st(), idx=st.integers(min_value=0, max_value=24)) +def test_enter_on_leaf_single_mode_returns_its_path(nodes, idx) -> None: + """ENTER on a leaf in single-select mode must return exactly that node's path.""" + leaf_indices = [i for i, n in enumerate(nodes) if not n.is_dir] + if not leaf_indices: + return + i = leaf_indices[idx % len(leaf_indices)] + browser = _browser(nodes, idx=i, config=BrowserConfig(multi=False)) + assert browser.feed_key("ENTER") == [nodes[i].path] + + +@given(nodes=_node_list_st()) +def test_enter_in_multi_mode_returns_all_selected_paths(nodes) -> None: + """ENTER in multi-select mode must return exactly the paths of selected nodes.""" + browser = _browser(nodes, config=BrowserConfig(multi=True)) + expected = [n.path for n in nodes if n.selected] + assert browser.feed_key("ENTER") == expected + + +@given( + nodes=_node_list_st(), + keys=st.lists( + st.sampled_from([*_NAV_KEYS, "LEFT", "RIGHT", "ESC", "ENTER", "SPACE"]), + min_size=1, + max_size=50, + ), + start=st.integers(min_value=0, max_value=24), +) +def test_any_key_sequence_never_raises(nodes, keys, start) -> None: + """No key combination on any tree should raise an exception.""" + browser = _browser(nodes, idx=start) + for key in keys: + result = browser.feed_key(key) + if result is not None: + break # ESC or selection — browser would exit here