Add parsing of plaixt definitions
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
fa0793cfc7
commit
d09c5e5a69
5 changed files with 1619 additions and 3 deletions
1374
Cargo.lock
generated
1374
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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
23
crates/plaixt/Cargo.toml
Normal 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
209
crates/plaixt/src/main.rs
Normal 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)
|
||||
}
|
||||
9
examples/definitions/changelog.kdl
Normal file
9
examples/definitions/changelog.kdl
Normal 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" }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue