map_to_const/lib.rs
1#![doc(html_root_url = "https://docs.rs/map-to-const/0.2")]
2#![warn(rust_2018_idioms, missing_docs)]
3#![deny(warnings, dead_code, unused_imports, unused_mut)]
4
5//! [![github]](https://github.com/rnag/map-to-const) [![crates-io]](https://crates.io/crates/map-to-const) [![docs-rs]](https://docs.rs/map-to-const)
6//!
7//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
8//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
9//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
10//!
11//! <br>
12//!
13//! Easily convert `HashMap<K, V>` to constant `[(K, V); N]` values.
14//!
15//! <br>
16//!
17//! ## Usage
18//!
19//! ```rust
20//! use map_to_const::*;
21//! use std::collections::HashMap;
22//!
23//! fn main() {
24//! // Create a HashMap in some manner. Ideally, this will be formatted
25//! // and returned in an API response or similar.
26//! let my_map = HashMap::from([("testing", "123"), ("hello", "world")]);
27//!
28//! let const_value = map_to_const(&my_map, None);
29//!
30//! println!("{const_value}");
31//!
32//! // later in code, construct the hashmap from the `const` slice:
33//! // let my_map = HashMap::from(MY_MAP);
34//! }
35//! ```
36//!
37//! ## Examples
38//!
39//! You can check out sample usage of this crate in the [examples/](https://github.com/rnag/map-to-const/tree/main/examples)
40//! folder in the project repo on GitHub.
41//!
42//! ## Readme Docs
43//!
44//! You can find the crate's readme documentation on the
45//! [crates.io] page, or alternatively in the [`README.md`] file on the GitHub project repo.
46//!
47//! [crates.io]: https://crates.io/crates/map-to-const
48//! [`README.md`]: https://github.com/rnag/map-to-const
49//!
50use std::collections::{BTreeMap, HashMap};
51
52pub(crate) const DEFAULT_CONST_NAME: &str = "my_map";
53pub(crate) const INDENT: &str = " ";
54
55/// Trait to retrieve the string value (i.e. name) of a type.
56///
57/// Credits:
58/// <https://stackoverflow.com/a/56100816/10237506>
59pub trait ExtTypeName {
60 /// Retrieve the string value of a type.
61 fn type_name(&self) -> &str
62 where
63 Self: Sized;
64}
65
66impl ExtTypeName for String {
67 fn type_name(&self) -> &str {
68 "&str"
69 }
70}
71
72macro_rules! impl_type_name {
73 (for $($t:ty),+) => {
74 $(impl ExtTypeName for $t {
75
76 fn type_name(&self) -> &str {
77 stringify!($t)
78 }
79 })*
80 }
81}
82
83impl_type_name!(for &str, str, bool, char, usize,
84 u8, u16, u32, u64, u128,
85 i8, i16, i32, i64, i128,
86 f32, f64);
87
88/// Converts a `HashMap<K, V>` to a `const` or constant `[(K, V); N]`
89/// string representation, where `N` is the number of key-value pairs in the
90/// input *map* object.
91///
92/// # Example
93///
94/// ```rust
95/// use map_to_const::*;
96///
97/// let const_value = map_to_const(
98/// &std::collections::HashMap::from([("hello", "world"), ("testing", "123")]),
99/// "my_const_name"
100/// );
101/// ```
102///
103/// # Errors
104///
105/// Panics if the input `HashMap` object is empty.
106///
107pub fn map_to_const<
108 'a,
109 K: ExtTypeName + std::fmt::Debug + std::cmp::Ord,
110 V: ExtTypeName + std::fmt::Debug,
111>(
112 map: &HashMap<K, V>,
113 const_name: impl Into<Option<&'a str>>,
114) -> String {
115 let const_name = const_name
116 .into()
117 .unwrap_or(DEFAULT_CONST_NAME)
118 .replace(' ', "_")
119 .replace('-', "_")
120 .to_uppercase();
121
122 let map_iter = map.iter();
123 let (k, v) = map.iter().next().unwrap();
124
125 let mut const_define = format!(
126 "const {name}: [({kt}, {vt}); {len}] = ",
127 name = const_name,
128 kt = k.type_name(),
129 vt = v.type_name(),
130 len = map.len()
131 );
132
133 const_define.push('[');
134
135 let sorted_map = BTreeMap::from_iter(map_iter);
136
137 for (key, value) in sorted_map {
138 let fmt = format!("\n{0}({1:?}, {2:?}),", INDENT, key, value);
139 const_define.push_str(&fmt);
140 }
141
142 const_define.push('\n');
143 const_define.push(']');
144 const_define.push(';');
145
146 const_define
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 use indoc::indoc;
154 use log::*;
155 use std::collections::HashMap;
156
157 #[test]
158 fn test_simple() {
159 sensible_env_logger::safe_init!();
160
161 let my_map = HashMap::from([("testing", "123"), ("hello", "world")]);
162
163 let const_value = map_to_const(&my_map, None);
164
165 trace!("RESULT:\n---\n{}", const_value);
166
167 assert_eq!(
168 const_value,
169 indoc! {r#"
170 const MY_MAP: [(&str, &str); 2] = [
171 ("hello", "world"),
172 ("testing", "123"),
173 ];
174 "#}
175 .trim_end()
176 );
177 }
178
179 #[test]
180 fn test_with_numeric_keys() {
181 sensible_env_logger::safe_init!();
182
183 let my_map = HashMap::from([
184 (9876543210u64, "123".to_owned()),
185 (1122334455u64, "world".to_owned()),
186 ]);
187
188 let const_value = map_to_const(&my_map, "my map value");
189
190 trace!("RESULT:\n---\n{}", const_value);
191
192 assert_eq!(
193 const_value,
194 indoc! {r#"
195 const MY_MAP_VALUE: [(u64, &str); 2] = [
196 (1122334455, "world"),
197 (9876543210, "123"),
198 ];
199 "#}
200 .trim_end()
201 );
202 }
203
204 #[test]
205 fn test_with_boolean_keys() {
206 sensible_env_logger::safe_init!();
207
208 let my_map = HashMap::from([(true, 123.45), (false, 54.321)]);
209
210 let const_value = map_to_const(&my_map, "my-bool-map");
211
212 trace!("RESULT:\n---\n{}", const_value);
213
214 assert_eq!(
215 const_value,
216 indoc! {r#"
217 const MY_BOOL_MAP: [(bool, f64); 2] = [
218 (false, 54.321),
219 (true, 123.45),
220 ];
221 "#}
222 .trim_end()
223 );
224 }
225}