use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::NodeId;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct Document {
#[serde(skip_serializing_if = "Option::is_none")]
pub parser: Option<ParserInfo>,
pub source: DocumentSource,
pub meta: DocumentMeta,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub diagnostics: Vec<Diagnostic>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocks: Vec<Block>,
pub sections: Vec<Section>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ParserInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum SourceFormat {
Man,
Mdoc,
Markdown,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct DocumentSource {
pub format: SourceFormat,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct DocumentMeta {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manual_section: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub volume: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub os: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arch: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub names: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub alias_target: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct Diagnostic {
pub level: DiagnosticLevel,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<SourceSpan>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum DiagnosticLevel {
Style,
Warning,
Error,
Unsupported,
}
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
JsonSchema,
)]
#[serde(transparent)]
pub struct TextSize(u32);
impl TextSize {
#[must_use]
pub const fn new(value: u32) -> Self {
Self(value)
}
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
#[must_use]
pub fn from_usize_saturating(value: usize) -> Self {
Self(u32::try_from(value).unwrap_or(u32::MAX))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct TextRange {
pub start: TextSize,
pub end: TextSize,
}
impl TextRange {
#[must_use]
pub fn new(start: TextSize, end: TextSize) -> Self {
assert!(start <= end, "a source range cannot end before it starts");
Self { start, end }
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.start.0 == self.end.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct SourceSpan {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub byte_range: Option<TextRange>,
pub line: u32,
pub column: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_line: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_column: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct Section {
pub id: NodeId,
pub title: String,
#[serde(default, skip_serializing_if = "is_zero_u16")]
pub spacing_before_lines: u16,
pub blocks: Vec<Block>,
pub children: Vec<Section>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<SourceSpan>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct LayoutHint {
#[serde(default, skip_serializing_if = "is_zero_u16")]
pub indent_columns: u16,
#[serde(default, skip_serializing_if = "is_zero_u16")]
pub spacing_before_lines: u16,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(
tag = "type",
rename_all = "kebab-case",
rename_all_fields = "camelCase"
)]
pub enum Block {
Paragraph {
children: Vec<Inline>,
#[serde(default, skip_serializing_if = "LayoutHint::is_empty")]
layout: LayoutHint,
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<SourceSpan>,
},
Preformatted {
children: Vec<Inline>,
#[serde(skip_serializing_if = "Option::is_none")]
language: Option<String>,
#[serde(default, skip_serializing_if = "LayoutHint::is_empty")]
layout: LayoutHint,
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<SourceSpan>,
},
List {
kind: ListKind,
#[serde(skip_serializing_if = "Option::is_none")]
start: Option<u64>,
#[serde(default, skip_serializing_if = "is_false")]
compact: bool,
items: Vec<ListItem>,
#[serde(default, skip_serializing_if = "LayoutHint::is_empty")]
layout: LayoutHint,
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<SourceSpan>,
},
DefinitionList {
items: Vec<DefinitionItem>,
#[serde(default, skip_serializing_if = "is_false")]
compact: bool,
#[serde(default, skip_serializing_if = "LayoutHint::is_empty")]
layout: LayoutHint,
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<SourceSpan>,
},
Table {
rows: Vec<TableRow>,
#[serde(default, skip_serializing_if = "LayoutHint::is_empty")]
layout: LayoutHint,
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<SourceSpan>,
},
Equation {
value: String,
#[serde(default, skip_serializing_if = "is_false")]
display: bool,
#[serde(default, skip_serializing_if = "LayoutHint::is_empty")]
layout: LayoutHint,
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<SourceSpan>,
},
VerticalSpace {
lines: u16,
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<SourceSpan>,
},
ThematicBreak {
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<SourceSpan>,
},
Unsupported {
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
text: String,
#[serde(default, skip_serializing_if = "LayoutHint::is_empty")]
layout: LayoutHint,
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<SourceSpan>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum ListKind {
Bullet,
Ordered,
Plain,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ListItem {
pub blocks: Vec<Block>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct DefinitionItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub identity: Option<DefinitionIdentity>,
pub terms: Vec<Vec<Inline>>,
pub description: Vec<Block>,
#[serde(default, skip_serializing_if = "is_false")]
pub inline_term: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub spacing_before_lines: Option<u16>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct DefinitionIdentity {
pub id: NodeId,
pub role: DefinitionRole,
pub case: DefinitionCase,
pub names: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum DefinitionCase {
Sensitive,
Insensitive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum DefinitionRole {
Option,
Marker,
Operand,
Command,
ConfigurationKey,
EnvironmentVariable,
Variable,
Value,
Term,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct TableRow {
pub cells: Vec<TableCell>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct TableCell {
pub blocks: Vec<Block>,
#[serde(default = "one_u16", skip_serializing_if = "is_one_u16")]
pub column_span: u16,
#[serde(default = "one_u16", skip_serializing_if = "is_one_u16")]
pub row_span: u16,
#[serde(skip_serializing_if = "Option::is_none")]
pub alignment: Option<TableAlignment>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum TableAlignment {
Left,
Center,
Right,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(
tag = "type",
rename_all = "kebab-case",
rename_all_fields = "camelCase"
)]
pub enum Inline {
Text {
value: String,
},
Strong {
children: Vec<Inline>,
},
Emphasis {
children: Vec<Inline>,
},
Code {
value: String,
},
Link {
target: LinkTarget,
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>,
children: Vec<Inline>,
},
Anchor {
id: NodeId,
},
LineBreak,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(
tag = "kind",
rename_all = "kebab-case",
rename_all_fields = "camelCase"
)]
pub enum LinkTarget {
External {
uri: String,
},
Email {
address: String,
},
Document {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
fragment: Option<String>,
},
Manual {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
manual_section: Option<String>,
},
Section {
id: NodeId,
},
}
impl LayoutHint {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.indent_columns == 0 && self.spacing_before_lines == 0
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn is_zero_u16(value: &u16) -> bool {
*value == 0
}
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn is_one_u16(value: &u16) -> bool {
*value == 1
}
const fn one_u16() -> u16 {
1
}
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn is_false(value: &bool) -> bool {
!*value
}