summaryrefslogtreecommitdiff
path: root/src/repo/profile/useflags/mod.rs
blob: d7fb96ccc10b2b13efb1fea576f24839997bc2a2 (plain)
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
use std::{
    io,
    path::{Path, PathBuf},
};

use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter};

use crate::{
    Parseable,
    repo::profile::{FlagOperation, LineBasedFileExpr, Profile, read_config_files},
    useflag::UseFlag,
};

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("{0}: io error: {1}")]
    Io(PathBuf, io::Error),
    #[error("parser error: {0}")]
    Parser(String),
}

#[derive(Debug, Clone, Copy)]
pub(super) enum Kind {
    Force,
    Mask,
    StableForce,
    StableMask,
}

#[allow(clippy::unnecessary_wraps)]
pub(super) fn evaluate<P: AsRef<Path>>(
    parents: &[Profile],
    kind: Kind,
    path: P,
) -> Result<Vec<UseFlag>, Error> {
    let file_path = match kind {
        Kind::Force => "use.force",
        Kind::Mask => "use.mask",
        Kind::StableForce => "use.stable.force",
        Kind::StableMask => "use.stable.mask",
    };

    let parsed = match read_config_files(path.as_ref().join(file_path)) {
        Ok(contents) => parse(&contents)?,
        Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Vec::new(),
        Err(e) => return Err(Error::Io(path.as_ref().to_path_buf(), e)),
    };

    Ok(inherit(parents, kind, parsed))
}

fn inherit(parents: &[Profile], kind: Kind, operations: Vec<FlagOperation>) -> Vec<UseFlag> {
    let mut accumulated = Vec::new();

    for parent in parents {
        let source = match kind {
            Kind::Force => parent.use_force(),
            Kind::Mask => parent.use_mask(),
            Kind::StableForce => parent.use_stable_force(),
            Kind::StableMask => parent.use_stable_mask(),
        };

        for flag in source {
            accumulated.push(flag.clone());
        }
    }

    for operation in operations {
        match operation {
            FlagOperation::Add(flag) => {
                accumulated.push(flag);
            }
            FlagOperation::Remove(flag) => {
                accumulated.retain(|v| *v != flag);
            }
        }
    }

    accumulated
}

fn parse(contents: &str) -> Result<Vec<FlagOperation>, Error> {
    Ok(LineBasedFileExpr::<FlagOperation>::parser()
        .separated_by_with_opt_trailing(ascii_whitespace1())
        .many()
        .parse_finished(InputIter::new(contents))
        .map_err(|e| Error::Parser(e.rest().to_string()))?
        .into_iter()
        .filter_map(|expr| match expr {
            LineBasedFileExpr::Comment => None,
            LineBasedFileExpr::Expr(flag_operation) => Some(flag_operation),
        })
        .collect())
}