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
use regex::Regex;
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};

use crate::line_parser;
use crate::line_parser::LineType;

/// Represents a Verilog Filelist
#[derive(PartialEq, Debug, Default)]
pub struct Filelist {
    /// List of all files
    pub files: Vec<PathBuf>,
    /// List of all Include Directories
    pub incdirs: Vec<PathBuf>,
    /// HashMap of all Defines
    pub defines: HashMap<String, Option<String>>,
    /// True if comments are present in the filelist
    pub comments_present: bool,
    /// True if unknown arguments are present in the filelist
    pub unknowns_present: bool,
}

impl Filelist {
    /// Returns an empty Filelist
    pub fn new() -> Filelist {
        Filelist {
            files: Vec::new(),
            incdirs: Vec::new(),
            defines: HashMap::new(),
            comments_present: false,
            unknowns_present: false,
        }
    }

    /// Adds the elements of the other filelist to the current filelist
    pub fn extend(&mut self, other: Filelist) {
        self.files.extend(other.files);
        self.incdirs.extend(other.incdirs);
        self.defines.extend(other.defines);
        self.comments_present |= other.comments_present;
        self.unknowns_present |= other.unknowns_present;
    }
}

/// Parses a filelist file.
///
/// Environment variables represented with paranthesis or
/// curly braces (i.e. `$()` or `${}`) will be automatically
/// substituted.
///
/// # Arguments
///
/// * `path` - The path to the filelist
///
/// # Errors
///
/// Returns an error if the filelist in `path` cannot be read. Also returns
/// error if any of the nested filelists cannot be read.
pub fn parse_file(path: impl AsRef<Path>) -> Result<Filelist, Box<dyn Error>> {
    let path = path.as_ref();
    let contents = fs::read_to_string(path)?;

    let mut filelist = Filelist::new();

    for line in contents.lines() {
        let line = replace_env_vars(&line);
        match line_parser::parse_line(&line) {
            LineType::File(file) => filelist.files.push(PathBuf::from(file)),
            LineType::Define(define_map) => {
                for (d, t) in define_map.into_iter() {
                    match t {
                        Some(text) => filelist
                            .defines
                            .insert(d.to_string(), Some(text.to_string())),
                        None => filelist.defines.insert(d.to_string(), None),
                    };
                }
            }
            LineType::IncDir(incdirs) => {
                for dir in incdirs {
                    filelist.incdirs.push(PathBuf::from(dir));
                }
            }
            LineType::Comment => filelist.comments_present = true,
            LineType::Unknown => filelist.unknowns_present = true,
            LineType::Empty => (),
            LineType::Filelist(path) => {
                filelist.extend(parse_file(path)?);
            }
        }
    }
    Ok(filelist)
}

fn replace_env_vars(line: &str) -> String {
    let re_env_brace = Regex::new(r"\$\{(?P<env>[^}]+)\}").unwrap();
    let re_env_paren = Regex::new(r"\$\((?P<env>[^)]+)\)").unwrap();

    let mut expanded_line = String::from(line);
    for caps in re_env_brace.captures_iter(&line) {
        let env = &caps["env"];
        if let Ok(env_var) = std::env::var(env) {
            expanded_line = expanded_line.replace(&format!("${{{}}}", env), &env_var);
        }
    }
    for caps in re_env_paren.captures_iter(&line) {
        let env = &caps["env"];
        if let Ok(env_var) = std::env::var(env) {
            expanded_line = expanded_line.replace(&format!("$({})", env), &env_var);
        }
    }
    expanded_line
}