feat(macros/config/file_tree): Add support for parsing a FileTree from file
This makes storing expected `FileTree`s as markdown possible (useful in tests).
This commit is contained in:
parent
ec929dabe5
commit
89fd67c45e
|
@ -29,7 +29,7 @@ proc-macro2 = {version = "1.0.79", optional = true}
|
|||
quote = {version = "1.0.35", optional = true}
|
||||
syn = { version = "2.0.55", features = ["extra-traits", "full", "parsing"], optional = true }
|
||||
thiserror = { version = "1.0.58", optional = true}
|
||||
|
||||
pulldown-cmark = {version = "0.10.0", optional = true}
|
||||
# macros
|
||||
prettyplease = {version = "0.2.17", optional = true}
|
||||
|
||||
|
@ -41,14 +41,13 @@ libc ={ version = "0.2.153", optional = true}
|
|||
log = { version = "0.4.21", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.81"
|
||||
# parser
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
[features]
|
||||
default = ["parser", "types", "macros", "build-binary"]
|
||||
# default = ["parser", "types", "macros"]
|
||||
build-binary = ["clap", "parser", "types", "macros"]
|
||||
# default = ["parser", "types", "macros", "build-binary"]
|
||||
default = ["parser", "types", "macros"]
|
||||
build-binary = ["parser", "types", "macros", "clap", "pulldown-cmark"]
|
||||
|
||||
parser = [ "regex", "thiserror", "convert_case" ]
|
||||
types = [ "parser", "libc", "log", "proc-macro2", "quote", "syn", "thiserror", "convert_case" ]
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
use std::{fmt::Display, path::PathBuf, str::FromStr};
|
||||
|
||||
use pulldown_cmark::{CodeBlockKind, CowStr, Event, HeadingLevel, Parser, Tag, TagEnd};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::macros::config::trixy::Language;
|
||||
|
||||
use super::{FileTree, GeneratedFile};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FileTreeParseError {
|
||||
#[error("Your Header has the wrong content: {0}")]
|
||||
WrongHeader(String),
|
||||
#[error("Your language is not recognized: {0}")]
|
||||
WrongLanguage(String),
|
||||
|
||||
#[error("A path seems to be missing from your input data")]
|
||||
NoPath,
|
||||
#[error("A language attribute seems to be missing from your input data")]
|
||||
NoLanguage,
|
||||
#[error("A value seems to be missing from your input data")]
|
||||
NoValue,
|
||||
|
||||
#[error("I exected: \n```\n{expected}\n```\nbut recieved:\n```\n{got}\n```")]
|
||||
EventNotExpected { expected: String, got: String },
|
||||
}
|
||||
|
||||
impl FromStr for Language {
|
||||
type Err = FileTreeParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"rust" => Ok(Self::Rust),
|
||||
"c" => Ok(Self::C),
|
||||
"lua" => Ok(Self::Lua),
|
||||
other => Err(Self::Err::WrongLanguage(other.to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Language {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self {
|
||||
Language::Rust => f.write_str("rust"),
|
||||
Language::C => f.write_str("c"),
|
||||
Language::Lua => f.write_str("lua"),
|
||||
Language::All => unreachable!("The `all` language variant should never be displayed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GeneratedFile {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("File path: `{}`\n", self.path.display()))?;
|
||||
f.write_fmt(format_args!("```{}\n", self.language))?;
|
||||
f.write_fmt(format_args!("{}", &self.value))?;
|
||||
f.write_str("```\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileTree {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if !self.host_files.is_empty() {
|
||||
f.write_str("# Host files\n")?;
|
||||
self.host_files
|
||||
.iter()
|
||||
.map(|file| -> std::fmt::Result { f.write_str(&file.to_string()) })
|
||||
.collect::<std::fmt::Result>()?;
|
||||
}
|
||||
|
||||
if !self.auxiliary_files.is_empty() {
|
||||
f.write_str("# Auxiliary files\n")?;
|
||||
self.auxiliary_files
|
||||
.iter()
|
||||
.map(|file| -> std::fmt::Result { f.write_str(&file.to_string()) })
|
||||
.collect::<std::fmt::Result>()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FileTree {
|
||||
type Err = FileTreeParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parser = Parser::new(s);
|
||||
|
||||
let iter = parser.into_iter();
|
||||
parse_start(iter)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_start(mut iter: Parser<'_>) -> Result<FileTree, FileTreeParseError> {
|
||||
let mut file_tree = FileTree::new();
|
||||
|
||||
if let Some(Event::Start(Tag::Heading { .. })) = iter.next() {
|
||||
while let Some(Event::Text(CowStr::Borrowed(text))) = iter.next() {
|
||||
match text {
|
||||
"Host files" => {
|
||||
let files = parse_files(&mut iter)?;
|
||||
file_tree.extend_host(files);
|
||||
}
|
||||
"Auxiliary files" => {
|
||||
let files = parse_files(&mut iter)?;
|
||||
file_tree.extend_auxiliary(files);
|
||||
}
|
||||
_ => return Err(FileTreeParseError::WrongHeader(text.to_owned())),
|
||||
};
|
||||
}
|
||||
};
|
||||
debug_assert_eq!(iter.next(), None, "Should be empty at this point");
|
||||
|
||||
Ok(file_tree)
|
||||
}
|
||||
|
||||
fn parse_files(iter: &mut Parser<'_>) -> Result<Vec<GeneratedFile>, FileTreeParseError> {
|
||||
// Remove the extra heading close node
|
||||
remove_event(
|
||||
iter,
|
||||
Event::End(pulldown_cmark::TagEnd::Heading(HeadingLevel::H1)),
|
||||
)?;
|
||||
|
||||
let mut files: Vec<GeneratedFile> = vec![];
|
||||
|
||||
while let Some(Event::Start(Tag::Paragraph)) = iter.next() {
|
||||
files.push(make_generated_file(iter)?);
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
fn make_generated_file(iter: &mut Parser<'_>) -> Result<GeneratedFile, FileTreeParseError> {
|
||||
// Remove the Start(Paragraph) (already removed in the calling function)
|
||||
// remove_event(iter, Event::Start(Tag::Paragraph))?;
|
||||
|
||||
// Remove the Text(Borrowed("File path: "))
|
||||
remove_event(iter, Event::Text(CowStr::Borrowed("File path: ")))?;
|
||||
|
||||
let file_path: PathBuf = if let Some(Event::Code(CowStr::Borrowed(path))) = iter.next() {
|
||||
path.into()
|
||||
} else {
|
||||
return Err(FileTreeParseError::NoPath);
|
||||
};
|
||||
|
||||
// Remove the End(Paragraph)
|
||||
remove_event(iter, Event::End(TagEnd::Paragraph))?;
|
||||
|
||||
let file_language: Language = if let Some(Event::Start(Tag::CodeBlock(
|
||||
CodeBlockKind::Fenced(CowStr::Borrowed(language)),
|
||||
))) = iter.next()
|
||||
{
|
||||
language.parse()?
|
||||
} else {
|
||||
return Err(FileTreeParseError::NoLanguage);
|
||||
};
|
||||
|
||||
let file_value: String = if let Some(Event::Text(CowStr::Borrowed(value))) = iter.next() {
|
||||
value.into()
|
||||
} else {
|
||||
return Err(FileTreeParseError::NoValue);
|
||||
};
|
||||
|
||||
// Remove the End(CodeBlock)
|
||||
remove_event(iter, Event::End(TagEnd::CodeBlock))?;
|
||||
|
||||
Ok(GeneratedFile {
|
||||
path: file_path,
|
||||
value: file_value,
|
||||
language: file_language,
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_event(iter: &mut Parser<'_>, event: Event) -> Result<(), FileTreeParseError> {
|
||||
let a: Vec<Event> = iter.take(1).collect();
|
||||
if a.first().expect("Should always contain a value") != &event {
|
||||
let expected = format!("{:#?}", event);
|
||||
let got = format!("{:#?}", a.first().unwrap());
|
||||
|
||||
return Err(FileTreeParseError::EventNotExpected { expected, got });
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::macros::config::{file_tree::FileTree, trixy::TrixyConfig};
|
||||
const API_FILE_PATH: &str = "./src/macros/config/file_tree/test_api.tri";
|
||||
|
||||
#[test]
|
||||
fn test_round_trip() {
|
||||
let base_config = TrixyConfig::new("callback_function")
|
||||
.trixy_path(Into::<PathBuf>::into(API_FILE_PATH))
|
||||
.dist_dir_path("dist")
|
||||
.out_dir_path("out/dir");
|
||||
|
||||
let file_tree = base_config.generate();
|
||||
let input = file_tree.to_string();
|
||||
let output: FileTree = input
|
||||
.parse()
|
||||
.map_err(|err| {
|
||||
// Improves the error readability
|
||||
panic!("{}", err);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(output, file_tree);
|
||||
}
|
||||
}
|
|
@ -2,16 +2,17 @@
|
|||
//! you.
|
||||
|
||||
use std::{
|
||||
fmt::Display,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use super::trixy::Language;
|
||||
|
||||
pub mod markdown_representation;
|
||||
|
||||
/// A file tree containing all files that were generated. These are separated into host and
|
||||
/// auxiliary files. See their respective descriptions about what differentiates them.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct FileTree {
|
||||
/// Files, that are supposed to be included in the compiled crate.
|
||||
pub host_files: Vec<GeneratedFile>,
|
||||
|
@ -21,7 +22,7 @@ pub struct FileTree {
|
|||
}
|
||||
|
||||
/// A generated files
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct GeneratedFile {
|
||||
/// The path this generated file would like to be placed at.
|
||||
/// This path is relative to the crate root.
|
||||
|
@ -77,6 +78,16 @@ impl FileTree {
|
|||
self.auxiliary_files.push(file)
|
||||
}
|
||||
|
||||
pub fn extend_host(&mut self, files: Vec<GeneratedFile>) {
|
||||
files.into_iter().for_each(|file| self.add_host_file(file));
|
||||
}
|
||||
|
||||
pub fn extend_auxiliary(&mut self, files: Vec<GeneratedFile>) {
|
||||
files
|
||||
.into_iter()
|
||||
.for_each(|file| self.add_auxiliary_file(file));
|
||||
}
|
||||
|
||||
pub fn materialize(self) -> io::Result<()> {
|
||||
self.host_files
|
||||
.into_iter()
|
||||
|
@ -89,39 +100,3 @@ impl FileTree {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Language {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self {
|
||||
Language::Rust => f.write_str("rust"),
|
||||
Language::C => f.write_str("c"),
|
||||
Language::Lua => f.write_str("lua"),
|
||||
Language::All => unreachable!("The `all` language variant should never be displayed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GeneratedFile {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("File path: `{}`\n", self.path.display()))?;
|
||||
f.write_fmt(format_args!("```{}\n", self.language))?;
|
||||
f.write_fmt(format_args!("{}\r", &self.value))?;
|
||||
f.write_str("```\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileTree {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("# Host files\n")?;
|
||||
self.host_files
|
||||
.iter()
|
||||
.map(|file| -> std::fmt::Result { f.write_str(&file.to_string()) })
|
||||
.collect::<std::fmt::Result>()?;
|
||||
|
||||
f.write_str("# Auxiliary files\n")?;
|
||||
self.auxiliary_files
|
||||
.iter()
|
||||
.map(|file| -> std::fmt::Result { f.write_str(&file.to_string()) })
|
||||
.collect::<std::fmt::Result>()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (C) 2023 - 2024:
|
||||
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* This file is part of the Trixy crate for Trinitrix.
|
||||
*
|
||||
* Trixy is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Lesser GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* and the Lesser GNU General Public License along with this program.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
fn print(message: String);
|
||||
|
||||
mod trinitrix {
|
||||
fn hi(name: String) -> String;
|
||||
}
|
||||
|
||||
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
|
||||
// vim: syntax=rust
|
|
@ -39,9 +39,9 @@ pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> String {
|
|||
let c_code = format_c(c_code);
|
||||
|
||||
format!(
|
||||
"
|
||||
{}\n{}
|
||||
",
|
||||
"\
|
||||
{}\n\
|
||||
{}",
|
||||
c_code, VIM_LINE_C
|
||||
)
|
||||
}
|
||||
|
|
|
@ -32,10 +32,9 @@ pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> String {
|
|||
let rust_code = format_rust(host_rust_code);
|
||||
|
||||
format!(
|
||||
"
|
||||
/* c API */
|
||||
{}
|
||||
",
|
||||
"\
|
||||
/* C API */\n\
|
||||
{}",
|
||||
rust_code
|
||||
)
|
||||
}
|
||||
|
|
|
@ -21,14 +21,11 @@ pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> String {
|
|||
let c_host = c::generate(trixy, config);
|
||||
|
||||
format!(
|
||||
"
|
||||
// Host code {{{{{{
|
||||
{}
|
||||
/* C API */
|
||||
{}
|
||||
// }}}}}}
|
||||
{}
|
||||
",
|
||||
"\
|
||||
// Host code\n\
|
||||
{}\
|
||||
{}\
|
||||
{}",
|
||||
rust_host, c_host, VIM_LINE_RUST
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,10 +31,9 @@ pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> String {
|
|||
let rust_code = format_rust(host_rust_code);
|
||||
|
||||
format!(
|
||||
"
|
||||
/* Rust API */
|
||||
{}
|
||||
",
|
||||
"\
|
||||
/* Rust API */\n\
|
||||
{}",
|
||||
rust_code
|
||||
)
|
||||
}
|
||||
|
|
Reference in New Issue