diff --git a/Cargo.toml b/Cargo.toml index 72c1022..c5fd287 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nixdoc" -version = "1.0.1" +version = "2.0.0" authors = ["Vincent Ambo "] [dependencies] diff --git a/README.md b/README.md index b6f91f7..d3a5be4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ This tool is (for now) a proof-of-concept to generate documentation for Nix library functions from the source files in `nixpkgs/lib`. It uses [rnix][] to parse Nix source files, which are then transformed -into a DocBook representation of the function set. +into a CommonMark (with some syntax extensions) representation of the +function set. Please see [this Discourse thread][] for information on the documentation format and general discussion. @@ -54,4 +55,4 @@ This project requires a nightly Rust compiler build. [rnix]: https://gitlab.com/jD91mZM2/rnix [this Discourse thread]: https://discourse.nixos.org/t/nixpkgs-library-function-documentation-doc-tests/1156 [this example]: https://storage.googleapis.com/files.tazj.in/nixdoc/manual.html#sec-functions-library-strings -[issues]: https://github.com/tazjin/nixdoc/issues +[issues]: https://github.com/nix-community/nixdoc/issues diff --git a/src/commonmark.rs b/src/commonmark.rs new file mode 100644 index 0000000..aba8e1b --- /dev/null +++ b/src/commonmark.rs @@ -0,0 +1,130 @@ +// Copyright (C) 2018 Vincent Ambo +// +// nixdoc is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! This module implements CommonMark output for a struct +//! representing a single entry in the manual. + +use std::io::Write; +use failure::Error; + +/// Represent a single function argument name and its (optional) +/// doc-string. +#[derive(Debug)] +pub struct SingleArg { + pub name: String, + pub doc: Option, +} + +/// Represent a function argument, which is either a flat identifier +/// or a pattern set. +#[derive(Debug)] +pub enum Argument { + /// Flat function argument (e.g. `n: n * 2`). + Flat(SingleArg), + + /// Pattern function argument (e.g. `{ name, age }: ...`) + Pattern(Vec), +} + +impl Argument { + /// Write CommonMark structure for a single function argument. + fn write_argument(self) -> Result<(), Error> { + match self { + // Write a flat argument entry. + Argument::Flat(arg) => { + println!("`{}`\n", &arg.name); + println!(": {}\n", arg.doc.unwrap_or("Function argument".into()).trim()) + }, + + // Write a pattern argument entry and its individual + // parameters as a nested structure. + Argument::Pattern(pattern_args) => { + println!("### pattern - structured function argument\n"); + for pattern_arg in pattern_args { + Argument::Flat(pattern_arg) + .write_argument()?; + } + }, + } + + Ok(()) + } +} + +/// Represents a single manual section describing a library function. +#[derive(Debug)] +pub struct ManualEntry { + /// Name of the function category (e.g. 'strings', 'trivial', 'attrsets') + pub category: String, + + /// Name of the section (used as the title) + pub name: String, + + /// Type signature (if provided). This is not actually a checked + /// type signature in any way. + pub fn_type: Option, + + /// Primary description of the entry. Each entry is written as a + /// separate paragraph. + pub description: Vec, + + /// Usage example for the entry. + pub example: Option, + + /// Arguments of the function + pub args: Vec, +} + +impl ManualEntry { + /// Write a single CommonMark entry for a documented Nix function. + pub fn write_section(self) -> Result<(), Error> { + let title = format!("lib.{}.{}", self.category, self.name); + let ident = format!("lib.{}.{}", self.category, self.name.replace("'", "-prime")); + + println!("## `{}` {{#function-library-{}}}\n", title, ident); + + // (type signature) + if let Some(t) = &self.fn_type { + println!("`{}`\n", t); + } + + // Primary doc string + // TODO: Split paragraphs? + for paragraph in &self.description { + println!("{}\n", paragraph); + } + + // Function argument names + if !self.args.is_empty() { + + for arg in self.args { + arg.write_argument()?; + } + } + + // Example program listing (if applicable) + // + // TODO: In grhmc's version there are multiple (named) + // examples, how can this be achieved automatically? + if let Some(example) = &self.example { + println!("### {} usage example {{#function-library-example-{}}}\n", title, ident); + println!("```nix{}```\n", example); + } + + println!("[Source](#function-location-{})", ident); + + Ok(()) + } +} diff --git a/src/docbook.rs b/src/docbook.rs deleted file mode 100644 index 7263e2c..0000000 --- a/src/docbook.rs +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (C) 2018 Vincent Ambo -// -// nixdoc is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! This module implements DocBook XML output for a struct -//! representing a single entry in th emanual. - -use std::io::Write; -use xml::writer::{EventWriter, XmlEvent}; -use failure::Error; - -/// Write a plain start element (most commonly used). -fn element(w: &mut EventWriter, name: &str) -> Result<(), Error> { - w.write(XmlEvent::start_element(name))?; - Ok(()) -} - -/// End an element. -fn end(w: &mut EventWriter) -> Result<(), Error> { - w.write(XmlEvent::end_element())?; - Ok(()) -} - -/// Write a string. -fn string(w: &mut EventWriter, content: &str) -> Result<(), Error> { - w.write(XmlEvent::characters(content))?; - Ok(()) -} - -/// Represent a single function argument name and its (optional) -/// doc-string. -#[derive(Debug)] -pub struct SingleArg { - pub name: String, - pub doc: Option, -} - -/// Represent a function argument, which is either a flat identifier -/// or a pattern set. -#[derive(Debug)] -pub enum Argument { - /// Flat function argument (e.g. `n: n * 2`). - Flat(SingleArg), - - /// Pattern function argument (e.g. `{ name, age }: ...`) - Pattern(Vec), -} - -impl Argument { - /// Write DocBook structure for a single function argument. - fn write_argument_xml(self, w: &mut EventWriter) -> Result<(), Error> { - match self { - // Write a flat argument entry. - Argument::Flat(arg) => { - element(w, "varlistentry")?; - - element(w, "term")?; - element(w, "varname")?; - string(w, &arg.name)?; - end(w)?; - end(w)?; - - element(w, "listitem")?; - element(w, "para")?; - string(w, arg.doc.unwrap_or("Function argument".into()).trim())?; - end(w)?; - end(w)?; - - end(w)?; - }, - - // Write a pattern argument entry and its individual - // parameters as a nested structure. - Argument::Pattern(pattern_args) => { - element(w, "varlistentry")?; - - element(w, "term")?; - element(w, "varname")?; - string(w, "pattern")?; - end(w)?; - end(w)?; - - element(w, "listitem")?; - element(w, "para")?; - string(w, "Structured function argument")?; - end(w)?; - - element(w, "variablelist")?; - for pattern_arg in pattern_args { - Argument::Flat(pattern_arg) - .write_argument_xml(w)?; - } - end(w)?; - end(w)?; - end(w)?; - }, - } - - Ok(()) - } -} - -/// Represents a single manual section describing a library function. -#[derive(Debug)] -pub struct ManualEntry { - /// Name of the function category (e.g. 'strings', 'trivial', 'attrsets') - pub category: String, - - /// Name of the section (used as the title) - pub name: String, - - /// Type signature (if provided). This is not actually a checked - /// type signature in any way. - pub fn_type: Option, - - /// Primary description of the entry. Each entry is written as a - /// separate paragraph. - pub description: Vec, - - /// Usage example for the entry. - pub example: Option, - - /// Arguments of the function - pub args: Vec, -} - -impl ManualEntry { - /// Write a single DocBook entry for a documented Nix function. - pub fn write_section_xml(self, w: &mut EventWriter) -> Result<(), Error> { - let title = format!("lib.{}.{}", self.category, self.name); - let ident = format!("lib.{}.{}", self.category, self.name.replace("'", "-prime")); - - //
... - element(w, "title")?; - element(w, "function")?; - string(w, title.as_str())?; - end(w)?; - end(w)?; - - // Write an include header that will load manually written - // documentation for this function if required. - let override_path = format!("./overrides/{}.xml", ident); - w.write(XmlEvent::start_element("xi:include") - .attr("href", &override_path))?; - element(w, "xi:fallback")?; - - // (type signature) - if let Some(t) = &self.fn_type { - element(w, "subtitle")?; - element(w, "literal")?; - string(w, t)?; - end(w)?; - end(w)?; - } - - // Primary doc string - // TODO: Split paragraphs? - for paragraph in &self.description { - element(w, "para")?; - string(w, paragraph)?; - end(w)?; - } - - // Function argument names - if !self.args.is_empty() { - element(w, "variablelist")?; - - for arg in self.args { - arg.write_argument_xml(w)?; - } - - end(w)?; - } - - // Example program listing (if applicable) - // - // TODO: In grhmc's version there are multiple (named) - // examples, how can this be achieved automatically? - if let Some(example) = &self.example { - element(w, "example")?; - - element(w, "title")?; - - element(w, "function")?; - string(w, title.as_str())?; - end(w)?; - - string(w, " usage example")?; - end(w)?; - - element(w, "programlisting")?; - w.write(XmlEvent::cdata(example))?; - end(w)?; - - end(w)?; - } - - // - end(w)?; - end(w)?; - - // Include link to function location (location information is - // generated by a separate script in nixpkgs) - w.write(XmlEvent::start_element("xi:include") - .attr("href", "./locations.xml") - .attr("xpointer", &ident))?; - end(w)?; - - //
- end(w)?; - - Ok(()) - } -} diff --git a/src/main.rs b/src/main.rs index a765c89..c255282 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! This tool generates DocBook XML from a Nix file defining library +//! This tool generates CommonMark from a Nix file defining library //! functions, such as the files in `lib/` in the nixpkgs repository. //! //! TODO: @@ -22,13 +22,12 @@ //! * figure out how to specify examples (& leading whitespace?!) #[macro_use] extern crate structopt; -extern crate xml; extern crate failure; extern crate rnix; -mod docbook; +mod commonmark; -use self::docbook::*; +use self::commonmark::*; use rnix::parser::{Arena, ASTNode, ASTKind, Data}; use rnix::tokenizer::Meta; use rnix::tokenizer::Trivia; @@ -36,11 +35,10 @@ use std::fs; use std::io; use std::path::PathBuf; use structopt::StructOpt; -use xml::writer::{EmitterConfig, XmlEvent}; /// Command line arguments for nixdoc #[derive(Debug, StructOpt)] -#[structopt(name = "nixdoc", about = "Generate Docbook from Nix library functions")] +#[structopt(name = "nixdoc", about = "Generate CommonMark from Nix library functions")] struct Options { /// Nix file to process. #[structopt(short = "f", long = "file", parse(from_os_str))] @@ -277,37 +275,10 @@ fn main() { }) .collect(); - let mut writer = EmitterConfig::new() - .perform_indent(true) - .create_writer(io::stdout()); - - writer.write( - XmlEvent::start_element("section") - .attr("xmlns", "http://docbook.org/ns/docbook") - .attr("xmlns:xlink", "http://www.w3.org/1999/xlink") - .attr("xmlns:xi", "http://www.w3.org/2001/XInclude") - .attr("xml:id", format!("sec-functions-library-{}", opts.category).as_str())) - .unwrap(); - - writer.write(XmlEvent::comment(r#"Do not edit this file manually! - -This file was generated using nixdoc[1]. Please edit the source Nix -file from which this XML was generated instead. - -If you need to manually override the documentation of a single -function in this file, create a new override file at -`nixpkgs/docs/functions/library/overrides/.xml`. - -[1]: https://github.com/tazjin/nixdoc -"#)).unwrap(); - - writer.write(XmlEvent::start_element("title")).unwrap(); - writer.write(XmlEvent::characters(&opts.description)).unwrap(); - writer.write(XmlEvent::end_element()).unwrap(); + println!("# {} {{#sec-functions-library-{}}}\n", &opts.description, opts.category); for entry in entries { - entry.write_section_xml(&mut writer).expect("Failed to write section") + entry.write_section().expect("Failed to write section") } - writer.write(XmlEvent::end_element()).unwrap(); }