Skip to content

Commit 516f2c1

Browse files
committed
Copy over the cli changes
1 parent 02e5f9a commit 516f2c1

File tree

10 files changed

+275
-11
lines changed

10 files changed

+275
-11
lines changed

Cargo.lock

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ metrics = ["dep:metrics", "dep:metrics-util", "nickel-lang-core/metrics"]
2424

2525
[dependencies]
2626
nickel-lang-core = { workspace = true, features = [ "markdown", "clap" ], default-features = false }
27+
# TODO: make optional
28+
nickel-lang-package.workspace = true
29+
gix = { workspace = true, features = ["blocking-http-transport-reqwest-rust-tls"]}
30+
# TODO: use the version parsing in nickel-lang-package instead
31+
semver = { version = "1.0.23", features = ["serde"] }
2732

2833
clap = { workspace = true, features = ["derive", "string"] }
2934
serde = { workspace = true, features = ["derive"] }

cli/src/cli.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use git_version::git_version;
44

55
use crate::{
66
completions::GenCompletionsCommand, eval::EvalCommand, export::ExportCommand,
7-
pprint_ast::PprintAstCommand, query::QueryCommand, typecheck::TypecheckCommand,
7+
package::PackageCommand, pprint_ast::PprintAstCommand, query::QueryCommand,
8+
typecheck::TypecheckCommand,
89
};
910

1011
use nickel_lang_core::error::report::ErrorFormat;
@@ -72,6 +73,8 @@ pub enum Command {
7273
Export(ExportCommand),
7374
/// Prints the metadata attached to an attribute, given as a path
7475
Query(QueryCommand),
76+
/// Performs packaging and dependency-resolution operations
77+
Package(PackageCommand),
7578
/// Typechecks the program but does not run it
7679
Typecheck(TypecheckCommand),
7780
/// Starts a REPL session

cli/src/error.rs

+39
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Error handling for the CLI.
22
3+
use std::path::PathBuf;
4+
35
use nickel_lang_core::{
46
error::{
57
report::{report, ColorOpt, ErrorFormat},
@@ -53,6 +55,14 @@ pub enum Error {
5355
files: Files,
5456
error: CliUsageError,
5557
},
58+
NoManifest,
59+
/// Provided a path without a parent directory.
60+
PathWithoutParent {
61+
path: PathBuf,
62+
},
63+
Package {
64+
error: nickel_lang_package::error::Error,
65+
},
5666
FailedTests,
5767
}
5868

@@ -240,6 +250,12 @@ impl From<nickel_lang_core::repl::InitError> for Error {
240250
}
241251
}
242252

253+
impl From<nickel_lang_package::error::Error> for Error {
254+
fn from(error: nickel_lang_package::error::Error) -> Self {
255+
Error::Package { error }
256+
}
257+
}
258+
243259
// Report a standalone error which doesn't actually refer to any source code.
244260
//
245261
// Wrapping all errors in a diagnostic makes sure all errors are rendered using
@@ -280,6 +296,29 @@ impl Error {
280296
Error::Format { error } => report_with_msg("format error", error.to_string()),
281297
Error::CliUsage { error, mut files } => core_report(&mut files, error, format, color),
282298
Error::FailedTests => report_str("tests failed"),
299+
Error::NoManifest => report_str("failed to find a manifest file"),
300+
Error::Package { error } => {
301+
if let nickel_lang_package::error::Error::ManifestEval {
302+
package,
303+
mut files,
304+
error,
305+
} = error
306+
{
307+
let msg = if let Some(package) = package {
308+
format!("failed to evaluate manifest file for package {package}")
309+
} else {
310+
"failed to evaluate package manifest".to_owned()
311+
};
312+
report_str(&msg);
313+
core_report(&mut files, error, format, color);
314+
} else {
315+
report_with_msg("failed to read manifest file", error.to_string())
316+
}
317+
}
318+
Error::PathWithoutParent { path } => report_str(&format!(
319+
"path {} doesn't have a parent directory",
320+
path.display()
321+
)),
283322
}
284323
}
285324
}

cli/src/input.rs

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::path::PathBuf;
22

33
use nickel_lang_core::{eval::cache::lazy::CBNCache, program::Program};
4+
use nickel_lang_package::{config::Config as PackageConfig, lock::LockFile};
45

5-
use crate::{customize::Customize, global::GlobalContext};
6+
use crate::{customize::Customize, error::Error, global::GlobalContext};
67

