Commit

Author:

Hash:

Timestamp:

+161 -16 +/-6 browse

Kevin Schoon [me@kevinschoon.com]

cc3b054584ad85a459f983a9535b2ec6f64ac5d8

Wed, 13 May 2026 19:38:55 +0000 (3 weeks ago)

improve man page and shell completion for packagers
1diff --git a/Cargo.lock b/Cargo.lock
2index 610876f..de57c6c 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -614,6 +614,15 @@ dependencies = [
6 ]
7
8 [[package]]
9+ name = "clap_complete"
10+ version = "4.6.5"
11+ source = "registry+https://github.com/rust-lang/crates.io-index"
12+ checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772"
13+ dependencies = [
14+ "clap",
15+ ]
16+
17+ [[package]]
18 name = "clap_derive"
19 version = "4.6.1"
20 source = "registry+https://github.com/rust-lang/crates.io-index"
21 @@ -3996,6 +4005,7 @@ version = "0.1.0"
22 dependencies = [
23 "ayllu_cmd",
24 "clap",
25+ "clap_complete",
26 "clap_mangen",
27 ]
28
29 diff --git a/ayllu.7.scd b/ayllu.7.scd
30new file mode 100644
31index 0000000..cfa4952
32--- /dev/null
33+++ b/ayllu.7.scd
34 @@ -0,0 +1,38 @@
35+ AYLLU(7)
36+
37+ # NAME
38+
39+ *Ayllu* The Hyper Performant & Hackable Code Forge
40+
41+ The name *Ayllu* _/ˈajʎu/_, _eye-joo_ is the Quechua word for the traditional
42+ form of a community in the Andes region of South America, particularly in
43+ Bolivia and Peru.
44+
45+ # OVERVIEW
46+
47+ Ayllu is a lightweight code forge oriented towards multi-user single server
48+ environments.
49+
50+ # COMPONENTS
51+
52+ Ayllu is shipped with several distinct binaries which are composed together.
53+
54+ [[ *Executable*
55+ :- Description
56+ | *ayllu-init*
57+ : PID 1 process mapped into build containers
58+ | *ayllu-build*
59+ : OCI based continuous integration system
60+ | *ayllu-keys*
61+ : OpenSSH AuthorizedKeysCommand shim script
62+ | *ayllu-migrate
63+ : Database migration tool
64+ | *ayllu-shell
65+ : Restricted shell for SSH based administration
66+ | *ayllu-web
67+ : Tokio powered web interface for Ayllu
68+ | *quipu
69+ : CLI tool to interact with local and remote Ayllu instances
70+
71+
72+
73 diff --git a/crates/cmd/src/ayllu.rs b/crates/cmd/src/ayllu.rs
74index 4ce3eb0..2ec0175 100644
75--- a/crates/cmd/src/ayllu.rs
76+++ b/crates/cmd/src/ayllu.rs
77 @@ -17,7 +17,7 @@ be served.
78 #[clap(
79 version,
80 about,
81- name = "ayllu",
82+ name = "ayllu-web",
83 long_about = LONG_ABOUT_DESCRIPTION,
84
85 )]
86 diff --git a/crates/cmd/src/init.rs b/crates/cmd/src/init.rs
87index 7dbd78b..59b702b 100644
88--- a/crates/cmd/src/init.rs
89+++ b/crates/cmd/src/init.rs
90 @@ -4,6 +4,18 @@ const LONG_ABOUT_DESCRIPTION: &str = r#"
91
92 ayllu-init is a tiny init process which is mapped inside build containers which
93 is responsible for initializing the build environment.
94+
95+ This binary is mapped inside each container build instance as a read-only
96+ binary. When first run the program will clone a git bundle from a well-known
97+ path of `/_ayllu/src.bundle` and extract it into the `/src` directory. Once
98+ the environment has been initialized a file will be created at
99+ /tmp/ayllu-build-ready.
100+
101+ If the --check flag is given this command will block until the
102+ /tmp/ayllu-build-ready file has been created and then exit normally. This is
103+ meant to be used for OCI health checks so that ayllu-build can synchronize
104+ the container state before executing steps.
105+
106 "#;
107
108 /// ayllu-init process
109 diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
110index dcc91e1..55eaf72 100644
111--- a/xtask/Cargo.toml
112+++ b/xtask/Cargo.toml
113 @@ -6,4 +6,5 @@ edition = "2024"
114 [dependencies]
115 ayllu_cmd = { path = "../crates/cmd" }
116 clap = {workspace = true}
117+ clap_complete.workspace = true
118 clap_mangen = "0.2.31"
119 diff --git a/xtask/src/main.rs b/xtask/src/main.rs
120index 2fe548e..c0d3830 100644
121--- a/xtask/src/main.rs
122+++ b/xtask/src/main.rs
123 @@ -1,27 +1,31 @@
124 use std::{
125 env,
126 fs::OpenOptions,
127- io::BufWriter,
128+ io::{BufWriter, Write},
129 path::{Path, PathBuf},
130+ process::{Command, Stdio},
131 };
132
133- const ARTIFACT_DIR: &str = "target/dist";
134+ use clap_complete::{
135+ aot::{Bash, Zsh},
136+ generate_to,
137+ };
138+
139+ const DIST_COMPLETE_PATH: &str = "target/dist/completion";
140+ const DIST_MAN_PATH: &str = "target/dist/man";
141
142- fn target_dir() -> PathBuf {
143+ const AYLLU_SUMMARY: &[u8] = include_bytes!("../../ayllu.7.scd");
144+
145+ fn target_dir(base: &str) -> PathBuf {
146 Path::new(&env!("CARGO_MANIFEST_DIR"))
147 .ancestors()
148 .nth(1)
149 .unwrap()
150- .join(ARTIFACT_DIR)
151+ .join(base)
152 .to_path_buf()
153 }
154
155- fn man_gen<T>(target: &Path) -> std::io::Result<()>
156- where
157- T: clap::Parser,
158- {
159- let as_cmd = T::command();
160- let man = clap_mangen::Man::new(as_cmd);
161+ fn open(target: &Path) -> Result<std::fs::File, std::io::Error> {
162 if let Some(parent) = target.parent() {
163 std::fs::create_dir_all(parent)?;
164 };
165 @@ -30,20 +34,100 @@ where
166 .write(true)
167 .truncate(true)
168 .open(target)?;
169+ Ok(fp)
170+ }
171+
172+ // HACK: Shell out to scdoc.
173+ fn scdoc_gen(input: &[u8], dst: &Path) -> std::io::Result<()> {
174+ let mut fp = Command::new("scdoc")
175+ .stdin(Stdio::piped())
176+ .stdout(Stdio::piped())
177+ .spawn()?;
178+ let stdin = fp.stdin.as_mut().unwrap();
179+ stdin.write_all(input)?;
180+ let output = fp.wait_with_output()?;
181+ assert!(output.status.success());
182+ std::fs::write(dst, output.stdout.as_slice())?;
183+ Ok(())
184+ }
185+
186+ fn man_gen<T>(base_dir: &Path) -> std::io::Result<()>
187+ where
188+ T: clap::Parser,
189+ {
190+ let cmd = T::command();
191+ let base_name = cmd.get_name();
192+ let target = base_dir.join(format!("{base_name}.1"));
193+ let man = clap_mangen::Man::new(cmd.clone());
194+ println!("Writing {target:?}");
195+ let fp = open(&target)?;
196 let mut buf = BufWriter::new(&fp);
197 man.render(&mut buf)?;
198 fp.sync_data()?;
199
200+ for subcommand in cmd.get_subcommands() {
201+ let name = subcommand.get_name();
202+ let target = base_dir.join(format!("{base_name}-{name}.1"));
203+ println!("Writing {target:?}");
204+ let man = clap_mangen::Man::new(subcommand.clone());
205+ let fp = open(&target)?;
206+ let mut buf = BufWriter::new(&fp);
207+ man.render(&mut buf)?;
208+ }
209+
210+ Ok(())
211+ }
212+
213+ fn complete_gen<T>(target: &Path, name: &str) -> std::io::Result<()>
214+ where
215+ T: clap::Parser,
216+ {
217+ let mut cmd = T::command();
218+ _ = generate_to(Bash, &mut cmd, name, target)?;
219+ _ = generate_to(Zsh, &mut cmd, name, target)?;
220 Ok(())
221 }
222
223 fn main() -> Result<(), Box<dyn std::error::Error>> {
224 match env::args().nth(1).as_deref() {
225- Some("man_gen") => {
226- man_gen::<ayllu_cmd::ayllu::Command>(&target_dir().join("ayllu.1"))?;
227- man_gen::<ayllu_cmd::quipu::Command>(&target_dir().join("quipu.1"))?;
228- man_gen::<ayllu_cmd::shell::Command>(&target_dir().join("ayllu-shell.1"))?;
229- man_gen::<ayllu_cmd::keys::Command>(&target_dir().join("ayllu-keys.1"))?;
230+ Some("dist") => {
231+ let man_dir = target_dir(DIST_MAN_PATH);
232+ // Binary man pages
233+ man_gen::<ayllu_cmd::ayllu::Command>(&man_dir)?;
234+ man_gen::<ayllu_cmd::shell::Command>(&man_dir)?;
235+ man_gen::<ayllu_cmd::keys::Command>(&man_dir)?;
236+ man_gen::<ayllu_cmd::migrate::Command>(&man_dir)?;
237+ man_gen::<ayllu_cmd::build::Command>(&man_dir)?;
238+ man_gen::<ayllu_cmd::init::Command>(&man_dir)?;
239+ man_gen::<ayllu_cmd::quipu::Command>(&man_dir)?;
240+
241+ // Misc man pages
242+ scdoc_gen(AYLLU_SUMMARY, &target_dir(DIST_MAN_PATH).join("ayllu.7"))?;
243+
244+ // Completion scripts
245+ std::fs::create_dir_all(target_dir(DIST_COMPLETE_PATH))?;
246+ complete_gen::<ayllu_cmd::quipu::Command>(&target_dir(DIST_COMPLETE_PATH), "quipu")?;
247+ complete_gen::<ayllu_cmd::shell::Command>(
248+ &target_dir(DIST_COMPLETE_PATH),
249+ "ayllu-shell",
250+ )?;
251+ complete_gen::<ayllu_cmd::keys::Command>(
252+ &target_dir(DIST_COMPLETE_PATH),
253+ "ayllu-keys",
254+ )?;
255+ complete_gen::<ayllu_cmd::migrate::Command>(
256+ &target_dir(DIST_COMPLETE_PATH),
257+ "ayllu-migrate",
258+ )?;
259+ complete_gen::<ayllu_cmd::build::Command>(
260+ &target_dir(DIST_COMPLETE_PATH),
261+ "ayllu-build",
262+ )?;
263+ complete_gen::<ayllu_cmd::init::Command>(
264+ &target_dir(DIST_COMPLETE_PATH),
265+ "ayllu-init",
266+ )?;
267+
268 Ok(())
269 }
270 _ => unimplemented!(),