Skip to content
Snippets Groups Projects
Commit 865ae135 authored by Ian Jackson's avatar Ian Jackson
Browse files

tor-config: Support functions for tracking ignored config keys

This turns out to need quite a complicated algorithm.
parent 9e526aad
No related branches found
No related tags found
No related merge requests found
......@@ -2794,6 +2794,15 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_ignored"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1940036ca2411651a40012009d062087dfe62817b2191a03750fb569e11fa633"
dependencies = [
"serde",
]
[[package]]
name = "serde_json"
version = "1.0.81"
......@@ -3423,11 +3432,13 @@ dependencies = [
"dirs",
"educe",
"fs-mistrust",
"itertools",
"once_cell",
"paste",
"regex",
"rmp-serde",
"serde",
"serde_ignored",
"serde_json",
"shellexpand-fork",
"tempfile",
......
......@@ -24,10 +24,12 @@ config = { version = "0.13", default-features = false, features = ["toml"] }
derive_builder = { version = "0.11.2", package = "derive_builder_fork_arti" }
educe = "0.4.6"
fs-mistrust = { path = "../fs-mistrust", version = "0.1.0" }
itertools = "0.10.1"
once_cell = "1"
paste = "1"
regex = { version = "1", default-features = false, features = ["std"] }
serde = { version = "1.0.103", features = ["derive"] }
serde_ignored = "0.1.3"
shellexpand = { version = "2.1", package = "shellexpand-fork", optional = true }
toml = "0.5"
tracing = "0.1.18"
......
//! Processing a config::Config into a validated configuration
#![allow(dead_code)] // Will go away in a moment
use std::collections::BTreeSet;
use std::fmt::{self, Display};
use std::iter;
use itertools::{chain, izip, Itertools};
use serde::de::DeserializeOwned;
use thiserror::Error;
......@@ -92,6 +99,24 @@ define_for_tuples! { A - B C D E }
/// You don't want to try to obtain one.
pub struct ResolveContext(config::Config);
/// Key in config file(s) ignored by all Resolvables we obtained
///
/// `Display`s in an approximation to TOML format.
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct IgnoredKey {
/// Can be empty only before returned from this module
path: Vec<PathEntry>,
}
/// Element of an IgnoredKey
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
enum PathEntry {
///
ArrayIndex(usize),
/// string value is unquoted, needs quoting for display
MapEntry(String),
}
/// Deserialize and build overall configuration from config sources
pub fn resolve<T>(input: config::Config) -> Result<T, ConfigResolveError>
where
......@@ -112,3 +137,146 @@ where
Ok(built)
}
}
/// Turns a [`serde_ignored::Path`] (which is borrowed) into an owned `IgnoredKey`
fn copy_path(mut path: &serde_ignored::Path) -> IgnoredKey {
use serde_ignored::Path as SiP;
use PathEntry as PE;
let mut descend = vec![];
loop {
let ent;
(path, ent) = match path {
SiP::Root => break,
SiP::Seq { parent, index } => (parent, Some(PE::ArrayIndex(*index))),
SiP::Map { parent, key } => (parent, Some(PE::MapEntry(key.clone()))),
SiP::Some { parent }
| SiP::NewtypeStruct { parent }
| SiP::NewtypeVariant { parent } => (parent, None),
};
descend.extend(ent);
}
descend.reverse();
IgnoredKey { path: descend }
}
/// Computes the intersection, resolving ignorances at different depths
///
/// Eg if `a` contains `application.wombat` and `b` contains `application`,
/// we need to return `application.wombat`.
fn intersect_ignored_lists(
al: BTreeSet<IgnoredKey>,
bl: BTreeSet<IgnoredKey>,
) -> BTreeSet<IgnoredKey> {
//eprintln!("INTERSECT:");
//for ai in &al { eprintln!("A: {}", ai); }
//for bi in &bl { eprintln!("B: {}", bi); }
// This function is written to never talk about "a" and "b".
// That (i) avoids duplication of code for handling a<b vs a>b, etc.
// (ii) make impossible bugs where a was written but b was intended, etc.
// The price is that the result is iterator combinator soup.
let mut inputs: [_; 2] = [al, bl].map(|input| input.into_iter().peekable());
let mut output = BTreeSet::new();
// The BTreeSets produce items in sort order. Peek one from each, while we can.
while let Ok(items) = {
// Ideally we would use array::try_map but it's nightly-only
<[_; 2]>::try_from(
inputs
.iter_mut()
.map(|input: &'_ mut _| input.peek())
.into_iter()
.flatten()
.collect::<Vec<_>>(),
)
} {
let prefix_len = items.iter().map(|i| i.path.len()).min().expect("wrong #");
let earlier_i = items
.iter()
.enumerate()
.min_by_key(|&(_i, item)| *item)
.expect("wrong #")
.0;
let later_i = 1 - earlier_i;
if items.iter().all_equal() {
// Take the identical items off the front of both iters,
// and put one into the output (the last will do nicely).
//dbg!(items);
let item = inputs
.iter_mut()
.map(|input| input.next().expect("but peekedr"))
.last()
.expect("wrong #");
output.insert(item);
continue;
} else if items
.iter()
.map(|item| &item.path[0..prefix_len])
.all_equal()
{
// They both have the same prefix. earlier_i is the shorter one.
let shorter_item = items[earlier_i];
let prefix = shorter_item.path.clone(); // borrowck can't prove disjointness
// Keep copying items from the side with the longer entries,
// so long as they fall within (have the prefix of) the shorter entry.
//dbg!(items, shorter_item, &prefix);
while let Some(longer_item) = inputs[later_i].peek() {
if !longer_item.path.starts_with(&prefix) {
break;
}
let longer_item = inputs[later_i].next().expect("but peeked");
output.insert(longer_item);
}
// We've "used up" the shorter item.
let _ = inputs[earlier_i].next().expect("but peeked");
} else {
// The items are just different. Eat the earlier one.
//dbg!(items, earlier_i);
let _ = inputs[earlier_i].next().expect("but peeked");
}
}
//for oi in &ol { eprintln!("O: {}", oi); }
output
}
impl Display for IgnoredKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use PathEntry as PE;
if self.path.is_empty() {
// shouldn't happen with calls outside this module, and shouldn't be used inside
// but handle it anyway
write!(f, r#""""#)?;
} else {
let delims = chain!(iter::once(""), iter::repeat("."));
for (delim, ent) in izip!(delims, self.path.iter()) {
match ent {
PE::ArrayIndex(index) => write!(f, "[{}]", index)?,
PE::MapEntry(s) => {
if ok_unquoted(s) {
write!(f, "{}{}", delim, s)?;
} else {
write!(f, "{}{:?}", delim, s)?;
}
}
}
}
}
Ok(())
}
}
/// Would `s` be OK to use unquoted as a key in a TOML file?
fn ok_unquoted(s: &str) -> bool {
let mut chars = s.chars();
if let Some(c) = chars.next() {
c.is_ascii_alphanumeric()
&& chars.all(|c| c == '_' || c == '-' || c.is_ascii_alphanumeric())
} else {
false
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment