initial commit
This commit is contained in:
commit
e69223d3c2
14 changed files with 663 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
target/
|
52
Cargo.lock
generated
Normal file
52
Cargo.lock
generated
Normal file
|
@ -0,0 +1,52 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "osstr_traits"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "osstr_traits_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"osstr_traits",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
5
Cargo.toml
Normal file
5
Cargo.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/osstr_traits", "crates/osstr_traits_derive"]
|
||||
|
||||
|
1
crates/osstr_traits/.gitignore
vendored
Normal file
1
crates/osstr_traits/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
7
crates/osstr_traits/Cargo.lock
generated
Normal file
7
crates/osstr_traits/Cargo.lock
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "traits"
|
||||
version = "0.1.0"
|
6
crates/osstr_traits/Cargo.toml
Normal file
6
crates/osstr_traits/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "osstr_traits"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
44
crates/osstr_traits/src/impls.rs
Normal file
44
crates/osstr_traits/src/impls.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use crate::{OsDisplay, OsStringFormatter};
|
||||
|
||||
macro_rules! impl_os_from_str {
|
||||
($type:ty) => {
|
||||
impl $crate::OsDisplay for $type {
|
||||
fn fmt_os(&self, f: &mut OsStringFormatter) -> std::fmt::Result {
|
||||
f.write_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_os_from_str!(bool);
|
||||
|
||||
impl_os_from_str!(u8);
|
||||
impl_os_from_str!(u16);
|
||||
impl_os_from_str!(u32);
|
||||
impl_os_from_str!(u64);
|
||||
impl_os_from_str!(u128);
|
||||
impl_os_from_str!(usize);
|
||||
impl_os_from_str!(i8);
|
||||
impl_os_from_str!(i16);
|
||||
impl_os_from_str!(i32);
|
||||
impl_os_from_str!(i64);
|
||||
impl_os_from_str!(i128);
|
||||
impl_os_from_str!(f32);
|
||||
impl_os_from_str!(f64);
|
||||
|
||||
impl_os_from_str!(char);
|
||||
|
||||
impl_os_from_str!(&str);
|
||||
impl_os_from_str!(String);
|
||||
|
||||
impl OsDisplay for std::ffi::OsString {
|
||||
fn fmt_os(&self, f: &mut OsStringFormatter) -> std::fmt::Result {
|
||||
f.write_os_str(self.as_os_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl OsDisplay for &std::ffi::OsStr {
|
||||
fn fmt_os(&self, f: &mut OsStringFormatter) -> std::fmt::Result {
|
||||
f.write_os_str(self)
|
||||
}
|
||||
}
|
56
crates/osstr_traits/src/lib.rs
Normal file
56
crates/osstr_traits/src/lib.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
pub mod impls;
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt;
|
||||
|
||||
pub trait FromOsStr: Sized {
|
||||
type Err;
|
||||
|
||||
fn from_os_str(s: &OsStr) -> Result<Self, Self::Err>;
|
||||
}
|
||||
|
||||
pub trait ToOsString {
|
||||
fn to_os_string(&self) -> OsString;
|
||||
}
|
||||
|
||||
pub struct OsStringFormatter {
|
||||
inner: OsString,
|
||||
}
|
||||
|
||||
impl OsStringFormatter {
|
||||
pub fn new() -> Self {
|
||||
OsStringFormatter {
|
||||
inner: OsString::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_os_str(&mut self, s: &OsStr) -> fmt::Result {
|
||||
self.inner.push(s);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.inner.push(s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOsString for OsStringFormatter {
|
||||
fn to_os_string(&self) -> OsString {
|
||||
self.inner.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OsDisplay {
|
||||
fn fmt_os(&self, f: &mut OsStringFormatter) -> fmt::Result;
|
||||
}
|
||||
|
||||
impl<T: OsDisplay> ToOsString for T {
|
||||
fn to_os_string(&self) -> OsString {
|
||||
let mut formatter = OsStringFormatter::new();
|
||||
match self.fmt_os(&mut formatter) {
|
||||
Ok(_) => formatter.to_os_string(),
|
||||
Err(_) => OsString::new(),
|
||||
}
|
||||
}
|
||||
}
|
1
crates/osstr_traits_derive/.gitignore
vendored
Normal file
1
crates/osstr_traits_derive/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
52
crates/osstr_traits_derive/Cargo.lock
generated
Normal file
52
crates/osstr_traits_derive/Cargo.lock
generated
Normal file
|
@ -0,0 +1,52 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "os_trait_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"os_traits",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_traits"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
13
crates/osstr_traits_derive/Cargo.toml
Normal file
13
crates/osstr_traits_derive/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "osstr_traits_derive"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
osstr_traits = { path = "../osstr_traits" }
|
71
crates/osstr_traits_derive/src/attr_args.rs
Normal file
71
crates/osstr_traits_derive/src/attr_args.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::{Expr, Ident, LitStr, Token, custom_keyword};
|
||||
|
||||
custom_keyword!(transparent);
|
||||
|
||||
pub enum OsDisplayAttribute {
|
||||
Transparent,
|
||||
Format(FormatArgs),
|
||||
}
|
||||
|
||||
pub struct FormatArgs {
|
||||
pub format_string: LitStr,
|
||||
pub positional_args: Vec<Expr>,
|
||||
pub named_args: Vec<(Ident, Expr)>,
|
||||
}
|
||||
|
||||
impl Parse for OsDisplayAttribute {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
|
||||
if lookahead.peek(transparent) {
|
||||
input.parse::<transparent>()?;
|
||||
if !input.is_empty() {
|
||||
return Err(input.error("Unexpected tokens after `transparent` attribute."));
|
||||
}
|
||||
return Ok(OsDisplayAttribute::Transparent);
|
||||
} else if lookahead.peek(LitStr) {
|
||||
let format_string = input.parse()?;
|
||||
|
||||
let mut positional_args = Vec::new();
|
||||
let mut named_args = Vec::new();
|
||||
|
||||
while input.peek(Token![,]) {
|
||||
input.parse::<Token![,]>()?;
|
||||
|
||||
if input.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let lookahead_arg = input.fork();
|
||||
if lookahead_arg.peek(Ident) && lookahead_arg.peek2(Token![=]) {
|
||||
let name: Ident = input.parse()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
let value: Expr = input.parse()?;
|
||||
named_args.push((name, value));
|
||||
} else {
|
||||
let expr: Expr = input.parse()?;
|
||||
positional_args.push(expr);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(OsDisplayAttribute::Format(FormatArgs {
|
||||
format_string,
|
||||
positional_args,
|
||||
named_args,
|
||||
}))
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FormatArgs {
|
||||
fn default() -> Self {
|
||||
FormatArgs {
|
||||
format_string: LitStr::new("", proc_macro2::Span::call_site()),
|
||||
positional_args: Vec::new(),
|
||||
named_args: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
351
crates/osstr_traits_derive/src/lib.rs
Normal file
351
crates/osstr_traits_derive/src/lib.rs
Normal file
|
@ -0,0 +1,351 @@
|
|||
mod attr_args;
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use crate::attr_args::{OsDisplayAttribute};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::parse::Parse;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Data, DeriveInput, Expr, Fields, Ident, LitStr, parse_macro_input};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[proc_macro_derive(OsDisplay, attributes(os_display))]
|
||||
pub fn os_display_derive(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let name = &input.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
let os_display_impl = match &input.data {
|
||||
Data::Enum(data_enum) => {
|
||||
let variant_arms: Vec<_> = data_enum
|
||||
.variants
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
let os_display_attr = variant
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path().is_ident("os_display"));
|
||||
|
||||
let format_tokens = if let Some(attr) = os_display_attr {
|
||||
let parsed_attr = attr
|
||||
.parse_args_with(OsDisplayAttribute::parse)
|
||||
.expect("Failed to parse #[os_display] attribute arguments");
|
||||
|
||||
match parsed_attr {
|
||||
OsDisplayAttribute::Transparent => {
|
||||
match &variant.fields {
|
||||
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
|
||||
quote_spanned! {variant.span() =>
|
||||
match self {
|
||||
#name::#variant_name(value) => value.fmt_os(f),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Fields::Named(fields) if fields.named.len() == 1 => {
|
||||
let field_ident = fields.named.first().unwrap().ident.as_ref().unwrap();
|
||||
quote_spanned! {variant.span() =>
|
||||
match self {
|
||||
#name::#variant_name{#field_ident} => #field_ident.fmt_os(f),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return quote_spanned! {variant.span() =>
|
||||
compile_error!("#[os_display(transparent)] can only be used on single-field enum variants.");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
OsDisplayAttribute::Format(format_args) => {
|
||||
let format_str_value = format_args.format_string.value();
|
||||
|
||||
let positional_expressions: Vec<&Expr> = format_args.positional_args.iter().collect();
|
||||
|
||||
let named_expressions: HashMap<String, Expr> = format_args.named_args
|
||||
.into_iter()
|
||||
.map(|(ident, expr)| (ident.to_string(), expr))
|
||||
.collect();
|
||||
|
||||
parse_os_display_format_string(
|
||||
&format_str_value,
|
||||
&positional_expressions,
|
||||
&named_expressions,
|
||||
variant.span(),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let variant_name_str = format!("{}", variant_name);
|
||||
quote! { f.write_str(#variant_name_str)?; }
|
||||
};
|
||||
|
||||
match &variant.fields {
|
||||
Fields::Unit => {
|
||||
quote! {
|
||||
#name::#variant_name => { #format_tokens }
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let field_idents: Vec<Ident> = fields
|
||||
.unnamed
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| Ident::new(&format!("_{}", i), variant.span()))
|
||||
.collect();
|
||||
if let Some(attr) = os_display_attr {
|
||||
if let Ok(OsDisplayAttribute::Transparent) = attr.parse_args_with(OsDisplayAttribute::parse) {
|
||||
quote! {
|
||||
#name::#variant_name(value) => { #format_tokens }
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name::#variant_name(#(#field_idents),*) => { #format_tokens }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name::#variant_name(#(#field_idents),*) => { #format_tokens }
|
||||
}
|
||||
}
|
||||
}
|
||||
Fields::Named(fields) => {
|
||||
let field_idents: Vec<Ident> = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f| f.ident.as_ref().unwrap().clone())
|
||||
.collect();
|
||||
if let Some(attr) = os_display_attr {
|
||||
if let Ok(OsDisplayAttribute::Transparent) = attr.parse_args_with(OsDisplayAttribute::parse) {
|
||||
let field_ident = fields.named.first().unwrap().ident.as_ref().unwrap();
|
||||
quote! {
|
||||
#name::#variant_name{#field_ident} => { #format_tokens }
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name::#variant_name{#(#field_idents),*} => { #format_tokens }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name::#variant_name{#(#field_idents),*} => { #format_tokens }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
impl #impl_generics os_traits::OsDisplay for #name #ty_generics #where_clause {
|
||||
fn fmt_os(&self, f: &mut os_traits::OsStringFormatter) -> std::fmt::Result {
|
||||
match self {
|
||||
#(#variant_arms),*
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Data::Struct(data_struct) => {
|
||||
let os_display_attr = input
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path().is_ident("os_display"));
|
||||
|
||||
if let Some(attr) = os_display_attr {
|
||||
let parsed_attr = attr
|
||||
.parse_args_with(OsDisplayAttribute::parse)
|
||||
.expect("Failed to parse #[os_display] attribute arguments");
|
||||
|
||||
match parsed_attr {
|
||||
OsDisplayAttribute::Transparent => {
|
||||
match &data_struct.fields {
|
||||
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
|
||||
quote! {
|
||||
impl #impl_generics os_traits::OsDisplay for #name #ty_generics #where_clause {
|
||||
fn fmt_os(&self, f: &mut os_traits::OsStringFormatter) -> std::fmt::Result {
|
||||
self.0.fmt_os(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Fields::Named(fields) if fields.named.len() == 1 => {
|
||||
let field_ident = fields.named.first().unwrap().ident.as_ref().unwrap();
|
||||
quote! {
|
||||
impl #impl_generics os_traits::OsDisplay for #name #ty_generics #where_clause {
|
||||
fn fmt_os(&self, f: &mut os_traits::OsStringFormatter) -> std::fmt::Result {
|
||||
self.#field_ident.fmt_os(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
quote_spanned! {name.span() =>
|
||||
compile_error!("#[os_display(transparent)] can only be used on single-field structs (newtypes).");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OsDisplayAttribute::Format(format_args) => {
|
||||
let format_str_value = format_args.format_string.value();
|
||||
let positional_expressions: Vec<&Expr> = format_args.positional_args.iter().collect();
|
||||
let named_expressions: HashMap<String, Expr> = format_args.named_args
|
||||
.into_iter()
|
||||
.map(|(ident, expr)| (ident.to_string(), expr))
|
||||
.collect();
|
||||
|
||||
let generated_code = parse_os_display_format_string(
|
||||
&format_str_value,
|
||||
&positional_expressions,
|
||||
&named_expressions,
|
||||
name.span(),
|
||||
);
|
||||
|
||||
let field_bindings = match &data_struct.fields {
|
||||
Fields::Named(fields) => {
|
||||
let idents: Vec<&Ident> = fields.named.iter().filter_map(|f| f.ident.as_ref()).collect();
|
||||
quote! {
|
||||
let Self { #(#idents),* } = self;
|
||||
}
|
||||
},
|
||||
Fields::Unnamed(fields) => {
|
||||
let idents: Vec<Ident> = fields.unnamed.iter().enumerate().map(|(i, _)| Ident::new(&format!("_{}", i), name.span())).collect();
|
||||
quote! {
|
||||
let Self(#(#idents),*) = self;
|
||||
}
|
||||
},
|
||||
Fields::Unit => quote!{},
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #impl_generics os_traits::OsDisplay for #name #ty_generics #where_clause {
|
||||
fn fmt_os(&self, f: &mut os_traits::OsStringFormatter) -> std::fmt::Result {
|
||||
#field_bindings
|
||||
#generated_code
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {name.span() =>
|
||||
compile_error!("OsDisplay derive macro is not yet implemented for structs without #[os_display] attribute. Consider adding #[os_display(transparent)] for newtypes or specifying a format string using #[os_display(\"...\")] syntax.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Data::Union(_) => {
|
||||
quote_spanned! {name.span() =>
|
||||
compile_error!("OsDisplay derive macro does not support unions");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
os_display_impl.into()
|
||||
}
|
||||
|
||||
fn parse_os_display_format_string(
|
||||
format_str: &str,
|
||||
positional_expressions: &[&syn::Expr],
|
||||
named_expressions: &HashMap<String, syn::Expr>,
|
||||
span: proc_macro2::Span,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let mut generated_code_parts = Vec::new();
|
||||
let mut current_literal = String::new();
|
||||
let mut chars = format_str.chars().peekable();
|
||||
let mut positional_arg_index = 0;
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '{' {
|
||||
if let Some('{') = chars.peek() {
|
||||
current_literal.push(c);
|
||||
chars.next();
|
||||
} else {
|
||||
if !current_literal.is_empty() {
|
||||
let lit_str = LitStr::new(¤t_literal, span);
|
||||
generated_code_parts.push(quote! { f.write_str(#lit_str)?; });
|
||||
current_literal.clear();
|
||||
}
|
||||
|
||||
let mut placeholder_content = String::new();
|
||||
|
||||
while let Some(p) = chars.next() {
|
||||
if p == '}' {
|
||||
break;
|
||||
}
|
||||
placeholder_content.push(p);
|
||||
}
|
||||
|
||||
let expr_to_format: syn::Expr = if placeholder_content.is_empty() {
|
||||
if let Some(expr_ref) = positional_expressions.get(positional_arg_index) {
|
||||
positional_arg_index += 1;
|
||||
(**expr_ref).clone()
|
||||
} else {
|
||||
return quote_spanned! {span =>
|
||||
compile_error!("Not enough positional arguments for format string: missing argument for empty '{}' placeholder.", #placeholder_content);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let parsed_ident_res: syn::Result<Ident> = syn::parse_str(&placeholder_content);
|
||||
|
||||
if let Ok(ident) = parsed_ident_res {
|
||||
if let Some(expr) = named_expressions.get(&ident.to_string()) {
|
||||
expr.clone()
|
||||
} else {
|
||||
match syn::parse_str(&placeholder_content) {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
let error_message = e.to_string();
|
||||
return quote_spanned! {span =>
|
||||
compile_error!(format!("Invalid placeholder content '{}'. Error: {}. Named arguments must be simple identifiers provided in the attribute, or full expressions.", #placeholder_content, #error_message));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match syn::parse_str(&placeholder_content) {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
let error_message = e.to_string();
|
||||
return quote_spanned! {span =>
|
||||
compile_error!(format!("Invalid expression in os_display attribute: {}. Error: {}", #placeholder_content, #error_message));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
generated_code_parts.push(quote! { (#expr_to_format).fmt_os(f)?; });
|
||||
}
|
||||
} else if c == '}' {
|
||||
if let Some('}') = chars.peek() {
|
||||
current_literal.push(c);
|
||||
chars.next();
|
||||
} else {
|
||||
return quote_spanned! {span =>
|
||||
compile_error!("Mismatched closing brace `}}` in os_display attribute.");
|
||||
};
|
||||
}
|
||||
} else {
|
||||
current_literal.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
if !current_literal.is_empty() {
|
||||
let lit_str = LitStr::new(¤t_literal, span);
|
||||
generated_code_parts.push(quote! { f.write_str(#lit_str)?; });
|
||||
}
|
||||
|
||||
if positional_arg_index < positional_expressions.len() {
|
||||
return quote_spanned! {span =>
|
||||
compile_error!("Too many positional arguments for format string: unused arguments provided.");
|
||||
};
|
||||
}
|
||||
quote! { #(#generated_code_parts)* }
|
||||
}
|
||||
|
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
|
Loading…
Add table
Reference in a new issue