Skip to content

Commit 7911d06

Browse files
luispedroclaude
andcommitted
Restore global/local import distinction and known-module auto-download
Global `import` is once again only accepted for known modules; local-only modules require `local import`. Known modules absent locally are downloaded into the user data directory, mirroring the Haskell `loadModule`/`findLoad` behaviour. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6bfcd3d commit 7911d06

4 files changed

Lines changed: 96 additions & 20 deletions

File tree

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ Version 1.6.0-beta2 2026-07-04 by luispedro
2323
* External module commands now see `NGLESS_NR_CORES` set to the configured
2424
worker thread count (`--jobs`/`--threads`, or the `batch` scheduler's
2525
allotment) instead of a hard-coded 1.
26+
* Restored the distinction between global and local module imports. A plain
27+
`import` is again only accepted for known modules (`igc`, `om-rgc`, `motus`,
28+
the gut-catalog modules, ...); importing a local-only module now requires
29+
`local import`. Known modules that are not present locally are auto-downloaded
30+
again.
2631
* Removed the deprecated `strand` argument to `count()`; use `sense`
2732
(`{both}`/`{sense}`/`{antisense}`) instead. `strand=True` is equivalent to
2833
`sense={sense}`.

src/cli.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,22 @@ fn run_script(opts: &RunOpts) -> NgResult<i32> {
930930
extra_funcs.extend(fs)
931931
}
932932
None => {
933+
// A plain (global) `import` is only allowed for known modules; local-only modules
934+
// must be brought in with `local import` (mirrors `loadModule`'s `isGlobalImport`
935+
// gate in `NGLess/ExternalModules.hs`).
936+
if matches!(m, crate::ast::ModInfo::Import { .. })
937+
&& !crate::external_modules::is_known_module(m.name())
938+
{
939+
let suggestion = crate::suggestion::suggestion_message(
940+
m.name(),
941+
crate::external_modules::KNOWN_MODULES,
942+
);
943+
return Err(NgError::script(format!(
944+
"Module '{}' is not known.\n\t{suggestion}\n\tTo import local modules, use \
945+
\"local import\"",
946+
m.name(),
947+
)));
948+
}
933949
let em = crate::external_modules::find_load(
934950
m.name(),
935951
m.version(),

src/external_modules.rs

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -642,8 +642,74 @@ pub fn module_env(module_dir: &Path, temp_dir: &Path) -> Vec<(String, String)> {
642642
env
643643
}
644644

645+
/// Modules that NGLess knows how to fetch from the download server (mirrors `knownModules` in
646+
/// `NGLess/Modules.hs`). A plain (global) `import` is only allowed for these; any other name must be
647+
/// brought in with `local import`. When one of these is requested but not present locally,
648+
/// `find_load` auto-downloads it.
649+
pub const KNOWN_MODULES: &[&str] = &[
650+
"example-cmd",
651+
"gmgc",
652+
"igc",
653+
"om-rgc",
654+
"DogGutCatalog",
655+
"MouseGutCatalog",
656+
"PigGutCatalog",
657+
"specI",
658+
"motus",
659+
];
660+
661+
/// Whether `name` is a known (downloadable, globally importable) module.
662+
pub fn is_known_module(name: &str) -> bool {
663+
KNOWN_MODULES.contains(&name)
664+
}
665+
666+
/// Download a known external module into the user data directory (mirrors `downloadModule`). Returns
667+
/// the module directory (`<user-data>/Modules/<name>.ngm/<version>`).
668+
fn download_module(name: &str, version: &str) -> NgResult<PathBuf> {
669+
let data_dir = crate::reference::user_data_directory().ok_or_else(|| {
670+
NgError::new(
671+
NgErrorType::SystemError,
672+
format!("Cannot download module '{name}': no user data directory is configured."),
673+
)
674+
})?;
675+
let nameversion = Path::new(&format!("{name}.ngm")).join(version);
676+
let destdir = data_dir.join("Modules").join(&nameversion);
677+
let base_url = crate::reference::download_base_url();
678+
let url = format!(
679+
"{}/Modules/{}.tar.gz",
680+
base_url.trim_end_matches('/'),
681+
nameversion.to_string_lossy()
682+
);
683+
crate::reference::download_expand_tar(&url, &data_dir)?;
684+
Ok(destdir)
685+
}
686+
687+
/// Load a module from a directory containing `module.yaml`, checking the version is compatible.
688+
fn load_from_dir(name: &str, version: &str, dir: PathBuf) -> NgResult<ExternalModule> {
689+
let yaml = dir.join("module.yaml");
690+
let text = std::fs::read_to_string(&yaml).map_err(|e| {
691+
NgError::new(
692+
NgErrorType::SystemError,
693+
format!("Could not read module file {}: {e}", yaml.display()),
694+
)
695+
})?;
696+
let raw: RawModule = serde_yaml::from_str(&text).map_err(|e| {
697+
NgError::new(
698+
NgErrorType::SystemError,
699+
format!(
700+
"Could not load module file {}. Error was `{e}`",
701+
yaml.display()
702+
),
703+
)
704+
})?;
705+
let module = ExternalModule::from_raw(raw, dir)?;
706+
check_compatible(name, version, &module)?;
707+
Ok(module)
708+
}
709+
645710
/// Find and load an external module (mirrors `findLoad`). Searches the current directory, then the
646-
/// global and user data directories, for `Modules/<name>.ngm/<version>/module.yaml`.
711+
/// global and user data directories, for `Modules/<name>.ngm/<version>/module.yaml`. If the module
712+
/// is not found locally but is a [`known module`](KNOWN_MODULES), it is downloaded first.
647713
pub fn find_load(name: &str, version: &str, data_dirs: &[String]) -> NgResult<ExternalModule> {
648714
let modpath = Path::new("Modules")
649715
.join(format!("{name}.ngm"))
@@ -663,26 +729,15 @@ pub fn find_load(name: &str, version: &str, data_dirs: &[String]) -> NgResult<Ex
663729
);
664730
searched.push(yaml.clone());
665731
if yaml.is_file() {
666-
let text = std::fs::read_to_string(&yaml).map_err(|e| {
667-
NgError::new(
668-
NgErrorType::SystemError,
669-
format!("Could not read module file {}: {e}", yaml.display()),
670-
)
671-
})?;
672-
let raw: RawModule = serde_yaml::from_str(&text).map_err(|e| {
673-
NgError::new(
674-
NgErrorType::SystemError,
675-
format!(
676-
"Could not load module file {}. Error was `{e}`",
677-
yaml.display()
678-
),
679-
)
680-
})?;
681-
let module = ExternalModule::from_raw(raw, dir)?;
682-
check_compatible(name, version, &module)?;
683-
return Ok(module);
732+
return load_from_dir(name, version, dir);
684733
}
685734
}
735+
// Not found locally: a known module is downloaded into the user data directory and loaded from
736+
// there (mirrors `findLoad`'s `downloadModule` fallback).
737+
if is_known_module(name) {
738+
let dir = download_module(name, version)?;
739+
return load_from_dir(name, version, dir);
740+
}
686741
let locations = searched
687742
.iter()
688743
.map(|p| format!("\t{}", p.display()))

src/reference.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ fn write_targz(oname: &str, entries: &[(PathBuf, String)]) -> NgResult<()> {
649649
/// The user data directory (mirrors `nConfUserDataDirectory`): defaults to
650650
/// `$XDG_DATA_HOME/ngless/data` or `$HOME/.local/share/ngless/data`, overridable via the
651651
/// `user-data-directory` config key.
652-
fn user_data_directory() -> Option<PathBuf> {
652+
pub fn user_data_directory() -> Option<PathBuf> {
653653
let dir = &crate::configuration::global().user_data_directory;
654654
if dir.is_empty() {
655655
None

0 commit comments

Comments
 (0)