From 0a7011e6343f877b8378d6fe1644294fe61bc599 Mon Sep 17 00:00:00 2001 From: leynos Date: Sat, 13 Jun 2026 17:31:26 +0200 Subject: [PATCH 1/2] Document runner file-IO and glob helpers; add theme example (#346) First increment of the Rustdoc-coverage work, delivering the concrete "missing Rustdoc on public/crate-visible helpers" acceptance item. Document the previously undocumented runner process file-I/O helpers in `src/runner/process/file_io.rs` (`create_temp_ninja_file`, `write_text_file_utf8`, `write_ninja_file`, `write_text_file`, `write_ninja_stdout`, `write_text_stdout`), each with a summary and an `# Errors` section. These are `pub` but only crate-reachable (the `process` module is private), so `missing_docs` never flagged them. Document the glob helper functions `normalize_separators`, `force_literal_escapes`, and `validate_brace_matching`, including the brace-validation `# Errors` contract. Add a worked `# Examples` doctest to the high-use public `ThemePreference::parse_raw`, exercising the trim/case-insensitive and error paths. Remaining `# Examples` coverage for the other high-use public APIs is staged as follow-up increments per the issue's "highest-use first" guidance. --- src/manifest/glob/normalize.rs | 11 ++++++ src/manifest/glob/validate.rs | 11 ++++++ src/runner/process/file_io.rs | 61 ++++++++++++++++++++++++++++++++++ src/theme.rs | 16 ++++++++- 4 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/manifest/glob/normalize.rs b/src/manifest/glob/normalize.rs index 1bc6a5f7..cc174472 100644 --- a/src/manifest/glob/normalize.rs +++ b/src/manifest/glob/normalize.rs @@ -1,5 +1,11 @@ //! Separator and escape normalisation for glob patterns. +/// Normalise path separators in a glob `pattern` to the platform-native one. +/// +/// Forward slashes (and, on Unix, backslash sequences) are rewritten to +/// [`std::path::MAIN_SEPARATOR`] so manifests can use `/` portably. On Unix the +/// backslash also acts as an escape, so escaped metacharacters are preserved +/// rather than treated as separators. pub(crate) fn normalize_separators(pattern: &str) -> String { let native = std::path::MAIN_SEPARATOR; #[cfg(unix)] @@ -75,6 +81,11 @@ fn push_normalized_backslash( out.push('\\'); } +/// Rewrite backslash escapes into bracket character classes (Unix only). +/// +/// The `glob` crate treats `\` literally on Unix, so an escaped metacharacter +/// such as `\*` is converted to the single-element class `[*]` to match the +/// literal character. Returns the pattern with all escapes expanded. #[cfg(unix)] pub(super) fn force_literal_escapes(pattern: &str) -> String { let mut out = String::with_capacity(pattern.len()); diff --git a/src/manifest/glob/validate.rs b/src/manifest/glob/validate.rs index db3b28fe..9f997890 100644 --- a/src/manifest/glob/validate.rs +++ b/src/manifest/glob/validate.rs @@ -102,6 +102,17 @@ impl ValidationState { } } +/// Validate that brace groups in a glob `pattern` are balanced. +/// +/// Braces inside a `[...]` character class are treated as literals, and on Unix +/// a backslash-escaped brace does not affect nesting depth. An unmatched +/// opening or closing brace yields a syntax error identifying the offending +/// character and position. +/// +/// # Errors +/// +/// Returns a [`minijinja::Error`](Error) with kind `SyntaxError` when an +/// opening brace is never closed or a closing brace has no matching opener. pub(super) fn validate_brace_matching(pattern: &str) -> std::result::Result<(), Error> { let mut state = ValidationState::new(); diff --git a/src/runner/process/file_io.rs b/src/runner/process/file_io.rs index 41a80cd9..62317ab9 100644 --- a/src/runner/process/file_io.rs +++ b/src/runner/process/file_io.rs @@ -18,6 +18,17 @@ pub fn is_stdout_path(path: &Path) -> bool { path.as_os_str() == "-" } +/// Write `content` to a freshly created temporary `*.ninja` file. +/// +/// The returned [`NamedTempFile`] keeps the file alive; it is deleted when the +/// guard is dropped, so callers must retain it for as long as the build file +/// is needed. The contents are flushed and `fsync`ed before returning so a +/// spawned `ninja` reads a complete file. +/// +/// # Errors +/// +/// Returns an error if the temporary file cannot be created, written, flushed, +/// or synced to disk. pub fn create_temp_ninja_file(content: &NinjaContent) -> AnyResult { let mut tmp = Builder::new() .prefix("netsuke.") @@ -40,6 +51,16 @@ pub fn create_temp_ninja_file(content: &NinjaContent) -> AnyResult AnyResult<()> { if let Some(parent) = path.parent().filter(|p| !p.as_str().is_empty()) { dir.create_dir_all(parent.as_str()).with_context(|| { @@ -91,11 +112,31 @@ fn derive_dir_and_relative(path: &Utf8Path) -> AnyResult<(cap_fs::Dir, Utf8PathB Ok((dir, relative)) } +/// Write generated Ninja `content` to `path`. +/// +/// Thin wrapper over [`write_text_file`] that unwraps the [`NinjaContent`] +/// newtype. +/// +/// # Errors +/// +/// Returns an error if `path` is not valid UTF-8, if no existing ancestor +/// directory can be opened, or if the write fails. pub fn write_ninja_file(path: &Path, content: &NinjaContent) -> AnyResult<()> { write_text_file(path, content.as_str())?; Ok(()) } +/// Write `content` to `path`, resolving it against a capability-scoped root. +/// +/// The path is split into the deepest existing ancestor directory (opened as a +/// [`cap_std`](cap_fs) handle) and the remaining relative path, so the write is +/// confined to that directory tree. Relative paths resolve against the current +/// working directory. +/// +/// # Errors +/// +/// Returns an error if `path` is not valid UTF-8, if no existing ancestor +/// directory can be opened, or if [`write_text_file_utf8`] fails. pub fn write_text_file(path: &Path, content: &str) -> AnyResult<()> { let utf8_path = Utf8Path::from_path(path).ok_or_else(|| { anyhow!( @@ -130,10 +171,30 @@ fn flush_ignoring_broken_pipe(writer: &mut impl Write) -> io::Result<()> { } } +/// Write generated Ninja `content` to standard output. +/// +/// Thin wrapper over [`write_text_stdout`] that unwraps the [`NinjaContent`] +/// newtype, used for the `-` stdout sentinel. +/// +/// # Errors +/// +/// Returns an error if writing to or flushing standard output fails for a +/// reason other than a broken pipe (a closed downstream reader is treated as +/// success). pub fn write_ninja_stdout(content: &NinjaContent) -> AnyResult<()> { write_text_stdout(content.as_str()) } +/// Write `content` to standard output, tolerating a closed downstream pipe. +/// +/// A `BrokenPipe` error (for example when piping into `head`) is treated as +/// success so the runner exits cleanly rather than reporting spurious I/O +/// failure. +/// +/// # Errors +/// +/// Returns an error if writing to or flushing standard output fails for any +/// reason other than a broken pipe. pub fn write_text_stdout(content: &str) -> AnyResult<()> { let mut stdout = io::stdout().lock(); write_all_ignoring_broken_pipe(&mut stdout, content.as_bytes()) diff --git a/src/theme.rs b/src/theme.rs index e2673193..9770b98e 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -36,12 +36,26 @@ impl ThemePreference { /// /// Returns `Ok(ThemePreference)` on success, or `Err(Self::VALID_OPTIONS)` /// on failure so callers can construct localised error messages using the - /// same canonical list of valid options. + /// same canonical list of valid options. Matching is case-insensitive and + /// ignores surrounding whitespace. /// /// # Errors /// /// Returns `Err(Self::VALID_OPTIONS)` if the input string does not match /// any valid theme option (case-insensitive). + /// + /// # Examples + /// + /// ``` + /// use netsuke::theme::ThemePreference; + /// + /// assert_eq!(ThemePreference::parse_raw(" UNICODE "), Ok(ThemePreference::Unicode)); + /// assert_eq!(ThemePreference::parse_raw("ascii"), Ok(ThemePreference::Ascii)); + /// assert_eq!( + /// ThemePreference::parse_raw("solarized"), + /// Err(ThemePreference::VALID_OPTIONS), + /// ); + /// ``` pub fn parse_raw(s: &str) -> Result { let trimmed = s.trim().to_ascii_lowercase(); match trimmed.as_str() { From eb1b42c3dc0107d4a3c4c047360e2d9bf767ec3c Mon Sep 17 00:00:00 2001 From: leynos Date: Tue, 16 Jun 2026 21:16:46 +0200 Subject: [PATCH 2/2] Clarify glob normalisation docs Tighten the Rustdoc for the glob separator and literal-escape helpers so each comment describes only its own pipeline step. --- src/manifest/glob/normalize.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/manifest/glob/normalize.rs b/src/manifest/glob/normalize.rs index cc174472..5f9968cd 100644 --- a/src/manifest/glob/normalize.rs +++ b/src/manifest/glob/normalize.rs @@ -1,11 +1,10 @@ //! Separator and escape normalisation for glob patterns. -/// Normalise path separators in a glob `pattern` to the platform-native one. +/// Normalise forward slashes in a glob `pattern` to the platform separator. /// -/// Forward slashes (and, on Unix, backslash sequences) are rewritten to -/// [`std::path::MAIN_SEPARATOR`] so manifests can use `/` portably. On Unix the -/// backslash also acts as an escape, so escaped metacharacters are preserved -/// rather than treated as separators. +/// Forward slashes are rewritten to [`std::path::MAIN_SEPARATOR`] so manifests +/// can use `/` portably. On Unix, backslash escapes are preserved for the +/// follow-up literal-escape pass rather than treated as separators here. pub(crate) fn normalize_separators(pattern: &str) -> String { let native = std::path::MAIN_SEPARATOR; #[cfg(unix)] @@ -81,11 +80,12 @@ fn push_normalized_backslash( out.push('\\'); } -/// Rewrite backslash escapes into bracket character classes (Unix only). +/// Rewrite selected backslash escapes into bracket character classes. /// /// The `glob` crate treats `\` literally on Unix, so an escaped metacharacter /// such as `\*` is converted to the single-element class `[*]` to match the -/// literal character. Returns the pattern with all escapes expanded. +/// literal character. Only escapes for `*`, `?`, `[`, `]`, `{`, and `}` are +/// rewritten; other escapes are left for the `glob` crate to interpret. #[cfg(unix)] pub(super) fn force_literal_escapes(pattern: &str) -> String { let mut out = String::with_capacity(pattern.len());