scriptconv 0.0.3a2__tar.gz → 0.0.3a4__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.
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/PKG-INFO +4 -1
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/pyproject.toml +2 -1
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv/__init__.py +16 -3
- scriptconv-0.0.3a4/scriptconv/cangjie.py +63 -0
- scriptconv-0.0.3a4/scriptconv/py.typed +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv/readings.py +52 -4
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv/version.py +1 -1
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv.egg-info/PKG-INFO +4 -1
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv.egg-info/SOURCES.txt +4 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv.egg-info/requires.txt +4 -0
- scriptconv-0.0.3a4/tests/test_cangjie.py +51 -0
- scriptconv-0.0.3a4/tests/test_readings_zh.py +78 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/LICENSE +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/README.md +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/requirements.txt +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv/__main__.py +0 -0
- /scriptconv-0.0.3a2/scriptconv/py.typed → /scriptconv-0.0.3a4/scriptconv/data/__init__.py +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv/notation.py +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv/scripts.py +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv/translit.py +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv.egg-info/dependency_links.txt +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/scriptconv.egg-info/top_level.txt +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/setup.cfg +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/tests/test_cli.py +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/tests/test_examples.py +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/tests/test_notation.py +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/tests/test_readings.py +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/tests/test_scripts.py +0 -0
- {scriptconv-0.0.3a2 → scriptconv-0.0.3a4}/tests/test_translit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scriptconv
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3a4
|
|
4
4
|
Summary: Zero-dependency script & phoneme-notation core — ISO-15924 detection & metadata, IPA↔ARPABET/X-SAMPA/Lexique/Kirshenbaum/Cotovía/RFE, Buckwalter↔Arabic, Hangul→jamo, kana
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/TigreGotico/scriptconv
|
|
@@ -22,10 +22,13 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Provides-Extra: ja
|
|
24
24
|
Requires-Dist: pykakasi<3,>=2.3; extra == "ja"
|
|
25
|
+
Provides-Extra: zh
|
|
26
|
+
Requires-Dist: pypinyin<1,>=0.50; extra == "zh"
|
|
25
27
|
Provides-Extra: test
|
|
26
28
|
Requires-Dist: pytest; extra == "test"
|
|
27
29
|
Requires-Dist: pytest-timeout; extra == "test"
|
|
28
30
|
Requires-Dist: pykakasi<3,>=2.3; extra == "test"
|
|
31
|
+
Requires-Dist: pypinyin<1,>=0.50; extra == "test"
|
|
29
32
|
Dynamic: license-file
|
|
30
33
|
|
|
31
34
|
# scriptconv
|
|
@@ -34,7 +34,8 @@ dependencies = {file = ["requirements.txt"]}
|
|
|
34
34
|
|
|
35
35
|
[project.optional-dependencies]
|
|
36
36
|
ja = ["pykakasi>=2.3,<3"]
|
|
37
|
-
|
|
37
|
+
zh = ["pypinyin>=0.50,<1"]
|
|
38
|
+
test = ["pytest", "pytest-timeout", "pykakasi>=2.3,<3", "pypinyin>=0.50,<1"]
|
|
38
39
|
|
|
39
40
|
[project.urls]
|
|
40
41
|
Homepage = "https://github.com/TigreGotico/scriptconv"
|
|
@@ -18,11 +18,13 @@ translit:
|
|
|
18
18
|
Script-level decomposition and transliteration (Hangul → jamo,
|
|
19
19
|
Hiragana ↔ Katakana).
|
|
20
20
|
readings:
|
|
21
|
-
Dictionary-backed respelling (Japanese kanji → kana
|
|
22
|
-
|
|
21
|
+
Dictionary-backed respelling (Japanese kanji → kana via the ``ja``
|
|
22
|
+
extra; Chinese hanzi → pinyin/bopomofo via the ``zh`` extra).
|
|
23
|
+
cangjie:
|
|
24
|
+
Hanzi → Cangjie5 input codes (shape decomposition, vendored table).
|
|
23
25
|
|
|
24
26
|
Zero required runtime dependencies (stdlib only); the readings module
|
|
25
|
-
needs the optional ``ja``
|
|
27
|
+
needs the optional ``ja``/``zh`` extras.
|
|
26
28
|
"""
|
|
27
29
|
|
|
28
30
|
from scriptconv.scripts import (
|
|
@@ -68,6 +70,12 @@ from scriptconv.translit import (
|
|
|
68
70
|
from scriptconv.readings import (
|
|
69
71
|
to_hiragana,
|
|
70
72
|
to_katakana,
|
|
73
|
+
to_pinyin,
|
|
74
|
+
to_bopomofo,
|
|
75
|
+
)
|
|
76
|
+
from scriptconv.cangjie import (
|
|
77
|
+
cangjie_code,
|
|
78
|
+
to_cangjie,
|
|
71
79
|
)
|
|
72
80
|
|
|
73
81
|
__all__ = [
|
|
@@ -111,4 +119,9 @@ __all__ = [
|
|
|
111
119
|
# readings (dictionary-backed, needs scriptconv[ja])
|
|
112
120
|
"to_hiragana",
|
|
113
121
|
"to_katakana",
|
|
122
|
+
# cangjie (vendored Cangjie5 table)
|
|
123
|
+
"cangjie_code",
|
|
124
|
+
"to_cangjie",
|
|
125
|
+
"to_pinyin",
|
|
126
|
+
"to_bopomofo",
|
|
114
127
|
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Hanzi → Cangjie input codes (fifth generation, traditional table).
|
|
2
|
+
|
|
3
|
+
Cangjie codes encode a glyph's SHAPE — its decomposition into geometric
|
|
4
|
+
radicals — not its reading: 日 is ``a`` because of what it looks like, however
|
|
5
|
+
it is pronounced. That makes this a deterministic, table-driven orthographic
|
|
6
|
+
transcode like the rest of scriptconv's core: no reading dictionary, no
|
|
7
|
+
ambiguity, no phonology.
|
|
8
|
+
|
|
9
|
+
The table is vendored (gzipped, loaded lazily on first use) from the Cangjie5
|
|
10
|
+
project — https://github.com/Jackchows/Cangjie5, MIT License — so no runtime
|
|
11
|
+
download or extra dependency is needed. Where the upstream table lists
|
|
12
|
+
alternative codes for a glyph, the primary (first) code is kept.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import gzip
|
|
17
|
+
from importlib.resources import files
|
|
18
|
+
from typing import Dict, Optional
|
|
19
|
+
|
|
20
|
+
__all__ = ["cangjie_code", "to_cangjie"]
|
|
21
|
+
|
|
22
|
+
_TABLE: Optional[Dict[str, str]] = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _table() -> Dict[str, str]:
|
|
26
|
+
global _TABLE
|
|
27
|
+
if _TABLE is None:
|
|
28
|
+
_TABLE = {}
|
|
29
|
+
raw = files("scriptconv.data").joinpath("cangjie5_tc.tsv.gz").read_bytes()
|
|
30
|
+
for line in gzip.decompress(raw).decode("utf-8").splitlines():
|
|
31
|
+
if line.startswith("#"):
|
|
32
|
+
continue
|
|
33
|
+
ch, _, code = line.partition("\t")
|
|
34
|
+
_TABLE[ch] = code
|
|
35
|
+
return _TABLE
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cangjie_code(char: str) -> Optional[str]:
|
|
39
|
+
"""Return the Cangjie5 code for a single glyph, or None if unmapped."""
|
|
40
|
+
return _table().get(char)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def to_cangjie(text: str, sep: str = " ") -> str:
|
|
44
|
+
"""Transcode hanzi in *text* to their Cangjie5 codes.
|
|
45
|
+
|
|
46
|
+
Mapped glyphs become their code, joined by *sep*; runs of unmapped
|
|
47
|
+
characters (Latin, digits, punctuation, rare glyphs) pass through
|
|
48
|
+
unchanged as single tokens.
|
|
49
|
+
"""
|
|
50
|
+
table = _table()
|
|
51
|
+
tokens = []
|
|
52
|
+
last_raw = False
|
|
53
|
+
for ch in text:
|
|
54
|
+
code = table.get(ch)
|
|
55
|
+
if code is not None:
|
|
56
|
+
tokens.append(code)
|
|
57
|
+
last_raw = False
|
|
58
|
+
elif last_raw:
|
|
59
|
+
tokens[-1] += ch
|
|
60
|
+
else:
|
|
61
|
+
tokens.append(ch)
|
|
62
|
+
last_raw = True
|
|
63
|
+
return sep.join(tokens)
|
|
File without changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Dictionary-backed script respelling (Japanese kanji → kana).
|
|
1
|
+
"""Dictionary-backed script respelling (Japanese kanji → kana, Chinese hanzi → pinyin/bopomofo).
|
|
2
2
|
|
|
3
3
|
Unlike the table-driven modules (:mod:`scriptconv.translit`,
|
|
4
4
|
:mod:`scriptconv.notation`), converting kanji to kana is not a codepoint
|
|
@@ -8,15 +8,21 @@ dictionary's segmentation. This is still orthography — the output is how the
|
|
|
8
8
|
word is written in kana, exactly as an IME or furigana would render it — not
|
|
9
9
|
phonemization; no sound rules are applied.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
The same reasoning covers Chinese: a hanzi's pinyin or bopomofo spelling is a
|
|
12
|
+
lexical property with genuinely ambiguous readings (heteronyms such as 行
|
|
13
|
+
xíng/háng) resolved by a phrase dictionary — standard orthographic respelling,
|
|
14
|
+
not phonemization.
|
|
15
|
+
|
|
16
|
+
Because it needs reading dictionaries, this module requires optional
|
|
17
|
+
dependencies: ``pykakasi`` for Japanese (``pip install scriptconv[ja]``) and
|
|
18
|
+
``pypinyin`` for Chinese (``pip install scriptconv[zh]``). Importing the
|
|
13
19
|
module is always safe; the dependency is resolved on first conversion and a
|
|
14
20
|
missing dictionary raises :class:`ImportError` rather than silently returning
|
|
15
21
|
unconverted text.
|
|
16
22
|
"""
|
|
17
23
|
from __future__ import annotations
|
|
18
24
|
|
|
19
|
-
__all__ = ["to_hiragana", "to_katakana"]
|
|
25
|
+
__all__ = ["to_hiragana", "to_katakana", "to_pinyin", "to_bopomofo"]
|
|
20
26
|
|
|
21
27
|
_kakasi = None
|
|
22
28
|
|
|
@@ -69,3 +75,45 @@ def to_katakana(text: str) -> str:
|
|
|
69
75
|
def _is_japanese(ch: str) -> bool:
|
|
70
76
|
cp = ord(ch)
|
|
71
77
|
return cp in _HIRA or cp in _KANA or 0x4E00 <= cp <= 0x9FFF
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
_PINYIN_STYLES = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _pinyin(text: str, style_name: str) -> str:
|
|
84
|
+
global _PINYIN_STYLES
|
|
85
|
+
if _PINYIN_STYLES is None:
|
|
86
|
+
try:
|
|
87
|
+
from pypinyin import Style, lazy_pinyin
|
|
88
|
+
except ImportError:
|
|
89
|
+
raise ImportError(
|
|
90
|
+
"hanzi→pinyin/bopomofo conversion needs the phrase dictionary "
|
|
91
|
+
"from pypinyin — install with `pip install scriptconv[zh]`"
|
|
92
|
+
) from None
|
|
93
|
+
_PINYIN_STYLES = {"mark": Style.TONE, "number": Style.TONE3,
|
|
94
|
+
"none": Style.NORMAL, "bopomofo": Style.BOPOMOFO,
|
|
95
|
+
"_fn": lazy_pinyin}
|
|
96
|
+
if style_name not in _PINYIN_STYLES:
|
|
97
|
+
raise ValueError(f"tone must be one of 'mark', 'number', 'none' — got {style_name!r}")
|
|
98
|
+
tokens = _PINYIN_STYLES["_fn"](text, style=_PINYIN_STYLES[style_name])
|
|
99
|
+
return " ".join(" ".join(tokens).split())
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def to_pinyin(text: str, tone: str = "mark") -> str:
|
|
103
|
+
"""Respell Chinese text in pinyin, syllables space-separated.
|
|
104
|
+
|
|
105
|
+
Hanzi are resolved to their dictionary reading (heteronyms decided by
|
|
106
|
+
pypinyin's phrase dictionary); non-Chinese characters pass through.
|
|
107
|
+
``tone`` selects the tone spelling: ``"mark"`` (``zhōng``, default),
|
|
108
|
+
``"number"`` (``zhong1``) or ``"none"`` (``zhong``).
|
|
109
|
+
"""
|
|
110
|
+
return _pinyin(text, tone)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def to_bopomofo(text: str) -> str:
|
|
114
|
+
"""Respell Chinese text in bopomofo (zhuyin), syllables space-separated.
|
|
115
|
+
|
|
116
|
+
Hanzi are resolved to their dictionary reading; non-Chinese characters
|
|
117
|
+
pass through. Tone marks follow zhuyin convention (first tone unmarked).
|
|
118
|
+
"""
|
|
119
|
+
return _pinyin(text, "bopomofo")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scriptconv
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3a4
|
|
4
4
|
Summary: Zero-dependency script & phoneme-notation core — ISO-15924 detection & metadata, IPA↔ARPABET/X-SAMPA/Lexique/Kirshenbaum/Cotovía/RFE, Buckwalter↔Arabic, Hangul→jamo, kana
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/TigreGotico/scriptconv
|
|
@@ -22,10 +22,13 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Provides-Extra: ja
|
|
24
24
|
Requires-Dist: pykakasi<3,>=2.3; extra == "ja"
|
|
25
|
+
Provides-Extra: zh
|
|
26
|
+
Requires-Dist: pypinyin<1,>=0.50; extra == "zh"
|
|
25
27
|
Provides-Extra: test
|
|
26
28
|
Requires-Dist: pytest; extra == "test"
|
|
27
29
|
Requires-Dist: pytest-timeout; extra == "test"
|
|
28
30
|
Requires-Dist: pykakasi<3,>=2.3; extra == "test"
|
|
31
|
+
Requires-Dist: pypinyin<1,>=0.50; extra == "test"
|
|
29
32
|
Dynamic: license-file
|
|
30
33
|
|
|
31
34
|
# scriptconv
|
|
@@ -4,6 +4,7 @@ pyproject.toml
|
|
|
4
4
|
requirements.txt
|
|
5
5
|
scriptconv/__init__.py
|
|
6
6
|
scriptconv/__main__.py
|
|
7
|
+
scriptconv/cangjie.py
|
|
7
8
|
scriptconv/notation.py
|
|
8
9
|
scriptconv/py.typed
|
|
9
10
|
scriptconv/readings.py
|
|
@@ -15,9 +16,12 @@ scriptconv.egg-info/SOURCES.txt
|
|
|
15
16
|
scriptconv.egg-info/dependency_links.txt
|
|
16
17
|
scriptconv.egg-info/requires.txt
|
|
17
18
|
scriptconv.egg-info/top_level.txt
|
|
19
|
+
scriptconv/data/__init__.py
|
|
20
|
+
tests/test_cangjie.py
|
|
18
21
|
tests/test_cli.py
|
|
19
22
|
tests/test_examples.py
|
|
20
23
|
tests/test_notation.py
|
|
21
24
|
tests/test_readings.py
|
|
25
|
+
tests/test_readings_zh.py
|
|
22
26
|
tests/test_scripts.py
|
|
23
27
|
tests/test_translit.py
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from scriptconv import cangjie_code, to_cangjie
|
|
4
|
+
from scriptconv import cangjie
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestCangjieCode(unittest.TestCase):
|
|
8
|
+
def test_official_single_radical_glyphs(self):
|
|
9
|
+
self.assertEqual(cangjie_code("日"), "a")
|
|
10
|
+
self.assertEqual(cangjie_code("木"), "d")
|
|
11
|
+
|
|
12
|
+
def test_multi_radical_glyph(self):
|
|
13
|
+
self.assertEqual(cangjie_code("昌"), "aa")
|
|
14
|
+
|
|
15
|
+
def test_unmapped_returns_none(self):
|
|
16
|
+
self.assertIsNone(cangjie_code("X"))
|
|
17
|
+
self.assertIsNone(cangjie_code("1"))
|
|
18
|
+
self.assertIsNone(cangjie_code(" "))
|
|
19
|
+
|
|
20
|
+
def test_codes_are_lowercase_alpha(self):
|
|
21
|
+
table = cangjie._table()
|
|
22
|
+
self.assertGreater(len(table), 100000)
|
|
23
|
+
sample = list(table.values())[:5000]
|
|
24
|
+
self.assertTrue(all(c.isalpha() and c.islower() for c in sample))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestToCangjie(unittest.TestCase):
|
|
28
|
+
def test_all_hanzi_sentence(self):
|
|
29
|
+
self.assertEqual(to_cangjie("倉頡"), "oiar grmbc")
|
|
30
|
+
|
|
31
|
+
def test_custom_separator(self):
|
|
32
|
+
self.assertEqual(to_cangjie("倉頡", sep="|"), "oiar|grmbc")
|
|
33
|
+
|
|
34
|
+
def test_unmapped_runs_stay_contiguous(self):
|
|
35
|
+
self.assertEqual(to_cangjie("我有ABC 123!"), "hqi kb ABC 123!")
|
|
36
|
+
|
|
37
|
+
def test_non_chinese_only_passes_through_verbatim(self):
|
|
38
|
+
self.assertEqual(to_cangjie("OVOS 123!"), "OVOS 123!")
|
|
39
|
+
|
|
40
|
+
def test_empty_string(self):
|
|
41
|
+
self.assertEqual(to_cangjie(""), "")
|
|
42
|
+
|
|
43
|
+
def test_table_loaded_once(self):
|
|
44
|
+
to_cangjie("日")
|
|
45
|
+
first = cangjie._TABLE
|
|
46
|
+
cangjie_code("木")
|
|
47
|
+
self.assertIs(cangjie._TABLE, first)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
unittest.main()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import unittest
|
|
3
|
+
from unittest import mock
|
|
4
|
+
|
|
5
|
+
from scriptconv import to_pinyin, to_bopomofo
|
|
6
|
+
from scriptconv import readings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestToPinyin(unittest.TestCase):
|
|
10
|
+
def test_hanzi_resolved_with_tone_marks(self):
|
|
11
|
+
self.assertEqual(to_pinyin("中国人"), "zhōng guó rén")
|
|
12
|
+
|
|
13
|
+
def test_tone_number_style(self):
|
|
14
|
+
self.assertEqual(to_pinyin("中国人", tone="number"), "zhong1 guo2 ren2")
|
|
15
|
+
|
|
16
|
+
def test_tone_none_style(self):
|
|
17
|
+
self.assertEqual(to_pinyin("中国人", tone="none"), "zhong guo ren")
|
|
18
|
+
|
|
19
|
+
def test_heteronym_resolved_by_phrase_context(self):
|
|
20
|
+
# 行 reads háng in 银行 (bank) but xíng in 行走 (to walk)
|
|
21
|
+
self.assertIn("háng", to_pinyin("银行"))
|
|
22
|
+
self.assertIn("xíng", to_pinyin("行走"))
|
|
23
|
+
|
|
24
|
+
def test_non_chinese_passes_through(self):
|
|
25
|
+
self.assertEqual(to_pinyin("OVOS 123!"), "OVOS 123!")
|
|
26
|
+
|
|
27
|
+
def test_mixed_script_sentence(self):
|
|
28
|
+
out = to_pinyin("我有3个apple")
|
|
29
|
+
self.assertIn("wǒ", out)
|
|
30
|
+
self.assertIn("apple", out)
|
|
31
|
+
|
|
32
|
+
def test_invalid_tone_raises_value_error(self):
|
|
33
|
+
with self.assertRaises(ValueError):
|
|
34
|
+
to_pinyin("中", tone="fancy")
|
|
35
|
+
|
|
36
|
+
def test_empty_string(self):
|
|
37
|
+
self.assertEqual(to_pinyin(""), "")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestToBopomofo(unittest.TestCase):
|
|
41
|
+
def test_hanzi_resolved_to_zhuyin(self):
|
|
42
|
+
self.assertEqual(to_bopomofo("中国"), "ㄓㄨㄥ ㄍㄨㄛˊ")
|
|
43
|
+
|
|
44
|
+
def test_first_tone_unmarked_second_tone_marked(self):
|
|
45
|
+
out = to_bopomofo("中国")
|
|
46
|
+
self.assertNotIn("ˉ", out)
|
|
47
|
+
self.assertIn("ˊ", out)
|
|
48
|
+
|
|
49
|
+
def test_non_chinese_passes_through(self):
|
|
50
|
+
self.assertEqual(to_bopomofo("OVOS 123!"), "OVOS 123!")
|
|
51
|
+
|
|
52
|
+
def test_empty_string(self):
|
|
53
|
+
self.assertEqual(to_bopomofo(""), "")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TestDependencyBoundary(unittest.TestCase):
|
|
57
|
+
def test_missing_pypinyin_raises_with_install_hint(self):
|
|
58
|
+
with mock.patch.object(readings, "_PINYIN_STYLES", None), \
|
|
59
|
+
mock.patch.dict(sys.modules, {"pypinyin": None}):
|
|
60
|
+
with self.assertRaises(ImportError) as ctx:
|
|
61
|
+
to_pinyin("中国")
|
|
62
|
+
self.assertIn("scriptconv[zh]", str(ctx.exception))
|
|
63
|
+
|
|
64
|
+
def test_style_table_is_cached(self):
|
|
65
|
+
to_pinyin("中国")
|
|
66
|
+
first = readings._PINYIN_STYLES
|
|
67
|
+
to_bopomofo("中国")
|
|
68
|
+
self.assertIs(readings._PINYIN_STYLES, first)
|
|
69
|
+
|
|
70
|
+
def test_chinese_and_japanese_backends_independent(self):
|
|
71
|
+
# pypinyin absence must not break Japanese conversion
|
|
72
|
+
with mock.patch.object(readings, "_PINYIN_STYLES", None), \
|
|
73
|
+
mock.patch.dict(sys.modules, {"pypinyin": None}):
|
|
74
|
+
self.assertEqual(readings.to_hiragana("日本語"), "にほんご")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|