diff --git a/extensions/scarb-doc/src/lib.rs b/extensions/scarb-doc/src/lib.rs index eb14edebf..9f8eb467d 100644 --- a/extensions/scarb-doc/src/lib.rs +++ b/extensions/scarb-doc/src/lib.rs @@ -1,5 +1,3 @@ -use anyhow::Result; - use cairo_lang_compiler::project::ProjectConfig; use cairo_lang_filesystem::db::FilesGroup; use cairo_lang_filesystem::ids::CrateLongId; @@ -10,14 +8,15 @@ use types::Crate; pub mod compilation; pub mod db; pub mod types; +pub mod versioned_json_output; pub fn generate_language_elements_tree_for_package( package_name: String, project_config: ProjectConfig, -) -> Result { +) -> Crate { let db = ScarbDocDatabase::new(Some(project_config)); let main_crate_id = db.intern_crate(CrateLongId::Real(package_name.into())); - Ok(Crate::new(&db, main_crate_id)) + Crate::new(&db, main_crate_id) } diff --git a/extensions/scarb-doc/src/main.rs b/extensions/scarb-doc/src/main.rs index d6eb45f4c..8dde04637 100644 --- a/extensions/scarb-doc/src/main.rs +++ b/extensions/scarb-doc/src/main.rs @@ -1,30 +1,90 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Parser; use scarb_doc::compilation::get_project_config; +use std::collections::BTreeMap; +use std::fs; use scarb_metadata::MetadataCommand; use scarb_ui::args::PackagesFilter; use scarb_doc::generate_language_elements_tree_for_package; +use scarb_doc::versioned_json_output::VersionedJsonOutput; + +#[derive(Default, Debug, Clone, clap::ValueEnum)] +enum OutputFormat { + /// Generates documentation in Markdown format. + #[default] + Markdown, + /// Saves information collected from packages in JSON format instead of generating + /// documentation. + /// This may be useful if you want to generate documentation files by yourself. + /// The precise output structure is not guaranteed to be stable. + Json, +} #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { #[command(flatten)] packages_filter: PackagesFilter, + + /// Specifies a format of generated files. + #[arg(long, value_enum, default_value_t)] + output_format: OutputFormat, } -fn main() -> Result<()> { +fn main_inner() -> Result<()> { let args = Args::parse(); - let metadata = MetadataCommand::new().inherit_stderr().exec()?; + let metadata = MetadataCommand::new() + .inherit_stderr() + .exec() + .context("metadata command failed")?; let metadata_for_packages = args.packages_filter.match_many(&metadata)?; + let mut package_information_map = BTreeMap::new(); + for package_metadata in metadata_for_packages { let project_config = get_project_config(&metadata, &package_metadata); - let _crate_ = - generate_language_elements_tree_for_package(package_metadata.name, project_config)?; + let crate_ = generate_language_elements_tree_for_package( + package_metadata.name.clone(), + project_config, + ); + + package_information_map.insert(package_metadata.name, crate_); + } + + let output_dir = metadata + .target_dir + .unwrap_or_else(|| metadata.workspace.root.join("target")) + .join("doc"); + + fs::create_dir_all(&output_dir).context("failed to create output directory for scarb doc")?; + + match args.output_format { + OutputFormat::Json => { + let versioned_json_output = VersionedJsonOutput::new(package_information_map); + let output = serde_json::to_string_pretty(&versioned_json_output) + .expect("failed to serialize information about crates") + + "\n"; + let output_path = output_dir.join("output.json"); + + fs::write(output_path, output) + .context("failed to write output of scarb doc to a file")?; + } + OutputFormat::Markdown => todo!("#1424"), } Ok(()) } + +fn main() { + match main_inner() { + Ok(()) => std::process::exit(0), + Err(error) => { + scarb_ui::Ui::new(scarb_ui::Verbosity::Normal, scarb_ui::OutputFormat::Text) + .error(error.to_string()); + std::process::exit(1); + } + } +} diff --git a/extensions/scarb-doc/src/versioned_json_output.rs b/extensions/scarb-doc/src/versioned_json_output.rs new file mode 100644 index 000000000..615e4f652 --- /dev/null +++ b/extensions/scarb-doc/src/versioned_json_output.rs @@ -0,0 +1,22 @@ +use crate::types::Crate; +use serde::Serialize; +use std::collections::BTreeMap; + +type PackageName = String; + +const FORMAT_VERSION: u8 = 1; + +#[derive(Serialize)] +pub struct VersionedJsonOutput { + format_version: u8, + pub package_information_map: BTreeMap, +} + +impl VersionedJsonOutput { + pub fn new(package_information_map: BTreeMap) -> Self { + Self { + format_version: FORMAT_VERSION, + package_information_map, + } + } +} diff --git a/extensions/scarb-doc/tests/data/integration_test_data.json b/extensions/scarb-doc/tests/data/integration_test_data.json deleted file mode 100644 index 39520be19..000000000 --- a/extensions/scarb-doc/tests/data/integration_test_data.json +++ /dev/null @@ -1,276 +0,0 @@ -{ - "root_module": { - "item_data": { - "name": "hello_world", - "doc": null, - "signature": null, - "full_path": "hello_world" - }, - "submodules": [ - { - "item_data": { - "name": "tests", - "doc": "Tests module\n", - "signature": null, - "full_path": "hello_world::tests" - }, - "submodules": [], - "constants": [], - "free_functions": [ - { - "item_data": { - "name": "it_works", - "doc": "Really\nworks.\n", - "signature": "fn it_works()", - "full_path": "hello_world::tests::it_works" - } - } - ], - "structs": [], - "enums": [], - "type_aliases": [], - "impl_aliases": [], - "traits": [], - "impls": [], - "extern_types": [], - "extern_functions": [] - } - ], - "constants": [ - { - "item_data": { - "name": "FOO", - "doc": "FOO constant with value 42\n", - "signature": "const FOO: u32 = 42;", - "full_path": "hello_world::FOO" - } - } - ], - "free_functions": [ - { - "item_data": { - "name": "main", - "doc": "Fibonacci sequence calculator\nMain function that calculates the 16th Fibonacci number\n", - "signature": "fn main() -> u32", - "full_path": "hello_world::main" - } - }, - { - "item_data": { - "name": "fib", - "doc": "Calculate the nth Fibonacci number\n\n# Arguments\n* `n` - The index of the Fibonacci number to calculate\n", - "signature": "fn fib(mut n: u32) -> u32", - "full_path": "hello_world::fib" - } - } - ], - "structs": [ - { - "members": [ - { - "item_data": { - "name": "radius", - "doc": "Radius of the circle", - "signature": null, - "full_path": "hello_world::Circle::radius" - } - } - ], - "item_data": { - "name": "Circle", - "doc": "Circle struct with radius field\n", - "signature": null, - "full_path": "hello_world::Circle" - } - } - ], - "enums": [ - { - "variants": [ - { - "item_data": { - "name": "Red", - "doc": "Red color", - "signature": null, - "full_path": "hello_world::Color::Red" - } - }, - { - "item_data": { - "name": "Green", - "doc": "Green color", - "signature": null, - "full_path": "hello_world::Color::Green" - } - }, - { - "item_data": { - "name": "Blue", - "doc": "Blue color", - "signature": null, - "full_path": "hello_world::Color::Blue" - } - } - ], - "item_data": { - "name": "Color", - "doc": "Color enum with Red, Green, and Blue variants\n", - "signature": null, - "full_path": "hello_world::Color" - } - } - ], - "type_aliases": [ - { - "item_data": { - "name": "Pair", - "doc": "Pair type alias for a tuple of two u32 values\n", - "signature": "type Pair = (u32, u32);", - "full_path": "hello_world::Pair" - } - } - ], - "impl_aliases": [], - "traits": [ - { - "trait_constants": [ - { - "item_data": { - "name": "SHAPE_CONST", - "doc": "Constant for the shape type\n", - "signature": null, - "full_path": "Shape::SHAPE_CONST" - } - } - ], - "trait_types": [ - { - "item_data": { - "name": "ShapePair", - "doc": "Type alias for a pair of shapes\n", - "signature": null, - "full_path": "Shape::ShapePair" - } - } - ], - "trait_functions": [ - { - "item_data": { - "name": "area", - "doc": "Calculate the area of the shape\n", - "signature": "fn area(self: T) -> u32", - "full_path": "Shape::area" - } - } - ], - "item_data": { - "name": "Shape", - "doc": "Shape trait for objects that have an area\n", - "signature": "trait Shape", - "full_path": "hello_world::Shape" - } - } - ], - "impls": [ - { - "impl_types": [ - { - "item_data": { - "name": "ShapePair", - "doc": "Type alias for a pair of circles\n", - "signature": "type ShapePair = (Circle, Circle);", - "full_path": "CircleShape::ShapePair" - } - } - ], - "impl_constants": [ - { - "item_data": { - "name": "SHAPE_CONST", - "doc": "Shape constant\n", - "signature": "const SHAPE_CONST = \"xyz\";", - "full_path": "CircleShape::SHAPE_CONST" - } - } - ], - "impl_functions": [ - { - "item_data": { - "name": "area", - "doc": "Implementation of the area method for Circle\n", - "signature": "fn area(self: Circle) -> u32", - "full_path": "hello_world::CircleShape::area" - } - } - ], - "item_data": { - "name": "CircleShape", - "doc": "Implementation of the Shape trait for Circle\n", - "signature": "impl CircleShape of Shape", - "full_path": "hello_world::CircleShape" - } - }, - { - "impl_types": [], - "impl_constants": [], - "impl_functions": [], - "item_data": { - "name": "CircleDrop", - "doc": null, - "signature": "impl CircleDrop of core::traits::Drop;", - "full_path": "hello_world::CircleDrop" - } - }, - { - "impl_types": [], - "impl_constants": [], - "impl_functions": [ - { - "item_data": { - "name": "serialize", - "doc": null, - "signature": "fn serialize(self: @Circle, ref output: core::array::Array)", - "full_path": "hello_world::CircleSerde::serialize" - } - }, - { - "item_data": { - "name": "deserialize", - "doc": null, - "signature": "fn deserialize(ref serialized: core::array::Span) -> core::option::Option", - "full_path": "hello_world::CircleSerde::deserialize" - } - } - ], - "item_data": { - "name": "CircleSerde", - "doc": null, - "signature": "impl CircleSerde of core::serde::Serde", - "full_path": "hello_world::CircleSerde" - } - }, - { - "impl_types": [], - "impl_constants": [], - "impl_functions": [ - { - "item_data": { - "name": "eq", - "doc": null, - "signature": "fn eq(lhs: @Circle, rhs: @Circle) -> bool", - "full_path": "hello_world::CirclePartialEq::eq" - } - } - ], - "item_data": { - "name": "CirclePartialEq", - "doc": null, - "signature": "impl CirclePartialEq of core::traits::PartialEq", - "full_path": "hello_world::CirclePartialEq" - } - } - ], - "extern_types": [], - "extern_functions": [] - } -} \ No newline at end of file diff --git a/extensions/scarb-doc/tests/data/json_output_test_data.json b/extensions/scarb-doc/tests/data/json_output_test_data.json new file mode 100644 index 000000000..c0f58e917 --- /dev/null +++ b/extensions/scarb-doc/tests/data/json_output_test_data.json @@ -0,0 +1,281 @@ +{ + "format_version": 1, + "package_information_map": { + "hello_world": { + "root_module": { + "item_data": { + "name": "hello_world", + "doc": null, + "signature": null, + "full_path": "hello_world" + }, + "submodules": [ + { + "item_data": { + "name": "tests", + "doc": "Tests module\n", + "signature": null, + "full_path": "hello_world::tests" + }, + "submodules": [], + "constants": [], + "free_functions": [ + { + "item_data": { + "name": "it_works", + "doc": "Really\nworks.\n", + "signature": "fn it_works()", + "full_path": "hello_world::tests::it_works" + } + } + ], + "structs": [], + "enums": [], + "type_aliases": [], + "impl_aliases": [], + "traits": [], + "impls": [], + "extern_types": [], + "extern_functions": [] + } + ], + "constants": [ + { + "item_data": { + "name": "FOO", + "doc": "FOO constant with value 42\n", + "signature": "const FOO: u32 = 42;", + "full_path": "hello_world::FOO" + } + } + ], + "free_functions": [ + { + "item_data": { + "name": "main", + "doc": "Fibonacci sequence calculator\nMain function that calculates the 16th Fibonacci number\n", + "signature": "fn main() -> u32", + "full_path": "hello_world::main" + } + }, + { + "item_data": { + "name": "fib", + "doc": "Calculate the nth Fibonacci number\n\n# Arguments\n* `n` - The index of the Fibonacci number to calculate\n", + "signature": "fn fib(mut n: u32) -> u32", + "full_path": "hello_world::fib" + } + } + ], + "structs": [ + { + "members": [ + { + "item_data": { + "name": "radius", + "doc": "Radius of the circle", + "signature": null, + "full_path": "hello_world::Circle::radius" + } + } + ], + "item_data": { + "name": "Circle", + "doc": "Circle struct with radius field\n", + "signature": null, + "full_path": "hello_world::Circle" + } + } + ], + "enums": [ + { + "variants": [ + { + "item_data": { + "name": "Red", + "doc": "Red color", + "signature": null, + "full_path": "hello_world::Color::Red" + } + }, + { + "item_data": { + "name": "Green", + "doc": "Green color", + "signature": null, + "full_path": "hello_world::Color::Green" + } + }, + { + "item_data": { + "name": "Blue", + "doc": "Blue color", + "signature": null, + "full_path": "hello_world::Color::Blue" + } + } + ], + "item_data": { + "name": "Color", + "doc": "Color enum with Red, Green, and Blue variants\n", + "signature": null, + "full_path": "hello_world::Color" + } + } + ], + "type_aliases": [ + { + "item_data": { + "name": "Pair", + "doc": "Pair type alias for a tuple of two u32 values\n", + "signature": "type Pair = (u32, u32);", + "full_path": "hello_world::Pair" + } + } + ], + "impl_aliases": [], + "traits": [ + { + "trait_constants": [ + { + "item_data": { + "name": "SHAPE_CONST", + "doc": "Constant for the shape type\n", + "signature": null, + "full_path": "Shape::SHAPE_CONST" + } + } + ], + "trait_types": [ + { + "item_data": { + "name": "ShapePair", + "doc": "Type alias for a pair of shapes\n", + "signature": null, + "full_path": "Shape::ShapePair" + } + } + ], + "trait_functions": [ + { + "item_data": { + "name": "area", + "doc": "Calculate the area of the shape\n", + "signature": "fn area(self: T) -> u32", + "full_path": "Shape::area" + } + } + ], + "item_data": { + "name": "Shape", + "doc": "Shape trait for objects that have an area\n", + "signature": "trait Shape", + "full_path": "hello_world::Shape" + } + } + ], + "impls": [ + { + "impl_types": [ + { + "item_data": { + "name": "ShapePair", + "doc": "Type alias for a pair of circles\n", + "signature": "type ShapePair = (Circle, Circle);", + "full_path": "CircleShape::ShapePair" + } + } + ], + "impl_constants": [ + { + "item_data": { + "name": "SHAPE_CONST", + "doc": "Shape constant\n", + "signature": "const SHAPE_CONST = \"xyz\";", + "full_path": "CircleShape::SHAPE_CONST" + } + } + ], + "impl_functions": [ + { + "item_data": { + "name": "area", + "doc": "Implementation of the area method for Circle\n", + "signature": "fn area(self: Circle) -> u32", + "full_path": "hello_world::CircleShape::area" + } + } + ], + "item_data": { + "name": "CircleShape", + "doc": "Implementation of the Shape trait for Circle\n", + "signature": "impl CircleShape of Shape", + "full_path": "hello_world::CircleShape" + } + }, + { + "impl_types": [], + "impl_constants": [], + "impl_functions": [], + "item_data": { + "name": "CircleDrop", + "doc": null, + "signature": "impl CircleDrop of core::traits::Drop;", + "full_path": "hello_world::CircleDrop" + } + }, + { + "impl_types": [], + "impl_constants": [], + "impl_functions": [ + { + "item_data": { + "name": "serialize", + "doc": null, + "signature": "fn serialize(self: @Circle, ref output: core::array::Array)", + "full_path": "hello_world::CircleSerde::serialize" + } + }, + { + "item_data": { + "name": "deserialize", + "doc": null, + "signature": "fn deserialize(ref serialized: core::array::Span) -> core::option::Option", + "full_path": "hello_world::CircleSerde::deserialize" + } + } + ], + "item_data": { + "name": "CircleSerde", + "doc": null, + "signature": "impl CircleSerde of core::serde::Serde", + "full_path": "hello_world::CircleSerde" + } + }, + { + "impl_types": [], + "impl_constants": [], + "impl_functions": [ + { + "item_data": { + "name": "eq", + "doc": null, + "signature": "fn eq(lhs: @Circle, rhs: @Circle) -> bool", + "full_path": "hello_world::CirclePartialEq::eq" + } + } + ], + "item_data": { + "name": "CirclePartialEq", + "doc": null, + "signature": "impl CirclePartialEq of core::traits::PartialEq", + "full_path": "hello_world::CirclePartialEq" + } + } + ], + "extern_types": [], + "extern_functions": [] + } + } + } +} diff --git a/extensions/scarb-doc/tests/test.rs b/extensions/scarb-doc/tests/test.rs index 6cda055a0..bbd7f0c35 100644 --- a/extensions/scarb-doc/tests/test.rs +++ b/extensions/scarb-doc/tests/test.rs @@ -1,25 +1,14 @@ use assert_fs::TempDir; use expect_test::expect_file; use indoc::indoc; -use std::env; -use std::path::PathBuf; +use std::fs; -use scarb_metadata::MetadataCommand; -use scarb_test_support::cargo::cargo_bin; +use scarb_test_support::command::Scarb; use scarb_test_support::project_builder::ProjectBuilder; -use scarb_doc::compilation::get_project_config; -use scarb_doc::generate_language_elements_tree_for_package; - -fn scarb_bin() -> PathBuf { - env::var_os("SCARB_TEST_BIN") - .map(PathBuf::from) - .unwrap_or_else(|| cargo_bin("scarb")) -} - // Run `UPDATE_EXPECT=1 cargo test` to fix this test. #[test] -fn integration_test() { +fn json_output() { let t = TempDir::new().unwrap(); ProjectBuilder::start() .name("hello_world") @@ -117,25 +106,15 @@ fn integration_test() { "#}) .build(&t); - let metadata = MetadataCommand::new() - .scarb_path(scarb_bin()) - .current_dir(t.path()) - .exec() - .expect("Failed to obtain metadata"); - let package_metadata = metadata - .packages - .iter() - .find(|pkg| pkg.id == metadata.workspace.members[0]) - .unwrap(); - - let project_config = get_project_config(&metadata, package_metadata); - - let crate_ = - generate_language_elements_tree_for_package(package_metadata.name.clone(), project_config) - .expect("Failed to generate language elements tree"); - - let serialized_crate = serde_json::to_string_pretty(&crate_).unwrap(); - - let expected = expect_file!["./data/integration_test_data.json"]; - expected.assert_eq(&serialized_crate); + Scarb::quick_snapbox() + .arg("doc") + .args(["--output-format", "json"]) + .current_dir(&t) + .assert() + .success(); + + let serialized_crates = fs::read_to_string(t.path().join("target/doc/output.json")) + .expect("Failed to read from file"); + let expected = expect_file!["./data/json_output_test_data.json"]; + expected.assert_eq(&serialized_crates); }