rime / plum

東風破 /plum/: Rime configuration manager and input schema repository
GNU Lesser General Public License v3.0
1.35k stars 139 forks source link

設計配方定義文件的格式 #4

Open lotem opened 6 years ago

lotem commented 6 years ago
  1. 「配方」用於定義一組配置及其他數據文件,指定其如何使用。

  2. 配方定義文件的格式有以下選擇:

無論哪種格式,都允許在代碼庫缺失配方定義文件的情況下,自動推導出默認的配方定義。 在 Windows 平臺 bash 不可用的情況下,只能忽略配方定義文件,做默認安裝。

  1. 在配方裏定義安裝動作,有以下選擇:

無論哪種形式,都支持在未定義安裝動作的情況下,執行默認的安裝動作。 目前已實現的默認安裝動作爲:複製代碼庫中的 *.yaml, *.txt, opencc/*.{json,ocd,txt} 文件。

  1. 一份代碼庫可以提供多份配方。 安裝時指定配方 user/repo:recipe 或安裝代碼庫默認的配方 user/repo

  2. 參數化配方,允許在安裝時定義一組變量以改變(具體化)安裝動作。 例如爲某項用補靪實現的定製動作設定目標輸入方案。

若選用 bash 安裝腳本,則實現參數化比較容易; 若選用 YAML 格式的補靪,則需要模板機制。

nameoverflow commented 6 years ago

听起来不错, 但是似乎破坏了现在的方案机制的简洁性,方案本身和包管理器耦合起来了。 感觉如果在 /plum/ 中对配方定义做集中管理可以减轻对方案制定的负担,就像 homebrew 做的一样。

lotem commented 6 years ago

我還考慮過一個加強版的無配方文件的簡易實現: 規範 *.custom.yaml 的格式,安裝時合併 *.custom.yaml 的內容,而非簡單地複製文件。 因爲有賴於打補靪實現的配方包之間,很容易發生同名補靪文件相互覆蓋的錯誤。

合併 *.custom.yaml 文件的具體方法是跳過文件開頭的註釋行,讀到 ^patch:$ 之後,把剩餘的縮進代碼追加到已有的補靪文件。如果考慮到 patch: 以外會有其他的一級節點,則需要一點點腳本功夫。 這些編輯操作用 bash, sed, awk 可以完成。Windows 的做法需要研究。

或者不用文本操作實現。升級一下 rime_patch 程序,妥善地合併 YAML 補靪文件。只不過這樣會依賴於 librime-tools

Node.js 也可以用來執行文本編輯操作;調 YAML 組件需要研究除了 exe 以外得安裝多大的 node_modules。

lotem commented 6 years ago

@nameoverflow 集中管理有他的好處,查詢下載都方便;也有潛在問題,就是不夠自由。一些用家可能只想整理歸納自用的方案和配置。 還有就是那個中心的維護成本、用戶更新代碼再提交 PR 更新配方的不便,幾乎超出了 Rime 用戶羣的承受力(homebrew 軟件包的維護者是與 Rime 開發者同級別的,更付得起辛苦成本)。

我認爲沒有中心,才能實現用戶僅通過維護一份配方列表實現終極配置。 去中心化輔以在「中心」以 user/repo 登記資源,就安穩了。

LEOYoon-Tsaw commented 6 years ago

用bash吧,畢竟更靈活,要能自動改名,例如提供一個xxx.schema.custom.yaml,然後根據提供的參數來確定xxx

lotem commented 6 years ago

草稿:(大體實現 https://github.com/rime-aca/OpenCC_Emoji ,加入了演示一些其他功能的代碼)

# encoding: utf-8
---
recipe:
  Rx: emoji-suggestion
  args:
    - schema=luna_pinyin
    - opt_extra_argument
  description: >-
    Show emoji suggestion with OpenCC
  # excludes:
  # includes:
  install_files: >-
    *.schema.yaml
    *.dict.yaml
    *.txt
    opencc/*.*
  patch_files:
    - recipe.yaml: ${schema:-luna_pinyin}.custom.yaml
    - default.custom.yaml

patch:
  switches/+:
    - name: emoji_suggestion
      reset: 1
      states: [ "🈚️️\uFE0E", "🈶️️\uFE0F" ]
  engine/filters/+:
    - simplifier@emoji_suggestion
  emoji_suggestion:
    opencc_config: emoji.json
    option_name: emoji_suggestion
    tips: all
...

--- | # this is how to apply patch to multiple files
recipe:
  patch_files:
    - recipe.yaml: luna_pinyin.custom.yaml
    - recipe.yaml: luna_pinyin_fluency.custom.yaml
    - recipe.yaml: luna_quanpin.custom.yaml
...

① 一些解釋:

不想讓配方作者具有執行任意腳本的能力(儘管用 bash 實現解析仍會有漏洞可鑽)。 所以配方文件仍是基於 YAML 語法。 但是爲了 bash 腳本實現方便,會做若干格式限定。

基於 bash 的實現,不會解析 YAML,肯定是當文本文件做字符串解析的。因此規定

  1. 配方文件不支持配置編譯器語法(__include, __patch)等
  2. recipe 下面的 key 嚴格按順序寫
  3. recipe/args 其實是變量賦值(默認值),會用 bash 求值
  4. recipe/install_files 包含所有要複製的文件名或 glob 通配文件名,可以寫成一個字符串(可理解爲 ls 的參數)也可以是 block style YAML 列表,省略該字段則執行默認安裝 recipe/install 或者叫這個名字?
  5. recipe/patch_files 合併補靪文件,格式爲 block style 列表或者有序映射表,每項將一個 YAML 源碼文件的 patch 節點合併到目標文件(如爲列表格式,則爲用戶文件夾的同名文件),目標文件可以使用變量替換語法,以支持參數化補靪 recipe/customize 曾考慮用這個名字,但會破壞 key 的字母表順序
  6. patch 節點,就是待合併的補靪,也可以寫在單獨的文件裏,如果配方只打個補靪,和 recipe 寫到一個文件裏比較方便
    1. 合併到已有的補靪文件,只複製 patch 之下的縮進行,注意 patch 同級的其他節點如本例的 recipe 不會合併過去

② 再談 recipe 的用法

安裝口令語法爲

bash rime-install emoji-suggestion#schema=luna_pinyin

其中 # 之後的逗號分隔的賦值語句定義配方參數(bash 的賦值語句用分號分隔,但分號用在一個命令行參數中間需要加引號,容易出錯)

這裏省略了配方名 recipe,如果寫全,會是

bash rime-install emoji-suggestion:recipe#schema=luna_pinyin

③ 如果一個包提供多個配方文件,文件名反映配方的用途,會有(配方文件、口令):

# rime/rime-luna-pinyin/enable.yaml
Rx luna-pinyin:enable
# rime/rime-util/enable_schema.yaml
Rx util:enable_schema#schema=luna_pinyin
# rime/rime-util/select_schema.yaml
Rx util:select_schema#schema=luna_pinyin

④ 上文的配方文件樣例是爲了明確地定義參數及補靪的打法。如果不寫配方文件,還可以怎樣實現呢?

對於不需要參數化的補靪,可以直接根據文件名 *.custom.yaml 識別補靪。 參數化補靪,我想可以約定前綴 arg_ 加上表示目標方案(或配置文件)的變量,比如本例可將補靪文件命名爲 arg_schema.custom.yaml 前提是安裝命令中必須指定 schema 參數。也就是說沒法提供默認值。

⑤ 還有一種實現比較快,但設計比較粗糙的實現:

# encoding: utf-8
# Show emoji suggestion with OpenCC
# Rx: emoji-suggestion
#$ target=${schema:-luna_pinyin}.custom.yaml
patch:
   # ...

這就是待合併的補靪文件,除了以上討論的格式要求之外,特殊註釋行 #$ 將作爲 bash 命令執行,然後通過 target 變量求得目標補靪文件。schema 是可能由安裝命令定義的變量。 實現略簡單,但比較容易出安全漏洞。如果不用 bash 實現,解析反而不如直接寫進 YAML 方便。

lotem commented 6 years ago

就 opencc emoji 這個例子來看,很可能需要一個專門打補靪用的配方,因爲需要給每個輸入方案都打上補靪,於是要分多次調用,每次給不同的 schema 參數。而複製 opencc/*.* 文件只需要一次。

所用配方大概是這樣的(Rx 代表給 rime-install 命令的配方參數):

Rx emoji-suggestion  # 只安裝文件(emoji-suggestion:opencc ?)
Rx emoji-suggestion:patch#schema=luna_pinyin
Rx emoji-suggestion:patch#schema=cangjie5
...
LEOYoon-Tsaw commented 6 years ago

有幾個問題:

  1. 如果複製文件時,發現重名文件怎麼辦,最優的做法可能是報警告,繼續執行,並不覆寫?同時bash指令允許用戶自行用argument指定覆寫行爲
  2. recipe/install_files下是如何指定安裝路徑的?逐個匹配文件名,符合要求則放入相當位置/還是在repo裏面就要按照目標路徑放置文件?
  3. patch下的內容如果與recipe/patch_files衝突,優先級?可以考慮報警告,中止執行,同時允許用戶自行用argument指定覆寫行爲
  4. 能否根據argument內容選擇性安裝文件/patch?
lotem commented 6 years ago
  1. 這個現在還做不到。因爲每個包都是直接安裝到用戶文件夾,情況可能是覆蓋了來自另一個包的文件,也可能是升級安裝時覆蓋上一個版本。將來如果所有配置都用配方生成,每次在一個空文件夾開始逐個安裝配方,那就可以確定發生了第一種情況從而報警。

  2. 是的,現在已經配好的配方都與用戶文件夾格局一致。opencc/ 也長一樣的。

  3. 你說的衝突是指出現重複的 key 吧?如果只用 bash 和 linux 小工具做字符串處理,按 key 覆寫也很吃力。比如用不同的引號括住相同的 key,也會發生 YAML 語法錯誤。另外邏輯錯誤也會因補靪代碼相互影響而發生。這是沒法防範的。所以總的原則還是用戶自己嘗試搭配。剛開始的實現,就按我先時描述的,可能只合併補靪代碼行。不檢查衝突。 不過你說的也對,確實有有意覆蓋補靪內容的場景,比如對某個已應用的補靪的微調。 librime/tools/rime_patch 這個原來是爲 rimekit 製作的小工具,用來根據命令行參數生成補靪。一個完善的實現肯定還要用這種帶 YAML 庫的可執行程序。配方對補靪文件的修改可以看成對 *.custom.yaml 文件內容應用一個來自配方的補靪。

lotem commented 6 years ago

先找人用 bash 腳本實現一個簡單的用用看。 想要完善一些的話,我感覺 bash 很快就會不夠用了。 再考慮到多數 Windows 用戶不具有 bash,用嚴肅代碼實現配置管理器將有必要。

lotem commented 6 years ago

稍稍細化 patch_files 的語法

patch_files:
  文件名甲:
    - 補靪一
    - 補靪二
  文件名乙:
    - 補靪三

補靪行及其下縮進行原樣(也考慮做字符串模板展開)追加到目標文件根節點的 __patch 節點下(若無 __patch 節點則添加)。腳本實現會非常簡單,且靈活度高。 並約定:一律寫作以 - 開頭的補靪列表,以避免出現應用多份補靪後出現 YAML key 重複的錯誤。

示例:

# emoji_suggestion.recipe.yaml
# encoding: utf-8

---
recipe:
  Rx: emoji_suggestion
  args:
    - schema=luna_pinyin
  description: >-
    Customize input schema to show emoji suggestion with OpenCC

patch_files:
  ${schema:-luna_pinyin}.custom.yaml:
    - patch/+:
        __include: emoji_suggestion.recipe:/patch

patch:
  switches/@next:
    name: emoji_suggestion
    reset: 1
    states: [ "🈚️️\uFE0E", "🈶️️\uFE0F" ]
  'engine/filters/@before 0':
    simplifier@emoji_suggestion
  emoji_suggestion:
    opencc_config: emoji.json
    option_name: emoji_suggestion
    tips: all

配置管理器修改後的文件:

# encoding: utf-8
__patch:
  # Rx opencc-emoji:emoji_suggestion {
    - patch/+:
        __include: emoji_suggestion.recipe:/patch
  # }

更多示例:

patch_files:
  # This is possible but as always we recommend
  # putting user customization in luna_pinyin.custom.yaml, default.custom.yaml
  ${schema:-luna_pinyin}.schema.yaml:
    - ${schema:-luna_pinyin}.custom:/patch
    - emoji_suggestion.recipe:/patch
  default.yaml:
    - schema_list/=: []
    - schema_list/+:
      - schema: luna_pinyin
    - schema_list/+:
      - schema: combo_pinyin_kbcon
    - menu/page_size: 9
  default.custom.yaml:
    - patch/+:
        menu/page_size: 10
marguerite commented 5 years ago

@lotem

我用 golang 实现了一个 rime-plum-go,本来是准备给 openSUSE 发行版升级 brise 做一个类似脚本一样的东西,可以在本地去把 repo 都 clone 好,结果不知不觉就做成近似完全版本的 plum 了。

目前没有 --selector 的功能,另外由于对 recipe 新概念的不熟悉可能有 bug...但是看到您的帖子,我感觉它可能可以解决 windows 用不了 bash 的问题。

目前参数是这样的:

-b 类似 Makefile 里面的功能
-d 指定 RIME_DIR
-r <prefix:-https://github.com/><user:-rime><repo:-rime-emoji>:<recipe:-customize>:<rimeOptions>

比起原来的 pattern,多了一个 prefix,可以支持除了 github 以外的网站比如 marguerite.su/marguerite/rime-custom:customize:key=value,key1=value1

但是由于加了参数的原因, selector 就不太好做了...在想别的办法比如自己实现参数的库。

另外准备看一下您在这里发的草案,检查一下对语法的处理有没有 bug...

lotem commented 5 years ago

@marguerite 久仰

目前這個格式的設計,以及打補丁的方法都在遷就文本行編輯工具,因爲bash腳本沒有解析YAML文件的能力,只好按文本文件來編輯。如果有再複雜一些的操作,已經做不下去了。 rime-plum-go 正是今後需要的工具。

具體來說,配方對 patch 節點打補丁,就是對缺乏YAML解析能力妥協的產物。所輸出的代碼非常難以維護。

理想中,補丁要直接修改 *.custom.yaml。用嚴肅編程語言就可以實現了。 將來改成這樣做,要有兩個前提:兼容現有的配方補丁;保護好用戶自己寫的patch,補丁做的修改要能撤回。這些要怎樣實現還得大討論。

marguerite commented 5 years ago

@lotem 不知道我说的对不对,翻看 recipe.sh 和自己跑那些 shell script 进行测试后,我感觉现在的 recipe 是这样的,第一行是要操作的文件 $(schema:-luna_pinyin}.schema.yaml ,针对这个文件做一个 .custom.yaml,然后把第二行开始的东西都原封不动的丢到 # Rx: xxx {} 这个 block 里去。plum 目前就做了这些事情。我的实现也就抄了这些...

看了下 config 的语法,不知道我的理解对不对。

- patch/+: 由于只有 .custom.yaml 中的 “patch:” 部分才能应用到 .schema.yaml(?) 所以这行实际上的意思是“向 .custom.yaml 的 patch: 追加”? - 表示列表,这里哪来的列表?
__include: emoji_suggestion:/patch 内容是 emoji_suggestion.yaml 的 patch: 部分。

现在已经采用 yaml 和 bash 结合的方式了,考虑到兼容性,再改纯 yaml 已经不太容易了。好比 install_files 下面可以直接写不带 - 的 opencc/*/。所以这里不差再多一个 reserved word 表示覆盖行为了,比如:

