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}