1
0
Fork 0
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:
Naju Mancheril 2022-04-20 08:16:37 -04:00 committed by GitHub
parent ae479b1036
commit 3833d37b15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 159 additions and 6 deletions

View file

@ -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",

View file

@ -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> {

View file

@ -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"));
}

View file

@ -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