Skip to content

Commit

Permalink
Copy over the cli changes
Browse files Browse the repository at this point in the history
  • Loading branch information
jneem committed Nov 21, 2024
1 parent 02e5f9a commit 516f2c1
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 11 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ metrics = ["dep:metrics", "dep:metrics-util", "nickel-lang-core/metrics"]

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

clap = { workspace = true, features = ["derive", "string"] }
serde = { workspace = true, features = ["derive"] }
Expand Down
5 changes: 4 additions & 1 deletion cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use git_version::git_version;

use crate::{
completions::GenCompletionsCommand, eval::EvalCommand, export::ExportCommand,
pprint_ast::PprintAstCommand, query::QueryCommand, typecheck::TypecheckCommand,
package::PackageCommand, pprint_ast::PprintAstCommand, query::QueryCommand,
typecheck::TypecheckCommand,
};

use nickel_lang_core::error::report::ErrorFormat;
Expand Down Expand Up @@ -72,6 +73,8 @@ pub enum Command {
Export(ExportCommand),
/// Prints the metadata attached to an attribute, given as a path
Query(QueryCommand),
/// Performs packaging and dependency-resolution operations
Package(PackageCommand),
/// Typechecks the program but does not run it
Typecheck(TypecheckCommand),
/// Starts a REPL session
Expand Down
39 changes: 39 additions & 0 deletions cli/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Error handling for the CLI.
use std::path::PathBuf;

use nickel_lang_core::{
error::{
report::{report, ColorOpt, ErrorFormat},
Expand Down Expand Up @@ -53,6 +55,14 @@ pub enum Error {
files: Files,
error: CliUsageError,
},
NoManifest,
/// Provided a path without a parent directory.
PathWithoutParent {
path: PathBuf,
},
Package {
error: nickel_lang_package::error::Error,
},
FailedTests,
}

Expand Down Expand Up @@ -240,6 +250,12 @@ impl From<nickel_lang_core::repl::InitError> for Error {
}
}

impl From<nickel_lang_package::error::Error> for Error {
fn from(error: nickel_lang_package::error::Error) -> Self {
Error::Package { error }
}
}

// Report a standalone error which doesn't actually refer to any source code.
//
// Wrapping all errors in a diagnostic makes sure all errors are rendered using
Expand Down Expand Up @@ -280,6 +296,29 @@ impl Error {
Error::Format { error } => report_with_msg("format error", error.to_string()),
Error::CliUsage { error, mut files } => core_report(&mut files, error, format, color),
Error::FailedTests => report_str("tests failed"),
Error::NoManifest => report_str("failed to find a manifest file"),
Error::Package { error } => {
if let nickel_lang_package::error::Error::ManifestEval {
package,
mut files,
error,
} = error
{
let msg = if let Some(package) = package {
format!("failed to evaluate manifest file for package {package}")
} else {
"failed to evaluate package manifest".to_owned()
};
report_str(&msg);
core_report(&mut files, error, format, color);
} else {
report_with_msg("failed to read manifest file", error.to_string())
}
}
Error::PathWithoutParent { path } => report_str(&format!(
"path {} doesn't have a parent directory",
path.display()
)),
}
}
}
30 changes: 29 additions & 1 deletion cli/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::path::PathBuf;

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

use crate::{customize::Customize, global::GlobalContext};
use crate::{customize::Customize, error::Error, global::GlobalContext};

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

#[command(flatten)]
pub customize_mode: Customize,

/// Path to a package lock file.
///
/// This is required for package management features to work. (Future
/// versions may auto-detect a lock file.)
#[arg(long, global = true)]
pub lock_file: Option<PathBuf>,

#[arg(long, global = true)]
/// Filesystem location for caching fetched packages.
///
/// Defaults to an appropriate platform-dependent value.
pub package_cache_dir: Option<PathBuf>,
}

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

if let Some(lock_file_path) = self.lock_file.as_ref() {
let lock_file = LockFile::from_path(lock_file_path);
let lock_dir = lock_file_path
.parent()
.ok_or_else(|| Error::PathWithoutParent {
path: lock_file_path.clone(),
})?;
let mut config = PackageConfig::default();
if let Some(cache_dir) = self.package_cache_dir.as_ref() {
config = config.with_cache_dir(cache_dir.to_owned());
};
program.set_package_map(lock_file.package_map(lock_dir, &config)?);
}

