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.
Files changed (100) hide show
  1. {pyptt-2.2.3 → pyptt-2.2.5}/PKG-INFO +1 -2
  2. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/PTT.py +51 -4
  3. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/__init__.py +1 -1
  4. pyptt-2.2.5/PyPtt/_api_set_signature_file.py +86 -0
  5. pyptt-2.2.5/PyPtt/_uao/LICENSE +26 -0
  6. pyptt-2.2.5/PyPtt/_uao/__init__.py +207 -0
  7. pyptt-2.2.5/PyPtt/_uao/b2u.py +2 -0
  8. pyptt-2.2.5/PyPtt/_uao/u2b.py +2 -0
  9. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/lang_en_US.py +1 -0
  10. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/lang_zh_TW.py +1 -0
  11. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/screens.py +1 -1
  12. pyptt-2.2.5/PyPtt/ssl_config.py +40 -0
  13. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt.egg-info/PKG-INFO +1 -2
  14. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt.egg-info/SOURCES.txt +6 -0
  15. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt.egg-info/requires.txt +0 -1
  16. {pyptt-2.2.3 → pyptt-2.2.5}/pyproject.toml +2 -3
  17. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_comment.py +2 -1
  18. pyptt-2.2.5/tests/test_set_signature_file.py +32 -0
  19. pyptt-2.2.3/PyPtt/ssl_config.py +0 -24
  20. {pyptt-2.2.3 → pyptt-2.2.5}/LICENSE +0 -0
  21. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_bucket.py +0 -0
  22. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_call_status.py +0 -0
  23. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_change_pw.py +0 -0
  24. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_comment.py +0 -0
  25. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_del_post.py +0 -0
  26. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_board_info.py +0 -0
  27. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_board_list.py +0 -0
  28. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_bottom_post_list.py +0 -0
  29. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_favourite_board.py +0 -0
  30. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_newest_index.py +0 -0
  31. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_post.py +0 -0
  32. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_post_index.py +0 -0
  33. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_post_list.py +0 -0
  34. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_time.py +0 -0
  35. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_user.py +0 -0
  36. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_get_waterball.py +0 -0
  37. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_give_money.py +0 -0
  38. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_has_new_mail.py +0 -0
  39. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_loginout.py +0 -0
  40. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_lottery.py +0 -0
  41. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_mail.py +0 -0
  42. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_mark_post.py +0 -0
  43. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_post.py +0 -0
  44. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_reply_post.py +0 -0
  45. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_search_user.py +0 -0
  46. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_set_board_title.py +0 -0
  47. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/_api_util.py +0 -0
  48. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/api_server.py +0 -0
  49. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/check_value.py +0 -0
  50. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/command.py +0 -0
  51. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/config.py +0 -0
  52. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/connect_core.py +0 -0
  53. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/data_type.py +0 -0
  54. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/exceptions.py +0 -0
  55. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/i18n.py +0 -0
  56. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/lib_util.py +0 -0
  57. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/log.py +0 -0
  58. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt/service.py +0 -0
  59. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt.egg-info/dependency_links.txt +0 -0
  60. {pyptt-2.2.3 → pyptt-2.2.5}/PyPtt.egg-info/top_level.txt +0 -0
  61. {pyptt-2.2.3 → pyptt-2.2.5}/README.md +0 -0
  62. {pyptt-2.2.3 → pyptt-2.2.5}/setup.cfg +0 -0
  63. {pyptt-2.2.3 → pyptt-2.2.5}/setup.py +0 -0
  64. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_bucket.py +0 -0
  65. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_change_pw.py +0 -0
  66. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_del_post.py +0 -0
  67. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_del_post_unit.py +0 -0
  68. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_exceptions.py +0 -0
  69. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_fast_post.py +0 -0
  70. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_board_info.py +0 -0
  71. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_board_list.py +0 -0
  72. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_bottom_post_list.py +0 -0
  73. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_content_formula.py +0 -0
  74. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_favourite_boards.py +0 -0
  75. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_mail.py +0 -0
  76. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_newest_index.py +0 -0
  77. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_post.py +0 -0
  78. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_post_list.py +0 -0
  79. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_post_list_parser.py +0 -0
  80. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_post_parser.py +0 -0
  81. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_time.py +0 -0
  82. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_user.py +0 -0
  83. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_get_waterball.py +0 -0
  84. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_give_money.py +0 -0
  85. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_i18n.py +0 -0
  86. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_init.py +0 -0
  87. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_logger.py +0 -0
  88. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_login_logout.py +0 -0
  89. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_lottery.py +0 -0
  90. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_mail_send_and_del.py +0 -0
  91. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_mark_post.py +0 -0
  92. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_parse_query_post.py +0 -0
  93. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_post.py +0 -0
  94. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_post_anonymous.py +0 -0
  95. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_pyte_parity.py +0 -0
  96. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_reply_post.py +0 -0
  97. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_search_user.py +0 -0
  98. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_service.py +0 -0
  99. {pyptt-2.2.3 → pyptt-2.2.5}/tests/test_set_board_title.py +0 -0
  100. {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
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: 樂透資訊,包含 price(每張價格)、total(已下注總額)、
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: 下注結果,包含 board、item、name(選項名稱)、amount、
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
 
@@ -1,4 +1,4 @@
1
- __version__ = '2.2.3'
1
+ __version__ = '2.2.5'
2
2
  __author__ = 'CodingMan'
3
3
  __email__ = 'pttcodingman@gmail.com'
4
4
 
@@ -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)