Skip to content

Commit

Permalink
Release version v0.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Volkalex28 committed Jan 8, 2025
1 parent ae74585 commit fe5eeda
Show file tree
Hide file tree
Showing 58 changed files with 2,710 additions and 1,198 deletions.
13 changes: 8 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,20 @@ edition = "2021"
homepage = "https://github.com/Volkalex28/varu-emb#readme"
license = "MIT"
repository = "https://github.com/Volkalex28/varu-emb"
rust-version = "1.77"
version = "0.3.3"
rust-version = "1.80"
version = "0.4.0"

[workspace]
members = ["cross", "executor", "lockfree", "notifier", "utils", "build"]
members = ["cfg", "cross", "executor", "lockfree", "notifier", "utils", "build"]

[features]
default = ["cross", "devices", "executor", "utils"]
std = ["cross/std", "executor/std"]
default = ["cfg", "cross", "devices", "executor", "utils"]
std = ["cross?/std", "executor?/std"]

cross = ["dep:cross", "utils"]

[dependencies]
cfg = { path = "cfg", package = "varuemb-cfg", optional = true }
cross = { path = "cross", package = "varuemb-cross", optional = true }
devices = { path = "devices", package = "varuemb-devices", optional = true }
executor = { path = "executor", package = "varuemb-executor", optional = true }
Expand Down
1 change: 1 addition & 0 deletions build/cross/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub struct Cfg;

impl Cfg {
pub fn link() {
println!("cargo::rustc-check-cfg=cfg(cross_platform)");
println!("cargo::rustc-check-cfg=cfg(cross_platform, values(any()))");
}
}
29 changes: 29 additions & 0 deletions cfg/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "varuemb-cfg"

categories = ["toml", "config"]
description = """Load configuration for your crate from toml file"""
include = ["/src"]
keywords = ["toml", "config", "varuemb"]
readme = "README.md"

authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true

[lib]
proc-macro = true

[dependencies]
enum-as-derive = { version = "0.1" }
heck = { version = "0.5" }
linked-hash-map = { version = "0.5" }
proc-macro2 = { version = "1.0" }
quote = { version = "1.0" }
syn = { version = "2.0", features = ["full", "extra-traits"] }
syn_derive = { version = "0.2" }
toml = { version = "0.8" }
229 changes: 229 additions & 0 deletions cfg/src/cfg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
use enum_as_derive::EnumAs;
use heck::{ToShoutySnakeCase, ToSnakeCase};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use std::collections::HashMap;
use std::str::FromStr;
use std::{env, path};
use syn::spanned::Spanned;
use syn::{parse, Error, Token};
use syn_derive::{Parse, ToTokens};

type Result<T, E = Error> = std::result::Result<T, E>;
type Fields<A = Attribute> = HashMap<syn::Ident, A>;
type CfgMap = HashMap<String, Fields<syn::Expr>>;

#[derive(EnumAs, ToTokens)]
enum Attribute {
Default(TokenStream),

Link {
#[to_tokens(|t, v| t.extend(quote! { #v ::__VARUEMB_CFG_LINK }) )]
path: syn::Path,
},
}
impl From<parsing::Attribute> for Attribute {
fn from(attribute: parsing::Attribute) -> Self {
match attribute.ty {
parsing::AttributeType::Default { value, .. } => Self::Default(value),
parsing::AttributeType::Link { path, .. } => Self::Link { path },
}
}
}