78
#[derive(clap::Parser, Debug)]
89
pub struct InputOptions<Customize: clap::Args> {
@@ -26,6 +27,19 @@ pub struct InputOptions<Customize: clap::Args> {
2627

2728
#[command(flatten)]
2829
pub customize_mode: Customize,
30+
31+
/// Path to a package lock file.
32+
///
33+
/// This is required for package management features to work. (Future
34+
/// versions may auto-detect a lock file.)
35+
#[arg(long, global = true)]
36+
pub lock_file: Option<PathBuf>,
37+
38+
#[arg(long, global = true)]
39+
/// Filesystem location for caching fetched packages.
40+
///
41+
/// Defaults to an appropriate platform-dependent value.
42+
pub package_cache_dir: Option<PathBuf>,
2943
}
3044

3145
pub enum PrepareError {
@@ -66,6 +80,20 @@ impl<C: clap::Args + Customize> Prepare for InputOptions<C> {
6680
program.add_import_paths(nickel_path.split(':'));
6781
}
6882

83+
if let Some(lock_file_path) = self.lock_file.as_ref() {
84+
let lock_file = LockFile::from_path(lock_file_path);
85+
let lock_dir = lock_file_path
86+
.parent()
87+
.ok_or_else(|| Error::PathWithoutParent {
88+
path: lock_file_path.clone(),
89+
})?;
90+
let mut config = PackageConfig::default();
91+
if let Some(cache_dir) = self.package_cache_dir.as_ref() {
92+
config = config.with_cache_dir(cache_dir.to_owned());
93+
};
94+
program.set_package_map(lock_file.package_map(lock_dir, &config)?);
95+
}
96+
6997
#[cfg(debug_assertions)]
7098
if self.nostdlib {
7199
program.set_skip_stdlib();

cli/src/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod eval;
1919
mod export;
2020
mod global;
2121
mod input;
22+
mod package;
2223
mod pprint_ast;
2324
mod query;
2425
mod typecheck;
@@ -48,6 +49,7 @@ fn main() -> ExitCode {
4849
Command::Export(export) => export.run(&mut ctxt),
4950
Command::Query(query) => query.run(&mut ctxt),
5051
Command::Typecheck(typecheck) => typecheck.run(&mut ctxt),
52+
Command::Package(package) => package.run(&mut ctxt),
5153
Command::GenCompletions(completions) => completions.run(&mut ctxt),
5254

5355
#[cfg(feature = "repl")]

cli/src/package.rs

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use std::{
2+
collections::HashMap,
3+
env::current_dir,
4+
path::{Path, PathBuf},
5+
};
6+
7+
use nickel_lang_core::{identifier::Ident, package::PackageMap};
8+
use nickel_lang_package::{
9+
config::Config,
10+
index::{self, PackageIndex},
11+
version::SemVer,
12+
ManifestFile, ObjectId,
13+
};
14+
15+
use crate::{
16+
error::{CliResult, Error},
17+
global::GlobalContext,
18+
};
19+
20+
#[derive(clap::Subcommand, Debug)]
21+
pub enum Command {
22+
GenerateLockfile,
23+
DebugResolution,
24+
DownloadDeps {
25+
#[arg(long)]
26+
out_dir: PathBuf,
27+
},
28+
RefreshIndex,
29+
/// Modify a local copy of the index, by adding a new version of a package.
30+
///
31+
/// You must first push your package to a github repository, and make a note of
32+
/// the commit id that you want to publish.
33+
///
34+
/// To actually publish to the global registry, you need to do a bunch more
35+
/// steps. Eventually, we'll provide tooling to automate this.
36+
///
37+
/// 1. Fork the nickel mine (github.com/nickel-lang/nickel-mine) on github.
38+
/// 2. Clone your fork onto your local machine.
39+
/// 3. Run `nickel publish-local --index <directory-of-your-clone> --package-id github/you/your-package --commit-id <git hash> --version 0.1.0`
40+
/// 4. You should see that your local machine's index was modified. Commit that modification
41+
/// and open a pull request to the nickel mine.
42+
PublishLocal {
43+
#[arg(long)]
44+
index: PathBuf,
45+
46+
#[arg(long)]
47+
version: SemVer,
48+
49+
#[arg(long)]
50+
commit_id: ObjectId,
51+
52+
#[arg(long)]
53+
package_id: index::Id,
54+
},
55+
}
56+
57+
#[derive(clap::Parser, Debug)]
58+
pub struct PackageCommand {
59+
#[command(subcommand)]
60+
pub command: Command,
61+
62+
#[arg(long, global = true)]
63+
pub manifest_path: Option<PathBuf>,
64+
}
65+
66+
impl PackageCommand {
67+
fn find_manifest(&self) -> CliResult<PathBuf> {
68+
match &self.manifest_path {
69+
Some(p) => Ok(p.clone()),
70+
None => {
71+
let mut dir = current_dir()?;
72+
73+
loop {
74+
let path = dir.join("package.ncl");
75+
if path.is_file() {
76+
return Ok(path);
77+
}
78+
79+
if !dir.pop() {
80+
return Err(Error::NoManifest);
81+
}
82+
}
83+
}
84+
}
85+
}
86+
87+
fn load_manifest(&self) -> CliResult<ManifestFile> {
88+
Ok(ManifestFile::from_path(self.find_manifest()?)?)
89+
}
90+
91+
pub fn run(self, ctxt: &mut GlobalContext) {
92+
ctxt.reporter.report_result(self.run_result());
93+
}
94+
95+
pub fn run_result(self) -> CliResult<()> {
96+
// TODO: have some global commands to change the config
97+
match &self.command {
98+
Command::GenerateLockfile => {
99+
self.load_manifest()?.regenerate_lock(Config::default())?;
100+
}
101+
Command::DebugResolution => {
102+
let path = self.find_manifest()?;
103+
let manifest = ManifestFile::from_path(path.clone())?;
104+
let resolution = manifest.resolve(Config::default())?;
105+
let package_map = resolution.package_map(&manifest)?;
106+
print_package_map(&package_map);
107+
}
108+
Command::PublishLocal {
109+
index,
110+
package_id,
111+
version,
112+
commit_id,
113+
} => {
114+
let package =
115+
nickel_lang_package::index::fetch_git(package_id, version.clone(), commit_id)?;
116+
let config = Config::default().with_index_dir(index.clone());
117+
let mut package_index = PackageIndex::new(config);
118+
package_index.save(package)?;
119+
eprintln!(
120+
"Added package {package_id}@{version} to the index at {}",
121+
index.display()
122+
);
123+
}
124+
Command::RefreshIndex => {
125+
let index = PackageIndex::new(Config::default());
126+
index.fetch_from_github()?;
127+
}
128+
Command::DownloadDeps { out_dir } => {
129+
let path = self.find_manifest()?;
130+
let manifest = ManifestFile::from_path(path.clone())?;
131+
let config = Config {
132+
index_package_dir: out_dir.join("index-packages"),
133+
git_package_dir: out_dir.join("git-packages"),
134+
..Config::default()
135+
};
136+
137+
let resolution = manifest.resolve(config)?;
138+
139+
for (pkg, versions) in resolution.index_packages {
140+
for v in versions {
141+
resolution.index.ensure_downloaded(&pkg, v).unwrap();
142+
}
143+
}
144+
}
145+
}
146+
147+
Ok(())
148+
}
149+
}
150+
151+
fn print_package_map(map: &PackageMap) {
152+
let mut by_parent: HashMap<&Path, Vec<(Ident, &Path)>> = HashMap::new();
153+
for ((parent, name), child) in &map.packages {
154+
by_parent
155+
.entry(parent.as_path())
156+
.or_default()
157+
.push((*name, child));
158+
}
159+
160+
if map.top_level.is_empty() {
161+
eprintln!("No top-level dependencies");
162+
} else {
163+
eprintln!("Top-level dependencies:");
164+
let mut top_level = map.top_level.iter().collect::<Vec<_>>();
165+
top_level.sort();
166+
for (name, path) in top_level {
167+
eprintln!(" {} -> {}", name, path.display());
168+
}
169+
}
170+
171+
let mut by_parent = by_parent.into_iter().collect::<Vec<_>>();
172+
by_parent.sort();
173+
if by_parent.is_empty() {
174+
eprintln!("No transitive dependencies");
175+
} else {
176+
eprintln!("Transitive dependencies:");
177+
178+
for (parent, mut deps) in by_parent {
179+
deps.sort();
180+
eprintln!(" {}", parent.display());
181+
182+
for (name, path) in deps {
183+
eprintln!(" {} -> {}", name, path.display());
184+
}
185+
}
186+
}
187+
}

package/src/error.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::path::{Path, PathBuf};
22

33
use gix::ObjectId;
4-
use nickel_lang_core::{eval::cache::CacheImpl, identifier::Ident, program::Program};
4+
use nickel_lang_core::{files::Files, identifier::Ident};
55

66
use crate::{
77
index::{self},
@@ -20,7 +20,7 @@ pub enum Error {
2020
},
2121
ManifestEval {
2222
package: Option<Ident>,
23-
program: Program<CacheImpl>,
23+
files: Files,
2424
error: nickel_lang_core::error::Error,
2525
},
2626
NoPackageRoot {
@@ -165,9 +165,9 @@ impl<T> ResultExt for Result<T, Error> {
165165

166166
fn in_package(self, package: Ident) -> Result<Self::T, Error> {
167167
self.map_err(|e| match e {
168-
Error::ManifestEval { program, error, .. } => Error::ManifestEval {
168+
Error::ManifestEval { files, error, .. } => Error::ManifestEval {
169169
package: Some(package),
170-
program,
170+
files,
171171
error,
172172
},
173173
x => x,

0 commit comments

Comments
 (0)