harn_hostlib/tools/
mod.rs1use std::collections::BTreeMap;
37use std::rc::Rc;
38use std::sync::Arc;
39
40use harn_vm::VmValue;
41
42use crate::error::HostlibError;
43use crate::registry::{BuiltinRegistry, HostlibCapability, RegisteredBuiltin, SyncHandler};
44
45mod args;
46mod file_io;
47mod git;
48mod outline;
49pub mod permissions;
50mod search;
51
52pub use permissions::FEATURE_TOOLS_DETERMINISTIC;
53
54#[derive(Default)]
56pub struct ToolsCapability;
57
58impl HostlibCapability for ToolsCapability {
59 fn module_name(&self) -> &'static str {
60 "tools"
61 }
62
63 fn register_builtins(&self, registry: &mut BuiltinRegistry) {
64 register_gated(registry, "hostlib_tools_search", "search", search::run);
65 register_gated(
66 registry,
67 "hostlib_tools_read_file",
68 "read_file",
69 file_io::read_file,
70 );
71 register_gated(
72 registry,
73 "hostlib_tools_write_file",
74 "write_file",
75 file_io::write_file,
76 );
77 register_gated(
78 registry,
79 "hostlib_tools_delete_file",
80 "delete_file",
81 file_io::delete_file,
82 );
83 register_gated(
84 registry,
85 "hostlib_tools_list_directory",
86 "list_directory",
87 file_io::list_directory,
88 );
89 register_gated(
90 registry,
91 "hostlib_tools_get_file_outline",
92 "get_file_outline",
93 outline::run,
94 );
95 register_gated(registry, "hostlib_tools_git", "git", git::run);
96
97 registry.register_unimplemented("hostlib_tools_run_command", "tools", "run_command");
99 registry.register_unimplemented("hostlib_tools_run_test", "tools", "run_test");
100 registry.register_unimplemented(
101 "hostlib_tools_run_build_command",
102 "tools",
103 "run_build_command",
104 );
105 registry.register_unimplemented(
106 "hostlib_tools_inspect_test_results",
107 "tools",
108 "inspect_test_results",
109 );
110 registry.register_unimplemented(
111 "hostlib_tools_manage_packages",
112 "tools",
113 "manage_packages",
114 );
115
116 let handler: SyncHandler = Arc::new(handle_enable);
119 registry.register(RegisteredBuiltin {
120 name: "hostlib_enable",
121 module: "tools",
122 method: "enable",
123 handler,
124 });
125 }
126}
127
128fn register_gated(
131 registry: &mut BuiltinRegistry,
132 name: &'static str,
133 method: &'static str,
134 runner: fn(&[VmValue]) -> Result<VmValue, HostlibError>,
135) {
136 let handler: SyncHandler = Arc::new(move |args: &[VmValue]| {
137 if !permissions::is_enabled(permissions::FEATURE_TOOLS_DETERMINISTIC) {
138 return Err(HostlibError::Backend {
139 builtin: name,
140 message: format!(
141 "feature `{}` is not enabled in this session — call \
142 `hostlib_enable(\"{}\")` before invoking deterministic tools",
143 permissions::FEATURE_TOOLS_DETERMINISTIC,
144 permissions::FEATURE_TOOLS_DETERMINISTIC
145 ),
146 });
147 }
148 runner(args)
149 });
150 registry.register(RegisteredBuiltin {
151 name,
152 module: "tools",
153 method,
154 handler,
155 });
156}
157
158fn handle_enable(args: &[VmValue]) -> Result<VmValue, HostlibError> {
163 let feature = match args.first() {
164 Some(VmValue::String(s)) => s.to_string(),
165 Some(VmValue::Dict(dict)) => match dict.get("feature") {
166 Some(VmValue::String(s)) => s.to_string(),
167 _ => {
168 return Err(HostlibError::MissingParameter {
169 builtin: "hostlib_enable",
170 param: "feature",
171 });
172 }
173 },
174 _ => {
175 return Err(HostlibError::MissingParameter {
176 builtin: "hostlib_enable",
177 param: "feature",
178 });
179 }
180 };
181
182 match feature.as_str() {
183 permissions::FEATURE_TOOLS_DETERMINISTIC => {
184 let newly_enabled = permissions::enable(&feature);
185 let mut map: BTreeMap<String, VmValue> = BTreeMap::new();
186 map.insert("feature".to_string(), VmValue::String(Rc::from(feature)));
187 map.insert("enabled".to_string(), VmValue::Bool(true));
188 map.insert("newly_enabled".to_string(), VmValue::Bool(newly_enabled));
189 Ok(VmValue::Dict(Rc::new(map)))
190 }
191 other => Err(HostlibError::InvalidParameter {
192 builtin: "hostlib_enable",
193 param: "feature",
194 message: format!("unknown feature `{other}`; supported: [`tools:deterministic`]"),
195 }),
196 }
197}