graft

graft: a simple package manager
git clone git://git.rr3.xyz/graft | git clone gits://git.rr3.xyz/graft
Log | Files | Refs | README | LICENSE

commit af271b39941c887cdaa27b865329f29757f84b1b
parent c516781ad1ac2b3a3d54f312ebd19d223bcedb7b
Author: Robert Russell <robert@rr3.xyz>
Date:   Sun, 26 Jul 2026 18:58:36 -0700

Create man page

Diffstat:
A.gitignore | 2++
MMakefile | 24+++++++++++++++++++++---
MREADME | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Agen-example-trace.sh | 17+++++++++++++++++
Agraft.1.template | 27+++++++++++++++++++++++++++
5 files changed, 181 insertions(+), 11 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +example-trace.txt +graft.1 diff --git a/Makefile b/Makefile @@ -2,15 +2,33 @@ PREFIX = /usr/local BINDIR = $(PREFIX)/bin +MANDIR = $(PREFIX)/share/man +MAN1DIR = $(MANDIR)/man1 -all: +all: README graft.1 -install: +README: graft.1 + mandoc -O width=80 ./graft.1 | col -bx > README + +graft.1: graft.1.template example-trace.txt + sed -e '/^{example-trace.txt}$$/{r example-trace.txt' -e 'd}' < graft.1.template > graft.1 + +example-trace.txt: graft gen-example-trace.sh + ./gen-example-trace.sh > example-trace.txt + +install: graft graft.1 mkdir -p $(DESTDIR)$(BINDIR) cp -f graft $(DESTDIR)$(BINDIR)/graft chmod 755 $(DESTDIR)$(BINDIR)/graft + mkdir -p $(DESTDIR)$(MAN1DIR) + cp -f graft.1 $(DESTDIR)$(MAN1DIR)/graft.1 + chmod 644 $(DESTDIR)$(MAN1DIR)/graft.1 uninstall: rm -f $(DESTDIR)$(BINDIR)/graft + rm -f $(DESTDIR)$(MAN1DIR)/graft.1 + +clean: + rm -f README graft.1 example-trace.txt -.PHONY: noop install uninstall +.PHONY: all install uninstall clean diff --git a/README b/README @@ -1,11 +1,117 @@ -Graft: a simple "package manager" +GRAFT(1) User Commands GRAFT(1) -Graft does not handle remote fetching, build steps, versions, dependencies, -package removal, or most other features you would expect from a "package -manager" (hence the scare quotes). Graft merely links or copies -files/directories into place and then prints what happened. +NAME + graft - simple "package manager" -You specify package installation steps in a `.graft.sh` script at the top-level -of the package. See `example-pkg`. +SYNOPSIS + graft [-hn] ROOT PKGS... -Graft is mainly intended for configuration file ("dotfile") management. +DESCRIPTION + graft is a simple "package manager" that merely links or copies + files/directories into place and then prints what happened. + + graft does not handle remote fetching, build steps, versions, + dependencies, package removal, or most other features you would expect + from a "package manager" (hence the scare quotes). + +OPTIONS + -h Display a help message. + + -n Enable dry-run mode, causing graft to not write any files but + still print-out what it would do. (More precisely, this only + affects the link and copy functions. .graft.sh scripts can still + do anything.) + +NOTES + graft is mainly intended for configuration file ("dotfile") management. + +EXAMPLE + $ tree -a --noreport example-pkg + example-pkg + ├── .graft.sh + ├── a + │   ├── 0.txt + │   ├── 1.txt + │   └── 2.txt + ├── b + │   ├── 0.txt + │   ├── 1.txt + │   └── 2.txt + ├── bar.txt + └── foo.txt + + $ cat example-pkg/.graft.sh + # Print messages during install: + msg_note "This is an important note (e.g., make sure you manually do this thing)" + msg_info "This is information" + msg_warning "This is a warning" + msg_error "This is an error (not fatal)" + # msg_fatal "This is an error (fatal)" + + # Link files/directories: + link foobar/foo.txt foo.txt + link a-link a + for f in b/*; do + link b-links/"$(basename "$f")" "$f" + done + + # Copy files/directories: + copy foobar/bar.txt bar.txt + copy a-copy a + for f in b/*; do + copy b-copies/"$(basename "$f")" "$f" + done + + $ graft example-root example-pkg + [example-pkg] NOTE: This is an important note (e.g., make sure you manually do this thing) + [example-pkg] INFO: This is information + [example-pkg] WARNING: This is a warning + [example-pkg] ERROR: This is an error (not fatal) + [example-pkg] LINK CREATE: /<omitted>/example-root/foobar/foo.txt --> foo.txt + [example-pkg] LINK CREATE: /<omitted>/example-root/a-link --> a + [example-pkg] LINK CREATE: /<omitted>/example-root/b-links/0.txt --> b/0.txt + [example-pkg] LINK CREATE: /<omitted>/example-root/b-links/1.txt --> b/1.txt + [example-pkg] LINK CREATE: /<omitted>/example-root/b-links/2.txt --> b/2.txt + [example-pkg] COPY CREATE: /<omitted>/example-root/foobar/bar.txt <-- bar.txt + [example-pkg] COPY CREATE: /<omitted>/example-root/a-copy <-- a + [example-pkg] COPY CREATE: /<omitted>/example-root/b-copies/0.txt <-- b/0.txt + [example-pkg] COPY CREATE: /<omitted>/example-root/b-copies/1.txt <-- b/1.txt + [example-pkg] COPY CREATE: /<omitted>/example-root/b-copies/2.txt <-- b/2.txt + + $ tree -a --noreport example-root + example-root + ├── a-copy + │   ├── 0.txt + │   ├── 1.txt + │   └── 2.txt + ├── a-link -> /<omitted>/example-pkg/a + ├── b-copies + │   ├── 0.txt + │   ├── 1.txt + │   └── 2.txt + ├── b-links + │   ├── 0.txt -> /<omitted>/example-pkg/b/0.txt + │   ├── 1.txt -> /<omitted>/example-pkg/b/1.txt + │   └── 2.txt -> /<omitted>/example-pkg/b/2.txt + └── foobar + ├── bar.txt + └── foo.txt -> /<omitted>/example-pkg/foo.txt + + $ graft example-root example-pkg # Idempotent + [example-pkg] NOTE: This is an important note (e.g., make sure you manually do this thing) + [example-pkg] INFO: This is information + [example-pkg] WARNING: This is a warning + [example-pkg] ERROR: This is an error (not fatal) + [example-pkg] LINK OK: /<omitted>/example-root/foobar/foo.txt --> foo.txt + [example-pkg] LINK OK: /<omitted>/example-root/a-link --> a + [example-pkg] LINK OK: /<omitted>/example-root/b-links/0.txt --> b/0.txt + [example-pkg] LINK OK: /<omitted>/example-root/b-links/1.txt --> b/1.txt + [example-pkg] LINK OK: /<omitted>/example-root/b-links/2.txt --> b/2.txt + [example-pkg] COPY OK: /<omitted>/example-root/foobar/bar.txt <-- bar.txt + [example-pkg] COPY OK: /<omitted>/example-root/a-copy <-- a + [example-pkg] COPY OK: /<omitted>/example-root/b-copies/0.txt <-- b/0.txt + [example-pkg] COPY OK: /<omitted>/example-root/b-copies/1.txt <-- b/1.txt + [example-pkg] COPY OK: /<omitted>/example-root/b-copies/2.txt <-- b/2.txt + + +graft 2026-07-26 GRAFT(1) diff --git a/gen-example-trace.sh b/gen-example-trace.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -euo pipefail + +run() { + printf '$ %s\n' "$1" + eval "$1" 2>&1 | sed -E 's:/.*/example-(root|pkg):/<omitted>/example-\1:' + printf '\n' +} + +mkdir -p example-root +trap 'rm -rf example-root' EXIT + +run 'tree -a --noreport example-pkg' +run 'cat example-pkg/.graft.sh' +PATH=".:$PATH" run 'graft example-root example-pkg' +run 'tree -a --noreport example-root' +PATH=".:$PATH" run 'graft example-root example-pkg # Idempotent' diff --git a/graft.1.template b/graft.1.template @@ -0,0 +1,27 @@ +.TH "GRAFT" "1" "2026-07-26" "graft" "User Commands" +.SH NAME +graft \- simple "package manager" +.SH SYNOPSIS +\fBgraft\fR [\-hn] \fIROOT\fR \fIPKGS\fR... +.SH DESCRIPTION +\fBgraft\fR is a simple "package manager" that merely links or copies +files/directories into place and then prints what happened. +.P +\fBgraft\fR does not handle remote fetching, build steps, versions, +dependencies, package removal, or most other features you would expect +from a "package manager" (hence the scare quotes). +.SH OPTIONS +.TP +\fB\-h\fR +Display a help message. +.TP +\fB\-n\fR +Enable dry-run mode, causing \fBgraft\fR to not write any files but still +print-out what it would do. (More precisely, this only affects the \fIlink\fR +and \fIcopy\fR functions. \fI.graft.sh\fR scripts can still do anything.) +.SH NOTES +\fBgraft\fR is mainly intended for configuration file ("dotfile") management. +.SH EXAMPLE +.EX +{example-trace.txt} +.EE