From cdb2e5fd301a25ef56d6a80192695a239a0f6aa5 Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Fri, 4 Aug 2023 14:31:56 +0000 Subject: [PATCH 01/11] Refactor the Nickel CLI structure --- Cargo.lock | 10 ++ Cargo.toml | 3 +- cli/Cargo.toml | 3 +- cli/bin/nickel.rs | 248 ----------------------------------------- cli/src/cli.rs | 72 ++++++++++++ cli/src/completions.rs | 18 +++ cli/src/doc.rs | 138 ++++++++++++++--------- cli/src/eval.rs | 50 +++++++++ cli/src/export.rs | 64 +++++++++++ cli/src/format.rs | 71 ++++++------ cli/src/lib.rs | 6 - cli/src/main.rs | 45 ++++++++ cli/src/pprint_ast.rs | 28 +++++ cli/src/query.rs | 56 ++++++++++ cli/src/repl.rs | 34 ++++-- cli/src/typecheck.rs | 22 ++++ core/src/program.rs | 2 +- 17 files changed, 512 insertions(+), 358 deletions(-) delete mode 100644 cli/bin/nickel.rs create mode 100644 cli/src/cli.rs create mode 100644 cli/src/completions.rs create mode 100644 cli/src/eval.rs create mode 100644 cli/src/export.rs delete mode 100644 cli/src/lib.rs create mode 100644 cli/src/main.rs create mode 100644 cli/src/pprint_ast.rs create mode 100644 cli/src/query.rs create mode 100644 cli/src/typecheck.rs diff --git a/Cargo.lock b/Cargo.lock index 67643f2965..d5e59764f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,6 +361,15 @@ dependencies = [ "terminal_size", ] +[[package]] +name = "clap_complete" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" +dependencies = [ + "clap 4.3.0", +] + [[package]] name = "clap_derive" version = "4.3.0" @@ -1620,6 +1629,7 @@ name = "nickel-lang-cli" version = "1.1.1" dependencies = [ "clap 4.3.0", + "clap_complete", "directories", "git-version", "insta", diff --git a/Cargo.toml b/Cargo.toml index db2ba8f822..04b8a5ffe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ anyhow = "1.0" assert_cmd = "2.0.11" assert_matches = "1.5.0" clap = "4.3" +clap_complete = "4.3.2" codespan = "0.11" codespan-lsp = "0.11" codespan-reporting = "0.11" @@ -46,6 +47,7 @@ csv = "1" derive_more = "0.99" directories = "4.0.1" env_logger = "0.10" +git-version = "0.3.5" indexmap = "1.9.3" indoc = "2" insta = "1.29.0" @@ -85,7 +87,6 @@ toml = "0.7.2" typed-arena = "2.0.2" unicode-segmentation = "1.10.1" void = "1" -git-version = "0.3.5" topiary = { version = "0.2.3", git = "https://github.com/tweag/topiary.git", rev = "refs/heads/main" } # This should be kept in sync with the revision in topiary diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 6b5e70060c..2d629d7fb2 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -12,7 +12,7 @@ readme.workspace = true [[bin]] name = "nickel" -path = "bin/nickel.rs" +path = "src/main.rs" bench = false [features] @@ -33,6 +33,7 @@ tree-sitter-nickel = { workspace = true, optional = true } tempfile = { workspace = true, optional = true } git-version = { workspace = true } +clap_complete = { workspace = true } [dev-dependencies] nickel-lang-utils.workspace = true diff --git a/cli/bin/nickel.rs b/cli/bin/nickel.rs deleted file mode 100644 index 789b7a8848..0000000000 --- a/cli/bin/nickel.rs +++ /dev/null @@ -1,248 +0,0 @@ -//! Entry point of the program. -use core::fmt; -use git_version::git_version; -use nickel_lang_core::error::{Error, IOError}; -use nickel_lang_core::eval::cache::CacheImpl; -use nickel_lang_core::program::Program; -use nickel_lang_core::repl::query_print; -use nickel_lang_core::term::RichTerm; -use nickel_lang_core::{serialize, serialize::ExportFormat}; - -use std::path::PathBuf; -use std::{fs, io::Write, process}; - -/// Command-line options and subcommands. -#[derive(clap::Parser, Debug)] -/// The interpreter of the Nickel language. -struct Opt { - /// The input file. Standard input by default - #[arg(short, long, global = true)] - file: Option, - - #[cfg(debug_assertions)] - /// Skips the standard library import. For debugging only. This does not affect REPL - #[arg(long)] - nostdlib: bool, - - /// Coloring: auto, always, never. - #[arg(long, global = true, value_enum, default_value_t)] - color: clap::ColorChoice, - - #[command(subcommand)] - command: Option, - - #[arg(long, short = 'V')] - version: bool, -} - -/// Available subcommands. -#[derive(clap::Subcommand, Debug)] -enum Command { - /// Converts the parsed representation (AST) back to Nickel source code and prints it. Used for - /// debugging purpose - PprintAst { - /// Performs code transformations before printing - #[arg(long)] - transform: bool, - }, - /// Exports the result to a different format - Export { - #[arg(long, value_enum, default_value_t)] - format: ExportFormat, - - /// Output file. Standard output by default - #[arg(short, long)] - output: Option, - }, - /// Prints the metadata attached to an attribute, given as a path - Query { - path: Option, - #[arg(long)] - doc: bool, - #[arg(long)] - contract: bool, - #[arg(long = "type")] - typ: bool, - #[arg(long)] - default: bool, - #[arg(long)] - value: bool, - }, - /// Typechecks the program but do not run it - Typecheck, - /// Starts an REPL session - #[cfg(feature = "repl")] - Repl { - #[arg(long)] - history_file: Option, - }, - /// Generates the documentation files for the specified nickel file - #[cfg(feature = "doc")] - Doc { - /// The path of the generated documentation file. Default to - /// `~/.nickel/doc/.md` for input `.ncl`, or to - /// `~/.nickel/doc/out.md` if the input is read from stdin. - #[arg(short, long)] - output: Option, - /// Write documentation to stdout. Takes precedence over `output` - #[arg(long)] - stdout: bool, - /// The output format for the generated documentation. - #[arg(long, value_enum, default_value_t)] - format: nickel_lang_cli::doc::DocFormat, - }, - /// Format a nickel file - #[cfg(feature = "format")] - Format { - /// Output file. Standard output by default. - #[arg(short, long)] - output: Option, - /// Format in place, overwriting the input file. - #[arg(short, long, requires = "file")] - in_place: bool, - }, -} - -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct ParseFormatError(String); - -impl fmt::Display for ParseFormatError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "unsupported export format {}", self.0) - } -} - -fn handle_eval_commands(opts: Opt) { - let mut program = opts - .file - .clone() - .map(|f| Program::new_from_file(f, std::io::stderr())) - .unwrap_or_else(|| Program::new_from_stdin(std::io::stderr())) - .unwrap_or_else(|err| { - eprintln!("Error when reading input: {err}"); - process::exit(1) - }); - - #[cfg(debug_assertions)] - if opts.nostdlib { - program.set_skip_stdlib(); - } - - program.set_color(opts.color.into()); - - let result = match opts.command { - Some(Command::PprintAst { transform }) => program.pprint_ast( - &mut std::io::BufWriter::new(Box::new(std::io::stdout())), - transform, - ), - Some(Command::Export { format, output }) => export(&mut program, format, output), - Some(Command::Query { - path, - doc, - contract, - typ: types, - default, - value, - }) => { - program.query(path).map(|term| { - // Print a default selection of attributes if no option is specified - let attrs = if !doc && !contract && !types && !default && !value { - query_print::Attributes::default() - } else { - query_print::Attributes { - doc, - contract, - typ: types, - default, - value, - } - }; - - query_print::write_query_result(&mut std::io::stdout(), &term, attrs).unwrap() - }) - } - Some(Command::Typecheck) => program.typecheck(), - #[cfg(feature = "doc")] - Some(Command::Doc { - output, - stdout, - format, - }) => nickel_lang_cli::doc::export_doc( - &mut program, - opts.file.as_ref(), - output, - stdout, - format, - ), - None => program.eval_full().map(|t| println!("{t}")), - - #[cfg(feature = "repl")] - Some(Command::Repl { .. }) => unreachable!(), - #[cfg(feature = "format")] - Some(Command::Format { .. }) => unreachable!(), - }; - - if let Err(err) = result { - program.report(err); - process::exit(1) - } -} - -fn main() { - use clap::Parser; - - let opts = Opt::parse(); - - if opts.version { - println!( - "{} {} (rev {})", - env!("CARGO_BIN_NAME"), - env!("CARGO_PKG_VERSION"), - git_version!(fallback = env!("NICKEL_NIX_BUILD_REV")) - ); - return; - } - - match opts.command { - #[cfg(feature = "repl")] - Some(Command::Repl { history_file }) => { - nickel_lang_cli::repl::repl(history_file, opts.color.into()) - } - #[cfg(feature = "format")] - Some(Command::Format { output, in_place }) => { - nickel_lang_cli::format::format(opts.file.as_deref(), output.as_deref(), in_place) - } - _ => handle_eval_commands(opts), - } -} - -fn export( - program: &mut Program, - format: ExportFormat, - output: Option, -) -> Result<(), Error> { - let rt = program.eval_full_for_export().map(RichTerm::from)?; - - // We only add a trailing newline for JSON exports. Both YAML and TOML - // exporters already append a trailing newline by default. - let trailing_newline = format == ExportFormat::Json; - - serialize::validate(format, &rt)?; - - if let Some(file) = output { - let mut file = fs::File::create(file).map_err(IOError::from)?; - serialize::to_writer(&mut file, format, &rt)?; - - if trailing_newline { - writeln!(file).map_err(IOError::from)?; - } - } else { - serialize::to_writer(std::io::stdout(), format, &rt)?; - - if trailing_newline { - println!(); - } - } - - Ok(()) -} diff --git a/cli/src/cli.rs b/cli/src/cli.rs new file mode 100644 index 0000000000..c7e04d2a42 --- /dev/null +++ b/cli/src/cli.rs @@ -0,0 +1,72 @@ +//! Command-line options and subcommands. +use std::path::PathBuf; + +use git_version::git_version; + +use crate::{ + completions::GenCompletionsOptions, doc::DocOptions, eval::EvalOptions, export::ExportOptions, + format::FormatOptions, pprint_ast::PprintAstOptions, query::QueryOptions, repl::ReplOptions, + typecheck::TypecheckOptions, +}; + +#[derive(clap::Parser, Debug)] +/// The interpreter of the Nickel language. +#[command( + author, + about, + long_about = None, + version = format!("{} {} (rev {})", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION"), git_version!(fallback = env!("NICKEL_NIX_BUILD_REV"))) +)] +pub struct Options { + #[command(flatten)] + pub global: GlobalOptions, + + #[command(subcommand)] + pub command: Command, +} + +#[derive(clap::Parser, Debug)] +pub struct GlobalOptions { + #[cfg(debug_assertions)] + /// Skips the standard library import. For debugging only. This does not affect REPL + #[arg(long, global = true)] + pub nostdlib: bool, + + /// Configure when to output messages in color + #[arg(long, global = true, value_enum, default_value_t)] + pub color: clap::ColorChoice, +} + +#[derive(clap::Parser, Debug)] +pub struct Files { + /// Input files, omit to read from stdin + pub files: Option, +} + +/// Available subcommands. +#[derive(clap::Subcommand, Debug)] +pub enum Command { + /// Evaluate a Nickel program and pretty print the result. + Eval(EvalOptions), + /// Converts the parsed representation (AST) back to Nickel source code and prints it. Used for + /// debugging purpose + PprintAst(PprintAstOptions), + /// Exports the result to a different format + Export(ExportOptions), + /// Prints the metadata attached to an attribute, given as a path + Query(QueryOptions), + /// Typechecks the program but do not run it + Typecheck(TypecheckOptions), + /// Starts a REPL session + #[cfg(feature = "repl")] + Repl(ReplOptions), + /// Generates the documentation files for the specified nickel file + #[cfg(feature = "doc")] + Doc(DocOptions), + /// Format Nickel files + #[cfg(feature = "format")] + Format(FormatOptions), + + #[command(hide = true)] + GenCompletions(GenCompletionsOptions), +} diff --git a/cli/src/completions.rs b/cli/src/completions.rs new file mode 100644 index 0000000000..b3b542e36a --- /dev/null +++ b/cli/src/completions.rs @@ -0,0 +1,18 @@ +use crate::cli::{GlobalOptions, Options}; + +#[derive(clap::Parser, Debug)] +pub struct GenCompletionsOptions { + #[arg(value_enum)] + pub shell: clap_complete::Shell, +} + +impl GenCompletionsOptions { + pub fn run(self, _: GlobalOptions) { + clap_complete::generate( + self.shell, + &mut ::command(), + env!("CARGO_BIN_NAME"), + &mut std::io::stdout(), + ) + } +} diff --git a/cli/src/doc.rs b/cli/src/doc.rs index 4be004e0c8..d6f86e9b60 100644 --- a/cli/src/doc.rs +++ b/cli/src/doc.rs @@ -1,6 +1,7 @@ use std::{ fmt, fs, path::{Path, PathBuf}, + process, }; use nickel_lang_core::{ @@ -9,6 +10,11 @@ use nickel_lang_core::{ program::Program, }; +use crate::{ + cli::{Files, GlobalOptions}, + eval, +}; + #[derive(Copy, Clone, Eq, PartialEq, Debug, Default, clap::ValueEnum)] pub enum DocFormat { Json, @@ -34,66 +40,88 @@ impl fmt::Display for DocFormat { } } -pub fn export_doc( - program: &mut Program, - file: Option<&PathBuf>, - output: Option, - stdout: bool, - format: DocFormat, -) -> Result<(), Error> { - let doc = program.extract_doc()?; - let mut out: Box = if stdout { - Box::new(std::io::stdout()) - } else { - Box::new( - output - .as_ref() - .map(|output| { - fs::File::create(output.clone()).map_err(|e| { - Error::IOError(IOError(format!( - "when opening or creating output file `{}`: {}", - output.to_string_lossy(), - e - ))) +#[derive(clap::Parser, Debug)] +pub struct DocOptions { + /// The path of the generated documentation file. Default to + /// `~/.nickel/doc/.md` for input `.ncl`, or to + /// `~/.nickel/doc/out.md` if the input is read from stdin. + #[arg(short, long)] + pub output: Option, + /// Write documentation to stdout. Takes precedence over `output` + #[arg(long)] + pub stdout: bool, + /// The output format for the generated documentation. + #[arg(long, value_enum, default_value_t)] + pub format: crate::doc::DocFormat, + + #[command(flatten)] + pub sources: Files, +} + +impl DocOptions { + pub fn run(self, global: GlobalOptions) { + let mut program = eval::prepare(&self.sources, &global); + if let Err(err) = self.export_doc(&mut program) { + program.report(err); + process::exit(1) + } + } + + fn export_doc(self, program: &mut Program) -> Result<(), Error> { + let doc = program.extract_doc()?; + let mut out: Box = if self.stdout { + Box::new(std::io::stdout()) + } else { + Box::new( + self.output + .as_ref() + .map(|output| { + fs::File::create(output.clone()).map_err(|e| { + Error::IOError(IOError(format!( + "when opening or creating output file `{}`: {}", + output.to_string_lossy(), + e + ))) + }) }) - }) - .unwrap_or_else(|| { - let docpath = Path::new(".nickel/doc/"); - fs::create_dir_all(docpath).map_err(|e| { - Error::IOError(IOError(format!( - "when creating output path `{}`: {}", - docpath.to_string_lossy(), - e - ))) - })?; - let mut output_file = docpath.to_path_buf(); + .unwrap_or_else(|| { + let docpath = Path::new(".nickel/doc/"); + fs::create_dir_all(docpath).map_err(|e| { + Error::IOError(IOError(format!( + "when creating output path `{}`: {}", + docpath.to_string_lossy(), + e + ))) + })?; + let mut output_file = docpath.to_path_buf(); - let mut has_file_name = false; + let mut has_file_name = false; - if let Some(path) = file { - if let Some(file_stem) = path.file_stem() { - output_file.push(file_stem); - has_file_name = true; + if let Some(path) = self.sources.files { + if let Some(file_stem) = path.file_stem() { + output_file.push(file_stem); + has_file_name = true; + } } - } - if !has_file_name { - output_file.push("out"); - } + if !has_file_name { + output_file.push("out"); + } - output_file.set_extension(format.extension()); - fs::File::create(output_file.clone().into_os_string()).map_err(|e| { - Error::IOError(IOError(format!( - "when opening or creating output file `{}`: {}", - output_file.to_string_lossy(), - e - ))) - }) - })?, - ) - }; - match format { - DocFormat::Json => doc.write_json(&mut out), - DocFormat::Markdown => doc.write_markdown(&mut out), + output_file.set_extension(self.format.extension()); + fs::File::create(output_file.clone().into_os_string()).map_err(|e| { + Error::IOError(IOError(format!( + "when opening or creating output file `{}`: {}", + output_file.to_string_lossy(), + e + ))) + }) + })?, + ) + }; + match self.format { + DocFormat::Json => doc.write_json(&mut out), + DocFormat::Markdown => doc.write_markdown(&mut out), + } } } diff --git a/cli/src/eval.rs b/cli/src/eval.rs new file mode 100644 index 0000000000..0c7cc7227e --- /dev/null +++ b/cli/src/eval.rs @@ -0,0 +1,50 @@ +use std::{ffi::OsString, process}; + +use nickel_lang_core::{eval::cache::lazy::CBNCache, program::Program}; + +use crate::cli::{Files, GlobalOptions}; + +#[derive(clap::Parser, Debug)] +pub struct EvalOptions { + #[command(flatten)] + pub sources: Files, + + /// freeform args after -- + #[arg(last = true)] + pub freeform: Vec, +} + +impl EvalOptions { + pub fn run(self, global: GlobalOptions) { + let mut program = prepare(&self.sources, &global); + if let Err(err) = program.eval_full().map(|t| println!("{t}")) { + program.report(err); + process::exit(1) + } + } + + pub fn prepare(&self, global: &GlobalOptions) -> Program { + prepare(&self.sources, global) + } +} + +pub fn prepare(sources: &Files, global: &GlobalOptions) -> Program { + let mut program = sources + .files + .clone() + .map(|f| Program::new_from_file(f, std::io::stderr())) + .unwrap_or_else(|| Program::new_from_stdin(std::io::stderr())) + .unwrap_or_else(|err| { + eprintln!("Error when reading input: {err}"); + process::exit(1) + }); + + #[cfg(debug_assertions)] + if global.nostdlib { + program.set_skip_stdlib(); + } + + program.set_color(global.color.into()); + + program +} diff --git a/cli/src/export.rs b/cli/src/export.rs new file mode 100644 index 0000000000..f3410a1f0f --- /dev/null +++ b/cli/src/export.rs @@ -0,0 +1,64 @@ +use std::io::Write; +use std::process; +use std::{fs, path::PathBuf}; + +use nickel_lang_core::error::Error; +use nickel_lang_core::{ + error::IOError, + eval::cache::lazy::CBNCache, + program::Program, + serialize::{self, ExportFormat}, + term::RichTerm, +}; + +use crate::{cli::GlobalOptions, eval::EvalOptions}; + +#[derive(clap::Parser, Debug)] +pub struct ExportOptions { + #[arg(long, value_enum, default_value_t)] + pub format: ExportFormat, + + /// Output file. Standard output by default + #[arg(short, long)] + pub output: Option, + + #[command(flatten)] + pub evaluation: EvalOptions, +} + +impl ExportOptions { + pub fn run(self, global: GlobalOptions) { + let mut program = self.evaluation.prepare(&global); + if let Err(err) = self.export(&mut program) { + program.report(err); + process::exit(1); + } + } + + fn export(self, program: &mut Program) -> Result<(), Error> { + let rt = program.eval_full_for_export().map(RichTerm::from)?; + + // We only add a trailing newline for JSON exports. Both YAML and TOML + // exporters already append a trailing newline by default. + let trailing_newline = self.format == ExportFormat::Json; + + serialize::validate(self.format, &rt)?; + + if let Some(file) = self.output { + let mut file = fs::File::create(file).map_err(IOError::from)?; + serialize::to_writer(&mut file, self.format, &rt)?; + + if trailing_newline { + writeln!(file).map_err(IOError::from)?; + } + } else { + serialize::to_writer(std::io::stdout(), self.format, &rt)?; + + if trailing_newline { + println!(); + } + } + + Ok(()) + } +} diff --git a/cli/src/format.rs b/cli/src/format.rs index 39dbd0a1a2..1a026a1fdf 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -9,6 +9,8 @@ use std::{ use tempfile::NamedTempFile; use topiary::TopiaryQuery; +use crate::cli::{Files, GlobalOptions}; + #[derive(Debug)] pub enum FormatError { NotAFile { path: PathBuf }, @@ -94,40 +96,41 @@ impl Write for Output { } } -pub fn format(input: Option<&Path>, output: Option<&Path>, in_place: bool) { - if let Err(e) = do_format(input, output, in_place) { - eprintln!("{e}"); - process::exit(1); - } +#[derive(clap::Parser, Debug)] +pub struct FormatOptions { + #[command(flatten)] + sources: Files, } -fn do_format( - input: Option<&Path>, - output: Option<&Path>, - in_place: bool, -) -> Result<(), FormatError> { - let mut output: Output = match (output, input, in_place) { - (None, None, _) | (None, Some(_), false) => Output::new(None)?, - (None, Some(file), true) | (Some(file), _, _) => Output::new(Some(file))?, - }; - let mut input: Box = match input { - None => Box::new(stdin()), - Some(f) => Box::new(BufReader::new(File::open(f)?)), - }; - let topiary_config = topiary::Configuration::parse_default_configuration()?; - let language = topiary::SupportedLanguage::Nickel.to_language(&topiary_config); - let grammar = tree_sitter_nickel::language().into(); - topiary::formatter( - &mut input, - &mut output, - &TopiaryQuery::nickel(), - language, - &grammar, - topiary::Operation::Format { - skip_idempotence: true, - tolerate_parsing_errors: true, - }, - )?; - output.persist(); - Ok(()) +impl FormatOptions { + pub fn run(self, _: GlobalOptions) { + if let Err(e) = self.format() { + eprintln!("{e}"); + process::exit(1); + } + } + + fn format(self) -> Result<(), FormatError> { + let mut output: Output = Output::new(self.sources.files.as_deref())?; + let mut input: Box = match self.sources.files { + None => Box::new(stdin()), + Some(f) => Box::new(BufReader::new(File::open(f)?)), + }; + let topiary_config = topiary::Configuration::parse_default_configuration()?; + let language = topiary::SupportedLanguage::Nickel.to_language(&topiary_config); + let grammar = tree_sitter_nickel::language().into(); + topiary::formatter( + &mut input, + &mut output, + &TopiaryQuery::nickel(), + language, + &grammar, + topiary::Operation::Format { + skip_idempotence: true, + tolerate_parsing_errors: true, + }, + )?; + output.persist(); + Ok(()) + } } diff --git a/cli/src/lib.rs b/cli/src/lib.rs deleted file mode 100644 index 027dc7c31f..0000000000 --- a/cli/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[cfg(feature = "doc")] -pub mod doc; -#[cfg(feature = "format")] -pub mod format; -#[cfg(feature = "repl")] -pub mod repl; diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 0000000000..5a89f1c5d0 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,45 @@ +//! Entry point of the program. + +#[cfg(feature = "doc")] +mod doc; +#[cfg(feature = "format")] +mod format; +#[cfg(feature = "repl")] +mod repl; + +mod cli; +mod completions; +mod eval; +mod export; +mod pprint_ast; +mod query; +mod typecheck; + +use crate::cli::{Command, Options}; + +fn main() { + let opts = ::parse(); + + match opts.command { + Command::Eval(eval) => eval.run(opts.global), + + Command::PprintAst(pprint_ast) => pprint_ast.run(opts.global), + + Command::Export(export) => export.run(opts.global), + + Command::Query(query) => query.run(opts.global), + + Command::Typecheck(typecheck) => typecheck.run(opts.global), + + #[cfg(feature = "repl")] + Command::Repl(repl) => repl.run(opts.global), + + #[cfg(feature = "doc")] + Command::Doc(doc) => doc.run(opts.global), + + #[cfg(feature = "format")] + Command::Format(format) => format.run(opts.global), + + Command::GenCompletions(completions) => completions.run(opts.global), + } +} diff --git a/cli/src/pprint_ast.rs b/cli/src/pprint_ast.rs new file mode 100644 index 0000000000..709925825e --- /dev/null +++ b/cli/src/pprint_ast.rs @@ -0,0 +1,28 @@ +use std::process; + +use crate::{ + cli::{Files, GlobalOptions}, + eval, +}; + +#[derive(clap::Parser, Debug)] +pub struct PprintAstOptions { + /// Performs code transformations before printing + #[arg(long)] + pub transform: bool, + + /// Input file, omit to read from stdin + #[command(flatten)] + pub sources: Files, +} + +impl PprintAstOptions { + pub fn run(self, global: GlobalOptions) { + let mut program = eval::prepare(&self.sources, &global); + + if let Err(err) = program.pprint_ast(&mut std::io::stdout(), self.transform) { + program.report(err); + process::exit(1); + } + } +} diff --git a/cli/src/query.rs b/cli/src/query.rs new file mode 100644 index 0000000000..9c9394296b --- /dev/null +++ b/cli/src/query.rs @@ -0,0 +1,56 @@ +use std::process; + +use nickel_lang_core::repl::query_print; + +use crate::{cli::GlobalOptions, eval::EvalOptions}; + +#[derive(clap::Parser, Debug)] +pub struct QueryOptions { + pub path: Option, + + #[arg(long)] + pub doc: bool, + + #[arg(long)] + pub contract: bool, + + #[arg(long = "type")] + pub typ: bool, + + #[arg(long)] + pub default: bool, + + #[arg(long)] + pub value: bool, + + #[command(flatten)] + pub evaluation: EvalOptions, +} + +impl QueryOptions { + pub fn run(self, global: GlobalOptions) { + let mut program = self.evaluation.prepare(&global); + + let result = program.query(self.path).map(|term| { + // Print a default selection of attributes if no option is specified + let attrs = if !self.doc && !self.contract && !self.typ && !self.default && !self.value + { + query_print::Attributes::default() + } else { + query_print::Attributes { + doc: self.doc, + contract: self.contract, + typ: self.typ, + default: self.default, + value: self.value, + } + }; + + query_print::write_query_result(&mut std::io::stdout(), &term, attrs).unwrap() + }); + if let Err(err) = result { + program.report(err); + process::exit(1); + } + } +} diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 4c176609f3..e2aec50185 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -1,18 +1,28 @@ use std::{path::PathBuf, process}; use directories::BaseDirs; -use nickel_lang_core::{program::ColorOpt, repl::rustyline_frontend}; +use nickel_lang_core::repl::rustyline_frontend; -pub fn repl(history_file: Option, color: ColorOpt) { - let histfile = if let Some(h) = history_file { - h - } else { - BaseDirs::new() - .expect("Cannot retrieve home directory path") - .home_dir() - .join(".nickel_history") - }; - if rustyline_frontend::repl(histfile, color).is_err() { - process::exit(1); +use crate::cli::GlobalOptions; + +#[derive(clap::Parser, Debug)] +pub struct ReplOptions { + #[arg(long)] + pub history_file: Option, +} + +impl ReplOptions { + pub fn run(self, global: GlobalOptions) { + let histfile = if let Some(h) = self.history_file { + h + } else { + BaseDirs::new() + .expect("Cannot retrieve home directory path") + .home_dir() + .join(".nickel_history") + }; + if rustyline_frontend::repl(histfile, global.color.into()).is_err() { + process::exit(1); + } } } diff --git a/cli/src/typecheck.rs b/cli/src/typecheck.rs new file mode 100644 index 0000000000..089add7996 --- /dev/null +++ b/cli/src/typecheck.rs @@ -0,0 +1,22 @@ +use std::process; + +use crate::{ + cli::{Files, GlobalOptions}, + eval, +}; + +#[derive(clap::Parser, Debug)] +pub struct TypecheckOptions { + #[command(flatten)] + sources: Files, +} + +impl TypecheckOptions { + pub fn run(self, global: GlobalOptions) { + let mut program = eval::prepare(&self.sources, &global); + if let Err(err) = program.typecheck() { + program.report(err); + process::exit(1); + } + } +} diff --git a/core/src/program.rs b/core/src/program.rs index 1f03c2d173..66608a8606 100644 --- a/core/src/program.rs +++ b/core/src/program.rs @@ -353,7 +353,7 @@ impl Program { pub fn pprint_ast( &mut self, - out: &mut std::io::BufWriter>, + out: &mut impl std::io::Write, apply_transforms: bool, ) -> Result<(), Error> { use crate::pretty::*; From 6643ffecf2f753f029ff95c78082f1c0157ae0f7 Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Mon, 7 Aug 2023 12:13:39 +0000 Subject: [PATCH 02/11] Add an `Error` enum for the CLI --- cli/src/completions.rs | 11 ++++--- cli/src/doc.rs | 11 +++---- cli/src/error.rs | 72 ++++++++++++++++++++++++++++++++++++++++++ cli/src/eval.rs | 31 +++++++++--------- cli/src/export.rs | 12 +++---- cli/src/format.rs | 31 ++++++------------ cli/src/main.rs | 7 ++-- cli/src/pprint_ast.rs | 15 ++++----- cli/src/query.rs | 55 ++++++++++++++++---------------- cli/src/repl.rs | 10 +++--- cli/src/typecheck.rs | 12 +++---- 11 files changed, 160 insertions(+), 107 deletions(-) create mode 100644 cli/src/error.rs diff --git a/cli/src/completions.rs b/cli/src/completions.rs index b3b542e36a..9c0e7db521 100644 --- a/cli/src/completions.rs +++ b/cli/src/completions.rs @@ -1,4 +1,7 @@ -use crate::cli::{GlobalOptions, Options}; +use crate::{ + cli::{GlobalOptions, Options}, + error::CliResult, +}; #[derive(clap::Parser, Debug)] pub struct GenCompletionsOptions { @@ -7,12 +10,12 @@ pub struct GenCompletionsOptions { } impl GenCompletionsOptions { - pub fn run(self, _: GlobalOptions) { - clap_complete::generate( + pub fn run(self, _: GlobalOptions) -> CliResult<()> { + Ok(clap_complete::generate( self.shell, &mut ::command(), env!("CARGO_BIN_NAME"), &mut std::io::stdout(), - ) + )) } } diff --git a/cli/src/doc.rs b/cli/src/doc.rs index d6f86e9b60..4348639faf 100644 --- a/cli/src/doc.rs +++ b/cli/src/doc.rs @@ -1,7 +1,6 @@ use std::{ fmt, fs, path::{Path, PathBuf}, - process, }; use nickel_lang_core::{ @@ -12,6 +11,7 @@ use nickel_lang_core::{ use crate::{ cli::{Files, GlobalOptions}, + error::{CliResult, WithProgram}, eval, }; @@ -59,12 +59,9 @@ pub struct DocOptions { } impl DocOptions { - pub fn run(self, global: GlobalOptions) { - let mut program = eval::prepare(&self.sources, &global); - if let Err(err) = self.export_doc(&mut program) { - program.report(err); - process::exit(1) - } + pub fn run(self, global: GlobalOptions) -> CliResult<()> { + let mut program = eval::prepare(&self.sources, &global)?; + Ok(self.export_doc(&mut program).with_program(program)?) } fn export_doc(self, program: &mut Program) -> Result<(), Error> { diff --git a/cli/src/error.rs b/cli/src/error.rs new file mode 100644 index 0000000000..f50c48512c --- /dev/null +++ b/cli/src/error.rs @@ -0,0 +1,72 @@ +use nickel_lang_core::{eval::cache::lazy::CBNCache, program::Program}; + +pub enum Error { + Program { + program: Program, + error: nickel_lang_core::error::Error, + }, + Io { + error: std::io::Error, + }, + #[cfg(feature = "repl")] + Repl { + error: nickel_lang_core::repl::InitError, + }, + #[cfg(feature = "format")] + Format { + error: crate::format::FormatError, + }, +} + +pub type CliResult = Result; + +impl From for Error { + fn from(error: std::io::Error) -> Self { + Error::Io { error } + } +} + +#[cfg(feature = "format")] +impl From for Error { + fn from(error: crate::format::FormatError) -> Self { + Error::Format { error } + } +} + +#[cfg(feature = "repl")] +impl From for Error { + fn from(error: nickel_lang_core::repl::InitError) -> Self { + Error::Repl { error } + } +} + +pub trait WithProgram { + fn with_program(self, program: Program) -> CliResult; +} + +impl WithProgram for Result { + fn with_program(self, program: Program) -> CliResult { + self.map_err(|error| Error::Program { program, error }) + } +} + +impl Error { + pub fn report(self) { + match self { + Error::Program { mut program, error } => program.report(error), + Error::Io { error } => { + eprintln!("{error}") + } + Error::Repl { error } => { + use nickel_lang_core::repl::InitError; + match error { + InitError::Stdlib => eprintln!("Failed to load the Nickel standard library"), + InitError::ReadlineError(msg) => { + eprintln!("Readline intialization failed: {msg}") + } + } + } + Error::Format { error } => eprintln!("{error}"), + } + } +} diff --git a/cli/src/eval.rs b/cli/src/eval.rs index 0c7cc7227e..c8146da308 100644 --- a/cli/src/eval.rs +++ b/cli/src/eval.rs @@ -1,8 +1,11 @@ -use std::{ffi::OsString, process}; +use std::ffi::OsString; use nickel_lang_core::{eval::cache::lazy::CBNCache, program::Program}; -use crate::cli::{Files, GlobalOptions}; +use crate::{ + cli::{Files, GlobalOptions}, + error::{CliResult, WithProgram}, +}; #[derive(clap::Parser, Debug)] pub struct EvalOptions { @@ -15,29 +18,25 @@ pub struct EvalOptions { } impl EvalOptions { - pub fn run(self, global: GlobalOptions) { - let mut program = prepare(&self.sources, &global); - if let Err(err) = program.eval_full().map(|t| println!("{t}")) { - program.report(err); - process::exit(1) - } + pub fn run(self, global: GlobalOptions) -> CliResult<()> { + let mut program = prepare(&self.sources, &global)?; + program + .eval_full() + .map(|t| println!("{t}")) + .with_program(program) } - pub fn prepare(&self, global: &GlobalOptions) -> Program { + pub fn prepare(&self, global: &GlobalOptions) -> CliResult> { prepare(&self.sources, global) } } -pub fn prepare(sources: &Files, global: &GlobalOptions) -> Program { +pub fn prepare(sources: &Files, global: &GlobalOptions) -> CliResult> { let mut program = sources .files .clone() .map(|f| Program::new_from_file(f, std::io::stderr())) - .unwrap_or_else(|| Program::new_from_stdin(std::io::stderr())) - .unwrap_or_else(|err| { - eprintln!("Error when reading input: {err}"); - process::exit(1) - }); + .unwrap_or_else(|| Program::new_from_stdin(std::io::stderr()))?; #[cfg(debug_assertions)] if global.nostdlib { @@ -46,5 +45,5 @@ pub fn prepare(sources: &Files, global: &GlobalOptions) -> Program { program.set_color(global.color.into()); - program + Ok(program) } diff --git a/cli/src/export.rs b/cli/src/export.rs index f3410a1f0f..80f9059782 100644 --- a/cli/src/export.rs +++ b/cli/src/export.rs @@ -1,5 +1,4 @@ use std::io::Write; -use std::process; use std::{fs, path::PathBuf}; use nickel_lang_core::error::Error; @@ -11,6 +10,7 @@ use nickel_lang_core::{ term::RichTerm, }; +use crate::error::{CliResult, WithProgram}; use crate::{cli::GlobalOptions, eval::EvalOptions}; #[derive(clap::Parser, Debug)] @@ -27,12 +27,10 @@ pub struct ExportOptions { } impl ExportOptions { - pub fn run(self, global: GlobalOptions) { - let mut program = self.evaluation.prepare(&global); - if let Err(err) = self.export(&mut program) { - program.report(err); - process::exit(1); - } + pub fn run(self, global: GlobalOptions) -> CliResult<()> { + let mut program = self.evaluation.prepare(&global)?; + + Ok(self.export(&mut program).with_program(program)?) } fn export(self, program: &mut Program) -> Result<(), Error> { diff --git a/cli/src/format.rs b/cli/src/format.rs index 1a026a1fdf..8bf83d94af 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -3,25 +3,20 @@ use std::{ fs::File, io::{self, stdin, stdout, BufReader, Read, Write}, path::{Path, PathBuf}, - process, }; use tempfile::NamedTempFile; use topiary::TopiaryQuery; -use crate::cli::{Files, GlobalOptions}; +use crate::{ + cli::{Files, GlobalOptions}, + error::CliResult, +}; #[derive(Debug)] pub enum FormatError { NotAFile { path: PathBuf }, TopiaryError(topiary::FormatterError), - IOError(io::Error), -} - -impl From for FormatError { - fn from(e: io::Error) -> Self { - Self::IOError(e) - } } impl From for FormatError { @@ -41,7 +36,6 @@ impl Display for FormatError { ) } FormatError::TopiaryError(e) => write!(f, "{e}"), - FormatError::IOError(e) => write!(f, "{e}"), } } } @@ -56,7 +50,7 @@ pub enum Output { } impl Output { - pub fn new(path: Option<&Path>) -> Result { + pub fn new(path: Option<&Path>) -> CliResult { match path { None => Ok(Self::Stdout), Some(path) => { @@ -103,20 +97,14 @@ pub struct FormatOptions { } impl FormatOptions { - pub fn run(self, _: GlobalOptions) { - if let Err(e) = self.format() { - eprintln!("{e}"); - process::exit(1); - } - } - - fn format(self) -> Result<(), FormatError> { + pub fn run(self, _: GlobalOptions) -> CliResult<()> { let mut output: Output = Output::new(self.sources.files.as_deref())?; let mut input: Box = match self.sources.files { None => Box::new(stdin()), Some(f) => Box::new(BufReader::new(File::open(f)?)), }; - let topiary_config = topiary::Configuration::parse_default_configuration()?; + let topiary_config = + topiary::Configuration::parse_default_configuration().map_err(FormatError::from)?; let language = topiary::SupportedLanguage::Nickel.to_language(&topiary_config); let grammar = tree_sitter_nickel::language().into(); topiary::formatter( @@ -129,7 +117,8 @@ impl FormatOptions { skip_idempotence: true, tolerate_parsing_errors: true, }, - )?; + ) + .map_err(FormatError::from)?; output.persist(); Ok(()) } diff --git a/cli/src/main.rs b/cli/src/main.rs index 5a89f1c5d0..924456576b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -9,6 +9,7 @@ mod repl; mod cli; mod completions; +mod error; mod eval; mod export; mod pprint_ast; @@ -20,7 +21,7 @@ use crate::cli::{Command, Options}; fn main() { let opts = ::parse(); - match opts.command { + let result = match opts.command { Command::Eval(eval) => eval.run(opts.global), Command::PprintAst(pprint_ast) => pprint_ast.run(opts.global), @@ -41,5 +42,7 @@ fn main() { Command::Format(format) => format.run(opts.global), Command::GenCompletions(completions) => completions.run(opts.global), - } + }; + + result.unwrap_or_else(|e| e.report()) } diff --git a/cli/src/pprint_ast.rs b/cli/src/pprint_ast.rs index 709925825e..422c3ecaa0 100644 --- a/cli/src/pprint_ast.rs +++ b/cli/src/pprint_ast.rs @@ -1,7 +1,6 @@ -use std::process; - use crate::{ cli::{Files, GlobalOptions}, + error::{CliResult, WithProgram}, eval, }; @@ -17,12 +16,10 @@ pub struct PprintAstOptions { } impl PprintAstOptions { - pub fn run(self, global: GlobalOptions) { - let mut program = eval::prepare(&self.sources, &global); - - if let Err(err) = program.pprint_ast(&mut std::io::stdout(), self.transform) { - program.report(err); - process::exit(1); - } + pub fn run(self, global: GlobalOptions) -> CliResult<()> { + let mut program = eval::prepare(&self.sources, &global)?; + Ok(program + .pprint_ast(&mut std::io::stdout(), self.transform) + .with_program(program)?) } } diff --git a/cli/src/query.rs b/cli/src/query.rs index 9c9394296b..963c14ae65 100644 --- a/cli/src/query.rs +++ b/cli/src/query.rs @@ -1,8 +1,10 @@ -use std::process; - use nickel_lang_core::repl::query_print; -use crate::{cli::GlobalOptions, eval::EvalOptions}; +use crate::{ + cli::GlobalOptions, + error::{CliResult, WithProgram}, + eval::EvalOptions, +}; #[derive(clap::Parser, Debug)] pub struct QueryOptions { @@ -28,29 +30,28 @@ pub struct QueryOptions { } impl QueryOptions { - pub fn run(self, global: GlobalOptions) { - let mut program = self.evaluation.prepare(&global); - - let result = program.query(self.path).map(|term| { - // Print a default selection of attributes if no option is specified - let attrs = if !self.doc && !self.contract && !self.typ && !self.default && !self.value - { - query_print::Attributes::default() - } else { - query_print::Attributes { - doc: self.doc, - contract: self.contract, - typ: self.typ, - default: self.default, - value: self.value, - } - }; - - query_print::write_query_result(&mut std::io::stdout(), &term, attrs).unwrap() - }); - if let Err(err) = result { - program.report(err); - process::exit(1); - } + pub fn run(self, global: GlobalOptions) -> CliResult<()> { + let mut program = self.evaluation.prepare(&global)?; + + Ok(program + .query(self.path) + .map(|term| { + // Print a default selection of attributes if no option is specified + let attrs = + if !self.doc && !self.contract && !self.typ && !self.default && !self.value { + query_print::Attributes::default() + } else { + query_print::Attributes { + doc: self.doc, + contract: self.contract, + typ: self.typ, + default: self.default, + value: self.value, + } + }; + + query_print::write_query_result(&mut std::io::stdout(), &term, attrs).unwrap() + }) + .with_program(program)?) } } diff --git a/cli/src/repl.rs b/cli/src/repl.rs index e2aec50185..553ceea53e 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -1,9 +1,9 @@ -use std::{path::PathBuf, process}; +use std::path::PathBuf; use directories::BaseDirs; use nickel_lang_core::repl::rustyline_frontend; -use crate::cli::GlobalOptions; +use crate::{cli::GlobalOptions, error::CliResult}; #[derive(clap::Parser, Debug)] pub struct ReplOptions { @@ -12,7 +12,7 @@ pub struct ReplOptions { } impl ReplOptions { - pub fn run(self, global: GlobalOptions) { + pub fn run(self, global: GlobalOptions) -> CliResult<()> { let histfile = if let Some(h) = self.history_file { h } else { @@ -21,8 +21,6 @@ impl ReplOptions { .home_dir() .join(".nickel_history") }; - if rustyline_frontend::repl(histfile, global.color.into()).is_err() { - process::exit(1); - } + Ok(rustyline_frontend::repl(histfile, global.color.into())?) } } diff --git a/cli/src/typecheck.rs b/cli/src/typecheck.rs index 089add7996..a20e43045f 100644 --- a/cli/src/typecheck.rs +++ b/cli/src/typecheck.rs @@ -1,7 +1,6 @@ -use std::process; - use crate::{ cli::{Files, GlobalOptions}, + error::{CliResult, WithProgram}, eval, }; @@ -12,11 +11,8 @@ pub struct TypecheckOptions { } impl TypecheckOptions { - pub fn run(self, global: GlobalOptions) { - let mut program = eval::prepare(&self.sources, &global); - if let Err(err) = program.typecheck() { - program.report(err); - process::exit(1); - } + pub fn run(self, global: GlobalOptions) -> CliResult<()> { + let mut program = eval::prepare(&self.sources, &global)?; + Ok(program.typecheck().with_program(program)?) } } From eea3b8a1b8adcc973d58626d66b4331b3492abd8 Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Mon, 7 Aug 2023 12:45:26 +0000 Subject: [PATCH 03/11] Restore the previous user-facing interface --- cli/src/cli.rs | 16 +++++++++++----- cli/src/completions.rs | 5 +++-- cli/src/doc.rs | 4 ++-- cli/src/eval.rs | 17 ++++------------- cli/src/export.rs | 2 +- cli/src/format.rs | 4 ++-- cli/src/main.rs | 4 +++- cli/src/pprint_ast.rs | 4 ++-- cli/src/query.rs | 4 ++-- cli/src/typecheck.rs | 2 +- 10 files changed, 31 insertions(+), 31 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index c7e04d2a42..9368d6d51f 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -22,7 +22,7 @@ pub struct Options { pub global: GlobalOptions, #[command(subcommand)] - pub command: Command, + pub command: Option, } #[derive(clap::Parser, Debug)] @@ -35,19 +35,25 @@ pub struct GlobalOptions { /// Configure when to output messages in color #[arg(long, global = true, value_enum, default_value_t)] pub color: clap::ColorChoice, + + #[command(flatten)] + pub files: Files, } #[derive(clap::Parser, Debug)] pub struct Files { - /// Input files, omit to read from stdin - pub files: Option, + /// Input file, omit to read from stdin + #[arg(long, short, global = true)] + pub file: Option, } /// Available subcommands. #[derive(clap::Subcommand, Debug)] pub enum Command { - /// Evaluate a Nickel program and pretty print the result. + /// Evaluate a Nickel program and pretty-print the result. + #[command(hide = true)] Eval(EvalOptions), + /// Converts the parsed representation (AST) back to Nickel source code and prints it. Used for /// debugging purpose PprintAst(PprintAstOptions), @@ -67,6 +73,6 @@ pub enum Command { #[cfg(feature = "format")] Format(FormatOptions), - #[command(hide = true)] + /// Generate shell completion files GenCompletions(GenCompletionsOptions), } diff --git a/cli/src/completions.rs b/cli/src/completions.rs index 9c0e7db521..51c7a803be 100644 --- a/cli/src/completions.rs +++ b/cli/src/completions.rs @@ -11,11 +11,12 @@ pub struct GenCompletionsOptions { impl GenCompletionsOptions { pub fn run(self, _: GlobalOptions) -> CliResult<()> { - Ok(clap_complete::generate( + clap_complete::generate( self.shell, &mut ::command(), env!("CARGO_BIN_NAME"), &mut std::io::stdout(), - )) + ); + Ok(()) } } diff --git a/cli/src/doc.rs b/cli/src/doc.rs index 4348639faf..8e2ef42288 100644 --- a/cli/src/doc.rs +++ b/cli/src/doc.rs @@ -61,7 +61,7 @@ pub struct DocOptions { impl DocOptions { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut program = eval::prepare(&self.sources, &global)?; - Ok(self.export_doc(&mut program).with_program(program)?) + self.export_doc(&mut program).with_program(program) } fn export_doc(self, program: &mut Program) -> Result<(), Error> { @@ -94,7 +94,7 @@ impl DocOptions { let mut has_file_name = false; - if let Some(path) = self.sources.files { + if let Some(path) = self.sources.file { if let Some(file_stem) = path.file_stem() { output_file.push(file_stem); has_file_name = true; diff --git a/cli/src/eval.rs b/cli/src/eval.rs index c8146da308..a6a493ed6b 100644 --- a/cli/src/eval.rs +++ b/cli/src/eval.rs @@ -1,5 +1,3 @@ -use std::ffi::OsString; - use nickel_lang_core::{eval::cache::lazy::CBNCache, program::Program}; use crate::{ @@ -8,18 +6,11 @@ use crate::{ }; #[derive(clap::Parser, Debug)] -pub struct EvalOptions { - #[command(flatten)] - pub sources: Files, - - /// freeform args after -- - #[arg(last = true)] - pub freeform: Vec, -} +pub struct EvalOptions {} impl EvalOptions { pub fn run(self, global: GlobalOptions) -> CliResult<()> { - let mut program = prepare(&self.sources, &global)?; + let mut program = prepare(&global.files, &global)?; program .eval_full() .map(|t| println!("{t}")) @@ -27,13 +18,13 @@ impl EvalOptions { } pub fn prepare(&self, global: &GlobalOptions) -> CliResult> { - prepare(&self.sources, global) + prepare(&global.files, global) } } pub fn prepare(sources: &Files, global: &GlobalOptions) -> CliResult> { let mut program = sources - .files + .file .clone() .map(|f| Program::new_from_file(f, std::io::stderr())) .unwrap_or_else(|| Program::new_from_stdin(std::io::stderr()))?; diff --git a/cli/src/export.rs b/cli/src/export.rs index 80f9059782..ffb03b3894 100644 --- a/cli/src/export.rs +++ b/cli/src/export.rs @@ -30,7 +30,7 @@ impl ExportOptions { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut program = self.evaluation.prepare(&global)?; - Ok(self.export(&mut program).with_program(program)?) + self.export(&mut program).with_program(program) } fn export(self, program: &mut Program) -> Result<(), Error> { diff --git a/cli/src/format.rs b/cli/src/format.rs index 8bf83d94af..b1c3654687 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -98,8 +98,8 @@ pub struct FormatOptions { impl FormatOptions { pub fn run(self, _: GlobalOptions) -> CliResult<()> { - let mut output: Output = Output::new(self.sources.files.as_deref())?; - let mut input: Box = match self.sources.files { + let mut output: Output = Output::new(self.sources.file.as_deref())?; + let mut input: Box = match self.sources.file { None => Box::new(stdin()), Some(f) => Box::new(BufReader::new(File::open(f)?)), }; diff --git a/cli/src/main.rs b/cli/src/main.rs index 924456576b..ca708d0004 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -16,12 +16,14 @@ mod pprint_ast; mod query; mod typecheck; +use eval::EvalOptions; + use crate::cli::{Command, Options}; fn main() { let opts = ::parse(); - let result = match opts.command { + let result = match opts.command.unwrap_or(Command::Eval(EvalOptions {})) { Command::Eval(eval) => eval.run(opts.global), Command::PprintAst(pprint_ast) => pprint_ast.run(opts.global), diff --git a/cli/src/pprint_ast.rs b/cli/src/pprint_ast.rs index 422c3ecaa0..58e07c618d 100644 --- a/cli/src/pprint_ast.rs +++ b/cli/src/pprint_ast.rs @@ -18,8 +18,8 @@ pub struct PprintAstOptions { impl PprintAstOptions { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut program = eval::prepare(&self.sources, &global)?; - Ok(program + program .pprint_ast(&mut std::io::stdout(), self.transform) - .with_program(program)?) + .with_program(program) } } diff --git a/cli/src/query.rs b/cli/src/query.rs index 963c14ae65..026ba10456 100644 --- a/cli/src/query.rs +++ b/cli/src/query.rs @@ -33,7 +33,7 @@ impl QueryOptions { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut program = self.evaluation.prepare(&global)?; - Ok(program + program .query(self.path) .map(|term| { // Print a default selection of attributes if no option is specified @@ -52,6 +52,6 @@ impl QueryOptions { query_print::write_query_result(&mut std::io::stdout(), &term, attrs).unwrap() }) - .with_program(program)?) + .with_program(program) } } diff --git a/cli/src/typecheck.rs b/cli/src/typecheck.rs index a20e43045f..a62d3a6e60 100644 --- a/cli/src/typecheck.rs +++ b/cli/src/typecheck.rs @@ -13,6 +13,6 @@ pub struct TypecheckOptions { impl TypecheckOptions { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut program = eval::prepare(&self.sources, &global)?; - Ok(program.typecheck().with_program(program)?) + program.typecheck().with_program(program) } } From bc5033b255f1fbee4bc85d4f38af5aae2de0be4b Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Mon, 7 Aug 2023 13:16:17 +0000 Subject: [PATCH 04/11] Make clippy happy --- cli/src/format.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/format.rs b/cli/src/format.rs index b1c3654687..db98f8218e 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -1,7 +1,7 @@ use std::{ fmt::Display, fs::File, - io::{self, stdin, stdout, BufReader, Read, Write}, + io::{stdin, stdout, BufReader, Read, Write}, path::{Path, PathBuf}, }; From e4360afd5f8043155ee4fb42590235bb646fa795 Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Mon, 7 Aug 2023 14:13:20 +0000 Subject: [PATCH 05/11] Restore `nickel format` CLI behaviour --- cli/src/doc.rs | 17 +++++++++-------- cli/src/format.rs | 17 ++++++++++++++--- cli/src/pprint_ast.rs | 8 ++------ cli/src/typecheck.rs | 9 +++------ 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/cli/src/doc.rs b/cli/src/doc.rs index 8e2ef42288..5062f7893c 100644 --- a/cli/src/doc.rs +++ b/cli/src/doc.rs @@ -10,7 +10,7 @@ use nickel_lang_core::{ }; use crate::{ - cli::{Files, GlobalOptions}, + cli::GlobalOptions, error::{CliResult, WithProgram}, eval, }; @@ -53,18 +53,19 @@ pub struct DocOptions { /// The output format for the generated documentation. #[arg(long, value_enum, default_value_t)] pub format: crate::doc::DocFormat, - - #[command(flatten)] - pub sources: Files, } impl DocOptions { pub fn run(self, global: GlobalOptions) -> CliResult<()> { - let mut program = eval::prepare(&self.sources, &global)?; - self.export_doc(&mut program).with_program(program) + let mut program = eval::prepare(&global.files, &global)?; + self.export_doc(&mut program, &global).with_program(program) } - fn export_doc(self, program: &mut Program) -> Result<(), Error> { + fn export_doc( + self, + program: &mut Program, + global: &GlobalOptions, + ) -> Result<(), Error> { let doc = program.extract_doc()?; let mut out: Box = if self.stdout { Box::new(std::io::stdout()) @@ -94,7 +95,7 @@ impl DocOptions { let mut has_file_name = false; - if let Some(path) = self.sources.file { + if let Some(path) = &global.files.file { if let Some(file_stem) = path.file_stem() { output_file.push(file_stem); has_file_name = true; diff --git a/cli/src/format.rs b/cli/src/format.rs index db98f8218e..de788ad987 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -94,12 +94,23 @@ impl Write for Output { pub struct FormatOptions { #[command(flatten)] sources: Files, + + /// Output file. Standard output by default. + #[arg(short, long)] + output: Option, + + /// Format in place, overwriting the input file. + #[arg(short, long, requires = "file")] + in_place: bool, } impl FormatOptions { - pub fn run(self, _: GlobalOptions) -> CliResult<()> { - let mut output: Output = Output::new(self.sources.file.as_deref())?; - let mut input: Box = match self.sources.file { + pub fn run(self, global: GlobalOptions) -> CliResult<()> { + let mut output: Output = match (&self.output, &global.files.file, self.in_place) { + (None, None, _) | (None, Some(_), false) => Output::new(None)?, + (None, Some(file), true) | (Some(file), _, _) => Output::new(Some(file))?, + }; + let mut input: Box = match global.files.file { None => Box::new(stdin()), Some(f) => Box::new(BufReader::new(File::open(f)?)), }; diff --git a/cli/src/pprint_ast.rs b/cli/src/pprint_ast.rs index 58e07c618d..e8a7af6d37 100644 --- a/cli/src/pprint_ast.rs +++ b/cli/src/pprint_ast.rs @@ -1,5 +1,5 @@ use crate::{ - cli::{Files, GlobalOptions}, + cli::GlobalOptions, error::{CliResult, WithProgram}, eval, }; @@ -9,15 +9,11 @@ pub struct PprintAstOptions { /// Performs code transformations before printing #[arg(long)] pub transform: bool, - - /// Input file, omit to read from stdin - #[command(flatten)] - pub sources: Files, } impl PprintAstOptions { pub fn run(self, global: GlobalOptions) -> CliResult<()> { - let mut program = eval::prepare(&self.sources, &global)?; + let mut program = eval::prepare(&global.files, &global)?; program .pprint_ast(&mut std::io::stdout(), self.transform) .with_program(program) diff --git a/cli/src/typecheck.rs b/cli/src/typecheck.rs index a62d3a6e60..f4e5760d30 100644 --- a/cli/src/typecheck.rs +++ b/cli/src/typecheck.rs @@ -1,18 +1,15 @@ use crate::{ - cli::{Files, GlobalOptions}, + cli::GlobalOptions, error::{CliResult, WithProgram}, eval, }; #[derive(clap::Parser, Debug)] -pub struct TypecheckOptions { - #[command(flatten)] - sources: Files, -} +pub struct TypecheckOptions {} impl TypecheckOptions { pub fn run(self, global: GlobalOptions) -> CliResult<()> { - let mut program = eval::prepare(&self.sources, &global)?; + let mut program = eval::prepare(&global.files, &global)?; program.typecheck().with_program(program) } } From 6e44778757116282ea92c070f0b9b0027310f3e1 Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Mon, 7 Aug 2023 16:09:46 +0000 Subject: [PATCH 06/11] Change command options naming --- cli/src/cli.rs | 24 ++++++++++++------------ cli/src/completions.rs | 4 ++-- cli/src/doc.rs | 6 +++--- cli/src/eval.rs | 15 ++++++++------- cli/src/export.rs | 8 ++++---- cli/src/format.rs | 4 ++-- cli/src/main.rs | 11 +++-------- cli/src/pprint_ast.rs | 6 +++--- cli/src/query.rs | 8 ++++---- cli/src/repl.rs | 4 ++-- cli/src/typecheck.rs | 6 +++--- 11 files changed, 46 insertions(+), 50 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 9368d6d51f..d4601bf935 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -4,9 +4,9 @@ use std::path::PathBuf; use git_version::git_version; use crate::{ - completions::GenCompletionsOptions, doc::DocOptions, eval::EvalOptions, export::ExportOptions, - format::FormatOptions, pprint_ast::PprintAstOptions, query::QueryOptions, repl::ReplOptions, - typecheck::TypecheckOptions, + completions::GenCompletionsCommand, doc::DocCommand, eval::EvalCommand, export::ExportCommand, + format::FormatCommand, pprint_ast::PprintAstCommand, query::QueryCommand, repl::ReplCommand, + typecheck::TypecheckCommand, }; #[derive(clap::Parser, Debug)] @@ -52,27 +52,27 @@ pub struct Files { pub enum Command { /// Evaluate a Nickel program and pretty-print the result. #[command(hide = true)] - Eval(EvalOptions), + Eval(EvalCommand), /// Converts the parsed representation (AST) back to Nickel source code and prints it. Used for /// debugging purpose - PprintAst(PprintAstOptions), + PprintAst(PprintAstCommand), /// Exports the result to a different format - Export(ExportOptions), + Export(ExportCommand), /// Prints the metadata attached to an attribute, given as a path - Query(QueryOptions), + Query(QueryCommand), /// Typechecks the program but do not run it - Typecheck(TypecheckOptions), + Typecheck(TypecheckCommand), /// Starts a REPL session #[cfg(feature = "repl")] - Repl(ReplOptions), + Repl(ReplCommand), /// Generates the documentation files for the specified nickel file #[cfg(feature = "doc")] - Doc(DocOptions), + Doc(DocCommand), /// Format Nickel files #[cfg(feature = "format")] - Format(FormatOptions), + Format(FormatCommand), /// Generate shell completion files - GenCompletions(GenCompletionsOptions), + GenCompletions(GenCompletionsCommand), } diff --git a/cli/src/completions.rs b/cli/src/completions.rs index 51c7a803be..f741bae85c 100644 --- a/cli/src/completions.rs +++ b/cli/src/completions.rs @@ -4,12 +4,12 @@ use crate::{ }; #[derive(clap::Parser, Debug)] -pub struct GenCompletionsOptions { +pub struct GenCompletionsCommand { #[arg(value_enum)] pub shell: clap_complete::Shell, } -impl GenCompletionsOptions { +impl GenCompletionsCommand { pub fn run(self, _: GlobalOptions) -> CliResult<()> { clap_complete::generate( self.shell, diff --git a/cli/src/doc.rs b/cli/src/doc.rs index 5062f7893c..b2b3acbc81 100644 --- a/cli/src/doc.rs +++ b/cli/src/doc.rs @@ -41,7 +41,7 @@ impl fmt::Display for DocFormat { } #[derive(clap::Parser, Debug)] -pub struct DocOptions { +pub struct DocCommand { /// The path of the generated documentation file. Default to /// `~/.nickel/doc/.md` for input `.ncl`, or to /// `~/.nickel/doc/out.md` if the input is read from stdin. @@ -55,9 +55,9 @@ pub struct DocOptions { pub format: crate::doc::DocFormat, } -impl DocOptions { +impl DocCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { - let mut program = eval::prepare(&global.files, &global)?; + let mut program = eval::prepare(&global)?; self.export_doc(&mut program, &global).with_program(program) } diff --git a/cli/src/eval.rs b/cli/src/eval.rs index a6a493ed6b..6bccc87906 100644 --- a/cli/src/eval.rs +++ b/cli/src/eval.rs @@ -1,16 +1,16 @@ use nickel_lang_core::{eval::cache::lazy::CBNCache, program::Program}; use crate::{ - cli::{Files, GlobalOptions}, + cli::GlobalOptions, error::{CliResult, WithProgram}, }; #[derive(clap::Parser, Debug)] -pub struct EvalOptions {} +pub struct EvalCommand {} -impl EvalOptions { +impl EvalCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { - let mut program = prepare(&global.files, &global)?; + let mut program = prepare(&global)?; program .eval_full() .map(|t| println!("{t}")) @@ -18,12 +18,13 @@ impl EvalOptions { } pub fn prepare(&self, global: &GlobalOptions) -> CliResult> { - prepare(&global.files, global) + prepare(global) } } -pub fn prepare(sources: &Files, global: &GlobalOptions) -> CliResult> { - let mut program = sources +pub fn prepare(global: &GlobalOptions) -> CliResult> { + let mut program = global + .files .file .clone() .map(|f| Program::new_from_file(f, std::io::stderr())) diff --git a/cli/src/export.rs b/cli/src/export.rs index ffb03b3894..135107cfe7 100644 --- a/cli/src/export.rs +++ b/cli/src/export.rs @@ -11,10 +11,10 @@ use nickel_lang_core::{ }; use crate::error::{CliResult, WithProgram}; -use crate::{cli::GlobalOptions, eval::EvalOptions}; +use crate::{cli::GlobalOptions, eval::EvalCommand}; #[derive(clap::Parser, Debug)] -pub struct ExportOptions { +pub struct ExportCommand { #[arg(long, value_enum, default_value_t)] pub format: ExportFormat, @@ -23,10 +23,10 @@ pub struct ExportOptions { pub output: Option, #[command(flatten)] - pub evaluation: EvalOptions, + pub evaluation: EvalCommand, } -impl ExportOptions { +impl ExportCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut program = self.evaluation.prepare(&global)?; diff --git a/cli/src/format.rs b/cli/src/format.rs index de788ad987..21142d81ba 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -91,7 +91,7 @@ impl Write for Output { } #[derive(clap::Parser, Debug)] -pub struct FormatOptions { +pub struct FormatCommand { #[command(flatten)] sources: Files, @@ -104,7 +104,7 @@ pub struct FormatOptions { in_place: bool, } -impl FormatOptions { +impl FormatCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut output: Output = match (&self.output, &global.files.file, self.in_place) { (None, None, _) | (None, Some(_), false) => Output::new(None)?, diff --git a/cli/src/main.rs b/cli/src/main.rs index ca708d0004..7d37663a8d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -16,23 +16,20 @@ mod pprint_ast; mod query; mod typecheck; -use eval::EvalOptions; +use eval::EvalCommand; use crate::cli::{Command, Options}; fn main() { let opts = ::parse(); - let result = match opts.command.unwrap_or(Command::Eval(EvalOptions {})) { + let result = match opts.command.unwrap_or(Command::Eval(EvalCommand {})) { Command::Eval(eval) => eval.run(opts.global), - Command::PprintAst(pprint_ast) => pprint_ast.run(opts.global), - Command::Export(export) => export.run(opts.global), - Command::Query(query) => query.run(opts.global), - Command::Typecheck(typecheck) => typecheck.run(opts.global), + Command::GenCompletions(completions) => completions.run(opts.global), #[cfg(feature = "repl")] Command::Repl(repl) => repl.run(opts.global), @@ -42,8 +39,6 @@ fn main() { #[cfg(feature = "format")] Command::Format(format) => format.run(opts.global), - - Command::GenCompletions(completions) => completions.run(opts.global), }; result.unwrap_or_else(|e| e.report()) diff --git a/cli/src/pprint_ast.rs b/cli/src/pprint_ast.rs index e8a7af6d37..f18e30ddc7 100644 --- a/cli/src/pprint_ast.rs +++ b/cli/src/pprint_ast.rs @@ -5,15 +5,15 @@ use crate::{ }; #[derive(clap::Parser, Debug)] -pub struct PprintAstOptions { +pub struct PprintAstCommand { /// Performs code transformations before printing #[arg(long)] pub transform: bool, } -impl PprintAstOptions { +impl PprintAstCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { - let mut program = eval::prepare(&global.files, &global)?; + let mut program = eval::prepare(&global)?; program .pprint_ast(&mut std::io::stdout(), self.transform) .with_program(program) diff --git a/cli/src/query.rs b/cli/src/query.rs index 026ba10456..7bb63f6f8e 100644 --- a/cli/src/query.rs +++ b/cli/src/query.rs @@ -3,11 +3,11 @@ use nickel_lang_core::repl::query_print; use crate::{ cli::GlobalOptions, error::{CliResult, WithProgram}, - eval::EvalOptions, + eval::EvalCommand, }; #[derive(clap::Parser, Debug)] -pub struct QueryOptions { +pub struct QueryCommand { pub path: Option, #[arg(long)] @@ -26,10 +26,10 @@ pub struct QueryOptions { pub value: bool, #[command(flatten)] - pub evaluation: EvalOptions, + pub evaluation: EvalCommand, } -impl QueryOptions { +impl QueryCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut program = self.evaluation.prepare(&global)?; diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 553ceea53e..255e4effd7 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -6,12 +6,12 @@ use nickel_lang_core::repl::rustyline_frontend; use crate::{cli::GlobalOptions, error::CliResult}; #[derive(clap::Parser, Debug)] -pub struct ReplOptions { +pub struct ReplCommand { #[arg(long)] pub history_file: Option, } -impl ReplOptions { +impl ReplCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let histfile = if let Some(h) = self.history_file { h diff --git a/cli/src/typecheck.rs b/cli/src/typecheck.rs index f4e5760d30..ea4a74b6d0 100644 --- a/cli/src/typecheck.rs +++ b/cli/src/typecheck.rs @@ -5,11 +5,11 @@ use crate::{ }; #[derive(clap::Parser, Debug)] -pub struct TypecheckOptions {} +pub struct TypecheckCommand {} -impl TypecheckOptions { +impl TypecheckCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { - let mut program = eval::prepare(&global.files, &global)?; + let mut program = eval::prepare(&global)?; program.typecheck().with_program(program) } } From 486a259123523963d0603bbb5599211257ebfe15 Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Tue, 8 Aug 2023 08:37:21 +0000 Subject: [PATCH 07/11] Properly respect feature flags --- cli/src/cli.rs | 14 +++++++++++--- cli/src/error.rs | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index d4601bf935..20f7281286 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -4,11 +4,19 @@ use std::path::PathBuf; use git_version::git_version; use crate::{ - completions::GenCompletionsCommand, doc::DocCommand, eval::EvalCommand, export::ExportCommand, - format::FormatCommand, pprint_ast::PprintAstCommand, query::QueryCommand, repl::ReplCommand, - typecheck::TypecheckCommand, + completions::GenCompletionsCommand, eval::EvalCommand, export::ExportCommand, + pprint_ast::PprintAstCommand, query::QueryCommand, typecheck::TypecheckCommand, }; +#[cfg(feature = "repl")] +use crate::repl::ReplCommand; + +#[cfg(feature = "doc")] +use crate::doc::DocCommand; + +#[cfg(feature = "format")] +use crate::format::FormatCommand; + #[derive(clap::Parser, Debug)] /// The interpreter of the Nickel language. #[command( diff --git a/cli/src/error.rs b/cli/src/error.rs index f50c48512c..6d4a90496b 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -57,6 +57,7 @@ impl Error { Error::Io { error } => { eprintln!("{error}") } + #[cfg(feature = "repl")] Error::Repl { error } => { use nickel_lang_core::repl::InitError; match error { @@ -66,6 +67,7 @@ impl Error { } } } + #[cfg(feature = "format")] Error::Format { error } => eprintln!("{error}"), } } From 7872a35a0f4397ef95e27bd0caff1e4c926356af Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Tue, 8 Aug 2023 08:52:47 +0000 Subject: [PATCH 08/11] WithProgram -> ReportWithProgram --- cli/src/doc.rs | 5 +++-- cli/src/error.rs | 8 ++++---- cli/src/eval.rs | 4 ++-- cli/src/export.rs | 4 ++-- cli/src/pprint_ast.rs | 4 ++-- cli/src/query.rs | 4 ++-- cli/src/typecheck.rs | 4 ++-- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/cli/src/doc.rs b/cli/src/doc.rs index b2b3acbc81..cdd102e8e2 100644 --- a/cli/src/doc.rs +++ b/cli/src/doc.rs @@ -11,7 +11,7 @@ use nickel_lang_core::{ use crate::{ cli::GlobalOptions, - error::{CliResult, WithProgram}, + error::{CliResult, ReportWithProgram}, eval, }; @@ -58,7 +58,8 @@ pub struct DocCommand { impl DocCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut program = eval::prepare(&global)?; - self.export_doc(&mut program, &global).with_program(program) + self.export_doc(&mut program, &global) + .report_with_program(program) } fn export_doc( diff --git a/cli/src/error.rs b/cli/src/error.rs index 6d4a90496b..93d6421488 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -40,12 +40,12 @@ impl From for Error { } } -pub trait WithProgram { - fn with_program(self, program: Program) -> CliResult; +pub trait ReportWithProgram { + fn report_with_program(self, program: Program) -> CliResult; } -impl WithProgram for Result { - fn with_program(self, program: Program) -> CliResult { +impl ReportWithProgram for Result { + fn report_with_program(self, program: Program) -> CliResult { self.map_err(|error| Error::Program { program, error }) } } diff --git a/cli/src/eval.rs b/cli/src/eval.rs index 6bccc87906..f35d409997 100644 --- a/cli/src/eval.rs +++ b/cli/src/eval.rs @@ -2,7 +2,7 @@ use nickel_lang_core::{eval::cache::lazy::CBNCache, program::Program}; use crate::{ cli::GlobalOptions, - error::{CliResult, WithProgram}, + error::{CliResult, ReportWithProgram}, }; #[derive(clap::Parser, Debug)] @@ -14,7 +14,7 @@ impl EvalCommand { program .eval_full() .map(|t| println!("{t}")) - .with_program(program) + .report_with_program(program) } pub fn prepare(&self, global: &GlobalOptions) -> CliResult> { diff --git a/cli/src/export.rs b/cli/src/export.rs index 135107cfe7..221a36038d 100644 --- a/cli/src/export.rs +++ b/cli/src/export.rs @@ -10,7 +10,7 @@ use nickel_lang_core::{ term::RichTerm, }; -use crate::error::{CliResult, WithProgram}; +use crate::error::{CliResult, ReportWithProgram}; use crate::{cli::GlobalOptions, eval::EvalCommand}; #[derive(clap::Parser, Debug)] @@ -30,7 +30,7 @@ impl ExportCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut program = self.evaluation.prepare(&global)?; - self.export(&mut program).with_program(program) + self.export(&mut program).report_with_program(program) } fn export(self, program: &mut Program) -> Result<(), Error> { diff --git a/cli/src/pprint_ast.rs b/cli/src/pprint_ast.rs index f18e30ddc7..cae5222b1f 100644 --- a/cli/src/pprint_ast.rs +++ b/cli/src/pprint_ast.rs @@ -1,6 +1,6 @@ use crate::{ cli::GlobalOptions, - error::{CliResult, WithProgram}, + error::{CliResult, ReportWithProgram}, eval, }; @@ -16,6 +16,6 @@ impl PprintAstCommand { let mut program = eval::prepare(&global)?; program .pprint_ast(&mut std::io::stdout(), self.transform) - .with_program(program) + .report_with_program(program) } } diff --git a/cli/src/query.rs b/cli/src/query.rs index 7bb63f6f8e..3a7fd55e23 100644 --- a/cli/src/query.rs +++ b/cli/src/query.rs @@ -2,7 +2,7 @@ use nickel_lang_core::repl::query_print; use crate::{ cli::GlobalOptions, - error::{CliResult, WithProgram}, + error::{CliResult, ReportWithProgram}, eval::EvalCommand, }; @@ -52,6 +52,6 @@ impl QueryCommand { query_print::write_query_result(&mut std::io::stdout(), &term, attrs).unwrap() }) - .with_program(program) + .report_with_program(program) } } diff --git a/cli/src/typecheck.rs b/cli/src/typecheck.rs index ea4a74b6d0..afceb7d896 100644 --- a/cli/src/typecheck.rs +++ b/cli/src/typecheck.rs @@ -1,6 +1,6 @@ use crate::{ cli::GlobalOptions, - error::{CliResult, WithProgram}, + error::{CliResult, ReportWithProgram}, eval, }; @@ -10,6 +10,6 @@ pub struct TypecheckCommand {} impl TypecheckCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut program = eval::prepare(&global)?; - program.typecheck().with_program(program) + program.typecheck().report_with_program(program) } } From 0dd10ee2464933e97433d422bcac8a1c11bc6cba Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Tue, 8 Aug 2023 08:56:35 +0000 Subject: [PATCH 09/11] Remove the unecessary `Files` struct for now --- cli/src/cli.rs | 6 ------ cli/src/doc.rs | 2 +- cli/src/eval.rs | 1 - cli/src/format.rs | 12 +++--------- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 20f7281286..d1877a6e4e 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -44,12 +44,6 @@ pub struct GlobalOptions { #[arg(long, global = true, value_enum, default_value_t)] pub color: clap::ColorChoice, - #[command(flatten)] - pub files: Files, -} - -#[derive(clap::Parser, Debug)] -pub struct Files { /// Input file, omit to read from stdin #[arg(long, short, global = true)] pub file: Option, diff --git a/cli/src/doc.rs b/cli/src/doc.rs index cdd102e8e2..98386b7017 100644 --- a/cli/src/doc.rs +++ b/cli/src/doc.rs @@ -96,7 +96,7 @@ impl DocCommand { let mut has_file_name = false; - if let Some(path) = &global.files.file { + if let Some(path) = &global.file { if let Some(file_stem) = path.file_stem() { output_file.push(file_stem); has_file_name = true; diff --git a/cli/src/eval.rs b/cli/src/eval.rs index f35d409997..9e018e315c 100644 --- a/cli/src/eval.rs +++ b/cli/src/eval.rs @@ -24,7 +24,6 @@ impl EvalCommand { pub fn prepare(global: &GlobalOptions) -> CliResult> { let mut program = global - .files .file .clone() .map(|f| Program::new_from_file(f, std::io::stderr())) diff --git a/cli/src/format.rs b/cli/src/format.rs index 21142d81ba..8df3bf5aab 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -8,10 +8,7 @@ use std::{ use tempfile::NamedTempFile; use topiary::TopiaryQuery; -use crate::{ - cli::{Files, GlobalOptions}, - error::CliResult, -}; +use crate::{cli::GlobalOptions, error::CliResult}; #[derive(Debug)] pub enum FormatError { @@ -92,9 +89,6 @@ impl Write for Output { #[derive(clap::Parser, Debug)] pub struct FormatCommand { - #[command(flatten)] - sources: Files, - /// Output file. Standard output by default. #[arg(short, long)] output: Option, @@ -106,11 +100,11 @@ pub struct FormatCommand { impl FormatCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { - let mut output: Output = match (&self.output, &global.files.file, self.in_place) { + let mut output: Output = match (&self.output, &global.file, self.in_place) { (None, None, _) | (None, Some(_), false) => Output::new(None)?, (None, Some(file), true) | (Some(file), _, _) => Output::new(Some(file))?, }; - let mut input: Box = match global.files.file { + let mut input: Box = match global.file { None => Box::new(stdin()), Some(f) => Box::new(BufReader::new(File::open(f)?)), }; From aeebbbac93daee2755f7bb69fa5e90ab35251e2c Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Tue, 8 Aug 2023 10:17:45 +0000 Subject: [PATCH 10/11] ReportWithProgram -> ResultErrorExt --- cli/src/doc.rs | 2 +- cli/src/error.rs | 4 ++-- cli/src/eval.rs | 2 +- cli/src/export.rs | 2 +- cli/src/pprint_ast.rs | 2 +- cli/src/query.rs | 2 +- cli/src/typecheck.rs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/src/doc.rs b/cli/src/doc.rs index 98386b7017..b454fd9603 100644 --- a/cli/src/doc.rs +++ b/cli/src/doc.rs @@ -11,7 +11,7 @@ use nickel_lang_core::{ use crate::{ cli::GlobalOptions, - error::{CliResult, ReportWithProgram}, + error::{CliResult, ResultErrorExt}, eval, }; diff --git a/cli/src/error.rs b/cli/src/error.rs index 93d6421488..0c8fff1be9 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -40,11 +40,11 @@ impl From for Error { } } -pub trait ReportWithProgram { +pub trait ResultErrorExt { fn report_with_program(self, program: Program) -> CliResult; } -impl ReportWithProgram for Result { +impl ResultErrorExt for Result { fn report_with_program(self, program: Program) -> CliResult { self.map_err(|error| Error::Program { program, error }) } diff --git a/cli/src/eval.rs b/cli/src/eval.rs index 9e018e315c..ff5e03ec49 100644 --- a/cli/src/eval.rs +++ b/cli/src/eval.rs @@ -2,7 +2,7 @@ use nickel_lang_core::{eval::cache::lazy::CBNCache, program::Program}; use crate::{ cli::GlobalOptions, - error::{CliResult, ReportWithProgram}, + error::{CliResult, ResultErrorExt}, }; #[derive(clap::Parser, Debug)] diff --git a/cli/src/export.rs b/cli/src/export.rs index 221a36038d..95e304640e 100644 --- a/cli/src/export.rs +++ b/cli/src/export.rs @@ -10,7 +10,7 @@ use nickel_lang_core::{ term::RichTerm, }; -use crate::error::{CliResult, ReportWithProgram}; +use crate::error::{CliResult, ResultErrorExt}; use crate::{cli::GlobalOptions, eval::EvalCommand}; #[derive(clap::Parser, Debug)] diff --git a/cli/src/pprint_ast.rs b/cli/src/pprint_ast.rs index cae5222b1f..bb2a439ce7 100644 --- a/cli/src/pprint_ast.rs +++ b/cli/src/pprint_ast.rs @@ -1,6 +1,6 @@ use crate::{ cli::GlobalOptions, - error::{CliResult, ReportWithProgram}, + error::{CliResult, ResultErrorExt}, eval, }; diff --git a/cli/src/query.rs b/cli/src/query.rs index 3a7fd55e23..e2aeb2a186 100644 --- a/cli/src/query.rs +++ b/cli/src/query.rs @@ -2,7 +2,7 @@ use nickel_lang_core::repl::query_print; use crate::{ cli::GlobalOptions, - error::{CliResult, ReportWithProgram}, + error::{CliResult, ResultErrorExt}, eval::EvalCommand, }; diff --git a/cli/src/typecheck.rs b/cli/src/typecheck.rs index afceb7d896..68c0b0162b 100644 --- a/cli/src/typecheck.rs +++ b/cli/src/typecheck.rs @@ -1,6 +1,6 @@ use crate::{ cli::GlobalOptions, - error::{CliResult, ReportWithProgram}, + error::{CliResult, ResultErrorExt}, eval, }; From 1bb6384383d913fe22ef32532d3ae13991339ef6 Mon Sep 17 00:00:00 2001 From: Viktor Kleen Date: Tue, 8 Aug 2023 11:12:36 +0000 Subject: [PATCH 11/11] Suggestions from code review --- cli/src/format.rs | 27 ++++++++++----------------- cli/src/query.rs | 44 +++++++++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/cli/src/format.rs b/cli/src/format.rs index 8df3bf5aab..8c38d034b9 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -47,21 +47,14 @@ pub enum Output { } impl Output { - pub fn new(path: Option<&Path>) -> CliResult { - match path { - None => Ok(Self::Stdout), - Some(path) => { - let path = nickel_lang_core::cache::normalize_path(path)?; - Ok(Self::Disk { - staged: NamedTempFile::new_in(path.parent().ok_or_else(|| { - FormatError::NotAFile { - path: path.to_owned(), - } - })?)?, - output: path.to_owned(), - }) - } - } + pub fn from_path(path: &Path) -> CliResult { + let path = nickel_lang_core::cache::normalize_path(path)?; + Ok(Self::Disk { + staged: NamedTempFile::new_in(path.parent().ok_or_else(|| FormatError::NotAFile { + path: path.to_owned(), + })?)?, + output: path.to_owned(), + }) } pub fn persist(self) { @@ -101,8 +94,8 @@ pub struct FormatCommand { impl FormatCommand { pub fn run(self, global: GlobalOptions) -> CliResult<()> { let mut output: Output = match (&self.output, &global.file, self.in_place) { - (None, None, _) | (None, Some(_), false) => Output::new(None)?, - (None, Some(file), true) | (Some(file), _, _) => Output::new(Some(file))?, + (None, None, _) | (None, Some(_), false) => Output::Stdout, + (None, Some(file), true) | (Some(file), _, _) => Output::from_path(file)?, }; let mut input: Box = match global.file { None => Box::new(stdin()), diff --git a/cli/src/query.rs b/cli/src/query.rs index e2aeb2a186..696bc229b4 100644 --- a/cli/src/query.rs +++ b/cli/src/query.rs @@ -30,27 +30,37 @@ pub struct QueryCommand { } impl QueryCommand { - pub fn run(self, global: GlobalOptions) -> CliResult<()> { + fn attributes_specified(&self) -> bool { + self.doc || self.contract || self.typ || self.default || self.value + } + + fn query_attributes(&self) -> query_print::Attributes { + // Use a default selection of attributes if no option is specified + if !self.attributes_specified() { + query_print::Attributes::default() + } else { + query_print::Attributes { + doc: self.doc, + contract: self.contract, + typ: self.typ, + default: self.default, + value: self.value, + } + } + } + + pub fn run(mut self, global: GlobalOptions) -> CliResult<()> { let mut program = self.evaluation.prepare(&global)?; program - .query(self.path) + .query(std::mem::take(&mut self.path)) .map(|term| { - // Print a default selection of attributes if no option is specified - let attrs = - if !self.doc && !self.contract && !self.typ && !self.default && !self.value { - query_print::Attributes::default() - } else { - query_print::Attributes { - doc: self.doc, - contract: self.contract, - typ: self.typ, - default: self.default, - value: self.value, - } - }; - - query_print::write_query_result(&mut std::io::stdout(), &term, attrs).unwrap() + query_print::write_query_result( + &mut std::io::stdout(), + &term, + self.query_attributes(), + ) + .unwrap() }) .report_with_program(program) }