From e49bb1db54d4fb272fe76fc86058dab4e54baa42 Mon Sep 17 00:00:00 2001 From: "Janik H." Date: Wed, 31 Jul 2024 23:15:23 +0200 Subject: [PATCH] feat: add json_output --- src/commonmark.rs | 72 +++++++++++++++++++++++++---------------------- src/legacy.rs | 17 +++++++++-- src/main.rs | 45 +++++++++++++++++++++-------- src/test.rs | 41 ++++++++++++++------------- 4 files changed, 109 insertions(+), 66 deletions(-) diff --git a/src/commonmark.rs b/src/commonmark.rs index 3d4a30e..8175435 100644 --- a/src/commonmark.rs +++ b/src/commonmark.rs @@ -16,13 +16,13 @@ //! This module implements CommonMark output for a struct //! representing a single entry in the manual. -use std::collections::HashMap; - use std::io::{Result, Write}; +use serde::Serialize; + /// Represent a single function argument name and its (optional) /// doc-string. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize)] pub struct SingleArg { pub name: String, pub doc: Option, @@ -30,7 +30,7 @@ pub struct SingleArg { /// Represent a function argument, which is either a flat identifier /// or a pattern set. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize)] pub enum Argument { /// Flat function argument (e.g. `n: n * 2`). Flat(SingleArg), @@ -99,16 +99,42 @@ fn handle_indentation(raw: &str) -> String { } } +/// Generate the identifier for CommonMark. +/// ident is used as URL Encoded link to the function and has thus stricter rules (i.e. "' " in "lib.map' " is not allowed). +pub(crate) fn get_identifier(prefix: &String, category: &String, name: &String) -> String { + let name_prime = name.replace('\'', "-prime"); + vec![prefix, category, &name_prime] + .into_iter() + .filter(|x| !x.is_empty()) + .cloned() + .collect::>() + .join(".") +} + +/// Generate the title for CommonMark. +/// the title is the human-readable name of the function. +pub(crate) fn get_title(prefix: &String, category: &String, name: &String) -> String { + vec![prefix, category, name] + .into_iter() + .filter(|x| !x.is_empty()) + .cloned() + .collect::>() + .join(".") +} + /// Represents a single manual section describing a library function. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize)] pub struct ManualEntry { /// Prefix for the category (e.g. 'lib' or 'utils'). pub prefix: String, - /// Name of the function category (e.g. 'strings', 'trivial', 'attrsets') + /// Name of the function category (e.g. 'strings', 'trivial', 'attrsets'). pub category: String, - /// Name of the section (used as the title) + /// Location of the function. + pub location: Option, + + /// Name of the section (used as the title). pub name: String, /// Type signature (if provided). This is not actually a checked @@ -122,39 +148,19 @@ pub struct ManualEntry { /// Usage example for the entry. pub example: Option, - /// Arguments of the function + /// Arguments of the function. pub args: Vec, } impl ManualEntry { - /// Generate the identifier and title for CommonMark. - /// title is the human-readable name of the function. - /// ident is used as URL Encoded link to the function and has thus stricter rules (i.e. "' " in "lib.map' " is not allowed). pub(crate) fn get_ident_title(&self) -> (String, String) { - let name_prime = self.name.replace('\'', "-prime"); - - let ident = vec![&self.prefix, &self.category, &name_prime] - .into_iter() - .filter(|x| !x.is_empty()) - .cloned() - .collect::>() - .join("."); - - let title = vec![&self.prefix, &self.category, &self.name] - .into_iter() - .filter(|x| !x.is_empty()) - .cloned() - .collect::>() - .join("."); - + let ident = get_identifier(&self.prefix, &self.category, &self.name); + let title = get_title(&self.prefix, &self.category, &self.name); (ident, title) } + /// Write a single CommonMark entry for a documented Nix function. - pub fn write_section( - self, - locs: &HashMap, - writer: &mut W, - ) -> Result<()> { + pub fn write_section(self, writer: &mut W) -> Result<()> { let (ident, title) = self.get_ident_title(); writeln!(writer, "## `{}` {{#function-library-{}}}\n", title, ident)?; @@ -194,7 +200,7 @@ impl ManualEntry { writeln!(writer, "```nix\n{}\n```\n:::\n", example.trim())?; } - if let Some(loc) = locs.get(&ident) { + if let Some(loc) = self.location { writeln!(writer, "Located at {loc}.\n")?; } diff --git a/src/legacy.rs b/src/legacy.rs index fc11923..7304f38 100644 --- a/src/legacy.rs +++ b/src/legacy.rs @@ -3,11 +3,12 @@ use rnix::{ SyntaxKind, SyntaxNode, }; use rowan::ast::AstNode; +use std::collections::HashMap; use crate::{ commonmark::{Argument, ManualEntry, SingleArg}, format::handle_indentation, - retrieve_doc_comment, DocComment, + get_identifier, retrieve_doc_comment, DocComment, }; #[derive(Debug)] @@ -18,10 +19,22 @@ pub struct LegacyDocItem { } impl LegacyDocItem { - pub fn into_entry(self, prefix: &str, category: &str) -> ManualEntry { + pub fn into_entry( + self, + prefix: &str, + category: &str, + locs: &HashMap, + ) -> ManualEntry { + let ident = get_identifier( + &prefix.to_string(), + &category.to_string(), + &self.name.to_string(), + ); + ManualEntry { prefix: prefix.to_string(), category: category.to_string(), + location: locs.get(&ident).cloned(), name: self.name, description: self .comment diff --git a/src/main.rs b/src/main.rs index 3a93330..57f7c52 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,6 +56,10 @@ struct Options { #[arg(short, long, default_value_t = String::from("lib"))] prefix: String, + /// Prefix for the category (e.g. 'lib' or 'utils'). + #[arg(short, long, default_value_t = false)] + json_output: bool, + /// Name of the function category (e.g. 'strings', 'attrsets'). #[arg(short, long)] category: String, @@ -222,6 +226,7 @@ fn collect_bindings( node: &SyntaxNode, prefix: &str, category: &str, + locs: &HashMap, scope: HashMap, ) -> Vec { for ev in node.preorder() { @@ -232,7 +237,7 @@ fn collect_bindings( if let Some(apv) = AttrpathValue::cast(child.clone()) { entries.extend( collect_entry_information(apv) - .map(|di| di.into_entry(prefix, category)), + .map(|di| di.into_entry(prefix, category, locs)), ); } else if let Some(inh) = Inherit::cast(child) { // `inherit (x) ...` needs much more handling than we can @@ -259,7 +264,12 @@ fn collect_bindings( // Main entrypoint for collection // TODO: document -fn collect_entries(root: rnix::Root, prefix: &str, category: &str) -> Vec { +fn collect_entries( + root: rnix::Root, + prefix: &str, + category: &str, + locs: &HashMap, +) -> Vec { // we will look into the top-level let and its body for function docs. // we only need a single level of scope for this. // since only the body can export a function we don't need to implement @@ -271,15 +281,16 @@ fn collect_entries(root: rnix::Root, prefix: &str, category: &str) -> Vec { - return collect_bindings(&n, prefix, category, Default::default()); + return collect_bindings(&n, prefix, category, locs, Default::default()); } _ => (), } @@ -303,7 +314,6 @@ fn retrieve_description(nix: &rnix::Root, description: &str, category: &str) -> } fn main() { - let mut output = io::stdout(); let opts = Options::parse(); let src = fs::read_to_string(&opts.file).unwrap(); let locs = match opts.locs { @@ -316,12 +326,23 @@ fn main() { let nix = rnix::Root::parse(&src).ok().expect("failed to parse input"); let description = retrieve_description(&nix, &opts.description, &opts.category); - // TODO: move this to commonmark.rs - writeln!(output, "{}", description).expect("Failed to write header"); - - for entry in collect_entries(nix, &opts.prefix, &opts.category) { - entry - .write_section(&locs, &mut output) - .expect("Failed to write section") + let entries = collect_entries(nix, &opts.prefix, &opts.category, &locs); + + if opts.json_output { + let json_string = match serde_json::to_string(&entries) { + Ok(json) => json, + Err(error) => panic!("Problem converting entries to json: {error:?}"), + }; + println!("{}", json_string); + } else { + // TODO: move this to commonmark.rs + let mut output = io::stdout(); + writeln!(output, "{}", description).expect("Failed to write header"); + + for entry in entries { + entry + .write_section(&mut output) + .expect("Failed to write section") + } } } diff --git a/src/test.rs b/src/test.rs index f71a518..23e42b6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,4 +1,5 @@ use rnix; +use std::collections::HashMap; use std::fs; use std::io::Write; @@ -9,7 +10,8 @@ use crate::{collect_entries, format::shift_headings, retrieve_description, Manua fn test_main() { let mut output = Vec::new(); let src = fs::read_to_string("test/strings.nix").unwrap(); - let locs = serde_json::from_str(&fs::read_to_string("test/strings.json").unwrap()).unwrap(); + let locs: HashMap = + serde_json::from_str(&fs::read_to_string("test/strings.json").unwrap()).unwrap(); let nix = rnix::Root::parse(&src).ok().expect("failed to parse input"); let desc = "string manipulation functions"; let prefix = "lib"; @@ -23,9 +25,9 @@ fn test_main() { ) .expect("Failed to write header"); - for entry in collect_entries(nix, prefix, category) { + for entry in collect_entries(nix, prefix, category, &locs) { entry - .write_section(&locs, &mut output) + .write_section(&mut output) .expect("Failed to write section") } @@ -44,9 +46,9 @@ fn test_description_of_lib_debug() { let desc = retrieve_description(&nix, &"Debug", category); writeln!(output, "{}", desc).expect("Failed to write header"); - for entry in collect_entries(nix, prefix, category) { + for entry in collect_entries(nix, prefix, category, &Default::default()) { entry - .write_section(&Default::default(), &mut output) + .write_section(&mut output) .expect("Failed to write section") } @@ -63,9 +65,9 @@ fn test_arg_formatting() { let prefix = "lib"; let category = "options"; - for entry in collect_entries(nix, prefix, category) { + for entry in collect_entries(nix, prefix, category, &Default::default()) { entry - .write_section(&Default::default(), &mut output) + .write_section(&mut output) .expect("Failed to write section") } @@ -82,9 +84,9 @@ fn test_inherited_exports() { let prefix = "lib"; let category = "let"; - for entry in collect_entries(nix, prefix, category) { + for entry in collect_entries(nix, prefix, category, &Default::default()) { entry - .write_section(&Default::default(), &mut output) + .write_section(&mut output) .expect("Failed to write section") } @@ -101,9 +103,9 @@ fn test_line_comments() { let prefix = "lib"; let category = "let"; - for entry in collect_entries(nix, prefix, category) { + for entry in collect_entries(nix, prefix, category, &Default::default()) { entry - .write_section(&Default::default(), &mut output) + .write_section(&mut output) .expect("Failed to write section") } @@ -120,9 +122,9 @@ fn test_multi_line() { let prefix = "lib"; let category = "let"; - for entry in collect_entries(nix, prefix, category) { + for entry in collect_entries(nix, prefix, category, &Default::default()) { entry - .write_section(&Default::default(), &mut output) + .write_section(&mut output) .expect("Failed to write section") } @@ -139,9 +141,9 @@ fn test_doc_comment() { let prefix = "lib"; let category = "debug"; - for entry in collect_entries(nix, prefix, category) { + for entry in collect_entries(nix, prefix, category, &Default::default()) { entry - .write_section(&Default::default(), &mut output) + .write_section(&mut output) .expect("Failed to write section") } @@ -178,9 +180,9 @@ fn test_doc_comment_section_description() { let desc = retrieve_description(&nix, &"Debug", category); writeln!(output, "{}", desc).expect("Failed to write header"); - for entry in collect_entries(nix, prefix, category) { + for entry in collect_entries(nix, prefix, category, &Default::default()) { entry - .write_section(&Default::default(), &mut output) + .write_section(&mut output) .expect("Failed to write section") } @@ -199,9 +201,9 @@ fn test_doc_comment_no_duplicate_arguments() { let desc = retrieve_description(&nix, &"Debug", category); writeln!(output, "{}", desc).expect("Failed to write header"); - for entry in collect_entries(nix, prefix, category) { + for entry in collect_entries(nix, prefix, category, &Default::default()) { entry - .write_section(&Default::default(), &mut output) + .write_section(&mut output) .expect("Failed to write section") } @@ -215,6 +217,7 @@ fn test_empty_prefix() { let test_entry = ManualEntry { args: vec![], category: "test".to_string(), + location: None, description: vec![], example: None, fn_type: None,