Skip to content

Commit 38fee59

Browse files
author
damon
committed
feat: a proper Unix tool deserves a proper man page
Add build-time man page generation via rust-cli/man. The build.rs produces batdoc.1 roff output on every cargo build and all packaging (deb, rpm, apk, arch, homebrew, tar.gz) now installs it. Bump v1.3.1.
1 parent 7bed810 commit 38fee59

10 files changed

Lines changed: 139 additions & 3 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ jobs:
6666
run: |
6767
mkdir -p batdoc_${{ env.VERSION }}_${{ matrix.arch }}
6868
cp target/${{ matrix.target }}/release/batdoc batdoc_${{ env.VERSION }}_${{ matrix.arch }}/
69+
cp target/man/batdoc.1 batdoc_${{ env.VERSION }}_${{ matrix.arch }}/
6970
cp LICENSE README.md batdoc_${{ env.VERSION }}_${{ matrix.arch }}/
7071
tar czf batdoc_${{ env.VERSION }}_${{ matrix.arch }}.tar.gz batdoc_${{ env.VERSION }}_${{ matrix.arch }}/
7172

Cargo.lock

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "batdoc"
3-
version = "1.3.0"
3+
version = "1.3.1"
44
edition = "2021"
55
description = "cat(1) for doc, docx, xls, xlsx, pptx, and pdf -- renders to markdown with bat"
66
license = "MIT"
@@ -25,6 +25,9 @@ pdf-extract = "0.10"
2525
thiserror = "2"
2626
zip = { version = "2", default-features = false, features = ["deflate"] }
2727

28+
[build-dependencies]
29+
man = "0.3"
30+
2831
[profile.release]
2932
strip = true
3033
lto = "thin"
@@ -39,5 +42,6 @@ section = "utils"
3942
priority = "optional"
4043
assets = [
4144
["target/release/batdoc", "usr/bin/", "755"],
45+
["target/man/batdoc.1", "usr/share/man/man1/batdoc.1", "644"],
4246
["README.md", "usr/share/doc/batdoc/README", "644"],
4347
]

Dockerfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ COPY . .
3737
RUN cargo build --release --locked
3838

