blob: f10a7eade483169ac67e71a6e8956456d7e86af8 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::Result,
regex::{Captures, Regex},
serde_json::Value,
std::path::PathBuf,
};
pub(crate) mod cache;
pub(crate) mod config;
pub(crate) mod data;
pub(crate) mod env_var;
pub(crate) mod file_check;
pub(crate) mod filter;
pub(crate) mod flatten;
pub(crate) mod home;
pub(crate) mod runtime;
// Negative lookbehind (or lookahead for that matter) is not supported in Rust's regex.
// Instead, replace with this string - which hopefully will not be used by anyone in the
// configuration. Insert joke here about how hope is not a strategy.
const TEMP_REPLACE: &str = "#<#ffx!!replace#>#";
pub(crate) fn preprocess(value: &Value) -> Option<String> {
value.as_str().map(|s| s.to_string()).map(|s| s.replace("$$", TEMP_REPLACE))
}
pub(crate) fn postprocess(value: String) -> Value {
Value::String(value.to_string().replace(TEMP_REPLACE, "$"))
}
pub(crate) fn replace_regex<T>(value: &String, regex: &Regex, replacer: T) -> String
where
T: Fn(&str) -> Result<String>,
{
regex
.replace_all(value, |caps: &Captures<'_>| {
// Skip the first one since that'll be the whole string.
caps.iter()
.skip(1)
.map(|cap| cap.map(|c| replacer(c.as_str())))
.fold(String::new(), |acc, v| if let Some(Ok(s)) = v { acc + &s } else { acc })
})
.into_owned()
}
pub(crate) fn replace<'a, P>(regex: &'a Regex, base_path: P, value: Value) -> Option<Value>
where
P: Fn() -> Result<PathBuf> + Sync + Send + 'a,
{
preprocess(&value)
.as_ref()
.map(|s| {
replace_regex(s, regex, |v| {
match base_path() {
Ok(p) => Ok(p.to_str().map_or(v.to_string(), |s| s.to_string())),
Err(_) => Ok(v.to_string()), //just pass through
}
})
})
.map(postprocess)
.or(Some(value))
}