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}
|
quote = {version = "1.0.35", optional = true}
|
||||||
syn = { version = "2.0.55", features = ["extra-traits", "full", "parsing"], optional = true }
|
syn = { version = "2.0.55", features = ["extra-traits", "full", "parsing"], optional = true }
|
||||||
thiserror = { version = "1.0.58", optional = true}
|
thiserror = { version = "1.0.58", optional = true}
|
||||||
|
pulldown-cmark = {version = "0.10.0", optional = true}
|
||||||
# macros
|
# macros
|
||||||
prettyplease = {version = "0.2.17", optional = true}
|
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}
|
log = { version = "0.4.21", optional = true}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1.0.81"
|
|
||||||
# parser
|
# parser
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["parser", "types", "macros", "build-binary"]
|
# default = ["parser", "types", "macros", "build-binary"]
|
||||||
# default = ["parser", "types", "macros"]
|
default = ["parser", "types", "macros"]
|
||||||
build-binary = ["clap", "parser", "types", "macros"]
|
build-binary = ["parser", "types", "macros", "clap", "pulldown-cmark"]
|
||||||
|
|
||||||
parser = [ "regex", "thiserror", "convert_case" ]
|
parser = [ "regex", "thiserror", "convert_case" ]
|
||||||
types = [ "parser", "libc", "log", "proc-macro2", "quote", "syn", "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.
|
//! you.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Display,
|
|
||||||
fs, io,
|
fs, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::trixy::Language;
|
use super::trixy::Language;
|
||||||
|
|
||||||
|
pub mod markdown_representation;
|
||||||
|
|
||||||
/// A file tree containing all files that were generated. These are separated into host and
|
/// 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.
|
/// auxiliary files. See their respective descriptions about what differentiates them.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct FileTree {
|
pub struct FileTree {
|
||||||
/// Files, that are supposed to be included in the compiled crate.
|
/// Files, that are supposed to be included in the compiled crate.
|
||||||
pub host_files: Vec<GeneratedFile>,
|
pub host_files: Vec<GeneratedFile>,
|
||||||
|
@ -21,7 +22,7 @@ pub struct FileTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A generated files
|
/// A generated files
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct GeneratedFile {
|
pub struct GeneratedFile {
|
||||||
/// The path this generated file would like to be placed at.
|
/// The path this generated file would like to be placed at.
|
||||||
/// This path is relative to the crate root.
|
/// This path is relative to the crate root.
|
||||||
|
@ -77,6 +78,16 @@ impl FileTree {
|
||||||
self.auxiliary_files.push(file)
|
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<()> {
|
pub fn materialize(self) -> io::Result<()> {
|
||||||
self.host_files
|
self.host_files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -89,39 +100,3 @@ impl FileTree {
|
||||||
Ok(())
|
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);
|
let c_code = format_c(c_code);
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"
|
"\
|
||||||
{}\n{}
|
{}\n\
|
||||||
",
|
{}",
|
||||||
c_code, VIM_LINE_C
|
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);
|
let rust_code = format_rust(host_rust_code);
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"
|
"\
|
||||||
/* c API */
|
/* C API */\n\
|
||||||
{}
|
{}",
|
||||||
",
|
|
||||||
rust_code
|
rust_code
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,11 @@ pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> String {
|
||||||
let c_host = c::generate(trixy, config);
|
let c_host = c::generate(trixy, config);
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"
|
"\
|
||||||
// Host code {{{{{{
|
// Host code\n\
|
||||||
{}
|
{}\
|
||||||
/* C API */
|
{}\
|
||||||
{}
|
{}",
|
||||||
// }}}}}}
|
|
||||||
{}
|
|
||||||
",
|
|
||||||
rust_host, c_host, VIM_LINE_RUST
|
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);
|
let rust_code = format_rust(host_rust_code);
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"
|
"\
|
||||||
/* Rust API */
|
/* Rust API */\n\
|
||||||
{}
|
{}",
|
||||||
",
|
|
||||||
rust_code
|
rust_code
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue