mirror of
https://github.com/MeshEnvy/mesh-forge.git
synced 2026-06-18 00:54:55 +02:00
108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
export interface MeshforgeTagsConfig {
|
|
/** JS regex tested against each tag name. Tags not matching are hidden. */
|
|
include?: string
|
|
}
|
|
|
|
export interface MeshforgeTargetsConfig {
|
|
/** JS regex tested against each env name. Non-matching envs are hidden. */
|
|
include?: string
|
|
/**
|
|
* Template string that becomes a regex after substituting captures from the tag matched
|
|
* against tags.include. Supported placeholders: ${captureName}, ${captureName_snake},
|
|
* ${captureName_camel}, ${captureName_pascal}, ${1}, ${2}, …
|
|
* Each substituted segment is regex-escaped before insertion.
|
|
* When the current tag matches tags.include and this field is set, the expanded pattern
|
|
* replaces targets.include for filtering. Falls back to targets.include when the tag
|
|
* does not match or this field is absent.
|
|
*/
|
|
include_template?: string
|
|
/** AND-filter: every listed capability must be present on the env (e.g. ["wifi"]). */
|
|
require_capabilities?: string[]
|
|
}
|
|
|
|
export interface MeshforgeConfig {
|
|
tags?: MeshforgeTagsConfig
|
|
targets?: MeshforgeTargetsConfig
|
|
}
|
|
|
|
/**
|
|
* Minimal parser for the meshforge.yaml format. Only the known schema is handled;
|
|
* unknown keys are silently ignored.
|
|
*
|
|
* Supports:
|
|
* - 2-space YAML-like indentation (0 / 2 / 4 spaces)
|
|
* - Quoted ("…" or '…') and unquoted scalar values
|
|
* - Inline flow lists [a, b, c]
|
|
* - Line comments starting with #
|
|
*/
|
|
export function parseMeshforgeYaml(raw: string): MeshforgeConfig | null {
|
|
const config: MeshforgeConfig = {}
|
|
let inMeshforge = false
|
|
let section: 'tags' | 'targets' | null = null
|
|
|
|
for (const rawLine of raw.split(/\r?\n/)) {
|
|
const stripped = rawLine.replace(/#.*$/, '').trimEnd()
|
|
if (!stripped.trim()) continue
|
|
|
|
const indent = stripped.length - stripped.trimStart().length
|
|
const content = stripped.trimStart()
|
|
|
|
if (indent === 0) {
|
|
inMeshforge = content === 'meshforge:'
|
|
section = null
|
|
continue
|
|
}
|
|
|
|
if (!inMeshforge) continue
|
|
|
|
if (indent === 2) {
|
|
if (content === 'tags:') {
|
|
section = 'tags'
|
|
if (!config.tags) config.tags = {}
|
|
} else if (content === 'targets:') {
|
|
section = 'targets'
|
|
if (!config.targets) config.targets = {}
|
|
} else {
|
|
section = null
|
|
}
|
|
continue
|
|
}
|
|
|
|
if (indent === 4 && section) {
|
|
const kv = content.match(/^(\w+):\s*(.*)$/)
|
|
if (!kv) continue
|
|
const [, key, rawVal] = kv
|
|
if (section === 'tags') {
|
|
if (key === 'include') config.tags!.include = parseScalar(rawVal)
|
|
} else if (section === 'targets') {
|
|
if (key === 'include') config.targets!.include = parseScalar(rawVal)
|
|
else if (key === 'include_template') config.targets!.include_template = parseScalar(rawVal)
|
|
else if (key === 'require_capabilities') config.targets!.require_capabilities = parseInlineList(rawVal)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!config.tags && !config.targets) return null
|
|
return config
|
|
}
|
|
|
|
function parseScalar(raw: string): string {
|
|
const s = raw.trim()
|
|
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
|
return s.slice(1, -1)
|
|
}
|
|
return s
|
|
}
|
|
|
|
function parseInlineList(raw: string): string[] {
|
|
const s = raw.trim()
|
|
if (s.startsWith('[') && s.endsWith(']')) {
|
|
return s
|
|
.slice(1, -1)
|
|
.split(',')
|
|
.map(p => parseScalar(p.trim()))
|
|
.filter(Boolean)
|
|
}
|
|
return s ? [parseScalar(s)] : []
|
|
}
|