3939
# Assemble the .deb with fpm-free dpkg-deb
40-
RUN mkdir -p dpkg/usr/bin dpkg/usr/share/doc/batdoc dpkg/DEBIAN /out \
40+
RUN mkdir -p dpkg/usr/bin dpkg/usr/share/doc/batdoc dpkg/usr/share/man/man1 dpkg/DEBIAN /out \
4141
&& install -m755 target/release/batdoc dpkg/usr/bin/batdoc \
42+
&& install -m644 target/man/batdoc.1 dpkg/usr/share/man/man1/batdoc.1 \
43+
&& gzip -9 dpkg/usr/share/man/man1/batdoc.1 \
4244
&& install -m644 README.md dpkg/usr/share/doc/batdoc/README \
4345
&& install -m644 LICENSE dpkg/usr/share/doc/batdoc/copyright \
4446
&& printf 'Package: batdoc\nVersion: %s\nSection: utils\nPriority: optional\nArchitecture: amd64\nMaintainer: Damon Petta <d@disassemble.net>\nDescription: cat(1) for doc, docx, xls, xlsx, pptx, and pdf -- renders to markdown with bat\n Reads legacy .doc and .xls, modern .docx, .xlsx, and .pptx, and PDF files\n and dumps their text to stdout as syntax-highlighted markdown via bat.\n' "${VERSION}" > dpkg/DEBIAN/control \
@@ -130,6 +132,7 @@ check() {\n\
130132
\n\
131133
package() {\n\
132134
\tinstall -Dm755 target/release/batdoc -t "$pkgdir"/usr/bin/\n\
135+
\tinstall -Dm644 target/man/batdoc.1 "$pkgdir"/usr/share/man/man1/batdoc.1\n\
133136
\tinstall -Dm644 LICENSE -t "$pkgdir"/usr/share/licenses/$pkgname/\n\
134137
}\n\
135138
\n\

build.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use man::prelude::*;
2+
use std::path::Path;
3+
4+
fn main() {
5+
let page = Manual::new("batdoc")
6+
.about("cat(1) for doc, docx, xls, xlsx, pptx, and pdf — renders to markdown with bat")
7+
.author(Author::new("Damon Petta").email("d@disassemble.net"))
8+
.flag(
9+
Flag::new()
10+
.short("-p")
11+
.long("--plain")
12+
.help("Force plain text output (no colors, no decorations)."),
13+
)
14+
.flag(
15+
Flag::new()
16+
.short("-m")
17+
.long("--markdown")
18+
.help("Output as markdown (default when terminal detected)."),
19+
)
20+
.flag(Flag::new().short("-i").long("--images").help(
21+
"Embed images as inline base64 data URIs in markdown output. \
22+
Extracts embedded images from .docx, .pptx, and .xlsx files. \
23+
Most useful when piping to a file \
24+
(batdoc --images report.docx > out.md). \
25+
Ignored in plain text mode and for formats without image \
26+
support (.doc, .xls, .pdf).",
27+
))
28+
.flag(
29+
Flag::new()
30+
.short("-h")
31+
.long("--help")
32+
.help("Show help information."),
33+
)
34+
.arg(Arg::new("[FILE...]"))
35+
.custom(
36+
Section::new("description")
37+
.paragraph(
38+
"batdoc reads Office documents and PDFs and dumps their contents \
39+
to the terminal as markdown. It is a spiritual successor to \
40+
catdoc(1) — cat had catdoc, bat gets batdoc.",
41+
)
42+
.paragraph(
43+
"Format is detected by magic bytes (file signature), not file \
44+
extension. Supported formats: .doc (OLE2 Word 97+), .docx \
45+
(OOXML), .xls (BIFF8 Excel 97+), .xlsx (OOXML), .pptx \
46+
(OOXML), and .pdf.",
47+
)
48+
.paragraph(
49+
"When stdout is a terminal, output is pretty-printed as \
50+
syntax-highlighted markdown via bat(1) with paging. When \
51+
piped, plain text is emitted.",
52+
)
53+
.paragraph(
54+
"Multiple files can be specified and will be processed in \
55+
order. Use \\fB-\\fR to read from stdin explicitly. Maximum \
56+
input size is 256 MiB.",
57+
),
58+
)
59+
.example(
60+
Example::new()
61+
.text("View a Word document in the terminal")
62+
.command("batdoc report.docx"),
63+
)
64+
.example(
65+
Example::new()
66+
.text("Extract a spreadsheet as plain-text TSV")
67+
.command("batdoc --plain data.xlsx > data.tsv"),
68+
)
69+
.example(
70+
Example::new()
71+
.text("Convert a presentation to markdown with embedded images")
72+
.command("batdoc --images slides.pptx > slides.md"),
73+
)
74+
.example(
75+
Example::new()
76+
.text("Read from stdin")
77+
.command("curl -sL https://example.com/file.docx | batdoc"),
78+
)
79+
.custom(
80+
Section::new("environment")
81+
.paragraph(
82+
"batdoc respects the \\fBNO_COLOR\\fR environment variable. \
83+
When set, colored output is suppressed even on a terminal.",
84+
)
85+
.paragraph(
86+
"The \\fBPAGER\\fR environment variable controls which pager \
87+
is used when output is displayed on a terminal.",
88+
),
89+
)
90+
.custom(Section::new("see also").paragraph("bat(1), catdoc(1), pdftotext(1)"))
91+
.render();
92+
93+
// Write to OUT_DIR (standard cargo output directory)
94+
let out_dir = std::env::var("OUT_DIR").unwrap();
95+
let out_path = Path::new(&out_dir).join("batdoc.1");
96+
std::fs::write(&out_path, &page).unwrap();
97+
98+
// Also write to target/man/ so packaging scripts have a stable path
99+
// that doesn't depend on the hash-based OUT_DIR.
100+
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
101+
let man_dir = Path::new(&manifest_dir).join("target").join("man");
102+
std::fs::create_dir_all(&man_dir).unwrap();
103+
std::fs::write(man_dir.join("batdoc.1"), &page).unwrap();
104+
105+
println!("cargo::rerun-if-changed=build.rs");
106+
}

pkg/alpine/APKBUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ check() {
3333

3434
package() {
3535
install -Dm755 target/release/batdoc -t "$pkgdir"/usr/bin/
36+
install -Dm644 target/man/batdoc.1 "$pkgdir"/usr/share/man/man1/batdoc.1
3637
install -Dm644 LICENSE -t "$pkgdir"/usr/share/licenses/$pkgname/
3738
install -Dm644 README.md -t "$pkgdir"/usr/share/doc/$pkgname/
3839
}

pkg/arch-bin/PKGBUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ sha256sums_aarch64=('SKIP')
2020

2121
package() {
2222
install -Dm755 "${srcdir}/batdoc_${pkgver}_${CARCH}/batdoc" "${pkgdir}/usr/bin/batdoc"
23+
install -Dm644 "${srcdir}/batdoc_${pkgver}_${CARCH}/batdoc.1" "${pkgdir}/usr/share/man/man1/batdoc.1"
2324
install -Dm644 "${srcdir}/batdoc_${pkgver}_${CARCH}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
2425
install -Dm644 "${srcdir}/batdoc_${pkgver}_${CARCH}/README.md" "${pkgdir}/usr/share/doc/${pkgname}/README.md"
2526
}

pkg/arch/PKGBUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ check() {
3535
package() {
3636
cd "$pkgname-$pkgver"
3737
install -Dm0755 -t "$pkgdir/usr/bin/" "target/release/$pkgname"
38+
install -Dm0644 target/man/$pkgname.1 "$pkgdir/usr/share/man/man1/$pkgname.1"
3839
install -Dm0644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
3940
install -Dm0644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md"
4041
}

pkg/homebrew/batdoc.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class Batdoc < Formula
1010

1111
def install
1212
system "cargo", "install", *std_cargo_args
13+
man1.install "target/man/batdoc.1"
1314
end
1415

1516
test do

pkg/rpm/batdoc.spec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ cargo build --release --locked
2727

2828
%install
2929
install -Dpm 0755 target/release/%{name} %{buildroot}%{_bindir}/%{name}
30+
install -Dpm 0644 target/man/%{name}.1 %{buildroot}%{_mandir}/man1/%{name}.1
3031

3132
%check
3233
cargo test --locked
@@ -35,6 +36,7 @@ cargo test --locked
3536
%license LICENSE
3637
%doc README.md
3738
%{_bindir}/%{name}
39+
%{_mandir}/man1/%{name}.1*
3840

3941
%changelog
4042
* Sat Feb 14 2026 Damon Petta <d@disassemble.net> - 1.0.0-1

0 commit comments

Comments
 (0)