Skip to main content

nom_kconfig/entry/
source.rs

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/// Entry that reads the specified configuration file. This file is always parsed.
163#[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}