Projects STRLCPY dum Commits ab52cab6
🤬
  • ■ ■ ■ ■ ■ ■
    CHANGELOG.md
     1 +## Unreleased
     2 + 
     3 +- Ability to select npm scripts interactively, with `-i, --interactive` flag
     4 + 
    1 5  ## v0.1.12
    2 6   
    3 7  - Properly concat `$PATH` on Windows. [#24](https://github.com/egoist/dum/issues/24)
    skipped 34 lines
  • ■ ■ ■ ■ ■ ■
    src/args.rs
    skipped 7 lines
    8 8   pub forwared: String,
    9 9   pub change_dir: PathBuf,
    10 10   pub command: String,
     11 + pub interactive: bool,
    11 12  }
    12 13   
    13 14  pub fn parse_args(args_vec: &[String]) -> AppArgs {
    skipped 4 lines
    18 19   change_dir: PathBuf::from(env::current_dir().as_ref().unwrap()),
    19 20   forwared: "".to_string(),
    20 21   command: "".to_string(),
     22 + interactive: false,
    21 23   };
    22 24   
    23 25   loop {
    skipped 16 lines
    40 42   std::process::exit(1);
    41 43   }
    42 44   args.change_dir = dir;
     45 + }
     46 + "-i" | "--interactive" => {
     47 + args.interactive = true;
    43 48   }
    44 49   "-h" | "--help" => {
    45 50   print!("{}", get_help());
    skipped 22 lines
    68 73   _ => v.to_string(),
    69 74   };
    70 75   } else {
     76 + if args.interactive {
     77 + eprintln!("You can't pass arguments to interactive mode");
     78 + exit(1);
     79 + }
    71 80   args.forwared.push_str(" ");
    72 81   args.forwared.push_str(&v);
    73 82   }
    skipped 29 lines
    103 112   
    104 113  FLAGS:
    105 114   -c <dir> Change working directory
     115 + -i, --interactive Interactive mode
    106 116   -h, --help Prints help information
    107 117   -v, --version Prints version number
    108 118  ",
    skipped 33 lines
  • ■ ■ ■ ■ ■
    src/install.rs
    1  -use dialoguer::console::Term;
    2  -use dialoguer::{theme::ColorfulTheme, Select};
     1 +use crate::prompt;
    3 2  use std::path::PathBuf;
    4 3   
    5 4  // A function to guess package manager by looking for lock file in current directory only
    skipped 18 lines
    24 23   }
    25 24   
    26 25   let items = vec!["pnpm", "npm", "yarn"];
    27  - let selection = Select::with_theme(&ColorfulTheme::default())
    28  - .with_prompt("Which package manager do you want to use?")
    29  - .items(&items)
    30  - .default(0)
    31  - .interact_on_opt(&Term::stderr())
    32  - .ok()?;
    33  - 
    34  - if selection.is_none() {
    35  - return None;
    36  - }
    37  - 
    38  - Some(items[selection.unwrap()].to_string())
     26 + prompt::select("Which package manager do you want to use?", items)
    39 27  }
    40 28   
  • ■ ■ ■ ■ ■ ■
    src/main.rs
    1  -use dum::{dum, parse_args};
     1 +mod args;
     2 +mod install;
     3 +mod prompt;
     4 +mod run;
     5 + 
    2 6  use std::env;
    3 7   
    4 8  fn main() {
    5 9   let args_vec: Vec<String> = env::args().collect();
    6  - let args = parse_args(&args_vec[1..]);
     10 + let args = args::parse_args(&args_vec[1..]);
    7 11   
    8  - dum(&args);
     12 + run::run(&args);
    9 13  }
    10 14   
  • ■ ■ ■ ■ ■ ■
    src/prompt.rs
     1 +use dialoguer::console::Term;
     2 +use dialoguer::{theme::ColorfulTheme, Input, Select};
     3 + 
     4 +pub fn select(message: &str, script_names: Vec<&str>) -> Option<String> {
     5 + let selection = Select::with_theme(&ColorfulTheme::default())
     6 + .with_prompt(message)
     7 + .items(&script_names)
     8 + .default(0)
     9 + .interact_on_opt(&Term::stderr())
     10 + .ok()?;
     11 + 
     12 + Term::stderr().show_cursor().expect("failed to show cursor");
     13 + 
     14 + if selection.is_none() {
     15 + return None;
     16 + }
     17 + 
     18 + Some(script_names[selection.unwrap()].to_string())
     19 +}
     20 + 
     21 +pub fn input(message: &str) -> String {
     22 + let input = Input::<String>::new()
     23 + .with_prompt(message)
     24 + .allow_empty(true)
     25 + .with_initial_text("")
     26 + .interact_text_on(&Term::stderr())
     27 + .ok();
     28 + 
     29 + Term::stderr().show_cursor().expect("failed to show cursor");
     30 + 
     31 + input.expect("should have input")
     32 +}
     33 + 
  • ■ ■ ■ ■ ■ ■
    src/lib.rs src/run.rs
    1  -mod args;
    2  -mod install;
    3  - 
    4  -// Re-export
    5  -pub use crate::args::parse_args;
     1 +use crate::{args, install, prompt};
    6 2   
    7 3  use serde_json::Value;
    8 4  use std::collections::HashMap;
    skipped 74 lines
    83 79   None
    84 80  }
    85 81   
    86  -pub fn dum(app_args: &args::AppArgs) {
     82 +pub fn run(app_args: &args::AppArgs) {
    87 83   let pkg_paths = find_closest_files(&app_args.change_dir, "package.json", true);
    88 84   let pkg_path = if pkg_paths.is_empty() {
    89  - println!("No package.json found");
     85 + eprintln!("No package.json found");
    90 86   exit(1);
    91 87   } else {
    92 88   pkg_paths[0].clone()
    skipped 11 lines
    104 100   let contents = read_to_string(pkg_path).expect("failed to read package.json");
    105 101   let v: Value = serde_json::from_str(&contents).expect("failed to parse package.json");
    106 102   
    107  - if app_args.command == "run" && app_args.script_name.is_empty() {
    108  - if let Some(scripts) = v["scripts"].as_object() {
    109  - println!("\nAvailable scripts:\n");
    110  - for (name, value) in scripts {
    111  - println!("{}", name);
    112  - println!(" {}", value);
    113  - }
    114  - } else {
    115  - println!("No scripts found.");
     103 + let scripts = v["scripts"].as_object();
     104 + if scripts.is_none() {
     105 + println!("No scripts found.");
     106 + return;
     107 + }
     108 + 
     109 + let mut script_name = app_args.script_name.clone();
     110 + let mut forwarded = app_args.forwared.clone();
     111 + 
     112 + if !app_args.interactive && app_args.command == "run" && script_name.is_empty() {
     113 + println!("\nAvailable scripts:\n");
     114 + for (name, value) in scripts.unwrap() {
     115 + println!("{}", name);
     116 + println!(" {}", value);
    116 117   }
    117 118   return;
    118 119   }
    119 120   
    120  - if app_args.script_name.is_empty() {
    121  - println!("No script name specified.\n");
    122  - println!("{}", args::get_help());
    123  - return;
     121 + if !script_name.is_empty() && app_args.interactive {
     122 + eprintln!("You can't specify script name in interactive mode");
     123 + exit(1);
     124 + }
     125 + 
     126 + if script_name.is_empty() {
     127 + if !app_args.interactive {
     128 + println!("No script name specified.\n");
     129 + println!("{}", args::get_help());
     130 + return;
     131 + }
     132 + 
     133 + // Choose an script interactively
     134 + // Convert keys of scripts to a vector of &str
     135 + let names_vec = scripts
     136 + .unwrap()
     137 + .keys()
     138 + .map(|k| k.as_str())
     139 + .collect::<Vec<&str>>();
     140 + script_name =
     141 + prompt::select("Select an npm script to run", names_vec).expect("nothing was selected");
     142 + forwarded = " ".to_string();
     143 + forwarded.push_str(&prompt::input("Enter arguments to pass to the script"));
    124 144   }
    125 145   
    126 146   // Run npm install if the script_name is "install"
    127  - if ["install", "add", "remove"].contains(&app_args.script_name.as_str()) {
     147 + if ["install", "add", "remove"].contains(&script_name.as_str()) {
    128 148   let pm = install::guess_package_manager(&execute_dir);
    129 149   
    130 150   if pm.is_none() {
    skipped 2 lines
    133 153   }
    134 154   
    135 155   run_command(
    136  - &[&pm.unwrap(), &app_args.script_name, &app_args.forwared],
     156 + &[&pm.unwrap(), &script_name, &forwarded],
    137 157   &RunOptions {
    138 158   current_dir: execute_dir,
    139 159   envs: HashMap::new(),
    skipped 2 lines
    142 162   return;
    143 163   }
    144 164   
    145  - let npm_script = v.get("scripts").and_then(|scripts| {
    146  - scripts.as_object().and_then(|scripts| {
    147  - scripts
    148  - .get(app_args.script_name.as_str())
    149  - .and_then(|script| {
    150  - let script = script.as_str().map(|script| script.to_string());
    151  - Some(script.unwrap_or_default())
    152  - })
     165 + let npm_script = scripts.and_then(|scripts| {
     166 + scripts.get(script_name.as_str()).and_then(|script| {
     167 + let script = script.as_str().map(|script| script.to_string());
     168 + Some(script.unwrap_or_default())
    153 169   })
    154 170   });
    155 171   
    156 172   if npm_script.is_some() {
    157 173   let script = npm_script.unwrap();
    158  - println!("> {}", app_args.script_name);
    159  - println!("> {}{}", script, app_args.forwared);
     174 + println!("> {}", script_name);
     175 + println!("> {}{}", script, forwarded);
    160 176   let envs = HashMap::from([("PATH".to_string(), get_path_env(bin_dirs))]);
    161 177   run_command(
    162  - &[&script, &app_args.forwared],
     178 + &[&script, &forwarded],
    163 179   &RunOptions {
    164 180   current_dir: execute_dir,
    165 181   envs,
    skipped 2 lines
    168 184   return;
    169 185   }
    170 186   
    171  - let resolved_bin = resolve_bin_path(app_args.script_name.as_str(), &bin_dirs);
     187 + let resolved_bin = resolve_bin_path(script_name.as_str(), &bin_dirs);
    172 188   if resolved_bin.is_some() {
    173 189   let bin_path = resolved_bin.unwrap();
    174  - println!("> {}", app_args.script_name);
    175  - println!("> {}{}", bin_path.to_str().unwrap(), app_args.forwared);
     190 + println!("> {}", script_name);
     191 + println!("> {}{}", bin_path.to_str().unwrap(), forwarded);
    176 192   let envs = HashMap::from([("PATH".to_string(), get_path_env(bin_dirs))]);
    177 193   run_command(
    178  - &[bin_path.to_str().unwrap(), &app_args.forwared],
     194 + &[bin_path.to_str().unwrap(), &forwarded],
    179 195   &RunOptions {
    180 196   current_dir: execute_dir,
    181 197   envs,
    skipped 10 lines
Please wait...
Page is in error, reload to recover