7 Commits
1.3.2 ... 1.4.1

Author SHA1 Message Date
3389b2264e Removed trim fron inner parenthesized 2024-07-17 12:48:09 +02:00
5cd1c075a5 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.*
2024-07-16 18:29:32 +02:00
7a0cf00cbc Accept IpAddr or Ipv6Addr as socket.
Added port in builder for only specifying port
2024-07-07 15:13:13 +02:00
971556af64 Merge remote-tracking branch 'origin/master' 2024-07-06 13:27:38 +02:00
f40c87aa8e Changed router visibility to pub 2024-07-06 13:27:24 +02:00
b685d81e00 comment 2024-07-03 11:24:28 +02:00
284ee73ffd Changed parsers to FnMut 2024-07-02 13:24:37 +02:00
23 changed files with 377 additions and 107 deletions

View File

@ -15,4 +15,4 @@ jobs:
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose --all-features
run: cargo test --verbose --all-features --workspace

9
.idea/lib.iml generated
View File

@ -2,13 +2,18 @@
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/derive/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples/multipart_file/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/derive/target" />
<sourceFolder url="file://$MODULE_DIR$/crates/into_response_derive/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/crates/read_files/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/crates/read_files/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/crates/read_files/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/examples/multipart_file/target" />
<excludeFolder url="file://$MODULE_DIR$/crates/into_response_derive/target" />
<excludeFolder url="file://$MODULE_DIR$/crates/read_files/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View File

@ -1,12 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="test --package lib --lib tests --all-features" />
<configuration default="false" name="All Tests" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="test --workspace" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="allFeatures" value="true" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />

19
.idea/runConfigurations/Release.xml generated Normal file
View File

@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Release" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="build --release --all-features" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="true" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="true" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

6
.idea/rust.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RsVcsConfiguration">
<option name="rustFmt" value="true" />
</component>
</project>

72
Cargo.lock generated
View File

@ -17,6 +17,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "async-trait"
version = "0.1.80"
@ -123,14 +132,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "derive"
version = "1.0.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "encoding_rs"
version = "0.8.34"
@ -280,6 +281,14 @@ dependencies = [
"tokio",
]
[[package]]
name = "into-response-derive"
version = "1.1.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "itoa"
version = "1.0.11"
@ -294,11 +303,12 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lib"
version = "1.3.2"
version = "1.4.1"
dependencies = [
"axum",
"derive",
"into-response-derive",
"nom",
"read-files",
"serde",
"thiserror",
"tokio",
@ -479,6 +489,44 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "read-files"
version = "0.1.0"
dependencies = [
"quote",
"regex",
"syn",
]
[[package]]
name = "regex"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@ -583,9 +631,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "syn"
version = "2.0.67"
version = "2.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90"
checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462"
dependencies = [
"proc-macro2",
"quote",

View File

@ -1,10 +1,21 @@
[package]
name = "lib"
version = "1.3.2"
[workspace]
members = ["crates/*"]
[workspace.package]
edition = "2021"
rust-version = "1.79.0"
authors = ["Martin Berg Alstad"]
homepage = "emberal.github.io"
[package]
name = "lib"
version = "1.4.1"
description = "A library with utilities and helper fuctions."
edition = { workspace = true }
rust-version = { workspace = true }
authors = { workspace = true }
homepage = { workspace = true }
[lib]
[dependencies]
@ -22,10 +33,15 @@ tracing = { version = "0.1.40", optional = true }
tracing-subscriber = { version = "0.3.18", optional = true }
# Parsing
nom = { version = "7.1.3", optional = true }
# Procedural macros
into-response-derive = { path = "crates/into_response_derive", optional = true }
read-files = { path = "crates/read_files", optional = true }
# Serialization / Deserialization
serde = { version = "1.0.203", optional = true, features = ["derive"] }
# Derive macros
derive = { path = "derive", optional = true }
[workspace.dependencies]
syn = "2.0.71"
quote = "1.0.36"
[features]
axum = ["dep:axum", "dep:tower", "dep:tower-http", "dep:thiserror", "dep:tracing", "dep:tracing-subscriber", "dep:tokio"]
@ -33,4 +49,5 @@ io = ["dep:tokio", "dep:tokio-util"]
iter = []
nom = ["dep:nom"]
serde = ["dep:serde"]
derive = ["dep:derive", "axum", "serde"]
derive = ["dep:into-response-derive", "axum", "serde"]
read-files = ["dep:read-files"]

3
Makefile Normal file
View File

@ -0,0 +1,3 @@
fmt:
cargo clippy --all-targets --all-features
cargo fmt

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Lib
-_-

View 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 }

View File

@ -1,16 +1,9 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use syn::DeriveInput;
#[proc_macro_derive(IntoResponse)]
pub fn into_response_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
into_response_derive_impl(input)
}
fn into_response_derive_impl(input: DeriveInput) -> TokenStream {
pub fn into_response_derive_impl(input: DeriveInput) -> TokenStream {
let name = &input.ident;
let expanded = quote! {

View 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)
}

View 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"

View 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)
}

View 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)
}

View 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());
}

46
derive/Cargo.lock generated
View File

@ -1,46 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "derive"
version = "1.0.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

