Skip to content

Commit 0f3d0ef

Browse files
committed
feat: add man page generation to all crates and fix CI/CD workflows
Add clap_mangen and --generate-man support to git-ledger and git-chain, matching the existing pattern in git-metadata. feat: add man page generation to git-ledger and git-chain fix: broaden CD tag regex to match all three crate prefixes fix: CI man job uses checkout@v4 and generates for all crates chore: remove redundant CI caching (handled by setup-rust-toolchain) Assisted-by: Zed (Claude Opus 4.6)
1 parent 57c12f7 commit 0f3d0ef

7 files changed

Lines changed: 153 additions & 24 deletions

File tree

.github/workflows/CD.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
if: github.event_name == 'release'
2222
run: |
2323
TAG="${{ github.event.release.tag_name }}"
24-
if [[ "$TAG" =~ ^git-metadata-v[0-9]+\.[0-9]+\.[0-9]+(-.*)?$ ]]; then
24+
if [[ "$TAG" =~ ^(git-metadata|git-ledger|git-chain)-v[0-9]+\.[0-9]+\.[0-9]+(-.*)?$ ]]; then
2525
echo "match=true" >> "$GITHUB_OUTPUT"
2626
else
2727
echo "match=false" >> "$GITHUB_OUTPUT"

.github/workflows/CI.yml

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -110,26 +110,11 @@ jobs:
110110
- uses: actions-rust-lang/setup-rust-toolchain@v1
111111
with:
112112
cache: true
113-
- name: Cache Cargo registry
114-
uses: actions/cache@v4
115-
with:
116-
path: |
117-
~/.cargo/registry/index/
118-
~/.cargo/registry/cache/
119-
~/.cargo/git/db/
120-
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
121-
restore-keys: |
122-
${{ runner.os }}-cargo-registry-
123-
- name: Cache Cargo build
124-
uses: actions/cache@v4
125-
with:
126-
path: target
127-
key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}
128-
restore-keys: |
129-
${{ runner.os }}-cargo-build-
130113
- name: Generate man pages
131114
run: |
132115
cargo run --package git-metadata -- --generate-man target/debug/man
116+
cargo run --package git-ledger -- --generate-man target/debug/man
117+
cargo run --package git-chain -- --generate-man target/debug/man
133118
134119
ci:
135120
name: CI

Cargo.lock

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/git-chain/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ path = "src/main.rs"
1616

1717
[dependencies]
1818
clap = { version = "4.5.60", features = ["derive"] }
19+
clap_mangen = "0.2.31"
1920
git2 = "0.20.4"
2021

2122
[dev-dependencies]

crates/git-chain/src/main.rs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
mod cli;
22

3-
use clap::Parser;
3+
use clap::{CommandFactory, Parser};
44
use cli::{Cli, Command};
55
use git_chain::Chain;
66
use git2::{Oid, Repository};
7-
use std::path::Path;
7+
use std::path::{Path, PathBuf};
88
use std::process;
99

