use std::path::Path;
use super::classify::Effect;
use super::judge::Ruling;
use super::rules::{DEFAULT, Reach, SECRET_FRAGMENTS};
const DEFAULT_REF: &str = "refs/heads/config/default";
pub const CAPABILITY_YAML: &str = "capability.yaml";
const REQUIRED: &str = "required";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Row {
pub program: String,
pub words: Vec<String>,
pub reach: Reach,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Policy {
pub confinement_required: bool,
table: Vec<(Effect, Ruling)>,
rules: Vec<Row>,
secrets: Vec<String>,
}
impl Policy {
pub fn read(workspace: &Path) -> Policy {
let bytes =
crate::config_edit::branch::config_file(workspace, DEFAULT_REF, CAPABILITY_YAML);
let text = bytes
.ok()
.and_then(|b| String::from_utf8(b).ok())
.unwrap_or_default();
Policy::parse(&text)
}
pub fn parse(text: &str) -> Policy {
let mut policy = Policy::default();
let mut section = "";
for raw in text.lines() {
let line = strip_comment(raw);
if line.trim().is_empty() {
continue;
}
if !line.starts_with([' ', '\t', '-']) {
section = policy.top_level(line);
continue;
}
policy.item(section, line.trim_start().trim_start_matches('-').trim());
}
policy
}
fn top_level(&mut self, line: &str) -> &'static str {
let (key, value) = split_pair(line);
match key {
"confinement" => {
self.confinement_required = value == REQUIRED;
""
}
"table" => "table",
"rules" => "rules",
"secrets" => "secrets",
_ => "",
}
}
fn item(&mut self, section: &str, item: &str) {
match section {
"table" => {
let (class, verdict) = split_pair(item);
if let (Some(effect), Some(ruling)) = (Effect::of(class), Ruling::of(verdict)) {
self.table.push((effect, ruling));
}
}
"rules" => {
let (key, class) = split_pair(item);
let mut words = key.split_whitespace().map(str::to_owned);
if let (Some(program), Some(effect)) = (words.next(), Effect::of(class)) {
self.rules.push(Row {
program,
words: words.collect(),
reach: Reach::Fixed(effect),
});
}
}
"secrets" => self.secrets.push(item.to_owned()),
_ => {}
}
}
pub fn ruling(&self, effect: Effect) -> Ruling {
self.table
.iter()
.rev()
.find(|(class, _)| *class == effect)
.map_or_else(|| super::judge::Table::ruling(effect), |(_, r)| *r)
}
pub fn rows(&self) -> Vec<Row> {
self.rules
.iter()
.cloned()
.chain(DEFAULT.iter().map(|(program, words, reach)| Row {
program: (*program).to_owned(),
words: words.iter().map(|w| (*w).to_owned()).collect(),
reach: *reach,
}))
.collect()
}
pub fn secret_fragments(&self) -> Vec<String> {
SECRET_FRAGMENTS
.iter()
.map(|f| (*f).to_owned())
.chain(self.secrets.iter().cloned())
.collect()
}
}
fn split_pair(line: &str) -> (&str, &str) {
match line.split_once(':') {
Some((key, value)) => (key.trim(), value.trim()),
None => (line.trim(), ""),
}
}
fn strip_comment(line: &str) -> &str {
line.split_once('#').map_or(line, |(head, _)| head)
}
#[cfg(test)]
mod tests;