From cbbd9443592f79f6abf9e5019840de4e01ff8580 Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Wed, 8 Jul 2020 16:50:12 +0200 Subject: [PATCH] feat(cli): json option for "deno info" (#6372) --- cli/flags.rs | 41 +++++++- cli/main.rs | 163 +++++++++++++++++++------------ cli/tests/055_info_file_json.out | 25 +++++ cli/tests/info_json.out | 5 + cli/tests/integration_tests.rs | 11 +++ core/lib.rs | 1 + core/modules.rs | 60 ++++++------ 7 files changed, 213 insertions(+), 93 deletions(-) create mode 100644 cli/tests/055_info_file_json.out create mode 100644 cli/tests/info_json.out diff --git a/cli/flags.rs b/cli/flags.rs index eb98c60325..313d8ff1a5 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -41,6 +41,7 @@ pub enum DenoSubcommand { }, Help, Info { + json: bool, file: Option, }, Install { @@ -454,10 +455,11 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) { fn info_parse(flags: &mut Flags, matches: &clap::ArgMatches) { ca_file_arg_parse(flags, matches); unstable_arg_parse(flags, matches); + let json = matches.is_present("json"); no_check_arg_parse(flags, matches); - flags.subcommand = DenoSubcommand::Info { file: matches.value_of("file").map(|f| f.to_string()), + json, }; } @@ -824,6 +826,12 @@ TypeScript compiler cache: Subdirectory containing TS compiler output.", .arg(ca_file_arg()) .arg(no_check_arg()) .arg(unstable_arg()) + .arg( + Arg::with_name("json") + .long("json") + .help("Outputs the information in JSON format") + .takes_value(false), + ) } fn cache_subcommand<'a, 'b>() -> App<'a, 'b> { @@ -1784,6 +1792,19 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Info { + json: false, + file: Some("script.ts".to_string()), + }, + ..Flags::default() + } + ); + + let r = flags_from_vec_safe(svec!["deno", "info", "--json", "script.ts"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Info { + json: true, file: Some("script.ts".to_string()), }, ..Flags::default() @@ -1794,7 +1815,22 @@ mod tests { assert_eq!( r.unwrap(), Flags { - subcommand: DenoSubcommand::Info { file: None }, + subcommand: DenoSubcommand::Info { + json: false, + file: None + }, + ..Flags::default() + } + ); + + let r = flags_from_vec_safe(svec!["deno", "info", "--json"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Info { + json: true, + file: None + }, ..Flags::default() } ); @@ -2790,6 +2826,7 @@ mod tests { r.unwrap(), Flags { subcommand: DenoSubcommand::Info { + json: false, file: Some("https://example.com".to_string()), }, ca_file: Some("example.crt".to_owned()), diff --git a/cli/main.rs b/cli/main.rs index 5c54a87caf..5a40a0da2c 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -81,6 +81,7 @@ use crate::permissions::Permissions; use crate::tsc::TargetLib; use crate::worker::MainWorker; use deno_core::v8_set_flags; +use deno_core::Deps; use deno_core::ErrBox; use deno_core::EsIsolate; use deno_core::ModuleSpecifier; @@ -91,6 +92,7 @@ use futures::Future; use log::Level; use log::Metadata; use log::Record; +use state::exit_unstable; use std::env; use std::io::Read; use std::io::Write; @@ -140,22 +142,47 @@ fn write_to_stdout_ignore_sigpipe(bytes: &[u8]) -> Result<(), std::io::Error> { } } -fn print_cache_info(state: &GlobalState) { - println!( - "{} {:?}", - colors::bold("DENO_DIR location:"), - state.dir.root - ); - println!( - "{} {:?}", - colors::bold("Remote modules cache:"), - state.file_fetcher.http_cache.location - ); - println!( - "{} {:?}", - colors::bold("TypeScript compiler cache:"), - state.dir.gen_cache.location - ); +fn write_json_to_stdout(value: &T) -> Result<(), ErrBox> +where + T: ?Sized + serde::ser::Serialize, +{ + let writer = std::io::BufWriter::new(std::io::stdout()); + serde_json::to_writer_pretty(writer, value).map_err(ErrBox::from) +} + +fn print_cache_info(state: &GlobalState, json: bool) -> Result<(), ErrBox> { + let deno_dir = &state.dir.root; + let modules_cache = &state.file_fetcher.http_cache.location; + let typescript_cache = &state.dir.gen_cache.location; + if json { + let output = json!({ + "denoDir": deno_dir, + "modulesCache": modules_cache, + "typescriptCache": typescript_cache, + }); + write_json_to_stdout(&output) + } else { + println!("{} {:?}", colors::bold("DENO_DIR location:"), deno_dir); + println!( + "{} {:?}", + colors::bold("Remote modules cache:"), + modules_cache + ); + println!( + "{} {:?}", + colors::bold("TypeScript compiler cache:"), + typescript_cache + ); + Ok(()) + } +} + +struct FileInfoOutput<'a> { + local: &'a str, + file_type: &'a str, + compiled: Option, + map: Option, + deps: Option, } // TODO(bartlomieju): this function de facto repeats @@ -163,6 +190,7 @@ fn print_cache_info(state: &GlobalState) { async fn print_file_info( worker: &MainWorker, module_specifier: ModuleSpecifier, + json: bool, ) -> Result<(), ErrBox> { let global_state = worker.state.borrow().global_state.clone(); @@ -171,17 +199,13 @@ async fn print_file_info( .fetch_source_file(&module_specifier, None, Permissions::allow_all()) .await?; - println!( - "{} {}", - colors::bold("local:"), - out.filename.to_str().unwrap() - ); - - println!( - "{} {}", - colors::bold("type:"), - msg::enum_name_media_type(out.media_type) - ); + let mut output = FileInfoOutput { + local: out.filename.to_str().unwrap(), + file_type: msg::enum_name_media_type(out.media_type), + compiled: None, + map: None, + deps: None, + }; let module_specifier_ = module_specifier.clone(); @@ -208,12 +232,8 @@ async fn print_file_info( .ts_compiler .get_compiled_source_file(&out.url) .unwrap(); - - println!( - "{} {}", - colors::bold("compiled:"), - compiled_source_file.filename.to_str().unwrap(), - ); + output.compiled = + compiled_source_file.filename.to_str().map(|s| s.to_owned()); } if let Ok(source_map) = global_state @@ -221,31 +241,48 @@ async fn print_file_info( .ts_compiler .get_source_map_file(&module_specifier) { - println!( - "{} {}", - colors::bold("map:"), - source_map.filename.to_str().unwrap() - ); + output.map = source_map.filename.to_str().map(|s| s.to_owned()); } - let es_state_rc = EsIsolate::state(&worker.isolate); let es_state = es_state_rc.borrow(); if let Some(deps) = es_state.modules.deps(&module_specifier) { - println!("{}{}", colors::bold("deps:\n"), deps.name); - if let Some(ref depsdeps) = deps.deps { - for d in depsdeps { - println!("{}", d); - } - } - } else { - println!( - "{} cannot retrieve full dependency graph", - colors::bold("deps:"), - ); + output.deps = Some(deps); } - Ok(()) + if json { + let output = json!({ + "local": output.local, + "fileType": output.file_type, + "compiled": output.compiled, + "map": output.map, + "deps": output.deps.map(|x| x.to_json()) + }); + write_json_to_stdout(&output) + } else { + println!("{} {}", colors::bold("local:"), output.local); + println!("{} {}", colors::bold("type:"), output.file_type); + if let Some(compiled) = output.compiled { + println!("{} {}", colors::bold("compiled:"), compiled); + } + if let Some(map) = output.map { + println!("{} {}", colors::bold("map:"), map); + } + if let Some(deps) = output.deps { + println!("{}{}", colors::bold("deps:\n"), deps.name); + if let Some(ref depsdeps) = deps.deps { + for d in depsdeps { + println!("{}", d); + } + } + } else { + println!( + "{} cannot retrieve full dependency graph", + colors::bold("deps:"), + ); + } + Ok(()) + } } fn get_types(unstable: bool) -> String { @@ -270,18 +307,21 @@ fn get_types(unstable: bool) -> String { async fn info_command( flags: Flags, file: Option, + json: bool, ) -> Result<(), ErrBox> { + if json && !flags.unstable { + exit_unstable("--json"); + } let global_state = GlobalState::new(flags)?; // If it was just "deno info" print location of caches and exit if file.is_none() { - print_cache_info(&global_state); - return Ok(()); + print_cache_info(&global_state, json) + } else { + let main_module = ModuleSpecifier::resolve_url_or_path(&file.unwrap())?; + let mut worker = MainWorker::create(global_state, main_module.clone())?; + worker.preload_module(&main_module).await?; + print_file_info(&worker, main_module.clone(), json).await } - - let main_module = ModuleSpecifier::resolve_url_or_path(&file.unwrap())?; - let mut worker = MainWorker::create(global_state, main_module.clone())?; - worker.preload_module(&main_module).await?; - print_file_info(&worker, main_module.clone()).await } async fn install_command( @@ -525,8 +565,7 @@ async fn doc_command( }; if json { - let writer = std::io::BufWriter::new(std::io::stdout()); - serde_json::to_writer_pretty(writer, &doc_nodes).map_err(ErrBox::from) + write_json_to_stdout(&doc_nodes) } else { let details = if let Some(filter) = maybe_filter { let nodes = @@ -691,7 +730,9 @@ pub fn main() { DenoSubcommand::Fmt { check, files } => { fmt::format(files, check).boxed_local() } - DenoSubcommand::Info { file } => info_command(flags, file).boxed_local(), + DenoSubcommand::Info { file, json } => { + info_command(flags, file, json).boxed_local() + } DenoSubcommand::Install { module_url, args, diff --git a/cli/tests/055_info_file_json.out b/cli/tests/055_info_file_json.out new file mode 100644 index 0000000000..20bf94f3f5 --- /dev/null +++ b/cli/tests/055_info_file_json.out @@ -0,0 +1,25 @@ +{ + "local": "[WILDCARD]005_more_imports.ts", + "fileType": "TypeScript", + "compiled": "[WILDCARD]005_more_imports.ts.js", + "map": null, + "deps": [ + "file://[WILDCARD]/005_more_imports.ts", + [ + [ + "file://[WILDCARD]/subdir/mod1.ts", + [ + [ + "file://[WILDCARD]/subdir/subdir2/mod2.ts", + [ + [ + "file://[WILDCARD]/subdir/print_hello.ts", + [] + ] + ] + ] + ] + ] + ] + ] +} \ No newline at end of file diff --git a/cli/tests/info_json.out b/cli/tests/info_json.out new file mode 100644 index 0000000000..361728a7b3 --- /dev/null +++ b/cli/tests/info_json.out @@ -0,0 +1,5 @@ +{ + "denoDir": "[WILDCARD]", + "modulesCache": "[WILDCARD]deps", + "typescriptCache": "[WILDCARD]gen" +} \ No newline at end of file diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 9acb4ed3f0..87a6f9d63c 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1499,6 +1499,11 @@ itest!(_041_info_flag { output: "041_info_flag.out", }); +itest!(info_json { + args: "info --json --unstable", + output: "info_json.out", +}); + itest!(_042_dyn_import_evalcontext { args: "run --quiet --allow-read --reload 042_dyn_import_evalcontext.ts", output: "042_dyn_import_evalcontext.ts.out", @@ -1552,6 +1557,12 @@ itest!(_054_info_local_imports { exit_code: 0, }); +itest!(_055_info_file_json { + args: "info --quiet --json --unstable 005_more_imports.ts", + output: "055_info_file_json.out", + exit_code: 0, +}); + itest!(_056_make_temp_file_write_perm { args: "run --quiet --allow-read --allow-write=./subdir/ 056_make_temp_file_write_perm.ts", diff --git a/core/lib.rs b/core/lib.rs index 47ded645e7..7358af1c43 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -36,6 +36,7 @@ pub use crate::es_isolate::EsIsolateState; pub use crate::flags::v8_set_flags; pub use crate::module_specifier::ModuleResolutionError; pub use crate::module_specifier::ModuleSpecifier; +pub use crate::modules::Deps; pub use crate::modules::ModuleId; pub use crate::modules::ModuleLoadId; pub use crate::modules::ModuleLoader; diff --git a/core/modules.rs b/core/modules.rs index ca850d0bb0..58ad767b9d 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -484,20 +484,14 @@ impl Deps { } } - pub fn to_json(&self) -> String { - let mut children = "[".to_string(); - - if let Some(ref deps) = self.deps { - for d in deps { - children.push_str(&d.to_json()); - if !d.is_last { - children.push_str(","); - } - } + pub fn to_json(&self) -> serde_json::Value { + let children; + if let Some(deps) = &self.deps { + children = deps.iter().map(|c| c.to_json()).collect(); + } else { + children = Vec::new() } - children.push_str("]"); - - format!("[\"{}\",{}]", self.name, children) + serde_json::json!([&self.name, children]) } } @@ -1056,6 +1050,29 @@ mod tests { assert!(modules.deps(&specifier).is_none()); } + #[test] + fn deps_to_json() { + fn dep(name: &str, deps: Option>) -> Deps { + Deps { + name: name.to_string(), + deps, + prefix: "".to_string(), + is_last: false, + } + } + let deps = dep( + "a", + Some(vec![ + dep("b", Some(vec![dep("b2", None)])), + dep("c", Some(vec![])), + ]), + ); + assert_eq!( + serde_json::json!(["a", [["b", [["b2", []]]], ["c", []]]]), + deps.to_json() + ); + } + /* TODO(bartlomieju): reenable #[test] fn deps() { @@ -1076,22 +1093,5 @@ mod tests { assert_eq!(bar_deps.deps, Some(vec![])); } - #[test] - fn test_deps_to_json() { - let mut modules = Modules::new(); - modules.register(1, "foo"); - modules.register(2, "bar"); - modules.register(3, "baz"); - modules.register(4, "zuh"); - modules.add_child(1, "bar"); - modules.add_child(1, "baz"); - modules.add_child(3, "zuh"); - let maybe_deps = modules.deps("foo"); - assert!(maybe_deps.is_some()); - assert_eq!( - "[\"foo\",[[\"bar\",[]],[\"baz\",[[\"zuh\",[]]]]]]", - maybe_deps.unwrap().to_json() - ); - } */ }