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
//! A quick and dirty parser for the .config files generated by kconfig systems (e.g. used
//! in the esp-idf).
use std::collections::HashMap;
use std::fs;
use std::io::{self, BufRead, Read};
use std::path::Path;
use anyhow::Result;
/// A tristate kconfig configuration item.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Tristate {
/// The item is enabled, compiled, true.
True,
/// The item is disabled, excluded, false.
False,
/// The item is compiled as module.
///
/// Related to a linux kernel functionality that should be compiled as a loadable
/// module (hence the name).
Module,
/// The item is unset.
NotSet,
}
/// Value of a kconfig configuration item.
#[derive(Clone, Debug)]
pub enum Value {
/// A [`Tristate`] value.
Tristate(Tristate),
/// A [`String`] value.
String(String),
}
impl Value {
/// Turn a configuration value of an item named `key` into a valid rust cfg.
///
/// Only the following cfgs will be generated:
/// - For a [`Tristate::True`], `<prefix>_<key>`;
/// - for a [`String`], `<prefix>_<key>="<value>"`.
///
/// All other values return [`None`].
///
/// Both `prefix` and `key` are lowercased.
pub fn to_rustc_cfg(&self, prefix: impl AsRef<str>, key: impl AsRef<str>) -> Option<String> {
match self {
Value::Tristate(Tristate::True) => Some(""),
Value::String(s) => Some(s.as_str()),
_ => None,
}
.map(|value| {
if value.is_empty() {
format!(
"{}_{}",
prefix.as_ref().to_lowercase(),
key.as_ref().to_lowercase()
)
} else {
format!(
"{}_{}=\"{}\"",
prefix.as_ref().to_lowercase(),
key.as_ref().to_lowercase(),
value.replace('\"', "\\\"")
)
}
})
}
}
/// Try to load the configurations from a generated kconfig json file.
pub fn try_from_json_file(path: impl AsRef<Path>) -> Result<impl Iterator<Item = (String, Value)>> {
try_from_json(fs::File::open(path)?)
}
/// Try to load the configurations from a generated kconfig json stream.
pub fn try_from_json<R>(reader: R) -> Result<impl Iterator<Item = (String, Value)>>
where
R: Read,
{
let values: HashMap<String, serde_json::Value> = serde_json::from_reader(reader)?;
let iter = values.into_iter().filter_map(|(k, v)| match v {
serde_json::Value::Bool(true) => Some((k, Value::Tristate(Tristate::True))),
serde_json::Value::Bool(false) => Some((k, Value::Tristate(Tristate::False))),
serde_json::Value::String(value) => Some((k, Value::String(value))),
// Numeric (int / hex) kconfig values are preserved as their string representation so that
// consumers can expose selected ones as value cfgs (e.g. `<key>="0"`). Which keys actually
// become cfgs is decided downstream (esp-idf-sys only whitelists a few), so preserving the
// value here adds no cfgs on its own.
serde_json::Value::Number(value) => Some((k, Value::String(value.to_string()))),
_ => None,
});
Ok(iter)
}
/// Try to load the configurations from a generated .config file.
pub fn try_from_config_file(
path: impl AsRef<Path>,
) -> Result<impl Iterator<Item = (String, Value)>> {
try_from_config(fs::File::open(path.as_ref())?)
}
/// Try to load the configurations from a generated .config stream.
pub fn try_from_config<R>(reader: R) -> Result<impl Iterator<Item = (String, Value)>>
where
R: Read,
{
let iter = io::BufReader::new(reader)
.lines()
.filter_map(|line| line.ok().map(|l| l.trim().to_owned()))
.filter(|line| !line.starts_with('#'))
.filter_map(|line| {
let mut split = line.split('=');
if let Some(key) = split.next() {
split
.next()
.map(|v| v.trim())
.and_then(parse_config_value)
.map(|value| (key.to_owned(), value))
} else {
None
}
});
Ok(iter)
}
fn parse_config_value(str: impl AsRef<str>) -> Option<Value> {
let str = str.as_ref();
Some(if str.starts_with('\"') {
Value::String(str[1..str.len() - 1].to_owned())
} else if str == "y" {
Value::Tristate(Tristate::True)
} else if str == "n" {
Value::Tristate(Tristate::False)
} else if str == "m" {
Value::Tristate(Tristate::Module)
} else if !str.is_empty() {
// Numeric (int / hex) values, e.g. `123` or `0x100`, are preserved as strings so that
// consumers may expose selected ones as value cfgs (see `try_from_json`).
Value::String(str.to_owned())
} else {
return None;
})
}