1010
fn main() {
11+
if let Some(dir) = parse_generate_man_flag() {
12+
if let Err(e) = generate_man_page(dir) {
13+
eprintln!("Error: {}", e);
14+
process::exit(1);
15+
}
16+
return;
17+
}
18+
1119
let cli = Cli::parse();
1220

1321
if let Err(e) = run(&cli) {
@@ -90,3 +98,64 @@ fn run(cli: &Cli) -> Result<(), Box<dyn std::error::Error>> {
9098

9199
Ok(())
92100
}
101+
102+
/// Check for `--generate-man <DIR>` before clap parses, so it doesn't
103+
/// conflict with the required subcommand.
104+
fn parse_generate_man_flag() -> Option<PathBuf> {
105+
let args: Vec<String> = std::env::args().collect();
106+
let pos = args.iter().position(|a| a == "--generate-man")?;
107+
let dir = args
108+
.get(pos + 1)
109+
.map(PathBuf::from)
110+
.unwrap_or_else(default_man_dir);
111+
Some(dir)
112+
}
113+
114+
fn default_man_dir() -> PathBuf {
115+
std::env::var_os("XDG_DATA_HOME")
116+
.map(PathBuf::from)
117+
.unwrap_or_else(|| {
118+
let home = std::env::var_os("HOME").expect("HOME is not set");
119+
PathBuf::from(home).join(".local/share")
120+
})
121+
.join("man")
122+
}
123+
124+
fn generate_man_page(output_dir: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
125+
let man1_dir = output_dir.join("man1");
126+
std::fs::create_dir_all(&man1_dir)?;
127+
128+
let cmd = Cli::command();
129+
let man = clap_mangen::Man::new(cmd);
130+
let mut buffer = Vec::new();
131+
man.render(&mut buffer)?;
132+
133+
let man_path = man1_dir.join("git-chain.1");
134+
std::fs::write(&man_path, buffer)?;
135+
136+
let output_dir = output_dir.canonicalize()?;
137+
eprintln!("Wrote man page to {}", man_path.canonicalize()?.display());
138+
139+
if !manpath_covers(&output_dir) {
140+
eprintln!();
141+
eprintln!("You may need to add this to your shell environment:");
142+
eprintln!();
143+
eprintln!(" export MANPATH=\"{}:$MANPATH\"", output_dir.display());
144+
}
145+
Ok(())
146+
}
147+
148+
fn manpath_covers(dir: &std::path::Path) -> bool {
149+
let Some(manpath) = std::env::var_os("MANPATH") else {
150+
return false;
151+
};
152+
for component in std::env::split_paths(&manpath) {
153+
let Ok(component) = component.canonicalize() else {
154+
continue;
155+
};
156+
if dir.starts_with(&component) {
157+
return true;
158+
}
159+
}
160+
false
161+
}

crates/git-ledger/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ path = "src/main.rs"
1616

1717
[dependencies]
1818
clap = { version = "4.5.60", features = ["derive"] }
19+
clap_mangen = "0.2.31"
1920
git2 = "0.20.4"
2021

2122
[dev-dependencies]

crates/git-ledger/src/main.rs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
mod cli;
22

3-
use clap::Parser;
3+
use clap::{CommandFactory, Parser};
44
use cli::{Cli, Command};
55
use git_ledger::{IdStrategy, Ledger, Mutation};
66
use git2::Repository;
77
use std::io::Read;
8-
use std::path::Path;
8+
use std::path::{Path, PathBuf};
99
use std::process;
1010

1111
fn main() {
12+
if let Some(dir) = parse_generate_man_flag() {
13+
if let Err(e) = generate_man_page(dir) {
14+
eprintln!("Error: {}", e);
15+
process::exit(1);
16+
}
17+
return;
18+
}
19+
1220
let cli = Cli::parse();
1321

1422
if let Err(e) = run(&cli) {
@@ -148,3 +156,66 @@ fn run(cli: &Cli) -> Result<(), Box<dyn std::error::Error>> {
148156

149157
Ok(())
150158
}
159+
160+
/// Check for `--generate-man <DIR>` before clap parses, so it doesn't
161+
/// conflict with the required subcommand.
162+
fn parse_generate_man_flag() -> Option<PathBuf> {
163+
let args: Vec<String> = std::env::args().collect();
164+
let pos = args.iter().position(|a| a == "--generate-man")?;
165+
let dir = args
166+
.get(pos + 1)
167+
.map(PathBuf::from)
168+
.unwrap_or_else(default_man_dir);
169+
Some(dir)
170+
}
171+
172+
fn default_man_dir() -> PathBuf {
173+
std::env::var_os("XDG_DATA_HOME")
174+
.map(PathBuf::from)
175+
.unwrap_or_else(|| {
176+
let home = std::env::var_os("HOME").expect("HOME is not set");
177+
PathBuf::from(home).join(".local/share")
178+
})
179+
.join("man")
180+
}
181+
182+
fn generate_man_page(output_dir: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
183+
let man1_dir = output_dir.join("man1");
184+
std::fs::create_dir_all(&man1_dir)?;
185+
186+
let cmd = Cli::command();
187+
let man = clap_mangen::Man::new(cmd);
188+
let mut buffer = Vec::new();
189+
man.render(&mut buffer)?;
190+
191+
let man_path = man1_dir.join("git-ledger.1");
192+
std::fs::write(&man_path, buffer)?;
193+
194+
let output_dir = output_dir.canonicalize()?;
195+
eprintln!("Wrote man page to {}", man_path.canonicalize()?.display());
196+
197+
if !manpath_covers(&output_dir) {
198+
eprintln!();
199+
eprintln!("You may need to add this to your shell environment:");
200+
eprintln!();
201+
eprintln!(" export MANPATH=\"{}:$MANPATH\"", output_dir.display());
202+
}
203+
Ok(())
204+
}
205+
206+
/// Returns `true` if `dir` is equal to, or a subdirectory of, any component
207+
/// in the `MANPATH` environment variable.
208+
fn manpath_covers(dir: &std::path::Path) -> bool {
209+
let Some(manpath) = std::env::var_os("MANPATH") else {
210+
return false;
211+
};
212+
for component in std::env::split_paths(&manpath) {
213+
let Ok(component) = component.canonicalize() else {
214+
continue;
215+
};
216+
if dir.starts_with(&component) {
217+
return true;
218+
}
219+
}
220+
false
221+
}

0 commit comments

Comments
 (0)