more improvements
This commit is contained in:
parent
738285dced
commit
3bc02dcb02
@ -7,7 +7,7 @@ publish = false
|
|||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["cli"]
|
default = ["cli", "mdbook-renderer"]
|
||||||
cli = ["structopt", "tokio/macros", "tokio/rt-multi-thread"]
|
cli = ["structopt", "tokio/macros", "tokio/rt-multi-thread"]
|
||||||
mdbook-renderer = ["mdbook", "includedir", "includedir_codegen", "glob", "phf"]
|
mdbook-renderer = ["mdbook", "includedir", "includedir_codegen", "glob", "phf"]
|
||||||
|
|
||||||
|
@ -6,18 +6,26 @@
|
|||||||
{% 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 -%}
|
||||||
- {{ ref | autolink }}
|
{%- if ref is url -%}
|
||||||
|
- <{{ ref }}>
|
||||||
|
{%- else -%}
|
||||||
|
- {{ ref }}
|
||||||
|
{%- endif -%}
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
{% endmacro references %}
|
{% endmacro references %}
|
||||||
|
|
||||||
{% macro doc_title(doc) -%}
|
{% macro doc_title(doc) -%}
|
||||||
{{ doc.name }} <small>([edit]({{ doc.id | domain_id_link(for="edit") }}))</small>
|
{{ document.name }} <small>([edit]({{ link_for(for="document", id=document.id, edit=true) }}))</small>
|
||||||
{%- endmacro doc_title %}
|
{%- endmacro doc_title %}
|
||||||
|
|
||||||
|
{% macro model_title(domain, model) -%}
|
||||||
|
{{ model | capitalize }}
|
||||||
|
{%- endmacro model_title %}
|
||||||
|
|
||||||
{% macro doc_details(doc) -%}
|
{% macro doc_details(doc) -%}
|
||||||
| Title | {{ doc.name }} |
|
| Title | {{ document.name }} |
|
||||||
|:---------------------------:|:------------------------|
|
|:---------------------------:|:------------------------|
|
||||||
{{ self::doc_details_next(title="ID", value=doc.id)}}
|
{{ self::doc_details_next(title="ID", value=document.id) }}
|
||||||
{%- endmacro doc_details %}
|
{%- endmacro doc_details %}
|
||||||
|
|
||||||
{% macro doc_details_next(title, value) -%}
|
{% macro doc_details_next(title, value) -%}
|
||||||
@ -37,21 +45,21 @@
|
|||||||
{%- endmacro doc_details_tags %}
|
{%- endmacro doc_details_tags %}
|
||||||
|
|
||||||
{% macro doc_rich_link(doc) -%}
|
{% macro doc_rich_link(doc) -%}
|
||||||
{{ doc.name }} ([{{ doc.id }}]({{ global(key="site_url")}}{{ doc.id | domain_id_link }}))
|
{{ document.name }} ([{{ document.id }}]({{ link_for(for="document", id=document.id) }}))
|
||||||
{%- endmacro doc_rich_link %}
|
{%- endmacro doc_rich_link %}
|
||||||
|
|
||||||
{% macro summary_table(instances) -%}
|
{% macro summary_table(items) -%}
|
||||||
| ID | Name |
|
| ID | Name |
|
||||||
|:---------------------------:|:------------------------|
|
|:---------------------------:|:------------------------|
|
||||||
{% for item in instances -%}
|
{% for item in items -%}
|
||||||
| [{{ item.id }}]({{ global(key="site_url") }}{{ item.id | domain_id_link }}) | {{ item.name }} |
|
| [{{ item.id }}]({{ link_for(for="document", id=item.id) }}) | {{ item.name }} |
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{%- endmacro summary_table %}
|
{%- endmacro summary_table %}
|
||||||
|
|
||||||
{% macro summary_list(instances) %}
|
{% macro summary_list(items) %}
|
||||||
{%- set last_domain = "" -%}
|
{%- set last_domain = "" -%}
|
||||||
{%- set last_model = "" -%}
|
{%- set last_model = "" -%}
|
||||||
{%- for item in summary -%}
|
{%- for item in items -%}
|
||||||
{%- set id_parts = item.id | domain_id -%}
|
{%- set id_parts = item.id | domain_id -%}
|
||||||
{%- if last_domain != id_parts.domain -%}
|
{%- if last_domain != id_parts.domain -%}
|
||||||
{%- set_global last_domain = id_parts.domain %}
|
{%- set_global last_domain = id_parts.domain %}
|
||||||
@ -59,8 +67,13 @@
|
|||||||
{%- 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 }}]({{ id_parts.domain ~ "/" ~ id_parts.model ~ ".md" }})
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
- [{{ item.name }}](./{{ item.id | domain_id_link }})
|
- [{{ item.name }}]({{ id_parts.domain ~ "/" ~ id_parts.model ~ "/" ~id_parts.instance ~ ".md" }})
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{% endmacro summary_list %}
|
{% endmacro summary_list %}
|
||||||
|
|
||||||
|
{% macro registry_content(key, default="") %}
|
||||||
|
{%- set content = global(key="registry") | get(key="content") %}
|
||||||
|
{%- if content is containing(key) %}{{ get(key=key) }}{% else %}{{ default }}{% endif -%}
|
||||||
|
{% endmacro registry_content %}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# {{ macros::doc_title(doc=doc) }}
|
# {{ macros::doc_title(doc=document) }}
|
||||||
|
|
||||||
{{ macros::doc_details(doc=doc) }}
|
{{ macros::doc_details(doc=document) }}
|
||||||
{%- if doc.WindowsEvent %}
|
{%- if document.WindowsEvent %}
|
||||||
{{ macros::doc_details_next(title="Type", value="Windows event") }}
|
{{ macros::doc_details_next(title="Type", value="Windows event") }}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
{{ macros::doc_details_next(title="Description", value=doc.description) }}
|
{{ macros::doc_details_next(title="Description", value=document.description) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
{{ macros::content(content=doc.content) }}
|
{{ macros::content(content=document.content) }}
|
||||||
|
|
||||||
{% if doc.WindowsEvent %}
|
{% if document.WindowsEvent %}
|
||||||
## Samples
|
## Samples
|
||||||
{% for sample in doc.WindowsEvent.sample %}
|
{% for sample in document.WindowsEvent.sample %}
|
||||||
```xml
|
```xml
|
||||||
{{ sample.xml }}
|
{{ sample.xml }}
|
||||||
```
|
```
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# Event
|
# {{ macros::model_title(domain="observe", model="event") }}
|
||||||
|
|
||||||
|
{% if content %}
|
||||||
|
{{ content }}
|
||||||
|
{% else %}
|
||||||
Observable items use to detect and respond to threat actor behaviour.
|
Observable items use to detect and respond to threat actor behaviour.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ macros::summary_table(instances=instances) }}
|
{{ macros::summary_table(items=instances) }}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
{% set stage = doc.stage | get_doc %}
|
{% set stage = document(id=document.stage) %}
|
||||||
|
|
||||||
# {{ macros::doc_title(doc=doc) }}
|
# {{ macros::doc_title(doc=document) }}
|
||||||
|
|
||||||
{{ macros::doc_details(doc=doc) }}
|
{{ macros::doc_details(doc=document) }}
|
||||||
{{ macros::doc_details_next(title="Stage", value=macros::doc_rich_link(doc=stage)) }}
|
{{ macros::doc_details_next(title="Stage", value=macros::doc_rich_link(doc=stage)) }}
|
||||||
{{ macros::doc_details_next(title="Description", value=doc.description) }}
|
{{ macros::doc_details_next(title="Description", value=document.description) }}
|
||||||
{{ macros::doc_details_tags(tags=doc.tags) }}
|
{{ macros::doc_details_tags(tags=document.tags) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
{{ macros::content(content=doc.content) }}
|
{{ macros::content(content=document.content) }}
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
{{ macros::references(refs=doc.references) }}
|
{{ macros::references(refs=document.references) }}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# Action
|
# {{ macros::model_title(domain="react", model="action") }}
|
||||||
|
|
||||||
|
{% if content %}
|
||||||
|
{{ content }}
|
||||||
|
{% else %}
|
||||||
An atomic human action assigned to an response stage.
|
An atomic human action assigned to an response stage.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ macros::summary_table(instances=instances) }}
|
{{ macros::summary_table(items=instances) }}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# {{ macros::doc_title(doc=doc) }}
|
# {{ macros::doc_title(doc=document) }}
|
||||||
|
|
||||||
{{ macros::doc_details(doc=doc) }}
|
{{ macros::doc_details(doc=document) }}
|
||||||
{{ macros::doc_details_next(title="Description", value=doc.description) }}
|
{{ macros::doc_details_next(title="Description", value=document.description) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
{{ macros::content(content=doc.content) }}
|
{{ macros::content(content=document.content) }}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# Stage
|
# {{ macros::model_title(domain="react", model="stage") }}
|
||||||
|
|
||||||
|
{% if content %}
|
||||||
|
{{ content }}
|
||||||
|
{% else %}
|
||||||
Phase of a response to observed threat behaviour.
|
Phase of a response to observed threat behaviour.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ macros::summary_table(instances=instances) }}
|
{{ macros::summary_table(items=instances) }}
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
{% set provider = document(id=document.provider) %}
|
||||||
|
|
||||||
# {{ macros::doc_title(doc=doc) }}
|
# {{ macros::doc_title(doc=document) }}
|
||||||
|
|
||||||
{{ macros::doc_details(doc=doc) }}
|
{{ macros::doc_details(doc=document) }}
|
||||||
{{ macros::doc_details_next(title="Provider", value=doc.provider) }}
|
{{ macros::doc_details_next(title="Provider", value=macros::doc_rich_link(doc=provider)) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
{{ macros::content(content=doc.content) }}
|
{{ macros::content(content=document.content) }}
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
{{ macros::references(refs=doc.references) }}
|
{{ macros::references(refs=document.references) }}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# Intelligence
|
# {{ macros::model_title(domain="source", model="intelligence") }}
|
||||||
|
|
||||||
|
{% if content %}
|
||||||
|
{{ content }}
|
||||||
|
{% else %}
|
||||||
Feed or result of a query used to satisfy intelligence requirements.
|
Feed or result of a query used to satisfy intelligence requirements.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ macros::summary_table(instances=instances) }}
|
{{ macros::summary_table(items=instances) }}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# {{ macros::doc_title(doc=doc) }}
|
# {{ macros::doc_title(doc=document) }}
|
||||||
|
|
||||||
{{ macros::doc_details(doc=doc) }}
|
{{ macros::doc_details(doc=document) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
{{ macros::content(content=doc.content) }}
|
{{ macros::content(content=document.content) }}
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
{{ macros::references(refs=doc.references) }}
|
{{ macros::references(refs=document.references) }}
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# Provider
|
# {{ macros::model_title(domain="source", model="provider") }}
|
||||||
|
|
||||||
|
{% if content %}
|
||||||
|
{{ content }}
|
||||||
|
{% else %}
|
||||||
An internal or external supplier of intelligence.
|
An internal or external supplier of intelligence.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ macros::summary_table(instances=instances) }}
|
{{ macros::registry_content(key="source-provider") }}
|
||||||
|
|
||||||
|
{{ macros::summary_table(items=instances) }}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# {{ macros::doc_title(doc=doc) }}
|
# {{ macros::doc_title(doc=document) }}
|
||||||
|
|
||||||
{{ macros::doc_details(doc=doc) }}
|
{{ macros::doc_details(doc=document) }}
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
{{ macros::content(content=doc.content) }}
|
{{ macros::content(content=document.content) }}
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
{{ macros::references(refs=doc.references) }}
|
{{ macros::references(refs=document.references) }}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
{% import "macros.tera" as macros %}
|
{% import "macros.tera" as macros %}
|
||||||
|
|
||||||
# Requirement
|
# {{ macros::model_title(domain="source", model="requirement") }}
|
||||||
|
|
||||||
|
{% if content %}
|
||||||
|
{{ content }}
|
||||||
|
{% else %}
|
||||||
Collection of intelligence used by actions, detections and mitigations.
|
Collection of intelligence used by actions, detections and mitigations.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ macros::summary_table(instances=instances) }}
|
{{ macros::summary_table(items=instances) }}
|
||||||
|
@ -4,4 +4,4 @@
|
|||||||
|
|
||||||
[STORM](./index.md)
|
[STORM](./index.md)
|
||||||
|
|
||||||
{{ macros::summary_list(instances=summary) }}
|
{{ macros::summary_list(items=summary) }}
|
||||||
|
@ -1 +1,5 @@
|
|||||||
edit-link = "https://localhost/registry/{{domain}}/{{model}}/{{model_id}}.md"
|
[edit]
|
||||||
|
page = "https://localhost/{{page}}.md"
|
||||||
|
document = "https://localhost/registry/{{domain}}/{{model}}/{{instance}}.md"
|
||||||
|
|
||||||
|
[content]
|
||||||
|
@ -115,12 +115,12 @@ impl DomainModelKind {
|
|||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
||||||
pub struct ModelId {
|
pub struct Instance {
|
||||||
pub major: u16,
|
pub major: u16,
|
||||||
pub minor: Option<NonZeroU16>,
|
pub minor: Option<NonZeroU16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelId {
|
impl Instance {
|
||||||
pub fn new(major: u16) -> Self {
|
pub fn new(major: u16) -> Self {
|
||||||
Self { major, minor: None }
|
Self { major, minor: None }
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ impl ModelId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ModelId {
|
impl fmt::Display for Instance {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{:0>5}", self.major)?;
|
write!(f, "{:0>5}", self.major)?;
|
||||||
if let Some(minor) = self.minor {
|
if let Some(minor) = self.minor {
|
||||||
@ -140,7 +140,7 @@ impl fmt::Display for ModelId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ModelId {
|
impl FromStr for Instance {
|
||||||
type Err = ParseIntError;
|
type Err = ParseIntError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
@ -153,7 +153,7 @@ impl FromStr for ModelId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for ModelId {
|
impl Serialize for Instance {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
@ -162,7 +162,7 @@ impl Serialize for ModelId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for ModelId {
|
impl<'de> Deserialize<'de> for Instance {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
@ -177,12 +177,12 @@ impl<'de> Deserialize<'de> for ModelId {
|
|||||||
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
||||||
pub struct DomainId {
|
pub struct DomainId {
|
||||||
pub kind: DomainModelKind,
|
pub kind: DomainModelKind,
|
||||||
pub model_id: ModelId,
|
pub instance: Instance,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DomainId {
|
impl DomainId {
|
||||||
pub fn new(kind: DomainModelKind, model_id: ModelId) -> Self {
|
pub fn new(kind: DomainModelKind, instance: Instance) -> Self {
|
||||||
Self { kind, model_id }
|
Self { kind, instance }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +196,7 @@ impl fmt::Debug for DomainId {
|
|||||||
|
|
||||||
impl fmt::Display for DomainId {
|
impl fmt::Display for DomainId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}{}", <&'static str>::from(&self.kind), self.model_id)
|
write!(f, "{}{}", <&'static str>::from(&self.kind), self.instance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,11 +205,12 @@ impl FromStr for DomainId {
|
|||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
if s.len() > 3 && s.is_char_boundary(3) {
|
if s.len() > 3 && s.is_char_boundary(3) {
|
||||||
let (kind, model_id) = s.split_at(3);
|
let (kind, instance) = s.split_at(3);
|
||||||
if let (Ok(kind), Ok(model_id)) =
|
if let (Ok(kind), Ok(instance)) = (
|
||||||
(DomainModelKind::from_str(kind), ModelId::from_str(model_id))
|
DomainModelKind::from_str(kind),
|
||||||
{
|
Instance::from_str(instance),
|
||||||
return Ok(Self { kind, model_id });
|
) {
|
||||||
|
return Ok(Self { kind, instance });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err("invalid storm id")
|
Err("invalid storm id")
|
||||||
|
@ -27,7 +27,7 @@ impl DirectoryRegistry {
|
|||||||
fn domain_id_path(&self, id: DomainId) -> PathBuf {
|
fn domain_id_path(&self, id: DomainId) -> PathBuf {
|
||||||
let (domain, model) = id.kind.parts();
|
let (domain, model) = id.kind.parts();
|
||||||
self.path
|
self.path
|
||||||
.join(format!("{}/{}/{}.md", domain, model, id.model_id))
|
.join(format!("{}/{}/{}.md", domain, model, id.instance))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,11 +41,11 @@ impl Registry for DirectoryRegistry {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_document<M>(&self, model_id: ModelId) -> Result<Document<M>, RegistryError>
|
async fn get_document<M>(&self, instance: Instance) -> Result<Document<M>, RegistryError>
|
||||||
where
|
where
|
||||||
M: DomainModel,
|
M: DomainModel,
|
||||||
{
|
{
|
||||||
let id = DomainId::new(M::kind(), model_id);
|
let id = DomainId::new(M::kind(), instance);
|
||||||
let path = self.domain_id_path(id);
|
let path = self.domain_id_path(id);
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
return Err(RegistryError::NotFound(id));
|
return Err(RegistryError::NotFound(id));
|
||||||
@ -61,7 +61,7 @@ impl Registry for DirectoryRegistry {
|
|||||||
let ids = self.get_document_ids(M::kind()).await?;
|
let ids = self.get_document_ids(M::kind()).await?;
|
||||||
let mut docs = Vec::with_capacity(ids.len());
|
let mut docs = Vec::with_capacity(ids.len());
|
||||||
for id in ids {
|
for id in ids {
|
||||||
docs.push((id, self.get_document(id.model_id).await?));
|
docs.push((id, self.get_document(id.instance).await?));
|
||||||
}
|
}
|
||||||
Ok(docs)
|
Ok(docs)
|
||||||
}
|
}
|
||||||
@ -80,9 +80,9 @@ impl Registry for DirectoryRegistry {
|
|||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
let filestem = path.file_stem().unwrap().to_string_lossy();
|
let filestem = path.file_stem().unwrap().to_string_lossy();
|
||||||
let model_id: ModelId = filestem.as_ref().parse().unwrap();
|
let instance: Instance = filestem.as_ref().parse().unwrap();
|
||||||
if !model_id.is_example() {
|
if !instance.is_example() {
|
||||||
ids.push(DomainId::new(kind, model_id))
|
ids.push(DomainId::new(kind, instance))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,13 +91,13 @@ impl Registry for DirectoryRegistry {
|
|||||||
|
|
||||||
async fn put_document<M>(
|
async fn put_document<M>(
|
||||||
&self,
|
&self,
|
||||||
model_id: ModelId,
|
instance: Instance,
|
||||||
document: Document<M>,
|
document: Document<M>,
|
||||||
) -> Result<(), RegistryError>
|
) -> Result<(), RegistryError>
|
||||||
where
|
where
|
||||||
M: DomainModel,
|
M: DomainModel,
|
||||||
{
|
{
|
||||||
let path = self.domain_id_path(DomainId::new(M::kind(), model_id));
|
let path = self.domain_id_path(DomainId::new(M::kind(), instance));
|
||||||
let document = document.to_string();
|
let document = document.to_string();
|
||||||
fs::write(path, document.as_str()).await?;
|
fs::write(path, document.as_str()).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
mod directory;
|
mod directory;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -32,28 +33,47 @@ impl From<std::io::Error> for RegistryError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct EditLinks {
|
||||||
|
page: Option<String>,
|
||||||
|
document: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct RegistryConfig {
|
pub struct RegistryConfig {
|
||||||
edit_link: Option<String>,
|
pub edit: EditLinks,
|
||||||
|
pub content: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_edit_link(
|
||||||
|
name: &str,
|
||||||
|
template: Option<&str>,
|
||||||
|
context: Context,
|
||||||
|
) -> Result<String, RegistryError> {
|
||||||
|
let template =
|
||||||
|
template.ok_or_else(|| RegistryError::Config(anyhow!("link for `{}` not set", name)))?;
|
||||||
|
Tera::one_off(template, &context, false)
|
||||||
|
.context("error rendering edit link")
|
||||||
|
.map_err(RegistryError::Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegistryConfig {
|
impl RegistryConfig {
|
||||||
pub fn edit_link(&self, domain_id: DomainId) -> Result<String, RegistryError> {
|
pub fn document_edit_link(&self, domain_id: DomainId) -> Result<String, RegistryError> {
|
||||||
let edit_link = self
|
|
||||||
.edit_link
|
|
||||||
.as_deref()
|
|
||||||
.ok_or_else(|| RegistryError::Config(anyhow!("edit-link option not set")))?;
|
|
||||||
let (domain, model) = domain_id.kind.parts();
|
let (domain, model) = domain_id.kind.parts();
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.insert("id", &domain_id);
|
context.insert("id", &domain_id);
|
||||||
context.insert("domain", domain);
|
context.insert("domain", domain);
|
||||||
context.insert("model", model);
|
context.insert("model", model);
|
||||||
context.insert("model_id", &domain_id.model_id);
|
context.insert("instance", &domain_id.instance);
|
||||||
|
build_edit_link("document", self.edit.document.as_deref(), context)
|
||||||
|
}
|
||||||
|
|
||||||
Tera::one_off(edit_link, &context, false)
|
pub fn page_edit_link(&self, page: &str) -> Result<String, RegistryError> {
|
||||||
.context("error rendering edit link")
|
let mut context = Context::new();
|
||||||
.map_err(RegistryError::Config)
|
context.insert("page", &page);
|
||||||
|
build_edit_link("page", self.edit.page.as_deref(), context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +81,7 @@ impl RegistryConfig {
|
|||||||
pub trait Registry: Send + Sync {
|
pub trait Registry: Send + Sync {
|
||||||
async fn get_config(&self) -> Result<Arc<RegistryConfig>, RegistryError>;
|
async fn get_config(&self) -> Result<Arc<RegistryConfig>, RegistryError>;
|
||||||
|
|
||||||
async fn get_document<M>(&self, model_id: ModelId) -> Result<Document<M>, RegistryError>
|
async fn get_document<M>(&self, instance: Instance) -> Result<Document<M>, RegistryError>
|
||||||
where
|
where
|
||||||
M: DomainModel;
|
M: DomainModel;
|
||||||
|
|
||||||
@ -74,7 +94,7 @@ pub trait Registry: Send + Sync {
|
|||||||
|
|
||||||
async fn put_document<M>(
|
async fn put_document<M>(
|
||||||
&self,
|
&self,
|
||||||
model_id: ModelId,
|
instance: Instance,
|
||||||
document: Document<M>,
|
document: Document<M>,
|
||||||
) -> Result<(), RegistryError>
|
) -> Result<(), RegistryError>
|
||||||
where
|
where
|
||||||
@ -90,11 +110,11 @@ where
|
|||||||
(**self).get_config().await
|
(**self).get_config().await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_document<M>(&self, model_id: ModelId) -> Result<Document<M>, RegistryError>
|
async fn get_document<M>(&self, instance: Instance) -> Result<Document<M>, RegistryError>
|
||||||
where
|
where
|
||||||
M: DomainModel,
|
M: DomainModel,
|
||||||
{
|
{
|
||||||
(**self).get_document(model_id).await
|
(**self).get_document(instance).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_documents<M>(&self) -> Result<Vec<(DomainId, Document<M>)>, RegistryError>
|
async fn get_documents<M>(&self) -> Result<Vec<(DomainId, Document<M>)>, RegistryError>
|
||||||
@ -113,13 +133,13 @@ where
|
|||||||
|
|
||||||
async fn put_document<M>(
|
async fn put_document<M>(
|
||||||
&self,
|
&self,
|
||||||
model_id: ModelId,
|
instance: Instance,
|
||||||
document: Document<M>,
|
document: Document<M>,
|
||||||
) -> Result<(), RegistryError>
|
) -> Result<(), RegistryError>
|
||||||
where
|
where
|
||||||
M: DomainModel,
|
M: DomainModel,
|
||||||
{
|
{
|
||||||
(**self).put_document(model_id, document).await
|
(**self).put_document(instance, document).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,12 +156,12 @@ impl Registry for SupportedRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_document<M>(&self, model_id: ModelId) -> Result<Document<M>, RegistryError>
|
async fn get_document<M>(&self, instance: Instance) -> Result<Document<M>, RegistryError>
|
||||||
where
|
where
|
||||||
M: DomainModel,
|
M: DomainModel,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Self::Directory(r) => r.get_document(model_id).await,
|
Self::Directory(r) => r.get_document(instance).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,14 +185,14 @@ impl Registry for SupportedRegistry {
|
|||||||
|
|
||||||
async fn put_document<M>(
|
async fn put_document<M>(
|
||||||
&self,
|
&self,
|
||||||
model_id: ModelId,
|
instance: Instance,
|
||||||
document: Document<M>,
|
document: Document<M>,
|
||||||
) -> Result<(), RegistryError>
|
) -> Result<(), RegistryError>
|
||||||
where
|
where
|
||||||
M: DomainModel,
|
M: DomainModel,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Self::Directory(r) => r.put_document(model_id, document).await,
|
Self::Directory(r) => r.put_document(instance, document).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ use tokio::fs;
|
|||||||
include!(concat!(env!("OUT_DIR"), "/mdbook-templates.rs"));
|
include!(concat!(env!("OUT_DIR"), "/mdbook-templates.rs"));
|
||||||
|
|
||||||
use crate::document::Document;
|
use crate::document::Document;
|
||||||
use crate::domains::common::{DomainId, DomainModel, ModelId};
|
use crate::domains::common::{DomainId, DomainModel, Instance};
|
||||||
use crate::domains::GenericDocument;
|
use crate::domains::GenericDocument;
|
||||||
use crate::registry::{Registry, RegistryConfig};
|
use crate::registry::{Registry, RegistryConfig};
|
||||||
|
|
||||||
@ -52,11 +52,11 @@ pub struct MDBookEngine {
|
|||||||
enum Inner {
|
enum Inner {
|
||||||
Start {
|
Start {
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
global: Map<String, Value>,
|
site_url: Option<String>,
|
||||||
},
|
},
|
||||||
LoadAndRender {
|
LoadAndRender {
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
global: Map<String, Value>,
|
site_url: Option<String>,
|
||||||
templates: Tera,
|
templates: Tera,
|
||||||
summary: Vec<SummaryItem>,
|
summary: Vec<SummaryItem>,
|
||||||
documents: DocumentMap,
|
documents: DocumentMap,
|
||||||
@ -66,17 +66,17 @@ enum Inner {
|
|||||||
|
|
||||||
impl MDBookEngine {
|
impl MDBookEngine {
|
||||||
pub fn new(root: &Path, config: &MDBookConfig) -> Self {
|
pub fn new(root: &Path, config: &MDBookConfig) -> Self {
|
||||||
let mut global = Map::new();
|
let site_url = if let Ok(Some(HtmlConfig { site_url, .. })) =
|
||||||
if let Ok(Some(HtmlConfig { site_url, .. })) = config.get_deserialized_opt("output.html") {
|
config.get_deserialized_opt("output.html")
|
||||||
global.insert(
|
{
|
||||||
"site_url".to_owned(),
|
site_url
|
||||||
site_url.as_deref().unwrap_or("/").to_owned().into(),
|
} else {
|
||||||
);
|
None
|
||||||
}
|
};
|
||||||
Self {
|
Self {
|
||||||
inner: Inner::Start {
|
inner: Inner::Start {
|
||||||
src: root.join(&config.book.src),
|
src: root.join(&config.book.src),
|
||||||
global,
|
site_url,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,13 +89,13 @@ impl Engine for MDBookEngine {
|
|||||||
where
|
where
|
||||||
R: Registry,
|
R: Registry,
|
||||||
{
|
{
|
||||||
if let Inner::Start { src, global } = mem::replace(&mut self.inner, Inner::Finish) {
|
if let Inner::Start { src, site_url } = 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,
|
site_url,
|
||||||
templates,
|
templates,
|
||||||
documents,
|
documents,
|
||||||
summary,
|
summary,
|
||||||
@ -106,7 +106,7 @@ impl Engine for MDBookEngine {
|
|||||||
|
|
||||||
async fn push_document<R>(
|
async fn push_document<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
model_id: ModelId,
|
instance: Instance,
|
||||||
doc: GenericDocument,
|
doc: GenericDocument,
|
||||||
_registry: &R,
|
_registry: &R,
|
||||||
) -> Result<(), RendererError>
|
) -> Result<(), RendererError>
|
||||||
@ -124,7 +124,7 @@ impl Engine for MDBookEngine {
|
|||||||
match doc {
|
match doc {
|
||||||
$(
|
$(
|
||||||
GenericDocument::$kind(doc) => {
|
GenericDocument::$kind(doc) => {
|
||||||
save_domain_model(summary, documents, model_id, &doc);
|
save_domain_model(summary, documents, instance, &doc);
|
||||||
}
|
}
|
||||||
)+
|
)+
|
||||||
}
|
}
|
||||||
@ -160,7 +160,7 @@ impl Engine for MDBookEngine {
|
|||||||
{
|
{
|
||||||
if let Inner::LoadAndRender {
|
if let Inner::LoadAndRender {
|
||||||
src,
|
src,
|
||||||
global,
|
site_url,
|
||||||
mut templates,
|
mut templates,
|
||||||
documents,
|
documents,
|
||||||
summary,
|
summary,
|
||||||
@ -169,8 +169,13 @@ 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, global);
|
register_filters(
|
||||||
render_indexes(&templates, &src, &summary).await?;
|
&mut templates,
|
||||||
|
documents.clone(),
|
||||||
|
registry_config.clone(),
|
||||||
|
site_url,
|
||||||
|
);
|
||||||
|
render_indexes(&templates, &src, &summary, registry_config).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?;
|
||||||
}
|
}
|
||||||
@ -206,12 +211,12 @@ 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 DocumentMap,
|
documents: &mut DocumentMap,
|
||||||
model_id: ModelId,
|
instance: Instance,
|
||||||
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(), instance);
|
||||||
if let Value::Object(mut document) = tera::to_value(&doc).unwrap() {
|
if let Value::Object(mut document) = tera::to_value(&doc).unwrap() {
|
||||||
document.insert("id".to_owned(), tera::to_value(&id).unwrap());
|
document.insert("id".to_owned(), tera::to_value(&id).unwrap());
|
||||||
documents.insert(id, document);
|
documents.insert(id, document);
|
||||||
@ -233,10 +238,10 @@ async fn render_documents(
|
|||||||
let template_path = format!("{}.instance.tera", model_path);
|
let template_path = format!("{}.instance.tera", model_path);
|
||||||
let output_path = src
|
let output_path = src
|
||||||
.join(model_path)
|
.join(model_path)
|
||||||
.join(id.model_id.to_string())
|
.join(id.instance.to_string())
|
||||||
.with_extension("md");
|
.with_extension("md");
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.insert("doc", &document);
|
context.insert("document", &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?;
|
||||||
}
|
}
|
||||||
@ -250,7 +255,6 @@ async fn render_summary(
|
|||||||
) -> Result<(), MDBookEngineError> {
|
) -> Result<(), MDBookEngineError> {
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.insert("summary", summary);
|
context.insert("summary", summary);
|
||||||
|
|
||||||
let contents = templates.render("summary.tera", &context)?;
|
let contents = templates.render("summary.tera", &context)?;
|
||||||
let summary_path = src.join("SUMMARY.md");
|
let summary_path = src.join("SUMMARY.md");
|
||||||
fs::write(summary_path, contents).await?;
|
fs::write(summary_path, contents).await?;
|
||||||
@ -261,6 +265,7 @@ async fn render_indexes(
|
|||||||
templates: &Tera,
|
templates: &Tera,
|
||||||
src: &Path,
|
src: &Path,
|
||||||
summary: &[SummaryItem],
|
summary: &[SummaryItem],
|
||||||
|
registry_config: Arc<RegistryConfig>,
|
||||||
) -> Result<(), MDBookEngineError> {
|
) -> Result<(), MDBookEngineError> {
|
||||||
let mut last_i = 0;
|
let mut last_i = 0;
|
||||||
let mut last_item = if let Some(item) = summary.get(0) {
|
let mut last_item = if let Some(item) = summary.get(0) {
|
||||||
@ -282,7 +287,11 @@ async fn render_indexes(
|
|||||||
fs::create_dir(&model_dir).await?;
|
fs::create_dir(&model_dir).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let content = registry_config
|
||||||
|
.content
|
||||||
|
.get(&format!("{}-{}", domain, model));
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
|
context.insert("content", &content);
|
||||||
context.insert("instances", &summary[last_i..i]);
|
context.insert("instances", &summary[last_i..i]);
|
||||||
|
|
||||||
let model_index = model_dir.with_extension("md");
|
let model_index = model_dir.with_extension("md");
|
||||||
@ -302,83 +311,138 @@ fn register_filters(
|
|||||||
tera: &mut Tera,
|
tera: &mut Tera,
|
||||||
documents: Arc<DocumentMap>,
|
documents: Arc<DocumentMap>,
|
||||||
registry_config: Arc<RegistryConfig>,
|
registry_config: Arc<RegistryConfig>,
|
||||||
global: Map<String, Value>,
|
site_url: Option<String>,
|
||||||
) {
|
) {
|
||||||
use tera::{try_get_value, Error};
|
use tera::{try_get_value, Error};
|
||||||
|
|
||||||
|
let site_url = site_url.as_deref().unwrap_or("/").to_owned();
|
||||||
|
|
||||||
|
let mut global: HashMap<&str, Value> = HashMap::new();
|
||||||
|
global.insert("site_url", tera::to_value(&site_url).unwrap());
|
||||||
|
global.insert("registry", tera::to_value(&*registry_config).unwrap());
|
||||||
|
|
||||||
tera.register_function("global", move |args: &HashMap<String, Value>| {
|
tera.register_function("global", move |args: &HashMap<String, Value>| {
|
||||||
if let Some(Value::String(key)) = args.get("key") {
|
if let Some(Value::String(full_key)) = args.get("key") {
|
||||||
Ok(global.get(key).cloned().unwrap_or(Value::Null))
|
let mut keys = full_key.split('.');
|
||||||
|
let mut value = global.get(keys.next().unwrap());
|
||||||
|
for key in keys {
|
||||||
|
match value {
|
||||||
|
Some(Value::Object(map)) => {
|
||||||
|
value = map.get(key);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(Error::msg(format_args!(
|
||||||
|
"no key `{}` section of `{}`",
|
||||||
|
key, full_key
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
return Err(Error::msg(format_args!(
|
||||||
|
"value cannot be indexed with `{}`",
|
||||||
|
key
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(value.cloned().unwrap_or(Value::Null))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::msg("expected `key` for `global` function"))
|
Err(Error::msg("expected valid `key` arg for `global` function"))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fn parse_domain_id(value: &Value) -> Result<DomainId, Error> {
|
tera.register_function("link_for", move |args: &HashMap<String, Value>| {
|
||||||
let id = try_get_value!("link", "value", String, value);
|
let link_for = if let Some(Value::String(link_for)) = args.get("for") {
|
||||||
id.parse()
|
link_for
|
||||||
.map_err(|_| Error::msg(format_args!("failed to parse domain id `{}`", id)))
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
tera.register_filter(
|
|
||||||
"domain_id_link",
|
|
||||||
move |value: &Value, args: &HashMap<String, Value>| {
|
|
||||||
let id = parse_domain_id(value)?;
|
|
||||||
let (domain, model) = id.kind.parts();
|
|
||||||
let link = match args.get("for").and_then(|v| v.as_str()) {
|
|
||||||
None => format!("{}/{}/{}.md", domain, model, id.model_id),
|
|
||||||
Some("model") => format!("{}/{}.md", domain, model),
|
|
||||||
Some("edit") => registry_config.edit_link(id).map_err(Error::msg)?,
|
|
||||||
Some(_) => {
|
|
||||||
return Err(Error::msg(
|
return Err(Error::msg(
|
||||||
"domain_id_link `for` arg can only be `model|edit`",
|
"expected string `for` arg for `link_for` function",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let edit = match args.get("edit") {
|
||||||
|
Some(Value::Bool(edit)) => *edit,
|
||||||
|
Some(_) => return Err(Error::msg("`edit` arg for `link_for` must be a boolean")),
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
let link = match link_for.as_str() {
|
||||||
|
"document" => match args.get("id") {
|
||||||
|
Some(value) => {
|
||||||
|
let id = parse_domain_id("link_for", value)?;
|
||||||
|
if edit {
|
||||||
|
registry_config.document_edit_link(id).map_err(Error::msg)?
|
||||||
|
} else {
|
||||||
|
let (domain, model) = id.kind.parts();
|
||||||
|
format!("{}{}/{}/{}.md", site_url, domain, model, id.instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::msg(
|
||||||
|
"`link_for(for=\"document\")` `` expects `id` arg",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page" => match args.get("path") {
|
||||||
|
Some(Value::String(page)) if edit => {
|
||||||
|
registry_config.page_edit_link(page).map_err(Error::msg)?
|
||||||
|
}
|
||||||
|
Some(Value::String(page)) => {
|
||||||
|
format!("{}{}.md", site_url, page)
|
||||||
|
}
|
||||||
|
_ => return Err(Error::msg("`link_for(for=\"path\")` `` expects `path` arg")),
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(Error::msg(
|
||||||
|
"`link_for` `for` arg can only be `document|page`",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Value::String(link))
|
Ok(Value::String(link))
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
tera.register_filter(
|
tera.register_function("document", move |args: &HashMap<String, Value>| {
|
||||||
"domain_id",
|
if let Some(value) = args.get("id") {
|
||||||
move |value: &Value, _: &HashMap<String, Value>| {
|
let id = parse_domain_id("document", value)?;
|
||||||
let id = parse_domain_id(value)?;
|
|
||||||
let (domain, model) = id.kind.parts();
|
|
||||||
let mut parts = Map::with_capacity(4);
|
|
||||||
parts.insert("domain".to_owned(), domain.into());
|
|
||||||
parts.insert("model".to_owned(), model.into());
|
|
||||||
parts.insert("model_id".to_owned(), id.model_id.to_string().into());
|
|
||||||
Ok(Value::Object(parts))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
tera.register_filter(
|
|
||||||
"get_doc",
|
|
||||||
move |value: &Value, _: &HashMap<String, Value>| {
|
|
||||||
let id = parse_domain_id(value)?;
|
|
||||||
documents
|
documents
|
||||||
.get(&id)
|
.get(&id)
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(Value::Object)
|
.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)))
|
||||||
|
} else {
|
||||||
|
Err(Error::msg("expected `id` arg for `document` function"))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fn parse_domain_id(within: &str, value: &Value) -> Result<DomainId, Error> {
|
||||||
|
let id = try_get_value!(within, "id", String, value);
|
||||||
|
id.parse()
|
||||||
|
.map_err(|_| Error::msg(format_args!("failed to parse domain id `{}`", id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
tera.register_filter(
|
||||||
|
"domain_id",
|
||||||
|
move |value: &Value, _: &HashMap<String, Value>| {
|
||||||
|
let id = parse_domain_id("domain_id", value)?;
|
||||||
|
let (domain, model) = id.kind.parts();
|
||||||
|
let mut parts = Map::with_capacity(4);
|
||||||
|
parts.insert("domain".to_owned(), domain.into());
|
||||||
|
parts.insert("model".to_owned(), model.into());
|
||||||
|
parts.insert("instance".to_owned(), id.instance.to_string().into());
|
||||||
|
Ok(Value::Object(parts))
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
tera.register_filter("autolink", |value: &Value, _: &HashMap<String, Value>| {
|
tera.register_tester("url", |value: Option<&Value>, _: &[Value]| {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref URL: Regex = Regex::new(
|
static ref URL: Regex = Regex::new(
|
||||||
r"(?ix)
|
r"(?ix)
|
||||||
\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))
|
^\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))$
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
let text = try_get_value!("autolink", "value", String, value);
|
if let Some(Value::String(text)) = value {
|
||||||
let text = if text.is_empty() {
|
Ok(URL.is_match(text))
|
||||||
text
|
|
||||||
} else {
|
} else {
|
||||||
URL.replace_all(text.as_str(), "[$0]($0)").into_owned()
|
Err(Error::msg("expected string for `url` test"))
|
||||||
};
|
}
|
||||||
Ok(Value::String(text))
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use async_trait::async_trait;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::document::Document;
|
use crate::document::Document;
|
||||||
use crate::domains::common::{DomainId, DomainModel, ModelId};
|
use crate::domains::common::{DomainId, DomainModel, Instance};
|
||||||
use crate::domains::GenericDocument;
|
use crate::domains::GenericDocument;
|
||||||
use crate::registry::{Registry, RegistryError};
|
use crate::registry::{Registry, RegistryError};
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ where
|
|||||||
$(
|
$(
|
||||||
for (id, doc) in self.get_documents().await? {
|
for (id, doc) in self.get_documents().await? {
|
||||||
self.engine
|
self.engine
|
||||||
.push_document(id.model_id, GenericDocument::$kind(doc), &self.registry)
|
.push_document(id.instance, GenericDocument::$kind(doc), &self.registry)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
@ -92,7 +92,7 @@ pub trait Engine {
|
|||||||
|
|
||||||
async fn push_document<R>(
|
async fn push_document<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: ModelId,
|
id: Instance,
|
||||||
doc: GenericDocument,
|
doc: GenericDocument,
|
||||||
registry: &R,
|
registry: &R,
|
||||||
) -> Result<(), RendererError>
|
) -> Result<(), RendererError>
|
||||||
|
Loading…
Reference in New Issue
Block a user