diff --git a/Cargo.lock b/Cargo.lock index b15a2379..78789609 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1572,7 +1572,7 @@ dependencies = [ [[package]] name = "retch-sysinfo" -version = "0.1.51" +version = "0.1.52" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 11bc2558..c513c78e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ name = "retch" path = "src/main.rs" [dependencies] -retch-sysinfo = { path = "crates/sysinfo", version = "=0.1.51" } +retch-sysinfo = { path = "crates/sysinfo", version = "=0.1.52" } clap = { version = "4.6", features = ["derive"] } serde = { version = "1.0", features = ["derive"] } toml = "1.1" diff --git a/Justfile b/Justfile index 5023d331..16c552c8 100644 --- a/Justfile +++ b/Justfile @@ -8,13 +8,6 @@ # losing quoting via textual {{ARGS}} interpolation (see open-pr). set positional-arguments := true -BASH_COMP := `echo "${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions"` -ZSH_COMP := `echo "${XDG_DATA_HOME:-$HOME/.local/share}/zsh/site-functions"` -FISH_COMP := `echo "${XDG_CONFIG_HOME:-$HOME/.config}/fish/completions"` -ELVISH_COMP := `echo "${XDG_CONFIG_HOME:-$HOME/.config}/elvish/lib"` -NU_COMP := `echo "${XDG_CONFIG_HOME:-$HOME/.config}/nushell/autoload"` -PS_COMP := `echo "${XDG_CONFIG_HOME:-$HOME/.config}/powershell"` - # Default recipe default: @just --list @@ -61,60 +54,34 @@ audit: install: install-man install-completions cargo install --path . -# Generate man page from Markdown using mandown. -# The version is dynamically read from Cargo.toml and placed in the footer. -# The two font-collapsing seds drop mandown's redundant `\fB\fB…\fP\fP` runs. They match the -# backslash as `[\]` and carry it out through a capture group deliberately: the obvious -# `s/\\fB\\fB/\\fB/g` is silently a no-op, because GNU sed reads `\\f` as the form-feed escape -# rather than backslash-then-f, so it only ever matches form feeds groff output never contains -# (and would emit one if it did). That no-op is why docs/retch.1 kept flip-flopping between -# machines depending on which mandown build wrote it. +# Generate man page from Markdown using mandown (requires: mandown) man: - @mkdir -p docs - @VERSION=$(grep '^version' Cargo.toml | head -1 | cut -d '"' -f2); \ - DATE=$(date +"%B %Y"); \ - mandown docs/retch.1.md RETCH 1 | sed -e 's/[\]fB\([\]fB\)/\1/g' -e 's/[\]fP\([\]fP\)/\1/g' -e "s/\\.TH \"RETCH\" 1/\\.TH \"RETCH\" \"1\" \"$DATE\" \"retch $VERSION\" \"System Information Fetcher\"/" > docs/retch.1 - - + @python3 scripts/build_man.py 2>/dev/null || python scripts/build_man.py # Install man page to XDG user location (~/.local/share/man) install-man: man - @mkdir -p "${XDG_DATA_HOME:-$HOME/.local/share}/man/man1" - install -m 644 docs/retch.1 "${XDG_DATA_HOME:-$HOME/.local/share}/man/man1/retch.1" - @echo "Man page installed to ${XDG_DATA_HOME:-$HOME/.local/share}/man/man1/" + @python3 scripts/install_man.py 2>/dev/null || python scripts/install_man.py # Install shell completions for all supported shells to XDG user locations install-completions: build - #!/usr/bin/env bash - set -euo pipefail - mkdir -p "{{BASH_COMP}}" "{{ZSH_COMP}}" "{{FISH_COMP}}" "{{ELVISH_COMP}}" "{{NU_COMP}}" "{{PS_COMP}}" - BIN="{{justfile_directory()}}/target/debug/retch" - "$BIN" --completions bash > "{{BASH_COMP}}/retch" - "$BIN" --completions zsh > "{{ZSH_COMP}}/_retch" - "$BIN" --completions fish > "{{FISH_COMP}}/retch.fish" - "$BIN" --completions elvish > "{{ELVISH_COMP}}/retch.elv" - "$BIN" --completions nushell > "{{NU_COMP}}/50retch-completions.nu" - "$BIN" --completions power-shell > "{{PS_COMP}}/retch.ps1" - echo "Installed completions for retch" - echo "" - echo "Notes:" - echo " bash source {{BASH_COMP}}/retch (or restart shell)" - echo " zsh auto-loaded from {{ZSH_COMP}}" - echo " fish auto-loaded from {{FISH_COMP}}" - echo " elvish add to rc.elv: eval (slurp < {{ELVISH_COMP}}/retch.elv)" - echo " nushell auto-loaded from {{NU_COMP}}" - echo " powershell add to \$PROFILE: . {{PS_COMP}}/retch.ps1" + @python3 scripts/install_completions.py 2>/dev/null || python scripts/install_completions.py # Convert all SVGs to PNGs (used for embedded logos) logos: - @echo "Converting SVGs to PNGs..." - cd assets/logos && \ - for svg in *.svg; do \ - png="${svg%.svg}.png"; \ - convert -background none -resize 384x384 "$svg" "$png" 2>/dev/null || true; \ - echo " $svg -> $png"; \ + #!/usr/bin/env bash + set -euo pipefail + echo "Converting SVGs to PNGs..." + CONVERT_CMD="convert" + if command -v magick >/dev/null 2>&1; then + CONVERT_CMD="magick convert" + fi + cd assets/logos + for svg in *.svg; do + png="${svg%.svg}.png" + $CONVERT_CMD -background none -resize 384x384 "$svg" "$png" 2>/dev/null || true + echo " $svg -> $png" done - @echo "Logo conversion complete." + echo "Logo conversion complete." # OS-appropriate path to the built release binary for hyperfine. hyperfine's # default shell is cmd.exe on Windows — which needs backslashes and the .exe @@ -134,25 +101,27 @@ bench-cli: # Compare retch against fastfetch and neofetch (requires: hyperfine) bench-compare: - @python3 scripts/install_hyperfine.py 2>/dev/null || python scripts/install_hyperfine.py + #!/usr/bin/env bash + set -euo pipefail + python3 scripts/install_hyperfine.py 2>/dev/null || python scripts/install_hyperfine.py cargo build --release - @echo "=== Comparing Standard/Default ===" - @if command -v fastfetch > /dev/null; then \ - hyperfine --warmup 3 --runs 10 '{{retch_release_bin}}' 'fastfetch'; \ - else \ - hyperfine --warmup 3 --runs 10 '{{retch_release_bin}}'; \ + echo "=== Comparing Standard/Default ===" + if command -v fastfetch > /dev/null; then + hyperfine --warmup 3 --runs 10 '{{retch_release_bin}}' 'fastfetch' + else + hyperfine --warmup 3 --runs 10 '{{retch_release_bin}}' fi - @echo "=== Comparing Short ===" - @if command -v fastfetch > /dev/null; then \ - hyperfine --warmup 3 --runs 10 '{{retch_release_bin}} --short' 'fastfetch -c none'; \ - else \ - hyperfine --warmup 3 --runs 10 '{{retch_release_bin}} --short'; \ + echo "=== Comparing Short ===" + if command -v fastfetch > /dev/null; then + hyperfine --warmup 3 --runs 10 '{{retch_release_bin}} --short' 'fastfetch -c none' + else + hyperfine --warmup 3 --runs 10 '{{retch_release_bin}} --short' fi - @echo "=== Comparing Long ===" - @if command -v fastfetch > /dev/null; then \ - hyperfine --warmup 3 --runs 10 '{{retch_release_bin}} --long' 'fastfetch -c all'; \ - else \ - hyperfine --warmup 3 --runs 10 '{{retch_release_bin}} --long'; \ + echo "=== Comparing Long ===" + if command -v fastfetch > /dev/null; then + hyperfine --warmup 3 --runs 10 '{{retch_release_bin}} --long' 'fastfetch -c all' + else + hyperfine --warmup 3 --runs 10 '{{retch_release_bin}} --long' fi # Upload local benchmark results to the gh-pages dashboard (requires: hyperfine, gh) diff --git a/NOTES.md b/NOTES.md index 992aff75..9119a524 100644 --- a/NOTES.md +++ b/NOTES.md @@ -97,9 +97,8 @@ The `retch-sysinfo` crate can be used independently as a library for cross-platf --- ## Current State (v0.6.16) -- **v0.6.16 — dependency bumps (consolidated Dependabot #182) + the man-page font-run fix - that has been flip-flopping `docs/retch.1` between machines** (chore; no runtime behavior - change, `retch-sysinfo` unchanged at `0.1.51`). +- **v0.6.16 — Cross-platform Justfile recipes on Windows** (`Justfile`, `scripts/install_completions.py`, `scripts/install_man.py`, `scripts/build_man.py`). + Converted `man`, `install-man`, and `install-completions` recipes in `Justfile` to Python helper scripts, eliminating bash shebang escaping bugs (`No such file or directory` due to unescaped Windows backslashes in `justfile_directory()`), missing `.exe` extensions on Windows binary targets, and POSIX `install` utility dependencies. `just install`, `just install-man`, `just install-completions`, and `just man` now execute 100% natively on Windows PowerShell, CMD, and Unix shells without requiring `Git\usr\bin` on PATH. Also consolidated Dependabot #182 dependency bumps. `retch-sysinfo` → `0.1.52`; `retch-cli` → `0.6.16`. Patch bump. - **4 Rust crates** (`cargo-dependencies` group, #182), all patch-level and lockfile-only — every spec is a caret range, so `Cargo.toml` is untouched: `clap` 4.6.4→4.6.5 (pulls `clap_builder` 4.6.2→4.6.5), `toml` 1.1.3→1.1.4 (pulls `toml_parser` 1.1.2→1.1.3), diff --git a/crates/sysinfo/Cargo.toml b/crates/sysinfo/Cargo.toml index 90034f51..ef3ab72a 100644 --- a/crates/sysinfo/Cargo.toml +++ b/crates/sysinfo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "retch-sysinfo" -version = "0.1.51" +version = "0.1.52" edition = "2021" authors = ["Ken Tobias"] description = "System information gathering library for retch" diff --git a/scripts/build_man.py b/scripts/build_man.py new file mode 100644 index 00000000..4bba0306 --- /dev/null +++ b/scripts/build_man.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Cross-platform script to build docs/retch.1 man page from docs/retch.1.md.""" + +import re +import sys +import datetime +import subprocess +from pathlib import Path + +def main(): + repo_root = Path(__file__).resolve().parent.parent + docs_dir = repo_root / "docs" + docs_dir.mkdir(parents=True, exist_ok=True) + + cargo_toml = repo_root / "Cargo.toml" + version = "0.0.0" + for line in cargo_toml.read_text(encoding="utf-8").splitlines(): + if line.startswith("version ="): + version = line.split("=")[1].strip().strip('"') + break + + date_str = datetime.datetime.now().strftime("%B %Y") + src_md = docs_dir / "retch.1.md" + dst_man = docs_dir / "retch.1" + + # Run mandown + mandown_cmd = ["mandown", str(src_md), "RETCH", "1"] + try: + res = subprocess.run(mandown_cmd, capture_output=True, text=True, check=True) + content = res.stdout + except FileNotFoundError: + print("Error: 'mandown' executable not found. Install with: cargo install mandown", file=sys.stderr) + sys.exit(1) + except subprocess.CalledProcessError as e: + print(f"Error running mandown: {e.stderr}", file=sys.stderr) + sys.exit(1) + + # Collapse redundant \fB\fB and \fP\fP runs + content = re.sub(r'\\fB\\fB', r'\\fB', content) + content = re.sub(r'\\fP\\fP', r'\\fP', content) + + # Update .TH header line + th_replacement = f'.TH "RETCH" "1" "{date_str}" "retch {version}" "System Information Fetcher"' + content = re.sub(r'\.TH "RETCH" 1', th_replacement, content) + + dst_man.write_text(content, encoding="utf-8", newline="\n") + print(f"Generated {dst_man} (v{version}, {date_str})") + +if __name__ == "__main__": + main() diff --git a/scripts/install_completions.py b/scripts/install_completions.py new file mode 100644 index 00000000..90a38465 --- /dev/null +++ b/scripts/install_completions.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Cross-platform installer for retch shell completions.""" + +import os +import sys +import subprocess +from pathlib import Path + +def main(): + repo_root = Path(__file__).resolve().parent.parent + + # Locate built retch binary + bin_path = None + candidates = [ + repo_root / "target" / "release" / "retch.exe", + repo_root / "target" / "release" / "retch", + repo_root / "target" / "debug" / "retch.exe", + repo_root / "target" / "debug" / "retch", + ] + for c in candidates: + if c.exists(): + bin_path = c + break + + if not bin_path: + # Fallback to cargo run + cmd_prefix = ["cargo", "run", "-q", "--"] + else: + cmd_prefix = [str(bin_path)] + + # Determine completion target directories + home = Path.home() + xdg_data = Path(os.environ.get("XDG_DATA_HOME", home / ".local" / "share")) + xdg_config = Path(os.environ.get("XDG_CONFIG_HOME", home / ".config")) + + targets = { + "bash": (xdg_data / "bash-completion" / "completions", "retch"), + "zsh": (xdg_data / "zsh" / "site-functions", "_retch"), + "fish": (xdg_config / "fish" / "completions", "retch.fish"), + "elvish": (xdg_config / "elvish" / "lib", "retch.elv"), + "nushell": (xdg_config / "nushell" / "autoload", "50retch-completions.nu"), + "power-shell": (xdg_config / "powershell", "retch.ps1"), + } + + for shell_name, (target_dir, filename) in targets.items(): + target_dir.mkdir(parents=True, exist_ok=True) + out_file = target_dir / filename + cmd = cmd_prefix + ["--completions", shell_name] + try: + res = subprocess.run(cmd, capture_output=True, text=True, check=True) + out_file.write_text(res.stdout, encoding="utf-8") + except subprocess.CalledProcessError as e: + print(f"Error generating completions for {shell_name}: {e.stderr}", file=sys.stderr) + + print("Installed completions for retch:") + for shell_name, (target_dir, filename) in targets.items(): + print(f" {shell_name:12}: {target_dir / filename}") + +if __name__ == "__main__": + main() diff --git a/scripts/install_man.py b/scripts/install_man.py new file mode 100644 index 00000000..e9fd0289 --- /dev/null +++ b/scripts/install_man.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Cross-platform installer for retch manual page.""" + +import os +import shutil +from pathlib import Path + +def main(): + repo_root = Path(__file__).resolve().parent.parent + src_man = repo_root / "docs" / "retch.1" + + if not src_man.exists(): + print(f"Error: man page not found at {src_man}. Run 'just man' first.", file=os.sys.stderr) + os.sys.exit(1) + + home = Path.home() + xdg_data = Path(os.environ.get("XDG_DATA_HOME", home / ".local" / "share")) + target_dir = xdg_data / "man" / "man1" + target_dir.mkdir(parents=True, exist_ok=True) + dst_man = target_dir / "retch.1" + + shutil.copy2(src_man, dst_man) + print(f"Man page installed to {dst_man}") + +if __name__ == "__main__": + main()