Read files macro for loading reading files to string at compile-time
Makefile for formatting and linting Workspace for subcrates. Moved crates to subdir and moved subcrate configs to workspace.*
This commit is contained in:
12
crates/into_response_derive/Cargo.toml
Normal file
12
crates/into_response_derive/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "into-response-derive"
|
||||
version = "1.1.0"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
20
crates/into_response_derive/src/derive.rs
Normal file
20
crates/into_response_derive/src/derive.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
use quote::quote;
|
||||
use syn::DeriveInput;
|
||||
|
||||
pub fn into_response_derive_impl(input: DeriveInput) -> TokenStream {
|
||||
let name = &input.ident;
|
||||
|
||||
let expanded = quote! {
|
||||
impl IntoResponse for #name {
|
||||
fn into_response(self) -> Response {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
lib::serde::response::BaseResponse::new(version, self)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
13
crates/into_response_derive/src/lib.rs
Normal file
13
crates/into_response_derive/src/lib.rs
Normal file
@ -0,0 +1,13 @@
|
||||
extern crate proc_macro;
|
||||
use {
|
||||
proc_macro::TokenStream,
|
||||
syn::{parse_macro_input, DeriveInput},
|
||||
};
|
||||
|
||||
mod derive;
|
||||
|
||||
#[proc_macro_derive(IntoResponse)]
|
||||
pub fn into_response_derive(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
derive::into_response_derive_impl(input)
|
||||
}
|
13
crates/read_files/Cargo.toml
Normal file
13
crates/read_files/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "read-files"
|
||||
version = "0.1.0"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
regex = "1.10.5"
|
34
crates/read_files/src/lib.rs
Normal file
34
crates/read_files/src/lib.rs
Normal file
@ -0,0 +1,34 @@
|
||||
extern crate proc_macro;
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
use syn::parse_macro_input;
|
||||
|
||||
use crate::read_files::read_files_to_string_impl;
|
||||
|
||||
mod read_files;
|
||||
|
||||
/// Read files from a directory into a HashMap.
|
||||
/// The key is the file path relative to the root directory.
|
||||
/// The value is the file contents as a string.
|
||||
/// # Arguments
|
||||
/// * `path` - The directory to search for files, relative to the root directory.
|
||||
/// * `pattern` - The regex pattern to match files against. If missing, all files are matched.
|
||||
/// # Returns
|
||||
/// A HashMap containing the file paths and contents.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use read_files::read_files_to_string;
|
||||
///
|
||||
/// let files = read_files_to_string!("./src", ".rs$");
|
||||
/// assert!(!files.is_empty());
|
||||
/// ```
|
||||
/// # Panics
|
||||
/// If the path is empty. \
|
||||
/// If the pattern is invalid. \
|
||||
/// If the path does not exist. \
|
||||
/// If there are unexpected tokens. \
|
||||
#[proc_macro]
|
||||
pub fn read_files_to_string(input: TokenStream) -> TokenStream {
|
||||
let args = parse_macro_input!(input as read_files::Args);
|
||||
read_files_to_string_impl(args)
|
||||
}
|
124
crates/read_files/src/read_files.rs
Normal file
124
crates/read_files/src/read_files.rs
Normal file
@ -0,0 +1,124 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{metadata, read_dir, read_to_string},
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
LitStr, Token,
|
||||
};
|
||||
|
||||
pub fn read_files_to_string_impl(args: Args) -> TokenStream {
|
||||
let (keys, values) = split_hashmap(args);
|
||||
|
||||
let expanded = quote! {
|
||||
{
|
||||
let keys = vec![#( #keys, )*];
|
||||
let values = vec![#( #values, )*];
|
||||
keys.into_iter()
|
||||
.zip(values.into_iter())
|
||||
.collect::<std::collections::HashMap<&'static str, &'static str>>()
|
||||
}
|
||||
};
|
||||
expanded.into()
|
||||
}
|
||||
|
||||
pub struct Args {
|
||||
pub path: String,
|
||||
pub pattern: String,
|
||||
}
|
||||
|
||||
struct Syntax {
|
||||
path: LitStr,
|
||||
/* Comma */
|
||||
pattern: Option<LitStr>,
|
||||
}
|
||||
|
||||
impl From<Syntax> for Args {
|
||||
fn from(syntax: Syntax) -> Self {
|
||||
Self {
|
||||
path: syntax.path.value(),
|
||||
pattern: syntax
|
||||
.pattern
|
||||
.map(|pattern| pattern.value())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Args {
|
||||
fn parse(stream: ParseStream) -> syn::Result<Self> {
|
||||
if stream.is_empty() {
|
||||
panic!("Expected path argument");
|
||||
}
|
||||
|
||||
let path: LitStr = stream.parse()?;
|
||||
if path.value().is_empty() {
|
||||
panic!("Path must not be empty");
|
||||
}
|
||||
|
||||
let pattern = if stream.peek(Token![,]) {
|
||||
stream.parse::<Token![,]>()?;
|
||||
Some(stream.parse()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let syntax = Syntax { path, pattern };
|
||||
if !stream.is_empty() {
|
||||
panic!("Expected end of input");
|
||||
}
|
||||
|
||||
Ok(syntax.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split_hashmap(args: Args) -> (Vec<String>, Vec<String>) {
|
||||
read_files_to_string(Path::new(&args.path), &args.pattern)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key.to_string_lossy().to_string(), value))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Find files within a directory and load them into a HashMap.
|
||||
/// The key is the file path relative to the root directory.
|
||||
/// The value is the file contents as a string.
|
||||
/// # Arguments
|
||||
/// * `path` - The directory to search for files.
|
||||
/// * `extension` - The pattern to match files against.
|
||||
/// # Returns
|
||||
/// A HashMap containing the file paths and contents.
|
||||
pub fn read_files_to_string(
|
||||
path: &Path,
|
||||
pattern: &str,
|
||||
) -> Result<HashMap<PathBuf, String>, io::Error> {
|
||||
use regex::Regex;
|
||||
|
||||
let mut files: HashMap<PathBuf, String> = HashMap::new();
|
||||
let dir = read_dir(path)?;
|
||||
|
||||
for entry in dir {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let file_name = entry.file_name();
|
||||
let file_name = file_name.to_string_lossy();
|
||||
let metadata = metadata(&path)?;
|
||||
|
||||
let regex =
|
||||
Regex::new(pattern).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
|
||||
if metadata.is_file() && regex.is_match(file_name.as_ref()) {
|
||||
let file = read_to_string(&path)?;
|
||||
files.insert(path, file);
|
||||
} else if metadata.is_dir() {
|
||||
files.extend(read_files_to_string(&path, pattern)?);
|
||||
}
|
||||
}
|
||||
Ok(files)
|
||||
}
|
13
crates/read_files/tests/lib.rs
Normal file
13
crates/read_files/tests/lib.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use read_files::read_files_to_string;
|
||||
|
||||
#[test]
|
||||
fn test_load_files() {
|
||||
let files = read_files_to_string!("./src", ".rs$");
|
||||
assert!(!files.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_all_files() {
|
||||
let files = read_files_to_string!("./src");
|
||||
assert!(!files.is_empty());
|
||||
}
|
Reference in New Issue
Block a user