View File

@ -1,12 +0,0 @@
[package]
name = "derive"
version = "1.0.0"
edition = "2021"
authors = ["Martin Berg Alstad"]
[lib]
proc-macro = true
[dependencies]
syn = "2.0.66"
quote = "1.0.36"

View File

@ -286,7 +286,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lib"
version = "1.3.2"
version = "1.3.5"
dependencies = [
"axum",
"thiserror",

View File

@ -1,3 +1,4 @@
use std::net::IpAddr;
use {
axum::{extract::Request, handler::Handler, Router, ServiceExt},
std::{io, net::Ipv4Addr, net::SocketAddr},
@ -26,7 +27,7 @@ macro_rules! create_app {
#[derive(Default)]
pub struct AppBuilder {
router: Router,
socket: Option<(Ipv4Addr, u16)>,
socket: Option<(IpAddr, u16)>,
cors: Option<CorsLayer>,
normalize_path: Option<bool>,
tracing: Option<TraceLayer<HttpMakeClassifier>>,
@ -47,8 +48,18 @@ impl AppBuilder {
self
}
pub fn socket(mut self, socket: impl Into<(Ipv4Addr, u16)>) -> Self {
self.socket = Some(socket.into());
pub fn socket<IP: Into<IpAddr>>(mut self, socket: impl Into<(IP, u16)>) -> Self {
let (ip, port) = socket.into();
self.socket = Some((ip.into(), port));
self
}
pub fn port(mut self, port: u16) -> Self {
self.socket = if let Some((ip, _)) = self.socket {
Some((ip, port))
} else {
Some((Ipv4Addr::UNSPECIFIED.into(), port))
};
self
}
@ -91,7 +102,7 @@ impl AppBuilder {
}
async fn listener(&self) -> io::Result<TcpListener> {
let addr = SocketAddr::from(self.socket.unwrap_or((Ipv4Addr::UNSPECIFIED, 8000)));
let addr = SocketAddr::from(self.socket.unwrap_or((Ipv4Addr::UNSPECIFIED.into(), 8000)));
info!("Initializing server on: {addr}");
TcpListener::bind(&addr).await
}

View File

@ -20,12 +20,12 @@
#[macro_export]
macro_rules! router {
($body:expr) => {
pub(crate) fn router() -> axum::Router {
pub fn router() -> axum::Router {
$body
}
};
($body:expr; $state:ty) => {
pub(crate) fn router() -> axum::Router<$state> {
pub fn router() -> axum::Router<$state> {
$body
}
};

View File

@ -1,5 +1,10 @@
#![allow(dead_code)]
#[cfg(all(feature = "derive", feature = "serde"))]
pub extern crate into_response_derive;
#[cfg(feature = "load-files")]
pub extern crate load_files;
#[cfg(feature = "axum")]
pub mod axum;
#[cfg(feature = "io")]
@ -11,6 +16,3 @@ pub mod serde;
pub mod traits;
#[cfg(feature = "iter")]
pub mod vector;
#[cfg(all(feature = "derive", feature = "serde"))]
pub extern crate derive;

View File

@ -17,7 +17,7 @@ use {
/// - Returns: A parser that trims leading and trailing whitespace from the input and then runs the value from the inner parser
pub fn trim<'a, Parser, R>(inner: Parser) -> impl FnMut(&'a str) -> IResult<&'a str, R>
where
Parser: Fn(&'a str) -> IResult<&'a str, R>,
Parser: FnMut(&'a str) -> IResult<&'a str, R>,
{
delimited(multispace0, inner, multispace0)
}
@ -29,9 +29,9 @@ where
/// - Returns: A parser that parses a parenthesized expression
pub fn parenthesized<'a, Parser, R>(inner: Parser) -> impl FnMut(&'a str) -> IResult<&'a str, R>
where
Parser: Fn(&'a str) -> IResult<&'a str, R>,
Parser: FnMut(&'a str) -> IResult<&'a str, R>,
{
delimited(char('('), trim(inner), char(')'))
delimited(char('('), inner, char(')'))
}
/// Take where the predicate is true and the length is exactly `n`
@ -49,14 +49,14 @@ where
pub fn exhausted<'a, Parser, R>(inner: Parser) -> impl FnMut(&'a str) -> IResult<&'a str, R>
where
Parser: Fn(&'a str) -> IResult<&'a str, R>,
Parser: FnMut(&'a str) -> IResult<&'a str, R>,
{
terminated(inner, eof)
}
#[cfg(test)]
mod tests {
use nom::bytes::streaming::take_while;
use nom::{bytes::complete::take_while, sequence::tuple};
use super::*;
@ -154,4 +154,16 @@ mod tests {
let input = "test ";
assert!(exhausted(take_where(4, |c: char| c.is_ascii_alphabetic()))(input).is_err());
}
#[test]
fn test_exhausted_tuple() {
let input = "test";
let (remaining, result) = exhausted(tuple((
take_where(3, |c: char| c.is_ascii_alphabetic()),
take_while(|c: char| c.is_ascii_alphabetic()),
)))(input)
.unwrap();
assert_eq!(remaining, "");
assert_eq!(result, ("tes", "t"));
}
}