Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial commit of wdl-doc documentation page style #262

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion wdl-doc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ repository.workspace = true
rust-version.workspace = true

[dependencies]
ammonia = "4.0.0"
anyhow.workspace = true
html = "0.6.3"
indexmap.workspace = true
maud = "0.26.0"
pulldown-cmark = "0.12.2"
tokio.workspace = true
wdl-analysis = { path = "../wdl-analysis", version = "0.6.0" }
Expand Down
158 changes: 92 additions & 66 deletions wdl-doc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,34 @@
#![warn(missing_debug_implementations)]
#![warn(clippy::missing_docs_in_private_items)]
#![warn(rustdoc::broken_intra_doc_links)]
#![recursion_limit = "512"]

pub mod parameter;
pub(crate) mod meta;
pub(crate) mod parameter;
pub mod r#struct;
pub mod task;
pub mod workflow;

use std::collections::HashMap;
use std::env::current_dir;
use std::fmt::Display;
use std::path::PathBuf;

use anyhow::Result;
use anyhow::anyhow;
use html::content;
use html::text_content;
use maud::DOCTYPE;
use maud::Markup;
use maud::PreEscaped;
use maud::Render;
use maud::html;
use pulldown_cmark::Options;
use pulldown_cmark::Parser;
use tokio::io::AsyncWriteExt;
use wdl_analysis::Analyzer;
use wdl_analysis::DiagnosticsConfig;
use wdl_analysis::rules;
use wdl_ast::AstToken;
use wdl_ast::Document as AstDocument;
use wdl_ast::SyntaxTokenExt;
use wdl_ast::Version;
use wdl_ast::VersionStatement;
use wdl_ast::v1::DocumentItem;
use wdl_ast::v1::MetadataValue;

Expand All @@ -39,6 +42,50 @@ use wdl_ast::v1::MetadataValue;
/// This directory will be created in the workspace directory.
const DOCS_DIR: &str = "docs";

/// Links to a CSS stylesheet at the given path.
struct Css<'a>(&'a str);

impl Render for Css<'_> {
fn render(&self) -> Markup {
html! {
link rel="stylesheet" type="text/css" href=(self.0);
}
}
}

/// A basic header with a dynamic `page_title`.
pub(crate) fn header(page_title: &str) -> Markup {
let style_path = current_dir().unwrap().join("theme/dist/styles.css");
html! {
(DOCTYPE)
html lang="en" {
head {
meta charset="utf-8";
title { (page_title) }
(Css(style_path.to_str().unwrap()))
}
}
}
}

/// Renders a block of Markdown using `pulldown-cmark`.
pub(crate) struct Markdown<T>(T);

impl<T: AsRef<str>> Render for Markdown<T> {
fn render(&self) -> Markup {
// Generate raw HTML
let mut unsafe_html = String::new();
let mut options = Options::empty();
options.insert(Options::ENABLE_TABLES);
options.insert(Options::ENABLE_STRIKETHROUGH);
let parser = Parser::new_ext(self.0.as_ref(), options);
pulldown_cmark::html::push_html(&mut unsafe_html, parser);
// Sanitize it with ammonia
let safe_html = ammonia::clean(&unsafe_html);
PreEscaped(safe_html)
}
}

