可否维护一个东风破第三方配方列表呢?
Opened this issue · 25 comments
大家可以去这里找到由社区收集的各种方案 👍
https://github.com/sgalal/awesome-rime
比如我使用了现有的 plum yaml 格式包装了一下 Patricivs 写的 easy_en 方案
https://github.com/BlindingDark/rime-easy-en
但是无处发布,希望可以让更多的人享受到 plum 带来的便利。
相关 issue #6
应该如何维护?像 https://hexo.io/themes/ 一样让作者发 PR 么?
应该如何维护?像 https://hexo.io/themes/ 一样让作者发 PR 么?
可以啊。比如在 readme 或者 wiki 里加一段:
以下是第三方配方列表,使用前请先确认配方的内容,以免破坏您的现有配置。
如在使用中发现问题,请联系第三方作者。
xxxx/xxxx
xxxxxxx/xxxxx
然后提 PR 加一行地址就好。
以下是我设计的一种 Rime 方案列表的程序,类似 Python 的 PyPI。如果需要,我可以帮助实现:
Rime Package Index
简介
该程序是一个 Rime 方案索引,类似于 PyPI,运行在 服务器端,用于 自动化下载 和 自动化更新 Rime 方案。
可行性
Rime 的输入方案通常只包含以下三种文件:
- 方案文件(
schema.yaml
) - 词库文件(
dict.yaml
) - OpenCC 配置文件
要实现 自动化下载 功能,客户端只需下载以上文件,放入相应的文件夹中部署即可。
在 Rime 的方案文件和词库文件中,都有版本号字段。要实现 自动化更新 功能,客户端只需比较该字段的值是否更新即可。
功能
- 客户端请求当前已知的所有输入方案的列表。得到列表后,客户端自行设计搜索算法,使用户选择需要的输入方案下载
- 客户端请求某个输入方案的下载地址。得到下载地址后,客户端自行下载该输入方案并放置到相应文件夹
其中,(2) 本可以并入 (1) 中处理,但此处分开处理,是为了在客户端请求 2 时,程序可以统计下载量。
内容
该程序记录一个方案列表,包括以下内容:
- 方案名称(由
schema.yaml
提取) - 方案作者(由
schema.yaml
提取) - 方案简介(由
schema.yaml
提取) - 方案版本号(文字形式,由
schema.yaml
提取) - 词库版本号(文字形式,由
dict.yaml
提取) - 方案所使用的开源协议名称(SPDX ID,由 GitHub API 取得)
- 方案所使用的开源协议文本链接
- 方案配置文件的下载链接
- 该方案的 GitHub 链接(如果有)
- 该方案 365 天内的下载量(由程序自动生成)
接口
设网站的域名为 https://example.org/,客户端请求 https://example.org/pkg/,得到如下格式的 JSON 数组(对应上述 1-7, 9-10):
{ "id": "非空,非负整数,方案在该程序中的编号"
, "name": "非空,字符串,方案名称(由 `schema.yaml` 提取)"
, "author": "非空,字符串,方案作者(由 `schema.yaml` 提取)TODO_FIXME: 改为数组,因为可能不止一个文件,例如 kahaani/dieghv"
, "description": " 字符串,方案简介(由 `schema.yaml` 提取)TODO_FIXME: 改为数组,因为可能不止一个文件"
, "schema_version": "非空,字符串,方案版本号(文字形式,由 `schema.yaml` 提取)TODO_FIXME: 改为数组 { name: xxx, version: xxxx },因为可能不止一个文件"
, "dict_version": "非空,字符串,词库版本号(文字形式,由 `dict.yaml` 提取)TODO_FIXME: 改为数组 { name: xxx, version: xxxx },因为可能不止一个文件"
, "license_spdx": " 字符串,方案所使用的开源协议,以 SPDX ID 的形式"
, "license_link": " 字符串,方案所使用的开源协议文本链接"
, "github_link": " 字符串,该方案的 GitHub 链接"
, "downloads": "非空,非负整数,该方案 365 天内的下载量"
}
当用户在客户端上选定了名为「訓読み」的方案后,设该方案 id
为 42,则客户端请求 https://example.com/pkg/42/,得到如下格式的 JSON 数据(对应上述 8):
{ "schema":
[ { "name": "kunyomi.schema.yaml"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/kunyomi.schema.yaml"
}
]
, "dict":
[ { "name": "kunyomi.dict.yaml"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/kunyomi.dict.yaml"
}
]
, "opencc_config":
[ { "name": "t2jp.json"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/opencc/t2jp.json"
}
, { "name": "JPVariants.txt"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/opencc/JPVariants.txt"
}
]
}
实现
使用编程语言+数据库实现。
添加方案
(A) 在 GitHub 上的方案
该程序的维护者提交 GitHub 链接(上述 8, 9),程序自动生成上述 1-7。
POST 请求 https://example.com/post/github/。
{ "config":
{ "schema":
[ { "name": "kunyomi.schema.yaml"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/kunyomi.schema.yaml"
}
]
, "dict":
[ { "name": "kunyomi.dict.yaml"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/kunyomi.dict.yaml"
}
]
, "opencc_config":
[ { "name": "t2jp.json"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/opencc/t2jp.json"
}
, { "name": "JPVariants.txt"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/opencc/JPVariants.txt"
}
]
}
, "github_link": "https://github.com/sgalal/rime-kunyomi"
}
(B) 不在 GitHub 上的方案
该程序的维护者提交上述 6,7,8,程序自动生成上述 1-5。
POST 请求 https://example.com/post/plain/。
虽然 sgalal/rime-kunyomi 在 GitHub 上,仍以该方案为例(原仓库无开源协议,此处假设为 MIT 协议;若实际使用时确无开源协议,用 NULL
)。
{ "license_spdx": "MIT License"
, "license_link": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/LICENSE"
, "config":
{ "schema":
[ { "name": "kunyomi.schema.yaml"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/kunyomi.schema.yaml"
}
]
, "dict":
[ { "name": "kunyomi.dict.yaml"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/kunyomi.dict.yaml"
}
]
, "opencc_config":
[ { "name": "t2jp.json"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/opencc/t2jp.json"
}
, { "name": "JPVariants.txt"
, "url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/master/opencc/JPVariants.txt"
}
]
}
}
该程序处理 POST 请求,存入数据库中。
数据库设计
TODO FIXME: 添加数据表的设计,另外需考虑下载量。
请求方案列表
由数据库中的数据生成 JSON 数据。TODO FIXME: 添加详细内容。
很好。
提兩個問題:
1、這個服務器是必須的嘛?
我看客戶端要實現不少邏輯,他一直是主動操作的。是不是意味着設計存入數據庫的信息,以靜態數據文件的形式記錄在一個GitHub代碼庫,供客戶端查詢,也能提供相同的服務呢。
2、如何更新數據庫中的信息。如,一位作者向代碼庫提交了新的修改。
請問這個配方列表,跟這裏 rime/home#336 所討論到的是一樣的嗎?
請問這個配方列表,跟這裏 rime/home#336 所討論到的是一樣的嗎?
我觉得这两个 issue 里提到的问题有相似的地方,其实我们需要的就是类似包管理器,或者浏览器中的插件管理器。
这个“列表”就是各种包的中心仓库,或者 chrome 应用商店之类的存在。
开发者遵守统一的文件组织结构,统一的发布流程,统一的版本控制,以进行插件的开发;用户使用简单的描述文件或者通过 GUI 勾选,即可完成安装,亦可控制插件版本。
如 @lotem 所说, “配方下載和管理的功能不到位”。配方,就是 rime 的 “包”。
本 issue 倾向的是包中心仓库的建立,能解决配方的管理功能,
而 #336 中的讨论更倾向是包管理器的客户端实现(尤其是 GUI),能解决配方的下载问题。
但是,@lotem 又说,目前首要的是 “用「嚴肅」編程技術重寫配置管理器”,因为 “現實問題是bash腳本的實現目前用戶接受度不夠好”。
所以要选一个新的,更容易让开发者接收的“配方”开发语言。
也就是目前用什么语言控制安装过程,用什么格式去组织文件还没有定下来,自然无法进行第二步 中心仓库,第三步 命令行/GUI 客户端的实现了。
不过幸好我们有很多可以参照的项目,比如 npm,rubygems,archlinux 的 aur,这些包管理都很先进,方便,允许用户通过中心仓库,本地仓库,github 地址等去安装,管理包 ……
那么应该用什么语言呢?(bash 已经被开除出“严肃编程”领域了😄️)
根据设想,这个语言要“一次开发,三个平台都能运行”,跨 rime 平台,那么用 rime 客户端自己去原生实现就不行了,因为 Mac,Linux,Windows 的 rime 客户端用的语言技术是不一样的。
那么可以跨平台的语言中,运行环境就要轻巧,因为要内置到不同平台的 rime 中。
性能到是要求不太高,没有什么特别费劲的操作。
所以可选的就是容易嵌入的脚本语言?Lua,Python,甚至 JavaScript。
不过对于配方开发者来说,影响不大,换语言只是换了个用以解析配方文件的东西而已(目前是 bash),配方的文件组织形式,配置格式变化应该不大。
总之期待 rime 能有一个成熟的包管理器吧(面向用户的名称可能是“配方商店”😄️)
請問這個配方列表,跟這裏 rime/home#336 所討論到的是一樣的嗎?
是的,我的發言是對 rime/home#336 重新構思後的結果。
根据设想,这个语言要“一次开发,三个平台都能运行”,跨 rime 平台,那么用 rime 客户端自己去原生实现就不行了,因为 Mac,Linux,Windows 的 rime 客户端用的语言技术是不一样的。
如我上面發言所述,不需做到這一點。
「一次开发,三个平台都能运行」是配方 管理器 的做法。而配方 列表 只需提供一個列表(以 JSON 格式),Mac、Linux、Windows 的 Rime 客户端分別以各自的編程語言解析 JSON 數據即可。
分別以各自的編程語言解析 JSON 數據
就是实现了三遍配方管理器,当然不是不行。。。
所以为啥不用跨平台的语言实现一遍,然后嵌入到三个平台呢。
当然,如果觉得实现分别三遍也没啥问题,也不是不行。。。维护成本嘛。。。
我個人是非常期待這樣的配方列表的出現的。因爲我的方言拼音收集倉庫,下一步的發展方向就是建設成各個方言拼音方案的總配方。但我不知道在rime/home#336 裏總結出的流程圖是否合理。如果確認要用json文件來儲存配方信息的話,我可以很快地準備好漢語方言配方總表。
如果是要跨平臺運行的話,我唯一想到的語言就是javascript。iRime因爲已採用中心化配方管理方式,所以無需考慮。但Android下的同文要是可以的話我希望還是能兼顧。
我認為實現並不複雜。客户端要實現的基本邏輯如下:
- 客户端初始化時,下載配方列表保存到本地;此後當用户提出更新配方列表請求時,或客户端自動檢測更新時,客户端向服務器請求新的配方列表保存到本地(服務器可加入時間戳功能,以供客户端確認配方列表是否被更新)
- 客户端提供檢索和排序功能。檢索的功能是:若用户輸入的檢索詞是某一方案名稱或方案描述的子串,則客户端顯示該方案;排序的功能是:用户可以根據方案名稱、方案下載量、方案最後更新時間進行排序(最後更新時間需要服務器記錄,以便向客户端提供)
- 當用户選擇下載某一方案時,客户端從服務器得到該方案的下載地址,並將方案所需的所有文件分別下載到該平台客户端的相應文件夾中。當下載完成後,客户端將下載文件的日誌記錄到配置文件中
- 當用户提出確認更新時,若客户端發現該方案有更新,則提示用户。若用户確認更新該方案,客户端從服務器得到該方案的新版本下載地址(可能與原地址相同,亦可能不同),並將方案所需的所有文件下載到臨時文件夾中。下載成功後,刪除原版本的文件,再將新版本的文件從臨時文件夾中移動到指定位置。客户端將配置文件中原有的相應信息刪除,將下載新文件的日誌記錄到配置文件中
總而言之,客户端只需實現 四種操作:下載列表、查看列表、下載方案、更新方案。
1、這個服務器是必須的嘛?
我看客戶端要實現不少邏輯,他一直是主動操作的。是不是意味着設計存入數據庫的信息,以靜態數據文件的形式記錄在一個GitHub代碼庫,供客戶端查詢,也能提供相同的服務呢。
是必需的。因為只有使用服務器纔可實現統計方案下載次數的功能(及便於日後拓展其他功能)。如果服務器具有統計方案下載次數的功能,可以使用户對方案的流行程度有直觀的感受,提高用戶使用體驗。
2、如何更新數據庫中的信息。如,一位作者向代碼庫提交了新的修改。
需要服務器維護者手動更新。否則若方案作者更新了方案內容,而服務器上的列表沒有更新,會出現數據不一致的情況。
實現並不複雜
就算开发成本(可能)不高,但是维护成本一定很高。
假如更新了配方格式,如何确保每个客户端都能兼容呢?--- 再写三遍同样的功能。。。
@sgalal 你说的中心仓库的方案也有点问题
客户端初始化時,下載配方列表保存到本地
没必要,可能会很慢。用户想装配方的时候再请求,而且不要请求全部的,只返回推荐的即可。
客户端提供檢索和排序功能
调用服务器的接口即可,不需本地实现。
需要服務器維護者手動更新
都搭建动态服务器了就别麻烦维护者更新了,配方开发者自己管理自己的不就行了。
你的提案感觉就是拿动态服务器做静态服务器做的事情,搞个动态服务器只统计下载,有点浪费。
静态服务可以是你说的这种玩法,一次拉下来所有的,然后搜索。
静态的话,可以用 github 做中心仓库记录。比如 ArchLinuxCN 就是采用的 github 做打包脚本的管理。
这个时候就是按你说的,全部拉下来看看(其实用 github 的接口也能做到部分查询功能)。更新,或者提交新的配方,就要配方开发者提 PR ,维护者审核通过即可。
升级就永远保持最新。(滚动更新 😄️)
我反而觉得目前先用 github 搞个仓库就好,简单,省钱,不需要怎么维护。
统计什么的,目前阶段没必要。
但是,上面讨论的都是中心仓库怎么做,是第二步。第一步是统一包管理的实现方式。
第一步不定下来,第二步都是空想。
@lotem 对于第一步的想法是跨平台,减少工作量,甚至连 GUI 他都想跨平台。。。你说的每个平台都实现一遍,对于 rime 这种小项目来说,不太现实。
(其实就是 bash 好烦,我要用严肃语言重构 plum😄️)
我也建议定义一个跨语言的方案,这样客户端可用各种语言实现。
rime现在自己就有域名、网站,可直接作为官方的。
其实 plum 已经实现了一个基本的包管理器功能了,上面我们讨论的,plum 都已经默默的实现了。
plum 已经可以
- 从 github 地址安装配方
- 直接通过“中心仓库”中的配方名安装配方
- 安装指定的配方版本
- 更新指定的配方
也就是我说的第一步已经实现了一大半了,离完整实现大概还差
- 已安装的记录
要有个记录安装什么的配置文件了,也就是 pip 中的 requirements.txt,gem 中的 gemfile。
这个配置文件表达能力肯定也要支持 本地,中心仓库,github,URL 等。如果采用 yaml,那么参照 gemfile 的 yaml 表示就好。 - 批量更新
通过上面的安装记录自然可以遍历然后更新。 - 版本控制(没必要做到这种地步吧😂️)
这个如果设计成时刻保持最新就不用费劲管理版本了。否则,要计算版本兼容,要有 lockfile。 - 依赖控制(太夸张了吧😂️)
允许 A 配方 基于 B 配方开发,那么用户指定安装 A 之后就要把 B 也下载下来。
后面两个难度大,意义不大,所以我觉得不用实现。毕竟不是做真的要做包管理,不会出现什么依赖或者版本问题。
所以现在 @lotem 的计划是搞完手头的活,用跨平台的语言重写一遍 plum 这个项目,实现上面说的功能。然后嵌入到 rime 中。最后搞个 GUI。
然后才能继续讨论中心仓库的问题。
那么关于中心仓库,我的提议是参照 ArchLinuxCN,用 github 管理就好。国内觉得速度慢的话,可以用国内的 git 服务做个镜像。
實現並不複雜
就算开发成本(可能)不高,但是维护成本一定很高。
假如更新了配方格式,如何确保每个客户端都能兼容呢?--- 再写三遍同样的功能。。。
小改動本來也不複雜,況且「更新了配方格式」應該是指「增加了字段」,這個對於 JSON 是向下兼容的,可以實現平穩升級。
@sgalal 你说的中心仓库的方案也有点问题
客户端初始化時,下載配方列表保存到本地
没必要,可能会很慢。用户想装配方的时候再请求,而且不要请求全部的,只返回推荐的即可。
不會很慢。例如你提到的 pacman 的安裝包數據更多,但是仍然是全部保存到本地的。目前 HTTP 請求大多使用 gzip 壓縮,實際大小非常小。
客户端提供檢索和排序功能
调用服务器的接口即可,不需本地实现。
這樣纔會慢,每次查詢都要發送 HTTP 請求。不如一次下載,然後在本地查找。
需要服務器維護者手動更新
都搭建动态服务器了就别麻烦维护者更新了,配方开发者自己管理自己的不就行了。
確實。可以使用 GitHub OAuth 認證實現方案開發者的登錄和維護功能。
如果採用下述靜態方案,則方案開發者向 GitHub 倉庫直接提交修改。
你的提案感觉就是拿动态服务器做静态服务器做的事情,搞个动态服务器只统计下载,有点浪费。
静态服务可以是你说的这种玩法,一次拉下来所有的,然后搜索。静态的话,可以用 github 做中心仓库记录。比如 ArchLinuxCN 就是采用的 github 做打包脚本的管理。
这个时候就是按你说的,全部拉下来看看(其实用 github 的接口也能做到部分查询功能)。更新,或者提交新的配方,就要配方开发者提 PR ,维护者审核通过即可。
升级就永远保持最新。(滚动更新 😄️)我反而觉得目前先用 github 搞个仓库就好,简单,省钱,不需要怎么维护。
统计什么的,目前阶段没必要。
我覺得有道理。我支持靜態服務的做法,建立一個 GitHub 倉庫保存數據。
但是,上面讨论的都是中心仓库怎么做,是第二步。第一步是统一包管理的实现方式。
第一步不定下来,第二步都是空想。@lotem 对于第一步的想法是跨平台,减少工作量,甚至连 GUI 他都想跨平台。。。你说的每个平台都实现一遍,对于 rime 这种小项目来说,不太现实。
目前跨桌面端和移動端的應用程序並不是主流,我認為還不如每個平台的開發者分別使用原生技術實現更容易。
上面提到客户端應該採用跨平台還是原生實現的問題,其實這個與我所説的 方案列表 無關。
一旦實現方案列表(或「中心倉庫」),那麼繼續採用 plum 現在的跨平台解決方案亦無問題。plum 從該列表獲取數據即可。
繼續上次的提問:
1、必須有服務器的理由,我能想到的也就是統計下載量這事兒。搞這個統計的話,就意味着下載點被「中心化」了。有點背離「方案可由作者自發完成發佈」的初衷。
2、如何更新中心服務器的數據:
- 服務器維護者手動更新:可能不及時,更糟的是過時的信息與實際代碼庫內容不一致;增加維護者的管理工作。
- 配方維護者手動提交更新:增加了額外的負擔,可能因此更新不及時或缺失;我原先設想的免服務器方案核心目標是鼓勵配方作者發佈配方,儘量簡化其操作步驟。輸入法作者不像軟件包管理的打包者那樣肯於投入時間和精力維護軟件包,我認爲這是本品不能照搬軟件包管理方式的重要理由,必須大幅簡化。
- 如要同步更新保存在服務器上的數據,可以做一個GitHub應用,讓作者註冊配方時授權其監聽向配方代碼庫推送代碼的事件。可是要求使用這種機制也會成爲阻攔發佈者的一道門檻。
通過回憶過去的討論,我現在想,服務器還是只負責簡單地記錄配方的地址爲好。沒有重覆的數據,就不需要同步。
編輯:優化維護者輸入的 URL
關於如何更新,我認為可以這樣:
只考慮在 GitHub 上的方案
維護者只需將 該方案的 GitHub 鏈接、方案配置文件的下載鏈接 和 該方案的開源協議文本鏈接 三個數據寫入方案列表倉庫。
{
"repository": "sgalal/rime-kunyomi",
"config": {
"schema": [{
"name": "kunyomi.schema.yaml",
"url": "/kunyomi.schema.yaml"
}],
"dict": [{
"name": "kunyomi.dict.yaml",
"url": "/kunyomi.dict.yaml"
}],
"opencc_config": [{
"name": "t2jp.json",
"url": "/opencc/t2jp.json"
}, {
"name": "JPVariants.txt",
"url": "/opencc/JPVariants.txt"
}]
},
"license_url": "/LICENSE"
}
由腳本生成如下 JSON,發佈到該倉庫的另一分支,便於後續處理:
{
"repository": "sgalal/rime-kunyomi",
"schema": [{
"schema_id": "kunyomi",
"name": "訓読み",
"version": "20181007 V1.0.01",
"author": ["其弦有余 <1727246457@qq.com>"],
"description": "This is the kunyomi input method for rime.\nThis schema uses OpenCC to convert the characters to Japanese style. Please put JPVariants.txt and t2jp.json in the opencc folder in advance.\n"
}],
"dictionary": [{
"name": "kunyomi",
"version": "20181009 V1.0.04"
}],
"license": {
"spdx_id": "GPL-3.0",
"url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/07199e2beca0a529489d15476a33b0d9cb3e745a/LICENSE"
},
"config": {
"schema": [{
"name": "kunyomi.schema.yaml",
"url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/07199e2beca0a529489d15476a33b0d9cb3e745a/kunyomi.schema.yaml"
}],
"dict": [{
"name": "kunyomi.dict.yaml",
"url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/07199e2beca0a529489d15476a33b0d9cb3e745a/kunyomi.dict.yaml"
}],
"opencc_config": [{
"name": "t2jp.json",
"url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/07199e2beca0a529489d15476a33b0d9cb3e745a/opencc/t2jp.json"
}, {
"name": "JPVariants.txt",
"url": "https://raw.githubusercontent.com/sgalal/rime-kunyomi/07199e2beca0a529489d15476a33b0d9cb3e745a/opencc/JPVariants.txt"
}]
}
}
關於如何更新中心服務器的數據,我認為可以從兩個方面解決:
- 編寫腳本,每次修改觸發更新;每周一 00:00 定期自動更新。
- 為了保證在沒有同步的情況下,數據亦不會出現不一致的現象,使用
07199e2beca0a529489d15476a33b0d9cb3e745a
等提交編號代替master
分支
我比較關心的是,這個配方列表具體的預期功能是什麼,應該如何實現,例如是yaml還是json。按照rime/home#336 的討論,配方列表只需保存於Github倉庫中,如Chinese_Rime 中維護一份所有漢語方言的方案總表。而“配方管理器”則由本地GUI實現。因此我感覺@sgalal 所提到的服務器並非必要,因爲Chinese_Rime的script/update_submodules.sh
正是實現了這個功能,只是無法自動更新,需要定期手動運行腳本更新而已。
gnu.org/fun/jokes/users-lightbulb.en.html
👀
Try not to necro issue with a pointless comment.
其实就是 bash 好烦,我要用严肃语言重构 plum
https://github.com/LibreService/micro_plum 我有一个PoC,已经成功用在了web上https://my-rime.vercel.app/
虽然js离跨平台还有一点点距离,但是项目只有几百行ts,完全可以用其他语言重写,甚至做成一个librime api
https://github.com/LibreService/rppi