embuild 0.33.1

A build support library for embedded Rust
Documentation
//! 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))),
        _ => 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 {
        return None;
    })
}