mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat(repl): add "--eval-file" flag to execute a script file on startup (#14247)
This commit adds support for "--eval-file" in "deno repl" subcommand. This flag can be used to pass paths or URLs to files, that will be executed on REPL startup. All files will be executed in the same context as the REPL (ie. as "plain old scripts", not ES modules), sharing the global scope. This feature allows to implement custom REPLs on top of Deno's REPL.
This commit is contained in:
parent
ae479b1036
commit
3833d37b15
4 changed files with 159 additions and 6 deletions
76
cli/flags.rs
76
cli/flags.rs
|
@ -144,6 +144,7 @@ pub struct LintFlags {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ReplFlags {
|
||||
pub eval_files: Option<Vec<String>>,
|
||||
pub eval: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -216,7 +217,10 @@ pub enum DenoSubcommand {
|
|||
|
||||
impl Default for DenoSubcommand {
|
||||
fn default() -> DenoSubcommand {
|
||||
DenoSubcommand::Repl(ReplFlags { eval: None })
|
||||
DenoSubcommand::Repl(ReplFlags {
|
||||
eval_files: None,
|
||||
eval: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -556,7 +560,13 @@ where
|
|||
Some(("uninstall", m)) => uninstall_parse(&mut flags, m),
|
||||
Some(("upgrade", m)) => upgrade_parse(&mut flags, m),
|
||||
Some(("vendor", m)) => vendor_parse(&mut flags, m),
|
||||
_ => handle_repl_flags(&mut flags, ReplFlags { eval: None }),
|
||||
_ => handle_repl_flags(
|
||||
&mut flags,
|
||||
ReplFlags {
|
||||
eval_files: None,
|
||||
eval: None,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
Ok(flags)
|
||||
|
@ -1360,6 +1370,16 @@ Ignore linting a file by adding an ignore comment at the top of the file:
|
|||
fn repl_subcommand<'a>() -> Command<'a> {
|
||||
runtime_args(Command::new("repl"), false, true)
|
||||
.about("Read Eval Print Loop")
|
||||
.arg(
|
||||
Arg::new("eval-file")
|
||||
.long("eval-file")
|
||||
.min_values(1)
|
||||
.takes_value(true)
|
||||
.use_value_delimiter(true)
|
||||
.require_equals(true)
|
||||
.help("Evaluates the provided file(s) as scripts when the REPL starts. Accepts file paths and URLs.")
|
||||
.value_hint(ValueHint::AnyPath),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("eval")
|
||||
.long("eval")
|
||||
|
@ -2446,9 +2466,15 @@ fn repl_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
|||
flags.type_check_mode = TypeCheckMode::None;
|
||||
runtime_args_parse(flags, matches, false, true);
|
||||
unsafely_ignore_certificate_errors_parse(flags, matches);
|
||||
|
||||
let eval_files: Option<Vec<String>> = matches
|
||||
.values_of("eval-file")
|
||||
.map(|values| values.map(String::from).collect());
|
||||
|
||||
handle_repl_flags(
|
||||
flags,
|
||||
ReplFlags {
|
||||
eval_files,
|
||||
eval: matches.value_of("eval").map(ToOwned::to_owned),
|
||||
},
|
||||
);
|
||||
|
@ -3884,7 +3910,10 @@ mod tests {
|
|||
r.unwrap(),
|
||||
Flags {
|
||||
repl: true,
|
||||
subcommand: DenoSubcommand::Repl(ReplFlags { eval: None }),
|
||||
subcommand: DenoSubcommand::Repl(ReplFlags {
|
||||
eval_files: None,
|
||||
eval: None
|
||||
}),
|
||||
allow_net: Some(vec![]),
|
||||
unsafely_ignore_certificate_errors: None,
|
||||
allow_env: Some(vec![]),
|
||||
|
@ -3906,7 +3935,10 @@ mod tests {
|
|||
r.unwrap(),
|
||||
Flags {
|
||||
repl: true,
|
||||
subcommand: DenoSubcommand::Repl(ReplFlags { eval: None }),
|
||||
subcommand: DenoSubcommand::Repl(ReplFlags {
|
||||
eval_files: None,
|
||||
eval: None
|
||||
}),
|
||||
import_map_path: Some("import_map.json".to_string()),
|
||||
no_remote: true,
|
||||
config_path: Some("tsconfig.json".to_string()),
|
||||
|
@ -3942,6 +3974,7 @@ mod tests {
|
|||
Flags {
|
||||
repl: true,
|
||||
subcommand: DenoSubcommand::Repl(ReplFlags {
|
||||
eval_files: None,
|
||||
eval: Some("console.log('hello');".to_string()),
|
||||
}),
|
||||
allow_net: Some(vec![]),
|
||||
|
@ -3957,6 +3990,35 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repl_with_eval_file_flag() {
|
||||
#[rustfmt::skip]
|
||||
let r = flags_from_vec(svec!["deno", "repl", "--eval-file=./a.js,./b.ts,https://examples.deno.land/hello-world.ts"]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
Flags {
|
||||
repl: true,
|
||||
subcommand: DenoSubcommand::Repl(ReplFlags {
|
||||
eval_files: Some(vec![
|
||||
"./a.js".to_string(),
|
||||
"./b.ts".to_string(),
|
||||
"https://examples.deno.land/hello-world.ts".to_string()
|
||||
]),
|
||||
eval: None,
|
||||
}),
|
||||
allow_net: Some(vec![]),
|
||||
allow_env: Some(vec![]),
|
||||
allow_run: Some(vec![]),
|
||||
allow_read: Some(vec![]),
|
||||
allow_write: Some(vec![]),
|
||||
allow_ffi: Some(vec![]),
|
||||
allow_hrtime: true,
|
||||
type_check_mode: TypeCheckMode::None,
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_read_allowlist() {
|
||||
use test_util::TempDir;
|
||||
|
@ -4590,6 +4652,7 @@ mod tests {
|
|||
Flags {
|
||||
repl: true,
|
||||
subcommand: DenoSubcommand::Repl(ReplFlags {
|
||||
eval_files: None,
|
||||
eval: Some("console.log('hello');".to_string()),
|
||||
}),
|
||||
unsafely_ignore_certificate_errors: Some(vec![]),
|
||||
|
@ -4663,7 +4726,10 @@ mod tests {
|
|||
r.unwrap(),
|
||||
Flags {
|
||||
repl: true,
|
||||
subcommand: DenoSubcommand::Repl(ReplFlags { eval: None }),
|
||||
subcommand: DenoSubcommand::Repl(ReplFlags {
|
||||
eval_files: None,
|
||||
eval: None
|
||||
}),
|
||||
unsafely_ignore_certificate_errors: Some(svec![
|
||||
"deno.land",
|
||||
"localhost",
|
||||
|
|
|
@ -930,7 +930,7 @@ async fn repl_command(
|
|||
}
|
||||
worker.run_event_loop(false).await?;
|
||||
|
||||
tools::repl::run(&ps, worker, repl_flags.eval).await
|
||||
tools::repl::run(&ps, worker, repl_flags.eval_files, repl_flags.eval).await
|
||||
}
|
||||
|
||||
async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
|
||||
|
|
|
@ -709,3 +709,55 @@ fn eval_flag_runtime_error() {
|
|||
assert!(out.contains("2500")); // should not prevent input
|
||||
assert!(err.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_file_flag_valid_input() {
|
||||
let (out, err) = util::run_and_collect_output_with_args(
|
||||
true,
|
||||
vec!["repl", "--eval-file=./001_hello.js"],
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
assert!(out.contains("Hello World"));
|
||||
assert!(err.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_file_flag_call_defined_function() {
|
||||
let (out, err) = util::run_and_collect_output_with_args(
|
||||
true,
|
||||
vec!["repl", "--eval-file=./tsc/d.ts"],
|
||||
Some(vec!["v4()"]),
|
||||
None,
|
||||
false,
|
||||
);
|
||||
assert!(out.contains("hello"));
|
||||
assert!(err.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_file_flag_http_input() {
|
||||
let (out, err) = util::run_and_collect_output_with_args(
|
||||
true,
|
||||
vec!["repl", "--eval-file=http://127.0.0.1:4545/tsc/d.ts"],
|
||||
Some(vec!["v4()"]),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(out.contains("hello"));
|
||||
assert!(err.contains("Download"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_file_flag_multiple_files() {
|
||||
let (out, err) = util::run_and_collect_output_with_args(
|
||||
true,
|
||||
vec!["repl", "--eval-file=http://127.0.0.1:4545/import_type.ts,./tsc/d.ts,http://127.0.0.1:4545/type_definitions/foo.js"],
|
||||
Some(vec!["b.method1=v4", "b.method1()+foo.toUpperCase()"]),
|
||||
None,
|
||||
true,
|
||||
);
|
||||
assert!(out.contains("helloFOO"));
|
||||
assert!(err.contains("Download"));
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use crate::proc_state::ProcState;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_runtime::permissions::Permissions;
|
||||
use deno_runtime::worker::MainWorker;
|
||||
use rustyline::error::ReadlineError;
|
||||
|
||||
|
@ -58,9 +59,24 @@ async fn read_line_and_poll(
|
|||
}
|
||||
}
|
||||
|
||||
async fn read_eval_file(
|
||||
ps: &ProcState,
|
||||
eval_file: &str,
|
||||
) -> Result<String, AnyError> {
|
||||
let specifier = deno_core::resolve_url_or_path(eval_file)?;
|
||||
|
||||
let file = ps
|
||||
.file_fetcher
|
||||
.fetch(&specifier, &mut Permissions::allow_all())
|
||||
.await?;
|
||||
|
||||
Ok((*file.source).clone())
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
ps: &ProcState,
|
||||
worker: MainWorker,
|
||||
maybe_eval_files: Option<Vec<String>>,
|
||||
maybe_eval: Option<String>,
|
||||
) -> Result<i32, AnyError> {
|
||||
let mut repl_session = ReplSession::initialize(worker).await?;
|
||||
|
@ -74,6 +90,25 @@ pub async fn run(
|
|||
let history_file_path = ps.dir.root.join("deno_history.txt");
|
||||
let editor = ReplEditor::new(helper, history_file_path);
|
||||
|
||||
if let Some(eval_files) = maybe_eval_files {
|
||||
for eval_file in eval_files {
|
||||
match read_eval_file(ps, &eval_file).await {
|
||||
Ok(eval_source) => {
|
||||
let output = repl_session
|
||||
.evaluate_line_and_get_output(&eval_source)
|
||||
.await?;
|
||||
// only output errors
|
||||
if let EvaluationOutput::Error(error_text) = output {
|
||||
println!("error in --eval-file file {}. {}", eval_file, error_text);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error in --eval-file file {}. {}", eval_file, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(eval) = maybe_eval {
|
||||
let output = repl_session.evaluate_line_and_get_output(&eval).await?;
|
||||
// only output errors
|
||||
|
|
Loading…
Reference in a new issue