Skip to content

Commit

Permalink
feat: #10 ✨ Initial draft of sub commands working
Browse files Browse the repository at this point in the history
  • Loading branch information
ddanier committed Apr 24, 2024
1 parent f8e8bd6 commit d8fb6c8
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 62 deletions.
63 changes: 46 additions & 17 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::commands::Nur;
use crate::errors::{NurError, NurResult};
use crate::names::NUR_NAME;
use nu_engine::{get_full_help, CallExt};
use nu_parser::escape_for_script_arg;
Expand All @@ -11,18 +12,46 @@ use nu_protocol::{
};
use nu_utils::stdout_write_all_and_flush;

pub(crate) fn gather_commandline_args(args: Vec<String>) -> (Vec<String>, bool, Vec<String>) {
pub(crate) fn is_safe_taskname(name: &str) -> bool {
// This is basically similar to string_should_be_quoted
// in nushell/crates/nu-parser/src/deparse.rs:1,
// BUT may change as the requirements are different.
// Also I added "#" and "^", as seem in
// nushell/crates/nu-parser/src/parse_keywords.rs:175
!name.starts_with('$')
&& !(name.chars().any(|c| {
c == ' '
|| c == '('
|| c == '\''
|| c == '`'
|| c == '"'
|| c == '\\'
|| c == '#'
|| c == '^'
}))
}

pub(crate) fn gather_commandline_args(
args: Vec<String>,
) -> NurResult<(Vec<String>, bool, Vec<String>)> {
let mut args_to_nur = Vec::from([String::from(NUR_NAME)]);
let mut task_and_args = Vec::from([String::from(NUR_NAME)]);
let mut task_call = Vec::from([String::from(NUR_NAME)]);
let mut has_task_call = false;
let mut args_iter = args.iter();

args_iter.next(); // Ignore own name
#[allow(clippy::while_let_on_iterator)]
while let Some(arg) = args_iter.next() {
if !arg.starts_with('-') {
// At least first non nur argument must be safe
if !is_safe_taskname(arg) {
eprintln!("{}", arg);
return Err(NurError::InvalidTaskName(arg.clone()));
}

// Register task name and switch to task call parsing
has_task_call = true;
task_and_args.push(arg.clone());
task_call.push(arg.clone());
break;
}

Expand All @@ -42,14 +71,14 @@ pub(crate) fn gather_commandline_args(args: Vec<String>) -> (Vec<String>, bool,
// Consume remaining elements in iterator
#[allow(clippy::while_let_on_iterator)]
while let Some(arg) = args_iter.next() {
task_and_args.push(escape_for_script_arg(arg));
task_call.push(escape_for_script_arg(arg));
}
} else {
// Also remove "nur" from task_and_args
task_and_args.clear();
// Also remove "nur" from task_call
task_call.clear();
}

(args_to_nur, has_task_call, task_and_args)
Ok((args_to_nur, has_task_call, task_call))
}

pub(crate) fn parse_commandline_args(
Expand Down Expand Up @@ -141,11 +170,11 @@ mod tests {
String::from("--task-option"),
String::from("task-value"),
];
let (nur_args, has_task_call, task_and_args) = gather_commandline_args(args);
let (nur_args, has_task_call, task_call) = gather_commandline_args(args).unwrap();
assert_eq!(nur_args, vec![String::from("nur"), String::from("--quiet")]);
assert_eq!(has_task_call, true);
assert_eq!(
task_and_args,
task_call,
vec![
String::from("nur"),
String::from("some_task_name"),
Expand All @@ -163,11 +192,11 @@ mod tests {
String::from("--task-option"),
String::from("task-value"),
];
let (nur_args, has_task_call, task_and_args) = gather_commandline_args(args);
let (nur_args, has_task_call, task_call) = gather_commandline_args(args).unwrap();
assert_eq!(nur_args, vec![String::from("nur")]);
assert_eq!(has_task_call, true);
assert_eq!(
task_and_args,
task_call,
vec![
String::from("nur"),
String::from("some_task_name"),
Expand All @@ -180,10 +209,10 @@ mod tests {
#[test]
fn test_gather_commandline_args_handles_missing_task_name() {
let args = vec![String::from("nur"), String::from("--help")];
let (nur_args, has_task_call, task_and_args) = gather_commandline_args(args);
let (nur_args, has_task_call, task_call) = gather_commandline_args(args).unwrap();
assert_eq!(nur_args, vec![String::from("nur"), String::from("--help")]);
assert_eq!(has_task_call, false);
assert_eq!(task_and_args, vec![] as Vec<String>);
assert_eq!(task_call, vec![] as Vec<String>);
}

#[test]
Expand All @@ -193,22 +222,22 @@ mod tests {
String::from("--quiet"),
String::from("some_task_name"),
];
let (nur_args, has_task_call, task_and_args) = gather_commandline_args(args);
let (nur_args, has_task_call, task_call) = gather_commandline_args(args).unwrap();
assert_eq!(nur_args, vec![String::from("nur"), String::from("--quiet")]);
assert_eq!(has_task_call, true);
assert_eq!(
task_and_args,
task_call,
vec![String::from("nur"), String::from("some_task_name")]
);
}

#[test]
fn test_gather_commandline_args_handles_no_args_at_all() {
let args = vec![String::from("nur")];
let (nur_args, has_task_call, task_and_args) = gather_commandline_args(args);
let (nur_args, has_task_call, task_call) = gather_commandline_args(args).unwrap();
assert_eq!(nur_args, vec![String::from("nur")]);
assert_eq!(has_task_call, false);
assert_eq!(task_and_args, vec![] as Vec<String>);
assert_eq!(task_call, vec![] as Vec<String>);
}

fn _create_minimal_engine_for_erg_parsing() -> EngineState {
Expand Down
41 changes: 36 additions & 5 deletions src/engine.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::args::{parse_commandline_args, NurArgs};
use crate::args::{is_safe_taskname, parse_commandline_args, NurArgs};
use crate::errors::{NurError, NurResult};
use crate::names::{
NUR_ENV_NU_LIB_DIRS, NUR_NAME, NUR_VAR_CONFIG_DIR, NUR_VAR_DEFAULT_LIB_DIR,
Expand Down Expand Up @@ -117,10 +117,12 @@ impl NurEngine {
Span::unknown(),
),
);
// nur_record.push(
// NUR_VAR_TASK_NAME,
// Value::string(&self.state.task_name, Span::unknown()),
// );
if let Some(full_task_name) = self.find_task_name() {
nur_record.push(
NUR_VAR_TASK_NAME,
Value::string(&full_task_name[4..], Span::unknown()), // strip "nur "
);
}
nur_record.push(
NUR_VAR_CONFIG_DIR,
Value::string(
Expand Down Expand Up @@ -185,6 +187,35 @@ impl NurEngine {
Ok(())
}

pub(crate) fn find_task_name(&self) -> Option<String> {
if !self.state.has_task_call {
return None;
}

let task_call_length = self.state.task_call.len();

let full_task_name = self.state.task_call[0..2].join(" ");
if !self.has_def(full_task_name) {
return None;
}

let mut i = 2;
while i < task_call_length {
if !is_safe_taskname(&self.state.task_call[i]) {
break;
}
let next_possible_task_name = self.state.task_call[0..i + 1].join(" ");
if !self.has_def(next_possible_task_name) {
break;
}
i += 1; // check next argument
}

let full_task_name = self.state.task_call[0..i].join(" ");

Some(full_task_name)
}

fn _parse_nu_script(
&mut self,
file_path: Option<&str>,
Expand Down
4 changes: 4 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ pub enum NurError {
#[diagnostic()]
ParseErrors(#[related] Vec<ParseError>),

#[error("Invalid task name {0}")]
#[diagnostic()]
InvalidTaskName(String),

#[error("Could not find the task {0}")]
#[diagnostic()]
TaskNotFound(String),
Expand Down
78 changes: 47 additions & 31 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use std::process::ExitCode;
fn main() -> Result<ExitCode, miette::ErrReport> {
// Initialise nur state
let run_path = get_init_cwd();
let nur_state = NurState::new(run_path, env::args().collect());
let nur_state = NurState::new(run_path, env::args().collect())?;

// Create raw nu engine state
let engine_state = init_engine_state(&nur_state.project_path)?;
Expand All @@ -44,7 +44,7 @@ fn main() -> Result<ExitCode, miette::ErrReport> {
eprintln!("project path: {:?}", nur_engine.state.project_path);
eprintln!();
eprintln!("nur args: {:?}", parsed_nur_args);
eprintln!("task call: {:?}", nur_engine.state.task_and_args);
eprintln!("task call: {:?}", nur_engine.state.task_call);
eprintln!();
eprintln!("nur config dir: {:?}", nur_engine.state.config_dir);
eprintln!(
Expand Down Expand Up @@ -98,34 +98,47 @@ fn main() -> Result<ExitCode, miette::ErrReport> {
std::process::exit(0);
}

// Initialize internal data
// let task_def_name = format!("{} {}", NUR_NAME, nur_engine.state.task_name);
// #[cfg(feature = "debug")]
// if parsed_nur_args.debug_output {
// eprintln!("task def name: {}", task_def_name);
// }
// Show help if no task call was found
// (error exit if --help was not passed)
if !nur_engine.state.has_task_call {
nur_engine.print_help(&Nur);
if parsed_nur_args.show_help {
std::process::exit(0);
} else {
std::process::exit(1);
}
}

// Find full task name
let full_task_name = match nur_engine.find_task_name() {
Some(full_task_name) => full_task_name,
None => {
return Err(miette::ErrReport::from(NurError::TaskNotFound(
nur_engine.state.task_call[1].clone(),
)))
}
};
#[cfg(feature = "debug")]
if parsed_nur_args.debug_output {
eprintln!("full task name: {}", full_task_name);
}

// Handle help
// if parsed_nur_args.show_help || nur_engine.state.task_name.is_empty() {
// if nur_engine.state.task_name.is_empty() {
// nur_engine.print_help(&Nur);
// } else if let Some(command) = nur_engine.get_def(task_def_name) {
// nur_engine.clone().print_help(command);
// } else {
// return Err(miette::ErrReport::from(NurError::TaskNotFound(
// nur_engine.state.task_name,
// )));
// }
//
// std::process::exit(0);
// }

// Check if requested task exists
// if !nur_engine.has_def(&task_def_name) {
// return Err(miette::ErrReport::from(NurError::TaskNotFound(
// nur_engine.state.task_and_args[1].clone(), // TODO: Use real task name
// )));
// }
if parsed_nur_args.show_help {
if !nur_engine.state.has_task_call {
nur_engine.print_help(&Nur);
std::process::exit(0);
}

if let Some(command) = nur_engine.get_def(&full_task_name) {
nur_engine.clone().print_help(command);
std::process::exit(0);
} else {
return Err(miette::ErrReport::from(NurError::TaskNotFound(
full_task_name.clone(),
)));
}
}

// Prepare input data - if requested
let input = if parsed_nur_args.attach_stdin {
Expand All @@ -151,7 +164,7 @@ fn main() -> Result<ExitCode, miette::ErrReport> {

// Execute the task
let exit_code: i64;
let full_task_call = nur_engine.state.task_and_args.join(" ");
let full_task_call = nur_engine.state.task_call.join(" ");
#[cfg(feature = "debug")]
if parsed_nur_args.debug_output {
eprintln!("full task call: {}", full_task_call);
Expand All @@ -165,8 +178,11 @@ fn main() -> Result<ExitCode, miette::ErrReport> {
}
} else {
println!("nur version {}", env!("CARGO_PKG_VERSION"));
println!("Project path {:?}", nur_engine.state.project_path);
println!("Executing task {}", nur_engine.state.task_and_args[1]); // TODO: Use real task name
println!(
"Project path: {}",
nur_engine.state.project_path.to_str().unwrap()
);
println!("Executing task: {}", &full_task_name[4..]); // strip "nur "
println!();
exit_code = nur_engine.eval_and_print(full_task_call, input)?;
#[cfg(feature = "debug")]
Expand Down
19 changes: 10 additions & 9 deletions src/state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::args::gather_commandline_args;
use crate::errors::NurResult;
use crate::names::{
NUR_CONFIG_CONFIG_FILENAME, NUR_CONFIG_DIR, NUR_CONFIG_ENV_FILENAME, NUR_CONFIG_LIB_PATH,
NUR_FILE, NUR_LOCAL_FILE,
Expand All @@ -22,11 +23,11 @@ pub(crate) struct NurState {

pub(crate) args_to_nur: Vec<String>,
pub(crate) has_task_call: bool,
pub(crate) task_and_args: Vec<String>,
pub(crate) task_call: Vec<String>,
}

impl NurState {
pub(crate) fn new(run_path: PathBuf, args: Vec<String>) -> Self {
pub(crate) fn new(run_path: PathBuf, args: Vec<String>) -> NurResult<Self> {
// Get initial directory details
let found_project_path = find_project_path(&run_path);
let has_project_path = found_project_path.is_some();
Expand All @@ -43,9 +44,9 @@ impl NurState {
let local_nurfile_path = project_path.join(NUR_LOCAL_FILE);

// Parse args into bits
let (args_to_nur, has_task_call, task_and_args) = gather_commandline_args(args);
let (args_to_nur, has_task_call, task_call) = gather_commandline_args(args)?;

NurState {
Ok(NurState {
run_path,
has_project_path,
project_path,
Expand All @@ -60,8 +61,8 @@ impl NurState {

args_to_nur,
has_task_call,
task_and_args,
}
task_call,
})
}
}

Expand Down Expand Up @@ -109,7 +110,7 @@ mod tests {
);
assert_eq!(state.has_task_call, true);
assert_eq!(
state.task_and_args,
state.task_call,
vec![
String::from("nur"),
String::from("some_task"),
Expand Down Expand Up @@ -157,7 +158,7 @@ mod tests {
);
assert_eq!(state.has_task_call, true);
assert_eq!(
state.task_and_args,
state.task_call,
vec![
String::from("nur"),
String::from("some_task"),
Expand Down Expand Up @@ -196,6 +197,6 @@ mod tests {
vec![String::from("nur"), String::from("--help"),]
);
assert_eq!(state.has_task_call, false);
assert_eq!(state.task_and_args, vec![] as Vec<String>);
assert_eq!(state.task_call, vec![] as Vec<String>);
}
}

0 comments on commit d8fb6c8

Please sign in to comment.