cmte
Version:
Design by Committee™ except it's just you and LLMs
109 lines (94 loc) • 2.75 kB
text/typescript
import { ValidationError } from './errors';
import { IterationContext } from './types';
export interface PathSegment {
name: string;
choice?: string | null;
isAll: boolean;
}
export class PathResolver {
/**
* Parse a path into segments, handling phase names, choices, and 'all' keyword
*/
static parsePath(path: string): PathSegment[] {
// Remove $ prefix if present
const cleanPath = path.startsWith('$') ? path.slice(1) : path;
const segments = cleanPath.split('.');
return segments.map(segment => {
// Handle 'all' keyword
if (segment === 'all') {
return { name: segment, choice: null, isAll: true };
}
// Check for choice syntax [...]
const choiceMatch = segment.match(/^([^[]+)(?:\[([^\]]+)\])?$/);
if (!choiceMatch) {
throw new ValidationError(`Invalid path segment: ${segment}`);
}
const [, name, choice] = choiceMatch;
return {
name,
choice: choice || null,
isAll: false
};
});
}
/**
* Resolve a path segment's choice, handling $this references
*/
static resolveChoice(
segment: PathSegment,
context: IterationContext,
objectsForIteration: Record<string, any>
): string {
if (!segment.choice) {
return '';
}
if (segment.choice === '$this') {
if (!context.currentIteration) {
throw new ValidationError('$this used outside iteration context');
}
return context.currentIteration;
}
// Validate choice exists in objectsForIteration
const iterationObject = objectsForIteration[segment.name];
if (!iterationObject?.[segment.choice]) {
throw new ValidationError(
`Invalid choice '${segment.choice}' for iteration '${segment.name}'`
);
}
return segment.choice;
}
/**
* Walk through a path to get or set a value
*/
static walkPath(
root: any,
segments: PathSegment[],
context: IterationContext,
objectsForIteration: Record<string, any>
): any {
let current = root;
for (const segment of segments) {
if (segment.isAll) {
return Object.values(current);
}
if (!current[segment.name]) {
throw new ValidationError(`Path segment '${segment.name}' not found`);
}
current = current[segment.name];
if (segment.choice) {
const resolvedChoice = this.resolveChoice(
segment,
context,
objectsForIteration
);
if (!current[resolvedChoice]) {
throw new ValidationError(
`Choice '${resolvedChoice}' not found in '${segment.name}'`
);
}
current = current[resolvedChoice];
}
}
return current;
}
}