π¨ ZSHAND Style GuideΒΆ
π This guide covers naming conventions, error handling patterns, zsh idioms, and code style rules used throughout the framework.
π§ Philosophy
Consistency over cleverness. Every file should look like it was written by the same person. When in doubt, match the existing code around you.
π’ File NamingΒΆ
π Numbered PrefixesΒΆ
Files in ordered directories use 2-digit numeric prefixes to enforce deterministic load order:
| Directory | Convention | Example |
|---|---|---|
core/ | NN_name.zsh (even: framework) | 02_vars.zsh, 06_engine.zsh |
shared_functions/ | NN_name.zsh | 01_stderr_error.zsh |
startup/ | NN_az_name.zsh | 24_az_safe_mode.zsh |
hooks/ | NN_name.zsh | 01_mise.zsh, 20_docker.zsh |
Rules:
- π΅ Even numbers (02, 04, 06...) β framework files
- π’ Odd numbers (01, 03, 05...) β reserved for user insertion
- π 2-number gaps between framework files allow interleaving
π Unnumbered DirectoriesΒΆ
| Directory | Convention | Example |
|---|---|---|
functions/ | name.zsh (descriptive) | dcopy.zsh, zopt.zsh |
widgets/ | name.zsh (descriptive) | aicmit.zsh, bufcopy.zsh |
bin/ | name (no extension, executable) | aicontext, zr |
private_functions/ | _name.zsh (underscore prefix) | _zshand_network_monitor.zsh |
π·οΈ Naming ConventionsΒΆ
π€ FunctionsΒΆ
| Scope | Convention | Example | Where |
|---|---|---|---|
| π Public | Short, memorable, lowercase | zcheck, zprime, dcopy | functions/, core/ |
| π Framework-internal | _az_ prefix | _az_safe_mode_enter, _az_zwc_stale_check | startup/, core/ |
| π Module-private | _zshand_ prefix | _zshand_profile_bundle_report | build/, private_functions/ |
| π§± Shared utility | Descriptive, snake_case | stderr_error, load_file_timed | shared_functions/ |
| β¨οΈ Widget | Short, descriptive | aicmit, bufcopy, aidebug | widgets/ |
| π§ Helper (file-local) | _name (single underscore) | _zlog_get_path, _fadd | Same file only |
π€ VariablesΒΆ
| Scope | Convention | Example |
|---|---|---|
| π Exported env | UPPER_SNAKE_CASE | ZSHAND_DEV_MODE, LOG_DIR |
| π Framework env | ZSHAND_ prefix | ZSHAND_CACHE_DIR, ZSHAND_PERF_BUDGET |
| π Internal global | _zshand_ prefix | _zshand_timings, _zshand_slow_files |
| π¦ Local | lower_snake_case | local cache_dir, local timing_log |
| π Loop | Short, contextual | local f, local hook, local bname |
| π Associative array | Descriptive, _ prefix for internal | typeset -gA _zshand_timings |
π€ Key Variable PrefixesΒΆ
| Prefix | Meaning | Example |
|---|---|---|
ZSHAND_ | Framework configuration (exported) | ZSHAND_DEBUG |
_ZSHAND_ | Internal framework state (not exported) | _ZSHAND_CACHE_DIR |
_az_ | Startup/internal function prefix | _az_safe_mode_enter |
_zshand_ | Module-level internal state | _zshand_full_bundle_loaded |
π Code PatternsΒΆ
β Error HandlingΒΆ
Use the shared functions for consistent error output:
# β
Good: Use shared functions
stderr_error "Configuration file not found: $config_file"
stderr_warn "Falling back to default settings"
# β Bad: Raw echo to stderr
echo "ERROR: file not found" >&2
β Guard AssertionsΒΆ
Use assertion functions before risky operations:
# β
Good: Assert before use
if assert_dir_exists "$ZSHAND/core" "Core directory"; then
source "$ZSHAND/core/02_vars.zsh"
fi
if assert_function_exists "zprime"; then
zprime
fi
# β Bad: Silent failure
[[ -d "$ZSHAND/core" ]] && source "$ZSHAND/core/02_vars.zsh"
β Graceful DegradationΒΆ
Functions should never crash the shell. Handle missing deps gracefully:
# β
Good: Check and degrade
my_function() {
(( $+commands[jq] )) || {
stderr_warn "jq not installed. Install: pacman -S jq"
return 1
}
# ... proceed with jq
}
# β Bad: Assume deps exist
my_function() {
jq '.key' "$file" # Crashes if jq missing
}
β Local VariablesΒΆ
Always declare variables local in functions:
# β
Good: Explicit local
my_function() {
local cache_dir="${ZSHAND_CACHE_DIR}"
local -a files=("$cache_dir"/*.zsh(N))
local -A timings=()
}
# β Bad: Implicit globals (leak into caller)
my_function() {
cache_dir="${ZSHAND_CACHE_DIR}"
files=("$cache_dir"/*.zsh(N))
}
β Global VariablesΒΆ
When a variable must be global, use typeset -g explicitly:
# β
Good: Explicit global declaration
typeset -g _zshand_full_bundle_loaded=0
typeset -gA _zshand_timings
# β Bad: Implicit global (confusing in function context)
_zshand_full_bundle_loaded=0
β Parameter ExpansionΒΆ
Prefer zsh parameter expansion over external commands:
# β
Good: Builtins
local basename="${path:t}" # basename
local dirname="${path:h}" # dirname
local extension="${file:e}" # extension
local no_ext="${file:r}" # remove extension
local absolute="${file:A}" # absolute path
# β Bad: Subprocesses
local basename="$(basename "$path")"
local dirname="$(dirname "$path")"
β Conditional PatternsΒΆ
# β
Good: Double-bracket test (zsh native)
[[ -f "$file" ]] && source "$file"
[[ -z "$var" ]] && var="default"
# β
Good: Arithmetic evaluation
(( ZSHAND_DEBUG )) && print "debug info"
(( $+commands[jq] )) || return 1
(( $+functions[zprime] )) && zprime
# β Bad: Single-bracket test (POSIX, slower)
[ -f "$file" ] && source "$file"
β Array PatternsΒΆ
# β
Good: Glob with nullglob qualifier
local -a files=("$dir"/*.zsh(N.)) # (N) = no error if empty, (.) = files only
# β
Good: Sorted glob with numeric sort
for f in "$dir"/*.zsh(nN.); do # (n) = numeric sort
source "$f"
done
# β Bad: Bare glob (errors on empty match)
for f in "$dir"/*.zsh; do
source "$f"
done
β Default ValuesΒΆ
# β
Good: Parameter expansion with default
local cache="${ZSHAND_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/zshand}"
# β
Good: Colon-equals for set-if-unset
: ${ZSHAND_PERF_BUDGET:=100}
# β Bad: Explicit if/else for defaults
if [[ -z "$ZSHAND_CACHE_DIR" ]]; then
local cache="$HOME/.cache/zshand"
else
local cache="$ZSHAND_CACHE_DIR"
fi
π Formatting RulesΒΆ
π IndentationΒΆ
- 2 spaces for indentation (no tabs)
- Enforced by
.editorconfigandshfmt
π Line LengthΒΆ
- 80 characters soft limit for code
- 78 characters for header divider content (after
#) - Long strings: break with
\continuation or use heredocs
π Blank LinesΒΆ
- 1 blank line between functions
- 1 blank line before section comments
- No trailing blank lines at end of file
- 1 trailing newline at end of file (enforced by
.editorconfig)
π QuotingΒΆ
# β
Always quote variable expansions in arguments
source "$ZSHAND/core/02_vars.zsh"
[[ -f "$config_file" ]]
print -P "%F{green}$message%f"
# β
Exception: Arithmetic context (no quotes needed)
(( ZSHAND_DEBUG ))
(( $+commands[jq] ))
# β Bad: Unquoted variables (word splitting risk)
source $ZSHAND/core/02_vars.zsh
[[ -f $config_file ]]
π¬ CommentsΒΆ
π Section CommentsΒΆ
Use the standard divider format for major sections within a file:
# ββ SECTION NAME ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Description of what this section does.
π Inline CommentsΒΆ
# β
Good: Explain WHY, not WHAT
local -gU path # Enforce unique entries to keep lookups fast
# β
Good: Explain zsh-specific idioms
[[ ${arr[(ie)$item]} -le ${#arr} ]] # (ie) = exact case-insensitive search
# β Bad: Restate the obvious
local count=0 # Set count to 0
π TODO/FIXMEΒΆ
# TODO: Add spec for ZSHAND_PROFILE_VERBOSE=1 output differences
# FIXME: This breaks when path contains newlines
# HACK: Workaround for zsh 5.0 compatibility (remove after 5.2+ minimum)
π Security PatternsΒΆ
π SecretsΒΆ
# β
Good: Secure file permissions
chmod 600 "$ZSHAND_CONFIG_DIR/secrets.zsh"
# β
Good: Never log secrets
_zrun --quiet "action" "description" # Telemetry without secrets
# β Bad: Echo secrets to log
echo "API_KEY=$API_KEY" >> "$LOG_DIR/debug.log"
π‘οΈ Input ValidationΒΆ
# β
Good: Validate before use
[[ -z "$1" ]] && { stderr_error "Missing argument"; return 1 }
[[ -d "$1" ]] || { stderr_error "Not a directory: $1"; return 1 }
# β
Good: Quote everything in eval-adjacent contexts
local cmd="${1:?Command required}"
π Print & Output PatternsΒΆ
π«§ Unified IO (Preferred)ΒΆ
All IO output should use the shared IO library (00_gum_io.zsh). This provides automatic gum enhancement when available, with graceful fallback.
# β
Preferred: Use IO library functions
stderr_error "Something failed" # Red error to stderr
stderr_warn "Something suspicious" # Yellow warning to stderr
echo_info "Processing..." # Blue info to stdout
echo_ok "Done" # Green success to stdout
# β
Rich output functions
az_banner "Module Title" # Double-border title banner
az_header "Section" # Bold section header
az_divider # Horizontal line
az_step 1 5 "Installing packages" # Step counter [1/5]
az_done "Complete!" # Green completion banner
az_note "Important: Install fonts" # Callout box
# β
Interactive functions
az_spin "Compiling..." zcompile f.zsh # Spinner with β/β
az_confirm "Continue?" && echo "yes" # Y/N prompt
selected=$(az_choose "A" "B" "C") # Selection menu
# β Avoid: Raw print -P for simple error/warn/info/ok messages
print -P "%F{red}ERROR: $message%f" >&2 # Use stderr_error instead
print -P "%F{yellow}Warning: $msg%f" >&2 # Use stderr_warn instead
π¨ Inline print -P (When Appropriate)ΒΆ
Inline print -P is still appropriate for complex multi-color display that cannot be expressed as a single function call:
# β
Acceptable: Complex dashboard display with multiple colors and variables
print -P " %F{cyan}System:%f Arch (%F{yellow}${session_type}%f) | Up: ${uptime}"
print -P " %F{green}%3d. [β]%f ${path_entry}"
# β
Acceptable: Interactive prompts with colored inline text
print -Pn "%F{cyan}σ°’ Continue? (y/n):%f "
# β
Acceptable: Template code written to files
cat <<EOF > "$file"
print -P "σ°¬ Executing ${name}..."
EOF
π Nerd Font IconsΒΆ
The framework uses Nerd Font icons consistently:
| Icon | Meaning | Usage |
|---|---|---|
| σ°¬ | Success / check | zcheck pass items |
| σ±Έ | Failure / error | zcheck fail items |
| σ°’ | Warning / stale | zcheck warnings |
| σ° | System / audit | Report headers |
| σ° | Sync / loading | Progress indicators |
| σ° | Directory | Directory operations |
| σ° | Bootstrap | Setup operations |
π Related DocumentsΒΆ
| Document | Purpose |
|---|---|
| π HEADER_STANDARD.md | Documentation header conventions |
| π€ CONTRIBUTING.md | Workflow and quality gate |
| ποΈ ARCHITECTURE.md | Directory structure and naming |
| β‘ PERFORMANCE.md | Builtin-over-subprocess patterns |
| π Documentation Index | Full doc nav |