Skip to content

Commit

Permalink
Introduce a FromfileExpander struct (pantsbuild#20811)
Browse files Browse the repository at this point in the history
As with pantsbuild#20807, this is another preliminary change to support
fromfile paths being relative to the buildroot. 

This change introduces a FromfileExpander struct, and moves
the expansion logic from free functions to struct methods. 
The free functions are then reimplemented as calls into 
these methods, to avoid perturbing calling code.

A followup change will plumb instances of this struct
wherever needed, and remove the free functions.

Another followup change will introduce the buildroot
context to this struct, to finally achieve the underlying
functionality change.

This change is just a refactoring, to make the subsequent
changes easier to grok.
  • Loading branch information
benjyw authored Apr 18, 2024
1 parent 1d47326 commit 0afdd65
Showing 1 changed file with 88 additions and 61 deletions.
149 changes: 88 additions & 61 deletions src/rust/engine/options/src/fromfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,6 @@ use std::{fs, io};
// Otherwise, the first component is None and the second is the original value.
type ExpandedValue = (Option<PathBuf>, Option<String>);

fn maybe_expand(value: String) -> Result<ExpandedValue, ParseError> {
if let Some(suffix) = value.strip_prefix('@') {
if suffix.starts_with('@') {
// @@ escapes the initial @.
Ok((None, Some(suffix.to_owned())))
} else {
match suffix.strip_prefix('?') {
Some(subsuffix) => {
// @? means the path is allowed to not exist.
let path = PathBuf::from(subsuffix);
match fs::read_to_string(&path) {
Ok(content) => Ok((Some(path), Some(content))),
Err(err) if err.kind() == io::ErrorKind::NotFound => {
warn!("Optional file config '{}' does not exist.", path.display());
Ok((Some(path), None))
}
Err(err) => Err(mk_parse_err(err, &path)),
}
}
_ => {
let path = PathBuf::from(suffix);
let content = fs::read_to_string(&path).map_err(|e| mk_parse_err(e, &path))?;
Ok((Some(path), Some(content)))
}
}
}
} else {
Ok((None, Some(value)))
}
}

pub(crate) fn expand(value: String) -> Result<Option<String>, ParseError> {
let (_, expanded_value) = maybe_expand(value)?;
Ok(expanded_value)
}

#[derive(Debug)]
enum FromfileType {
Json,
Expand Down Expand Up @@ -87,38 +51,101 @@ fn try_deserialize<'a, DE: Deserialize<'a>>(
}
}

pub(crate) fn expand_to_list<T: Parseable>(
value: String,
) -> Result<Option<Vec<ListEdit<T>>>, ParseError> {
let (path_opt, value_opt) = maybe_expand(value)?;
if let Some(value) = value_opt {
if let Some(items) = try_deserialize(&value, path_opt)? {
Ok(Some(vec![ListEdit {
action: ListEditAction::Replace,
items,
}]))
pub struct FromfileExpander {}

impl FromfileExpander {
pub fn new() -> Self {
Self {}
}

fn maybe_expand(&self, value: String) -> Result<ExpandedValue, ParseError> {
if let Some(suffix) = value.strip_prefix('@') {
if suffix.starts_with('@') {
// @@ escapes the initial @.
Ok((None, Some(suffix.to_owned())))
} else {
match suffix.strip_prefix('?') {
Some(subsuffix) => {
// @? means the path is allowed to not exist.
let path = PathBuf::from(subsuffix);
match fs::read_to_string(&path) {
Ok(content) => Ok((Some(path), Some(content))),
Err(err) if err.kind() == io::ErrorKind::NotFound => {
warn!("Optional file config '{}' does not exist.", path.display());
Ok((Some(path), None))
}
Err(err) => Err(mk_parse_err(err, &path)),
}
}
_ => {
let path = PathBuf::from(suffix);
let content =
fs::read_to_string(&path).map_err(|e| mk_parse_err(e, &path))?;
Ok((Some(path), Some(content)))
}
}
}
} else {
T::parse_list(&value).map(Some)
Ok((None, Some(value)))
}
} else {
Ok(None)
}
}

pub(crate) fn expand_to_dict(value: String) -> Result<Option<Vec<DictEdit>>, ParseError> {
let (path_opt, value_opt) = maybe_expand(value)?;
if let Some(value) = value_opt {
if let Some(items) = try_deserialize(&value, path_opt)? {
Ok(Some(vec![DictEdit {
action: DictEditAction::Replace,
items,
}]))
pub(crate) fn expand(&self, value: String) -> Result<Option<String>, ParseError> {
let (_, expanded_value) = self.maybe_expand(value)?;
Ok(expanded_value)
}

pub(crate) fn expand_to_list<T: Parseable>(
&self,
value: String,
) -> Result<Option<Vec<ListEdit<T>>>, ParseError> {
let (path_opt, value_opt) = self.maybe_expand(value)?;
if let Some(value) = value_opt {
if let Some(items) = try_deserialize(&value, path_opt)? {
Ok(Some(vec![ListEdit {
action: ListEditAction::Replace,
items,
}]))
} else {
T::parse_list(&value).map(Some)
}
} else {
parse_dict(&value).map(|x| Some(vec![x]))
Ok(None)
}
} else {
Ok(None)
}

pub(crate) fn expand_to_dict(
&self,
value: String,
) -> Result<Option<Vec<DictEdit>>, ParseError> {
let (path_opt, value_opt) = self.maybe_expand(value)?;
if let Some(value) = value_opt {
if let Some(items) = try_deserialize(&value, path_opt)? {
Ok(Some(vec![DictEdit {
action: DictEditAction::Replace,
items,
}]))
} else {
parse_dict(&value).map(|x| Some(vec![x]))
}
} else {
Ok(None)
}
}
}

pub(crate) fn expand(value: String) -> Result<Option<String>, ParseError> {
FromfileExpander::new().expand(value)
}

pub(crate) fn expand_to_list<T: Parseable>(
value: String,
) -> Result<Option<Vec<ListEdit<T>>>, ParseError> {
FromfileExpander::new().expand_to_list(value)
}

pub(crate) fn expand_to_dict(value: String) -> Result<Option<Vec<DictEdit>>, ParseError> {
FromfileExpander::new().expand_to_dict(value)
}

#[cfg(test)]
Expand Down

0 comments on commit 0afdd65

Please sign in to comment.