Skip to content

Commit 66910dc

Browse files
committed
feat: add --include-path/-I flag for module resolution
1 parent 82f99cd commit 66910dc

File tree

4 files changed

+86
-3
lines changed

4 files changed

+86
-3
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
- [Syntax Highlighting](#syntax-highlighting)
6262
- [Streaming Input](#streaming-input)
6363
- [Plugins](#plugins)
64+
- [Module Paths](#module-paths)
6465
- [Embedded Modules](#embedded-modules)
6566
- [Routing](#routing)
6667
- [HTML DSL](#html-dsl)
@@ -670,6 +671,14 @@ $ http-nu --plugin ~/.cargo/bin/nu_plugin_inc eval -c '1 | inc'
670671
2
671672
```
672673

674+
### Module Paths
675+
676+
Make module paths available with `-I` / `--include-path`:
677+
678+
```bash
679+
$ http-nu -I ./lib -I ./vendor :3001 '{|req| use mymod.nu; ...}'
680+
```
681+
673682
### Embedded Modules
674683

675684
#### Routing

src/engine.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use nu_protocol::format_cli_error;
1212
use nu_protocol::{
1313
debugger::WithoutDebug,
1414
engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet},
15-
OutDest, PipelineData, PluginIdentity, RegisteredPlugin, ShellError, Signals, Span, Value,
15+
OutDest, PipelineData, PluginIdentity, RegisteredPlugin, ShellError, Signals, Span, Type,
16+
Value,
1617
};
1718

1819
use crate::commands::{
@@ -180,6 +181,29 @@ impl Engine {
180181
self.state.set_signals(Signals::new(interrupt));
181182
}
182183

184+
/// Sets NU_LIB_DIRS const for module resolution
185+
pub fn set_lib_dirs(&mut self, paths: &[std::path::PathBuf]) -> Result<(), Error> {
186+
if paths.is_empty() {
187+
return Ok(());
188+
}
189+
let span = Span::unknown();
190+
let vals: Vec<Value> = paths
191+
.iter()
192+
.map(|p| Value::string(p.to_string_lossy(), span))
193+
.collect();
194+
195+
let mut working_set = StateWorkingSet::new(&self.state);
196+
let var_id = working_set.add_variable(
197+
b"$NU_LIB_DIRS".into(),
198+
span,
199+
Type::List(Box::new(Type::String)),
200+
false,
201+
);
202+
working_set.set_variable_const_val(var_id, Value::list(vals, span));
203+
self.state.merge_delta(working_set.render())?;
204+
Ok(())
205+
}
206+
183207
/// Evaluate a script string and return the result value
184208
pub fn eval(&self, script: &str) -> Result<Value, Error> {
185209
let mut working_set = StateWorkingSet::new(&self.state);

src/main.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ struct Args {
5454
/// Trust proxies from these CIDR ranges for X-Forwarded-For parsing
5555
#[clap(long = "trust-proxy", value_name = "CIDR")]
5656
trust_proxies: Vec<ipnet::IpNet>,
57+
58+
/// Set NU_LIB_DIRS for module resolution (can be repeated)
59+
#[clap(short = 'I', long = "include-path", global = true, value_name = "PATH")]
60+
include_paths: Vec<PathBuf>,
5761
}
5862

5963
#[derive(Clone, Debug, Default, clap::ValueEnum)]
@@ -81,9 +85,11 @@ enum Command {
8185
fn create_base_engine(
8286
interrupt: Arc<AtomicBool>,
8387
plugins: &[PathBuf],
88+
include_paths: &[PathBuf],
8489
) -> Result<Engine, Box<dyn std::error::Error + Send + Sync>> {
8590
let mut engine = Engine::new()?;
8691
engine.add_custom_commands()?;
92+
engine.set_lib_dirs(include_paths)?;
8793

8894
// Load plugins
8995
for plugin_path in plugins {
@@ -386,6 +392,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
386392

387393
let mut engine = Engine::new()?;
388394
engine.add_custom_commands()?;
395+
engine.set_lib_dirs(&args.include_paths)?;
389396

390397
for plugin_path in &args.plugins {
391398
engine.load_plugin(plugin_path)?;
@@ -411,7 +418,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
411418
let read_stdin = closure == "-";
412419

413420
// Create base engine with commands, signals, and plugins
414-
let base_engine = create_base_engine(interrupt.clone(), &args.plugins)?;
421+
let base_engine = create_base_engine(interrupt.clone(), &args.plugins, &args.include_paths)?;
415422

416423
// Create channel for scripts
417424
let (tx, rx) = mpsc::channel::<String>(1);

tests/eval_test.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use assert_cmd::cargo::cargo_bin;
22
use assert_cmd::Command;
33
use std::io::Write;
4-
use tempfile::NamedTempFile;
4+
use tempfile::{NamedTempFile, TempDir};
55

66
#[test]
77
fn test_eval_commands_flag() {
@@ -108,3 +108,46 @@ fn test_eval_with_plugin() {
108108
.success()
109109
.stdout("PLUGIN_WORKS\n");
110110
}
111+
112+
#[test]
113+
fn test_eval_include_path() {
114+
let dir = TempDir::new().unwrap();
115+
let module_path = dir.path().join("mymod.nu");
116+
std::fs::write(&module_path, "export def hello [] { 'world' }").unwrap();
117+
118+
Command::cargo_bin("http-nu")
119+
.unwrap()
120+
.args([
121+
"-I",
122+
dir.path().to_str().unwrap(),
123+
"eval",
124+
"-c",
125+
"use mymod.nu; mymod hello",
126+
])
127+
.assert()
128+
.success()
129+
.stdout("world\n");
130+
}
131+
132+
#[test]
133+
fn test_eval_include_path_multiple() {
134+
let dir1 = TempDir::new().unwrap();
135+
let dir2 = TempDir::new().unwrap();
136+
std::fs::write(dir1.path().join("mod1.nu"), "export def a [] { 1 }").unwrap();
137+
std::fs::write(dir2.path().join("mod2.nu"), "export def b [] { 2 }").unwrap();
138+
139+
Command::cargo_bin("http-nu")
140+
.unwrap()
141+
.args([
142+
"-I",
143+
dir1.path().to_str().unwrap(),
144+
"-I",
145+
dir2.path().to_str().unwrap(),
146+
"eval",
147+
"-c",
148+
"use mod1.nu; use mod2.nu; (mod1 a) + (mod2 b)",
149+
])
150+
.assert()
151+
.success()
152+
.stdout("3\n");
153+
}

0 commit comments

Comments
 (0)