use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize};
use crate::{LinkTarget, NameCase, NodeId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, JsonSchema)]
#[serde(
tag = "kind",
rename_all = "kebab-case",
rename_all_fields = "camelCase",
deny_unknown_fields
)]
pub enum EntryKind {
Command,
Parameter {
parameter_kind: ParameterKind,
},
ConfigurationKey,
EnvironmentVariable,
Variable,
Value,
Term,
}
#[derive(Deserialize)]
#[serde(
tag = "kind",
rename_all = "kebab-case",
rename_all_fields = "camelCase",
deny_unknown_fields
)]
enum ClosedEntryKind {
Command {},
Parameter { parameter_kind: ParameterKind },
ConfigurationKey {},
EnvironmentVariable {},
Variable {},
Value {},
Term {},
}
impl<'de> Deserialize<'de> for EntryKind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(match ClosedEntryKind::deserialize(deserializer)? {
ClosedEntryKind::Command {} => Self::Command,
ClosedEntryKind::Parameter { parameter_kind } => Self::Parameter { parameter_kind },
ClosedEntryKind::ConfigurationKey {} => Self::ConfigurationKey,
ClosedEntryKind::EnvironmentVariable {} => Self::EnvironmentVariable,
ClosedEntryKind::Variable {} => Self::Variable,
ClosedEntryKind::Value {} => Self::Value,
ClosedEntryKind::Term {} => Self::Term,
})
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "kebab-case")]
pub enum ParameterKind {
Option,
Marker,
Operand,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(
tag = "kind",
rename_all = "kebab-case",
rename_all_fields = "camelCase"
)]
pub enum ValueDomain {
Choices {
exhaustive: bool,
},
EntrySet {
reference: DocumentReference,
#[schemars(length(min = 1, max = 9))]
entry_kinds: Vec<EntryKind>,
#[serde(skip_serializing_if = "Option::is_none")]
source: Option<crate::SourceSpan>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(
tag = "kind",
rename_all = "kebab-case",
rename_all_fields = "camelCase",
deny_unknown_fields
)]
pub enum DocumentReference {
Document {
#[schemars(length(min = 1))]
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(length(min = 1))]
fragment: Option<String>,
},
Manual {
#[schemars(length(min = 1))]
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(length(min = 1))]
manual_section: Option<String>,
},
}
impl DocumentReference {
#[must_use]
pub fn is_well_formed(&self) -> bool {
match self {
Self::Document { name, fragment } => {
!name.is_empty()
&& !name.starts_with('/')
&& !name.contains(['\\', '?', '#'])
&& !name.chars().any(char::is_control)
&& name.split('/').all(|component| !component.is_empty())
&& fragment.as_deref().is_none_or(|fragment| {
!fragment.is_empty() && !fragment.chars().any(char::is_control)
})
}
Self::Manual {
name,
manual_section,
} => {
!name.is_empty()
&& !name.contains(['/', '\\'])
&& !name
.chars()
.any(|character| character.is_whitespace() || character.is_control())
&& manual_section
.as_deref()
.is_none_or(crate::is_manual_section)
}
}
}
#[must_use]
pub fn from_link_target(target: &LinkTarget) -> Option<Self> {
match target {
LinkTarget::Document { name, fragment } => Some(Self::Document {
name: name.clone(),
fragment: fragment.clone(),
}),
LinkTarget::Manual {
name,
manual_section,
} => Some(Self::Manual {
name: name.clone(),
manual_section: manual_section.clone(),
}),
LinkTarget::External { .. } | LinkTarget::Email { .. } | LinkTarget::Section { .. } => {
None
}
}
}
#[must_use]
pub fn resolve_from(&self, from: &crate::DocumentAddress) -> Option<crate::DocumentAddress> {
match self {
Self::Document { name, .. } => from.resolve_document_reference(name),
Self::Manual {
name,
manual_section: Some(manual_section),
} => Some(crate::DocumentAddress::Manual {
name: name.clone(),
manual_section: manual_section.clone(),
}),
Self::Manual {
manual_section: None,
..
} => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SemanticDocumentTarget {
pub label: String,
pub reference: DocumentReference,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SemanticEntry {
pub id: NodeId,
pub kind: EntryKind,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub names: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub alias_groups: Vec<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub alias_of: Option<NodeId>,
pub case: NameCase,
pub forms: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub document_targets: Vec<SemanticDocumentTarget>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub children: Vec<SemanticEntry>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_domain: Option<ValueDomain>,
}
impl SemanticEntry {
#[must_use]
pub fn subtree_len(&self) -> usize {
1 + self.children.iter().map(Self::subtree_len).sum::<usize>()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct EntrySummary {
pub direct: u32,
pub descendants: u32,
pub forms: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub by_kind: Vec<EntryKindCount>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct EntryKindCount {
pub kind: EntryKind,
pub count: u32,
}
impl EntrySummary {
#[must_use]
pub fn for_entries(entries: &[SemanticEntry]) -> Self {
let mut summary = Self {
direct: u32::try_from(entries.len()).unwrap_or(u32::MAX),
..Self::default()
};
for entry in entries {
summarize_entry(entry, &mut summary, true);
}
summary
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.direct == 0 && self.descendants == 0 && self.forms == 0
}
}
fn summarize_entry(entry: &SemanticEntry, summary: &mut EntrySummary, direct: bool) {
if !direct {
summary.descendants = summary.descendants.saturating_add(1);
}
summary.forms = summary
.forms
.saturating_add(u32::try_from(entry.forms.len()).unwrap_or(u32::MAX));
if let Some(count) = summary
.by_kind
.iter_mut()
.find(|count| count.kind == entry.kind)
{
count.count = count.count.saturating_add(1);
} else {
summary.by_kind.push(EntryKindCount {
kind: entry.kind,
count: 1,
});
summary.by_kind.sort_by_key(|count| count.kind);
}
for child in &entry.children {
summarize_entry(child, summary, false);
}
}