PyPtt 2.2.3__tar.gz → 2.2.5__tar.gz
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.
- {pyptt-2.2.3 → pyptt-2.2.5}/PKG-INFO +1 -2
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/PTT.py +51 -4
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/__init__.py +1 -1
- pyptt-2.2.5/PyPtt/_api_set_signature_file.py +86 -0
- pyptt-2.2.5/PyPtt/_uao/LICENSE +26 -0
- pyptt-2.2.5/PyPtt/_uao/__init__.py +207 -0
- pyptt-2.2.5/PyPtt/_uao/b2u.py +2 -0
- pyptt-2.2.5/PyPtt/_uao/u2b.py +2 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/lang_en_US.py +1 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/lang_zh_TW.py +1 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/screens.py +1 -1
- pyptt-2.2.5/PyPtt/ssl_config.py +40 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt.egg-info/PKG-INFO +1 -2
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt.egg-info/SOURCES.txt +6 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt.egg-info/requires.txt +0 -1
- {pyptt-2.2.3 → pyptt-2.2.5}/pyproject.toml +2 -3
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_comment.py +2 -1
- pyptt-2.2.5/tests/test_set_signature_file.py +32 -0
- pyptt-2.2.3/PyPtt/ssl_config.py +0 -24
- {pyptt-2.2.3 → pyptt-2.2.5}/LICENSE +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_bucket.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_call_status.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_change_pw.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_comment.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_del_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_board_info.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_board_list.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_bottom_post_list.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_favourite_board.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_newest_index.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_post_index.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_post_list.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_time.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_user.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_waterball.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_give_money.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_has_new_mail.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_loginout.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_lottery.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_mail.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_mark_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_reply_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_search_user.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_set_board_title.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_util.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/api_server.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/check_value.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/command.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/config.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/connect_core.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/data_type.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/exceptions.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/i18n.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/lib_util.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/log.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/service.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt.egg-info/dependency_links.txt +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt.egg-info/top_level.txt +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/README.md +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/setup.cfg +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/setup.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_bucket.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_change_pw.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_del_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_del_post_unit.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_exceptions.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_fast_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_board_info.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_board_list.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_bottom_post_list.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_content_formula.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_favourite_boards.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_mail.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_newest_index.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_post_list.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_post_list_parser.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_post_parser.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_time.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_user.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_waterball.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_give_money.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_i18n.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_init.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_logger.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_login_logout.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_lottery.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_mail_send_and_del.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_mark_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_parse_query_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_post_anonymous.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_pyte_parity.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_reply_post.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_search_user.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_service.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_set_board_title.py +0 -0
- {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_vt100_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPtt
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.5
|
|
4
4
|
Summary: PyPtt github: https://github.com/PyPtt/PyPtt
|
|
5
5
|
Author-email: CodingMan <pttcodingman@gmail.com>
|
|
6
6
|
License: GNU Lesser General Public License v3 (LGPLv3)
|
|
@@ -28,7 +28,6 @@ License-File: LICENSE
|
|
|
28
28
|
Requires-Dist: progressbar2
|
|
29
29
|
Requires-Dist: websockets>=12
|
|
30
30
|
Requires-Dist: certifi
|
|
31
|
-
Requires-Dist: uao
|
|
32
31
|
Requires-Dist: requests>=2.32
|
|
33
32
|
Requires-Dist: AutoStrEnum
|
|
34
33
|
Requires-Dist: PyYAML
|
|
@@ -28,6 +28,7 @@ from . import _api_post
|
|
|
28
28
|
from . import _api_reply_post
|
|
29
29
|
from . import _api_search_user
|
|
30
30
|
from . import _api_set_board_title
|
|
31
|
+
from . import _api_set_signature_file
|
|
31
32
|
from . import check_value
|
|
32
33
|
from . import config
|
|
33
34
|
from . import connect_core
|
|
@@ -611,8 +612,7 @@ class API:
|
|
|
611
612
|
board (str): 看板名稱。
|
|
612
613
|
|
|
613
614
|
Returns:
|
|
614
|
-
Dict:
|
|
615
|
-
options(下注選項清單,每個選項包含 index、name、sold)。
|
|
615
|
+
Dict,樂透資訊,詳見 :ref:`lottery-field`。
|
|
616
616
|
|
|
617
617
|
Raises:
|
|
618
618
|
RequireLogin: 需要登入。
|
|
@@ -646,8 +646,7 @@ class API:
|
|
|
646
646
|
amount (int): 購買張數,範圍 1~9999,並受限於帳戶內的 Ptt 幣餘額。
|
|
647
647
|
|
|
648
648
|
Returns:
|
|
649
|
-
Dict:
|
|
650
|
-
cost(本次下注花費的 Ptt 幣總額)。
|
|
649
|
+
Dict,下注結果,詳見 :ref:`lottery-bet-field`。
|
|
651
650
|
|
|
652
651
|
Raises:
|
|
653
652
|
RequireLogin: 需要登入。
|
|
@@ -808,6 +807,38 @@ class API:
|
|
|
808
807
|
|
|
809
808
|
_api_set_board_title.set_board_title(self, board, new_title)
|
|
810
809
|
|
|
810
|
+
def set_signature_file(self, content: str) -> None:
|
|
811
|
+
|
|
812
|
+
"""
|
|
813
|
+
更新登入帳號的名片檔(PTT 的 plan),對應【個人設定】->【個人檔案】
|
|
814
|
+
->(Q)ueryEdit 編輯名片檔的功能。`get_user()` 回傳的 `signature_file`
|
|
815
|
+
欄位讀的就是這份內容。
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
content (str): 新的名片檔內容,會整份取代原本的內容。
|
|
819
|
+
|
|
820
|
+
Returns:
|
|
821
|
+
None
|
|
822
|
+
|
|
823
|
+
Raises:
|
|
824
|
+
RequireLogin: 需要登入。
|
|
825
|
+
|
|
826
|
+
範例::
|
|
827
|
+
|
|
828
|
+
import PyPtt
|
|
829
|
+
|
|
830
|
+
ptt_bot = PyPtt.API()
|
|
831
|
+
try:
|
|
832
|
+
# .. login ..
|
|
833
|
+
ptt_bot.set_signature_file(content='你好,我是 PyPtt')
|
|
834
|
+
# .. do something ..
|
|
835
|
+
finally:
|
|
836
|
+
ptt_bot.logout()
|
|
837
|
+
|
|
838
|
+
"""
|
|
839
|
+
|
|
840
|
+
_api_set_signature_file.set_signature_file(self, content)
|
|
841
|
+
|
|
811
842
|
def mark_post(self, mark_type: int, board: str, aid: Optional[str] = None, index: int = 0, search_type: int = 0,
|
|
812
843
|
search_condition: Optional[str] = None) -> None:
|
|
813
844
|
"""
|
|
@@ -1002,6 +1033,22 @@ class API:
|
|
|
1002
1033
|
finally:
|
|
1003
1034
|
ptt_bot.logout()
|
|
1004
1035
|
|
|
1036
|
+
以作者搜尋信件(先用 get_newest_index 取得該作者的信件數,再逐封取得)::
|
|
1037
|
+
|
|
1038
|
+
import PyPtt
|
|
1039
|
+
|
|
1040
|
+
ptt_bot = PyPtt.API()
|
|
1041
|
+
|
|
1042
|
+
search_list = [(PyPtt.SearchType.AUTHOR, 'CodingMan')]
|
|
1043
|
+
|
|
1044
|
+
try:
|
|
1045
|
+
# .. login ..
|
|
1046
|
+
count = ptt_bot.get_newest_index(PyPtt.NewIndex.MAIL, search_list=search_list)
|
|
1047
|
+
mail = ptt_bot.get_mail(index=count, search_list=search_list)
|
|
1048
|
+
# .. do something ..
|
|
1049
|
+
finally:
|
|
1050
|
+
ptt_bot.logout()
|
|
1051
|
+
|
|
1005
1052
|
參考 :doc:`get_newest_index`
|
|
1006
1053
|
"""
|
|
1007
1054
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from . import _api_util
|
|
2
|
+
from . import check_value
|
|
3
|
+
from . import command
|
|
4
|
+
from . import connect_core
|
|
5
|
+
from . import exceptions
|
|
6
|
+
from . import i18n
|
|
7
|
+
from . import lib_util
|
|
8
|
+
from . import log
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def set_signature_file(api, content: str) -> None:
|
|
12
|
+
_api_util.one_thread(api)
|
|
13
|
+
|
|
14
|
+
if not api._is_login:
|
|
15
|
+
raise exceptions.RequireLogin(i18n.require_login)
|
|
16
|
+
|
|
17
|
+
check_value.check_type(content, str, 'content')
|
|
18
|
+
|
|
19
|
+
log.logger.info(i18n.set_signature_file)
|
|
20
|
+
|
|
21
|
+
cmd_list = []
|
|
22
|
+
cmd_list.append(command.go_main_menu)
|
|
23
|
+
cmd_list.append('U')
|
|
24
|
+
cmd_list.append(command.enter)
|
|
25
|
+
cmd_list.append('M')
|
|
26
|
+
cmd_list.append(command.enter)
|
|
27
|
+
cmd_list.append('Q')
|
|
28
|
+
cmd_list.append(command.enter)
|
|
29
|
+
cmd = ''.join(cmd_list)
|
|
30
|
+
|
|
31
|
+
target_list = [
|
|
32
|
+
# Existing signature file -> getans asks Delete/Edit/Cancel first.
|
|
33
|
+
# max_match=1: the echoed prompt text can linger on the next
|
|
34
|
+
# screen refresh while waiting for the editor to draw (same
|
|
35
|
+
# quirk documented for '確定要儲存檔案嗎' in _api_post.py); without
|
|
36
|
+
# it this would re-fire 'E' + Enter into the editor buffer.
|
|
37
|
+
connect_core.TargetUnit('名片 (D)刪除', response='E' + command.enter, max_match=1),
|
|
38
|
+
# Empty signature file skips the getans prompt and opens the
|
|
39
|
+
# editor directly.
|
|
40
|
+
connect_core.TargetUnit('編輯文章', break_detect=True),
|
|
41
|
+
]
|
|
42
|
+
index = api.connect_core.send(
|
|
43
|
+
cmd,
|
|
44
|
+
target_list,
|
|
45
|
+
screen_timeout=api.config.screen_long_timeout)
|
|
46
|
+
if index < 0:
|
|
47
|
+
ori_screen = api.connect_core.get_screen_queue()[-1]
|
|
48
|
+
raise exceptions.UnknownError(ori_screen)
|
|
49
|
+
|
|
50
|
+
content = lib_util.uniform_new_line(content)
|
|
51
|
+
|
|
52
|
+
cmd_list = []
|
|
53
|
+
# ponytail: unlike post's blank template, the card-file editor preloads
|
|
54
|
+
# the account's existing signature file, so it has to be cleared line by
|
|
55
|
+
# line before writing the new content. ctrl_y*60 is a "clear up to 60
|
|
56
|
+
# pre-loaded lines" ceiling -- bump it if a real card file ever runs
|
|
57
|
+
# longer than that.
|
|
58
|
+
cmd_list.append(command.ctrl_y * 60)
|
|
59
|
+
cmd_list.append(content)
|
|
60
|
+
cmd_list.append(command.ctrl_x)
|
|
61
|
+
cmd = ''.join(cmd_list)
|
|
62
|
+
|
|
63
|
+
target_list = [
|
|
64
|
+
connect_core.TargetUnit('確定要儲存檔案嗎', response='s' + command.enter, max_match=1),
|
|
65
|
+
connect_core.TargetUnit('名片更新完畢', break_detect=True),
|
|
66
|
+
]
|
|
67
|
+
index = api.connect_core.send(
|
|
68
|
+
cmd,
|
|
69
|
+
target_list,
|
|
70
|
+
screen_timeout=api.config.screen_long_timeout)
|
|
71
|
+
if index < 0:
|
|
72
|
+
ori_screen = api.connect_core.get_screen_queue()[-1]
|
|
73
|
+
raise exceptions.UnknownError(ori_screen)
|
|
74
|
+
|
|
75
|
+
# The leading space in go_main_menu dismisses the "請按任意鍵繼續" banner
|
|
76
|
+
# left by the save; the trailing left-arrows walk back out of the
|
|
77
|
+
# 個人設定/個人檔案 submenus to the main menu.
|
|
78
|
+
target_list = [
|
|
79
|
+
connect_core.TargetUnit('主功能表', break_detect=True),
|
|
80
|
+
]
|
|
81
|
+
api.connect_core.send(
|
|
82
|
+
command.go_main_menu,
|
|
83
|
+
target_list,
|
|
84
|
+
screen_timeout=api.config.screen_long_timeout)
|
|
85
|
+
|
|
86
|
+
log.logger.info(i18n.set_signature_file, '...', i18n.success)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 eight
|
|
4
|
+
|
|
5
|
+
Vendored into PyPtt from PttCodingMan/pyUAO (a fork of eight04/pyUAO):
|
|
6
|
+
https://github.com/PttCodingMan/pyUAO
|
|
7
|
+
Commit 9d9a8bf59dbe60800d214b66ce972bc2ddf8da89 (v0.3.0)
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Vendored Big5-UAO codec.
|
|
2
|
+
|
|
3
|
+
Vendored (unmodified) from PttCodingMan/pyUAO, a fork of eight04/pyUAO,
|
|
4
|
+
to drop the external `uao` PyPI dependency (unmaintained upstream) and
|
|
5
|
+
pick up this fork's correctness fixes and incremental/stream codec
|
|
6
|
+
support. Vendoring is for dependency control, not performance: PyPtt is
|
|
7
|
+
I/O bound, so codec speed is not the point here.
|
|
8
|
+
|
|
9
|
+
Source: https://github.com/PttCodingMan/pyUAO
|
|
10
|
+
Commit: 9d9a8bf59dbe60800d214b66ce972bc2ddf8da89 (v0.3.0)
|
|
11
|
+
License: MIT (c) 2018 eight -- see LICENSE in this directory.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from codecs import (
|
|
15
|
+
Codec,
|
|
16
|
+
CodecInfo,
|
|
17
|
+
IncrementalDecoder,
|
|
18
|
+
IncrementalEncoder,
|
|
19
|
+
StreamReader,
|
|
20
|
+
StreamWriter,
|
|
21
|
+
lookup_error,
|
|
22
|
+
register,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from .b2u import b2u_table
|
|
26
|
+
from .u2b import u2b_table
|
|
27
|
+
|
|
28
|
+
__version__ = "0.3.0"
|
|
29
|
+
|
|
30
|
+
# ASCII is not part of the generated table; fill it in once at import time so
|
|
31
|
+
# the hot loops below never have to special-case it or mutate the tables.
|
|
32
|
+
for _i in range(0x80):
|
|
33
|
+
u2b_table.setdefault(chr(_i), bytes((_i,)))
|
|
34
|
+
del _i
|
|
35
|
+
|
|
36
|
+
# UAO maps the lone byte 0xFF to U+F8F8. The generated u2b table stores that
|
|
37
|
+
# mapping as a zero-padded pair (b"\x00\xff"), which is not a valid Big5 byte
|
|
38
|
+
# sequence, and the generated b2u table omits it entirely. 0xFF is never a
|
|
39
|
+
# lead byte (b2u lead bytes are 0x81-0xFE), so a single-byte entry is safe.
|
|
40
|
+
u2b_table["\uf8f8"] = b"\xff"
|
|
41
|
+
b2u_table.setdefault(0xFF, "\uf8f8")
|
|
42
|
+
|
|
43
|
+
_u2b_get = u2b_table.get
|
|
44
|
+
_b2u_get = b2u_table.get
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _encode(input, errors):
|
|
48
|
+
"""Encode a str, returning a list of bytes chunks."""
|
|
49
|
+
try:
|
|
50
|
+
# fast path: every character is in the table
|
|
51
|
+
return [u2b_table[c] for c in input]
|
|
52
|
+
except KeyError:
|
|
53
|
+
pass
|
|
54
|
+
out = []
|
|
55
|
+
append = out.append
|
|
56
|
+
error_handler = None
|
|
57
|
+
i = 0
|
|
58
|
+
n = len(input)
|
|
59
|
+
while i < n:
|
|
60
|
+
b = _u2b_get(input[i])
|
|
61
|
+
if b is not None:
|
|
62
|
+
append(b)
|
|
63
|
+
i += 1
|
|
64
|
+
continue
|
|
65
|
+
if error_handler is None:
|
|
66
|
+
error_handler = lookup_error(errors)
|
|
67
|
+
replacement, i = error_handler(UnicodeEncodeError(
|
|
68
|
+
"big5-uao", input, i, i + 1, "illegal multibyte sequence"))
|
|
69
|
+
if i < 0:
|
|
70
|
+
i += n
|
|
71
|
+
if isinstance(replacement, str):
|
|
72
|
+
out += _encode(replacement, "strict")
|
|
73
|
+
else:
|
|
74
|
+
append(replacement)
|
|
75
|
+
return out
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _decode(input, errors, final):
|
|
79
|
+
"""Decode bytes, returning (list of str chunks, bytes consumed).
|
|
80
|
+
|
|
81
|
+
With final=False, a trailing lead byte is left unconsumed so the
|
|
82
|
+
incremental decoder can buffer it until more data arrives.
|
|
83
|
+
"""
|
|
84
|
+
out = []
|
|
85
|
+
append = out.append
|
|
86
|
+
error_handler = None
|
|
87
|
+
i = 0
|
|
88
|
+
n = len(input)
|
|
89
|
+
while i < n:
|
|
90
|
+
c = input[i]
|
|
91
|
+
if c < 0x80:
|
|
92
|
+
append(chr(c))
|
|
93
|
+
i += 1
|
|
94
|
+
continue
|
|
95
|
+
if i + 1 < n:
|
|
96
|
+
u = _b2u_get(c * 0x100 + input[i + 1])
|
|
97
|
+
if u is not None:
|
|
98
|
+
append(u)
|
|
99
|
+
i += 2
|
|
100
|
+
continue
|
|
101
|
+
u = _b2u_get(c)
|
|
102
|
+
if u is not None:
|
|
103
|
+
append(u)
|
|
104
|
+
i += 1
|
|
105
|
+
continue
|
|
106
|
+
reason = "illegal multibyte sequence"
|
|
107
|
+
end = i + 1
|
|
108
|
+
else:
|
|
109
|
+
# single-byte mappings (0xFF) never start a pair, so they can
|
|
110
|
+
# be emitted even when more data may follow
|
|
111
|
+
u = _b2u_get(c)
|
|
112
|
+
if u is not None:
|
|
113
|
+
append(u)
|
|
114
|
+
i += 1
|
|
115
|
+
continue
|
|
116
|
+
if not final:
|
|
117
|
+
break
|
|
118
|
+
reason = "incomplete multibyte sequence"
|
|
119
|
+
end = n
|
|
120
|
+
if error_handler is None:
|
|
121
|
+
error_handler = lookup_error(errors)
|
|
122
|
+
replacement, i = error_handler(UnicodeDecodeError(
|
|
123
|
+
"big5-uao", input, i, end, reason))
|
|
124
|
+
if i < 0:
|
|
125
|
+
i += n
|
|
126
|
+
append(replacement)
|
|
127
|
+
return out, i
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class Big5UAOCodec(Codec):
|
|
131
|
+
def encode(self, input, errors="strict"):
|
|
132
|
+
"""Encode str to bytes with the u2b table."""
|
|
133
|
+
return b"".join(_encode(input, errors)), len(input)
|
|
134
|
+
|
|
135
|
+
def decode(self, input, errors="strict"):
|
|
136
|
+
"""Decode bytes to str with the b2u table."""
|
|
137
|
+
input = bytes(input)
|
|
138
|
+
out, consumed = _decode(input, errors, True)
|
|
139
|
+
return "".join(out), consumed
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class Big5UAOIncrementalEncoder(IncrementalEncoder):
|
|
143
|
+
def encode(self, input, final=False):
|
|
144
|
+
return b"".join(_encode(input, self.errors))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class Big5UAOIncrementalDecoder(IncrementalDecoder):
|
|
148
|
+
def __init__(self, errors="strict"):
|
|
149
|
+
IncrementalDecoder.__init__(self, errors)
|
|
150
|
+
self.buffer = b""
|
|
151
|
+
|
|
152
|
+
def decode(self, input, final=False):
|
|
153
|
+
data = self.buffer + bytes(input)
|
|
154
|
+
out, consumed = _decode(data, self.errors, final)
|
|
155
|
+
self.buffer = data[consumed:]
|
|
156
|
+
return "".join(out)
|
|
157
|
+
|
|
158
|
+
def reset(self):
|
|
159
|
+
self.buffer = b""
|
|
160
|
+
|
|
161
|
+
def getstate(self):
|
|
162
|
+
return self.buffer, 0
|
|
163
|
+
|
|
164
|
+
def setstate(self, state):
|
|
165
|
+
self.buffer = state[0]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class Big5UAOStreamWriter(Big5UAOCodec, StreamWriter):
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Big5UAOStreamReader(Big5UAOCodec, StreamReader):
|
|
173
|
+
def decode(self, input, errors="strict"):
|
|
174
|
+
# Never treat the chunk as final: StreamReader keeps unconsumed
|
|
175
|
+
# bytes in its buffer, so a split multibyte sequence is completed
|
|
176
|
+
# by the next read instead of raising.
|
|
177
|
+
input = bytes(input)
|
|
178
|
+
out, consumed = _decode(input, errors, False)
|
|
179
|
+
return "".join(out), consumed
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
_codec_info = None
|
|
183
|
+
|
|
184
|
+
def _lookup(name):
|
|
185
|
+
if name in ("big5-uao", "big5uao", "big5_uao"):
|
|
186
|
+
global _codec_info
|
|
187
|
+
if _codec_info is None:
|
|
188
|
+
codec = Big5UAOCodec()
|
|
189
|
+
_codec_info = CodecInfo(
|
|
190
|
+
name="big5-uao",
|
|
191
|
+
encode=codec.encode,
|
|
192
|
+
decode=codec.decode,
|
|
193
|
+
incrementalencoder=Big5UAOIncrementalEncoder,
|
|
194
|
+
incrementaldecoder=Big5UAOIncrementalDecoder,
|
|
195
|
+
streamreader=Big5UAOStreamReader,
|
|
196
|
+
streamwriter=Big5UAOStreamWriter,
|
|
197
|
+
)
|
|
198
|
+
return _codec_info
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
REGISTERED = False
|
|
202
|
+
def register_uao():
|
|
203
|
+
global REGISTERED
|
|
204
|
+
if REGISTERED:
|
|
205
|
+
return
|
|
206
|
+
REGISTERED = True
|
|
207
|
+
register(_lookup)
|