-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparser.js
More file actions
116 lines (99 loc) · 3.9 KB
/
Copy pathparser.js
File metadata and controls
116 lines (99 loc) · 3.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
const fs = require('fs');
const path = require('path');
const DATA_FILE = path.join(__dirname, 'data.txt');
const EMPTY_TEMPLATE = `name:
role:
theme: light
`;
/**
* Parses data.txt into a structured object.
*
* Text file format:
* name: <string>
* role: <string>
*
* - <text> standalone task
* * <text> pending step (2+ spaces + *)
* * [x] <text> completed step (2+ spaces + * [x])
*
* - [<name>] project opener
* - <text> task inside project (2+ spaces + -)
* * <text> pending step inside project task
* * [x] <text> completed step inside project task
*
* Returns:
* {
* user: { name: string, role: string },
* items: Array<Task | Project>
* }
*
* Step = { type: 'step', text: string, done: boolean }
* Task = { type: 'task', text: string, steps: Step[] }
* Project = { type: 'project', name: string, tasks: Task[] }
*/
function parse(raw) {
const lines = raw.split('\n');
const result = { user: { name: '', role: '' }, items: [] };
let currentProject = null;
let currentTask = null;
for (const line of lines) {
if (!line.trim()) continue;
// ── User fields ──────────────────────────────────────────────────────────
const nameMatch = line.match(/^name:\s*(.+)$/);
if (nameMatch) { result.user.name = nameMatch[1].trim(); continue; }
const roleMatch = line.match(/^role:\s*(.+)$/);
if (roleMatch) { result.user.role = roleMatch[1].trim(); continue; }
const themeMatch = line.match(/^theme:\s*(.+)$/);
if (themeMatch) { result.user.theme = themeMatch[1].trim(); continue; }
// ── Step: " * [x] text" (done) or " * text" (pending) ─────────────────
const stepMatch = line.match(/^(\s+)\*\s+(?:\[x\]\s+)?(.+)$/i);
if (stepMatch && currentTask) {
const done = /^\s+\*\s+\[x\]\s+/i.test(line);
currentTask.steps.push({ type: 'step', text: stepMatch[2].trim(), done });
continue;
}
// ── Project task: " - text" ──────────────────────────────────────────────
const projectTaskMatch = line.match(/^\s{2,}-\s+(?!\[)(.+)$/);
if (projectTaskMatch && currentProject) {
currentTask = { type: 'task', text: projectTaskMatch[1].trim(), steps: [] };
currentProject.tasks.push(currentTask);
continue;
}
// ── Project opener: "- [Name]" ────────────────────────────────────────────
const projectMatch = line.match(/^-\s+\[(.+)\]$/);
if (projectMatch) {
currentTask = null;
currentProject = { type: 'project', name: projectMatch[1].trim(), tasks: [] };
result.items.push(currentProject);
continue;
}
// ── Root-level task: "- text" ─────────────────────────────────────────────
const taskMatch = line.match(/^-\s+(.+)$/);
if (taskMatch) {
currentProject = null;
currentTask = { type: 'task', text: taskMatch[1].trim(), steps: [] };
result.items.push(currentTask);
continue;
}
}
return result;
}
let _cache = null;
let _mtime = null;
function load() {
if (!fs.existsSync(DATA_FILE)) {
fs.writeFileSync(DATA_FILE, EMPTY_TEMPLATE, 'utf8');
invalidate();
}
const stat = fs.statSync(DATA_FILE);
if (_cache !== null && stat.mtimeMs === _mtime) return _cache;
const raw = fs.readFileSync(DATA_FILE, 'utf8');
_cache = parse(raw);
_mtime = stat.mtimeMs;
return _cache;
}
function invalidate() {
_cache = null;
_mtime = null;
}
module.exports = { load, parse, invalidate };