|
#!/usr/bin/env bash |
|
usage() { cat <<'EOF' | fmt -70 |
|
ell: tiny portable shell environment: self installs, useful prompts/aliases/etc. |
|
Copyright (c) 2025 Tim Menzies, MIT License. |
|
https://opensource.org/licenses/MIT |
|
|
|
Usage: sh ell |
|
|
|
Motto: Own what matters (VITAL), skip what doesn't (YAGNI). |
|
|
|
Why ell? Because you don't want a manual— you want control. Not |
|
helpless, but motivated and enabled.You'd rather fix friction than |
|
live with it. It can be better and you can make it so. Such control |
|
should be easy, fast, portable: minimal setup, no dependencies, no |
|
root. Code should start, work, and leave nothing behind. |
|
|
|
But control doesn't mean building everything. YAGNI (You Ain't Gonna |
|
Need It) says skip unnecessary features. VITAL (Vital Infrastructure: |
|
Acquire Locally) says own what's critical. Your shell environment? |
|
Critical. Every flag in ls or awk? Not critical. |
|
|
|
When critical infrastructure lives elsewhere, you're exposed. |
|
Remember left-pad? Supply-chain attacks? Richard Hipp, creator of |
|
SQLite, understood this. He called it "backpacking"—carrying only |
|
what you need. He nearly built on Berkeley DB until Oracle bought |
|
it and started charging licensing fees. But Hipp didn't care— he'd |
|
already written his own engine. Now it's the most deployed database |
|
in history. Self-reliance at scale. |
|
|
|
ell is lightweight control. I don't reboot or replace my OS— I refine, |
|
then release, with nothing left behind. Containers add weight. Nix |
|
adds complexity. Package managers promised simplicity; now we script |
|
around them. |
|
|
|
ell is readable, portable, and teaches by showing: shell variables, |
|
directories, temp files, small tools playing nicely. Each trick |
|
opens a door to design and simplicity. Its methods can last decades. |
|
Your parents could have written this; your kids might fix it. But |
|
will they grasp (e.g.) .toml and why it is replacing setup.py? |
|
|
|
So why wrestle chaos when control can be simple? Take the ell |
|
challenge: strip it down, make it run, walk away clean. |
|
EOF |
|
} |
|
|
|
NEED="git python3 nvim gawk tree ruff pyright" |
|
OPTIONAL="bat cmatrix eza gawk htop micro ncdu tree watch yazi zellij" |
|
|
|
bold=$(tput bold) col0=$(tput sgr0) col1=$(tput setaf 6) col2=$(tput setaf 3) |
|
|
|
hi() { clear; echo "${col1}"; cat<<EOF |
|
.-. |
|
_/ ..\ |
|
( \ u/__ There is no escape... |
|
\ \__) ... from (sh)ell. |
|
/ \ |
|
__/ \ |
|
( _._.-._/ |
|
jgs '-' |
|
EOF |
|
echo "${col0}" |
|
} |
|
|
|
inst() { |
|
local m="" |
|
for p in $1; do command -v "$p" &>/dev/null || m+="$p "; done |
|
[ "$m" ] && case "$(uname -s)" in |
|
Darwin*) brew install $m ;; |
|
Linux*) sudo apt install -y $m ;; |
|
MINGW*) winget install $m ;; |
|
esac |
|
} |
|
|
|
# If executed (not sourced), start bash with this as init file |
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then |
|
hi |
|
inst $NEED |
|
exec bash --init-file "${BASH_SOURCE[0]}" -i |
|
fi |
|
|
|
# Core setup |
|
Here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
|
Xdir="$(basename "$(dirname "$Here")")" |
|
export BASH_SILENCE_DEPRECATION_WARNING=1 |
|
export PATH="$Here:$PATH" |
|
|
|
# Colors & prompt |
|
branch() { git branch 2>/dev/null | awk '/^\*/ {print $2}'; } |
|
dirty() { [[ -n $(git status -s 2>/dev/null) ]] && echo "*"; } |
|
PROMPT_COMMAND='PS1="${bold}${col1}$(basename "$(dirname "$PWD")")/$(basename "$PWD")${col0} ${col2}$(branch)$(dirty)${col0} ▶ "' |
|
|
|
# Essential aliases |
|
alias ..='cd ..' c='clear' Q='exit' |
|
alias l='ls -lh' la='ls -la' t='tree -L 1' ls="\ls --color" |
|
alias gs='git status -sb' ga='git add .' gp='git push' gl='git log --oneline --graph --decorate' |
|
alias gc='read -p "Commit message: " msg && git commit -am "$msg" && git push' |
|
alias h='history' |
|
alias py='python3 -B' |
|
alias tree='tree -hC' |
|
alias reload="source '$Here/ell' && echo ✅" |
|
|
|
# nvim control |
|
# - --clean: ignore config files, factory defaults |
|
# - number + relativenumber: show line numbers with distance from cursor |
|
# - cursorline: highlight current line | mouse=a: enable mouse in all modes |
|
# - clipboard=unnamedplus: yank/paste uses system clipboard |
|
# - ignorecase + smartcase: case-insensitive search unless capitals used |
|
# - expandtab: spaces not tabs | tabstop + shiftwidth=4: 4-space indents |
|
# - splitright + splitbelow: new splits open right/below (not left/above) |
|
# - undofile: persistent undo across sessions | undodir: where undo files live |
|
# - zaibatsu: color scheme |
|
# - laststatus=2: always show status bar |
|
# - statusline: custom format with file, position, percentage |
|
# - netrw(Lex): file browser. no banner, tree view (style 3), split to side, 15% width |
|
# - Q key: quit all buffers/windows at once |
|
vi() { |
|
nvim --clean \ |
|
--cmd "set number relativenumber cursorline mouse=a clipboard=unnamedplus ignorecase smartcase" \ |
|
--cmd "set expandtab tabstop=2 shiftwidth=2 splitright splitbelow" \ |
|
--cmd "set undofile undodir=~/.vim/undo" \ |
|
--cmd "colorscheme zaibatsu" \ |
|
--cmd "set laststatus=2" \ |
|
--cmd "set statusline=%#StatusLine#\ ▶\ %f\ %m%r%=%y\ ❖\ %l:%c\ ❖\ %p%%\ " \ |
|
--cmd "let g:netrw_banner=0 | let g:netrw_liststyle=3 | let g:netrw_browse_split=4 | let g:netrw_winsize=15" \ |
|
--cmd "nnoremap Q :quitall<CR>" \ |
|
"$@" |
|
} |
|
|
|
_TMP=$(mktemp -d) |
|
alias mu="micro -config-dir '$_TMP'" |
|
trap "rm -rf '$_TMP'" EXIT INT TERM |
|
|
|
cat > "$_TMP/settings.json" <<'EOF' |
|
{ "colorscheme": "atom-dark", |
|
"tabsize": 2, |
|
"tabstospaces": true, |
|
"ruler": true, |
|
"mouse": true, |
|
"autoindent": true, |
|
"cursorline": true, |
|
"statusline": true, |
|
"savecursor": true, |
|
"saveundo": true |
|
} |
|
EOF |
|
|
|
# History improvements |
|
export HISTSIZE=10000 |
|
export HISTFILESIZE=20000 |
|
export HISTCONTROL=ignoredups:erasedups # No duplicates |
|
|
|
# Mega useful extras |
|
alias grep='grep --color=auto' less='less -R' |
|
alias ports='lsof -i -P -n | grep LISTEN' |
|
alias myip='curl -s ipinfo.io/ip' |
|
|
|
mkcd() { mkdir -p "$1" && cd "$1"; } |
|
|
|
plot() { plot -p -e 'plot "-"'; } |
|
|
|
checks() { for f in *.py; do check $f; done; } |
|
|
|
check() { |
|
# E,F=errors, B=bugs, I=imports, N=naming, UP=modern syntax |
|
# ignore: E501=line-length, E701/2=multi-statement, E731=lambda |
|
# for more rules, see https://docs.astral.sh/ruff/rules/ |
|
echo $f |
|
ruff check \ |
|
--select=E,F,B,I,N,UP \ |
|
--ignore=E501,E701,E702,E731 \ |
|
--output-format=concise "$1" 2>&1 \ |
|
| grep -v "All checks passed" |
|
pyright "$1" 2>&1 | grep -E "^\s+.*:\d+:\d+" |
|
} |
|
|
|
getsMac() { |
|
inst $OPTIONAL |
|
brew install --cask font-fira-code-nerd-font |
|
alias ls='eza --icons' |
|
alias cat='bat -p' |
|
} |
|
|
|
# Create a bash init script with MIT license that: |
|
# - Checks for required tools (git, python3, nvim, gawk, tree, ruff, pyright) |
|
# - Shows ASCII art greeting when launched |
|
# - Can be executed directly or sourced |
|
# - Sets up colorized prompt showing: parent/current dir and git branch |
|
# - Includes essential aliases for: navigation, git workflow, file listing |
|
# - Tries to work wth off-the-shelf config with minimal installs (currently, none) |
|
# - Don't overwrite existing config files; instead, make config in temp dirs |
|
# - Creates a vi() function wrapping nvim with: line numbers, mouse support, |
|
# system clipboard, smart search, 4-space tabs, persistent undo, custom |
|
# statusline, and configured netrw file browser |
|
# - Creates a micro() function with useful defaults from that editor. |
|
# - Add comprehensive inline comments explaining each nvim setting |
|
# - Include history improvements (no duplicates, larger size) |
|
# - Add utility functions: mkcd, plot, check (ruff+pyright) |
|
# - Use tput for colors, keep style compact and professional |
At the end of ell, after all function definitions, let ell run command line args
then use ell to debug pre-commit hooks before adding them them to .git/hooks/pre-commit