1use std::path::PathBuf;
2
3#[cfg(feature = "coreboot")]
4use glob::glob;
5use nom::{
6 branch::alt,
7 bytes::complete::tag,
8 character::complete::{alphanumeric1, one_of},
9 combinator::{cut, map, recognize},
10 error::{Error, ErrorKind, ParseError},
11 multi::many1,
12 sequence::delimited,
13 IResult, Parser,
14};
15use regex::Regex;
16#[cfg(feature = "deserialize")]
17use serde::Deserialize;
18#[cfg(feature = "serialize")]
19use serde::Serialize;
20
21use crate::{
22 kconfig::{parse_kconfig, Kconfig},
23 util::{ws, wsi},
24 KconfigFile, KconfigInput,
25};
26
27fn parse_filepath(input: KconfigInput<'_>) -> IResult<KconfigInput<'_>, &str> {
28 map(
29 recognize(ws(many1(alt((
30 alphanumeric1::<KconfigInput, _>,
31 recognize(one_of(".$()*-_$+@/")),
32 ))))),
33 |d| d.fragment().to_owned(),
34 )
35 .parse(input)
36}
37
38fn parse_source_kconfig(
39 input: KconfigInput,
40 source_kconfig_file: KconfigFile,
41) -> Result<Kconfig, nom::Err<Error<KconfigInput>>> {
42 let source_content = source_kconfig_file
43 .read_to_string()
44 .map_err(|_| nom::Err::Error(Error::from_error_kind(input.clone(), ErrorKind::Fail)))?;
45
46 #[allow(clippy::let_and_return)]
47 let x = match cut(parse_kconfig).parse(KconfigInput::new_extra(
48 &source_content,
49 source_kconfig_file.clone(),
50 )) {
51 Ok((_, kconfig)) => Ok(kconfig),
52 Err(_e) => Err(nom::Err::Error(Error::new(
53 KconfigInput::new_extra("", source_kconfig_file),
54 ErrorKind::Fail,
55 ))),
56 };
57 x
58}
59
60#[cfg(feature = "coreboot")]
61fn expand_source_files<'a>(
62 input: KconfigInput<'a>,
63 file: &str,
64) -> Result<Vec<PathBuf>, nom::Err<Error<KconfigInput<'a>>>> {
65 let full_path_pattern = input.extra.root_dir.join(file).display().to_string();
66 let mut expanded_files = Vec::new();
67 for source_path in glob(&full_path_pattern)
68 .map_err(|_| nom::Err::Error(Error::from_error_kind(input.clone(), ErrorKind::Fail)))?
69 {
70 let source_path = source_path
71 .map_err(|_| nom::Err::Error(Error::from_error_kind(input.clone(), ErrorKind::Fail)))?;
72 let source_path_without_root = source_path
73 .strip_prefix(&input.extra.root_dir)
74 .map_err(|_| nom::Err::Error(Error::from_error_kind(input.clone(), ErrorKind::Fail)))?;
75 expanded_files.push(source_path_without_root.to_path_buf());
76 }
77 expanded_files.sort();
78 if expanded_files.is_empty() {
79 return Err(nom::Err::Error(Error::from_error_kind(
80 input,
81 ErrorKind::Fail,
82 )));
83 }
84
85 Ok(expanded_files)
86}
87
88pub fn parse_source(input: KconfigInput) -> IResult<KconfigInput, Source> {
89 let (input, _) = ws(tag("source")).parse(input)?;
90 let (input, file) = wsi(alt((
91 delimited(tag("\""), parse_filepath, tag("\"")),
92 parse_filepath,
93 )))
94 .parse(input)?;
95 if let Some(file) = apply_vars(file, &input.extra.vars) {
96 #[cfg(feature = "coreboot")]
97 {
98 let expanded_files = expand_source_files(input.clone(), &file)?;
99 let mut sources = vec![];
100
101 for expanded_file in expanded_files {
102 let source_kconfig_file = KconfigFile::new_with_vars(
103 input.clone().extra.root_dir,
104 expanded_file,
105 &input.extra.vars,
106 );
107 let source = parse_source_kconfig(input.clone(), source_kconfig_file)?;
108 sources.push(source);
109 }
110
111 Ok((input, Source { entries: sources }))
112 }
113
114 #[cfg(not(feature = "coreboot"))]
115 {
116 let source_kconfig_file = KconfigFile::new_with_vars(
117 input.clone().extra.root_dir,
118 PathBuf::from(file),
119 &input.extra.vars,
120 );
121 let source = parse_source_kconfig(input.clone(), source_kconfig_file)?;
122 Ok((
123 input,
124 Source {
125 entries: vec![source],
126 },
127 ))
128 }
129 } else {
130 Ok((
131 input,
132 Source {
133 entries: vec![Kconfig {
134 file: file.to_string(),
135 ..Default::default()
136 }],
137 },
138 ))
139 }
140}
141
142pub fn apply_vars(
143 file: &str,
144 extra_vars: &std::collections::HashMap<String, String>,
145) -> Option<String> {
146 let re = Regex::new(r"\$\((\S+)\)").unwrap();
147 let mut file_copy = String::from(file);
148 for (var_name, var_value) in re.captures_iter(file).map(|cap| {
149 let ex: (&str, [&str; 1]) = cap.extract();
150 let var = ex.1[0];
151 (var, extra_vars.get(var))
152 }) {
153 if let Some(var_value) = var_value {
154 file_copy = file_copy.replace(&format!("$({var_name})"), var_value);
155 } else {
156 return None;
157 }
158 }
159 Some(file_copy)
160}
161
162#[derive(Debug, Clone, PartialEq)]
164#[cfg_attr(feature = "hash", derive(Hash))]
165#[cfg_attr(feature = "serialize", derive(Serialize))]
166#[cfg_attr(feature = "deserialize", derive(Deserialize))]
167pub struct Source {
168 pub entries: Vec<Kconfig>,
169}
170
171#[cfg(test)]
172use crate::assert_parsing_eq;
173
174#[test]
175fn test_parse_filepath() {
176 assert_parsing_eq!(
177 parse_filepath,
178 "u-boot/board/sagem/f@st1704/Kconfig",
179 Ok(("", "u-boot/board/sagem/f@st1704/Kconfig"))
180 );
181
182 assert_parsing_eq!(
183 parse_filepath,
184 "u-boot/board/l+g/vinco/Kconfig",
185 Ok(("", "u-boot/board/l+g/vinco/Kconfig"))
186 );
187}