├── .clang-format
├── .gitignore
├── .vscode
├── c_cpp_properties.json
├── extensions.json
└── settings.json
├── LICENSE
├── README.md
├── archlinux
├── PKGBUILD
└── cmath-include.patch
├── cpp
├── CMakeLists.txt
└── src
│ ├── module.cc
│ ├── python_filter.cc
│ ├── python_filter.h
│ ├── python_simple_filter.cc
│ ├── python_simple_filter.h
│ ├── python_simple_translator.cc
│ ├── python_simple_translator.h
│ ├── python_translator.cc
│ └── python_translator.h
├── examples
├── charset.simple_filter.py
├── complex.filter.py
├── datetime.simple_translator.py
└── putonghua_cloud_input.translator.py
├── images
├── charset-0.png
├── charset-1.png
├── complex-0.png
├── complex-1.png
├── datetime.png
└── putonghua_cloud_input.png
└── python
├── setup.py
└── src
└── rimeext
├── Filter.py
├── Translator.py
└── __init__.py
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | BasedOnStyle: Chromium
3 | IndentWidth: 4
4 | ColumnLimit: 0
5 | Cpp11BracedListStyle: 'false'
6 | SortIncludes: false
7 | SpaceAfterCStyleCast: 'true'
8 | SpaceBeforeCpp11BracedList: 'true'
9 | SpacesBeforeTrailingComments: '2'
10 | SpacesInParentheses: 'true'
11 | ...
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.dict.yaml
2 | dist
3 | *.egg-info
4 | .mypy_cache
5 | .pytest_cache
6 | __pycache__
7 |
8 | /cpp/cpp
9 | /python/README.md
10 | /archlinux/*.gz
11 | /archlinux/*.zst
12 | /archlinux/src
13 | /archlinux/pkg
14 |
--------------------------------------------------------------------------------
/.vscode/c_cpp_properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "Linux",
5 | "includePath": [
6 | "${default}",
7 | "${workspaceFolder}/../librime/src",
8 | "${workspaceFolder}/../librime/build/src",
9 | "/usr/include/python3.9"
10 | ],
11 | "compilerPath": "/usr/bin/clang",
12 | "cStandard": "c17",
13 | "cppStandard": "c++17",
14 | "intelliSenseMode": "linux-clang-x64"
15 | }
16 | ],
17 | "version": 4
18 | }
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-vscode.cpptools",
4 | "xaver.clang-format",
5 | "streetsidesoftware.code-spell-checker",
6 | "esbenp.prettier-vscode"
7 | ]
8 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | "vector": "cpp",
4 | "iosfwd": "cpp",
5 | "iterator": "cpp",
6 | "string": "cpp",
7 | "filesystem": "cpp",
8 | "type_traits": "cpp",
9 | "cctype": "cpp",
10 | "clocale": "cpp",
11 | "cmath": "cpp",
12 | "cstdarg": "cpp",
13 | "cstddef": "cpp",
14 | "cstdio": "cpp",
15 | "cstdlib": "cpp",
16 | "cstring": "cpp",
17 | "ctime": "cpp",
18 | "cwchar": "cpp",
19 | "cwctype": "cpp",
20 | "any": "cpp",
21 | "array": "cpp",
22 | "atomic": "cpp",
23 | "strstream": "cpp",
24 | "bit": "cpp",
25 | "*.tcc": "cpp",
26 | "bitset": "cpp",
27 | "chrono": "cpp",
28 | "cinttypes": "cpp",
29 | "codecvt": "cpp",
30 | "compare": "cpp",
31 | "complex": "cpp",
32 | "concepts": "cpp",
33 | "condition_variable": "cpp",
34 | "cstdint": "cpp",
35 | "deque": "cpp",
36 | "forward_list": "cpp",
37 | "list": "cpp",
38 | "map": "cpp",
39 | "set": "cpp",
40 | "unordered_map": "cpp",
41 | "unordered_set": "cpp",
42 | "exception": "cpp",
43 | "algorithm": "cpp",
44 | "functional": "cpp",
45 | "memory": "cpp",
46 | "memory_resource": "cpp",
47 | "numeric": "cpp",
48 | "optional": "cpp",
49 | "random": "cpp",
50 | "ratio": "cpp",
51 | "source_location": "cpp",
52 | "string_view": "cpp",
53 | "system_error": "cpp",
54 | "tuple": "cpp",
55 | "utility": "cpp",
56 | "hash_map": "cpp",
57 | "hash_set": "cpp",
58 | "slist": "cpp",
59 | "fstream": "cpp",
60 | "future": "cpp",
61 | "initializer_list": "cpp",
62 | "iomanip": "cpp",
63 | "iostream": "cpp",
64 | "istream": "cpp",
65 | "limits": "cpp",
66 | "mutex": "cpp",
67 | "new": "cpp",
68 | "numbers": "cpp",
69 | "ostream": "cpp",
70 | "semaphore": "cpp",
71 | "shared_mutex": "cpp",
72 | "sstream": "cpp",
73 | "stdexcept": "cpp",
74 | "stop_token": "cpp",
75 | "streambuf": "cpp",
76 | "thread": "cpp",
77 | "cfenv": "cpp",
78 | "typeindex": "cpp",
79 | "typeinfo": "cpp",
80 | "valarray": "cpp",
81 | "variant": "cpp",
82 | "__bit_reference": "cpp",
83 | "__bits": "cpp",
84 | "__config": "cpp",
85 | "__debug": "cpp",
86 | "__errc": "cpp",
87 | "__functional_base": "cpp",
88 | "__hash_table": "cpp",
89 | "__locale": "cpp",
90 | "__mutex_base": "cpp",
91 | "__node_handle": "cpp",
92 | "__nullptr": "cpp",
93 | "__split_buffer": "cpp",
94 | "__string": "cpp",
95 | "__threading_support": "cpp",
96 | "__tree": "cpp",
97 | "__tuple": "cpp",
98 | "ios": "cpp",
99 | "locale": "cpp",
100 | "queue": "cpp",
101 | "stack": "cpp",
102 | "__functional_03": "cpp",
103 | "csignal": "cpp",
104 | "coroutine": "cpp"
105 | },
106 | "[cpp]": {
107 | "editor.defaultFormatter": "xaver.clang-format",
108 | "editor.formatOnSave": true
109 | },
110 | "[python]": {
111 | "editor.formatOnSave": true
112 | },
113 | "C_Cpp.default.cppStandard": "c++17",
114 | "python.formatting.provider": "autopep8",
115 | "python.formatting.autopep8Args": [
116 | "--max-line-length=65536",
117 | "--ignore=E302"
118 | ]
119 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # librime-python
2 |
3 | **为什么你需要 librime-python?**
4 |
5 | 1\. Python 是解释执行的脚本语言,因此可以在 rime 输入法的基础上动态地添加功能,而无需重新编译源代码、重启输入法程序,甚至无需重新部署。
6 |
7 | 2\. 与 librime 本身使用的 C++ 语言相比,Python 提供了丰富的库函数,因此可以非常方便地拓展 rime 输入法的功能。Python 常见的应用场景包括字符串处理(自动纠错)、连接 HTTP 服务器(云输入)、深度学习(智能输入引擎)等。
8 |
9 | ## 一、示例
10 |
11 | ### 1.1. `simple_translator`
12 |
13 | 输出当前日期:[`python_simple_translator@datetime`](examples/datetime.simple_translator.py)
14 |
15 |
16 |
17 | ### 1.2. `translator`
18 |
19 | 汉语拼音(普通话)云输入:[`python_translator@putonghua_cloud_input`](examples/putonghua_cloud_input.translator.py)
20 |
21 |
22 |
23 | ### 1.3. `simple_filter`
24 |
25 | 过滤拓展区汉字:[`python_simple_filter@charset`](examples/charset.simple_filter.py)
26 |
27 | 过滤前
28 |
29 |
30 |
31 | 过滤后
32 |
33 |
34 |
35 | ### 1.4. `filter`
36 |
37 | 一个复杂的例子,删除长度为 2 的候选,并将长度为 3 的候选转为简体:[`python_filter@complex`](examples/complex.filter.py)
38 |
39 | 过滤前
40 |
41 |
42 |
43 | 过滤后
44 |
45 |
46 |
47 | **FIXME**:由上图可以看出,长度为 2 的候选并没有被完全刪除,下一个版本修复。
48 |
49 | ## 二、用法
50 |
51 | | 在输入方案中指定的组件名称 | 对应的 Python 脚本文件名 |
52 | | :- | :- |
53 | | `python_simple_translator@` | `.simple_translator.py` |
54 | | `python_translator@` | `.translator.py` |
55 | | `python_simple_filter@` | `.simple_filter.py` |
56 | | `python_filter@` | `.filter.py` |
57 |
58 | 在 Python 脚本文件中按照下面的要求定义 `rime_main` 函数,即可实现相应组件的功能。
59 |
60 | ### 2.1. `simple_translator`
61 |
62 | `simple_translator` 要求 `rime_main` 函数的输入为字符串,输出为字符串的列表。
63 |
64 | 其中,输入的字符串为待翻译的输入码;输出的列表为多个翻译结果,每一项表示一个翻译结果。
65 |
66 | ```python
67 | def rime_main(s: str) -> Optional[List[str]]:
68 | ...
69 | ```
70 |
71 | ### 2.2. `translator`
72 |
73 | `translator` 要求 `rime_main` 函数的输入为 `TranslatorQuery`,输出为 `TranslatorAnswer` 的列表。
74 |
75 | 其中,输入的 `TranslatorQuery` 为待翻译的內容,其中包含待翻译的输入码等多项信息;输出的列表为多个翻译结果,每一项为一个 `TranslatorAnswer`,其中包含翻译结果、`comment` 等多项信息。
76 |
77 | ```python
78 | from rimeext import TranslatorQuery, TranslatorAnswer
79 |
80 | def rime_main(translator_query: TranslatorQuery) -> Optional[List[TranslatorAnswer]]:
81 | ...
82 | ```
83 |
84 | ### 2.3. `simple_filter`
85 |
86 | `simple_filter` 要求 `rime_main` 函数的输入为字符串,输出为布尔值。
87 |
88 | 其中,输入的字符串为翻译结果;输出的布尔值表示是否保留该项翻译结果,为真则保留,否则删除。
89 |
90 | ```python
91 | def rime_main(s: str) -> bool:
92 | ...
93 | ```
94 |
95 | ### 2.4. `filter`
96 |
97 | `filter` 要求 `rime_main` 函数的输入为 `FilterQuery`,输出为 `FilterAnswer`。
98 |
99 | 其中,输入的 `FilterQuery` 为翻译结果,其中包含翻译结果文本、`comment` 等多项信息;输出的 `FilterAnswer` 有三种:跳过(保持该翻译结果不变)、删除(将该翻译结果从候选列表中移除)和重写(修改翻译结果中的某些信息)。
100 |
101 | ```python
102 | from rimeext import FilterQuery, FilterAnswer
103 |
104 | def rime_main(filter_query: FilterQuery) -> FilterAnswer:
105 | ...
106 | ```
107 |
108 | ## 三、安裝
109 |
110 | 安裝 librime-python 分为两部分:Python 部分需要安装 rimeext 库,C++ 部分需要编译并安装带有 pythonext 插件的 librime。
111 |
112 | ### 3.1. Arch Linux
113 |
114 | Python 部分:
115 |
116 | ```sh
117 | cd python
118 | pip install .
119 | ```
120 |
121 | C++ 部分:
122 |
123 | ```sh
124 | cd archlinux
125 | makepkg -si
126 | ```
127 |
128 | 在编译时,Boost.Python 1.76.0 可能会报错,经过判断这是 Boost.Python 库本身的问题(见 [boostorg/python issue #359](https://github.com/boostorg/python/issues/359)),这时自行修改两个系统头文件即可正常编译。
129 |
130 | **FIXME**:在 `PKGBUILD` 中,编译选项 `-DBUILD_MERGED_PLUGINS=Off` 被修改为 `On`,这是因为如果不修改,用户在 Python 脚本中 `import math` 会产生如下 `ImportError`。
131 |
132 | ```
133 | ImportError: /usr/lib/python3.9/lib-dynload/math.cpython-39-x86_64-linux-gnu.so: undefined symbol: PyLong_AsLongLongAndOverflow
134 | ```
135 |
136 | 下一个版本修复。
137 |
138 | ### 3.2. Windows
139 |
140 | **FIXME**:在 Windows 上遇到各种编译错误而失败,下一个版本修复。
141 |
142 | ## 四、用户 Q&A
143 |
144 | **Q1. Python 的脚本文件放在哪里?**
145 |
146 | rime 用户文件夹或 rime 系统文件夹。
147 |
148 | **Q2. 我更新了 Python 脚本,应如何通知 rime 输入法?**
149 |
150 | 重新部署,或者切换到其他方案再切回来。
151 |
152 | **Q3. 我在某个方案中使用了 librime-python,但一选择那个方案,输入法就会崩溃,怎么知道哪里出了问题呢?**
153 |
154 | 查看 rime 的错误日志。
155 |
156 | **Q4. 不同的 Python 脚本文件中的变量会相互冲突吗?**
157 |
158 | **FIXME**:目前会相互冲突,因为 pybind11 不支持 [sub-interpreter](https://pybind11.readthedocs.io/en/stable/advanced/embedding.html#sub-interpreter-support),下一个版本修复。
159 |
160 | **Q5. `rime_main` 函数会被调用几次?在什么时候被调用呢?**
161 |
162 | **TODO**:下一个版本补充。
163 |
164 | **Q6. 我想实现一个计数器的功能,应该如何使用全局变量?**
165 |
166 | **TODO**:下一个版本补充。
167 |
168 | ## 五、开发指南
169 |
170 | ### 5.1. 项目原理
171 |
172 | 利用 [pybind11](https://pybind11.readthedocs.io/en/stable/index.html)。pybind11 提供了对 [Python/C API](https://docs.python.org/3/c-api/) 的 C++ 封装。对本项目而言,pybind11 提供的最重要的功能是在 C++ 代码中调用 Python 解释器。
173 |
174 | ### 5.2. 项目结构
175 |
176 | 分为 Python 部分和 C++ 部分。Python 部分定义了数个类,但并不是对 rime C++ API 的绑定,而是 librime-python 自定义的简化版的接口,因此不依赖于 C++ 代码。
177 |
178 | ### 5.3. 代码准则
179 |
180 | 如果你使用任何编辑器,请确保编辑器会读取项目根目录的 `.clang-format`,并在保存时自动格式化代码。
181 |
182 | 本项目是 Python 插件,因此遵守 Python 之禅:Explicit is better that implicit。
183 |
184 | 特别地,对于字符串类型,使用 `std::string` 的写法,而不使用 rime 定义的别名 `rime::string`,这是为了保证与 Python API 交互部分的代码不会引起误解。
185 |
186 | 由于本项目是输入法插件,当插件在运行时产生异常时,异常必须在插件内部处理完毕,不应该使输入法崩溃,且结果必须等价于插件不存在。
187 |
188 | 使用正确的现代 C++17 语法。
189 |
190 | ## 六、开发 Q&A
191 |
192 | **Q1. Python 部分使用了哪些外部库?**
193 |
194 | Python 部分没有使用外部库,只使用了标准库,但是用户可以安装并引入其他外部库。
195 |
196 | **Q2. C++ 部分使用了哪些外部库?**
197 |
198 | 使用了 pybind11。
199 |
200 | **Q3. librime-python 用的是 C++17,但 librime 是 C++14,是否会出现问题?**
201 |
202 | [不会](https://stackoverflow.com/a/49119902)。
203 |
204 | **Q4. Python 部分是对 librime API 的绑定吗?**
205 |
206 | 不是。Python 部分是独立于 C++ 的。是 librime-python 自定义的简化版的接口。
207 |
208 | **Q5. 未来还需要做什么?**
209 |
210 | 1\. 修复上文提到的 bug;
211 |
212 | 2\. 为 librime-python 自定义的简化版的接口添加更多功能,如读取输入法开关状态等;
213 |
214 | 3\. 其他功能添加和优化。
215 |
--------------------------------------------------------------------------------
/archlinux/PKGBUILD:
--------------------------------------------------------------------------------
1 | # Maintainer: Felix Yan
2 | # Contributor: GONG Chen
3 | # Contributor: 網軍總司令
4 | # Contributor: Ayaka Mikazuki
5 |
6 | # Forked from https://github.com/archlinux/svntogit-community/blob/302200f/trunk/PKGBUILD
7 |
8 | pkgname=librime
9 | pkgver=1.7.3
10 | _octagramcommit=f92e083052b9983ee3cbddcda5ed60bb3c068e24
11 | _luacommit=d45a41af2f9d731e3c1516a191cc3160e3cb8377
12 | _charcodecommit=b569184772b12965e3ebe1dfd431026951fed81c
13 | pkgrel=6
14 | epoch=1
15 | pkgdesc="Rime input method engine"
16 | arch=('x86_64')
17 | url="https://github.com/rime/librime"
18 | license=('custom:BSD3')
19 | depends=('boost-libs' 'capnproto' 'opencc' 'yaml-cpp' 'leveldb' 'librime-data' 'lua' 'google-glog' 'marisa' 'pybind11')
20 | makedepends=('cmake' 'boost' 'gtest' 'ninja')
21 | source=("https://github.com/rime/librime/archive/$pkgver/$pkgname-$pkgver.tar.gz"
22 | "https://github.com/lotem/librime-octagram/archive/$_octagramcommit/librime-octagram-$_octagramcommit.tar.gz"
23 | "https://github.com/hchunhui/librime-lua/archive/$_luacommit/librime-lua-$_luacommit.tar.gz"
24 | "https://github.com/rime/librime-charcode/archive/$_charcodecommit/librime-lua-$_charcodecommit.tar.gz"
25 | cmath-include.patch)
26 | sha512sums=('8767d17c3d14a5a1bbb8269fab1627b907de72c288b362fdbc6191223937da21e8b18471b4ae8f83ce5afc0ec5c3ab12fbcb49930eb9969c1764c7390d9ee4b0'
27 | '737d1c58982d2f79a6e8b2548eefa1dddc036dd6e6d5436e7d6b4f3adfa2e9d8e45b29a13c1b8207a93cb77f3b5dbd9d18436f44d4e8040eb95b962de582b386'
28 | '2a3d3b49d53066fe96dd008e8064718082225e6bf185574a25b8e98175d9936abcfa1fdc56e48f9c72a2deb46f8157d6132fd119ff8e0a3d52fbe9e2ea21386c'
29 | '6670a2b089479cf4fb23012e61675065d483ab6123f6dcad136b226dbe361a16bc8f33caece2e139c8d89161a73a2126afe2bed3759996153de6e4888a95a430'
30 | '531b4143a7636e6f2cdb67ed2d9b6aa5d43c50dfbbad626757b435debf863f9d466a5923611d9213b0c897af919870d367e88889ea2bbc1037e11fa88a0c7602')
31 |
32 | prepare() {
33 | cd $pkgname-$pkgver
34 | ln -sf "$srcdir"/librime-octagram-$_octagramcommit plugins/librime-octagram
35 | ln -sf "$srcdir"/librime-lua-$_luacommit plugins/librime-lua
36 | ln -sf "$srcdir"/librime-charcode-$_charcodecommit plugins/librime-charcode
37 | ln -sf "$srcdir"/../../cpp plugins/librime-python
38 |
39 | patch -Np1 -d . -i "$srcdir"/cmath-include.patch
40 | }
41 |
42 | build() {
43 | cd $pkgname-$pkgver
44 | export CXXFLAGS="$CXXFLAGS -DNDEBUG"
45 | cmake . -GNinja -Bbuild -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_MERGED_PLUGINS=On -DENABLE_EXTERNAL_PLUGINS=On
46 | cmake --build build
47 | }
48 |
49 | check() {
50 | cd $pkgname-$pkgver/build
51 | ninja test
52 | }
53 |
54 | package() {
55 | cd $pkgname-$pkgver/build
56 | DESTDIR="$pkgdir" ninja install
57 | }
58 |
--------------------------------------------------------------------------------
/archlinux/cmath-include.patch:
--------------------------------------------------------------------------------
1 | From 57cffcd02ac70148e21ce982be834876b3df87db Mon Sep 17 00:00:00 2001
2 | From: HanatoK
3 | Date: Sat, 1 May 2021 01:04:38 -0500
4 | Subject: [PATCH] Fix FTBFS.
5 |
6 | Previous boost library (1.75) may implicitly include cmath, but the
7 | latest version (1.76) does not, so the calls to exp are undefined. This
8 | commit include cmath in script_translator.cc and table_translator.cc to
9 | fix the issue (#462).
10 | ---
11 | src/rime/gear/script_translator.cc | 1 +
12 | src/rime/gear/table_translator.cc | 1 +
13 | 2 files changed, 2 insertions(+)
14 |
15 | diff --git a/src/rime/gear/script_translator.cc b/src/rime/gear/script_translator.cc
16 | index 4a45f05e..25061659 100644
17 | --- a/src/rime/gear/script_translator.cc
18 | +++ b/src/rime/gear/script_translator.cc
19 | @@ -8,6 +8,7 @@
20 | //
21 | #include
22 | #include
23 | +#include
24 | #include
25 | #include
26 | #include
27 | diff --git a/src/rime/gear/table_translator.cc b/src/rime/gear/table_translator.cc
28 | index 162ac024..c95e5e24 100644
29 | --- a/src/rime/gear/table_translator.cc
30 | +++ b/src/rime/gear/table_translator.cc
31 | @@ -6,6 +6,7 @@
32 | //
33 | #include
34 | #include
35 | +#include
36 | #include
37 | #include
38 | #include
39 |
--------------------------------------------------------------------------------
/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | set(PYTHONEXT_VERSION 1.0.0)
2 | set(PYTHONEXT_SOVERSION 1)
3 |
4 | set(CMAKE_CXX_STANDARD 17)
5 | set(CMAKE_CXX_STANDARD_REQUIRED True)
6 |
7 | find_package(PythonLibs REQUIRED)
8 | include_directories(${PYTHON_INCLUDE_DIRS})
9 |
10 | find_package(pybind11 REQUIRED)
11 | include_directories(${pybind11_INCLUDE_DIRS})
12 |
13 | aux_source_directory(src pythonext_src)
14 |
15 | set(pythonext_library rime-pythonext)
16 | set(pythonext_deps ${rime_library} ${PYTHON_LIBRARIES} ${pybind11_LIBRARIES})
17 | set(pythonext_modules "pythonext")
18 |
19 | add_library(rime-pythonext-objs OBJECT ${pythonext_src})
20 | if(BUILD_SHARED_LIBS)
21 | set_target_properties(rime-pythonext-objs PROPERTIES POSITION_INDEPENDENT_CODE ON)
22 | endif()
23 |
24 | set(plugin_name ${pythonext_library} PARENT_SCOPE)
25 | set(plugin_objs $ PARENT_SCOPE)
26 | set(plugin_deps ${pythonext_deps} PARENT_SCOPE)
27 | set(plugin_modules ${pythonext_modules} PARENT_SCOPE)
28 |
--------------------------------------------------------------------------------
/cpp/src/module.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include "python_simple_translator.h"
8 | #include "python_translator.h"
9 | #include "python_simple_filter.h"
10 | #include "python_filter.h"
11 |
12 | namespace py = pybind11;
13 |
14 | template
15 | struct ComponentName { static const char* const name; };
16 |
17 | template <>
18 | const char* const ComponentName::name { "simple_translator" };
19 | template <>
20 | const char* const ComponentName::name { "translator" };
21 | template <>
22 | const char* const ComponentName::name { "simple_filter" };
23 | template <>
24 | const char* const ComponentName::name { "filter" };
25 |
26 | /**
27 | * Determine the path to the Python script file according to the name string of a rime component.
28 | *
29 | * @param componentName The name of the component (e.g. translator, filter).
30 | * @param tagName The name string of the rime component.
31 | * @return Path to the Python script.
32 | * @exception Throws `std::runtime_error` if the file does not exist.
33 | */
34 | static std::string getPythonScriptRealPath( const char* const componentName, const std::string& tagName ) {
35 | // determine the file name
36 | const std::string fileName { tagName + "." + componentName + ".py" };
37 |
38 | // the file should either exist in the user directory or the shared directory
39 | const std::filesystem::path userDir { std::filesystem::canonical( RimeGetUserDataDir() ) };
40 | const std::filesystem::path sharedDir { std::filesystem::canonical( RimeGetSharedDataDir() ) };
41 | const std::filesystem::path userFile { userDir / fileName };
42 | const std::filesystem::path sharedFile { sharedDir / fileName };
43 | const bool userFileExists { std::filesystem::exists( userFile ) };
44 | const bool sharedFileExists { std::filesystem::exists( sharedFile ) };
45 |
46 | if ( !userFileExists && !sharedFileExists ) {
47 | LOG( ERROR ) << "Expected file " << fileName << " in the user directory ("
48 | << userDir.string() << ") or in the shared directory ("
49 | << sharedDir.string() << ").";
50 | std::terminate();
51 | }
52 |
53 | // prefer the file in the user directory
54 | return ( userFileExists ? userFile : sharedFile ).string();
55 | }
56 |
57 | template
58 | class PythonComponent : public T::Component {
59 | public:
60 | PythonComponent() {};
61 |
62 | T* Create( const rime::Ticket& ticket ) {
63 | const rime::Ticket ticket_ { ticket.engine, ticket.name_space, ticket.name_space };
64 | const char* const componentName { ComponentName::name };
65 | const std::string& tagName { ticket_.name_space };
66 |
67 | // load Python script file
68 | const std::string pythonScriptPath { getPythonScriptRealPath( componentName, tagName ) };
69 | LOG( INFO ) << "reading Python script file " << pythonScriptPath << ".";
70 |
71 | try {
72 | py::eval_file( pythonScriptPath, py::globals(), py::globals() );
73 | py::function py_entry { py::eval( "rime_main", py::globals(), py::globals() ) };
74 | return new T { ticket_, py_entry };
75 | } catch ( const py::error_already_set& e ) {
76 | LOG( ERROR ) << e.what();
77 | std::terminate();
78 | }
79 | }
80 | };
81 |
82 | static void rime_pythonext_initialize() {
83 | LOG( INFO ) << "registering components from module 'pythonext'.";
84 |
85 | py::initialize_interpreter();
86 |
87 | try {
88 | py::exec( "import rimeext", py::globals(), py::globals() );
89 | } catch ( const py::error_already_set& e ) {
90 | LOG( ERROR ) << e.what();
91 | std::terminate();
92 | }
93 |
94 | rime::Registry& r { rime::Registry::instance() };
95 | r.Register( "python_simple_translator", new PythonComponent() );
96 | r.Register( "python_translator", new PythonComponent() );
97 | r.Register( "python_simple_filter", new PythonComponent() );
98 | r.Register( "python_filter", new PythonComponent() );
99 | }
100 |
101 | static void rime_pythonext_finalize() {
102 | py::finalize_interpreter();
103 | }
104 |
105 | RIME_REGISTER_MODULE( pythonext )
106 |
--------------------------------------------------------------------------------
/cpp/src/python_filter.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "python_filter.h"
6 |
7 | namespace pythonext {
8 |
9 | // PythonFilter
10 |
11 | PythonFilter::PythonFilter( const rime::Ticket& ticket, py::function py_entry )
12 | : Filter { ticket }, py_entry { py_entry }, FilterQuery { py::eval( "rimeext.FilterQuery", py::globals(), py::globals() ) } {}
13 |
14 | rime::an PythonFilter::Apply( rime::an translation, rime::CandidateList* candidates ) {
15 | return rime::New( this, translation );
16 | }
17 |
18 | void PythonFilter::FilterFunc( rime::an cand, rime::CandidateQueue* cand_queue ) {
19 | const std::string& candidate_type { cand->type() };
20 | const std::string& text { cand->text() };
21 | const std::string comment { cand->comment() }; // return value is not a reference
22 | const std::string preedit { cand->preedit() }; // return value is not a reference
23 |
24 | try {
25 | const py::object filter_result { py_entry( FilterQuery( candidate_type, text, comment, preedit ) ) };
26 |
27 | // Case 1: Skip the current candidate. The candidate will be remained intact
28 | const bool should_skip { filter_result.attr( "should_skip" ).cast() };
29 | if ( should_skip ) {
30 | cand_queue->emplace_back( cand );
31 | return;
32 | }
33 |
34 | // Case 2: Remove the current candidate from the candidate list
35 | const bool should_remove { filter_result.attr( "should_remove" ).cast() };
36 | if ( should_remove )
37 | return;
38 |
39 | // Case 3: Change certain attributes of the current candidate
40 | const std::string new_candidate_type { filter_result.attr( "candidate_type" ).cast() };
41 | const std::string new_text { filter_result.attr( "text" ).cast() };
42 | const std::string new_comment { filter_result.attr( "comment" ).cast() };
43 |
44 | const rime::an new_cand { rime::New( cand, new_candidate_type, new_text, new_comment ) };
45 |
46 | cand_queue->emplace_back( new_cand );
47 | } catch ( const py::error_already_set& e ) {
48 | LOG( ERROR ) << e.what();
49 | }
50 | }
51 |
52 | // PythonFilterTranslation
53 |
54 | PythonFilterTranslation::PythonFilterTranslation( PythonFilter* filter, rime::an translation )
55 | : filter_ { filter }, PrefetchTranslation { translation } {}
56 |
57 | bool PythonFilterTranslation::Replenish() {
58 | const rime::an cand { translation_->Peek() };
59 | translation_->Next();
60 | filter_->FilterFunc( cand, &cache_ );
61 | return !cache_.empty();
62 | }
63 |
64 | } // namespace pythonext
65 |
--------------------------------------------------------------------------------
/cpp/src/python_filter.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | namespace py = pybind11;
10 |
11 | namespace pythonext {
12 |
13 | class PythonFilter : public rime::Filter {
14 | public:
15 | PythonFilter( const rime::Ticket& ticket, py::function py_entry );
16 | virtual rime::an Apply( rime::an translation, rime::CandidateList* candidates ) override;
17 | void FilterFunc( rime::an cand, rime::CandidateQueue* cand_queue );
18 |
19 | private:
20 | const py::function py_entry;
21 | const py::object FilterQuery;
22 | };
23 |
24 | class PythonFilterTranslation : public rime::PrefetchTranslation {
25 | public:
26 | PythonFilterTranslation( PythonFilter* filter, rime::an translation );
27 |
28 | protected:
29 | virtual bool Replenish() override;
30 | PythonFilter* const filter_;
31 | };
32 |
33 | } // namespace pythonext
34 |
--------------------------------------------------------------------------------
/cpp/src/python_simple_filter.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "python_simple_filter.h"
5 |
6 | namespace pythonext {
7 |
8 | // PythonSimpleFilter
9 |
10 | PythonSimpleFilter::PythonSimpleFilter( const rime::Ticket& ticket, py::function py_entry )
11 | : Filter { ticket }, py_entry { py_entry } {}
12 |
13 | rime::an PythonSimpleFilter::Apply( rime::an translation, rime::CandidateList* candidates ) {
14 | return rime::New( this, translation );
15 | }
16 |
17 | bool PythonSimpleFilter::FilterFunc( rime::an cand ) noexcept {
18 | const std::string& text { cand->text() };
19 |
20 | try {
21 | return py_entry( text ).cast();
22 | } catch ( const py::error_already_set& e ) {
23 | LOG( ERROR ) << e.what();
24 | }
25 |
26 | return true;
27 | }
28 |
29 | // PythonSimpleFilterTranslation
30 |
31 | PythonSimpleFilterTranslation::PythonSimpleFilterTranslation( PythonSimpleFilter* filter, rime::an translation )
32 | : filter_ { filter }, translation_ { translation } {
33 | LocateNextCandidate();
34 | }
35 |
36 | bool PythonSimpleFilterTranslation::Next() {
37 | if ( exhausted() )
38 | return false;
39 | if ( !translation_->Next() ) {
40 | set_exhausted( true );
41 | return false;
42 | }
43 | return LocateNextCandidate();
44 | }
45 |
46 | rime::an PythonSimpleFilterTranslation::Peek() {
47 | return translation_->Peek();
48 | }
49 |
50 | bool PythonSimpleFilterTranslation::LocateNextCandidate() {
51 | while ( !translation_->exhausted() ) {
52 | const rime::an cand { translation_->Peek() };
53 | if ( cand && filter_->FilterFunc( cand ) )
54 | return true;
55 | translation_->Next();
56 | }
57 | set_exhausted( true );
58 | return false;
59 | }
60 |
61 | } // namespace pythonext
62 |
--------------------------------------------------------------------------------
/cpp/src/python_simple_filter.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | namespace py = pybind11;
10 |
11 | namespace pythonext {
12 |
13 | class PythonSimpleFilter : public rime::Filter {
14 | public:
15 | PythonSimpleFilter( const rime::Ticket& ticket, py::function py_entry );
16 | virtual rime::an Apply( rime::an translation, rime::CandidateList* candidates ) override;
17 | bool FilterFunc( rime::an cand ) noexcept;
18 |
19 | private:
20 | const py::function py_entry;
21 | };
22 |
23 | class PythonSimpleFilterTranslation : public rime::Translation {
24 | public:
25 | PythonSimpleFilterTranslation( PythonSimpleFilter* filter, rime::an translation );
26 | virtual bool Next() override;
27 | virtual rime::an Peek() override;
28 |
29 | protected:
30 | bool LocateNextCandidate();
31 | rime::an translation_;
32 | PythonSimpleFilter* const filter_;
33 | };
34 |
35 | } // namespace pythonext
36 |
--------------------------------------------------------------------------------
/cpp/src/python_simple_translator.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "python_simple_translator.h"
5 |
6 | namespace pythonext {
7 |
8 | PythonSimpleTranslator::PythonSimpleTranslator( const rime::Ticket& ticket, py::function py_entry )
9 | : Translator { ticket }, py_entry { py_entry } {}
10 |
11 | rime::an PythonSimpleTranslator::Query( const std::string& input, const rime::Segment& segment ) noexcept {
12 | if ( !segment.HasTag( "abc" ) )
13 | return nullptr;
14 |
15 | const size_t segment_start { segment.start };
16 | const size_t segment_end { segment.end };
17 |
18 | try {
19 | // skip if return value is None
20 | // Note: cannot assign to type `py::list` directly
21 | // https://pybind11.readthedocs.io/en/stable/advanced/pycpp/object.html#assigning-py-none-to-wrappers
22 | const py::object output { py_entry( input ) };
23 | if ( output.is_none() )
24 | return nullptr;
25 |
26 | const py::list output_list { output };
27 |
28 | // skip if returned list is empty
29 | if ( output_list.empty() )
30 | return nullptr;
31 |
32 | // create translation list from the returned list
33 | const rime::an translation { rime::New() };
34 | for ( const py::handle& item : output_list ) {
35 | const std::string item_string { item.cast() };
36 | translation->Append( rime::New( "python_simple", segment_start, segment_end, item_string ) );
37 | }
38 | return translation;
39 | } catch ( const py::error_already_set& e ) {
40 | LOG( ERROR ) << e.what();
41 | }
42 |
43 | return nullptr;
44 | }
45 |
46 | } // namespace pythonext
47 |
--------------------------------------------------------------------------------
/cpp/src/python_simple_translator.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | namespace py = pybind11;
9 |
10 | namespace pythonext {
11 |
12 | class PythonSimpleTranslator : public rime::Translator {
13 | public:
14 | PythonSimpleTranslator( const rime::Ticket& ticket, py::function py_entry );
15 | virtual rime::an Query( const std::string& input, const rime::Segment& segment ) noexcept override;
16 |
17 | private:
18 | const py::function py_entry;
19 | };
20 |
21 | } // namespace pythonext
22 |
--------------------------------------------------------------------------------
/cpp/src/python_translator.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "python_translator.h"
5 |
6 | namespace pythonext {
7 |
8 | PythonTranslator::PythonTranslator( const rime::Ticket& ticket, py::function py_entry )
9 | : Translator { ticket }, py_entry { py_entry }, TranslatorQuery { py::eval( "rimeext.TranslatorQuery", py::globals(), py::globals() ) } {}
10 |
11 | rime::an PythonTranslator::Query( const std::string& input, const rime::Segment& segment ) noexcept {
12 | if ( !segment.HasTag( "abc" ) )
13 | return nullptr;
14 |
15 | const size_t segment_start { segment.start };
16 |
17 | try {
18 | // skip if return value is None
19 | const py::object output { py_entry( TranslatorQuery( input ) ) };
20 | if ( output.is_none() )
21 | return nullptr;
22 |
23 | const py::list output_list { output };
24 |
25 | // skip if returned list is empty
26 | if ( output_list.empty() )
27 | return nullptr;
28 |
29 | // create translation list from the returned list
30 | const rime::an translation { rime::New() };
31 | for ( const py::handle& candidate : output_list ) {
32 | // get all the 5 attributes from the object
33 | const std::string text { candidate.attr( "text" ).cast() };
34 | const size_t length { candidate.attr( "length" ).cast() };
35 | const std::string candidate_type { candidate.attr( "candidate_type" ).cast() };
36 | const std::string comment { candidate.attr( "comment" ).cast() };
37 | const std::string preedit { candidate.attr( "preedit" ).cast() };
38 |
39 | translation->Append( rime::New( candidate_type, segment_start, segment_start + length, text, comment, preedit ) );
40 | }
41 | return translation;
42 | } catch ( const py::error_already_set& e ) {
43 | LOG( ERROR ) << e.what();
44 | }
45 |
46 | return nullptr;
47 | }
48 |
49 | } // namespace pythonext
50 |
--------------------------------------------------------------------------------
/cpp/src/python_translator.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | namespace py = pybind11;
9 |
10 | namespace pythonext {
11 |
12 | class PythonTranslator : public rime::Translator {
13 | public:
14 | PythonTranslator( const rime::Ticket& ticket, py::function py_entry );
15 | virtual rime::an Query( const std::string& input, const rime::Segment& segment ) noexcept override;
16 |
17 | private:
18 | const py::function py_entry;
19 | const py::object TranslatorQuery;
20 | };
21 |
22 | } // namespace pythonext
23 |
--------------------------------------------------------------------------------
/examples/charset.simple_filter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # 过滤拓展区汉字
3 |
4 | import re
5 |
6 | # https://ayaka.shn.hk/hanregex/
7 | # CJK Unified Ideographs Extension A to G
8 | extblk = re.compile(r'[\u3400-\u4dbf\U00020000-\U0002a6df\U0002a700-\U0002ebef\U00030000-\U0003134f]')
9 |
10 | def rime_main(s: str) -> bool:
11 | return not extblk.match(s)
12 |
--------------------------------------------------------------------------------
/examples/complex.filter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # 删除长度为 2 的候选,并将长度为 3 的候选转为简体
3 |
4 | from opencc import OpenCC
5 | from rimeext import FilterQuery, FilterAnswer
6 |
7 | t2s = OpenCC('t2s').convert
8 |
9 | def rime_main(filter_query: FilterQuery) -> FilterAnswer:
10 | text = filter_query.text
11 | comment = filter_query.comment
12 | if len(text) == 2:
13 | return filter_query.do_remove()
14 | if len(text) == 3:
15 | return filter_query.do_rewrite(text=t2s(text), comment=comment + ' [简]')
16 | return filter_query.do_skip()
17 |
--------------------------------------------------------------------------------
/examples/datetime.simple_translator.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # 输入 date 时输出当前日期
3 |
4 | from datetime import datetime
5 | from typing import List, Optional
6 |
7 | def rime_main(s: str) -> Optional[List[str]]:
8 | if s == 'date':
9 | date = datetime.now().strftime('%Y-%m-%d')
10 | return [date]
11 |
--------------------------------------------------------------------------------
/examples/putonghua_cloud_input.translator.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # 汉语拼音(普通话)云输入
3 |
4 | from itertools import repeat
5 | import requests
6 | from rimeext import TranslatorQuery, TranslatorAnswer
7 | from typing import List, Optional
8 | import unicodedata
9 |
10 | def format_pinyin(s: str) -> str:
11 | '''
12 | >>> format_pinyin('hao3')
13 | 'hǎo'
14 | >>> format_pinyin('nv3')
15 | 'nǚ'
16 | '''
17 | s = s.replace('v', 'ü')
18 | if s.endswith('5'):
19 | s = s[:-1]
20 | elif s.endswith(tuple('1234')):
21 | tone = int(s[-1])
22 | tone_mark = '\u0304\u0301\u030c\u0300'[tone - 1]
23 | s = s[:-1]
24 | for component in ['iu', 'ui', 'a', 'o', 'e', 'i', 'u', 'ü']:
25 | if component in s:
26 | s = s.replace(component, component + tone_mark)
27 | break
28 | s = unicodedata.normalize('NFC', s)
29 | return s
30 |
31 | def format_pinyin_sequence(s: str) -> str:
32 | '''
33 | >>> format_pinyin_sequence('ni3 hao3')
34 | 'nǐ hǎo'
35 | '''
36 | return ' '.join(map(format_pinyin, s.split(' ')))
37 |
38 | def rime_main(translator_query: TranslatorQuery) -> Optional[List[TranslatorAnswer]]:
39 | input_str = translator_query.text
40 |
41 | num_of_items = 5
42 |
43 | payload = {
44 | 'text': input_str,
45 | 'itc': 'zh-hant-t-i0-pinyin',
46 | 'num': num_of_items,
47 | 'cp': 0,
48 | 'cs': 1,
49 | 'ie': 'utf-8',
50 | 'oe': 'utf-8',
51 | 'app': 'test',
52 | }
53 | r = requests.get('http://inputtools.google.com/request', params=payload)
54 | r.raise_for_status()
55 | response = r.json()
56 |
57 | if response[0] != 'SUCCESS':
58 | return
59 |
60 | _, texts, _, d = response[1][0]
61 | annotations = d.get('annotation', repeat(None))
62 | matched_lengths = d.get('matched_length', repeat(None))
63 | return [translator_query.create_answer(
64 | text,
65 | length=matched_length,
66 | comment=format_pinyin_sequence(annotation))
67 | for text, annotation, matched_length
68 | in zip(texts, annotations, matched_lengths)]
69 |
--------------------------------------------------------------------------------
/images/charset-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayaka14732/librime-python/c210fcc77366c54bf5a383c5b14f10afbf608042/images/charset-0.png
--------------------------------------------------------------------------------
/images/charset-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayaka14732/librime-python/c210fcc77366c54bf5a383c5b14f10afbf608042/images/charset-1.png
--------------------------------------------------------------------------------
/images/complex-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayaka14732/librime-python/c210fcc77366c54bf5a383c5b14f10afbf608042/images/complex-0.png
--------------------------------------------------------------------------------
/images/complex-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayaka14732/librime-python/c210fcc77366c54bf5a383c5b14f10afbf608042/images/complex-1.png
--------------------------------------------------------------------------------
/images/datetime.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayaka14732/librime-python/c210fcc77366c54bf5a383c5b14f10afbf608042/images/datetime.png
--------------------------------------------------------------------------------
/images/putonghua_cloud_input.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayaka14732/librime-python/c210fcc77366c54bf5a383c5b14f10afbf608042/images/putonghua_cloud_input.png
--------------------------------------------------------------------------------
/python/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from setuptools import setup, find_packages
4 | from os import path
5 |
6 | here = path.abspath(path.dirname(__file__))
7 |
8 | long_description = 'Please see [ayaka14732/librime-python](https://github.com/ayaka14732/librime-python).'
9 |
10 | setup(
11 | name='rimeext',
12 | version='0.0.1',
13 | description='Rime Python extension',
14 | long_description=long_description,
15 | long_description_content_type='text/markdown',
16 | url='https://github.com/ayaka14732/librime-python',
17 | author='ayaka14732',
18 | author_email='ayaka@mail.shn.hk',
19 | classifiers=[
20 | 'Development Status :: 4 - Beta',
21 | 'Intended Audience :: Developers',
22 | 'Topic :: Software Development :: Libraries :: Python Modules',
23 | 'Topic :: Text Processing :: Linguistic',
24 | 'Natural Language :: Chinese (Simplified)',
25 | 'Natural Language :: Chinese (Traditional)',
26 | 'Natural Language :: Cantonese',
27 | 'License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication',
28 | 'Programming Language :: Python :: 3',
29 | 'Programming Language :: Python :: 3.7',
30 | 'Programming Language :: Python :: 3.8',
31 | 'Programming Language :: Python :: 3.9',
32 | 'Programming Language :: Python :: 3.10',
33 | ],
34 | keywords='linguistics input-method',
35 | packages=find_packages('src'),
36 | package_dir={'': 'src'},
37 | python_requires='>=3.7, <4',
38 | entry_points={},
39 | project_urls={
40 | 'Bug Reports': 'https://github.com/ayaka14732/librime-python/issues',
41 | 'Source': 'https://github.com/ayaka14732/librime-python',
42 | }
43 | )
44 |
--------------------------------------------------------------------------------
/python/src/rimeext/Filter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from typing import Optional
4 |
5 | class FilterAnswer:
6 | '''
7 | The answer to a filter query.
8 | '''
9 |
10 | def __init__(self, should_skip: bool = False, should_remove: bool = False, candidate_type: Optional[str] = None, text: Optional[str] = None, comment: Optional[str] = None, direct_call: bool = True):
11 | assert not direct_call, 'Please use `Skip`, `Remove` or `Rewrite` instead of calling this constructor directly'
12 |
13 | self.should_skip = should_skip
14 |
15 | self.should_remove = should_remove
16 |
17 | self.candidate_type = candidate_type
18 | self.text = text
19 | self.comment = comment
20 |
21 | @classmethod
22 | def Skip(cls):
23 | return cls(should_skip=True, direct_call=False)
24 |
25 | @classmethod
26 | def Remove(cls):
27 | return cls(should_remove=True, direct_call=False)
28 |
29 | @classmethod
30 | def Rewrite(cls, **kwargs):
31 | return cls(**kwargs, direct_call=False)
32 |
33 | class FilterQuery:
34 | '''
35 | The input to the filter.
36 | '''
37 |
38 | def __init__(self, candidate_type: str, text: str, comment: str, preedit: str):
39 | self.candidate_type = candidate_type
40 | self.text = text
41 | self.comment = comment
42 | self.preedit = preedit
43 |
44 | def __repr__(self) -> str:
45 | return f''
46 |
47 | def do_skip(self) -> FilterAnswer:
48 | '''Skip the current candidate. The candidate will be remained intact.'''
49 | return FilterAnswer.Skip()
50 |
51 | def do_remove(self) -> FilterAnswer:
52 | '''Remove the current candidate from the candidate list.'''
53 | return FilterAnswer.Remove()
54 |
55 | def do_rewrite(self, candidate_type: Optional[str] = None, text: Optional[str] = None, comment: Optional[str] = None) -> FilterAnswer:
56 | '''Change certain attributes of the current candidate.'''
57 | # default values
58 | if candidate_type is None:
59 | candidate_type = 'python_filtered'
60 | if text is None:
61 | text = self.text
62 | if comment is None:
63 | comment = self.comment
64 |
65 | # type checking
66 | assert isinstance(candidate_type, str)
67 | assert isinstance(text, str)
68 | assert isinstance(comment, str)
69 |
70 | return FilterAnswer.Rewrite(text=text, candidate_type=candidate_type, comment=comment)
71 |
--------------------------------------------------------------------------------
/python/src/rimeext/Translator.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from typing import Optional
4 |
5 | class TranslatorAnswer:
6 | '''
7 | The answer to a translator query.
8 | '''
9 |
10 | def __init__(self, text: Optional[str] = None, length: Optional[int] = None, candidate_type: Optional[str] = None, comment: Optional[str] = None, preedit: Optional[str] = None):
11 | self.text = text
12 | self.length = length
13 | self.candidate_type = candidate_type
14 | self.comment = comment
15 | self.preedit = preedit
16 |
17 | def __repr__(self) -> str:
18 | return f''
19 |
20 | class TranslatorQuery:
21 | '''
22 | The input to the translator.
23 | '''
24 |
25 | def __init__(self, text: str):
26 | self.text = text
27 |
28 | def __repr__(self) -> str:
29 | return f''
30 |
31 | def create_answer(self, text: str, length: Optional[int] = None, candidate_type: Optional[str] = None, comment: Optional[str] = None, preedit: Optional[str] = None) -> TranslatorAnswer:
32 | # default values
33 | if length is None:
34 | length = len(self.text)
35 | if candidate_type is None:
36 | candidate_type = 'python'
37 | if comment is None:
38 | comment = ''
39 | if preedit is None:
40 | preedit = ''
41 |
42 | # type checking
43 | assert isinstance(text, str)
44 | assert isinstance(length, int)
45 | assert isinstance(candidate_type, str)
46 | assert isinstance(comment, str)
47 | assert isinstance(preedit, str)
48 |
49 | return TranslatorAnswer(text=text, length=length, candidate_type=candidate_type, comment=comment, preedit=preedit)
50 |
--------------------------------------------------------------------------------
/python/src/rimeext/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from .Translator import TranslatorQuery, TranslatorAnswer
4 | from .Filter import FilterQuery, FilterAnswer
5 |
--------------------------------------------------------------------------------