mrmd-project 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +335 -0
- package/package.json +46 -0
- package/spec.md +1056 -0
- package/src/assets.js +142 -0
- package/src/fsml.js +371 -0
- package/src/index.js +65 -0
- package/src/links.js +198 -0
- package/src/project.js +293 -0
- package/src/scaffold.js +136 -0
- package/src/search.js +177 -0
package/README.md
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# mrmd-project
|
|
2
|
+
|
|
3
|
+
Pure logic for understanding mrmd project structure and conventions.
|
|
4
|
+
|
|
5
|
+
This package provides zero-side-effect functions for parsing project configuration, building navigation trees, resolving internal links, and managing asset paths in [mrmd](https://github.com/anthropics/mrmd) markdown notebook projects.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install mrmd-project
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Zero I/O** - All functions are pure; no file system access
|
|
16
|
+
- **Project Config** - Parse `mrmd.md` files with `yaml config` blocks
|
|
17
|
+
- **FSML** - Filesystem Markup Language for navigation trees
|
|
18
|
+
- **Internal Links** - Parse and resolve `[[wiki-style]]` links
|
|
19
|
+
- **Asset Paths** - Compute relative paths between documents and assets
|
|
20
|
+
- **Search** - Fuzzy matching for file picker functionality
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import { Project, FSML, Links, Assets, Scaffold, Search } from 'mrmd-project';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Project Configuration
|
|
29
|
+
|
|
30
|
+
Parse project config from `mrmd.md` files:
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
const mrmdContent = `# My Project
|
|
34
|
+
|
|
35
|
+
\`\`\`yaml config
|
|
36
|
+
name: "My Thesis"
|
|
37
|
+
session:
|
|
38
|
+
python:
|
|
39
|
+
venv: ".venv"
|
|
40
|
+
\`\`\`
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const config = Project.parseConfig(mrmdContent);
|
|
44
|
+
// { name: 'My Thesis', session: { python: { venv: '.venv' } } }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Parse document frontmatter:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const docContent = `---
|
|
51
|
+
title: "Chapter 1"
|
|
52
|
+
session:
|
|
53
|
+
python:
|
|
54
|
+
name: "gpu-session"
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
# Content here
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const frontmatter = Project.parseFrontmatter(docContent);
|
|
61
|
+
// { title: 'Chapter 1', session: { python: { name: 'gpu-session' } } }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Merge configs (document overrides project):
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const merged = Project.mergeConfig(projectConfig, frontmatter);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Resolve session configuration:
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
const session = Project.resolveSession(
|
|
74
|
+
'/home/user/thesis/chapter.md', // document path
|
|
75
|
+
'/home/user/thesis', // project root
|
|
76
|
+
merged // merged config
|
|
77
|
+
);
|
|
78
|
+
// {
|
|
79
|
+
// name: 'my-thesis:gpu-session',
|
|
80
|
+
// venv: '/home/user/thesis/.venv',
|
|
81
|
+
// cwd: '/home/user/thesis',
|
|
82
|
+
// autoStart: true
|
|
83
|
+
// }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Find project root:
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const root = Project.findRoot(
|
|
90
|
+
'/home/user/thesis/chapter/doc.md',
|
|
91
|
+
(path) => fs.existsSync(path + '/mrmd.md')
|
|
92
|
+
);
|
|
93
|
+
// '/home/user/thesis'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### FSML (Filesystem Markup Language)
|
|
97
|
+
|
|
98
|
+
Parse paths into components:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
const parsed = FSML.parsePath('02-getting-started/01-installation.md');
|
|
102
|
+
// {
|
|
103
|
+
// path: '02-getting-started/01-installation.md',
|
|
104
|
+
// order: 1,
|
|
105
|
+
// name: 'installation',
|
|
106
|
+
// title: 'Installation',
|
|
107
|
+
// extension: '.md',
|
|
108
|
+
// isFolder: false,
|
|
109
|
+
// isHidden: false,
|
|
110
|
+
// isSystem: false,
|
|
111
|
+
// depth: 1,
|
|
112
|
+
// parent: '02-getting-started'
|
|
113
|
+
// }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Sort paths by FSML rules:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const sorted = FSML.sortPaths([
|
|
120
|
+
'appendix.md',
|
|
121
|
+
'03-results.md',
|
|
122
|
+
'01-intro.md',
|
|
123
|
+
'02-methods.md',
|
|
124
|
+
]);
|
|
125
|
+
// ['01-intro.md', '02-methods.md', '03-results.md', 'appendix.md']
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Build navigation tree:
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
const tree = FSML.buildNavTree([
|
|
132
|
+
'01-intro.md',
|
|
133
|
+
'02-getting-started/index.md',
|
|
134
|
+
'02-getting-started/01-install.md',
|
|
135
|
+
'02-getting-started/02-config.md',
|
|
136
|
+
]);
|
|
137
|
+
// [
|
|
138
|
+
// { path: '01-intro.md', title: 'Intro', isFolder: false, children: [] },
|
|
139
|
+
// { path: '02-getting-started', title: 'Getting Started', isFolder: true, hasIndex: true, children: [...] }
|
|
140
|
+
// ]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Derive titles from filenames:
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
FSML.titleFromFilename('01-getting-started.md'); // 'Getting Started'
|
|
147
|
+
FSML.titleFromFilename('my_cool_doc.md'); // 'My Cool Doc'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Internal Links
|
|
151
|
+
|
|
152
|
+
Parse wiki-style links:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
const links = Links.parse('See [[installation]] and [[config#advanced|advanced config]].');
|
|
156
|
+
// [
|
|
157
|
+
// { raw: '[[installation]]', target: 'installation', anchor: null, display: null, ... },
|
|
158
|
+
// { raw: '[[config#advanced|advanced config]]', target: 'config', anchor: 'advanced', display: 'advanced config', ... }
|
|
159
|
+
// ]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Resolve links to files:
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
const files = ['01-intro.md', '02-getting-started/01-installation.md'];
|
|
166
|
+
const resolved = Links.resolve('installation', '01-intro.md', files);
|
|
167
|
+
// '02-getting-started/01-installation.md'
|
|
168
|
+
|
|
169
|
+
// Special links
|
|
170
|
+
Links.resolve('next', '01-intro.md', files); // next document in order
|
|
171
|
+
Links.resolve('prev', '02-methods.md', files); // previous document
|
|
172
|
+
Links.resolve('home', 'any.md', files); // first document
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Refactor links when files move:
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
const updated = Links.refactor(
|
|
179
|
+
'See [[old-name]] for details.',
|
|
180
|
+
[{ from: 'old-name.md', to: 'new-name.md' }],
|
|
181
|
+
'index.md'
|
|
182
|
+
);
|
|
183
|
+
// 'See [[new-name]] for details.'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Asset Paths
|
|
187
|
+
|
|
188
|
+
Compute relative paths from documents to assets:
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
Assets.computeRelativePath('01-intro.md', '_assets/img.png');
|
|
192
|
+
// '_assets/img.png'
|
|
193
|
+
|
|
194
|
+
Assets.computeRelativePath('02-section/01-doc.md', '_assets/img.png');
|
|
195
|
+
// '../_assets/img.png'
|
|
196
|
+
|
|
197
|
+
Assets.computeRelativePath('02-section/sub/deep/doc.md', '_assets/img.png');
|
|
198
|
+
// '../../../_assets/img.png'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Refactor paths when documents move:
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
const content = '';
|
|
205
|
+
const updated = Assets.refactorPaths(
|
|
206
|
+
content,
|
|
207
|
+
'01-intro.md', // old location
|
|
208
|
+
'02-section/01-intro.md', // new location
|
|
209
|
+
'_assets'
|
|
210
|
+
);
|
|
211
|
+
// ''
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Extract asset references:
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
const refs = Assets.extractPaths('');
|
|
218
|
+
// [{ path: '../_assets/img.png', type: 'image', start: 6, end: 25 }]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Scaffolding
|
|
222
|
+
|
|
223
|
+
Generate new project scaffold:
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
const scaffold = Scaffold.project('my-research');
|
|
227
|
+
// {
|
|
228
|
+
// files: [
|
|
229
|
+
// { path: 'mrmd.md', content: '...' },
|
|
230
|
+
// { path: '01-index.md', content: '...' },
|
|
231
|
+
// { path: '_assets/.gitkeep', content: '' }
|
|
232
|
+
// ],
|
|
233
|
+
// venvPath: '.venv'
|
|
234
|
+
// }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Generate standalone file frontmatter:
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
const frontmatter = Scaffold.standaloneFrontmatter({
|
|
241
|
+
venv: '/home/user/.venv',
|
|
242
|
+
cwd: '/home/user/work',
|
|
243
|
+
title: 'Quick Analysis'
|
|
244
|
+
});
|
|
245
|
+
// '---\ntitle: "Quick Analysis"\nsession:\n python:\n venv: "/home/user/.venv"\n cwd: "/home/user/work"\n---\n'
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Search
|
|
249
|
+
|
|
250
|
+
Fuzzy match strings:
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
const result = Search.fuzzyMatch('instal', 'installation');
|
|
254
|
+
// { score: 15, matches: [0, 1, 2, 3, 4, 5] }
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Search files by path:
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
const results = Search.files('thesis readme', [
|
|
261
|
+
'/home/user/thesis/README.md',
|
|
262
|
+
'/home/user/other/README.md',
|
|
263
|
+
]);
|
|
264
|
+
// Ranked by match quality, thesis/README.md first
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## FSML Conventions
|
|
268
|
+
|
|
269
|
+
FSML (Filesystem Markup Language) treats the filesystem as markup:
|
|
270
|
+
|
|
271
|
+
| Pattern | Example | Meaning |
|
|
272
|
+
|---------|---------|---------|
|
|
273
|
+
| `NN-name` | `01-intro.md` | Ordered item (position 1) |
|
|
274
|
+
| No prefix | `appendix.md` | Unordered (sorted alphabetically after numbered) |
|
|
275
|
+
| `_folder/` | `_assets/` | Hidden from navigation (author-only) |
|
|
276
|
+
| `.folder/` | `.git/` | System folder (ignored) |
|
|
277
|
+
| `index.md` | `02-section/index.md` | Section landing page |
|
|
278
|
+
| `mrmd.md` | Root `mrmd.md` | Project configuration file |
|
|
279
|
+
|
|
280
|
+
## API Reference
|
|
281
|
+
|
|
282
|
+
### Project Module
|
|
283
|
+
|
|
284
|
+
| Function | Description |
|
|
285
|
+
|----------|-------------|
|
|
286
|
+
| `parseConfig(content)` | Extract and merge `yaml config` blocks |
|
|
287
|
+
| `parseFrontmatter(content)` | Extract YAML frontmatter |
|
|
288
|
+
| `mergeConfig(project, doc)` | Deep merge configs (doc wins) |
|
|
289
|
+
| `findRoot(path, hasFile)` | Walk up to find project root |
|
|
290
|
+
| `resolveSession(doc, root, config)` | Compute session with absolute paths |
|
|
291
|
+
| `getDefaults()` | Get default configuration values |
|
|
292
|
+
|
|
293
|
+
### FSML Module
|
|
294
|
+
|
|
295
|
+
| Function | Description |
|
|
296
|
+
|----------|-------------|
|
|
297
|
+
| `parsePath(path)` | Parse path into FSML components |
|
|
298
|
+
| `sortPaths(paths)` | Sort by FSML rules |
|
|
299
|
+
| `buildNavTree(paths)` | Build nested navigation structure |
|
|
300
|
+
| `titleFromFilename(name)` | Derive human-readable title |
|
|
301
|
+
| `computeNewPath(src, target, pos)` | Compute path for reordering |
|
|
302
|
+
|
|
303
|
+
### Links Module
|
|
304
|
+
|
|
305
|
+
| Function | Description |
|
|
306
|
+
|----------|-------------|
|
|
307
|
+
| `parse(content)` | Extract all `[[links]]` |
|
|
308
|
+
| `resolve(target, from, files)` | Resolve link to file path |
|
|
309
|
+
| `refactor(content, moves, doc)` | Update links after file moves |
|
|
310
|
+
|
|
311
|
+
### Assets Module
|
|
312
|
+
|
|
313
|
+
| Function | Description |
|
|
314
|
+
|----------|-------------|
|
|
315
|
+
| `computeRelativePath(doc, asset)` | Get relative path to asset |
|
|
316
|
+
| `refactorPaths(content, old, new, dir)` | Update paths after doc moves |
|
|
317
|
+
| `extractPaths(content)` | Find all asset references |
|
|
318
|
+
|
|
319
|
+
### Scaffold Module
|
|
320
|
+
|
|
321
|
+
| Function | Description |
|
|
322
|
+
|----------|-------------|
|
|
323
|
+
| `project(name)` | Generate project scaffold |
|
|
324
|
+
| `standaloneFrontmatter(config)` | Generate standalone frontmatter |
|
|
325
|
+
|
|
326
|
+
### Search Module
|
|
327
|
+
|
|
328
|
+
| Function | Description |
|
|
329
|
+
|----------|-------------|
|
|
330
|
+
| `fuzzyMatch(query, target)` | Fuzzy match with scoring |
|
|
331
|
+
| `files(query, paths)` | Search and rank file paths |
|
|
332
|
+
|
|
333
|
+
## License
|
|
334
|
+
|
|
335
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mrmd-project",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pure logic for understanding mrmd project structure and conventions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"spec.md",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test test/*.test.js"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mrmd",
|
|
20
|
+
"markdown",
|
|
21
|
+
"project",
|
|
22
|
+
"fsml",
|
|
23
|
+
"session",
|
|
24
|
+
"configuration",
|
|
25
|
+
"notebook",
|
|
26
|
+
"wiki-links",
|
|
27
|
+
"navigation"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/anthropics/mrmd.git",
|
|
33
|
+
"directory": "packages/mrmd-project"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/anthropics/mrmd/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/anthropics/mrmd/tree/main/packages/mrmd-project#readme",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"yaml": "^2.3.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {}
|
|
46
|
+
}
|