/// A WDL document.
#[derive(Debug)]
pub struct Document {
Expand All @@ -47,86 +94,65 @@ pub struct Document {
/// This is the filename of the document without the extension.
name: String,
/// The version of the document.
version: Version,
/// The Markdown preamble comments.
preamble: String,
version: VersionStatement,
}

impl Document {
/// Create a new document.
pub fn new(name: String, version: Version, preamble: String) -> Self {
Self {
name,
version,
preamble,
}
pub fn new(name: String, version: VersionStatement) -> Self {
Self { name, version }
}

/// Get the name of the document.
pub fn name(&self) -> &str {
&self.name
}

/// Get the version of the document.
/// Get the version of the document as text.
pub fn version(&self) -> String {
self.version.as_str().to_owned()
self.version.version().as_str().to_string()
}

/// Get the preamble comments of the document.
pub fn preamble(&self) -> &str {
&self.preamble
pub fn preamble(&self) -> Markup {
let preamble = fetch_preamble_comments(self.version.clone());
Markdown(&preamble).render()
}
}

impl Display for Document {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let document_name = content::Heading1::builder()
.text(self.name().to_owned())
.build();
let version = text_content::Paragraph::builder()
.text(format!("WDL Version: {}", self.version()))
.build();

let mut options = Options::empty();
options.insert(Options::ENABLE_TABLES);
options.insert(Options::ENABLE_STRIKETHROUGH);
let parser = Parser::new_ext(&self.preamble, options);
let mut preamble = String::new();
pulldown_cmark::html::push_html(&mut preamble, parser);
let markup = html! {
(header(&self.name()))
body {
h1 { (self.name()) }
h2 { "WDL Version: " (self.version()) }
div { (self.preamble()) }
}
};

write!(f, "{}", document_name)?;
write!(f, "{}", version)?;
write!(f, "{}", preamble)
write!(f, "{}", markup.into_string())
}
}

/// Fetch the preamble comments from a document.
pub fn fetch_preamble_comments(document: AstDocument) -> String {
let comments = match document.version_statement() {
Some(version) => {
let comments = version
.keyword()
.syntax()
.preceding_trivia()
.map(|t| match t.kind() {
wdl_ast::SyntaxKind::Comment => match t.to_string().strip_prefix("## ") {
Some(comment) => comment.to_string(),
None => "".to_string(),
},
wdl_ast::SyntaxKind::Whitespace => "".to_string(),
_ => {
panic!("Unexpected token kind: {:?}", t.kind())
}
})
.collect::<Vec<_>>();
comments
}
None => {
vec![]
}
}
.join("\n");
comments
fn fetch_preamble_comments(version: VersionStatement) -> String {
let comments = version
.keyword()
.syntax()
.preceding_trivia()
.map(|t| match t.kind() {
wdl_ast::SyntaxKind::Comment => match t.to_string().strip_prefix("## ") {
Some(comment) => comment.to_string(),
None => "".to_string(),
},
wdl_ast::SyntaxKind::Whitespace => "".to_string(),
_ => {
panic!("Unexpected token kind: {:?}", t.kind())
}
})
.collect::<Vec<_>>();
comments.join("\n")
}

/// Generate HTML documentation for a workspace.
Expand Down Expand Up @@ -167,12 +193,10 @@ pub async fn document_workspace(path: PathBuf) -> Result<()> {
let ast_doc = result.document().node();
let version = ast_doc
.version_statement()
.expect("document should have a version statement")
.version();
let preamble = fetch_preamble_comments(ast_doc.clone());
.expect("Document should have a version statement");
let ast = ast_doc.ast().unwrap_v1();

let document = Document::new(name.to_string(), version, preamble);
let document = Document::new(name.to_string(), version);

let index = cur_dir.join("index.html");
let mut index = tokio::fs::File::create(index).await?;
Expand Down Expand Up @@ -339,6 +363,8 @@ pub async fn document_workspace(path: PathBuf) -> Result<()> {

#[cfg(test)]
mod tests {
use wdl_ast::Document as AstDocument;

use super::*;

#[test]
Expand All @@ -361,7 +387,7 @@ mod tests {
}
"#;
let (document, _) = AstDocument::parse(source);
let preamble = fetch_preamble_comments(document);
let preamble = fetch_preamble_comments(document.version_statement().unwrap());
assert_eq!(preamble, "This is a comment\nThis is also a comment");
}
}
47 changes: 47 additions & 0 deletions wdl-doc/src/meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Create HTML documentation for WDL meta sections.

use std::fmt::Display;

use maud::Markup;
use maud::html;
use wdl_ast::AstNode;
use wdl_ast::AstToken;
use wdl_ast::v1::MetadataSection;

/// A meta section in a WDL document.
#[derive(Debug)]
pub struct Meta(MetadataSection);

impl Meta {
/// Create a new meta section.
pub fn new(meta: MetadataSection) -> Self {
Self(meta)
}

/// Render the meta section as HTML.
pub fn render(&self) -> Markup {
html! {
h2 { "Meta" }
ul {
@for entry in self.0.items() {
li {
b {
(entry.name().as_str())
":"
}
" "
(entry.value().syntax().to_string())
}
}
}
}
}
}

impl Display for Meta {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let markup = self.render();

write!(f, "{}", markup.into_string())
}
}
39 changes: 19 additions & 20 deletions wdl-doc/src/parameter.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
//! Parameter module
//! Create HTML documentation for WDL parameters.

use std::fmt::Display;

use html::content;
use html::text_content;
use maud::Markup;
use maud::html;
use wdl_ast::AstToken;
use wdl_ast::v1::Decl;
use wdl_ast::v1::MetadataValue;

/// A parameter in a workflow or task.
/// A parameter (input or output) in a workflow or task.
#[derive(Debug)]
pub struct Parameter {
/// The declaration of the parameter.
Expand Down Expand Up @@ -42,26 +42,25 @@ impl Parameter {
pub fn meta(&self) -> Option<&MetadataValue> {
self.meta.as_ref()
}

/// Render the parameter as HTML.
pub fn render(&self) -> Markup {
html! {
h2 { (self.name()) }
p { "Type: " (self.ty()) }
@if let Some(expr) = self.expr() {
p { "Expr: " (expr) }
} @else {
p { "Expr: None" }
}
}
}
}

impl Display for Parameter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let parameter_name = content::Heading2::builder().text(self.name()).build();
let parameter_type = text_content::Paragraph::builder()
.text(format!("Type: {}", self.ty()))
.build();
let parameter_expr = if let Some(expr) = self.expr() {
text_content::Paragraph::builder()
.text(format!("Expr: {}", expr))
.build()
} else {
text_content::Paragraph::builder()
.text("Expr: None")
.build()
};
let markup = self.render();

write!(f, "{}", parameter_name)?;
write!(f, "{}", parameter_type)?;
write!(f, "{}", parameter_expr)
write!(f, "{}", markup.into_string())
}
}
Loading
Loading