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
65 changes: 61 additions & 4 deletions crates/bashkit-coreutils-port/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,26 @@ fn run(uutils_dir: &Path, util: &str, rev: &str) -> Result<String> {
let translations = parse_ftl(&ftl);
let file = syn::parse_file(&src).context("parse uutils source as rust")?;

let options_mod = find_mod(&file, "options")
.ok_or_else(|| anyhow!("could not find `mod options` in {}", src_path.display()))?;
// Two option-key declaration styles in the wild:
// 1. `mod options { pub static FOO: &str = "foo"; ... }` (cat, tac,
// truncate, stat, shuf). uu_app() refers to keys via
// `options::FOO`.
// 2. Module-level `const OPT_FOO: &str = "foo";` / `static OPT_FOO`
// (mktemp, realpath, readlink, od). uu_app() refers to keys by
// bare name (`OPT_FOO`).
//
// Collect both: the optional `options` mod (if present) and any
// bare-name `OPT_*` / `ARG_*` constants we should also emit so
// uu_app's bare-name references resolve.
let options_mod = find_mod(&file, "options").cloned();
let bare_name_consts = collect_option_constants(&file);
if options_mod.is_none() && bare_name_consts.is_empty() {
bail!(
"could not find `mod options` or any module-level `OPT_*`/`ARG_*` \
constants in {}",
src_path.display()
);
}
let mut uu_app = find_fn(&file, "uu_app")
.ok_or_else(|| anyhow!("could not find `fn uu_app` in {}", src_path.display()))?
.clone();
Expand Down Expand Up @@ -129,7 +147,18 @@ fn run(uutils_dir: &Path, util: &str, rev: &str) -> Result<String> {
// Original uutils licensed MIT; see THIRD_PARTY_LICENSES.\n\n",
);

let options_tokens = options_mod;
// Optional `mod options { ... }` (cat-style); collapses to nothing
// for utils that use bare-name constants.
let options_mod_tokens: TokenStream = match options_mod {
Some(m) => quote!(#m),
None => quote!(),
};
// Bare-name constants for utils that don't wrap them in a mod.
let const_tokens: Vec<TokenStream> = bare_name_consts
.into_iter()
.map(|c| quote::quote!(#c))
.collect();

let body: TokenStream = quote! {
#![allow(unused_imports, dead_code)]

Expand All @@ -144,7 +173,8 @@ fn run(uutils_dir: &Path, util: &str, rev: &str) -> Result<String> {
use std::ops::RangeInclusive;
use std::str::FromStr;

#options_tokens
#options_mod_tokens
#(#const_tokens)*

/// Vendored stand-in for `uucore::format_usage`.
///
Expand Down Expand Up @@ -269,6 +299,33 @@ fn find_mod<'a>(file: &'a syn::File, name: &str) -> Option<&'a ItemMod> {
})
}

/// Collect module-level `const OPT_FOO: &str = ...` / `static OPT_FOO`
/// declarations that some uutils sources use in place of a
/// `mod options { ... }`. uu_app() bodies in these utils refer to the
/// constants by their bare name (`Arg::new(OPT_FOO)`); we emit them
/// at the same scope in the generated file so the bare-name references
/// resolve unchanged.
///
/// Filter: name must start with `OPT_` or `ARG_` to avoid sweeping in
/// unrelated source-level constants.
fn collect_option_constants(file: &syn::File) -> Vec<Item> {
file.items
.iter()
.filter(|it| match it {
Item::Const(c) => {
let n = c.ident.to_string();
n.starts_with("OPT_") || n.starts_with("ARG_")
}
Item::Static(s) => {
let n = s.ident.to_string();
n.starts_with("OPT_") || n.starts_with("ARG_")
}
_ => false,
})
.cloned()
.collect()
}

fn find_fn<'a>(file: &'a syn::File, name: &str) -> Option<&'a ItemFn> {
file.items.iter().find_map(|it| match it {
Item::Fn(f) if f.sig.ident == name => Some(f),
Expand Down
1 change: 1 addition & 0 deletions crates/bashkit/src/builtins/generated/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
pub const UUTILS_REVISION: &str = "39364b6";

pub mod cat_args;
pub mod readlink_args;
pub mod shuf_args;
pub mod tac_args;
pub mod truncate_args;
125 changes: 125 additions & 0 deletions crates/bashkit/src/builtins/generated/readlink_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// GENERATED by bashkit-coreutils-port. DO NOT EDIT.
//
// Source: uutils/coreutils@39364b6 src/uu/readlink/
// Regenerate: cargo run -p bashkit-coreutils-port -- <UUTILS_DIR> readlink <REV>
//
// Original uutils licensed MIT; see THIRD_PARTY_LICENSES.

#![allow(unused_imports, dead_code)]
use clap::{Arg, ArgAction, Command, builder::ValueParser};
use std::ffi::OsString;
use std::ops::RangeInclusive;
use std::str::FromStr;
const OPT_CANONICALIZE: &str = "canonicalize";
const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing";
const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing";
const OPT_NO_NEWLINE: &str = "no-newline";
const OPT_QUIET: &str = "quiet";
const OPT_SILENT: &str = "silent";
const OPT_VERBOSE: &str = "verbose";
const OPT_ZERO: &str = "zero";
const ARG_FILES: &str = "files";
/// Vendored stand-in for `uucore::format_usage`.
///
/// Upstream wraps the usage line with stylized "Usage:" prefix logic
/// driven by uucore's locale stack. For our purposes the raw string
/// is enough; clap's `override_usage` accepts the literal as-is.
fn format_usage(s: &str) -> String {
s.to_string()
}
pub fn readlink_command() -> Command {
Command::new("readlink")
.version(env!("CARGO_PKG_VERSION"))
.about(
::std::string::String::from(
"Print value of a symbolic link or canonical file name.",
),
)
.override_usage(
format_usage(&::std::string::String::from("readlink [OPTION]... [FILE]...")),
)
.infer_long_args(true)
.arg(
Arg::new(OPT_CANONICALIZE)
.short('f')
.long(OPT_CANONICALIZE)
.help(
::std::string::String::from(
"canonicalize by following every symlink in every component of the given name recursively; all but the last component must exist",
),
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_CANONICALIZE_EXISTING)
.short('e')
.long("canonicalize-existing")
.help(
::std::string::String::from(
"canonicalize by following every symlink in every component of the given name recursively, all components must exist",
),
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_CANONICALIZE_MISSING)
.short('m')
.long(OPT_CANONICALIZE_MISSING)
.help(
::std::string::String::from(
"canonicalize by following every symlink in every component of the given name recursively, without requirements on components existence",
),
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_NO_NEWLINE)
.short('n')
.long(OPT_NO_NEWLINE)
.help(
::std::string::String::from("do not output the trailing delimiter"),
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_QUIET)
.short('q')
.long(OPT_QUIET)
.help(::std::string::String::from("suppress most error messages"))
.overrides_with_all([OPT_QUIET, OPT_SILENT, OPT_VERBOSE])
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_SILENT)
.short('s')
.long(OPT_SILENT)
.help(::std::string::String::from("suppress most error messages"))
.overrides_with_all([OPT_QUIET, OPT_SILENT, OPT_VERBOSE])
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_VERBOSE)
.short('v')
.long(OPT_VERBOSE)
.help(::std::string::String::from("report error message"))
.overrides_with_all([OPT_QUIET, OPT_SILENT, OPT_VERBOSE])
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_ZERO)
.short('z')
.long(OPT_ZERO)
.help(
::std::string::String::from(
"separate output with NUL rather than newline",
),
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(ARG_FILES)
.action(ArgAction::Append)
.value_parser(clap::value_parser!(OsString))
.value_hint(clap::ValueHint::AnyPath),
)
}
Loading
Loading