#[cfg(debug_assertions)]
if self.nostdlib {
program.set_skip_stdlib();
Expand Down
2 changes: 2 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod eval;
mod export;
mod global;
mod input;
mod package;
mod pprint_ast;
mod query;
mod typecheck;
Expand Down Expand Up @@ -48,6 +49,7 @@ fn main() -> ExitCode {
Command::Export(export) => export.run(&mut ctxt),
Command::Query(query) => query.run(&mut ctxt),
Command::Typecheck(typecheck) => typecheck.run(&mut ctxt),
Command::Package(package) => package.run(&mut ctxt),
Command::GenCompletions(completions) => completions.run(&mut ctxt),

#[cfg(feature = "repl")]
Expand Down
187 changes: 187 additions & 0 deletions cli/src/package.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
use std::{
collections::HashMap,
env::current_dir,
path::{Path, PathBuf},
};

use nickel_lang_core::{identifier::Ident, package::PackageMap};
use nickel_lang_package::{
config::Config,
index::{self, PackageIndex},
version::SemVer,
ManifestFile, ObjectId,
};

use crate::{
error::{CliResult, Error},
global::GlobalContext,
};

#[derive(clap::Subcommand, Debug)]
pub enum Command {
GenerateLockfile,
DebugResolution,
DownloadDeps {
#[arg(long)]
out_dir: PathBuf,
},
RefreshIndex,
/// Modify a local copy of the index, by adding a new version of a package.
///
/// You must first push your package to a github repository, and make a note of
/// the commit id that you want to publish.
///
/// To actually publish to the global registry, you need to do a bunch more
/// steps. Eventually, we'll provide tooling to automate this.
///
/// 1. Fork the nickel mine (github.com/nickel-lang/nickel-mine) on github.
/// 2. Clone your fork onto your local machine.
/// 3. Run `nickel publish-local --index <directory-of-your-clone> --package-id github/you/your-package --commit-id <git hash> --version 0.1.0`
/// 4. You should see that your local machine's index was modified. Commit that modification
/// and open a pull request to the nickel mine.
PublishLocal {
#[arg(long)]
index: PathBuf,

#[arg(long)]
version: SemVer,

#[arg(long)]
commit_id: ObjectId,

#[arg(long)]
package_id: index::Id,
},
}

#[derive(clap::Parser, Debug)]
pub struct PackageCommand {
#[command(subcommand)]
pub command: Command,

#[arg(long, global = true)]
pub manifest_path: Option<PathBuf>,
}

impl PackageCommand {
fn find_manifest(&self) -> CliResult<PathBuf> {
match &self.manifest_path {
Some(p) => Ok(p.clone()),
None => {
let mut dir = current_dir()?;

loop {
let path = dir.join("package.ncl");
if path.is_file() {
return Ok(path);
}

if !dir.pop() {
return Err(Error::NoManifest);
}
}
}
}
}

fn load_manifest(&self) -> CliResult<ManifestFile> {
Ok(ManifestFile::from_path(self.find_manifest()?)?)
}

pub fn run(self, ctxt: &mut GlobalContext) {
ctxt.reporter.report_result(self.run_result());
}

pub fn run_result(self) -> CliResult<()> {
// TODO: have some global commands to change the config
match &self.command {
Command::GenerateLockfile => {
self.load_manifest()?.regenerate_lock(Config::default())?;
}
Command::DebugResolution => {
let path = self.find_manifest()?;
let manifest = ManifestFile::from_path(path.clone())?;
let resolution = manifest.resolve(Config::default())?;
let package_map = resolution.package_map(&manifest)?;
print_package_map(&package_map);
}
Command::PublishLocal {
index,
package_id,
version,
commit_id,
} => {
let package =
nickel_lang_package::index::fetch_git(package_id, version.clone(), commit_id)?;
let config = Config::default().with_index_dir(index.clone());
let mut package_index = PackageIndex::new(config);
package_index.save(package)?;
eprintln!(
"Added package {package_id}@{version} to the index at {}",
index.display()
);
}
Command::RefreshIndex => {
let index = PackageIndex::new(Config::default());
index.fetch_from_github()?;
}
Command::DownloadDeps { out_dir } => {
let path = self.find_manifest()?;
let manifest = ManifestFile::from_path(path.clone())?;
let config = Config {
index_package_dir: out_dir.join("index-packages"),
git_package_dir: out_dir.join("git-packages"),
..Config::default()
};

let resolution = manifest.resolve(config)?;

for (pkg, versions) in resolution.index_packages {
for v in versions {
resolution.index.ensure_downloaded(&pkg, v).unwrap();
}
}
}
}

Ok(())
}
}

fn print_package_map(map: &PackageMap) {
let mut by_parent: HashMap<&Path, Vec<(Ident, &Path)>> = HashMap::new();
for ((parent, name), child) in &map.packages {
by_parent
.entry(parent.as_path())
.or_default()
.push((*name, child));
}

if map.top_level.is_empty() {
eprintln!("No top-level dependencies");
} else {
eprintln!("Top-level dependencies:");
let mut top_level = map.top_level.iter().collect::<Vec<_>>();
top_level.sort();
for (name, path) in top_level {
eprintln!(" {} -> {}", name, path.display());
}
}

let mut by_parent = by_parent.into_iter().collect::<Vec<_>>();
by_parent.sort();
if by_parent.is_empty() {
eprintln!("No transitive dependencies");
} else {
eprintln!("Transitive dependencies:");

for (parent, mut deps) in by_parent {
deps.sort();
eprintln!(" {}", parent.display());

for (name, path) in deps {
eprintln!(" {} -> {}", name, path.display());
}
}
}
}
8 changes: 4 additions & 4 deletions package/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::path::{Path, PathBuf};

use gix::ObjectId;
use nickel_lang_core::{eval::cache::CacheImpl, identifier::Ident, program::Program};
use nickel_lang_core::{files::Files, identifier::Ident};

use crate::{
index::{self},
Expand All @@ -20,7 +20,7 @@ pub enum Error {
},
ManifestEval {
package: Option<Ident>,
program: Program<CacheImpl>,
files: Files,
error: nickel_lang_core::error::Error,
},
NoPackageRoot {
Expand Down Expand Up @@ -165,9 +165,9 @@ impl<T> ResultExt for Result<T, Error> {

fn in_package(self, package: Ident) -> Result<Self::T, Error> {
self.map_err(|e| match e {
Error::ManifestEval { program, error, .. } => Error::ManifestEval {
Error::ManifestEval { files, error, .. } => Error::ManifestEval {
package: Some(package),
program,
files,
error,
},
x => x,
Expand Down
Loading

0 comments on commit 516f2c1

Please sign in to comment.