summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/args/flags.rs123
-rw-r--r--cli/main.rs2
-rw-r--r--cli/tests/integration/doc_tests.rs35
-rw-r--r--cli/tools/doc.rs220
4 files changed, 301 insertions, 79 deletions
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index b3fe61abd..dbc868efa 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -106,10 +106,17 @@ impl Default for DocSourceFileFlag {
}
#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct DocHtmlFlag {
+ pub name: String,
+ pub output: PathBuf,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DocFlags {
pub private: bool,
pub json: bool,
pub lint: bool,
+ pub html: Option<DocHtmlFlag>,
pub source_files: DocSourceFileFlag,
pub filter: Option<String>,
}
@@ -1325,6 +1332,12 @@ Output documentation to standard output:
deno doc ./path/to/module.ts
+Output documentation in HTML format:
+
+ deno doc --html --name=\"My library\" ./path/to/module.ts
+ deno doc --html --name=\"My library\" ./main.ts ./dev.ts
+ deno doc --html --name=\"My library\" --output=./documentation/ ./path/to/module.ts
+
Output private documentation to standard output:
deno doc --private ./path/to/module.ts
@@ -1361,6 +1374,30 @@ Show documentation for runtime built-ins:
.action(ArgAction::SetTrue),
)
.arg(
+ Arg::new("html")
+ .long("html")
+ .help("Output documentation in HTML format")
+ .action(ArgAction::SetTrue)
+ .conflicts_with("json")
+ )
+ .arg(
+ Arg::new("name")
+ .long("name")
+ .help("The name that will be displayed in the docs")
+ .action(ArgAction::Set)
+ .required_if_eq("html", "true")
+ .require_equals(true)
+ )
+ .arg(
+ Arg::new("output")
+ .long("output")
+ .help("Directory for HTML documentation output")
+ .action(ArgAction::Set)
+ .require_equals(true)
+ .value_hint(ValueHint::DirPath)
+ .value_parser(value_parser!(PathBuf))
+ )
+ .arg(
Arg::new("private")
.long("private")
.help("Output private documentation")
@@ -1372,7 +1409,8 @@ Show documentation for runtime built-ins:
.help("Dot separated path to symbol")
.required(false)
.conflicts_with("json")
- .conflicts_with("lint"),
+ .conflicts_with("lint")
+ .conflicts_with("html"),
)
.arg(
Arg::new("lint")
@@ -3180,10 +3218,21 @@ fn doc_parse(flags: &mut Flags, matches: &mut ArgMatches) {
let lint = matches.get_flag("lint");
let json = matches.get_flag("json");
let filter = matches.remove_one::<String>("filter");
+ let html = if matches.get_flag("html") {
+ let name = matches.remove_one::<String>("name").unwrap();
+ let output = matches
+ .remove_one::<PathBuf>("output")
+ .unwrap_or(PathBuf::from("./docs/"));
+ Some(DocHtmlFlag { name, output })
+ } else {
+ None
+ };
+
flags.subcommand = DenoSubcommand::Doc(DocFlags {
source_files,
json,
lint,
+ html,
filter,
private,
});
@@ -6085,6 +6134,7 @@ mod tests {
source_files: DocSourceFileFlag::Paths(vec!["script.ts".to_owned()]),
private: false,
json: false,
+ html: None,
lint: false,
filter: None,
}),
@@ -7374,10 +7424,64 @@ mod tests {
subcommand: DenoSubcommand::Doc(DocFlags {
private: false,
json: true,
+ html: None,
lint: false,
- source_files: DocSourceFileFlag::Paths(vec![
- "path/to/module.ts".to_string()
- ]),
+ source_files: DocSourceFileFlag::Paths(svec!["path/to/module.ts"]),
+ filter: None,
+ }),
+ ..Flags::default()
+ }
+ );
+
+ let r = flags_from_vec(svec!["deno", "doc", "--html", "path/to/module.ts"]);
+ assert!(r.is_err());
+
+ let r = flags_from_vec(svec![
+ "deno",
+ "doc",
+ "--html",
+ "--name=My library",
+ "path/to/module.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Doc(DocFlags {
+ private: false,
+ json: false,
+ lint: false,
+ html: Some(DocHtmlFlag {
+ name: "My library".to_string(),
+ output: PathBuf::from("./docs/"),
+ }),
+ source_files: DocSourceFileFlag::Paths(svec!["path/to/module.ts"]),
+ filter: None,
+ }),
+ ..Flags::default()
+ }
+ );
+
+ let r = flags_from_vec(svec![
+ "deno",
+ "doc",
+ "--html",
+ "--name=My library",
+ "--lint",
+ "--output=./foo",
+ "path/to/module.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Doc(DocFlags {
+ private: false,
+ json: false,
+ html: Some(DocHtmlFlag {
+ name: "My library".to_string(),
+ output: PathBuf::from("./foo"),
+ }),
+ lint: true,
+ source_files: DocSourceFileFlag::Paths(svec!["path/to/module.ts"]),
filter: None,
}),
..Flags::default()
@@ -7397,6 +7501,7 @@ mod tests {
subcommand: DenoSubcommand::Doc(DocFlags {
private: false,
json: false,
+ html: None,
lint: false,
source_files: DocSourceFileFlag::Paths(vec![
"path/to/module.ts".to_string()
@@ -7414,6 +7519,7 @@ mod tests {
subcommand: DenoSubcommand::Doc(DocFlags {
private: false,
json: false,
+ html: None,
lint: false,
source_files: Default::default(),
filter: None,
@@ -7436,6 +7542,7 @@ mod tests {
private: false,
lint: false,
json: false,
+ html: None,
source_files: DocSourceFileFlag::Builtin,
filter: Some("Deno.Listener".to_string()),
}),
@@ -7458,9 +7565,8 @@ mod tests {
private: true,
lint: false,
json: false,
- source_files: DocSourceFileFlag::Paths(vec![
- "path/to/module.js".to_string()
- ]),
+ html: None,
+ source_files: DocSourceFileFlag::Paths(svec!["path/to/module.js"]),
filter: None,
}),
no_npm: true,
@@ -7482,6 +7588,7 @@ mod tests {
private: false,
lint: false,
json: false,
+ html: None,
source_files: DocSourceFileFlag::Paths(vec![
"path/to/module.js".to_string(),
"path/to/module2.js".to_string()
@@ -7505,6 +7612,7 @@ mod tests {
subcommand: DenoSubcommand::Doc(DocFlags {
private: false,
json: false,
+ html: None,
lint: false,
source_files: DocSourceFileFlag::Paths(vec![
"path/to/module.js".to_string(),
@@ -7530,6 +7638,7 @@ mod tests {
private: false,
lint: true,
json: false,
+ html: None,
source_files: DocSourceFileFlag::Paths(vec![
"path/to/module.js".to_string(),
"path/to/module2.js".to_string()
diff --git a/cli/main.rs b/cli/main.rs
index dbd3b470b..7a8647a81 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -95,7 +95,7 @@ async fn run_subcommand(flags: Flags) -> Result<i32, AnyError> {
tools::bundle::bundle(flags, bundle_flags).await
}),
DenoSubcommand::Doc(doc_flags) => {
- spawn_subcommand(async { tools::doc::print_docs(flags, doc_flags).await })
+ spawn_subcommand(async { tools::doc::doc(flags, doc_flags).await })
}
DenoSubcommand::Eval(eval_flags) => spawn_subcommand(async {
tools::run::eval_command(flags, eval_flags).await
diff --git a/cli/tests/integration/doc_tests.rs b/cli/tests/integration/doc_tests.rs
index a16f99dd9..f5ac44a02 100644
--- a/cli/tests/integration/doc_tests.rs
+++ b/cli/tests/integration/doc_tests.rs
@@ -64,6 +64,12 @@ itest!(deno_doc_lint_referenced_private_types_fixed {
output: "doc/referenced_private_types_fixed.out",
});
+itest!(deno_doc_html_lint_referenced_private_types_fixed {
+ args: "doc --lint --html --name=Library doc/referenced_private_types.ts",
+ exit_code: 1,
+ output: "doc/referenced_private_types_lint.out",
+});
+
itest!(_060_deno_doc_displays_all_overloads_in_details_view {
args:
"doc --filter NS.test doc/060_deno_doc_displays_all_overloads_in_details_view.ts",
@@ -96,3 +102,32 @@ itest!(doc_no_lock {
cwd: Some("lockfile/basic"),
output: "lockfile/basic/doc.nolock.out",
});
+
+#[test]
+fn deno_doc_html() {
+ let context = TestContext::default();
+ let temp_dir = context.temp_dir();
+ let output = context
+ .new_command()
+ .env("NO_COLOR", "1")
+ .args_vec(vec![
+ "doc",
+ "--html",
+ "--name=MyLib",
+ &format!("--output={}", temp_dir.path().to_string_lossy()),
+ "doc/referenced_private_types_fixed.ts",
+ ])
+ .split_output()
+ .run();
+
+ output.assert_exit_code(0);
+ assert_contains!(output.stderr(), "Written 8 files to");
+ assert!(temp_dir.path().join("index.html").exists());
+ assert!(temp_dir.path().join("compound_index.html").exists());
+ assert!(temp_dir.path().join("fuse.js").exists());
+ assert!(temp_dir.path().join("search.js").exists());
+ assert!(temp_dir.path().join("search_index.js").exists());
+ assert!(temp_dir.path().join("styles.css").exists());
+ assert!(temp_dir.path().join("MyInterface.html").exists());
+ assert!(temp_dir.path().join("MyClass.html").exists());
+}
diff --git a/cli/tools/doc.rs b/cli/tools/doc.rs
index 4028c412f..9c88c8e84 100644
--- a/cli/tools/doc.rs
+++ b/cli/tools/doc.rs
@@ -1,8 +1,8 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-use std::collections::BTreeMap;
-
+use crate::args::CliOptions;
use crate::args::DocFlags;
+use crate::args::DocHtmlFlag;
use crate::args::DocSourceFileFlag;
use crate::args::Flags;
use crate::colors;
@@ -12,21 +12,69 @@ use crate::factory::CliFactory;
use crate::graph_util::graph_lock_or_exit;
use crate::graph_util::CreateGraphOptions;
use crate::tsc::get_types_declaration_file_text;
+use crate::util::glob::expand_globs;
use deno_core::anyhow::bail;
+use deno_core::anyhow::Context;
use deno_core::error::AnyError;
+use deno_core::futures::FutureExt;
use deno_core::resolve_url_or_path;
use deno_doc as doc;
use deno_graph::CapturingModuleParser;
use deno_graph::DefaultParsedSourceStore;
use deno_graph::GraphKind;
+use deno_graph::ModuleAnalyzer;
use deno_graph::ModuleSpecifier;
use doc::DocDiagnostic;
use indexmap::IndexMap;
+use std::collections::BTreeMap;
+use std::path::PathBuf;
+use std::sync::Arc;
-pub async fn print_docs(
- flags: Flags,
+async fn generate_doc_nodes_for_builtin_types(
doc_flags: DocFlags,
-) -> Result<(), AnyError> {
+ cli_options: &Arc<CliOptions>,
+ capturing_parser: CapturingModuleParser<'_>,
+ analyzer: &dyn ModuleAnalyzer,
+) -> Result<IndexMap<ModuleSpecifier, Vec<doc::DocNode>>, AnyError> {
+ let source_file_specifier =
+ ModuleSpecifier::parse("internal://lib.deno.d.ts").unwrap();
+ let content = get_types_declaration_file_text(cli_options.unstable());
+ let mut loader = deno_graph::source::MemoryLoader::new(
+ vec![(
+ source_file_specifier.to_string(),
+ deno_graph::source::Source::Module {
+ specifier: source_file_specifier.to_string(),
+ content,
+ maybe_headers: None,
+ },
+ )],
+ Vec::new(),
+ );
+ let mut graph = deno_graph::ModuleGraph::new(GraphKind::TypesOnly);
+ graph
+ .build(
+ vec![source_file_specifier.clone()],
+ &mut loader,
+ deno_graph::BuildOptions {
+ module_analyzer: Some(analyzer),
+ ..Default::default()
+ },
+ )
+ .await;
+ let doc_parser = doc::DocParser::new(
+ &graph,
+ capturing_parser,
+ doc::DocParserOptions {
+ diagnostics: false,
+ private: doc_flags.private,
+ },
+ )?;
+ let nodes = doc_parser.parse_module(&source_file_specifier)?.definitions;
+
+ Ok(IndexMap::from([(source_file_specifier, nodes)]))
+}
+
+pub async fn doc(flags: Flags, doc_flags: DocFlags) -> Result<(), AnyError> {
let factory = CliFactory::from_flags(flags).await?;
let cli_options = factory.cli_options();
let module_info_cache = factory.module_info_cache()?;
@@ -37,52 +85,30 @@ pub async fn print_docs(
let capturing_parser =
CapturingModuleParser::new(Some(&source_parser), &store);
- let mut doc_nodes = match doc_flags.source_files {
+ let doc_nodes_by_url = match doc_flags.source_files {
DocSourceFileFlag::Builtin => {
- let source_file_specifier =
- ModuleSpecifier::parse("internal://lib.deno.d.ts").unwrap();
- let content = get_types_declaration_file_text(cli_options.unstable());
- let mut loader = deno_graph::source::MemoryLoader::new(
- vec![(
- source_file_specifier.to_string(),
- deno_graph::source::Source::Module {
- specifier: source_file_specifier.to_string(),
- content,
- maybe_headers: None,
- },
- )],
- Vec::new(),
- );
- let mut graph = deno_graph::ModuleGraph::new(GraphKind::TypesOnly);
- graph
- .build(
- vec![source_file_specifier.clone()],
- &mut loader,
- deno_graph::BuildOptions {
- module_analyzer: Some(&analyzer),
- ..Default::default()
- },
- )
- .await;
- let doc_parser = doc::DocParser::new(
- &graph,
+ generate_doc_nodes_for_builtin_types(
+ doc_flags.clone(),
+ cli_options,
capturing_parser,
- doc::DocParserOptions {
- private: doc_flags.private,
- diagnostics: false,
- },
- )?;
- doc_parser.parse_module(&source_file_specifier)?.definitions
+ &analyzer,
+ )
+ .await?
}
- DocSourceFileFlag::Paths(source_files) => {
+ DocSourceFileFlag::Paths(ref source_files) => {
let module_graph_builder = factory.module_graph_builder().await?;
let maybe_lockfile = factory.maybe_lockfile();
+ let expanded_globs =
+ expand_globs(source_files.iter().map(PathBuf::from).collect())?;
let module_specifiers: Result<Vec<ModuleSpecifier>, AnyError> =
- source_files
+ expanded_globs
.iter()
.map(|source_file| {
- Ok(resolve_url_or_path(source_file, cli_options.initial_cwd())?)
+ Ok(resolve_url_or_path(
+ &source_file.to_string_lossy(),
+ cli_options.initial_cwd(),
+ )?)
})
.collect();
let module_specifiers = module_specifiers?;
@@ -109,11 +135,12 @@ pub async fn print_docs(
},
)?;
- let mut doc_nodes = vec![];
+ let mut doc_nodes_by_url =
+ IndexMap::with_capacity(module_specifiers.len());
- for module_specifier in module_specifiers {
- let nodes = doc_parser.parse_with_reexports(&module_specifier)?;
- doc_nodes.extend_from_slice(&nodes);
+ for module_specifier in &module_specifiers {
+ let nodes = doc_parser.parse_with_reexports(module_specifier)?;
+ doc_nodes_by_url.insert(module_specifier.clone(), nodes);
}
if doc_flags.lint {
@@ -121,37 +148,88 @@ pub async fn print_docs(
check_diagnostics(&diagnostics)?;
}
- doc_nodes
+ doc_nodes_by_url
}
};
- if doc_flags.json {
- write_json_to_stdout(&doc_nodes)
+ if let Some(html_options) = doc_flags.html {
+ generate_docs_directory(&doc_nodes_by_url, html_options)
+ .boxed_local()
+ .await
} else {
- doc_nodes.retain(|doc_node| doc_node.kind != doc::DocNodeKind::Import);
- let details = if let Some(filter) = doc_flags.filter {
- let nodes =
- doc::find_nodes_by_name_recursively(doc_nodes, filter.clone());
- if nodes.is_empty() {
- bail!("Node {} was not found!", filter);
- }
- format!(
- "{}",
- doc::DocPrinter::new(&nodes, colors::use_color(), doc_flags.private)
- )
- } else {
- format!(
- "{}",
- doc::DocPrinter::new(
- &doc_nodes,
- colors::use_color(),
- doc_flags.private
- )
- )
- };
+ let doc_nodes: Vec<doc::DocNode> =
+ doc_nodes_by_url.values().flatten().cloned().collect();
+ print_docs(doc_flags, doc_nodes)
+ }
+}
+
+async fn generate_docs_directory(
+ doc_nodes_by_url: &IndexMap<ModuleSpecifier, Vec<doc::DocNode>>,
+ html_options: DocHtmlFlag,
+) -> Result<(), AnyError> {
+ let cwd = std::env::current_dir().context("Failed to get CWD")?;
+ let output_dir_resolved = cwd.join(&html_options.output);
+
+ let options = deno_doc::html::GenerateOptions {
+ package_name: html_options.name,
+ };
- write_to_stdout_ignore_sigpipe(details.as_bytes()).map_err(AnyError::from)
+ let files = deno_doc::html::generate(options, doc_nodes_by_url)
+ .context("Failed to generate HTML documentation")?;
+
+ let path = &output_dir_resolved;
+ let _ = std::fs::remove_dir_all(path);
+ std::fs::create_dir(path)
+ .with_context(|| format!("Failed to create directory {:?}", path))?;
+
+ let no_of_files = files.len();
+ for (name, content) in files {
+ let this_path = path.join(name);
+ let prefix = this_path.parent().with_context(|| {
+ format!("Failed to get parent path for {:?}", this_path)
+ })?;
+ std::fs::create_dir_all(prefix)
+ .with_context(|| format!("Failed to create directory {:?}", prefix))?;
+ std::fs::write(&this_path, content)
+ .with_context(|| format!("Failed to write file {:?}", this_path))?;
}
+
+ log::info!(
+ "{}",
+ colors::green(format!(
+ "Written {} files to {:?}",
+ no_of_files, html_options.output
+ ))
+ );
+ Ok(())
+}
+
+fn print_docs(
+ doc_flags: DocFlags,
+ mut doc_nodes: Vec<deno_doc::DocNode>,
+) -> Result<(), AnyError> {
+ if doc_flags.json {
+ return write_json_to_stdout(&doc_nodes);
+ }
+
+ doc_nodes.retain(|doc_node| doc_node.kind != doc::DocNodeKind::Import);
+ let details = if let Some(filter) = doc_flags.filter {
+ let nodes = doc::find_nodes_by_name_recursively(doc_nodes, filter.clone());
+ if nodes.is_empty() {
+ bail!("Node {} was not found!", filter);
+ }
+ format!(
+ "{}",
+ doc::DocPrinter::new(&nodes, colors::use_color(), doc_flags.private)
+ )
+ } else {
+ format!(
+ "{}",
+ doc::DocPrinter::new(&doc_nodes, colors::use_color(), doc_flags.private)
+ )
+ };
+
+ write_to_stdout_ignore_sigpipe(details.as_bytes()).map_err(AnyError::from)
}
fn check_diagnostics(diagnostics: &[DocDiagnostic]) -> Result<(), AnyError> {