GEML 图解 · geml CLI · English

GEML 图解 · CLI

geml 命令行做三类事:转换一份文档(json、html、md、geml 之间),按块读写(list、find、get、set、add、delete、rename、revert),校验(check,以及 profile 各自的 style check、history verify、codemap verify)。核心动词从不带 profile 名。写操作有一道守卫:结果解析不过就拒绝写入。这一页把每个动词、它接受的地址形式、退出码,用一份小文档实跑一遍。

看板

16 条规则,各自的出处和状态

已定 geml --help 或规范写死的。实测 跑出来的行为。GEP 草案 地址形式来自草案。

已定 12实测 3GEP 草案 1
规则出处状态
通用
1文件位置可以写 - 读 stdin。写动词(set / add / delete / rename)对文件原地整写,对 - 输出到 stdout;-o f 重定向,-o - 也是 stdout。退出码 0 成功、1 文档或操作错误。--help已定
2--root d 把跨文档解析放宽到目录 d,读写动词都吃它。写入在结果解析不过时被拒绝,所以一份靠仓库根才能解析 ../x.md 的文档不带 --root 就根本不可编辑,否则守卫会把自己的盲区读成损坏。--help已定
3先调 list:它打印的地址就是其余动词接受的东西。get 无 #id 时也列出全部 id。--help已定
读
4list <file> [--json]:每个可寻址块一行:地址、kind、行范围;匿名块显示为 === type 或带内容哈希的 type@hash;块间散文以派生地址出现。--help · §4已定
5find <pattern> [file|dir …]:搜块内容,回答 file<TAB>address,是地址不是行号,所以能直接粘进 get / set。命名文件不限扩展名(.md 也搜),目录只走 *.geml。无命中 exit 1。--help已定
6get <file> [#id] [--json] [--head|--intro|--body]:标题 id = 整节;--head 只标题行,--body 标题下全部,--intro 到第一个子标题前;--json 给模型节点。选择器也可以是位置 L27 / L27-58:包含这些行的最小块,grep 命中或堆栈行号由此变成地址。--help已定
写
7set <file> #id [--head|--intro|--body] [--in f[#src]|-]:替换一块。--in F 取 F 的块 #id,F#src 取 #src,否则 stdin 原样;缺省整块,--head 只换标题行,--body 只换正文。--help已定
8守卫:写入结果重新解析,不通过就不写,exit 1,原文不动。实测:把一个未关闭的 code 块塞进 set,得到「replacement would break the document … not written」。--help · 实测已定
9add (--append | --before #id | --after #id) --in f|-:插入片段,一个或多个块和散文;内容保留自己的 id,冲突拒绝。--help已定
10delete #id [#id2…]:缺的 id 跳过;留下悬空引用只是 warning,不拒绝,提示跑 check 时它是 error。实测:删掉 #cmd 后 warning 一条、写入成功、随后 check exit 1。--help · 实测已定
11rename #old #new:改 id 和每一处引用,按 id 边界匹配(不会把 #cmd 改进 #cmdline)。实测:块和 [[#c]] 一起变成 cmd。--help · 实测已定
12replace <old> <new> [--within sel]:EXPERIMENTAL 的字面替换,受检并报告。revert <file> #id [--rev sel]:从 .gemlhistory 把一个块回到过去某版(拼回、复活或删除),sel 是 0 | -N | id 前缀 | changed,缺省 -1。--help已定
转换与校验
13geml <file> [--to json|html|md|geml] [--from geml|md|json]:默认输出文档模型 JSON;--to md 有损(标题 id 和属性丢掉,会加 note);--to html 自包含,--fragment 只给正文标记;--to geml 规范重排;--from md 把 Markdown 读成 GEML,扩展名能推断则不用写。--help · 实测已定
14check [--root d] [--json]:只校验,诊断加退出码;--json 给诊断数组。这是本图解系列每一页右侧输出的来源。--help已定
15profile 自带动词:style check(exit 0/1/2)、history save|get|restore|verify、codemap build|verify|render|serve|refresh|find;另有 mcp --root(11 个工具,每个写操作落盘前校验)、skill install。核心动词永不带 profile 名。--help · profiles README实测
16地址形式:#id、'## Heading'(整节)、L27-58(位置)、#fy[2]["Q1"] / #meta["key"](GEP-0011 坐标,本分支已实现)、#form#field(GEP-0008 草案,未实现)。--help · GEP-0011 · GEP-0008GEP 草案
读写

list → find → get → set,以及被拒的一次写入

一份七行的小文档。左边是它,右边是每一步的真实输出。

地址从 list 来,粘进 get 和 set 已定
cli.geml
=== meta
title = "cli probe"
===
# Intro {#intro}
Hello world paragraph.
=== code {#c lang=sh}
echo hi
===
## Next {#next}
See [[#c]].
set · 被守卫拒绝
$ printf '=== code {#c lang=sh}\nunterminated\n' | geml set cli.geml '#c' --in -
error: replacement would break the document: unterminated `code` block
  (no matching === or `=== #c`) (line 6); not written
exit 1 · cli.geml 一字未动
shell
$ geml list cli.geml
=== meta         meta     anon  L1-3
#intro           heading  h1    L4-11  Intro
#intro-before-c  prose          L5-5      ← 派生地址:#intro 之内、#c 之前
#c               code           L6-8
#next            heading  h2    L9-11  Next

$ geml find "echo" cli.geml
cli.geml	#c                          exit 0
$ geml find "nothing-here" cli.geml  exit 1

$ geml get cli.geml 'L6'                位置 → 最小包含块
=== code {#c lang=sh}
echo hi
===
$ geml get cli.geml '#intro' --head
# Intro {#intro}
$ geml get cli.geml '#intro' --body     标题下全部,含子节 #next
Hello world paragraph.
=== code {#c lang=sh}
echo hi
===
## Next {#next}
See [[#c]].
地址不是行号
find 回答的是地址,因为地址在下一次编辑之后仍然有效,行号不会。反过来,get 'L6' 把一个行号变成地址:包含它的最小块。
整节
标题 id 指整节:#intro 是 H1,它的节到文件尾,所以 --body 连 ## Next 一起给出。要只改标题行用 --head。
守卫
set 先把结果重新解析,坏了就不写。这是块级编辑安全的根,也是 --root 为什么对写动词也重要:解析不到的跨文档引用会让守卫误判。
改

rename 连引用一起改,add 保留 id,delete 只警告悬空

三次写入接着一次 check。

rename → add → delete → check 已定 实测
shell
$ geml rename cli.geml '#c' '#cmd'
wrote cli.geml
$ grep -n cmd cli.geml
6:=== code {#cmd lang=sh}
10:See [[#cmd]].                          ← 引用一起改了

$ printf '=== note {#n}\nadded\n===\n' | geml add cli.geml --after '#cmd' --in -
wrote cli.geml
$ geml list cli.geml | tail -4
#intro-before-cmd  prose          L5-5
#cmd               code           L6-8
#n                 note           L10-12
#next              heading  h2    L14-16  Next

$ geml delete cli.geml '#cmd'
warning: unresolved reference `#cmd` (line 12) — left dangling by delete;
  run 'geml check' to see it as an error
wrote cli.geml                            exit 0:删除照做
$ geml check cli.geml
error: unresolved reference `#cmd` (line 12)
1 error(s), 0 warning(s)                  exit 1
规则
动词对引用的态度
rename改 id 和每一处引用,id 边界安全
add片段保留自己的 id;与文档已有 id 冲突则拒绝
delete留下悬空引用是 warning 不是拒绝:删除是作者的明确意图,check 再把它当 error
set结果解析不过就不写
派生地址跟着改:#intro-before-c 变成 #intro-before-cmd
为什么 delete 不拒绝
删块是作者的明确动作;悬空引用是后果,工具说出来但不替作者决定。真正的红线在 check:exit 1。
派生地址
散文的派生地址两端都是它的邻居,邻居改名地址就变,这正是 §4 想要的:变动是响的,不会悄悄指向一段短了的散文。
转换

--to 与 --from

默认输出是文档模型 JSON;Markdown 是有损的一边。

四种输出,三种输入 已定
shell
$ geml cli.geml --to md
note: heading id/attributes dropped (Markdown has no attribute syntax)
error: unresolved reference `#cmd` (line 12)   诊断照报,输出照给
---
title: cli probe
---

# Intro

Hello world paragraph.

> added

## Next

$ printf '# T\n\nsome md\n' | geml - --from md --to geml
# T

some md
格式一览
标志得到备注
--to json文档模型默认;§8.4 一致性面的形状
--to html自包含 HTML--fragment 只给正文标记,资产走 pageAssets;本系列右侧标签由此而来
--to mdMarkdown有损:标题 id、属性丢,note 块变引用块,data 变代码块;会加 note 说明
--to geml规范重排data 的 json 重排为两空格缩进
--from md把 Markdown 读成 GEML扩展名是 .md 时自动推断;geml notes.md 直接可用
--from json把 --to json 的结果读回往返
md 读层
list / find / get 直接读 Markdown(--from md 自动推断),什么都不转换、不写;这就是 geml 技能里「按块读长 Markdown」的用法。
地址

一个块有几种叫法

所有动词共用同一套选择器。

选择器形式 已定 坐标与字段路径来自草案
写法指什么出处 · 状态
#id声明了 id 的块;标题 id 是整节§4 · 已定
'## Heading'按标题文字,同样是整节--help · 已定
L27 · L27-58包含这些行的最小块--help · 已定
#a-between-b · #c-before-n · #c-after-p两块之间散文的派生地址,list 会打印§4 · 已定
=== type · type@hash匿名块在 list 里的显示;type@hash 可粘回 get实测
#fy[2]["Q1"] · #fy[summary] · #meta["title"] · #cfg["tags"][1]表格行/格/列、汇总格、meta 值、data 值树节点GEP-0011 · 草案,本分支已实现(第 2、3 页)
#vendor#contacts#emailform 内的字段GEP-0008 · 草案,未实现(第 6 页)
other.geml#id · other.geml#fy[2]跨文档,同上任意形式§5.2 · GEP-0011
依据

探针

cli.geml 在会话 scratchpad 里跑,用仓库内当前构建,1.9.2。

命令结果对应
list · find(命中 / 未命中)· get L6 · get --head / --body如上;find 未命中 exit 1读写、看板 4–6
set 塞入未关闭块exit 1 not written,文件不动看板 8
rename · add --after · delete · check引用同改;片段插入;悬空 warning 后 check exit 1改、看板 9–11
--to md · - --from md --to geml有损 note;stdin Markdown 读成 GEML转换
geml --help · geml history · geml codemap动词与用法文本看板 1–3、12–15