Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# spell-checker:ignore dtolnay libsystemd libattr libcap gsub

name: Check uudoc Documentation Generation

on:
pull_request:
paths:
- 'src/bin/uudoc.rs'
- 'src/uu/*/locales/en-US.ftl'
- 'Cargo.toml'
- '.github/workflows/documentation.yml'
push:
branches:
- main
paths:
- 'src/bin/uudoc.rs'
- 'src/uu/*/locales/en-US.ftl'
- 'Cargo.toml'

jobs:
check-doc:
name: Verify uudoc generates correct documentation
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install/setup prerequisites
shell: bash
run: sudo apt-get -y update ; sudo apt-get -y install libselinux1-dev libsystemd-dev libacl1-dev libattr1-dev libcap-dev

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Download tldr
run: curl https://tldr.sh/assets/tldr.zip -o docs/tldr.zip

- name: Generate documentation
run: cargo run --bin uudoc --all-features

- name: Get current version from Cargo.toml
id: version
run: |
VERSION=$(awk '/\[workspace\.package\]/{flag=1; next} flag && /version = /{gsub(/.*= "/, ""); gsub(/".*/, ""); print; exit}' Cargo.toml)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Detected version: $VERSION"

- name: Check for --repeated option in uniq.md
run: |
if [ ! -f "docs/src/utils/uniq.md" ]; then
echo "docs/src/utils/uniq.md does not exist"
exit 1
fi

if ! grep -q -- "--repeated" docs/src/utils/uniq.md; then
echo "'--repeated' option not found in docs/src/utils/uniq.md"
echo "Content of uniq.md:"
head -50 docs/src/utils/uniq.md
exit 1
fi

- name: Check for correct version in ls.md
run: |
VERSION="${{ steps.version.outputs.version }}"

if [ ! -f "docs/src/utils/ls.md" ]; then
echo "docs/src/utils/ls.md does not exist"
exit 1
fi

if ! grep -q "v(uutils coreutils) $VERSION" docs/src/utils/ls.md; then
echo "Version '$VERSION' not found in docs/src/utils/ls.md"
echo "Found version info:"
grep "v(uutils coreutils)" docs/src/utils/ls.md || echo "No version info found"
echo "Full version section:"
grep -A2 -B2 "version" docs/src/utils/ls.md || echo "No version section found"
exit 1
fi

- name: Verify usage information is present
run: |
if [ ! -f "docs/src/utils/cat.md" ]; then
echo "docs/src/utils/cat.md does not exist"
exit 1
fi

if ! grep -q "cat \[OPTION\].*\[FILE\]" docs/src/utils/cat.md; then
echo "Usage information missing from cat.md"
echo "Content around usage:"
grep -A5 -B5 "cat" docs/src/utils/cat.md | head -20
exit 1
fi

- name: Verify help text is properly resolved
run: |
if [ ! -f "docs/src/utils/cat.md" ]; then
echo "docs/src/utils/cat.md does not exist"
exit 1
fi

if grep -q "cat-help-" docs/src/utils/cat.md; then
echo "Found unresolved Fluent keys in cat.md - help text not properly translated"
echo "Unresolved Fluent keys found:"
grep "cat-help-" docs/src/utils/cat.md
exit 1
fi

- name: Verify about text is present
run: |
if [ ! -f "docs/src/utils/cat.md" ]; then
echo "docs/src/utils/cat.md does not exist"
exit 1
fi

if ! grep -q "Concatenate FILE(s)" docs/src/utils/cat.md; then
echo "About text missing from cat.md"
echo "Content of cat.md:"
head -30 docs/src/utils/cat.md
exit 1
fi

- name: Verify tldr examples integration
run: |
if [ ! -f "docs/src/utils/cp.md" ]; then
echo "docs/src/utils/cp.md does not exist"
exit 1
fi

