feat(uci): allow skipping derived fields and map repeated sections
Build binaries / Build (aarch64-musl) (push) Successful in 1m51s
Build binaries / Build (arm-musleabi) (push) Successful in 1m52s
Build binaries / Build (arm-musleabihf) (push) Successful in 1m49s
Build binaries / Build (armv7-musleabi) (push) Successful in 1m50s
Build binaries / Build (i686-musl) (push) Successful in 1m49s
Build binaries / Build (armv7-musleabihf) (push) Successful in 1m50s
Build binaries / Build (mips64-muslabi64) (push) Successful in 2m11s
Build binaries / Build (mips-musl) (push) Successful in 2m12s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 2m13s
Build binaries / Build (mipsel-musl) (push) Successful in 2m13s
Build binaries / Build (riscv64gc-musl) (push) Successful in 1m55s
Build binaries / Build (x86_64-musl) (push) Successful in 2m0s
Build binaries / Build (aarch64-musl) (push) Successful in 1m51s
Build binaries / Build (arm-musleabi) (push) Successful in 1m52s
Build binaries / Build (arm-musleabihf) (push) Successful in 1m49s
Build binaries / Build (armv7-musleabi) (push) Successful in 1m50s
Build binaries / Build (i686-musl) (push) Successful in 1m49s
Build binaries / Build (armv7-musleabihf) (push) Successful in 1m50s
Build binaries / Build (mips64-muslabi64) (push) Successful in 2m11s
Build binaries / Build (mips-musl) (push) Successful in 2m12s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 2m13s
Build binaries / Build (mipsel-musl) (push) Successful in 2m13s
Build binaries / Build (riscv64gc-musl) (push) Successful in 1m55s
Build binaries / Build (x86_64-musl) (push) Successful in 2m0s
This commit is contained in:
5 files changed
+127
-28
No files matched your search
Generated
+2
-2
@@ -403,7 +403,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "libuci-sys"
|
name = "libuci-sys"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
source = "git+https://github.com/Lochnair/rust-uci.git?rev=fcfedd204fb76928ef617bf9631b041c816e03d4#fcfedd204fb76928ef617bf9631b041c816e03d4"
|
source = "git+https://github.com/Lochnair/rust-uci.git?rev=6835397f4f4ba8b69e75e727105186865c357417#6835397f4f4ba8b69e75e727105186865c357417"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -629,7 +629,7 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-uci"
|
name = "rust-uci"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
source = "git+https://github.com/Lochnair/rust-uci.git?rev=fcfedd204fb76928ef617bf9631b041c816e03d4#fcfedd204fb76928ef617bf9631b041c816e03d4"
|
source = "git+https://github.com/Lochnair/rust-uci.git?rev=6835397f4f4ba8b69e75e727105186865c357417#6835397f4f4ba8b69e75e727105186865c357417"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"libuci-sys",
|
"libuci-sys",
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ netlink-bindings = { version = "0.3.3", features = ["rt-link", "tc"] }
|
|||||||
netlink-socket2 = { version = "0.3.3" }
|
netlink-socket2 = { version = "0.3.3" }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
rust-uci = { git = "https://github.com/Lochnair/rust-uci.git", rev = "fcfedd204fb76928ef617bf9631b041c816e03d4", optional = true, default-features = false, }
|
rust-uci = { git = "https://github.com/Lochnair/rust-uci.git", rev = "6835397f4f4ba8b69e75e727105186865c357417", optional = true, default-features = false, }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["irtt", "toml", "uci-vendored"]
|
default = ["irtt", "toml", "uci-vendored"]
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ use syn::{Data, DeriveInput, Field, Fields, FieldsNamed, LitStr, Token, parse_ma
|
|||||||
|
|
||||||
/// Generates `UciSectionSchema` from a settings struct.
|
/// Generates `UciSectionSchema` from a settings struct.
|
||||||
///
|
///
|
||||||
/// Every named field is mapped one-to-one:
|
/// Every named field without `#[uci(skip)]` is mapped one-to-one:
|
||||||
///
|
///
|
||||||
/// ```text
|
/// ```text
|
||||||
/// Rust field name -> UCI option name
|
/// Rust field name -> UCI option name
|
||||||
/// Rust field name -> config-rs field name
|
/// Rust field name -> config-rs field name
|
||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_derive(UciSection)]
|
#[proc_macro_derive(UciSection, attributes(uci))]
|
||||||
pub fn derive_uci_section(input: TokenStream) -> TokenStream {
|
pub fn derive_uci_section(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
@@ -56,26 +56,28 @@ fn expand_uci_section(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStre
|
|||||||
let struct_name = &input.ident;
|
let struct_name = &input.ident;
|
||||||
let fields = require_named_fields(input)?;
|
let fields = require_named_fields(input)?;
|
||||||
|
|
||||||
let mappings = fields
|
let mut mappings = Vec::with_capacity(fields.named.len());
|
||||||
.named
|
|
||||||
.iter()
|
|
||||||
.map(|field| {
|
|
||||||
let field_ident = field
|
|
||||||
.ident
|
|
||||||
.as_ref()
|
|
||||||
.expect("named fields always have identifiers");
|
|
||||||
|
|
||||||
let field_name = field_ident.unraw().to_string();
|
for field in &fields.named {
|
||||||
let field_name = LitStr::new(&field_name, field_ident.span());
|
if parse_skip(field)? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
quote! {
|
let field_ident = field
|
||||||
crate::settings::uci_schema::UciOptionMapping {
|
.ident
|
||||||
uci_option: #field_name,
|
.as_ref()
|
||||||
config_field: #field_name,
|
.expect("named fields always have identifiers");
|
||||||
}
|
|
||||||
|
let field_name = field_ident.unraw().to_string();
|
||||||
|
let field_name = LitStr::new(&field_name, field_ident.span());
|
||||||
|
|
||||||
|
mappings.push(quote! {
|
||||||
|
crate::settings::uci_schema::UciOptionMapping {
|
||||||
|
uci_option: #field_name,
|
||||||
|
config_field: #field_name,
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.collect::<Vec<_>>();
|
}
|
||||||
|
|
||||||
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
|
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
|
||||||
|
|
||||||
@@ -95,6 +97,29 @@ fn expand_uci_section(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStre
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_skip(field: &Field) -> syn::Result<bool> {
|
||||||
|
let mut skip = false;
|
||||||
|
|
||||||
|
for attribute in &field.attrs {
|
||||||
|
if !attribute.path().is_ident("uci") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute.parse_nested_meta(|meta| {
|
||||||
|
if !meta.path.is_ident("skip") {
|
||||||
|
return Err(meta.error("expected `skip`"));
|
||||||
|
}
|
||||||
|
if skip {
|
||||||
|
return Err(meta.error("duplicate `skip` attribute"));
|
||||||
|
}
|
||||||
|
skip = true;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(skip)
|
||||||
|
}
|
||||||
|
|
||||||
fn expand_uci_config(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
|
fn expand_uci_config(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
|
||||||
let struct_name = &input.ident;
|
let struct_name = &input.ident;
|
||||||
let fields = require_named_fields(input)?;
|
let fields = require_named_fields(input)?;
|
||||||
|
|||||||
+68
-6
@@ -1,6 +1,6 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::settings::uci_schema::{UciConfigSchema, UciSectionMapping};
|
use crate::settings::uci_schema::{UciConfigSchema, UciRepeatedSectionMapping, UciSectionMapping};
|
||||||
use config::{ConfigError as SourceError, Map, Source, Value, ValueKind};
|
use config::{ConfigError as SourceError, Map, Source, Value, ValueKind};
|
||||||
|
|
||||||
use rust_uci::config::{
|
use rust_uci::config::{
|
||||||
@@ -18,6 +18,7 @@ pub(crate) struct UciSource {
|
|||||||
packages: PackageSelection,
|
packages: PackageSelection,
|
||||||
required: bool,
|
required: bool,
|
||||||
schema: &'static [UciSectionMapping],
|
schema: &'static [UciSectionMapping],
|
||||||
|
repeated_schema: &'static [UciRepeatedSectionMapping],
|
||||||
config_dir: Option<PathBuf>,
|
config_dir: Option<PathBuf>,
|
||||||
save_dir: Option<PathBuf>,
|
save_dir: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
@@ -28,6 +29,7 @@ impl UciSource {
|
|||||||
packages,
|
packages,
|
||||||
required: true,
|
required: true,
|
||||||
schema: &[],
|
schema: &[],
|
||||||
|
repeated_schema: &[],
|
||||||
config_dir: None,
|
config_dir: None,
|
||||||
save_dir: None,
|
save_dir: None,
|
||||||
}
|
}
|
||||||
@@ -65,6 +67,17 @@ impl UciSource {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers section types collected as arrays of tables at their config paths.
|
||||||
|
/// Absent section types leave the destination unset for Serde/source defaults.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn with_repeated_schema(
|
||||||
|
mut self,
|
||||||
|
schema: &'static [UciRepeatedSectionMapping],
|
||||||
|
) -> Self {
|
||||||
|
self.repeated_schema = schema;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn with_directories(
|
pub(crate) fn with_directories(
|
||||||
mut self,
|
mut self,
|
||||||
@@ -145,6 +158,7 @@ impl UciSource {
|
|||||||
package_name: &str,
|
package_name: &str,
|
||||||
package: &UciPackage,
|
package: &UciPackage,
|
||||||
values: &mut Map<String, Value>,
|
values: &mut Map<String, Value>,
|
||||||
|
repeated_values: &mut Map<String, Vec<Value>>,
|
||||||
) -> Result<(), SourceError> {
|
) -> Result<(), SourceError> {
|
||||||
let sections = package.sections().map_err(|e| {
|
let sections = package.sections().map_err(|e| {
|
||||||
SourceError::Message(format!(
|
SourceError::Message(format!(
|
||||||
@@ -154,6 +168,12 @@ impl UciSource {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
for section in sections {
|
for section in sections {
|
||||||
|
let repeated_mapping = self
|
||||||
|
.repeated_schema
|
||||||
|
.iter()
|
||||||
|
.find(|mapping| mapping.uci_section == section.type_());
|
||||||
|
let mut section_values = Map::new();
|
||||||
|
|
||||||
let selector = section.selector().ok_or_else(|| {
|
let selector = section.selector().ok_or_else(|| {
|
||||||
SourceError::Message(format!(
|
SourceError::Message(format!(
|
||||||
"loaded UCI section of type {:?} in package \
|
"loaded UCI section of type {:?} in package \
|
||||||
@@ -181,9 +201,21 @@ impl UciSource {
|
|||||||
|
|
||||||
let source_key = format!("{package_name}.{config_relative_key}");
|
let source_key = format!("{package_name}.{config_relative_key}");
|
||||||
|
|
||||||
let destination_key = self
|
let repeated_option = repeated_mapping.and_then(|mapping| {
|
||||||
.mapped_config_key(&selector, option_name)
|
mapping
|
||||||
.unwrap_or(source_key);
|
.options
|
||||||
|
.iter()
|
||||||
|
.find(|option| option.uci_option == option_name)
|
||||||
|
});
|
||||||
|
|
||||||
|
let destination_key = if let Some(mapping) = repeated_option {
|
||||||
|
mapping.config_field.to_owned()
|
||||||
|
} else if repeated_mapping.is_some() {
|
||||||
|
source_key
|
||||||
|
} else {
|
||||||
|
self.mapped_config_key(&selector, option_name)
|
||||||
|
.unwrap_or(source_key)
|
||||||
|
};
|
||||||
|
|
||||||
let Some(value) = option.get().map_err(|e| {
|
let Some(value) = option.get().map_err(|e| {
|
||||||
SourceError::Message(format!(
|
SourceError::Message(format!(
|
||||||
@@ -210,13 +242,27 @@ impl UciSource {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if values.insert(destination_key.clone(), value).is_some() {
|
let destination = if repeated_option.is_some() {
|
||||||
|
&mut section_values
|
||||||
|
} else {
|
||||||
|
&mut *values
|
||||||
|
};
|
||||||
|
|
||||||
|
if destination.insert(destination_key.clone(), value).is_some() {
|
||||||
return Err(SourceError::Message(format!(
|
return Err(SourceError::Message(format!(
|
||||||
"duplicate UCI destination key \
|
"duplicate UCI destination key \
|
||||||
{destination_key:?} while mapping {uci_key:?}"
|
{destination_key:?} while mapping {uci_key:?}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(mapping) = repeated_mapping {
|
||||||
|
let origin = format!("UCI section {package_name}.{selector}");
|
||||||
|
repeated_values
|
||||||
|
.entry(mapping.config_path.to_owned())
|
||||||
|
.or_default()
|
||||||
|
.push(Value::new(Some(&origin), ValueKind::Table(section_values)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -272,6 +318,7 @@ impl Source for UciSource {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut values = Map::new();
|
let mut values = Map::new();
|
||||||
|
let mut repeated_values = Map::new();
|
||||||
|
|
||||||
for package in packages {
|
for package in packages {
|
||||||
let package_name = package
|
let package_name = package
|
||||||
@@ -279,7 +326,22 @@ impl Source for UciSource {
|
|||||||
.map_err(|e| SourceError::Message(format!("failed to read UCI package name: {e}")))?
|
.map_err(|e| SourceError::Message(format!("failed to read UCI package name: {e}")))?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
self.collect_package(&package_name, &package, &mut values)?;
|
self.collect_package(&package_name, &package, &mut values, &mut repeated_values)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (config_path, sections) in repeated_values {
|
||||||
|
let origin = "UCI repeated sections".to_owned();
|
||||||
|
if values
|
||||||
|
.insert(
|
||||||
|
config_path.clone(),
|
||||||
|
Value::new(Some(&origin), ValueKind::Array(sections)),
|
||||||
|
)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(SourceError::Message(format!(
|
||||||
|
"duplicate UCI destination key {config_path:?} while mapping repeated sections"
|
||||||
|
)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(values)
|
Ok(values)
|
||||||
|
|||||||
@@ -19,6 +19,18 @@ pub(crate) struct UciSectionMapping {
|
|||||||
pub(crate) options: &'static [UciOptionMapping],
|
pub(crate) options: &'static [UciOptionMapping],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "uci", unix))]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) struct UciRepeatedSectionMapping {
|
||||||
|
/// UCI section type, for both named and anonymous sections.
|
||||||
|
pub(crate) uci_section: &'static str,
|
||||||
|
|
||||||
|
/// Config-rs / Serde path receiving an array of section tables.
|
||||||
|
pub(crate) config_path: &'static str,
|
||||||
|
|
||||||
|
pub(crate) options: &'static [UciOptionMapping],
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) trait UciSectionSchema {
|
pub(crate) trait UciSectionSchema {
|
||||||
const UCI_OPTIONS: &'static [UciOptionMapping];
|
const UCI_OPTIONS: &'static [UciOptionMapping];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user