Add parsing of plaixt definitions

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2025-01-29 21:15:46 +01:00
parent fa0793cfc7
commit d09c5e5a69
5 changed files with 1619 additions and 3 deletions

1374
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,11 +4,18 @@ members = [
]
resolver = "2"
[workspace.lints.rust]
unsafe_code = "forbid"
[workspace.package]
version = "0.0.0"
edition = "2021"
description = "PLAIn teXT tools for your data"
license = "EUPL-1.2"
[workspace.dependencies]
kdl = "6.3.3"
[workspace.metadata.crane]
name = "plaixt"

23
crates/plaixt/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "plaixt"
version.workspace = true
edition.workspace = true
description.workspace = true
license.workspace = true
[dependencies]
camino = { version = "1.1.9", features = ["serde", "serde1"] }
clap = { version = "4.5.27", features = ["derive"] }
futures = "0.3.31"
kdl.workspace = true
miette = { version = "7.4.0", features = ["fancy", "syntect-highlighter"] }
owo-colors = "4.1.0"
time = { version = "0.3.37", features = ["macros", "parsing", "serde-well-known"] }
tokio = { version = "1.43.0", features = ["full"] }
tokio-stream = { version = "0.1.17", features = ["full"] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
[lints]
workspace = true

209
crates/plaixt/src/main.rs Normal file
View file

@ -0,0 +1,209 @@
#![allow(dead_code)]
use std::collections::BTreeMap;
use std::collections::HashMap;
use camino::Utf8PathBuf;
use clap::Parser;
use futures::StreamExt;
use futures::TryStreamExt;
use kdl::KdlDocument;
use kdl::KdlValue;
use miette::IntoDiagnostic;
use miette::LabeledSpan;
use miette::NamedSource;
use owo_colors::OwoColorize;
use time::OffsetDateTime;
use tokio_stream::wrappers::ReadDirStream;
use tracing::info;
#[derive(Debug, Parser)]
struct Args {
#[arg(short, long)]
path: Utf8PathBuf,
}
#[tokio::main]
async fn main() -> miette::Result<()> {
tracing_subscriber::fmt().pretty().init();
let args = Args::parse();
let definitions = load_definitions(args.path.join("definitions")).await?;
info!(?definitions, "Got definitions!");
Ok(())
}
#[derive(Debug)]
pub enum DefinitionKind {
String,
OneOf(Vec<String>),
}
impl TryFrom<&str> for DefinitionKind {
type Error = miette::Report;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_ascii_lowercase().as_str() {
"string" => Ok(DefinitionKind::String),
other => miette::bail!("Did not recognize valid field kind: \"{other}\""),
}
}
}
#[derive(Debug)]
pub struct Definition {
since: OffsetDateTime,
fields: HashMap<String, DefinitionKind>,
}
fn parse_definition(bytes: &str) -> miette::Result<Vec<Definition>> {
let doc: KdlDocument = bytes.parse()?;
let mut defs = vec![];
for node in doc.nodes() {
match node.name().value() {
"define" => {
let Some(since_entry) = node.entry("since") else {
return Err(miette::diagnostic!(
labels = vec![LabeledSpan::new_primary_with_span(
Some(String::from("this define")),
node.name().span()
)],
"Missing `since` property. Every `define` block requires one."
))?;
};
let KdlValue::String(since) = since_entry.value() else {
return Err(miette::diagnostic!(
labels = vec![LabeledSpan::new_primary_with_span(
Some(String::from("in this define")),
since_entry.span()
)],
"The `since` property needs to be a string in RFC3339 format."
))?;
};
let since = match OffsetDateTime::parse(
since,
&time::format_description::well_known::Rfc3339,
) {
Ok(since) => since,
Err(_err) => {
return Err(miette::diagnostic!(
labels = vec![LabeledSpan::new_primary_with_span(
Some(String::from("in this define")),
since_entry.span()
)],
"Could not parse the `since` property as a valid RFC3339 time"
))?;
}
};
let Some(fields) = node
.iter_children()
.find(|field| field.name().value() == "fields")
else {
return Err(miette::diagnostic!(
labels = vec![LabeledSpan::new_primary_with_span(
Some(String::from("in this define")),
node.span()
)],
"Could not find `fields` child, which is a required child node."
))?;
};
let fields = fields
.iter_children()
.map(|field| {
let kind = if let Some(kind) = field.get("is") {
kind.as_string()
.ok_or_else(|| {
miette::Report::from(miette::diagnostic!(
labels = vec![LabeledSpan::new_primary_with_span(
Some(String::from("in this define")),
field.span()
)],
"The `is` field needs to be a string."
))
})
.and_then(DefinitionKind::try_from)?
} else {
let Some(children) = field.children() else {
return Err(miette::diagnostic!(
labels = vec![LabeledSpan::new_primary_with_span(
Some(String::from("in this define")),
field.span()
)],
"Either set a `is` property, or a child with the given definition"
))?;
};
if let Some(one_of) = children.get("oneOf") {
DefinitionKind::OneOf(
one_of.iter().map(|opt| opt.value().to_string()).collect(),
)
} else {
return Err(miette::diagnostic!(
labels = vec![LabeledSpan::new_primary_with_span(
Some(String::from("in this define")),
field.span()
)],
"Unrecognizable field definition"
))?;
}
};
Ok((field.name().to_string(), kind))
})
.collect::<miette::Result<_>>()?;
defs.push(Definition { since, fields });
}
unknown => {
return Err(miette::diagnostic!(
labels = vec![LabeledSpan::new_primary_with_span(
Some(String::from("here")),
node.name().span()
)],
help = "Allowed nodes are: \"define\"",
"Unknown node \"{}\".",
unknown.red(),
))?
}
}
}
Ok(defs)
}
async fn load_definitions(path: Utf8PathBuf) -> miette::Result<BTreeMap<String, Vec<Definition>>> {
let defs = ReadDirStream::new(tokio::fs::read_dir(path).await.into_diagnostic()?)
.map_err(miette::Report::from_err)
.and_then(|entry| async move {
if entry.file_type().await.into_diagnostic()?.is_file() {
Ok(Some((
Utf8PathBuf::from_path_buf(entry.path().to_path_buf()).unwrap(),
tokio::fs::read_to_string(entry.path())
.await
.into_diagnostic()?,
)))
} else {
Ok(None)
}
})
.flat_map(|val| futures::stream::iter(val.transpose()))
.and_then(|(name, bytes)| async move {
Ok((
name.file_stem().unwrap().to_string(),
parse_definition(&bytes).map_err(|e| {
e.with_source_code(NamedSource::new(name, bytes).with_language("kdl"))
})?,
))
})
.try_collect()
.await?;
Ok(defs)
}

View file

@ -0,0 +1,9 @@
// This is the default changelog entry for the plaixt project
define since="2025-01-29 20:27:30+01:00" {
fields {
title is=string
version is=string
kind { oneOf "Bugfix" "Feature" "Chore" }
}
}