pub struct Cfg {
path: path::PathBuf,
toml: CfgMap,
fields: Fields,
input: syn::Ident,
}
impl Cfg {
fn get_fields(input: syn::Fields) -> Result<Fields<Vec<syn::Attribute>>> {
let syn::Fields::Named(fields) = input else {
return Err(Error::new(input.span(), "Supported only named fields"));
};
let fields = fields.named.into_iter();
let fields = fields.map(|f| (f.ident.unwrap(), f.attrs)).collect();
Ok(fields)
}

fn parse_field(field: syn::Ident, attrs: Vec<syn::Attribute>) -> Result<<Fields as IntoIterator>::Item> {
const MESSAGE: &'static str = "Attribute 'varuemb_cfg(default: ...)' or 'varuemb_cfg(link: ...)' not found on field";

let attribute = attrs
.into_iter()
.find_map(|attr| attr.path().is_ident("varuemb_cfg").then(|| attr.parse_args::<parsing::Attribute>()))
.unwrap_or_else(|| Err(Error::new(field.span(), MESSAGE)))?;

Ok((field, attribute.into()))
}

fn parse_fields(input: syn::Fields) -> Result<Fields> {
let fields = Self::get_fields(input)?;
fields.into_iter().map(|(field, attrs)| Self::parse_field(field, attrs)).try_collect()
}

fn parse_offset(input: Vec<syn::Attribute>) -> Result<Option<String>> {
let Some(attribute) = input
.into_iter()
.find_map(|attr| attr.path().is_ident("varuemb_cfg").then(|| attr.parse_args::<syn::LitStr>()))
.transpose()?
else {
return Ok(None);
};

Ok(Some(attribute.value()))
}

fn apply_offset(
offset: &mut dyn Iterator<Item = &str>,
mut table: toml::Table,
) -> Option<Result<toml::Table, &'static str>> {
let Some(current) = offset.next() else {
return Some(Ok(table));
};
let toml::Value::Table(table) = table.remove(current)? else {
return Some(Err("Must be a table"));
};
Self::apply_offset(offset, table)
}
}
impl parse::Parse for Cfg {
fn parse(input: parse::ParseStream) -> Result<Self> {
let input = input.parse::<syn::ItemStruct>()?;

let fields = Self::parse_fields(input.fields)?;

let (loaded, path) = crate::load()?;
let loaded = loaded.unwrap_or_default();
let offset = Self::parse_offset(input.attrs)?;

let mut toml = CfgMap::with_capacity(loaded.len());
for (key, value) in loaded {
let (entry, table) = if key.starts_with("cfg(") && key.ends_with(")") {
let toml::Value::Table(table) = value else {
return Err(Error::new(Span::call_site(), format!("{key} must be a table")));
};
TokenStream::from_str(key.as_str())?;
(toml.entry(key), table)
} else {
(toml.entry(String::new()), toml::Table::from_iter([(key, value)]))
};

match {
let offset = offset.iter().flat_map(|s| s.split('.').map(|s| s.trim()));
let mut offset = offset.filter(|s| !s.is_empty());

Self::apply_offset(&mut offset, table)
} {
Some(Ok(table)) => {
let is_no_cfg = entry.key().is_empty();
let map = entry.or_default();
for (k, v) in table {
let Some((field, attr)) = fields.get_key_value(&syn::Ident::new(&k, Span::call_site())) else {
continue;
};
if is_no_cfg {
if v.is_table() && attr.is_default() {
return Err(Error::new(field.span(), "Attribute 'default' is not supported for table"));
}
if !v.is_table() && attr.is_link() {
return Err(Error::new(field.span(), "Attribute 'link' is not supported for not table"));
}
}
if !v.is_table() {
let expr = syn::parse_str(v.to_string().as_str())?;
map.insert(field.clone(), expr);
}
}
}
Some(Err(err)) => {
let message = format!(
"{pkg}.{key}.{offset}: {err}",
pkg = env::var("CARGO_PKG_NAME").as_ref().unwrap(),
key = entry.key(),
offset = offset.as_ref().unwrap()
);
return Err(Error::new(Span::call_site(), message));
}
None => continue,
}
}

Ok(Self { path, toml, fields, input: input.ident })
}
}
impl ToTokens for Cfg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let ident = &self.input;
let cfg_path = format!("{}", self.path.display());
let retrigger_ident =
syn::Ident::new(&format!("__varuemb_cfg_{}__", ident.to_string().to_snake_case()), ident.span());
let cfg_ident = syn::Ident::new(&ident.to_string().to_shouty_snake_case(), ident.span());

let default = self.fields.iter().map(|(f, a)| quote! {#f : #a });
let (loaded_default, loaded_cfgs) =
self.toml
.iter()
.fold((TokenStream::new(), TokenStream::new()), |(mut default, mut cfgs), (cfg, data)| {
let cfg = (!cfg.is_empty()).then(|| {
let cfg = TokenStream::from_str(cfg).unwrap();
quote! {#[#cfg]}
});

if !data.is_empty() {
let data = data.iter().map(|(f, v)| quote! { __value . #f = #v });

if cfg.is_some() { &mut cfgs } else { &mut default }.extend(quote! {
#cfg
{ #( #data ;)* }
});
}

(default, cfgs)
});

tokens.extend(quote! {
pub const #cfg_ident : #ident = {
#[allow(unused_mut)]
let mut __value = #ident { #(#default,)* };

#loaded_default
#loaded_cfgs

__value
};

impl #ident {
#[allow(unused)]
pub const __VARUEMB_CFG_LINK: Self = #cfg_ident;
}

mod #retrigger_ident {
const _: &[u8] = include_bytes!(#cfg_path);
}
});
}
}

mod tokens {
syn::custom_keyword!(default);
syn::custom_keyword!(link);
}

mod parsing {
use super::*;

#[derive(Parse, EnumAs)]
pub enum AttributeType {
#[parse(peek = tokens::default)]
Default { _default: tokens::default, _colon: Token![:], value: TokenStream },
#[parse(peek = tokens::link)]
Link { _link: tokens::link, _colon: Token![:], path: syn::Path },
}

#[derive(Parse)]
pub struct Attribute {
pub ty: AttributeType,
}
}
Loading

0 comments on commit fe5eeda

Please sign in to comment.