///|
/// In-memory cache of parsed Gherkin features, keyed by path.
/// Mutation is controlled — only `load_*` methods can add entries.
pub struct FeatureCache {
priv cache : Map[String, @gherkin.Feature]
}
///|
/// Create an empty feature cache.
pub fn FeatureCache::new() -> FeatureCache {
{ cache: {}, }
}
///|
/// Load a feature from a file path. Idempotent — skips if already cached.
pub fn FeatureCache::load_file(
self : FeatureCache,
path : String,
) -> Unit raise Error {
if self.cache.contains(path) {
return
}
let content = @fs.read_file_to_string(path)
// The URI lets the parser pick Markdown with Gherkin for .feature.md paths
let source = @gherkin.Source::from_string(content, uri=path)
let doc = @gherkin.parse(source)
match doc.feature {
Some(feature) => self.cache.set(path, feature)
None => ()
}
}
///|
/// Load a feature from inline text. Always overwrites existing entry.
pub fn FeatureCache::load_text(
self : FeatureCache,
path : String,
content : String,
) -> Unit raise Error {
let source = @gherkin.Source::from_string(content, uri=path)
let doc = @gherkin.parse(source)
match doc.feature {
Some(feature) => self.cache.set(path, feature)
None => ()
}
}
///|
/// Store a pre-parsed feature directly.
pub fn FeatureCache::load_parsed(
self : FeatureCache,
path : String,
feature : @gherkin.Feature,
) -> Unit {
self.cache.set(path, feature)
}
///|
/// Load a feature from any FeatureSource variant.
/// Returns an array of parse errors (empty on success).
pub fn FeatureCache::load_from_source(
self : FeatureCache,
source : FeatureSource,
) -> Array[ParseErrorInfo] {
match source {
Parsed(path, feature) => {
self.load_parsed(path, feature)
return []
}
Text(path, content) =>
try {
self.load_text(path, content)
[]
} catch {
e => parse_error_infos(path, e)
}
File(path) =>
try {
self.load_file(path)
[]
} catch {
e => parse_error_infos(path, e)
}
}
}
///|
/// Look up a cached feature by path.
pub fn FeatureCache::get(
self : FeatureCache,
path : String,
) -> @gherkin.Feature? {
self.cache.get(path)
}
///|
/// Return all cached features as (path, feature) pairs.
pub fn FeatureCache::features(
self : FeatureCache,
) -> Array[(String, @gherkin.Feature)] {
let result : Array[(String, @gherkin.Feature)] = []
self.cache.each(fn(k, v) { result.push((k, v)) })
result
}
///|
/// Check if a path is cached.
pub fn FeatureCache::contains(self : FeatureCache, path : String) -> Bool {
self.cache.contains(path)
}
///|
/// Number of cached features.
pub fn FeatureCache::length(self : FeatureCache) -> Int32 {
self.cache.length()
}
///|
/// Turn a load error into ParseErrorInfo entries. Gherkin parse errors keep
/// their message and line; a CompositeError gives one entry per inner error.
fn parse_error_infos(path : String, err : Error) -> Array[ParseErrorInfo] {
let infos : Array[ParseErrorInfo] = []
fn add(e : Error) -> Unit {
match e {
@gherkin.UnexpectedToken(message~, location~)
| @gherkin.UnexpectedEof(message~, location~)
| @gherkin.InconsistentTableCells(message~, location~) =>
infos.push({ uri: path, message, line: Some(location.line), })
@gherkin.CompositeError(errors~) =>
for inner in errors {
add(inner)
}
other =>
infos.push({ uri: path, message: other.to_string(), line: None, })
}
}
add(err)
infos
}