Skip to main content

kaish_kernel/tools/builtin/
mod.rs

1//! Built-in tools for kaish.
2//!
3//! These tools are always available and provide core functionality.
4
5mod alias;
6mod assert;
7mod awk;
8mod base64_tool;
9mod basename;
10#[cfg(feature = "subprocess")]
11mod bg;
12mod cat;
13mod cd;
14mod checksum;
15mod cmp;
16mod cp;
17mod cut;
18mod date;
19mod dd;
20mod diff;
21mod dirname;
22mod patch;
23mod echo;
24mod env;
25#[cfg(feature = "subprocess")]
26mod exec;
27#[cfg(feature = "subprocess")]
28mod spawn;
29#[cfg(feature = "subprocess")]
30pub use spawn::{resolve_in_path, virtual_cwd_error};
31mod export;
32#[cfg(feature = "subprocess")]
33mod fg;
34mod file;
35mod fromjson;
36mod fromjsonl;
37mod glob;
38mod find;
39pub(crate) mod format_string;
40mod gather;
41mod grep;
42mod grep_engine;
43mod regex_dialect;
44mod head;
45mod ignore;
46mod help;
47// `hostname` is pure host introspection (reads /proc) — gated behind the host
48// capability. `uname` stays available and reports kaish identity instead.
49#[cfg(feature = "host")]
50mod hostname;
51mod introspect;
52mod jobs;
53mod jq_native;
54mod kaish_ast;
55mod kaish_clear;
56mod kaish_last;
57mod kaish_trash;
58mod kaish_status;
59mod kaish_version;
60mod kaish_vfs;
61mod keys;
62mod kill;
63mod ln;
64mod ls;
65mod mkdir;
66mod mktemp;
67mod mv;
68mod output_limit;
69mod printf;
70mod push;
71mod pwd;
72mod read;
73mod readlink;
74mod realpath;
75mod rm;
76mod scatter;
77mod sed;
78mod seq;
79mod split;
80mod set;
81mod sleep;
82mod sort;
83mod stat;
84mod tac;
85mod tail;
86mod tee;
87mod test;
88mod timeout;
89mod tojson;
90mod tojsonl;
91#[cfg(feature = "tokens")]
92mod tokens;
93mod touch;
94mod tr;
95mod tree;
96mod true_false;
97// Module named `type_of`, not `typeof` — `typeof` is a reserved (but unused)
98// Rust keyword, so `mod typeof;` doesn't compile. The tool name is still the
99// plain string "typeof" (see `Tool::name`).
100mod type_of;
101mod uname;
102mod uniq;
103mod unset;
104mod validate;
105mod values;
106mod vars;
107mod wait;
108mod wc;
109#[cfg(feature = "subprocess")]
110mod which;
111mod write;
112mod xxd;
113
114use super::ToolRegistry;
115
116/// Read a repeatable string-valued flag off the raw `ToolArgs`.
117///
118/// Repeatable value flags (clap `Append`) are accumulated by the kernel into a
119/// `Value::Json(Array)` under the flag's long name; a single occurrence may
120/// arrive as a bare `Value::String`. `to_argv()` can't round-trip the array, so
121/// the clap field isn't a reliable source — search builtins read the raw args
122/// here. Shared by grep/glob `--ftype`/`--ftype-not`; mirrors sed's
123/// `collect_expressions` (see the repeatable-flag gotcha in `arch_repeatable_flags`).
124///
125/// Loud on binary (GH #116): a single binary occurrence lands in the bare
126/// `Some(other)` arm; a *repeated* binary occurrence lands inside the
127/// `Json(Array)` as a base64 byte envelope (`push_repeatable_value` runs every
128/// value through `value_to_json`, which encodes `Value::Bytes` that way) — the
129/// array arm's `filter_map(v.as_str())` used to silently DROP that entry
130/// instead of erroring, which is worse than a placeholder: the filter/pattern
131/// just vanished and the caller ran unfiltered against real data. Both shapes
132/// now error instead of silently stringifying or dropping.
133pub(crate) fn read_repeatable_strings(
134    args: &super::ToolArgs,
135    key: &str,
136) -> Result<Vec<String>, String> {
137    use crate::ast::Value;
138    match args.named.get(key) {
139        Some(Value::Json(serde_json::Value::Array(items))) => {
140            let mut out = Vec::with_capacity(items.len());
141            for v in items {
142                match v.as_str() {
143                    Some(s) => out.push(s.to_string()),
144                    // Not a string: check for a binary envelope before
145                    // silently skipping (the pre-existing behavior, preserved
146                    // below for any other JSON shape — out of this sweep's
147                    // scope).
148                    None => {
149                        if let Some(bytes) = kaish_types::envelope_to_bytes(v) {
150                            return Err(format!(
151                                "binary data ({} bytes) cannot be used as a repeatable \
152                                 flag value — decode it (base64/xxd) or redirect to a file",
153                                bytes.len()
154                            ));
155                        }
156                    }
157                }
158            }
159            Ok(out)
160        }
161        Some(Value::String(s)) => Ok(vec![s.clone()]),
162        Some(other) => {
163            match crate::interpreter::value_to_text_sink_named(other, "a repeatable flag value") {
164                Ok(s) => Ok(vec![s]),
165                Err(e) => Err(e.to_string()),
166            }
167        }
168        None => Ok(Vec::new()),
169    }
170}
171
172/// Read a path-typed positional/named arg as a string, going LOUD on a
173/// `Value::Bytes` operand rather than `ToolArgs::get_string`'s silent `None`
174/// (that method lives in the `kaish-types` leaf crate, which has no
175/// `EvalError`/rich-error machinery to report *why* it returned nothing, so it
176/// just treats any non-scalar-text value as absent).
177///
178/// A caller reading `get_string` for a *path* generally treats `None` as
179/// "operand missing" and either errors or falls back to some other input
180/// (stdin, `$HOME`, …) — exactly the silent-wrong-source shape GH #93 item 1
181/// is about, just reached through `get_string`'s catch-all instead of
182/// `value_to_string`'s placeholder. `Ok(None)` here means genuinely absent;
183/// `Err` means it was present and binary.
184pub(crate) fn get_path_string(
185    args: &super::ToolArgs,
186    name: &str,
187    positional_index: usize,
188) -> Result<Option<String>, String> {
189    use crate::ast::Value;
190    match args.get(name, positional_index) {
191        Some(v @ Value::Bytes(_)) => match crate::interpreter::value_to_text_sink_named(v, "a path") {
192            Ok(s) => Ok(Some(s)),
193            Err(e) => Err(e.to_string()),
194        },
195        _ => Ok(args.get_string(name, positional_index)),
196    }
197}
198
199/// Register all built-in tools with the registry.
200pub fn register_builtins(registry: &mut ToolRegistry) {
201    registry.register(alias::Alias);
202    registry.register(alias::Unalias);
203    registry.register(assert::Assert);
204    registry.register(awk::Awk);
205    registry.register(base64_tool::Base64Tool);
206    registry.register(basename::Basename);
207    #[cfg(feature = "subprocess")]
208    registry.register(bg::Bg);
209    registry.register(cat::Cat);
210    registry.register(cd::Cd);
211    registry.register(checksum::Checksum);
212    registry.register(cmp::Cmp);
213    registry.register(cp::Cp);
214    registry.register(cut::Cut);
215    registry.register(date::Date::new());
216    registry.register(dd::Dd);
217    registry.register(diff::Diff);
218    registry.register(dirname::Dirname);
219    registry.register(echo::Echo);
220    registry.register(env::Env);
221    #[cfg(feature = "subprocess")]
222    registry.register(exec::Exec);
223    #[cfg(feature = "subprocess")]
224    registry.register(spawn::Spawn);
225    registry.register(export::Export);
226    #[cfg(feature = "subprocess")]
227    registry.register(fg::Fg);
228    registry.register(file::File);
229    registry.register(fromjson::FromJson);
230    registry.register(fromjsonl::FromJsonl);
231    registry.register(glob::Glob);
232    registry.register(find::Find);
233    registry.register(gather::Gather);
234    registry.register(grep::Grep);
235    registry.register(head::Head);
236    registry.register(help::Help);
237    registry.register(ignore::KaishIgnore);
238    #[cfg(feature = "host")]
239    registry.register(hostname::Hostname);
240    registry.register(introspect::Mounts);
241    registry.register(introspect::Tools);
242    registry.register(jobs::Jobs);
243    registry.register(jq_native::JqNative);
244    registry.register(kaish_ast::KaishAst);
245    registry.register(kaish_clear::KaishClear);
246    registry.register(kaish_last::KaishLast);
247    registry.register(kaish_trash::KaishTrash);
248    registry.register(kaish_status::KaishStatus);
249    registry.register(kaish_version::KaishVersion);
250    registry.register(kaish_vfs::KaishVfs);
251    registry.register(keys::Keys);
252    registry.register(kill::Kill);
253    registry.register(ln::Ln);
254    registry.register(ls::Ls);
255    registry.register(mkdir::Mkdir);
256    registry.register(mktemp::Mktemp);
257    registry.register(mv::Mv);
258    registry.register(output_limit::KaishOutputLimit);
259    registry.register(patch::Patch);
260    registry.register(printf::Printf);
261    registry.register(push::Push);
262    #[cfg(all(target_os = "linux", feature = "host"))]
263    registry.register(kaish_tools_host::Ps);
264    registry.register(pwd::Pwd);
265    registry.register(read::Read);
266    registry.register(readlink::Readlink);
267    registry.register(realpath::Realpath);
268    registry.register(rm::Rm);
269    registry.register(scatter::Scatter);
270    registry.register(sed::Sed);
271    registry.register(seq::Seq);
272    registry.register(set::Set);
273    registry.register(split::Split);
274    registry.register(sleep::Sleep);
275    registry.register(sort::Sort);
276    registry.register(stat::Stat);
277    registry.register(tac::Tac);
278    registry.register(tail::Tail);
279    registry.register(tee::Tee);
280    registry.register(test::Test);
281    registry.register(timeout::Timeout);
282    #[cfg(feature = "tokens")]
283    registry.register(tokens::Tokens);
284    registry.register(tojson::ToJson);
285    registry.register(tojsonl::ToJsonl);
286    registry.register(touch::Touch);
287    registry.register(tr::Tr);
288    registry.register(tree::Tree);
289    registry.register(true_false::True);
290    registry.register(true_false::False);
291    registry.register(type_of::TypeOf);
292    registry.register(uname::Uname);
293    registry.register(uniq::Uniq);
294    registry.register(unset::Unset);
295    registry.register(validate::Validate);
296    registry.register(values::Values);
297    registry.register(vars::Vars);
298    registry.register(wait::Wait);
299    registry.register(wc::Wc);
300    #[cfg(feature = "subprocess")]
301    registry.register(which::Which);
302    registry.register(write::Write);
303    registry.register(xxd::Xxd);
304}