diff --git a/.vscode/settings.json b/.vscode/settings.json index 42809997..563a931e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "Bashmatic", - "rubygems" + "rubygems", + "RUSTUP" ] } diff --git a/CLAUDE.md b/CLAUDE.md index 8764c257..18699d1b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,5 @@ -# Bashmatic® — Project Guide for Claude +# Bashmatic® — Project Guide for Claudens (“a BASH DSL for humans”), focused on -> A BASH framework of ~900 helper functions (“a BASH DSL for humans”), focused on > beautiful terminal output, consistent command execution, and self-documenting > scripts. Loads in under 200ms. MIT licensed. diff --git a/bin/rust-install b/bin/rust-install new file mode 100755 index 00000000..99b21de8 --- /dev/null +++ b/bin/rust-install @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# vim: ft=bash +# +set +e + +# Re-exec under modern bash if we landed in bash 3 (macOS /bin/bash). +# Without this, sourcing parts of bashmatic from bash 3 misbehaves and +# leaves the user in odd states. Done before anything else. +if [[ -n ${BASH_VERSION} && ${BASH_VERSION:0:1} -lt 4 ]]; then + for __new_bash in /opt/homebrew/bin/bash /usr/local/bin/bash; do + if [[ -x ${__new_bash} ]]; then + exec "${__new_bash}" "$0" "$@" + fi + done +fi + +[[ -z ${BASHMATIC_HOME} ]] && export BASHMATIC_HOME="$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1; pwd -P)")" + +if [[ $(uname -s) == Darwin ]] ; then + command -v brew >/dev/null && { + brew update + [[ ${BASH_VERSION:0:1} -lt 4 ]] && brew install bash + export PATH="$(brew --prefix)/bin:$PATH" + } +fi + +[[ -z ${BASHMATIC_HOME} ]] && export BASHMATIC_HOME="${HOME}/.bashmatic" +[[ -d ${BASHMATIC_HOME} ]] || bash -c "$(curl -fsSL https://bashmatic.re1.re); bashmatic-install" +[[ -d ${BASHMATIC_HOME} ]] || { + echo "Can't find Bashmatic, even after attempting an installation." + echo "Please install Bashmatic with the following command line:" + echo 'bash -c "$(curl -fsSL https://bashmatic.re1.re); bashmatic-install"' + exit 1 +} + +# shellcheck source=/dev/null +source "${BASHMATIC_HOME}/lib/color.sh" +source "${BASHMATIC_HOME}/lib/output.sh" +source "${BASHMATIC_HOME}/lib/output-boxes.sh" +source "${BASHMATIC_HOME}/lib/output-admonitions.sh" +source "${BASHMATIC_HOME}/lib/output-utils.sh" +source "${BASHMATIC_HOME}/lib/runtime.sh" +source "${BASHMATIC_HOME}/lib/time.sh" +source "${BASHMATIC_HOME}/lib/util.sh" + +command -v rustc >/dev/null && command -v cargo >/dev/null && { + printf "${txtgrn}Rust is already installed.${clr}\n" 2>/dev/null + exit 0 +} + +readonly __rust_rustup_home=/opt/rust/rustup +readonly __rust_cargo_home=/opt/rust/cargo + +run "sudo mkdir -p \"${__rust_rustup_home}\" \"${__rust_cargo_home}\"" +run "sudo chown -R \"$(id -un)\":\"$(id -gn)\" /opt/rust" + +export RUSTUP_HOME="${__rust_rustup_home}" +export CARGO_HOME="${__rust_cargo_home}" + +run "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path" + +declare -a local_shells_done=() + +while IFS= read -r shell_path || [[ -n ${shell_path} ]]; do + [[ -z ${shell_path// } ]] && continue + [[ ${shell_path} =~ ^[[:space:]]*# ]] && continue + shell="$(basename "${shell_path}")" + # shellcheck disable=SC2076 + if [[ " ${local_shells_done[*]} " =~ " ${shell} " ]]; then + continue + fi + info "Installing hooks into ${SHELL_RC} for ${shell}..." + local_shells_done+=( "${shell}" ) + + SHELL_RC="${HOME}/.${shell}rc" + if [[ ! -s ${SHELL_RC} ]] ; then + touch "${SHELL_RC}" + fi + echo "Installing hooks into ${SHELL_RC} for ${shell}..." + if [[ -f ${SHELL_RC} ]] && ! grep -q 'RUSTUP_HOME=/opt/rust/rustup' "${SHELL_RC}" 2>/dev/null; then + echo "Appending Rust Initialization for ${shell} → ${SHELL_RC}" + { + echo "export RUSTUP_HOME=${__rust_rustup_home}" + echo "export CARGO_HOME=${__rust_cargo_home}" + echo "export PATH=\"${__rust_rustup_home}/bin:${__rust_cargo_home}/bin:\${PATH}\"" + } >> "${SHELL_RC}" + fi +done < /etc/shells + diff --git a/bin/rust-install-tools b/bin/rust-install-tools new file mode 100755 index 00000000..819a20f5 --- /dev/null +++ b/bin/rust-install-tools @@ -0,0 +1,258 @@ +#!/usr/bin/env bash +# +# rust-install-tools — opinionated installer for modern Rust CLI/TUI tools. +# Skips anything already installed; for the rest, prints a colorful h2bg +# banner describing the tool, then installs it via cargo-binstall. +# +# USAGE: +# # We need BASH version 4+ +# brew install bash # MacOS only +# +# # Then we can just run this script or do it from the Public Gist. +# curl -fsSL https://bit.ly/install-rust-tools | /opt/homebrew/bin/bash + +set +e + +# Re-exec under modern bash if we landed in bash 3 (macOS /bin/bash). +if [[ -n ${BASH_VERSION} && ${BASH_VERSION:0:1} -lt 4 ]]; then + for __new_bash in /opt/homebrew/bin/bash /usr/local/bin/bash; do + if [[ -x ${__new_bash} ]]; then + exec "${__new_bash}" "$0" "$@" + fi + done +fi + +export OLD_IFS="${IFS}" + +# Parent of bin/ when this script lives in the bashmatic checkout. +_rust_tools_bashmatic_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +[[ -z "${BASHMATIC_HOME:-}" ]] && export BASHMATIC_HOME="${_rust_tools_bashmatic_root}" + +if [[ ! -d "${HOME}/.bashmatic" ]]; then + echo "→ Installing bashmatic..." + if [[ -x "${_rust_tools_bashmatic_root}/bin/bashmatic-install" ]]; then + "${_rust_tools_bashmatic_root}/bin/bashmatic-install" -q -l + elses + bash -c "$(curl -fsSL https://bashmatic.re1.re)" _ -q -l || \ + bash -c "$(curl -fsSL https://raw.githubusercontent.com/kigster/bashmatic/master/bin/bashmatic-install)" _ -q -l + fi +fi +export BASHMATIC_HOME="${HOME}/.bashmatic" + +# shellcheck source=/dev/null +source "${BASHMATIC_HOME}/init" + +# --- Bootstrap cargo-binstall (so we don't compile everything from source) --- +if ! command -v cargo-binstall >/dev/null 2>&1; then + h3bg "cargo-binstall — fetches prebuilt Cargo binaries instead of compiling" + cargo install cargo-binstall +fi + +# --- Tool catalog: binary | crate | one-line description --- +# ============================================================================ +# Rust CLI tools, grouped by category. Format: binary|crate|description +# ============================================================================ + +TOOLS=( + # --- Shell & Prompt --- + "atuin |atuin |Synced shell history with searchable TUI" + "nu |nu |Nushell: structured-data shell" + "starship|starship|Minimal, blazing-fast cross-shell prompt" + "zoxide |zoxide |Smarter cd that learns your habits (frecency)" + + # --- File Management --- + "bat |bat |Cat clone with syntax highlighting and paging" + "broot|broot |Tree-style file navigator" + "dua |dua-cli|Interactive TUI disk-usage explorer" + "dust |du-dust|Sorted disk-usage tree, drop-in du replacement" + "erd |erdtree|Modern tree with file-size and gitignore support" + "eza |eza |Modern ls with tree view, git status, and icons" + "fd |fd-find|Find replacement: gitignore-aware, parallel, sane defaults" + "xplr |xplr |Hackable, minimal, fast TUI file explorer" + "yazi |yazi-fm|Async TUI file manager with image previews" + + # --- System Monitoring --- + "bandwhich|bandwhich|Per-process and per-connection bandwidth monitor" + "btm |bottom |Ratatui system monitor: top/htop replacement" + "kmon |kmon |Linux kernel module manager TUI" + "pgtop |pgtop |Postgres activity monitor, top-like" + "procs |procs |Modern ps: tree mode, search, color" + + # --- Cargo / Rust dev --- + "bacon |bacon |Background cargo check/clippy/test TUI" + "cargo-audit |cargo-audit |Audit Cargo.lock for known security advisories" + "cargo-binstall|cargo-binstall|Install Rust binaries from prebuilt artifacts" + "cargo-deb |cargo-deb |Build .deb packages from Cargo metadata" + "cargo-deny |cargo-deny |Lint dependencies for licenses, bans, advisories" + "cargo-generate|cargo-generate|Scaffold new projects from git templates" + "cargo-liner |cargo-liner |Declarative cargo install via liner.toml" + "cargo-machete |cargo-machete |Find and remove unused Cargo dependencies" + "cargo-nextest |cargo-nextest |Next-gen test runner: parallel, retry, faster" + + # --- Git & VCS --- + "delta |git-delta |Beautiful syntax-highlighted git diff pager" + "difft |difftastic|Syntax-aware structural diff, not line diff" + "git-cliff|git-cliff |Generate changelog from conventional commits" + "gitui |gitui |Full-featured git TUI: stage, commit, branch, blame" + "jj |jj-cli |Jujutsu: git-compatible VCS, rethought UX" + "mergiraf |mergiraf |AST-aware git merge driver, resolves false conflicts" + "serie |serie |Git log graph viewer (ratatui)" + + # --- Data Inspection --- + "csvlens|csvlens |Interactive CSV pager with column-aware scrolling" + "gobang |gobang |Cross-database SQL client TUI" + "hexyl |hexyl |Colored, structured hex viewer" + "jless |jless |Interactive JSON/YAML viewer" + "qsv |qsv |CSV/data swiss-army knife, 50+ subcommands" + "rg |ripgrep |Lightning-fast recursive grep" + "rga |ripgrep_all|ripgrep for PDFs, ebooks, archives, SQLite" + "tokei |tokei |Count lines of code by language, blazing fast" + + # --- Networking --- + "gping |gping |Ping with a live latency graph" + "monolith|monolith|Save complete web pages as single HTML files" + "oha |oha |HTTP load tester with a live TUI" + "slumber |slumber |YAML-driven HTTP API client TUI" + "trip |trippy |Network diagnostics: traceroute + ping + MTR in one" + "xh |xh |HTTPie-style HTTP client, fast and ergonomic" + + # --- Productivity & TUI --- + "fzf-make |fzf-make |Interactive Makefile/justfile target picker" + "gpg-tui |gpg-tui |TUI for managing GPG keys" + "kdash |kdash |Kubernetes dashboard TUI" + "mprocs |mprocs |Run multiple commands in parallel in a TUI" + "or |octorus |GitHub PR review TUI built for Helix users" + "oxker |oxker |Docker container TUI: logs, exec, stats" + "presenterm |presenterm |Markdown slide presentations in the terminal" + "sk |skim |Rust fuzzy finder, fzf alternative" + "taskwarrior-tui|taskwarrior-tui|TUI front-end for Taskwarrior" + "tickrs |tickrs |Stock ticker dashboard in the terminal" + "tldr |tealdeer |tldr-pages client: cheatsheets for commands" + "topgrade |topgrade |Update everything: brew, cargo, npm, pip, apt..." + "tv |television |Channel-based fuzzy finder, scales past fzf" + "zellij |zellij |Modern terminal multiplexer, tmux alternative" + + # --- Editors --- + "hx|helix-term|Post-modern modal editor with built-in LSP" + + # --- Workflow & Automation --- + "just |just |Project-scoped command runner, not a build system" + "mise |mise |Polyglot runtime + env vars + tasks (replaces asdf, nvm, direnv)" + "ouch |ouch |One verb works for every archive format" + "pueue|pueue|Persistent task queue daemon, survives SSH disconnects" + + # --- AI / LLM --- + "aichat|aichat|Multi-provider LLM CLI with RAG, agents, shell assist" + + # --- Terminal Recording & Sharing --- + "agg |agg |asciinema cast to animated GIF (crate is stale; see note above)" + "asciinema|asciinema|Terminal session recorder, now in Rust (v3+)" + + # --- Build, Benchmark & Test --- + "hyperfine|hyperfine|Statistical CLI benchmarking tool" + + # --- Text Processing --- + "choose|choose |Human-friendly column selector, cut/awk subset" + "sd |sd |Saner regex find-and-replace, simpler than sed" + "sg |ast-grep|Tree-sitter structural search/rewrite, pattern-as-code" + "srgn |srgn |Surgical search-replace via tree-sitter queries" +) + + +installed=0 +skipped=0 +failed=() + +_cargo_install_root="${CARGO_INSTALL_ROOT:-${CARGO_HOME:-${HOME}/.cargo}}" +_cargo_bin_path="${_cargo_install_root}/bin" +[[ -d "${_cargo_bin_path}" ]] && export PATH="$PATH:${_cargo_bin_path}" + +cargo_install_list="$(cargo install --list 2>/dev/null || true)" + +function print-status() { + local status="$1"; shift + local desc="$*"; shift + + printf "%-60s |${clr} %-60s " "${status}" "${desc}" +} + +function print-bin-crate() { + local bin="$1" + local crate="$2" + printf "%-16s → %-16s" "${bin}" "${crate}" +} + +for entry in "${TOOLS[@]}"; do + bin= + crate= + desc= + + IFS='|' read -r bin crate desc <<<"${entry}" + + bin="$(echo "${bin}" | tr -d ' ')" + crate="$(echo "${crate}" | tr -d ' ')" + + if command -v ${bin} >/dev/null 2>&1; then + h.green "$(print-status "✓ $(print-bin-crate "${bin}" "${crate}") is pre-installed" "${desc}")" + skipped=$((skipped + 1)) + continue + fi + + if [[ -x "${_cargo_install_root}/bin/${bin}" ]]; then + h.green "$(print-status "✓ $(print-bin-crate "${bin}" "${crate}") exists, but not in \$PATH" "${desc}")" + skipped=$((skipped + 1)) + continue + fi + + if [[ -n "${cargo_install_list}" ]] && printf '%s\n' "${cargo_install_list}" | grep -q "^${crate} v"; then + h.green "$(print-status "✓ $(print-bin-crate "${bin}" "${crate}") was installed by cargo" "${desc}")" + skipped=$((skipped + 1)) + continue + fi + + h.yellow "$(print-status "✓ $(print-bin-crate "${bin}" "${crate}") is installing" "${desc}")" + if cargo binstall -y ${crate} >/dev/null 2>&1; then + installed=$((installed + 1)) + else + warn "binstall of ${crate} failed, attempting to build from sources..." + if cargo install ${crate} >/dev/null 2>&1; then + installed=$((installed + 1)) + else + warning "failed to install ${bin} (${crate})" + failed+=("${bin} (${crate})") + continue + fi + fi +done + +hr +info "Installation completed. All the tools with their descriptions will be shown below." +sleep 3 +hr + +h1bg "Summary" "installed: ${installed}" "skipped: ${skipped}" "failed: ${#failed[@]}" + +if (( ${#failed[@]} > 0 )); then + warn "The following did not install cleanly:" + for f in "${failed[@]}"; do + echo " • ${f}" + done + echo + echo "Tip: try \`cargo install \` directly — binstall sometimes can't find a" + echo "prebuilt for your platform, but a from-source build will still work." +fi + +tool_col_width=15 + +catalog_lines=( + "$(printf '%-*s %s' "${tool_col_width}" 'Tool' 'Description')" +) + +for entry in "${TOOLS[@]}"; do + IFS='|' read -r bin crate desc <<<"${entry}" + catalog_lines+=("$(printf '%-*s %s' "${tool_col_width}" "${bin}" "${desc}")") +done + +h4bg "${catalog_lines[@]}" + +export IFS="${OLD_IFS}" diff --git a/init.sh b/init.sh index af74def4..631e49dc 100755 --- a/init.sh +++ b/init.sh @@ -541,6 +541,8 @@ function os.yaml() { # Main Flow #——————————————————————————————————————————————————————————————————————————— +unalias find 2>/dev/null +unalias ls 2>/dev/null # resolve BASHMATIC_HOME if necessary __bashmatic.prerequisites diff --git a/lib/color.sh b/lib/color.sh index 785efe45..caab5d9f 100644 --- a/lib/color.sh +++ b/lib/color.sh @@ -5,10 +5,21 @@ # # Any modifications, © 2016-2026 Konstantin Gredeskoul, All rights reserved. MIT License. -export BashMatic__ColorLoaded=${BashMatic__ColorLoaded:-"0"} +# Detect the runtime shell. Do NOT use ${SHELL} — that is the login shell, +# not the shell currently executing this file. Do NOT exec into another shell +# from this library: color.sh is sourced by scripts (bin/*) and by interactive +# zsh sessions; replacing the current process here breaks both. +if [[ -n ${ZSH_VERSION} ]] ; then + export GLOBAL="typeset -gx" +elif [[ -n ${BASH_VERSION} && ${BASH_VERSION:0:1} -ge 4 ]] ; then + export GLOBAL="declare -g" +else + # bash 3 (e.g. macOS /bin/bash) or unknown shell: fall back to declare. + # Scripts that need bash 4+ should re-exec themselves at their entry point. + export GLOBAL="declare" +fi -[[ -z ${GLOBAL} ]] && export GLOBAL="declare " -[[ ${SHELL} =~ zsh ]] && export GLOBAL="declare -g " +export BashMatic__ColorLoaded=${BashMatic__ColorLoaded:-"0"} if [[ ${BashMatic__ColorLoaded} -ne 1 ]]; then DECLARATIONS=" @@ -130,7 +141,6 @@ if [[ ${BashMatic__ColorLoaded} -ne 1 ]]; then " eval "${DECLARATIONS}" - fi function reset-color() {