///|
/// A dot-separated path that identifies one value in a configuration tree.
///
/// The first version intentionally supports only simple paths such as
/// `server.port`. Escaping dots inside key names will be added separately if
/// the project later needs it.
pub struct ConfigPath {
segments : Array[String]
} derive(Eq, Debug)
///|
/// Describes why a textual configuration path could not be parsed.
pub(all) enum ConfigPathError {
EmptyPath
EmptySegment(Int)
} derive(Eq, Debug)
///|
/// Parse a simple dot-separated configuration path.
///
/// Empty paths and empty segments are rejected, so `.server`, `server..port`,
/// and `server.` are invalid.
pub fn parse_path(source : String) -> Result[ConfigPath, ConfigPathError] {
if source.is_empty() {
return Err(EmptyPath)
}
let segments : Array[String] = []
for index, segment in source.split(".") {
if segment.is_empty() {
return Err(EmptySegment(index))
}
segments.push(String::from_iter(segment.iter()))
}
Ok({ segments, })
}
///|
/// Return the number of segments in the path.
pub fn ConfigPath::length(self : ConfigPath) -> Int {
self.segments.length()
}
///|
/// Return one segment, or `None` when the index is outside the path.
pub fn ConfigPath::segment(self : ConfigPath, index : Int) -> String? {
self.segments.get(index)
}
///|
/// Convert the path back to its canonical dot-separated representation.
pub fn ConfigPath::to_string(self : ConfigPath) -> String {
self.segments.join(".")
}
///|
/// Create an internal path from merge traversal segments.
///
/// An empty segment array represents a conflict at the configuration root.
fn config_path_from_segments(segments : Array[String]) -> ConfigPath {
{ segments: segments.copy() }
}
///|
/// Render a concise diagnostic for an invalid path.
pub fn ConfigPathError::to_string(self : ConfigPathError) -> String {
match self {
EmptyPath => "configuration path must not be empty"
EmptySegment(index) =>
"configuration path segment \{index} must not be empty"
}
}