diff --git a/.lock b/.lock new file mode 100644 index 0000000..e69de29 diff --git a/axconfig_gen/all.html b/axconfig_gen/all.html new file mode 100644 index 0000000..54ae735 --- /dev/null +++ b/axconfig_gen/all.html @@ -0,0 +1 @@ +
Redirecting to ../../axconfig_gen/struct.Config.html...
+ + + \ No newline at end of file diff --git a/axconfig_gen/config/struct.ConfigItem.html b/axconfig_gen/config/struct.ConfigItem.html new file mode 100644 index 0000000..2292e97 --- /dev/null +++ b/axconfig_gen/config/struct.ConfigItem.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../axconfig_gen/struct.ConfigItem.html...
+ + + \ No newline at end of file diff --git a/axconfig_gen/enum.ConfigErr.html b/axconfig_gen/enum.ConfigErr.html new file mode 100644 index 0000000..76564d8 --- /dev/null +++ b/axconfig_gen/enum.ConfigErr.html @@ -0,0 +1,24 @@ +pub enum ConfigErr {
+ Parse(TomlError),
+ InvalidValue,
+ InvalidType,
+ ValueTypeMismatch,
+ Other(String),
+}
The error type on config parsing.
+TOML parsing error.
+Invalid config value.
+Invalid config type.
+Config value and type mismatch.
+Other error.
+pub enum ConfigType {
+ Bool,
+ Int,
+ Uint,
+ String,
+ Tuple(Vec<ConfigType>),
+ Array(Box<ConfigType>),
+ Unknown,
+}
The supported types in the config file.
+Boolean type (bool
).
Signed integer type (int
).
Unsigned integer type (uint
).
String type (str
).
Tuple type (e.g., (int, str)
).
Array type (e.g., [int]
).
Type is unknown.
+It is used for type inference.
+Parses a type string into a ConfigType
.
Converts the type into a Rust type string.
+source
. Read morekey
and return true
if they are equal.pub enum OutputFormat {
+ Toml,
+ Rust,
+}
The format of the generated file.
+source
. Read moreA TOML-based configuration generation tool for ArceOS.
+axconfig-gen [OPTIONS] --spec <SPEC>
+
+Options:
+ -s, --spec <SPEC> Path to the config specification file
+ -c, --oldconfig <OLDCONFIG> Path to the old config file
+ -o, --output <OUTPUT> Path to the output config file
+ -f, --fmt <FMT> The output format [default: toml] [possible values: toml, rust]
+ -w, --write <CONFIG> Setting a config item with format `table.key=value`
+ -v, --verbose Verbose mode
+ -h, --help Print help
+ -V, --version Print version
For example, to generate a config file .axconfig.toml
from the config specifications distributed in a.toml
and b.toml
, you can run:
axconfig-gen -s a.toml -s b.toml -o .axconfig.toml -f toml
See defconfig.toml for an example of a config specification file.
+Value types are necessary for generating Rust constant definitions. Types can be specified by the comment following the config item. Currently supported types are bool
, int
, uint
, str
, (type1, type2, ...)
for tuples, and [type]
for arrays. If no type is specified, it will try to infer the type from the value.
use axconfig_gen::{Config, OutputFormat};
+
+let config_toml = r#"
+are-you-ok = true
+one-two-three = 123
+
+[hello]
+"one-two-three" = "456" # int
+array = [1, 2, 3] # [uint]
+tuple = [1, "abc", 3]
+"#;
+
+let config = Config::from_toml(config_toml).unwrap();
+let rust_code = config.dump(OutputFormat::Rust).unwrap();
+
+assert_eq!(rust_code, r#"
+pub const ARE_YOU_OK: bool = true;
+pub const ONE_TWO_THREE: usize = 123;
+
+pub mod hello {
+ pub const ARRAY: &[usize] = &[1, 2, 3];
+ pub const ONE_TWO_THREE: isize = 456;
+ pub const TUPLE: (usize, &str, usize) = (1, "abc", 3);
+}
+"#);
There is also a procedural macro library axconfig-gen-macros
that can be
+used to include TOML files in your project and convert them to Rust code at
+compile time.
Redirecting to ../../axconfig_gen/enum.OutputFormat.html...
+ + + \ No newline at end of file diff --git a/axconfig_gen/sidebar-items.js b/axconfig_gen/sidebar-items.js new file mode 100644 index 0000000..a9a6307 --- /dev/null +++ b/axconfig_gen/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ConfigErr","ConfigType","OutputFormat"],"struct":["Config","ConfigItem","ConfigValue"],"type":["ConfigResult"]}; \ No newline at end of file diff --git a/axconfig_gen/struct.Config.html b/axconfig_gen/struct.Config.html new file mode 100644 index 0000000..06fe4b2 --- /dev/null +++ b/axconfig_gen/struct.Config.html @@ -0,0 +1,51 @@ +pub struct Config { /* private fields */ }
A structure storing all config items.
+It contains a global table and multiple named tables, each table is a map
+from key to value, the key is a string and the value is a ConfigItem
.
The name of the global table of the config.
+Returns the global table of the config.
+Returns the reference to the table with the specified name.
+Returns the mutable reference to the table with the specified name.
+Returns the reference to the config item with the specified table name and key.
+Returns the mutable reference to the config item with the specified +table name and key.
+Returns the comments of the table with the specified name.
+Returns the iterator of all tables.
+The iterator returns a tuple of table name, table and comments. The
+global table is named $GLOBAL
.
Returns the iterator of all config items.
+The iterator returns a tuple of table name, key and config item. The
+global table is named $GLOBAL
.
Parse a toml string into a config object.
+Dump the config into a string with the specified format.
+Dump the config into TOML format.
+Dump the config into Rust code.
+Merge the other config into self
, if there is a duplicate key, return an error.
Update the values of self
with the other config, if there is a key not
+found in self
, skip it.
It returns two vectors of ConfigItem
, the first contains the keys that
+are included in self
but not in other
, the second contains the keys
+that are included in other
but not in self
.
pub struct ConfigItem { /* private fields */ }
A structure representing a config item.
+It contains the config key, value and comments.
+Returns the unique name of the config item.
+If the item is contained in the global table, it returns the iten key.
+Otherwise, it returns a string with the format table.key
.
Returns the table name of the config item.
+Returns the value of the config item.
+Returns the mutable reference to the value of the config item.
+source
. Read morepub struct ConfigValue { /* private fields */ }
A structure representing a config value.
+Parses a TOML-formatted string into a ConfigValue
.
Parses a TOML-formatted string into a ConfigValue
with a specified type.
Returns the type of the config value if it is specified on construction.
+Updates the config value with a new value.
+Returns the inferred type of the config value.
+Returns whether the type of the config value matches the specified type.
+Returns the TOML-formatted string of the config value.
+Returns the Rust code of the config value.
+The indent
parameter specifies the number of spaces to indent the code.
source
. Read moreRedirecting to ../../axconfig_gen/enum.ConfigType.html...
+ + + \ No newline at end of file diff --git a/axconfig_gen/type.ConfigResult.html b/axconfig_gen/type.ConfigResult.html new file mode 100644 index 0000000..3410e59 --- /dev/null +++ b/axconfig_gen/type.ConfigResult.html @@ -0,0 +1,7 @@ +Redirecting to ../../axconfig_gen/struct.ConfigValue.html...
+ + + \ No newline at end of file diff --git a/axconfig_gen_macros/all.html b/axconfig_gen_macros/all.html new file mode 100644 index 0000000..baae02a --- /dev/null +++ b/axconfig_gen_macros/all.html @@ -0,0 +1 @@ +Procedural macros for converting TOML format configurations to equivalent Rust constant definitions.
+axconfig_gen_macros::parse_configs!(r#"
+are-you-ok = true
+one-two-three = 123
+
+[hello]
+"one-two-three" = "456" # int
+array = [1, 2, 3] # [uint]
+tuple = [1, "abc", 3]
+"#);
+
+assert_eq!(ARE_YOU_OK, true);
+assert_eq!(ONE_TWO_THREE, 123usize);
+assert_eq!(hello::ONE_TWO_THREE, 456isize);
+assert_eq!(hello::ARRAY, [1, 2, 3]);
+assert_eq!(hello::TUPLE, (1, "abc", 3));
Value types are necessary for generating Rust constant definitions. Types can be specified by the comment following the config item. Currently supported types are bool
, int
, uint
, str
, (type1, type2, ...)
for tuples, and [type]
for arrays. If no type is specified, it will try to infer the type from the value.
The above example will generate the following constants:
+ +pub const ARE_YOU_OK: bool = true;
+pub const ONE_TWO_THREE: usize = 123;
+
+pub mod hello {
+ pub const ARRAY: &[usize] = &[1, 2, 3];
+ pub const ONE_TWO_THREE: isize = 456;
+ pub const TUPLE: (usize, &str, usize) = (1, "abc", 3);
+}
You can also include the configuration file directly:
+ +axconfig_gen_macros::include_configs!("/path/to/config.toml");
Redirecting to macro.include_configs.html...
+ + + \ No newline at end of file diff --git a/axconfig_gen_macros/macro.include_configs.html b/axconfig_gen_macros/macro.include_configs.html new file mode 100644 index 0000000..4a2fd89 --- /dev/null +++ b/axconfig_gen_macros/macro.include_configs.html @@ -0,0 +1,6 @@ +include_configs!() { /* proc-macro */ }
Includes a TOML format config file and expands it into Rust code.
+The given path should be an absolute path or a path relative to your
+project’s Cargo.toml
.
See the crate-level documentation.
+Redirecting to macro.parse_configs.html...
+ + + \ No newline at end of file diff --git a/axconfig_gen_macros/macro.parse_configs.html b/axconfig_gen_macros/macro.parse_configs.html new file mode 100644 index 0000000..217a4a3 --- /dev/null +++ b/axconfig_gen_macros/macro.parse_configs.html @@ -0,0 +1,4 @@ +parse_configs!() { /* proc-macro */ }
Parses TOML config content and expands it into Rust code.
+See the crate-level documentation.
+[int]
).\nBoolean type (bool
).\nA structure storing all config items.\nThe error type on config parsing.\nA structure representing a config item.\nA specialized Result
type with ConfigErr
as the error type.\nThe supported types in the config file.\nA structure representing a config value.\nContains the error value\nThe name of the global table of the config.\nSigned integer type (int
).\nInvalid config type.\nInvalid config value.\nContains the success value\nOther error.\nThe format of the generated file.\nTOML parsing error.\nOutput is Rust code.\nString type (str
).\nOutput is in TOML format.\nTuple type (e.g., (int, str)
).\nUnsigned integer type (uint
).\nType is unknown.\nConfig value and type mismatch.\nReturns the comments of the config item.\nReturns the reference to the config item with the …\nReturns the mutable reference to the config item with the …\nDump the config into a string with the specified format.\nDump the config into Rust code.\nDump the config into TOML format.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nParse a toml string into a config object.\nReturns the global table of the config.\nReturns the inferred type of the config value.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nReturns the unique name of the config item.\nReturns the iterator of all config items.\nReturns the key of the config item.\nMerge the other config into self
, if there is a duplicate …\nCreate a new empty config object.\nParses a type string into a ConfigType
.\nParses a TOML-formatted string into a ConfigValue
.\nParses a TOML-formatted string into a ConfigValue
with a …\nReturns the reference to the table with the specified name.\nReturns the mutable reference to the table with the …\nReturns the comments of the table with the specified name.\nReturns the iterator of all tables.\nReturns the table name of the config item.\nConverts the type into a Rust type string.\nReturns the Rust code of the config value.\nReturns the TOML-formatted string of the config value.\nReturns the type of the config value if it is specified on …\nReturns whether the type of the config value matches the …\nUpdate the values of self
with the other config, if there …\nUpdates the config value with a new value.\nReturns the value of the config item.\nReturns the mutable reference to the value of the config …")
\ No newline at end of file
diff --git a/search.desc/axconfig_gen_macros/axconfig_gen_macros-desc-0-.js b/search.desc/axconfig_gen_macros/axconfig_gen_macros-desc-0-.js
new file mode 100644
index 0000000..b46f953
--- /dev/null
+++ b/search.desc/axconfig_gen_macros/axconfig_gen_macros-desc-0-.js
@@ -0,0 +1 @@
+searchState.loadedDescShard("axconfig_gen_macros", 0, "axconfig-gen-macros\nIncludes a TOML format config file and expands it into …\nParses TOML config content and expands it into Rust code.")
\ No newline at end of file
diff --git a/settings.html b/settings.html
new file mode 100644
index 0000000..97db2bc
--- /dev/null
+++ b/settings.html
@@ -0,0 +1 @@
++1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325
use std::collections::{BTreeMap, BTreeSet};
+use toml_edit::{Decor, DocumentMut, Item, Table, Value};
+
+use crate::output::{Output, OutputFormat};
+use crate::{ConfigErr, ConfigResult, ConfigType, ConfigValue};
+
+type ConfigTable = BTreeMap<String, ConfigItem>;
+
+/// A structure representing a config item.
+///
+/// It contains the config key, value and comments.
+#[derive(Debug, Clone)]
+pub struct ConfigItem {
+ table_name: String,
+ key: String,
+ value: ConfigValue,
+ comments: String,
+}
+
+impl ConfigItem {
+ fn new(table_name: &str, table: &Table, key: &str, value: &Value) -> ConfigResult<Self> {
+ let inner = || {
+ let item = table.key(key).unwrap();
+ let comments = prefix_comments(item.leaf_decor())
+ .unwrap_or_default()
+ .to_string();
+ let suffix = suffix_comments(value.decor()).unwrap_or_default().trim();
+ let value = if !suffix.is_empty() {
+ let ty_str = suffix.trim_start_matches('#');
+ let ty = ConfigType::new(ty_str)?;
+ ConfigValue::from_raw_value_type(value, ty)?
+ } else {
+ ConfigValue::from_raw_value(value)?
+ };
+ Ok(Self {
+ table_name: table_name.into(),
+ key: key.into(),
+ value,
+ comments,
+ })
+ };
+ let res = inner();
+ if let Err(e) = &res {
+ eprintln!("Parsing error at key `{}`: {:?}", key, e);
+ }
+ res
+ }
+
+ fn new_global(table: &Table, key: &str, value: &Value) -> ConfigResult<Self> {
+ Self::new(Config::GLOBAL_TABLE_NAME, table, key, value)
+ }
+
+ /// Returns the unique name of the config item.
+ ///
+ /// If the item is contained in the global table, it returns the iten key.
+ /// Otherwise, it returns a string with the format `table.key`.
+ pub fn item_name(&self) -> String {
+ if self.table_name == Config::GLOBAL_TABLE_NAME {
+ self.key.clone()
+ } else {
+ format!("{}.{}", self.table_name, self.key)
+ }
+ }
+
+ /// Returns the table name of the config item.
+ pub fn table_name(&self) -> &str {
+ &self.table_name
+ }
+
+ /// Returns the key of the config item.
+ pub fn key(&self) -> &str {
+ &self.key
+ }
+
+ /// Returns the value of the config item.
+ pub fn value(&self) -> &ConfigValue {
+ &self.value
+ }
+
+ /// Returns the comments of the config item.
+ pub fn comments(&self) -> &str {
+ &self.comments
+ }
+
+ /// Returns the mutable reference to the value of the config item.
+ pub fn value_mut(&mut self) -> &mut ConfigValue {
+ &mut self.value
+ }
+}
+
+/// A structure storing all config items.
+///
+/// It contains a global table and multiple named tables, each table is a map
+/// from key to value, the key is a string and the value is a [`ConfigItem`].
+#[derive(Default, Debug)]
+pub struct Config {
+ global: ConfigTable,
+ tables: BTreeMap<String, ConfigTable>,
+ table_comments: BTreeMap<String, String>,
+}
+
+impl Config {
+ /// The name of the global table of the config.
+ pub const GLOBAL_TABLE_NAME: &'static str = "$GLOBAL";
+
+ /// Create a new empty config object.
+ pub fn new() -> Self {
+ Self {
+ global: ConfigTable::new(),
+ tables: BTreeMap::new(),
+ table_comments: BTreeMap::new(),
+ }
+ }
+
+ fn new_table(&mut self, name: &str, comments: &str) -> ConfigResult<&mut ConfigTable> {
+ if name == Self::GLOBAL_TABLE_NAME {
+ return Err(ConfigErr::Other(format!(
+ "Table name `{}` is reserved",
+ Self::GLOBAL_TABLE_NAME
+ )));
+ }
+ if self.tables.contains_key(name) {
+ return Err(ConfigErr::Other(format!("Duplicate table name `{}`", name)));
+ }
+ self.tables.insert(name.into(), ConfigTable::new());
+ self.table_comments.insert(name.into(), comments.into());
+ Ok(self.tables.get_mut(name).unwrap())
+ }
+
+ /// Returns the global table of the config.
+ pub fn global_table(&self) -> &BTreeMap<String, ConfigItem> {
+ &self.global
+ }
+
+ /// Returns the reference to the table with the specified name.
+ pub fn table_at(&self, name: &str) -> Option<&BTreeMap<String, ConfigItem>> {
+ if name == Self::GLOBAL_TABLE_NAME {
+ Some(&self.global)
+ } else {
+ self.tables.get(name)
+ }
+ }
+
+ /// Returns the mutable reference to the table with the specified name.
+ pub fn table_at_mut(&mut self, name: &str) -> Option<&mut BTreeMap<String, ConfigItem>> {
+ if name == Self::GLOBAL_TABLE_NAME {
+ Some(&mut self.global)
+ } else {
+ self.tables.get_mut(name)
+ }
+ }
+
+ /// Returns the reference to the config item with the specified table name and key.
+ pub fn config_at(&self, table: &str, key: &str) -> Option<&ConfigItem> {
+ self.table_at(table).and_then(|t| t.get(key))
+ }
+
+ /// Returns the mutable reference to the config item with the specified
+ /// table name and key.
+ pub fn config_at_mut(&mut self, table: &str, key: &str) -> Option<&mut ConfigItem> {
+ self.table_at_mut(table).and_then(|t| t.get_mut(key))
+ }
+
+ /// Returns the comments of the table with the specified name.
+ pub fn table_comments_at(&self, name: &str) -> Option<&str> {
+ self.table_comments.get(name).map(|s| s.as_str())
+ }
+
+ /// Returns the iterator of all tables.
+ ///
+ /// The iterator returns a tuple of table name, table and comments. The
+ /// global table is named `$GLOBAL`.
+ pub fn table_iter(&self) -> impl Iterator<Item = (&str, &ConfigTable, &str)> {
+ let global_iter = [(Self::GLOBAL_TABLE_NAME, &self.global, "")].into_iter();
+ let other_iter = self.tables.iter().map(|(name, configs)| {
+ (
+ name.as_str(),
+ configs,
+ self.table_comments.get(name).unwrap().as_str(),
+ )
+ });
+ global_iter.chain(other_iter)
+ }
+
+ /// Returns the iterator of all config items.
+ ///
+ /// The iterator returns a tuple of table name, key and config item. The
+ /// global table is named `$GLOBAL`.
+ pub fn iter(&self) -> impl Iterator<Item = &ConfigItem> {
+ self.table_iter().flat_map(|(_, c, _)| c.values())
+ }
+}
+
+impl Config {
+ /// Parse a toml string into a config object.
+ pub fn from_toml(toml: &str) -> ConfigResult<Self> {
+ let doc = toml.parse::<DocumentMut>()?;
+ let table = doc.as_table();
+
+ let mut result = Self::new();
+ for (key, item) in table.iter() {
+ match item {
+ Item::Value(val) => {
+ result
+ .global
+ .insert(key.into(), ConfigItem::new_global(table, key, val)?);
+ }
+ Item::Table(table) => {
+ let table_name = key;
+ let comments = prefix_comments(table.decor());
+ let configs = result.new_table(key, comments.unwrap_or_default())?;
+ for (key, item) in table.iter() {
+ if let Item::Value(val) = item {
+ configs
+ .insert(key.into(), ConfigItem::new(table_name, table, key, val)?);
+ } else {
+ return Err(ConfigErr::InvalidValue);
+ }
+ }
+ }
+ Item::None => {}
+ _ => {
+ return Err(ConfigErr::Other(format!(
+ "Object array `[[{}]]` is not supported",
+ key
+ )))
+ }
+ }
+ }
+ Ok(result)
+ }
+
+ /// Dump the config into a string with the specified format.
+ pub fn dump(&self, fmt: OutputFormat) -> ConfigResult<String> {
+ let mut output = Output::new(fmt);
+ for (name, table, comments) in self.table_iter() {
+ if name != Self::GLOBAL_TABLE_NAME {
+ output.table_begin(name, comments);
+ }
+ for (key, item) in table.iter() {
+ if let Err(e) = output.write_item(item) {
+ eprintln!("Dump config `{}` failed: {:?}", key, e);
+ }
+ }
+ if name != Self::GLOBAL_TABLE_NAME {
+ output.table_end();
+ }
+ }
+ Ok(output.result().into())
+ }
+
+ /// Dump the config into TOML format.
+ pub fn dump_toml(&self) -> ConfigResult<String> {
+ self.dump(OutputFormat::Toml)
+ }
+
+ /// Dump the config into Rust code.
+ pub fn dump_rs(&self) -> ConfigResult<String> {
+ self.dump(OutputFormat::Rust)
+ }
+
+ /// Merge the other config into `self`, if there is a duplicate key, return an error.
+ pub fn merge(&mut self, other: &Self) -> ConfigResult<()> {
+ for (name, other_table, table_comments) in other.table_iter() {
+ let self_table = if let Some(table) = self.table_at_mut(name) {
+ table
+ } else {
+ self.new_table(name, table_comments)?
+ };
+ for (key, item) in other_table.iter() {
+ if self_table.contains_key(key) {
+ return Err(ConfigErr::Other(format!("Duplicate key `{}`", key)));
+ } else {
+ self_table.insert(key.into(), item.clone());
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Update the values of `self` with the other config, if there is a key not
+ /// found in `self`, skip it.
+ ///
+ /// It returns two vectors of `ConfigItem`, the first contains the keys that
+ /// are included in `self` but not in `other`, the second contains the keys
+ /// that are included in `other` but not in `self`.
+ pub fn update(&mut self, other: &Self) -> ConfigResult<(Vec<ConfigItem>, Vec<ConfigItem>)> {
+ let mut touched = BTreeSet::new(); // included in both `self` and `other`
+ let mut extra = Vec::new(); // included in `other` but not in `self`
+
+ for other_item in other.iter() {
+ let table_name = other_item.table_name.clone();
+ let key = other_item.key.clone();
+ let self_table = if let Some(table) = self.table_at_mut(&table_name) {
+ table
+ } else {
+ extra.push(other_item.clone());
+ continue;
+ };
+
+ if let Some(self_item) = self_table.get_mut(&key) {
+ self_item.value.update(other_item.value.clone())?;
+ touched.insert(self_item.item_name());
+ } else {
+ extra.push(other_item.clone());
+ }
+ }
+
+ // included in `self` but not in `other`
+ let untouched = self
+ .iter()
+ .filter(|item| !touched.contains(&item.item_name()))
+ .cloned()
+ .collect::<Vec<_>>();
+ Ok((untouched, extra))
+ }
+}
+
+fn prefix_comments(decor: &Decor) -> Option<&str> {
+ decor.prefix().and_then(|s| s.as_str())
+}
+
+fn suffix_comments(decor: &Decor) -> Option<&str> {
+ decor.suffix().and_then(|s| s.as_str())
+}
+
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57
#![doc = include_str!("../README.md")]
+
+mod config;
+mod output;
+mod ty;
+mod value;
+
+#[cfg(test)]
+mod tests;
+
+use toml_edit::TomlError;
+
+pub use self::config::{Config, ConfigItem};
+pub use self::output::OutputFormat;
+pub use self::ty::ConfigType;
+pub use self::value::ConfigValue;
+
+/// The error type on config parsing.
+pub enum ConfigErr {
+ /// TOML parsing error.
+ Parse(TomlError),
+ /// Invalid config value.
+ InvalidValue,
+ /// Invalid config type.
+ InvalidType,
+ /// Config value and type mismatch.
+ ValueTypeMismatch,
+ /// Other error.
+ Other(String),
+}
+
+impl From<TomlError> for ConfigErr {
+ fn from(e: TomlError) -> Self {
+ Self::Parse(e)
+ }
+}
+
+impl core::fmt::Display for ConfigErr {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Self::Parse(e) => write!(f, "{}", e),
+ Self::InvalidValue => write!(f, "Invalid config value"),
+ Self::InvalidType => write!(f, "Invalid config type"),
+ Self::ValueTypeMismatch => write!(f, "Config value and type mismatch"),
+ Self::Other(s) => write!(f, "{}", s),
+ }
+ }
+}
+
+impl core::fmt::Debug for ConfigErr {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{}", self)
+ }
+}
+
+/// A specialized [`Result`] type with [`ConfigErr`] as the error type.
+pub type ConfigResult<T> = Result<T, ConfigErr>;
+
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130
use crate::{ConfigErr, ConfigItem, ConfigResult, ConfigType};
+
+/// The format of the generated file.
+#[derive(Debug, Clone)]
+pub enum OutputFormat {
+ /// Output is in TOML format.
+ Toml,
+ /// Output is Rust code.
+ Rust,
+}
+
+impl std::fmt::Display for OutputFormat {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let s = match self {
+ Self::Toml => "toml",
+ Self::Rust => "rust",
+ };
+ s.fmt(f)
+ }
+}
+
+impl std::str::FromStr for OutputFormat {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "toml" => Ok(Self::Toml),
+ "rust" => Ok(Self::Rust),
+ _ => Err(s.into()),
+ }
+ }
+}
+
+/// The output writer.
+pub struct Output {
+ fmt: OutputFormat,
+ indent: usize,
+ result: String,
+}
+
+impl Output {
+ pub fn new(fmt: OutputFormat) -> Self {
+ Self {
+ fmt,
+ indent: 0,
+ result: String::new(),
+ }
+ }
+
+ pub fn result(&self) -> &str {
+ &self.result
+ }
+
+ pub fn println_fmt(&mut self, fmt: std::fmt::Arguments) {
+ self.result += &format!("{:indent$}{}\n", "", fmt, indent = self.indent);
+ }
+
+ pub fn println(&mut self, s: &str) {
+ self.println_fmt(format_args!("{}", s));
+ }
+
+ pub fn table_begin(&mut self, name: &str, comments: &str) {
+ match self.fmt {
+ OutputFormat::Toml => {
+ self.println_fmt(format_args!("{}[{}]", comments, name));
+ }
+ OutputFormat::Rust => {
+ for line in comments.lines() {
+ self.println(&line.replacen("#", "///", 1));
+ }
+ self.println_fmt(format_args!("pub mod {} {{", mod_name(name)));
+ self.indent += 4;
+ }
+ }
+ }
+
+ pub fn table_end(&mut self) {
+ if let OutputFormat::Rust = self.fmt {
+ self.indent -= 4;
+ self.println("}");
+ }
+ }
+
+ pub fn write_item(&mut self, item: &ConfigItem) -> ConfigResult<()> {
+ match self.fmt {
+ OutputFormat::Toml => {
+ self.println_fmt(format_args!(
+ "{}{} = {}",
+ item.comments(),
+ item.key(),
+ item.value().to_toml_value()
+ ));
+ }
+ OutputFormat::Rust => {
+ for line in item.comments().lines() {
+ self.println(&line.replacen("#", "///", 1));
+ }
+ let key = const_name(item.key());
+ let val = item.value();
+ let ty = if let Some(ty) = val.ty() {
+ ty.clone()
+ } else {
+ val.inferred_type()?
+ };
+
+ if matches!(ty, ConfigType::Unknown) {
+ return Err(ConfigErr::Other(format!(
+ "Unknown type for key `{}`",
+ item.key()
+ )));
+ }
+ self.println_fmt(format_args!(
+ "pub const {}: {} = {};",
+ key,
+ ty.to_rust_type(),
+ val.to_rust_value(&ty, self.indent)?,
+ ));
+ }
+ }
+ Ok(())
+ }
+}
+
+fn mod_name(name: &str) -> String {
+ name.replace("-", "_")
+}
+
+fn const_name(name: &str) -> String {
+ name.to_uppercase().replace('-', "_")
+}
+
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111
use crate::{ConfigErr, ConfigResult};
+
+/// The supported types in the config file.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ConfigType {
+ /// Boolean type (`bool`).
+ Bool,
+ /// Signed integer type (`int`).
+ Int,
+ /// Unsigned integer type (`uint`).
+ Uint,
+ /// String type (`str`).
+ String,
+ /// Tuple type (e.g., `(int, str)`).
+ Tuple(Vec<ConfigType>),
+ /// Array type (e.g., `[int]`).
+ Array(Box<ConfigType>),
+ /// Type is unknown.
+ ///
+ /// It is used for type inference.
+ Unknown,
+}
+
+impl ConfigType {
+ /// Parses a type string into a [`ConfigType`].
+ pub fn new(ty: &str) -> ConfigResult<Self> {
+ let ty = ty.trim();
+ #[cfg(test)]
+ if ty == "?" {
+ return Ok(Self::Unknown);
+ }
+ match ty {
+ "bool" => Ok(Self::Bool),
+ "int" => Ok(Self::Int),
+ "uint" => Ok(Self::Uint),
+ "str" => Ok(Self::String),
+ _ => {
+ if ty.starts_with("(") && ty.ends_with(")") {
+ let tuple = ty[1..ty.len() - 1].trim();
+ if tuple.is_empty() {
+ return Ok(Self::Tuple(Vec::new()));
+ }
+ let items = split_tuple_items(tuple).ok_or(ConfigErr::InvalidType)?;
+ let tuple_types = items
+ .into_iter()
+ .map(Self::new)
+ .collect::<ConfigResult<Vec<_>>>()?;
+ Ok(Self::Tuple(tuple_types))
+ } else if ty.starts_with('[') && ty.ends_with("]") {
+ let element = ty[1..ty.len() - 1].trim();
+ if element.is_empty() {
+ return Err(ConfigErr::InvalidType);
+ }
+ Ok(Self::Array(Box::new(Self::new(element)?)))
+ } else {
+ Err(ConfigErr::InvalidType)
+ }
+ }
+ }
+ }
+
+ /// Converts the type into a Rust type string.
+ pub fn to_rust_type(&self) -> String {
+ match self {
+ Self::Bool => "bool".into(),
+ Self::Int => "isize".into(),
+ Self::Uint => "usize".into(),
+ Self::String => "&str".into(),
+ Self::Tuple(items) => {
+ let items = items
+ .iter()
+ .map(Self::to_rust_type)
+ .collect::<Vec<_>>()
+ .join(", ");
+ format!("({})", items)
+ }
+ Self::Array(ty) => format!("&[{}]", ty.to_rust_type()),
+ _ => panic!("Unknown type"),
+ }
+ }
+}
+
+fn split_tuple_items(s: &str) -> Option<Vec<&str>> {
+ let mut items = Vec::new();
+ let mut start = 0;
+ let mut level = 0;
+ for (i, c) in s.char_indices() {
+ match c {
+ '(' => level += 1,
+ ')' => level -= 1,
+ ',' if level == 0 => {
+ if start < i {
+ items.push(&s[start..i]);
+ } else {
+ return None;
+ }
+ start = i + 1;
+ }
+ _ => {}
+ }
+ if level < 0 {
+ return None;
+ }
+ }
+ if level == 0 && start < s.len() {
+ items.push(&s[start..]);
+ Some(items)
+ } else {
+ None
+ }
+}
+
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288
use std::fmt;
+
+use toml_edit::Value;
+
+use crate::{ConfigErr, ConfigResult, ConfigType};
+
+/// A structure representing a config value.
+#[derive(Clone)]
+pub struct ConfigValue {
+ value: Value,
+ ty: Option<ConfigType>,
+}
+
+impl ConfigValue {
+ /// Parses a TOML-formatted string into a [`ConfigValue`].
+ pub fn new(s: &str) -> ConfigResult<Self> {
+ let value = s.parse::<Value>()?;
+ Self::from_raw_value(&value)
+ }
+
+ /// Parses a TOML-formatted string into a [`ConfigValue`] with a specified type.
+ pub fn new_with_type(s: &str, ty: &str) -> ConfigResult<Self> {
+ let value = s.parse::<Value>()?;
+ let ty = ConfigType::new(ty)?;
+ Self::from_raw_value_type(&value, ty)
+ }
+
+ pub(crate) fn from_raw_value(value: &Value) -> ConfigResult<Self> {
+ if !value_is_valid(value) {
+ return Err(ConfigErr::InvalidValue);
+ }
+ Ok(Self {
+ value: value.clone(),
+ ty: None,
+ })
+ }
+
+ pub(crate) fn from_raw_value_type(value: &Value, ty: ConfigType) -> ConfigResult<Self> {
+ if !value_is_valid(value) {
+ return Err(ConfigErr::InvalidValue);
+ }
+ if value_type_matches(value, &ty) {
+ Ok(Self {
+ value: value.clone(),
+ ty: Some(ty),
+ })
+ } else {
+ Err(ConfigErr::ValueTypeMismatch)
+ }
+ }
+
+ /// Returns the type of the config value if it is specified on construction.
+ pub fn ty(&self) -> Option<&ConfigType> {
+ self.ty.as_ref()
+ }
+
+ /// Updates the config value with a new value.
+ pub fn update(&mut self, new_value: Self) -> ConfigResult<()> {
+ match (&self.ty, &new_value.ty) {
+ (Some(ty), Some(new_ty)) => {
+ if ty != new_ty {
+ return Err(ConfigErr::ValueTypeMismatch);
+ }
+ }
+ (Some(ty), None) => {
+ if !value_type_matches(&new_value.value, ty) {
+ return Err(ConfigErr::ValueTypeMismatch);
+ }
+ }
+ (None, Some(new_ty)) => {
+ if !value_type_matches(&self.value, new_ty) {
+ return Err(ConfigErr::ValueTypeMismatch);
+ }
+ self.ty = new_value.ty;
+ }
+ _ => {}
+ }
+ self.value = new_value.value;
+ Ok(())
+ }
+
+ /// Returns the inferred type of the config value.
+ pub fn inferred_type(&self) -> ConfigResult<ConfigType> {
+ inferred_type(&self.value)
+ }
+
+ /// Returns whether the type of the config value matches the specified type.
+ pub fn type_matches(&self, ty: &ConfigType) -> bool {
+ value_type_matches(&self.value, ty)
+ }
+
+ /// Returns the TOML-formatted string of the config value.
+ pub fn to_toml_value(&self) -> String {
+ to_toml(&self.value)
+ }
+
+ /// Returns the Rust code of the config value.
+ ///
+ /// The `indent` parameter specifies the number of spaces to indent the code.
+ pub fn to_rust_value(&self, ty: &ConfigType, indent: usize) -> ConfigResult<String> {
+ to_rust(&self.value, ty, indent)
+ }
+}
+
+impl fmt::Debug for ConfigValue {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ConfigValue")
+ .field("value", &self.to_toml_value())
+ .field("type", &self.ty)
+ .finish()
+ }
+}
+
+fn is_num(s: &str) -> bool {
+ let s = s.to_lowercase().replace('_', "");
+ if let Some(s) = s.strip_prefix("0x") {
+ usize::from_str_radix(s, 16).is_ok()
+ } else if let Some(s) = s.strip_prefix("0b") {
+ usize::from_str_radix(s, 2).is_ok()
+ } else if let Some(s) = s.strip_prefix("0o") {
+ usize::from_str_radix(s, 8).is_ok()
+ } else {
+ s.parse::<usize>().is_ok()
+ }
+}
+
+fn value_is_valid(value: &Value) -> bool {
+ match value {
+ Value::Boolean(_) | Value::Integer(_) | Value::String(_) => true,
+ Value::Array(arr) => {
+ for e in arr {
+ if !value_is_valid(e) {
+ return false;
+ }
+ }
+ true
+ }
+ _ => false,
+ }
+}
+
+fn value_type_matches(value: &Value, ty: &ConfigType) -> bool {
+ match (value, ty) {
+ (Value::Boolean(_), ConfigType::Bool) => true,
+ (Value::Integer(_), ConfigType::Int | ConfigType::Uint) => true,
+ (Value::String(s), _) => {
+ let s = s.value();
+ if is_num(s) {
+ matches!(ty, ConfigType::Int | ConfigType::Uint | ConfigType::String)
+ } else {
+ matches!(ty, ConfigType::String)
+ }
+ }
+ (Value::Array(arr), ConfigType::Tuple(ty)) => {
+ if arr.len() != ty.len() {
+ return false;
+ }
+ for (e, t) in arr.iter().zip(ty.iter()) {
+ if !value_type_matches(e, t) {
+ return false;
+ }
+ }
+ true
+ }
+ (Value::Array(arr), ConfigType::Array(ty)) => {
+ for e in arr {
+ if !value_type_matches(e, ty) {
+ return false;
+ }
+ }
+ true
+ }
+ _ => false,
+ }
+}
+
+fn inferred_type(value: &Value) -> ConfigResult<ConfigType> {
+ match value {
+ Value::Boolean(_) => Ok(ConfigType::Bool),
+ Value::Integer(i) => {
+ let val = *i.value();
+ if val < 0 {
+ Ok(ConfigType::Int)
+ } else {
+ Ok(ConfigType::Uint)
+ }
+ }
+ Value::String(s) => {
+ let s = s.value();
+ if is_num(s) {
+ Ok(ConfigType::Uint)
+ } else {
+ Ok(ConfigType::String)
+ }
+ }
+ Value::Array(arr) => {
+ let types = arr
+ .iter()
+ .map(inferred_type)
+ .collect::<ConfigResult<Vec<_>>>()?;
+ if types.is_empty() {
+ return Ok(ConfigType::Unknown);
+ }
+
+ let mut all_same = true;
+ for t in types.iter() {
+ if matches!(t, ConfigType::Unknown) {
+ return Ok(ConfigType::Unknown);
+ }
+ if t != &types[0] {
+ all_same = false;
+ break;
+ }
+ }
+
+ if all_same {
+ Ok(ConfigType::Array(Box::new(types[0].clone())))
+ } else {
+ Ok(ConfigType::Tuple(types))
+ }
+ }
+ _ => Err(ConfigErr::InvalidValue),
+ }
+}
+
+pub fn to_toml(value: &Value) -> String {
+ match &value {
+ Value::Boolean(b) => b.display_repr().to_string(),
+ Value::Integer(i) => i.display_repr().to_string(),
+ Value::String(s) => s.display_repr().to_string(),
+ Value::Array(arr) => {
+ let elements = arr.iter().map(to_toml).collect::<Vec<_>>();
+ if arr.iter().any(|e| e.is_array()) {
+ format!("[\n {}\n]", elements.join(",\n").replace("\n", "\n "))
+ } else {
+ format!("[{}]", elements.join(", "))
+ }
+ }
+ _ => "".to_string(),
+ }
+}
+
+pub fn to_rust(value: &Value, ty: &ConfigType, indent: usize) -> ConfigResult<String> {
+ match (value, ty) {
+ (Value::Boolean(b), ConfigType::Bool) => Ok(b.display_repr().to_string()),
+ (Value::Integer(i), ConfigType::Int | ConfigType::Uint) => Ok(i.display_repr().to_string()),
+ (Value::String(s), _) => {
+ if matches!(ty, ConfigType::Int | ConfigType::Uint) {
+ Ok(s.value().to_string())
+ } else if matches!(ty, ConfigType::String) {
+ Ok(s.display_repr().to_string())
+ } else {
+ Err(ConfigErr::ValueTypeMismatch)
+ }
+ }
+ (Value::Array(arr), ConfigType::Tuple(ty)) => {
+ if arr.len() != ty.len() {
+ return Err(ConfigErr::ValueTypeMismatch);
+ }
+ let elements = arr
+ .iter()
+ .zip(ty)
+ .map(|(v, t)| to_rust(v, t, indent))
+ .collect::<ConfigResult<Vec<_>>>()?;
+ Ok(format!("({})", elements.join(", ")))
+ }
+ (Value::Array(arr), ConfigType::Array(ty)) => {
+ let elements = arr
+ .iter()
+ .map(|v| to_rust(v, ty, indent + 4))
+ .collect::<ConfigResult<Vec<_>>>()?;
+ let code = if arr.iter().any(|e| e.is_array()) {
+ let spaces = format!("\n{:indent$}", "", indent = indent + 4);
+ let spaces_end = format!(",\n{:indent$}", "", indent = indent);
+ format!(
+ "&[{}{}{}]",
+ spaces,
+ elements.join(&format!(",{}", spaces)),
+ spaces_end
+ )
+ } else {
+ format!("&[{}]", elements.join(", "))
+ };
+ Ok(code)
+ }
+ _ => Err(ConfigErr::ValueTypeMismatch),
+ }
+}
+
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64
#![cfg_attr(feature = "nightly", feature(proc_macro_expand))]
+#![doc = include_str!("../README.md")]
+
+use proc_macro::{LexError, TokenStream};
+use quote::{quote, ToTokens};
+use syn::parse_macro_input;
+use syn::{Error, LitStr};
+
+use axconfig_gen::{Config, OutputFormat};
+
+fn compiler_error<T: ToTokens>(tokens: T, msg: String) -> TokenStream {
+ Error::new_spanned(tokens, msg).to_compile_error().into()
+}
+
+/// Parses TOML config content and expands it into Rust code.
+///
+/// # Example
+///
+/// See the [crate-level documentation][crate].
+#[proc_macro]
+pub fn parse_configs(config_toml: TokenStream) -> TokenStream {
+ #[cfg(feature = "nightly")]
+ let config_toml = match config_toml.expand_expr() {
+ Ok(s) => s,
+ Err(e) => {
+ return Error::new(proc_macro2::Span::call_site(), e.to_string())
+ .to_compile_error()
+ .into()
+ }
+ };
+
+ let config_toml = parse_macro_input!(config_toml as LitStr).value();
+ let code = Config::from_toml(&config_toml).and_then(|cfg| cfg.dump(OutputFormat::Rust));
+ match code {
+ Ok(code) => code
+ .parse()
+ .unwrap_or_else(|e: LexError| compiler_error(config_toml, e.to_string())),
+ Err(e) => compiler_error(config_toml, e.to_string()),
+ }
+}
+
+/// Includes a TOML format config file and expands it into Rust code.
+///
+/// The given path should be an absolute path or a path relative to your
+/// project's `Cargo.toml`.
+///
+/// # Example
+///
+/// See the [crate-level documentation][crate].
+#[proc_macro]
+pub fn include_configs(path: TokenStream) -> TokenStream {
+ let path = parse_macro_input!(path as LitStr);
+ let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into());
+ let cfg_path = std::path::Path::new(&root).join(path.value());
+
+ let Ok(config_toml) = std::fs::read_to_string(&cfg_path) else {
+ return compiler_error(path, format!("Failed to read config file: {:?}", cfg_path));
+ };
+
+ quote! {
+ ::axconfig_gen_macros::parse_configs!(#config_toml);
+ }
+ .into()
+}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`Takes each element in the Iterator
: if it is an Err
, no further\nelements are taken, and the Err
is returned. Should no Err
occur, a\ncontainer with the values of each Result
is returned.
Here is an example which increments every integer in a vector,\nchecking for overflow:
\n\nlet v = vec![1, 2];\nlet res: Result<Vec<u32>, &'static str> = v.iter().map(|x: &u32|\n x.checked_add(1).ok_or(\"Overflow!\")\n).collect();\nassert_eq!(res, Ok(vec![2, 3]));
Here is another example that tries to subtract one from another list\nof integers, this time checking for underflow:
\n\nlet v = vec![1, 2, 0];\nlet res: Result<Vec<u32>, &'static str> = v.iter().map(|x: &u32|\n x.checked_sub(1).ok_or(\"Underflow!\")\n).collect();\nassert_eq!(res, Err(\"Underflow!\"));
Here is a variation on the previous example, showing that no\nfurther elements are taken from iter
after the first Err
.
let v = vec![3, 2, 1, 10];\nlet mut shared = 0;\nlet res: Result<Vec<u32>, &'static str> = v.iter().map(|x: &u32| {\n shared += x;\n x.checked_sub(2).ok_or(\"Underflow!\")\n}).collect();\nassert_eq!(res, Err(\"Underflow!\"));\nassert_eq!(shared, 6);
Since the third element caused an underflow, no further elements were taken,\nso the final value of shared
is 6 (= 3 + 2 + 1
), not 16.
try_trait_v2
)Residual
type. Read moreReturns a consuming iterator over the possibly contained value.
\nThe iterator yields one value if the result is Result::Ok
, otherwise none.
let x: Result<u32, &str> = Ok(5);\nlet v: Vec<u32> = x.into_iter().collect();\nassert_eq!(v, [5]);\n\nlet x: Result<u32, &str> = Err(\"nothing!\");\nlet v: Vec<u32> = x.into_iter().collect();\nassert_eq!(v, []);
Takes each element in the Iterator
: if it is an Err
, no further\nelements are taken, and the Err
is returned. Should no Err
\noccur, the product of all elements is returned.
This multiplies each number in a vector of strings,\nif a string could not be parsed the operation returns Err
:
let nums = vec![\"5\", \"10\", \"1\", \"2\"];\nlet total: Result<usize, _> = nums.iter().map(|w| w.parse::<usize>()).product();\nassert_eq!(total, Ok(100));\nlet nums = vec![\"5\", \"10\", \"one\", \"2\"];\nlet total: Result<usize, _> = nums.iter().map(|w| w.parse::<usize>()).product();\nassert!(total.is_err());
Maps a Result<&T, E>
to a Result<T, E>
by copying the contents of the\nOk
part.
let val = 12;\nlet x: Result<&i32, i32> = Ok(&val);\nassert_eq!(x, Ok(&12));\nlet copied = x.copied();\nassert_eq!(copied, Ok(12));
Maps a Result<&mut T, E>
to a Result<T, E>
by copying the contents of the\nOk
part.
let mut val = 12;\nlet x: Result<&mut i32, i32> = Ok(&mut val);\nassert_eq!(x, Ok(&mut 12));\nlet copied = x.copied();\nassert_eq!(copied, Ok(12));
Maps a Result<&mut T, E>
to a Result<T, E>
by cloning the contents of the\nOk
part.
let mut val = 12;\nlet x: Result<&mut i32, i32> = Ok(&mut val);\nassert_eq!(x, Ok(&mut 12));\nlet cloned = x.cloned();\nassert_eq!(cloned, Ok(12));
Transposes a Result
of an Option
into an Option
of a Result
.
Ok(None)
will be mapped to None
.\nOk(Some(_))
and Err(_)
will be mapped to Some(Ok(_))
and Some(Err(_))
.
#[derive(Debug, Eq, PartialEq)]\nstruct SomeErr;\n\nlet x: Result<Option<i32>, SomeErr> = Ok(Some(5));\nlet y: Option<Result<i32, SomeErr>> = Some(Ok(5));\nassert_eq!(x.transpose(), y);
result_flattening
)Converts from Result<Result<T, E>, E>
to Result<T, E>
#![feature(result_flattening)]\nlet x: Result<Result<&'static str, u32>, u32> = Ok(Ok(\"hello\"));\nassert_eq!(Ok(\"hello\"), x.flatten());\n\nlet x: Result<Result<&'static str, u32>, u32> = Ok(Err(6));\nassert_eq!(Err(6), x.flatten());\n\nlet x: Result<Result<&'static str, u32>, u32> = Err(6);\nassert_eq!(Err(6), x.flatten());
Flattening only removes one level of nesting at a time:
\n\n#![feature(result_flattening)]\nlet x: Result<Result<Result<&'static str, u32>, u32>, u32> = Ok(Ok(Ok(\"hello\")));\nassert_eq!(Ok(Ok(\"hello\")), x.flatten());\nassert_eq!(Ok(\"hello\"), x.flatten().flatten());
Returns true
if the result is Ok
and the value inside of it matches a predicate.
let x: Result<u32, &str> = Ok(2);\nassert_eq!(x.is_ok_and(|x| x > 1), true);\n\nlet x: Result<u32, &str> = Ok(0);\nassert_eq!(x.is_ok_and(|x| x > 1), false);\n\nlet x: Result<u32, &str> = Err(\"hey\");\nassert_eq!(x.is_ok_and(|x| x > 1), false);
Returns true
if the result is Err
and the value inside of it matches a predicate.
use std::io::{Error, ErrorKind};\n\nlet x: Result<u32, Error> = Err(Error::new(ErrorKind::NotFound, \"!\"));\nassert_eq!(x.is_err_and(|x| x.kind() == ErrorKind::NotFound), true);\n\nlet x: Result<u32, Error> = Err(Error::new(ErrorKind::PermissionDenied, \"!\"));\nassert_eq!(x.is_err_and(|x| x.kind() == ErrorKind::NotFound), false);\n\nlet x: Result<u32, Error> = Ok(123);\nassert_eq!(x.is_err_and(|x| x.kind() == ErrorKind::NotFound), false);
Converts from Result<T, E>
to Option<E>
.
Converts self
into an Option<E>
, consuming self
,\nand discarding the success value, if any.
let x: Result<u32, &str> = Ok(2);\nassert_eq!(x.err(), None);\n\nlet x: Result<u32, &str> = Err(\"Nothing here\");\nassert_eq!(x.err(), Some(\"Nothing here\"));
Converts from &Result<T, E>
to Result<&T, &E>
.
Produces a new Result
, containing a reference\ninto the original, leaving the original in place.
let x: Result<u32, &str> = Ok(2);\nassert_eq!(x.as_ref(), Ok(&2));\n\nlet x: Result<u32, &str> = Err(\"Error\");\nassert_eq!(x.as_ref(), Err(&\"Error\"));
Converts from &mut Result<T, E>
to Result<&mut T, &mut E>
.
fn mutate(r: &mut Result<i32, i32>) {\n match r.as_mut() {\n Ok(v) => *v = 42,\n Err(e) => *e = 0,\n }\n}\n\nlet mut x: Result<i32, i32> = Ok(2);\nmutate(&mut x);\nassert_eq!(x.unwrap(), 42);\n\nlet mut x: Result<i32, i32> = Err(13);\nmutate(&mut x);\nassert_eq!(x.unwrap_err(), 0);
Maps a Result<T, E>
to Result<U, E>
by applying a function to a\ncontained Ok
value, leaving an Err
value untouched.
This function can be used to compose the results of two functions.
\nPrint the numbers on each line of a string multiplied by two.
\n\nlet line = \"1\\n2\\n3\\n4\\n\";\n\nfor num in line.lines() {\n match num.parse::<i32>().map(|i| i * 2) {\n Ok(n) => println!(\"{n}\"),\n Err(..) => {}\n }\n}
Returns the provided default (if Err
), or\napplies a function to the contained value (if Ok
).
Arguments passed to map_or
are eagerly evaluated; if you are passing\nthe result of a function call, it is recommended to use map_or_else
,\nwhich is lazily evaluated.
let x: Result<_, &str> = Ok(\"foo\");\nassert_eq!(x.map_or(42, |v| v.len()), 3);\n\nlet x: Result<&str, _> = Err(\"bar\");\nassert_eq!(x.map_or(42, |v| v.len()), 42);
Maps a Result<T, E>
to U
by applying fallback function default
to\na contained Err
value, or function f
to a contained Ok
value.
This function can be used to unpack a successful result\nwhile handling an error.
\nlet k = 21;\n\nlet x : Result<_, &str> = Ok(\"foo\");\nassert_eq!(x.map_or_else(|e| k * 2, |v| v.len()), 3);\n\nlet x : Result<&str, _> = Err(\"bar\");\nassert_eq!(x.map_or_else(|e| k * 2, |v| v.len()), 42);
Maps a Result<T, E>
to Result<T, F>
by applying a function to a\ncontained Err
value, leaving an Ok
value untouched.
This function can be used to pass through a successful result while handling\nan error.
\nfn stringify(x: u32) -> String { format!(\"error code: {x}\") }\n\nlet x: Result<u32, u32> = Ok(2);\nassert_eq!(x.map_err(stringify), Ok(2));\n\nlet x: Result<u32, u32> = Err(13);\nassert_eq!(x.map_err(stringify), Err(\"error code: 13\".to_string()));
Converts from Result<T, E>
(or &Result<T, E>
) to Result<&<T as Deref>::Target, &E>
.
Coerces the Ok
variant of the original Result
via Deref
\nand returns the new Result
.
let x: Result<String, u32> = Ok(\"hello\".to_string());\nlet y: Result<&str, &u32> = Ok(\"hello\");\nassert_eq!(x.as_deref(), y);\n\nlet x: Result<String, u32> = Err(42);\nlet y: Result<&str, &u32> = Err(&42);\nassert_eq!(x.as_deref(), y);
Converts from Result<T, E>
(or &mut Result<T, E>
) to Result<&mut <T as DerefMut>::Target, &mut E>
.
Coerces the Ok
variant of the original Result
via DerefMut
\nand returns the new Result
.
let mut s = \"HELLO\".to_string();\nlet mut x: Result<String, u32> = Ok(\"hello\".to_string());\nlet y: Result<&mut str, &mut u32> = Ok(&mut s);\nassert_eq!(x.as_deref_mut().map(|x| { x.make_ascii_uppercase(); x }), y);\n\nlet mut i = 42;\nlet mut x: Result<String, u32> = Err(42);\nlet y: Result<&mut str, &mut u32> = Err(&mut i);\nassert_eq!(x.as_deref_mut().map(|x| { x.make_ascii_uppercase(); x }), y);
Returns an iterator over the possibly contained value.
\nThe iterator yields one value if the result is Result::Ok
, otherwise none.
let x: Result<u32, &str> = Ok(7);\nassert_eq!(x.iter().next(), Some(&7));\n\nlet x: Result<u32, &str> = Err(\"nothing!\");\nassert_eq!(x.iter().next(), None);
Returns a mutable iterator over the possibly contained value.
\nThe iterator yields one value if the result is Result::Ok
, otherwise none.
let mut x: Result<u32, &str> = Ok(7);\nmatch x.iter_mut().next() {\n Some(v) => *v = 40,\n None => {},\n}\nassert_eq!(x, Ok(40));\n\nlet mut x: Result<u32, &str> = Err(\"nothing!\");\nassert_eq!(x.iter_mut().next(), None);
Returns the contained Ok
value, consuming the self
value.
Because this function may panic, its use is generally discouraged.\nInstead, prefer to use pattern matching and handle the Err
\ncase explicitly, or call unwrap_or
, unwrap_or_else
, or\nunwrap_or_default
.
Panics if the value is an Err
, with a panic message including the\npassed message, and the content of the Err
.
let x: Result<u32, &str> = Err(\"emergency failure\");\nx.expect(\"Testing expect\"); // panics with `Testing expect: emergency failure`
We recommend that expect
messages are used to describe the reason you\nexpect the Result
should be Ok
.
let path = std::env::var(\"IMPORTANT_PATH\")\n .expect(\"env variable `IMPORTANT_PATH` should be set by `wrapper_script.sh`\");
Hint: If you’re having trouble remembering how to phrase expect\nerror messages remember to focus on the word “should” as in “env\nvariable should be set by blah” or “the given binary should be available\nand executable by the current user”.
\nFor more detail on expect message styles and the reasoning behind our recommendation please\nrefer to the section on “Common Message\nStyles” in the\nstd::error
module docs.
Returns the contained Ok
value, consuming the self
value.
Because this function may panic, its use is generally discouraged.\nPanics are meant for unrecoverable errors, and\nmay abort the entire program.
\nInstead, prefer to use the ?
(try) operator, or pattern matching\nto handle the Err
case explicitly, or call unwrap_or
,\nunwrap_or_else
, or unwrap_or_default
.
Panics if the value is an Err
, with a panic message provided by the\nErr
’s value.
Basic usage:
\n\nlet x: Result<u32, &str> = Ok(2);\nassert_eq!(x.unwrap(), 2);
let x: Result<u32, &str> = Err(\"emergency failure\");\nx.unwrap(); // panics with `emergency failure`
Returns the contained Ok
value or a default
Consumes the self
argument then, if Ok
, returns the contained\nvalue, otherwise if Err
, returns the default value for that\ntype.
Converts a string to an integer, turning poorly-formed strings\ninto 0 (the default value for integers). parse
converts\na string to any other type that implements FromStr
, returning an\nErr
on error.
let good_year_from_input = \"1909\";\nlet bad_year_from_input = \"190blarg\";\nlet good_year = good_year_from_input.parse().unwrap_or_default();\nlet bad_year = bad_year_from_input.parse().unwrap_or_default();\n\nassert_eq!(1909, good_year);\nassert_eq!(0, bad_year);
Returns the contained Err
value, consuming the self
value.
Panics if the value is an Ok
, with a panic message including the\npassed message, and the content of the Ok
.
let x: Result<u32, &str> = Ok(10);\nx.expect_err(\"Testing expect_err\"); // panics with `Testing expect_err: 10`
Returns the contained Err
value, consuming the self
value.
Panics if the value is an Ok
, with a custom panic message provided\nby the Ok
’s value.
let x: Result<u32, &str> = Ok(2);\nx.unwrap_err(); // panics with `2`
let x: Result<u32, &str> = Err(\"emergency failure\");\nassert_eq!(x.unwrap_err(), \"emergency failure\");
unwrap_infallible
)Returns the contained Ok
value, but never panics.
Unlike unwrap
, this method is known to never panic on the\nresult types it is implemented for. Therefore, it can be used\ninstead of unwrap
as a maintainability safeguard that will fail\nto compile if the error type of the Result
is later changed\nto an error that can actually occur.
\nfn only_good_news() -> Result<String, !> {\n Ok(\"this is fine\".into())\n}\n\nlet s: String = only_good_news().into_ok();\nprintln!(\"{s}\");
unwrap_infallible
)Returns the contained Err
value, but never panics.
Unlike unwrap_err
, this method is known to never panic on the\nresult types it is implemented for. Therefore, it can be used\ninstead of unwrap_err
as a maintainability safeguard that will fail\nto compile if the ok type of the Result
is later changed\nto a type that can actually occur.
\nfn only_bad_news() -> Result<!, String> {\n Err(\"Oops, it failed\".into())\n}\n\nlet error: String = only_bad_news().into_err();\nprintln!(\"{error}\");
Returns res
if the result is Ok
, otherwise returns the Err
value of self
.
Arguments passed to and
are eagerly evaluated; if you are passing the\nresult of a function call, it is recommended to use and_then
, which is\nlazily evaluated.
let x: Result<u32, &str> = Ok(2);\nlet y: Result<&str, &str> = Err(\"late error\");\nassert_eq!(x.and(y), Err(\"late error\"));\n\nlet x: Result<u32, &str> = Err(\"early error\");\nlet y: Result<&str, &str> = Ok(\"foo\");\nassert_eq!(x.and(y), Err(\"early error\"));\n\nlet x: Result<u32, &str> = Err(\"not a 2\");\nlet y: Result<&str, &str> = Err(\"late error\");\nassert_eq!(x.and(y), Err(\"not a 2\"));\n\nlet x: Result<u32, &str> = Ok(2);\nlet y: Result<&str, &str> = Ok(\"different result type\");\nassert_eq!(x.and(y), Ok(\"different result type\"));
Calls op
if the result is Ok
, otherwise returns the Err
value of self
.
This function can be used for control flow based on Result
values.
fn sq_then_to_string(x: u32) -> Result<String, &'static str> {\n x.checked_mul(x).map(|sq| sq.to_string()).ok_or(\"overflowed\")\n}\n\nassert_eq!(Ok(2).and_then(sq_then_to_string), Ok(4.to_string()));\nassert_eq!(Ok(1_000_000).and_then(sq_then_to_string), Err(\"overflowed\"));\nassert_eq!(Err(\"not a number\").and_then(sq_then_to_string), Err(\"not a number\"));
Often used to chain fallible operations that may return Err
.
use std::{io::ErrorKind, path::Path};\n\n// Note: on Windows \"/\" maps to \"C:\\\"\nlet root_modified_time = Path::new(\"/\").metadata().and_then(|md| md.modified());\nassert!(root_modified_time.is_ok());\n\nlet should_fail = Path::new(\"/bad/path\").metadata().and_then(|md| md.modified());\nassert!(should_fail.is_err());\nassert_eq!(should_fail.unwrap_err().kind(), ErrorKind::NotFound);
Returns res
if the result is Err
, otherwise returns the Ok
value of self
.
Arguments passed to or
are eagerly evaluated; if you are passing the\nresult of a function call, it is recommended to use or_else
, which is\nlazily evaluated.
let x: Result<u32, &str> = Ok(2);\nlet y: Result<u32, &str> = Err(\"late error\");\nassert_eq!(x.or(y), Ok(2));\n\nlet x: Result<u32, &str> = Err(\"early error\");\nlet y: Result<u32, &str> = Ok(2);\nassert_eq!(x.or(y), Ok(2));\n\nlet x: Result<u32, &str> = Err(\"not a 2\");\nlet y: Result<u32, &str> = Err(\"late error\");\nassert_eq!(x.or(y), Err(\"late error\"));\n\nlet x: Result<u32, &str> = Ok(2);\nlet y: Result<u32, &str> = Ok(100);\nassert_eq!(x.or(y), Ok(2));
Calls op
if the result is Err
, otherwise returns the Ok
value of self
.
This function can be used for control flow based on result values.
\nfn sq(x: u32) -> Result<u32, u32> { Ok(x * x) }\nfn err(x: u32) -> Result<u32, u32> { Err(x) }\n\nassert_eq!(Ok(2).or_else(sq).or_else(sq), Ok(2));\nassert_eq!(Ok(2).or_else(err).or_else(sq), Ok(2));\nassert_eq!(Err(3).or_else(sq).or_else(err), Ok(9));\nassert_eq!(Err(3).or_else(err).or_else(err), Err(3));
Returns the contained Ok
value or a provided default.
Arguments passed to unwrap_or
are eagerly evaluated; if you are passing\nthe result of a function call, it is recommended to use unwrap_or_else
,\nwhich is lazily evaluated.
let default = 2;\nlet x: Result<u32, &str> = Ok(9);\nassert_eq!(x.unwrap_or(default), 9);\n\nlet x: Result<u32, &str> = Err(\"error\");\nassert_eq!(x.unwrap_or(default), default);
Returns the contained Ok
value, consuming the self
value,\nwithout checking that the value is not an Err
.
Calling this method on an Err
is undefined behavior.
let x: Result<u32, &str> = Ok(2);\nassert_eq!(unsafe { x.unwrap_unchecked() }, 2);
let x: Result<u32, &str> = Err(\"emergency failure\");\nunsafe { x.unwrap_unchecked(); } // Undefined behavior!
Returns the contained Err
value, consuming the self
value,\nwithout checking that the value is not an Ok
.
Calling this method on an Ok
is undefined behavior.
let x: Result<u32, &str> = Ok(2);\nunsafe { x.unwrap_err_unchecked() }; // Undefined behavior!
let x: Result<u32, &str> = Err(\"emergency failure\");\nassert_eq!(unsafe { x.unwrap_err_unchecked() }, \"emergency failure\");
Takes each element in the Iterator
: if it is an Err
, no further\nelements are taken, and the Err
is returned. Should no Err
\noccur, the sum of all elements is returned.
This sums up every integer in a vector, rejecting the sum if a negative\nelement is encountered:
\n\nlet f = |&x: &i32| if x < 0 { Err(\"Negative element found\") } else { Ok(x) };\nlet v = vec![1, 2];\nlet res: Result<i32, _> = v.iter().map(f).sum();\nassert_eq!(res, Ok(3));\nlet v = vec![1, -2];\nlet res: Result<i32, _> = v.iter().map(f).sum();\nassert_eq!(res, Err(\"Negative element found\"));
try_trait_v2
)?
when not short-circuiting.try_trait_v2
)FromResidual::from_residual
\nas part of ?
when short-circuiting. Read moretry_trait_v2
)Output
type. Read moretry_trait_v2
)?
to decide whether the operator should produce a value\n(because this returned ControlFlow::Continue
)\nor propagate a value back to the caller\n(because this returned ControlFlow::Break
). Read more