forked from gentoo-utils/gentoo-utils
impl profile evaluation
This commit is contained in:
@@ -10,11 +10,15 @@ use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter, tag};
|
||||
use crate::{
|
||||
Parseable,
|
||||
atom::{self, Atom},
|
||||
repo::ebuild::{Depend, Eapi, Ebuild, Eclass, License, SrcUri},
|
||||
repo::{
|
||||
ebuild::{Depend, Eapi, Ebuild, Eclass, License, SrcUri},
|
||||
profile::Profile,
|
||||
},
|
||||
useflag::IUseFlag,
|
||||
};
|
||||
|
||||
pub mod ebuild;
|
||||
pub mod profile;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
@@ -26,6 +30,8 @@ pub enum Error {
|
||||
Unicode(PathBuf),
|
||||
#[error("parser error: {0}")]
|
||||
Parser(String),
|
||||
#[error("profile error: {0}")]
|
||||
Profile(profile::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Get)]
|
||||
@@ -62,6 +68,10 @@ impl Repo {
|
||||
fs::read_dir(&path).map_err(|e| Error::Io(path, e))?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn evaluate_profile<P: AsRef<Path>>(&self, path: P) -> Result<Profile, Error> {
|
||||
Profile::evaluate(self.path.join("profiles").join(path)).map_err(Error::Profile)
|
||||
}
|
||||
}
|
||||
|
||||
impl Category {
|
||||
|
||||
135
src/repo/profile/make_defaults/mod.rs
Normal file
135
src/repo/profile/make_defaults/mod.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter};
|
||||
|
||||
use crate::{
|
||||
Parseable,
|
||||
repo::profile::{LineBasedFileExpr, Profile},
|
||||
};
|
||||
|
||||
mod parsers;
|
||||
|
||||
#[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, PartialEq, Eq, Hash)]
|
||||
struct Key(String);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Literal(String);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Interpolation(String);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Segment {
|
||||
Literal(Literal),
|
||||
Interpolation(Interpolation),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Assignment(Key, Vec<Segment>);
|
||||
|
||||
pub(super) fn evaluate<P: AsRef<Path>>(
|
||||
parents: &[Profile],
|
||||
path: P,
|
||||
) -> Result<HashMap<String, String>, Error> {
|
||||
let parsed = match fs::read_to_string(path.as_ref().join("make.defaults")) {
|
||||
Ok(contents) => parse(&contents)?,
|
||||
Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => HashMap::new(),
|
||||
Err(e) => return Err(Error::Io(path.as_ref().to_path_buf(), e)),
|
||||
};
|
||||
|
||||
let mut vars = interpolate(parents, parsed);
|
||||
|
||||
incremental(parents, &mut vars);
|
||||
|
||||
Ok(vars)
|
||||
}
|
||||
|
||||
fn incremental(parents: &[Profile], vars: &mut HashMap<String, String>) {
|
||||
for key in [
|
||||
"USE",
|
||||
"USE_EXPAND",
|
||||
"USE_EXPAND_HIDDEN",
|
||||
"CONFIG_PROTECT",
|
||||
"CONFIG_PROTECT_MASK",
|
||||
] {
|
||||
let mut accumulated = Vec::new();
|
||||
|
||||
for parent in parents {
|
||||
if let Some(values) = parent.make_defaults().get(key) {
|
||||
accumulated.extend(values.split_ascii_whitespace());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(values) = vars.get(key) {
|
||||
accumulated.extend(values.split_ascii_whitespace());
|
||||
}
|
||||
|
||||
let mut final_values = Vec::new();
|
||||
|
||||
for var in accumulated {
|
||||
if var == "-*" {
|
||||
final_values.clear();
|
||||
} else if let Some(stripped) = var.strip_prefix("-") {
|
||||
final_values.retain(|v| *v != stripped);
|
||||
} else {
|
||||
final_values.push(var);
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
final_values.retain(|v| seen.insert(*v));
|
||||
|
||||
if !final_values.is_empty() {
|
||||
vars.insert(key.to_string(), final_values.join(" "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn interpolate(parents: &[Profile], vars: HashMap<Key, Vec<Segment>>) -> HashMap<String, String> {
|
||||
let parent_vars = parents
|
||||
.iter()
|
||||
.flat_map(|parent| parent.make_defaults().clone().into_iter())
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
vars.into_iter()
|
||||
.map(|(key, segments)| {
|
||||
let interpolated = segments
|
||||
.into_iter()
|
||||
.map(|segment| match segment {
|
||||
Segment::Interpolation(i) => parent_vars.get(&i.0).cloned().unwrap_or_default(),
|
||||
Segment::Literal(literal) => literal.0.trim().to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let joined = interpolated.join("");
|
||||
|
||||
(key.0, joined)
|
||||
})
|
||||
.collect::<HashMap<String, String>>()
|
||||
}
|
||||
|
||||
fn parse(contents: &str) -> Result<HashMap<Key, Vec<Segment>>, Error> {
|
||||
Ok(LineBasedFileExpr::<Assignment>::parser()
|
||||
.separated_by_with_opt_trailing(ascii_whitespace1())
|
||||
.many()
|
||||
.parse_finished(InputIter::new(contents))
|
||||
.map_err(|e| Error::Parser(dbg!(e).rest().to_string()))?
|
||||
.into_iter()
|
||||
.filter_map(|expr| match expr {
|
||||
LineBasedFileExpr::Comment => None,
|
||||
LineBasedFileExpr::Expr(Assignment(key, value)) => Some((key, value)),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
88
src/repo/profile/make_defaults/parsers.rs
Normal file
88
src/repo/profile/make_defaults/parsers.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use mon::{Parser, ParserIter, ascii_alpha, ascii_alphanumeric, r#if, one_of, tag};
|
||||
|
||||
use crate::{
|
||||
Parseable,
|
||||
repo::profile::make_defaults::{Assignment, Interpolation, Key, Literal, Segment},
|
||||
};
|
||||
|
||||
impl<'a> Parseable<'a, &'a str> for Key {
|
||||
type Parser = impl Parser<&'a str, Output = Self>;
|
||||
|
||||
fn parser() -> Self::Parser {
|
||||
let start = ascii_alpha();
|
||||
let rest = ascii_alphanumeric()
|
||||
.or(one_of("_".chars()))
|
||||
.repeated()
|
||||
.many();
|
||||
|
||||
start
|
||||
.followed_by(rest)
|
||||
.recognize()
|
||||
.map(|output: &str| Key(output.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parseable<'a, &'a str> for Literal {
|
||||
type Parser = impl Parser<&'a str, Output = Self>;
|
||||
|
||||
fn parser() -> Self::Parser {
|
||||
r#if(|c: &char| *c != '"')
|
||||
.and_not(Interpolation::parser())
|
||||
.repeated()
|
||||
.at_least(1)
|
||||
.recognize()
|
||||
.map(|output: &str| Literal(output.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parseable<'a, &'a str> for Interpolation {
|
||||
type Parser = impl Parser<&'a str, Output = Self>;
|
||||
|
||||
fn parser() -> Self::Parser {
|
||||
Key::parser()
|
||||
.recognize()
|
||||
.delimited_by(tag("{"), tag("}"))
|
||||
.preceded_by(tag("$"))
|
||||
.map(|output: &str| Interpolation(output.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parseable<'a, &'a str> for Segment {
|
||||
type Parser = impl Parser<&'a str, Output = Self>;
|
||||
|
||||
fn parser() -> Self::Parser {
|
||||
Literal::parser()
|
||||
.map(Segment::Literal)
|
||||
.or(Interpolation::parser().map(Segment::Interpolation))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parseable<'a, &'a str> for Assignment {
|
||||
type Parser = impl Parser<&'a str, Output = Self>;
|
||||
|
||||
fn parser() -> Self::Parser {
|
||||
Key::parser()
|
||||
.followed_by(tag("="))
|
||||
.and(
|
||||
Segment::parser()
|
||||
.repeated()
|
||||
.many()
|
||||
.delimited_by(tag("\""), tag("\"")),
|
||||
)
|
||||
.map(|(key, value)| Assignment(key, value))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use mon::input::InputIter;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_value() {
|
||||
let it = InputIter::new(r#"KEY="foo ${bar}""#);
|
||||
|
||||
Assignment::parser().check_finished(it).unwrap();
|
||||
}
|
||||
}
|
||||
157
src/repo/profile/mod.rs
Normal file
157
src/repo/profile/mod.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{self, File},
|
||||
io::{self, Read},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use get::Get;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{atom::Atom, useflag::UseFlag};
|
||||
|
||||
mod make_defaults;
|
||||
mod package;
|
||||
mod package_use;
|
||||
mod packages;
|
||||
mod parsers;
|
||||
mod useflags;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum LineBasedFileExpr<T> {
|
||||
Comment,
|
||||
Expr(T),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum FlagOperation {
|
||||
Add(UseFlag),
|
||||
Remove(UseFlag),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("{0}: io error: {1}")]
|
||||
Io(PathBuf, io::Error),
|
||||
#[error("error evaluating make.defaults settings: {0}")]
|
||||
MakeDefaults(#[from] make_defaults::Error),
|
||||
#[error("error evaluating packages settings: {0}")]
|
||||
Packages(#[from] packages::Error),
|
||||
#[error("error evaluating package settings: {0}")]
|
||||
Package(#[from] package::Error),
|
||||
#[error("error evaluating package.use settings: {0}")]
|
||||
PackageUse(#[from] package_use::Error),
|
||||
#[error("error evaluating use settings: {0}")]
|
||||
Use(#[from] useflags::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Get)]
|
||||
pub struct Profile {
|
||||
#[get(kind = "deref")]
|
||||
path: PathBuf,
|
||||
#[get(kind = "deref")]
|
||||
parents: Vec<Profile>,
|
||||
make_defaults: HashMap<String, String>,
|
||||
#[get(kind = "deref")]
|
||||
packages: Vec<Atom>,
|
||||
#[get(kind = "deref")]
|
||||
package_mask: Vec<Atom>,
|
||||
#[get(kind = "deref")]
|
||||
package_provided: Vec<Atom>,
|
||||
package_use: HashMap<Atom, Vec<UseFlag>>,
|
||||
package_use_force: HashMap<Atom, Vec<UseFlag>>,
|
||||
package_use_mask: HashMap<Atom, Vec<UseFlag>>,
|
||||
package_use_stable_force: HashMap<Atom, Vec<UseFlag>>,
|
||||
package_use_stable_mask: HashMap<Atom, Vec<UseFlag>>,
|
||||
#[get(kind = "deref")]
|
||||
use_force: Vec<UseFlag>,
|
||||
#[get(kind = "deref")]
|
||||
use_mask: Vec<UseFlag>,
|
||||
#[get(kind = "deref")]
|
||||
use_stable_force: Vec<UseFlag>,
|
||||
#[get(kind = "deref")]
|
||||
use_stable_mask: Vec<UseFlag>,
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub(super) fn evaluate<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||
let parents_path = path.as_ref().join("parent");
|
||||
|
||||
let parents = match fs::read_to_string(&parents_path) {
|
||||
Ok(parents) => parents
|
||||
.lines()
|
||||
.map(|line| path.as_ref().join(line))
|
||||
.map(Profile::evaluate)
|
||||
.collect::<Result<_, _>>()?,
|
||||
Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Vec::new(),
|
||||
Err(e) => return Err(Error::Io(parents_path, e)),
|
||||
};
|
||||
|
||||
let make_defaults = make_defaults::evaluate(&parents, &path)?;
|
||||
|
||||
let packages = packages::evaluate(&parents, &path)?;
|
||||
|
||||
let package_mask = package::evaluate(&parents, package::Kind::Mask, &path)?;
|
||||
let package_provided = package::evaluate(&parents, package::Kind::Provided, &path)?;
|
||||
|
||||
let package_use = package_use::evaluate(&parents, package_use::Kind::Use, &path)?;
|
||||
let package_use_force = package_use::evaluate(&parents, package_use::Kind::Force, &path)?;
|
||||
let package_use_mask = package_use::evaluate(&parents, package_use::Kind::Mask, &path)?;
|
||||
let package_use_stable_force =
|
||||
package_use::evaluate(&parents, package_use::Kind::StableForce, &path)?;
|
||||
let package_use_stable_mask =
|
||||
package_use::evaluate(&parents, package_use::Kind::StableMask, &path)?;
|
||||
|
||||
let use_force = useflags::evaluate(&parents, useflags::Kind::Force, &path)?;
|
||||
let use_mask = useflags::evaluate(&parents, useflags::Kind::Mask, &path)?;
|
||||
let use_stable_force = useflags::evaluate(&parents, useflags::Kind::StableForce, &path)?;
|
||||
let use_stable_mask = useflags::evaluate(&parents, useflags::Kind::StableMask, &path)?;
|
||||
|
||||
Ok(Self {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
parents,
|
||||
make_defaults,
|
||||
packages,
|
||||
package_mask,
|
||||
package_provided,
|
||||
package_use,
|
||||
package_use_force,
|
||||
package_use_mask,
|
||||
package_use_stable_force,
|
||||
package_use_stable_mask,
|
||||
use_force,
|
||||
use_mask,
|
||||
use_stable_force,
|
||||
use_stable_mask,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn read_config_files<P: AsRef<Path>>(path: P) -> Result<String, io::Error> {
|
||||
let metadata = fs::metadata(&path)?;
|
||||
|
||||
if metadata.is_file() {
|
||||
fs::read_to_string(&path)
|
||||
} else if metadata.is_dir() {
|
||||
let mut buffer = String::new();
|
||||
let paths = fs::read_dir(&path)?
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.map(|entry| entry.path())
|
||||
.filter(|path| path.starts_with("."))
|
||||
.sorted()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for path in &paths {
|
||||
let mut file = File::open(path)?;
|
||||
|
||||
file.read_to_string(&mut buffer)?;
|
||||
}
|
||||
|
||||
Ok(buffer)
|
||||
} else {
|
||||
let path = fs::canonicalize(&path)?;
|
||||
|
||||
read_config_files(path)
|
||||
}
|
||||
}
|
||||
95
src/repo/profile/package/mod.rs
Normal file
95
src/repo/profile/package/mod.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use std::{
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter};
|
||||
|
||||
use crate::{
|
||||
Parseable,
|
||||
atom::Atom,
|
||||
repo::profile::{LineBasedFileExpr, Profile, read_config_files},
|
||||
};
|
||||
|
||||
mod parsers;
|
||||
|
||||
#[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 {
|
||||
Mask,
|
||||
Provided,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Package {
|
||||
Add(Atom),
|
||||
Remove(Atom),
|
||||
}
|
||||
|
||||
pub(super) fn evaluate<P: AsRef<Path>>(
|
||||
parents: &[Profile],
|
||||
kind: Kind,
|
||||
path: P,
|
||||
) -> Result<Vec<Atom>, Error> {
|
||||
let file_path = match kind {
|
||||
Kind::Mask => "package.mask",
|
||||
Kind::Provided => "package.provided",
|
||||
};
|
||||
|
||||
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, packages: Vec<Package>) -> Vec<Atom> {
|
||||
let mut accumulated = Vec::new();
|
||||
|
||||
for parent in parents {
|
||||
let source = match kind {
|
||||
Kind::Mask => parent.package_mask(),
|
||||
Kind::Provided => parent.package_provided(),
|
||||
};
|
||||
|
||||
for package in source {
|
||||
accumulated.push(package.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for package in packages {
|
||||
match package {
|
||||
Package::Add(package) => {
|
||||
accumulated.push(package);
|
||||
}
|
||||
Package::Remove(package) => {
|
||||
accumulated.retain(|p| *p != package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accumulated
|
||||
}
|
||||
|
||||
fn parse(contents: &str) -> Result<Vec<Package>, Error> {
|
||||
Ok(LineBasedFileExpr::<Package>::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(package) => Some(package),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
13
src/repo/profile/package/parsers.rs
Normal file
13
src/repo/profile/package/parsers.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use mon::{Parser, tag};
|
||||
|
||||
use crate::{Parseable, atom::Atom, repo::profile::package::Package};
|
||||
|
||||
impl<'a> Parseable<'a, &'a str> for Package {
|
||||
type Parser = impl Parser<&'a str, Output = Self>;
|
||||
|
||||
fn parser() -> Self::Parser {
|
||||
Atom::parser()
|
||||
.map(Package::Add)
|
||||
.or(Atom::parser().preceded_by(tag("-")).map(Package::Remove))
|
||||
}
|
||||
}
|
||||
125
src/repo/profile/package_use/mod.rs
Normal file
125
src/repo/profile/package_use/mod.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter};
|
||||
|
||||
use crate::{
|
||||
Parseable,
|
||||
atom::Atom,
|
||||
repo::profile::{FlagOperation, LineBasedFileExpr, Profile, read_config_files},
|
||||
useflag::UseFlag,
|
||||
};
|
||||
|
||||
mod parsers;
|
||||
|
||||
#[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)]
|
||||
struct Expr(Atom, Vec<FlagOperation>);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(super) enum Kind {
|
||||
Use,
|
||||
Force,
|
||||
Mask,
|
||||
StableForce,
|
||||
StableMask,
|
||||
}
|
||||
|
||||
pub(super) fn evaluate<P: AsRef<Path>>(
|
||||
parents: &[Profile],
|
||||
kind: Kind,
|
||||
path: P,
|
||||
) -> Result<HashMap<Atom, Vec<UseFlag>>, Error> {
|
||||
let file_path = match kind {
|
||||
Kind::Use => "package.use",
|
||||
Kind::Force => "package.use.force",
|
||||
Kind::Mask => "package.use.mask",
|
||||
Kind::StableForce => "package.use.stable.force",
|
||||
Kind::StableMask => "package.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) => HashMap::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,
|
||||
vars: HashMap<Atom, Vec<FlagOperation>>,
|
||||
) -> HashMap<Atom, Vec<UseFlag>> {
|
||||
let mut accumulated: HashMap<Atom, Vec<UseFlag>> = HashMap::new();
|
||||
|
||||
for parent in parents {
|
||||
let source = match kind {
|
||||
Kind::Use => parent.package_use(),
|
||||
Kind::Force => parent.package_use_force(),
|
||||
Kind::Mask => parent.package_use_mask(),
|
||||
Kind::StableForce => parent.package_use_stable_force(),
|
||||
Kind::StableMask => parent.package_use_stable_mask(),
|
||||
};
|
||||
|
||||
for (atom, flags) in source {
|
||||
accumulated
|
||||
.entry(atom.clone())
|
||||
.and_modify(|f| f.extend(flags.iter().cloned()))
|
||||
.or_insert(flags.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for (atom, flags) in vars {
|
||||
match accumulated.get_mut(&atom) {
|
||||
Some(accumulated) => {
|
||||
for flag in flags {
|
||||
match flag {
|
||||
FlagOperation::Add(flag) => accumulated.push(flag),
|
||||
FlagOperation::Remove(flag) => accumulated.retain(|v| *v != flag),
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
accumulated.insert(
|
||||
atom.clone(),
|
||||
flags
|
||||
.iter()
|
||||
.filter_map(|flag| match flag {
|
||||
FlagOperation::Add(flag) => Some(flag),
|
||||
FlagOperation::Remove(_) => None,
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accumulated
|
||||
}
|
||||
|
||||
fn parse(contents: &str) -> Result<HashMap<Atom, Vec<FlagOperation>>, Error> {
|
||||
Ok(LineBasedFileExpr::<Expr>::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(Expr(atom, operations)) => Some((atom, operations)),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
36
src/repo/profile/package_use/parsers.rs
Normal file
36
src/repo/profile/package_use/parsers.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use mon::{Parser, ParserIter, ascii_whitespace, ascii_whitespace1, tag};
|
||||
|
||||
use crate::{
|
||||
Parseable,
|
||||
atom::Atom,
|
||||
repo::profile::{FlagOperation, package_use::Expr},
|
||||
};
|
||||
|
||||
impl<'a> Parseable<'a, &'a str> for Expr {
|
||||
type Parser = impl Parser<&'a str, Output = Self>;
|
||||
|
||||
fn parser() -> Self::Parser {
|
||||
Atom::parser()
|
||||
.followed_by(ascii_whitespace1())
|
||||
.and(
|
||||
FlagOperation::parser()
|
||||
.separated_by(ascii_whitespace().and_not(tag("\n")).repeated().at_least(1))
|
||||
.at_least(1),
|
||||
)
|
||||
.map(|(atom, operations)| Expr(atom, operations))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use mon::input::InputIter;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_expr() {
|
||||
let it = InputIter::new("foo/bar a -b");
|
||||
|
||||
Expr::parser().check_finished(it).unwrap();
|
||||
}
|
||||
}
|
||||
75
src/repo/profile/packages/mod.rs
Normal file
75
src/repo/profile/packages/mod.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::{
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter};
|
||||
|
||||
use crate::{
|
||||
Parseable,
|
||||
atom::Atom,
|
||||
repo::profile::{LineBasedFileExpr, Profile},
|
||||
};
|
||||
|
||||
mod parsers;
|
||||
|
||||
#[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)]
|
||||
enum Package {
|
||||
Add(Atom),
|
||||
Remove(Atom),
|
||||
}
|
||||
|
||||
pub(super) fn evaluate<P: AsRef<Path>>(parents: &[Profile], path: P) -> Result<Vec<Atom>, Error> {
|
||||
let parsed = match fs::read_to_string(path.as_ref().join("packages")) {
|
||||
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, parsed))
|
||||
}
|
||||
|
||||
fn inherit(parents: &[Profile], packages: Vec<Package>) -> Vec<Atom> {
|
||||
let mut accumulated = Vec::new();
|
||||
|
||||
for parent in parents {
|
||||
for package in parent.packages() {
|
||||
accumulated.push(package.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for package in packages {
|
||||
match package {
|
||||
Package::Add(package) => {
|
||||
accumulated.push(package);
|
||||
}
|
||||
Package::Remove(package) => {
|
||||
accumulated.retain(|p| *p != package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accumulated
|
||||
}
|
||||
|
||||
fn parse(contents: &str) -> Result<Vec<Package>, Error> {
|
||||
Ok(LineBasedFileExpr::<Package>::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(package) => Some(package),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
14
src/repo/profile/packages/parsers.rs
Normal file
14
src/repo/profile/packages/parsers.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use mon::{Parser, tag};
|
||||
|
||||
use crate::{Parseable, atom::Atom, repo::profile::packages::Package};
|
||||
|
||||
impl<'a> Parseable<'a, &'a str> for Package {
|
||||
type Parser = impl Parser<&'a str, Output = Self>;
|
||||
|
||||
fn parser() -> Self::Parser {
|
||||
Atom::parser()
|
||||
.preceded_by(tag("*"))
|
||||
.map(Package::Add)
|
||||
.or(Atom::parser().preceded_by(tag("-*")).map(Package::Remove))
|
||||
}
|
||||
}
|
||||
35
src/repo/profile/parsers.rs
Normal file
35
src/repo/profile/parsers.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use mon::{Parser, ParserIter, any, ascii_whitespace1, tag};
|
||||
|
||||
use crate::{
|
||||
Parseable,
|
||||
repo::profile::{FlagOperation, LineBasedFileExpr},
|
||||
useflag::UseFlag,
|
||||
};
|
||||
|
||||
impl<'a, T> Parseable<'a, &'a str> for LineBasedFileExpr<T>
|
||||
where
|
||||
T: Parseable<'a, &'a str>,
|
||||
{
|
||||
type Parser = impl Parser<&'a str, Output = Self>;
|
||||
|
||||
fn parser() -> Self::Parser {
|
||||
let comment = tag("#")
|
||||
.preceded_by(ascii_whitespace1().opt())
|
||||
.followed_by(any().and_not(tag("\n")).repeated().many())
|
||||
.map(|_| LineBasedFileExpr::Comment);
|
||||
let expr = T::parser().map(|expr| LineBasedFileExpr::Expr(expr));
|
||||
|
||||
comment.or(expr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parseable<'a, &'a str> for FlagOperation {
|
||||
type Parser = impl Parser<&'a str, Output = Self>;
|
||||
|
||||
fn parser() -> Self::Parser {
|
||||
UseFlag::parser()
|
||||
.preceded_by(tag("-"))
|
||||
.map(FlagOperation::Remove)
|
||||
.or(UseFlag::parser().map(FlagOperation::Add))
|
||||
}
|
||||
}
|
||||
94
src/repo/profile/useflags/mod.rs
Normal file
94
src/repo/profile/useflags/mod.rs
Normal file
@@ -0,0 +1,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())
|
||||
}
|
||||
Reference in New Issue
Block a user