opencc/**/* !override
*.json !keep

反正这里肯定是要从 yaml 中提取出来单独 parse 的...至少我在 rime-plum-go 这边是这样做的。因为 golang 的在文件夹下 Glob 的函数是我自己写的(官方没有),只支持 re2 的 regexp。所以我这里需要转换比如 */ *.{txt,json} 这样的 shell script 为 regex。那就不差再多识别一个参数了...

另外感觉我这边可以尝试解析 yaml,直接写 custom.yaml 的。比如第一个 recipe 写 custom.yaml,我会读取 emoji_suggestion.yaml 直接把 patch: 内容写到 custom.yaml,反正 rime 是支持的。第二个 recipe 再写 custom.yaml,我也是读取 patch: 然后按 key 查找冲突。如果冲突则提示但不写入,只写入非冲突部分。至于怎么调整哪个 recipe 在先,目前没有这样的机制吧,毕竟用户安装顺序是乱序的。现在 recipe 可以嵌套吗?

lotem commented 5 years ago

install_files 本來應該用YAML列表,但是純粹爲了bash實現省力就做了一個長字符串

至於那個patch,怕是要多費些口舌才說得清……

我先說最後一個問題。安裝配方是講究順序的。 可能發生覆蓋,有些配方可能會有意覆蓋已有的配置項。 比方說,有個配方叫做清空輸入方案列表,那正常情況要用在所有添加輸入方案的配方之前。 所以使用配方的順序重要,並且最終效果是指定順序的用戶來保證。