if ! grep -q "The examples are provided by" docs/src/utils/cp.md; then
echo "tldr examples integration missing from cp.md"
echo "Expected to find 'The examples are provided by' text"
echo "Content of cp.md:"
tail -20 docs/src/utils/cp.md
exit 1
fi
7 changes: 1 addition & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ expensive_tests = []
# "test_risky_names" == enable tests that create problematic file names (would make a network share inaccessible to Windows, breaks SVN on Mac OS, etc.)
test_risky_names = []
# * only build `uudoc` when `--feature uudoc` is activated
uudoc = ["zip", "dep:uuhelp_parser"]
Comment thread
sylvestre marked this conversation as resolved.
Outdated
uudoc = ["zip", "dep:fluent-syntax"]
## features
## Optional feature for stdbuf
# "feat_external_libstdbuf" == use an external libstdbuf.so for stdbuf instead of embedding it
Expand Down Expand Up @@ -282,7 +282,6 @@ members = [
"src/uu/stdbuf/src/libstdbuf",
"src/uucore",
"src/uucore_procs",
"src/uuhelp_parser",
"tests/benches/factor",
"tests/uutests",
# "fuzz", # TODO
Expand Down Expand Up @@ -415,8 +414,8 @@ phf.workspace = true
selinux = { workspace = true, optional = true }
textwrap.workspace = true
zip = { workspace = true, optional = true }
fluent-syntax = { workspace = true, optional = true }

uuhelp_parser = { optional = true, version = "0.2.2", path = "src/uuhelp_parser" }

# * uutils
uu_test = { optional = true, version = "0.2.2", package = "uu_test", path = "src/uu/test" }
Expand Down
66 changes: 47 additions & 19 deletions src/bin/uudoc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// spell-checker:ignore tldr uuhelp

use clap::Command;
use fluent_syntax::ast::{Entry, Message, Pattern};
use fluent_syntax::parser;
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs::File;
Expand Down Expand Up @@ -140,7 +142,7 @@ fn main() -> io::Result<()> {
}
let p = format!("docs/src/utils/{name}.md");

let markdown = File::open(format!("src/uu/{name}/{name}.md"))
let fluent = File::open(format!("src/uu/{name}/locales/en-US.ftl"))
.and_then(|mut f: File| {
let mut s = String::new();
f.read_to_string(&mut s)?;
Expand All @@ -155,7 +157,7 @@ fn main() -> io::Result<()> {
name,
tldr_zip: &mut tldr_zip,
utils_per_platform: &utils_per_platform,
markdown,
fluent,
}
.markdown()?;
println!("Wrote to '{p}'");
Expand All @@ -173,7 +175,7 @@ struct MDWriter<'a, 'b> {
name: &'a str,
tldr_zip: &'b mut Option<ZipArchive<File>>,
utils_per_platform: &'b HashMap<&'b str, Vec<String>>,
markdown: Option<String>,
fluent: Option<String>,
}

impl MDWriter<'_, '_> {
Expand All @@ -189,6 +191,33 @@ impl MDWriter<'_, '_> {
self.examples()
}

/// Extract value for a Fluent key from the .ftl content
fn extract_fluent_value(&self, key: &str) -> Option<String> {
let content = self.fluent.as_ref()?;
let resource = parser::parse(content.clone()).ok()?;

for entry in resource.body {
if let Entry::Message(Message {
id,
value: Some(Pattern { elements }),
..
}) = entry
{
if id.name == key {
// Simple text extraction - just concatenate text elements
let mut result = String::new();
for element in elements {
if let fluent_syntax::ast::PatternElement::TextElement { value } = element {
result.push_str(&value);
}
}
return Some(result);
}
}
}
None
}

/// # Errors
/// Returns an error if the writer fails.
fn additional(&mut self) -> io::Result<()> {
Expand Down Expand Up @@ -237,10 +266,7 @@ impl MDWriter<'_, '_> {
/// # Errors
/// Returns an error if the writer fails.
fn usage(&mut self) -> io::Result<()> {
if let Some(markdown) = &self.markdown {
let usage = uuhelp_parser::parse_usage(markdown);
let usage = usage.replace("{}", self.name);

if let Some(usage) = self.extract_fluent_value(&format!("{}-usage", self.name)) {
writeln!(self.w, "\n```")?;
writeln!(self.w, "{usage}")?;
writeln!(self.w, "```")
Expand All @@ -252,8 +278,8 @@ impl MDWriter<'_, '_> {
/// # Errors
/// Returns an error if the writer fails.
fn about(&mut self) -> io::Result<()> {
if let Some(markdown) = &self.markdown {
writeln!(self.w, "{}", uuhelp_parser::parse_about(markdown))
if let Some(about) = self.extract_fluent_value(&format!("{}-about", self.name)) {
writeln!(self.w, "{about}")
} else {
Ok(())
}
Expand All @@ -262,13 +288,11 @@ impl MDWriter<'_, '_> {
/// # Errors
/// Returns an error if the writer fails.
fn after_help(&mut self) -> io::Result<()> {
if let Some(markdown) = &self.markdown {
if let Some(after_help) = uuhelp_parser::parse_section("after help", markdown) {
return writeln!(self.w, "\n\n{after_help}");
}
if let Some(after_help) = self.extract_fluent_value(&format!("{}-after-help", self.name)) {
writeln!(self.w, "\n\n{after_help}")
} else {
Ok(())
}

Ok(())
}

/// # Errors
Expand Down Expand Up @@ -368,13 +392,17 @@ impl MDWriter<'_, '_> {
write!(self.w, "</code>")?;
}
writeln!(self.w, "</dt>")?;
let help_text = arg.get_help().unwrap_or_default().to_string();
// Try to resolve Fluent key if it looks like one, otherwise use as-is
let resolved_help = if help_text.starts_with(&format!("{}-help-", self.name)) {
self.extract_fluent_value(&help_text).unwrap_or(help_text)
} else {
help_text
};
writeln!(
self.w,
"<dd>\n\n{}\n\n</dd>",
arg.get_help()
.unwrap_or_default()
.to_string()
.replace('\n', "<br />")
resolved_help.replace('\n', "<br />")
)?;
}
writeln!(self.w, "</dl>\n")
Expand Down
2 changes: 0 additions & 2 deletions src/uucore_procs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# spell-checker:ignore uuhelp
[package]
name = "uucore_procs"
description = "uutils ~ 'uucore' proc-macros"
Expand All @@ -17,4 +16,3 @@ proc-macro = true
[dependencies]
proc-macro2 = "1.0.81"
quote = "1.0.36"
uuhelp_parser = { path = "../uuhelp_parser", version = "0.2.2" }
Loading
Loading