///|
/// The kind of an OKF document, from its file name (OKF v0.2, section 3.1).
pub(all) enum DocKind {
/// The `index.md` at the bundle root.
RootIndex
/// An `index.md` below the bundle root.
SubIndex
/// A `log.md` file.
Log
/// Any other Markdown file.
Concept
} derive(Eq, Debug)
///|
/// The kind of the Markdown file at `path`.
pub fn kind_of(path : BundlePath) -> DocKind {
match path.name().to_lower() {
"index.md" => if path.depth() == 0 { RootIndex } else { SubIndex }
"log.md" => Log
_ => Concept
}
}
///|
/// True when `kind` is an index.
pub fn DocKind::is_index(self : DocKind) -> Bool {
self is (RootIndex | SubIndex)
}
///|
/// One Markdown document of a bundle, read once and immutable.
struct Document {
path : BundlePath
store_path : StorePath
kind : DocKind
text : String
header : Header
written : Metadata?
error : MetadataError?
derived : Metadata?
markdown : @mdc.Document?
}
///|
/// Parses the Markdown body with `moonrockz/mdc`. A blank first line is added
/// so that a body that starts with `---` is never read as frontmatter.
fn parse_markdown(body : String) -> @mdc.Document? {
try @mdc.parse("\n" + body, strict=false, layout=true, locs=true) catch {
_ => None
} noraise {
doc => Some(doc)
}
}
///|
/// Reads one document. Frontmatter errors are kept as data.
pub fn Document::Document(
path : BundlePath,
store_path : StorePath,
text : String,
) -> Document {
let header = split_header(text)
let (written, error) = match header.shape {
NoBlock => (None, None)
Unterminated => (None, Some(MetadataError::Unterminated))
Block(raw~) =>
try Metadata(raw, first_line=2) catch {
e => (None, Some(e))
} noraise {
m => (Some(m), None)
}
}
{
path,
store_path,
kind: kind_of(path),
text,
header,
written,
error,
derived: None,
markdown: parse_markdown(header.body),
}
}
///|
/// A copy with derived metadata, for a document without frontmatter.
pub fn Document::with_derived(self : Document, fields : Fields) -> Document {
{ ..self, derived: Some(Metadata::of_fields(fields)), }
}
///|
pub fn Document::path(self : Document) -> BundlePath {
self.path
}
///|
pub fn Document::store_path(self : Document) -> StorePath {
self.store_path
}
///|
pub fn Document::kind(self : Document) -> DocKind {
self.kind
}
///|
/// The full source text.
pub fn Document::text(self : Document) -> String {
self.text
}
///|
/// The Markdown body after the frontmatter block.
pub fn Document::body(self : Document) -> String {
self.header.body
}
///|
/// The file line of the first body line, counted from 1.
pub fn Document::body_line(self : Document) -> Int32 {
self.header.body_line
}
///|
/// The shape of the start of the document.
pub fn Document::header(self : Document) -> Header {
self.header
}
///|
/// True when the document starts with a frontmatter block, valid or not.
pub fn Document::has_frontmatter_block(self : Document) -> Bool {
!(self.header.shape is NoBlock)
}
///|
/// The written frontmatter, when it is valid.
pub fn Document::written_metadata(self : Document) -> Metadata? {
self.written
}
///|
/// The frontmatter error, when the block is invalid.
pub fn Document::metadata_error(self : Document) -> MetadataError? {
self.error
}
///|
/// The metadata that profiles derived for a document without frontmatter.
pub fn Document::derived_metadata(self : Document) -> Metadata? {
self.derived
}
///|
/// The written metadata, else the derived metadata, else empty metadata.
pub fn Document::metadata(self : Document) -> Metadata {
match (self.written, self.derived) {
(Some(m), _) => m
(None, Some(m)) => m
(None, None) => Metadata::empty()
}
}
///|
/// True when the metadata of this document comes from a profile.
pub fn Document::is_derived(self : Document) -> Bool {
self.written is None && self.derived is Some(_)
}
///|
/// The parsed Markdown body.
pub fn Document::markdown(self : Document) -> @mdc.Document? {
self.markdown
}
///|
pub fn Document::is_concept(self : Document) -> Bool {
self.kind is Concept
}
///|
/// The Concept ID, for a concept.
pub fn Document::concept_id(self : Document) -> ConceptId? {
if self.kind is Concept {
ConceptId::of_path(self.path)
} else {
None
}
}
///|
pub fn Document::concept_type(self : Document) -> ConceptType? {
self.metadata().concept_type()
}
///|
/// `title`, else the file name without `.md`.
pub fn Document::display_title(self : Document) -> String {
match self.metadata().title() {
Some(title) => title
None => {
let name = self.path.name()
if is_markdown_name(name) {
name[:name.length() - 3].to_owned()
} else {
name
}
}
}
}
///|
/// The file line for a line of the body as cmark counts it in the parsed text.
fn Document::file_line(self : Document, parsed_line : Int32) -> Int32 {
// The parsed text has one extra blank line before the body.
parsed_line - 1 + self.header.body_line - 1
}
///|
pub extend DocKind with Eq::{not_equal, equal}
///|
pub extend DocKind with @debug.Debug::{to_repr}