refactor usage
This commit is contained in:
parent
b07d70c769
commit
5c25d628f5
27
Cargo.toml
27
Cargo.toml
@ -6,24 +6,37 @@ edition = "2018"
|
|||||||
publish = false
|
publish = false
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["cli"]
|
||||||
|
cli = ["structopt", "tokio/macros", "tokio/rt-multi-thread"]
|
||||||
|
mdbook-renderer = ["mdbook", "includedir", "includedir_codegen", "glob", "phf"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "cyberstorm"
|
||||||
|
required-features = ["cli"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zc = "0.4"
|
zc = "0.4"
|
||||||
|
log = "0.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
strum = { version = "0.20", features = ["derive"] }
|
strum = { version = "0.20", features = ["derive"] }
|
||||||
roxmltree = "0.14"
|
roxmltree = "0.14"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "fs", "macros"] }
|
tokio = { version = "1", features = ["fs"] }
|
||||||
validator = { version = "0.12", features = ["derive"] }
|
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tera = "1.6"
|
tera = "1.6"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
structopt = "0.3"
|
env_logger = "0.8"
|
||||||
phf = "0.8.0"
|
chrono = "0.4"
|
||||||
includedir = "0.6"
|
|
||||||
|
structopt = { version = "0.3", optional = true }
|
||||||
|
phf = { version = "0.8.0", optional = true }
|
||||||
|
includedir = { version = "0.6", optional = true }
|
||||||
|
mdbook = { version = "0.4", optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
includedir_codegen = "0.6"
|
includedir_codegen = { version = "0.6", optional = true }
|
||||||
glob = "0.3"
|
glob = { version = "0.3", optional = true }
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
{% macro content(content) %}
|
||||||
|
{%- set trimmed = content | trim -%}
|
||||||
|
{%- if trimmed | length == 0 %}No description{% else %}{{ trimmed }}{% endif -%}
|
||||||
|
{% endmacro content %}
|
||||||
|
|
||||||
{% macro references(refs) %}
|
{% macro references(refs) %}
|
||||||
{%- if refs | length == 0 %}No references{% endif -%}
|
{%- if refs | length == 0 %}No references{% endif -%}
|
||||||
{%- for ref in refs -%}
|
{%- for ref in refs -%}
|
||||||
@ -5,46 +10,41 @@
|
|||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
{% endmacro references %}
|
{% endmacro references %}
|
||||||
|
|
||||||
{% macro title(title, id) -%}
|
{% macro doc_title(doc) -%}
|
||||||
{{ title }} <small>([edit]({{ id | domain_id_link(for="edit") }}))</small>
|
{{ doc.name }} <small>([edit]({{ doc.id | domain_id_link(for="edit") }}))</small>
|
||||||
{%- endmacro details_next %}
|
{%- endmacro doc_title %}
|
||||||
|
|
||||||
{% macro details(id, name) -%}
|
{% macro doc_details(doc) -%}
|
||||||
| Title | {{ name }} |
|
| Title | {{ doc.name }} |
|
||||||
|:---------------------------:|:------------------------|
|
|:---------------------------:|:------------------------|
|
||||||
{{ self::details_next(title="ID", value=id)}}
|
{{ self::doc_details_next(title="ID", value=doc.id)}}
|
||||||
{%- endmacro details %}
|
{%- endmacro doc_details %}
|
||||||
|
|
||||||
{% macro details_next(title, value) -%}
|
{% macro doc_details_next(title, value) -%}
|
||||||
| **{{ title }}** | {{ value }} |
|
| **{{ title }}** | {{ value }} |
|
||||||
{%- endmacro details_next %}
|
{%- endmacro doc_details_next %}
|
||||||
|
|
||||||
{% macro details_authors(authors) -%}
|
{% macro doc_details_authors(authors) -%}
|
||||||
{{ self::details_next(title="Authors", value=authors | join )}}
|
{{ self::doc_details_next(title="Authors", value=authors | join )}}
|
||||||
{%- endmacro details_next %}
|
{%- endmacro doc_details_authors %}
|
||||||
|
|
||||||
{% macro details_tags(tags) -%}
|
{% macro doc_details_tags(tags) -%}
|
||||||
{% if tags | length == 0 -%}
|
{% if tags | length == 0 -%}
|
||||||
{{ self::details_next(title="Tags", value="No tags") }}
|
{{ self::doc_details_next(title="Tags", value="No tags") }}
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
{{ self::details_next(title="Tags", value=tags | join) }}
|
{{ self::doc_details_next(title="Tags", value=tags | join) }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endmacro details_next %}
|
{%- endmacro doc_details_tags %}
|
||||||
|
|
||||||
{% macro content(content) %}
|
{% macro doc_rich_link(doc) -%}
|
||||||
{%- set trimmed = content | trim -%}
|
{{ doc.name }} ([{{ doc.id }}]({{ global(key="site_url")}}{{ doc.id | domain_id_link }}))
|
||||||
{%- if trimmed | length == 0 %}No description{% else %}{{ trimmed }}{% endif -%}
|
{%- endmacro doc_rich_link %}
|
||||||
{% endmacro details_next %}
|
|
||||||
|
|
||||||
{% macro name_and_id_link(value) -%}
|
|
||||||
{{ value["doc"]["name"] }} ([{{ value["id"] }}]({{ value["id"] | domain_id_link }}))
|
|
||||||
{%- endmacro name_and_id_link %}
|
|
||||||
|
|
||||||
{% macro summary_table(instances) -%}
|
{% macro summary_table(instances) -%}
|
||||||
| ID | Name |
|
| ID | Name |
|
||||||
|:---------------------------:|:------------------------|
|
|:---------------------------:|:------------------------|
|
||||||
{% for item in instances -%}
|
{% for item in instances -%}
|
||||||
| [{{ item.id }}]({{ item.id | domain_id_link }}) | {{ item.name }} |
|
| [{{ item.id }}]({{ global(key="site_url") }}{{ item.id | domain_id_link }}) | {{ item.name }} |
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{%- endmacro summary_table %}
|
{%- endmacro summary_table %}
|
||||||
|
|
||||||
@ -59,8 +59,8 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- if last_model != id_parts.model -%}
|
{%- if last_model != id_parts.model -%}
|
||||||
{%- set_global last_model = id_parts.model %}
|
{%- set_global last_model = id_parts.model %}
|
||||||
- [{{ id_parts.model | capitalize }}](.{{ item.id | domain_id_link(for="model") }})
|
- [{{ id_parts.model | capitalize }}](./{{ item.id | domain_id_link(for="model") }})
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
- [{{ item.name }}](.{{ item.id | domain_id_link }})
|
- [{{ item.name }}](./{{ item.id | domain_id_link }})
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{% endmacro summary_list %}
|
{% endmacro summary_list %}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# {{ macros::title(title=doc.name, id=id) }}
|
# {{ macros::doc_title(doc=doc) }}
|
||||||
|
|
||||||
{{ macros::details(id=id, name=doc.name) }}
|
{{ macros::doc_details(doc=doc) }}
|
||||||
{%- if doc.WindowsEvent %}
|
{%- if doc.WindowsEvent %}
|
||||||
{{ macros::details_next(title="Type", value="Windows event") }}
|
{{ macros::doc_details_next(title="Type", value="Windows event") }}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
{{ macros::details_next(title="Description", value=doc.description) }}
|
{{ macros::doc_details_next(title="Description", value=doc.description) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
{% set stage = doc.stage | get_doc %}
|
{% set stage = doc.stage | get_doc %}
|
||||||
|
|
||||||
# {{ macros::title(title=doc.name, id=id) }}
|
# {{ macros::doc_title(doc=doc) }}
|
||||||
|
|
||||||
{{ macros::details(id=id, name=doc.name) }}
|
{{ macros::doc_details(doc=doc) }}
|
||||||
{{ macros::details_next(title="Stage", value=macros::name_and_id_link(value=stage)) }}
|
{{ macros::doc_details_next(title="Stage", value=macros::doc_rich_link(doc=stage)) }}
|
||||||
{{ macros::details_next(title="Description", value=doc.description) }}
|
{{ macros::doc_details_next(title="Description", value=doc.description) }}
|
||||||
{{ macros::details_tags(tags=doc.tags) }}
|
{{ macros::doc_details_tags(tags=doc.tags) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# {{ macros::title(title=doc.name, id=id) }}
|
# {{ macros::doc_title(doc=doc) }}
|
||||||
|
|
||||||
{{ macros::details(id=id, name=doc.name) }}
|
{{ macros::doc_details(doc=doc) }}
|
||||||
{{ macros::details_next(title="Description", value=doc.description) }}
|
{{ macros::doc_details_next(title="Description", value=doc.description) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# {{ macros::title(title=doc.name, id=id) }}
|
# {{ macros::doc_title(doc=doc) }}
|
||||||
|
|
||||||
{{ macros::details(id=id, name=doc.name) }}
|
{{ macros::doc_details(doc=doc) }}
|
||||||
{{ macros::details_next(title="Provider", value=doc.provider) }}
|
{{ macros::doc_details_next(title="Provider", value=doc.provider) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# {{ macros::title(title=doc.name, id=id) }}
|
# {{ macros::doc_title(doc=doc) }}
|
||||||
|
|
||||||
{{ macros::details(id=id, name=doc.name) }}
|
{{ macros::doc_details(doc=doc) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# {{ macros::title(title=doc.name, id=id) }}
|
# {{ macros::doc_title(doc=doc) }}
|
||||||
|
|
||||||
{{ macros::details(id=id, name=doc.name) }}
|
{{ macros::doc_details(doc=doc) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
12
build.rs
12
build.rs
@ -1,7 +1,13 @@
|
|||||||
use glob::glob;
|
|
||||||
use includedir_codegen::Compression;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
|
include_mdbook_templates();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
|
fn include_mdbook_templates() {
|
||||||
|
use glob::glob;
|
||||||
|
use includedir_codegen::Compression;
|
||||||
|
|
||||||
let mut templates = includedir_codegen::start("BASE_TEMPLATES");
|
let mut templates = includedir_codegen::start("BASE_TEMPLATES");
|
||||||
|
|
||||||
for path_result in glob("book/**/*.tera").unwrap() {
|
for path_result in glob("book/**/*.tera").unwrap() {
|
||||||
|
@ -2,5 +2,5 @@ mod util;
|
|||||||
|
|
||||||
pub mod document;
|
pub mod document;
|
||||||
pub mod domains;
|
pub mod domains;
|
||||||
pub mod generator;
|
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
|
pub mod render;
|
||||||
|
114
src/main.rs
114
src/main.rs
@ -1,12 +1,18 @@
|
|||||||
use std::path::PathBuf;
|
use std::env;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{Context, Error};
|
use anyhow::{anyhow, Context, Error};
|
||||||
|
use chrono::Local;
|
||||||
|
use log::LevelFilter;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
use cyberstorm::generator::mdbook::MDBookEngine;
|
|
||||||
use cyberstorm::generator::Generator;
|
|
||||||
use cyberstorm::registry::{DirectoryRegistry, SupportedRegistry};
|
use cyberstorm::registry::{DirectoryRegistry, SupportedRegistry};
|
||||||
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
|
use cyberstorm::render::mdbook::MDBookEngine;
|
||||||
|
use cyberstorm::render::Renderer;
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
#[structopt(name = "cyberstorm")]
|
#[structopt(name = "cyberstorm")]
|
||||||
@ -20,13 +26,19 @@ struct Opt {
|
|||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
enum Cmd {
|
enum Cmd {
|
||||||
BuildMdbook(BuildMdbookOpt),
|
/// Renders the content into a mdBook.
|
||||||
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
|
MdbookRender(MdbookRenderOpt),
|
||||||
|
/// Renders the content into a mdBook and builds it.
|
||||||
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
|
MdbookBuild(MdbookRenderOpt),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
struct BuildMdbookOpt {
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
/// Path to the output MDBook directory.
|
struct MdbookRenderOpt {
|
||||||
#[structopt(parse(from_os_str))]
|
/// Path to the output mdBook root or `book.toml`.
|
||||||
|
#[structopt(parse(from_os_str), default_value = "./book.toml")]
|
||||||
book: PathBuf,
|
book: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,25 +60,97 @@ async fn run(opt: Opt) -> Result<(), Error> {
|
|||||||
RegistryOpt::Directory(path) => SupportedRegistry::Directory(DirectoryRegistry::new(path)),
|
RegistryOpt::Directory(path) => SupportedRegistry::Directory(DirectoryRegistry::new(path)),
|
||||||
};
|
};
|
||||||
match &opt.cmd {
|
match &opt.cmd {
|
||||||
Cmd::BuildMdbook(build_opt) => build_mdbook(&opt, build_opt, ®istry).await,
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
|
Cmd::MdbookRender(render_opt) => {
|
||||||
|
let (root, config) = load_mdbook_config(&render_opt.book).await?;
|
||||||
|
render_mdbook(®istry, &root, &config).await
|
||||||
|
}
|
||||||
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
|
Cmd::MdbookBuild(render_opt) => {
|
||||||
|
let (root, config) = load_mdbook_config(&render_opt.book).await?;
|
||||||
|
render_mdbook(®istry, &root, &config).await?;
|
||||||
|
mdbook::MDBook::load_with_config(root, config)?.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn build_mdbook(
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
_opt: &Opt,
|
async fn load_mdbook_config(book: &Path) -> Result<(PathBuf, mdbook::Config), Error> {
|
||||||
build_opt: &BuildMdbookOpt,
|
let (root, config_file) = book
|
||||||
|
.canonicalize()
|
||||||
|
.ok()
|
||||||
|
.and_then(|book| {
|
||||||
|
let (root, config) = if book.extension().is_some() {
|
||||||
|
(book.parent().unwrap().to_owned(), book.to_owned())
|
||||||
|
} else {
|
||||||
|
let config_file = book.join("book.toml");
|
||||||
|
(book, config_file)
|
||||||
|
};
|
||||||
|
if config.is_file() {
|
||||||
|
Some((root, config))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"expected mdbook config path (eg. `book.toml`) at `{}`",
|
||||||
|
book.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let config = fs::read_to_string(config_file)
|
||||||
|
.await
|
||||||
|
.context("failed to read mdbook configuration")?
|
||||||
|
.parse()?;
|
||||||
|
|
||||||
|
Ok((root, config))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
|
async fn render_mdbook(
|
||||||
registry: &SupportedRegistry,
|
registry: &SupportedRegistry,
|
||||||
|
root: &Path,
|
||||||
|
config: &mdbook::Config,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let engine = MDBookEngine::new(&build_opt.book).context("failed to load mdbook engine")?;
|
let engine = MDBookEngine::new(root, config);
|
||||||
Generator::new(engine, registry)
|
Renderer::new(engine, registry)
|
||||||
.generate()
|
.generate()
|
||||||
.await
|
.await
|
||||||
.context("failed to generate mdbook content")
|
.context("failed to generate mdbook content")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn init_logger() {
|
||||||
|
let mut builder = env_logger::Builder::new();
|
||||||
|
|
||||||
|
builder.format(|formatter, record| {
|
||||||
|
writeln!(
|
||||||
|
formatter,
|
||||||
|
"{} [{}] ({}): {}",
|
||||||
|
Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||||
|
record.level(),
|
||||||
|
record.target(),
|
||||||
|
record.args()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Ok(var) = env::var("RUST_LOG") {
|
||||||
|
builder.parse_filters(&var);
|
||||||
|
} else {
|
||||||
|
// if no RUST_LOG provided, default to logging at the Info level
|
||||||
|
builder.filter(None, LevelFilter::Info);
|
||||||
|
// Filter extraneous html5ever not-implemented messages
|
||||||
|
builder.filter(Some("html5ever"), LevelFilter::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.init();
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
init_logger();
|
||||||
|
|
||||||
if let Err(err) = run(Opt::from_args()).await {
|
if let Err(err) = run(Opt::from_args()).await {
|
||||||
eprintln!("{:#}", err);
|
log::error!("{:#}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ use std::{io, mem};
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use mdbook::config::{Config as MDBookConfig, HtmlConfig};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tera::{Context, Map, Tera, Value};
|
use tera::{Context, Map, Tera, Value};
|
||||||
@ -18,7 +19,9 @@ use crate::domains::common::{DomainId, DomainModel, ModelId};
|
|||||||
use crate::domains::GenericDocument;
|
use crate::domains::GenericDocument;
|
||||||
use crate::registry::{Registry, RegistryConfig};
|
use crate::registry::{Registry, RegistryConfig};
|
||||||
|
|
||||||
use crate::generator::{Engine, GeneratorError};
|
use crate::render::{Engine, RendererError};
|
||||||
|
|
||||||
|
type DocumentMap = HashMap<DomainId, Map<String, Value>>;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum MDBookEngineError {
|
pub enum MDBookEngineError {
|
||||||
@ -28,9 +31,9 @@ pub enum MDBookEngineError {
|
|||||||
Tera(#[from] tera::Error),
|
Tera(#[from] tera::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<MDBookEngineError> for GeneratorError {
|
impl From<MDBookEngineError> for RendererError {
|
||||||
fn from(err: MDBookEngineError) -> Self {
|
fn from(err: MDBookEngineError) -> Self {
|
||||||
GeneratorError::Engine(err.into())
|
RendererError::Engine(err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,39 +52,50 @@ pub struct MDBookEngine {
|
|||||||
enum Inner {
|
enum Inner {
|
||||||
Start {
|
Start {
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
|
global: Map<String, Value>,
|
||||||
},
|
},
|
||||||
LoadAndRender {
|
LoadAndRender {
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
|
global: Map<String, Value>,
|
||||||
templates: Tera,
|
templates: Tera,
|
||||||
summary: Vec<SummaryItem>,
|
summary: Vec<SummaryItem>,
|
||||||
documents: HashMap<DomainId, Value>,
|
documents: DocumentMap,
|
||||||
},
|
},
|
||||||
Finish,
|
Finish,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MDBookEngine {
|
impl MDBookEngine {
|
||||||
pub fn new(src: &Path) -> Result<Self, MDBookEngineError> {
|
pub fn new(root: &Path, config: &MDBookConfig) -> Self {
|
||||||
Ok(Self {
|
let mut global = Map::new();
|
||||||
|
if let Ok(Some(HtmlConfig { site_url, .. })) = config.get_deserialized_opt("output.html") {
|
||||||
|
global.insert(
|
||||||
|
"site_url".to_owned(),
|
||||||
|
site_url.as_deref().unwrap_or("/").to_owned().into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Self {
|
||||||
inner: Inner::Start {
|
inner: Inner::Start {
|
||||||
src: src.canonicalize()?,
|
src: root.join(&config.book.src),
|
||||||
|
global,
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// (?Send)
|
// (?Send)
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Engine for MDBookEngine {
|
impl Engine for MDBookEngine {
|
||||||
async fn start<R>(&mut self, _registry: &R) -> Result<(), GeneratorError>
|
async fn start<R>(&mut self, _registry: &R) -> Result<(), RendererError>
|
||||||
where
|
where
|
||||||
R: Registry,
|
R: Registry,
|
||||||
{
|
{
|
||||||
if let Inner::Start { src } = mem::replace(&mut self.inner, Inner::Finish) {
|
if let Inner::Start { src, global } = mem::replace(&mut self.inner, Inner::Finish) {
|
||||||
let templates = load_templates(&src).await?;
|
let templates = load_templates(&src).await?;
|
||||||
let documents = HashMap::new();
|
let documents = HashMap::new();
|
||||||
let summary = Vec::new();
|
let summary = Vec::new();
|
||||||
self.inner = Inner::LoadAndRender {
|
self.inner = Inner::LoadAndRender {
|
||||||
src,
|
src,
|
||||||
|
global,
|
||||||
templates,
|
templates,
|
||||||
documents,
|
documents,
|
||||||
summary,
|
summary,
|
||||||
@ -95,7 +109,7 @@ impl Engine for MDBookEngine {
|
|||||||
model_id: ModelId,
|
model_id: ModelId,
|
||||||
doc: GenericDocument,
|
doc: GenericDocument,
|
||||||
_registry: &R,
|
_registry: &R,
|
||||||
) -> Result<(), GeneratorError>
|
) -> Result<(), RendererError>
|
||||||
where
|
where
|
||||||
R: Registry,
|
R: Registry,
|
||||||
{
|
{
|
||||||
@ -140,12 +154,13 @@ impl Engine for MDBookEngine {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn finish<R>(&mut self, registry: &R) -> Result<(), GeneratorError>
|
async fn finish<R>(&mut self, registry: &R) -> Result<(), RendererError>
|
||||||
where
|
where
|
||||||
R: Registry,
|
R: Registry,
|
||||||
{
|
{
|
||||||
if let Inner::LoadAndRender {
|
if let Inner::LoadAndRender {
|
||||||
src,
|
src,
|
||||||
|
global,
|
||||||
mut templates,
|
mut templates,
|
||||||
documents,
|
documents,
|
||||||
summary,
|
summary,
|
||||||
@ -154,7 +169,7 @@ impl Engine for MDBookEngine {
|
|||||||
let summary = Arc::new(summary);
|
let summary = Arc::new(summary);
|
||||||
let documents = Arc::new(documents);
|
let documents = Arc::new(documents);
|
||||||
let registry_config = registry.get_config().await?;
|
let registry_config = registry.get_config().await?;
|
||||||
register_filters(&mut templates, documents.clone(), registry_config);
|
register_filters(&mut templates, documents.clone(), registry_config, global);
|
||||||
render_indexes(&templates, &src, &summary).await?;
|
render_indexes(&templates, &src, &summary).await?;
|
||||||
render_summary(&templates, &src, &summary).await?;
|
render_summary(&templates, &src, &summary).await?;
|
||||||
render_documents(&templates, &src, documents).await?;
|
render_documents(&templates, &src, documents).await?;
|
||||||
@ -190,17 +205,17 @@ async fn load_templates(src: &Path) -> Result<Tera, MDBookEngineError> {
|
|||||||
|
|
||||||
fn save_domain_model<M>(
|
fn save_domain_model<M>(
|
||||||
summary: &mut Vec<SummaryItem>,
|
summary: &mut Vec<SummaryItem>,
|
||||||
documents: &mut HashMap<DomainId, Value>,
|
documents: &mut DocumentMap,
|
||||||
model_id: ModelId,
|
model_id: ModelId,
|
||||||
doc: &Document<M>,
|
doc: &Document<M>,
|
||||||
) where
|
) where
|
||||||
M: DomainModel,
|
M: DomainModel,
|
||||||
{
|
{
|
||||||
let id = DomainId::new(M::kind(), model_id);
|
let id = DomainId::new(M::kind(), model_id);
|
||||||
let mut value = Map::new();
|
if let Value::Object(mut document) = tera::to_value(&doc).unwrap() {
|
||||||
value.insert("id".to_owned(), tera::to_value(&id).unwrap());
|
document.insert("id".to_owned(), tera::to_value(&id).unwrap());
|
||||||
value.insert("doc".to_owned(), tera::to_value(&doc).unwrap());
|
documents.insert(id, document);
|
||||||
documents.insert(id, value.into());
|
}
|
||||||
summary.push(SummaryItem {
|
summary.push(SummaryItem {
|
||||||
id,
|
id,
|
||||||
name: doc.meta.name().to_owned(),
|
name: doc.meta.name().to_owned(),
|
||||||
@ -210,9 +225,9 @@ fn save_domain_model<M>(
|
|||||||
async fn render_documents(
|
async fn render_documents(
|
||||||
templates: &Tera,
|
templates: &Tera,
|
||||||
src: &Path,
|
src: &Path,
|
||||||
documents: Arc<HashMap<DomainId, Value>>,
|
documents: Arc<DocumentMap>,
|
||||||
) -> Result<(), MDBookEngineError> {
|
) -> Result<(), MDBookEngineError> {
|
||||||
for (id, value) in documents.iter() {
|
for (id, document) in documents.iter() {
|
||||||
let (domain, model) = id.kind.parts();
|
let (domain, model) = id.kind.parts();
|
||||||
let model_path = format!("{}/{}", domain, model);
|
let model_path = format!("{}/{}", domain, model);
|
||||||
let template_path = format!("{}.instance.tera", model_path);
|
let template_path = format!("{}.instance.tera", model_path);
|
||||||
@ -220,7 +235,8 @@ async fn render_documents(
|
|||||||
.join(model_path)
|
.join(model_path)
|
||||||
.join(id.model_id.to_string())
|
.join(id.model_id.to_string())
|
||||||
.with_extension("md");
|
.with_extension("md");
|
||||||
let context = Context::from_value(value.clone())?;
|
let mut context = Context::new();
|
||||||
|
context.insert("doc", &document);
|
||||||
let contents = templates.render(&template_path, &context)?;
|
let contents = templates.render(&template_path, &context)?;
|
||||||
fs::write(output_path, contents).await?;
|
fs::write(output_path, contents).await?;
|
||||||
}
|
}
|
||||||
@ -284,11 +300,20 @@ async fn render_indexes(
|
|||||||
|
|
||||||
fn register_filters(
|
fn register_filters(
|
||||||
tera: &mut Tera,
|
tera: &mut Tera,
|
||||||
registry: Arc<HashMap<DomainId, Value>>,
|
documents: Arc<DocumentMap>,
|
||||||
registry_config: Arc<RegistryConfig>,
|
registry_config: Arc<RegistryConfig>,
|
||||||
|
global: Map<String, Value>,
|
||||||
) {
|
) {
|
||||||
use tera::{try_get_value, Error};
|
use tera::{try_get_value, Error};
|
||||||
|
|
||||||
|
tera.register_function("global", move |args: &HashMap<String, Value>| {
|
||||||
|
if let Some(Value::String(key)) = args.get("key") {
|
||||||
|
Ok(global.get(key).cloned().unwrap_or(Value::Null))
|
||||||
|
} else {
|
||||||
|
Err(Error::msg("expected `key` for `global` function"))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
fn parse_domain_id(value: &Value) -> Result<DomainId, Error> {
|
fn parse_domain_id(value: &Value) -> Result<DomainId, Error> {
|
||||||
let id = try_get_value!("link", "value", String, value);
|
let id = try_get_value!("link", "value", String, value);
|
||||||
id.parse()
|
id.parse()
|
||||||
@ -301,8 +326,8 @@ fn register_filters(
|
|||||||
let id = parse_domain_id(value)?;
|
let id = parse_domain_id(value)?;
|
||||||
let (domain, model) = id.kind.parts();
|
let (domain, model) = id.kind.parts();
|
||||||
let link = match args.get("for").and_then(|v| v.as_str()) {
|
let link = match args.get("for").and_then(|v| v.as_str()) {
|
||||||
None => format!("/{}/{}/{}.md", domain, model, id.model_id),
|
None => format!("{}/{}/{}.md", domain, model, id.model_id),
|
||||||
Some("model") => format!("/{}/{}.md", domain, model),
|
Some("model") => format!("{}/{}.md", domain, model),
|
||||||
Some("edit") => registry_config.edit_link(id).map_err(Error::msg)?,
|
Some("edit") => registry_config.edit_link(id).map_err(Error::msg)?,
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
return Err(Error::msg(
|
return Err(Error::msg(
|
||||||
@ -331,20 +356,15 @@ fn register_filters(
|
|||||||
"get_doc",
|
"get_doc",
|
||||||
move |value: &Value, _: &HashMap<String, Value>| {
|
move |value: &Value, _: &HashMap<String, Value>| {
|
||||||
let id = parse_domain_id(value)?;
|
let id = parse_domain_id(value)?;
|
||||||
registry
|
documents
|
||||||
.get(&id)
|
.get(&id)
|
||||||
.cloned()
|
.cloned()
|
||||||
|
.map(Value::Object)
|
||||||
.ok_or_else(|| Error::msg(format_args!("unknown document id {}", id)))
|
.ok_or_else(|| Error::msg(format_args!("unknown document id {}", id)))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
tera.register_filter("autolink", |value: &Value, _: &HashMap<String, Value>| {
|
tera.register_filter("autolink", |value: &Value, _: &HashMap<String, Value>| {
|
||||||
let text = try_get_value!("autolink", "value", String, value);
|
|
||||||
|
|
||||||
if text.is_empty() {
|
|
||||||
return Ok(Value::String(String::new()));
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref URL: Regex = Regex::new(
|
static ref URL: Regex = Regex::new(
|
||||||
r"(?ix)
|
r"(?ix)
|
||||||
@ -353,9 +373,12 @@ fn register_filters(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
let text = try_get_value!("autolink", "value", String, value);
|
||||||
let replaced = URL.replace_all(text.as_str(), "[$0]($0)").into_owned();
|
let text = if text.is_empty() {
|
||||||
|
text
|
||||||
Ok(Value::String(replaced))
|
} else {
|
||||||
|
URL.replace_all(text.as_str(), "[$0]($0)").into_owned()
|
||||||
|
};
|
||||||
|
Ok(Value::String(text))
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
#[cfg(feature = "mdbook-renderer")]
|
||||||
pub mod mdbook;
|
pub mod mdbook;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@ -9,19 +10,19 @@ use crate::domains::GenericDocument;
|
|||||||
use crate::registry::{Registry, RegistryError};
|
use crate::registry::{Registry, RegistryError};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum GeneratorError {
|
pub enum RendererError {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Engine(#[from] anyhow::Error),
|
Engine(#[from] anyhow::Error),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Registry(#[from] RegistryError),
|
Registry(#[from] RegistryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Generator<E, R> {
|
pub struct Renderer<E, R> {
|
||||||
engine: E,
|
engine: E,
|
||||||
registry: R,
|
registry: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E, R> Generator<E, R>
|
impl<E, R> Renderer<E, R>
|
||||||
where
|
where
|
||||||
E: Engine,
|
E: Engine,
|
||||||
R: Registry,
|
R: Registry,
|
||||||
@ -30,7 +31,8 @@ where
|
|||||||
Self { engine, registry }
|
Self { engine, registry }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate(mut self) -> Result<(), GeneratorError> {
|
pub async fn generate(mut self) -> Result<(), RendererError> {
|
||||||
|
log::info!("Rendering of content has started");
|
||||||
self.engine.start(&self.registry).await?;
|
self.engine.start(&self.registry).await?;
|
||||||
macro_rules! push_documents {
|
macro_rules! push_documents {
|
||||||
($($kind:ident),+) => {
|
($($kind:ident),+) => {
|
||||||
@ -62,10 +64,12 @@ where
|
|||||||
MitigatePlatform,
|
MitigatePlatform,
|
||||||
MitigateConfiguration
|
MitigateConfiguration
|
||||||
);
|
);
|
||||||
self.engine.finish(&self.registry).await
|
self.engine.finish(&self.registry).await?;
|
||||||
|
log::info!("Rendering of content has finished");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_documents<M>(&self) -> Result<Vec<(DomainId, Document<M>)>, GeneratorError>
|
async fn get_documents<M>(&self) -> Result<Vec<(DomainId, Document<M>)>, RendererError>
|
||||||
where
|
where
|
||||||
M: DomainModel,
|
M: DomainModel,
|
||||||
{
|
{
|
||||||
@ -76,13 +80,13 @@ where
|
|||||||
docs.sort_by_key(|(id, _)| *id);
|
docs.sort_by_key(|(id, _)| *id);
|
||||||
docs
|
docs
|
||||||
})
|
})
|
||||||
.map_err(GeneratorError::Registry)
|
.map_err(RendererError::Registry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Engine {
|
pub trait Engine {
|
||||||
async fn start<R>(&mut self, registry: &R) -> Result<(), GeneratorError>
|
async fn start<R>(&mut self, registry: &R) -> Result<(), RendererError>
|
||||||
where
|
where
|
||||||
R: Registry;
|
R: Registry;
|
||||||
|
|
||||||
@ -91,11 +95,11 @@ pub trait Engine {
|
|||||||
id: ModelId,
|
id: ModelId,
|
||||||
doc: GenericDocument,
|
doc: GenericDocument,
|
||||||
registry: &R,
|
registry: &R,
|
||||||
) -> Result<(), GeneratorError>
|
) -> Result<(), RendererError>
|
||||||
where
|
where
|
||||||
R: Registry;
|
R: Registry;
|
||||||
|
|
||||||
async fn finish<R>(&mut self, registry: &R) -> Result<(), GeneratorError>
|
async fn finish<R>(&mut self, registry: &R) -> Result<(), RendererError>
|
||||||
where
|
where
|
||||||
R: Registry;
|
R: Registry;
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user