scriptconv 0.0.3a17__tar.gz → 0.0.3a19__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 (81) hide show
  1. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/PKG-INFO +3 -2
  2. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/README.md +2 -1
  3. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/graph.py +2 -0
  4. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/notation.py +123 -5
  5. scriptconv-0.0.3a19/scriptconv/phonemizers/_thirdparty/bw2ipa.py +2 -0
  6. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/enums.py +2 -1
  7. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/version.py +1 -1
  8. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv.egg-info/PKG-INFO +3 -2
  9. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_notation.py +72 -4
  10. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_phonemizers_base.py +1 -1
  11. scriptconv-0.0.3a17/scriptconv/phonemizers/_thirdparty/bw2ipa.py +0 -207
  12. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/LICENSE +0 -0
  13. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/pyproject.toml +0 -0
  14. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/requirements.txt +0 -0
  15. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/__init__.py +0 -0
  16. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/__main__.py +0 -0
  17. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/cangjie.py +0 -0
  18. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/conventions.py +0 -0
  19. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/data/__init__.py +0 -0
  20. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/data/cangjie5_tc.tsv.gz +0 -0
  21. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/__init__.py +0 -0
  22. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/__init__.py +0 -0
  23. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/hangul2ipa.py +0 -0
  24. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/ko_tables/aspiration.csv +0 -0
  25. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/ko_tables/assimilation.csv +0 -0
  26. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/ko_tables/double_coda.csv +0 -0
  27. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/ko_tables/hanja.tsv +0 -0
  28. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/ko_tables/ipa.csv +0 -0
  29. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/ko_tables/neutralization.csv +0 -0
  30. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/ko_tables/tensification.csv +0 -0
  31. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/ko_tables/yale.csv +0 -0
  32. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/shami/__init__.py +0 -0
  33. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/shami/codeswitch.py +0 -0
  34. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/shami/diacritize.py +0 -0
  35. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/shami/dialectal.py +0 -0
  36. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/shami/english_g2p.py +0 -0
  37. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/shami/espeak.py +0 -0
  38. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/shami/frontend.py +0 -0
  39. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/shami/levantine_g2p.py +0 -0
  40. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/shami/normalize.py +0 -0
  41. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/shami/phoneme_inventory.py +0 -0
  42. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/_thirdparty/zh_num.py +0 -0
  43. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/ar.py +0 -0
  44. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/base.py +0 -0
  45. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/en.py +0 -0
  46. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/eu.py +0 -0
  47. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/fa.py +0 -0
  48. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/gl.py +0 -0
  49. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/he.py +0 -0
  50. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/ja.py +0 -0
  51. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/ko.py +0 -0
  52. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/mul.py +0 -0
  53. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/mwl.py +0 -0
  54. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/o2ipa.py +0 -0
  55. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/pt.py +0 -0
  56. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/registry.py +0 -0
  57. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/shami.py +0 -0
  58. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/vi.py +0 -0
  59. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/phonemizers/zh.py +0 -0
  60. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/py.typed +0 -0
  61. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/readings.py +0 -0
  62. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/scripts.py +0 -0
  63. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv/translit.py +0 -0
  64. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv.egg-info/SOURCES.txt +0 -0
  65. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv.egg-info/dependency_links.txt +0 -0
  66. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv.egg-info/requires.txt +0 -0
  67. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/scriptconv.egg-info/top_level.txt +0 -0
  68. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/setup.cfg +0 -0
  69. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_arpa_stress.py +0 -0
  70. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_cangjie.py +0 -0
  71. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_cli.py +0 -0
  72. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_conventions.py +0 -0
  73. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_errors_policy.py +0 -0
  74. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_examples.py +0 -0
  75. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_graph.py +0 -0
  76. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_phonemizers_cjk_ar.py +0 -0
  77. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_readings.py +0 -0
  78. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_readings_zh.py +0 -0
  79. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_scripts.py +0 -0
  80. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_scripts_stressonnx_compat.py +0 -0
  81. {scriptconv-0.0.3a17 → scriptconv-0.0.3a19}/tests/test_translit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptconv
3
- Version: 0.0.3a17
3
+ Version: 0.0.3a19
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
@@ -296,7 +296,8 @@ below describes the per-converter default.
296
296
  |----------|----------------------------------|----------------------------------|-------------------------|
297
297
  | **ARPABET** | **Lossless with `stress=True`** (digits ↔ IPA `ˈ`/`ˌ` before the stressed vowel; residues: extended-ARPABET `AX` normalises to CMUdict's `AH0` spelling, and `AH0 R` fuses to the r-colored `AXR0` — stable from the IPA side). Default `stress=False` drops digits and merges `AH0`↔`AX` | **Lossy** — ARPABET is an English-only inventory, so any IPA symbol outside it becomes the *unknown* placeholder | `arpa_to_ipa`: passed through unchanged. `ipa_to_arpa`: diacritics and suprasegmentals are dropped; other out-of-inventory symbols follow `errors=` (default `"replace"` → `?`) |
298
298
  | **X-SAMPA** | Exact for all canonical symbols | Exact except aliases (`f\`→ɸ, `&`→æ) normalise to their canonical spelling | Passed through unchanged |
299
- | **Buckwalter ↔ Arabic** | Exact (the `^` shadda alias normalises to canonical `~`; precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Passed through unchanged |
299
+ | **Buckwalter ↔ Arabic** | Exact (precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Follows `errors=` (default: passed through) |
300
+ | **Mantoq → IPA** | One-directional (gemination/word markers consumed; no IPA→Mantoq) | — | Follows `errors=` (default: passed through) |
300
301
  | **Lexique ↔ IPA** | Exact except the `°`/`3` schwa pair (both → `ə`; reverse always → `°`) | Exact | Passed through unchanged |
301
302
  | **Kirshenbaum ↔ IPA** | Exact | **Lossy** — restricted ASCII inventory; IPA outside it passes through | Passed through unchanged |
302
303
  | **Cotovía ↔ IPA** | Exact except the three `L`/`Z`/`jj` symbols for `ʎ` normalise to `L` | **Lossy** — Galician/Spanish inventory; IPA outside it passes through | Passed through unchanged |
@@ -179,7 +179,8 @@ below describes the per-converter default.
179
179
  |----------|----------------------------------|----------------------------------|-------------------------|
180
180
  | **ARPABET** | **Lossless with `stress=True`** (digits ↔ IPA `ˈ`/`ˌ` before the stressed vowel; residues: extended-ARPABET `AX` normalises to CMUdict's `AH0` spelling, and `AH0 R` fuses to the r-colored `AXR0` — stable from the IPA side). Default `stress=False` drops digits and merges `AH0`↔`AX` | **Lossy** — ARPABET is an English-only inventory, so any IPA symbol outside it becomes the *unknown* placeholder | `arpa_to_ipa`: passed through unchanged. `ipa_to_arpa`: diacritics and suprasegmentals are dropped; other out-of-inventory symbols follow `errors=` (default `"replace"` → `?`) |
181
181
  | **X-SAMPA** | Exact for all canonical symbols | Exact except aliases (`f\`→ɸ, `&`→æ) normalise to their canonical spelling | Passed through unchanged |
182
- | **Buckwalter ↔ Arabic** | Exact (the `^` shadda alias normalises to canonical `~`; precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Passed through unchanged |
182
+ | **Buckwalter ↔ Arabic** | Exact (precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Follows `errors=` (default: passed through) |
183
+ | **Mantoq → IPA** | One-directional (gemination/word markers consumed; no IPA→Mantoq) | — | Follows `errors=` (default: passed through) |
183
184
  | **Lexique ↔ IPA** | Exact except the `°`/`3` schwa pair (both → `ə`; reverse always → `°`) | Exact | Passed through unchanged |
184
185
  | **Kirshenbaum ↔ IPA** | Exact | **Lossy** — restricted ASCII inventory; IPA outside it passes through | Passed through unchanged |
185
186
  | **Cotovía ↔ IPA** | Exact except the three `L`/`Z`/`jj` symbols for `ʎ` normalise to `L` | **Lossy** — Galician/Spanish inventory; IPA outside it passes through | Passed through unchanged |
@@ -170,6 +170,8 @@ REPRESENTATIONS: Dict[str, Representation] = {r.id: r for r in (
170
170
  Representation("kirshenbaum", "notation", reference="Kirshenbaum ASCII-IPA"),
171
171
  Representation("cotovia", "notation", reference="Cotovía (Galician TTS)"),
172
172
  Representation("rfe", "notation", reference="Revista de Filología Española"),
173
+ Representation("mantoq", "notation", system="mantoq",
174
+ reference="Halabi Arabic-Phonetiser phoneme inventory"),
173
175
  Representation("buckwalter", "notation", script="Latn",
174
176
  reference="Buckwalter Arabic transliteration"),
175
177
  Representation("arabic", "orthography", script="Arab",
@@ -56,7 +56,8 @@ class Notation(str, Enum):
56
56
  LEXIQUE = "lexique" # Lexique one-char-per-phoneme French notation
57
57
  KIRSHENBAUM = "kirshenbaum" # ASCII-IPA (espeak-ng native notation)
58
58
  COTOVIA = "cotovia" # Universidade de Vigo Cotovía TTS notation (gl)
59
- RFE = "rfe" # Revista de Filología Española phonetic alphabet
59
+ RFE = "rfe"
60
+ MANTOQ = "mantoq" # Revista de Filología Española phonetic alphabet
60
61
 
61
62
  def __repr__(self) -> str:
62
63
  return f"Notation.{self.name}"
@@ -523,7 +524,8 @@ _BW_TO_ARABIC: dict[str, str] = {
523
524
  "K": "ٍ", # tanwin kasr
524
525
  # Special
525
526
  "_": "ـ", # tatweel
526
- "^": "ّ", # shadda alias
527
+ "{": "ٱ", # alef wasla
528
+ "`": "ٰ", # dagger (superscript) alef
527
529
  }
528
530
 
529
531
  _ARABIC_TO_BW: dict[str, str] = {v: k for k, v in _BW_TO_ARABIC.items()}
@@ -532,9 +534,6 @@ _ARABIC_TO_BW["\uFEFB"] = "lA" # لا lam + alef ligature
532
534
  _ARABIC_TO_BW["\uFEF9"] = "l<" # لإ lam + alef hamza below
533
535
  _ARABIC_TO_BW["\uFEF7"] = "l>" # لأ lam + alef hamza above
534
536
  _ARABIC_TO_BW["\uFEF8"] = "l|" # لآ lam + alef madda
535
- # The reverse comprehension lets the "^" shadda alias win over canonical "~";
536
- # restore the standard Buckwalter shadda so round-trips stay faithful.
537
- _ARABIC_TO_BW["\u0651"] = "~" # shadda -> canonical "~", not the "^" alias
538
537
 
539
538
 
540
539
  def buckwalter_to_arabic(bw: str, errors: str = "pass") -> str:
@@ -1005,6 +1004,118 @@ def ipa_to_rfe(ipa: str, errors: str = "pass") -> str:
1005
1004
  return "".join(result)
1006
1005
 
1007
1006
 
1007
+
1008
+ # ---------------------------------------------------------------------------
1009
+ # Mantoq → IPA
1010
+ #
1011
+ # Mantoq is the phonetic alphabet of Nawar Halabi's Arabic-Phonetiser (the
1012
+ # inventory emitted by the mantoq G2P pipeline): an ASCII phoneme notation
1013
+ # with Buckwalter-derived consonant letters (deviating where the phoneme set
1014
+ # requires — "^" is θ here, "v" a loan phoneme), stress/emphatic-free short
1015
+ # and long vowels ("a"/"aa"/"aaaa"), a gemination marker ``_dbl_`` and a word
1016
+ # separator ``_+_``. The token→IPA mapping below is authored in-house from
1017
+ # the inventory's phonetic values — the phonetiser CODE is CC BY-NC and lives
1018
+ # elsewhere; a factual symbol table is not encumbered by it. Published
1019
+ # models trained on mantoq sequences make this notation worth transcoding.
1020
+ # One-directional: IPA → mantoq is not defined.
1021
+ # ---------------------------------------------------------------------------
1022
+
1023
+ _MANTOQ_CONSONANTS: dict[str, str] = {
1024
+ "b": "b", "t": "t", "^": "θ", "j": "d͡ʒ", "H": "ħ", "x": "x",
1025
+ "d": "d", "*": "ð", "r": "r", "z": "z", "s": "s", "$": "ʃ",
1026
+ "S": "sˤ", "D": "dˤ", "T": "tˤ", "Z": "ðˤ", "E": "ʕ", "g": "ɣ",
1027
+ "f": "f", "q": "q", "k": "k", "l": "l", "m": "m", "n": "n",
1028
+ "h": "h", "w": "w", "y": "j", "v": "v",
1029
+ }
1030
+
1031
+ _MANTOQ_VOWELS: dict[str, str] = {
1032
+ "a": "a", "aa": "aː", "aaaa": "aːː",
1033
+ "i": "i", "ii": "iː",
1034
+ "u": "u", "uu": "uː",
1035
+ }
1036
+
1037
+ _MANTOQ_MULTI = ("_dbl_", "_+_", "_sil_", "_eos_", "_pad_",
1038
+ "aaaa", "aa", "ii", "uu")
1039
+
1040
+
1041
+ def _tokenize_mantoq(text: str) -> list[str]:
1042
+ tokens = []
1043
+ i = 0
1044
+ while i < len(text):
1045
+ for sym in _MANTOQ_MULTI:
1046
+ if text.startswith(sym, i):
1047
+ tokens.append(sym)
1048
+ i += len(sym)
1049
+ break
1050
+ else:
1051
+ tokens.append(text[i])
1052
+ i += 1
1053
+ return tokens
1054
+
1055
+
1056
+ def mantoq_to_ipa(mantoq: str, errors: str = "pass") -> str:
1057
+ """Convert a Mantoq phoneme string to IPA.
1058
+
1059
+ ``_dbl_`` lengthens/geminates the preceding symbol (``ː``), ``_+_``
1060
+ becomes a space, ``<`` is the glottal stop. Unknown characters follow
1061
+ the ``errors`` policy.
1062
+
1063
+ Examples
1064
+ --------
1065
+ >>> mantoq_to_ipa("s a l aa m")
1066
+ 's a l aː m'
1067
+ >>> mantoq_to_ipa("b_dbl_a")
1068
+ 'bːa'
1069
+
1070
+ The inventory's special tokens are honoured: ``_sil_`` becomes a space,
1071
+ ``_eos_``/``_pad_`` are dropped.
1072
+ """
1073
+ out: list[str] = []
1074
+ last_tok = None
1075
+ for pos, tok in enumerate(_tokenize_mantoq(mantoq)):
1076
+ if tok == "_dbl_":
1077
+ # upstream emits _dbl_ only by splitting a doubled CONSONANT
1078
+ # (mantoq buck/tokenization.py: `phon not in vowels and
1079
+ # phon[0] == phon[1]`); geminate it with a length mark. A
1080
+ # vowel+_dbl_ sequence does not occur in genuine mantoq output
1081
+ # and is left unchanged.
1082
+ if out and last_tok in _MANTOQ_CONSONANTS \
1083
+ and not out[-1].endswith("ː"):
1084
+ out[-1] += "ː"
1085
+ continue
1086
+ if tok == "_sil_":
1087
+ out.append(" ")
1088
+ last_tok = None
1089
+ continue
1090
+ if tok in ("_eos_", "_pad_"):
1091
+ last_tok = None
1092
+ continue
1093
+ if tok == "_+_":
1094
+ out.append(" ")
1095
+ last_tok = None
1096
+ continue
1097
+ if tok == "<":
1098
+ out.append("ʔ")
1099
+ last_tok = tok
1100
+ continue
1101
+ if tok in _MANTOQ_VOWELS:
1102
+ out.append(_MANTOQ_VOWELS[tok])
1103
+ elif tok in _MANTOQ_CONSONANTS:
1104
+ out.append(_MANTOQ_CONSONANTS[tok])
1105
+ elif tok.isspace() or tok in ".,;:!?()[]{}\"'":
1106
+ out.append(tok)
1107
+ last_tok = None
1108
+ continue
1109
+ else:
1110
+ res = _unknown(tok, pos, "mantoq", errors)
1111
+ if res:
1112
+ out.append(res)
1113
+ last_tok = None
1114
+ continue
1115
+ last_tok = tok
1116
+ return "".join(out)
1117
+
1118
+
1008
1119
  # ---------------------------------------------------------------------------
1009
1120
  # convert — facade routing through IPA where no direct map exists
1010
1121
  # ---------------------------------------------------------------------------
@@ -1069,6 +1180,7 @@ def convert(text: str, src: str | Notation, dst: str | Notation,
1069
1180
  # ---------------------------------------------------------------------------
1070
1181
 
1071
1182
  _TO_IPA: dict[Notation, "callable"] = {
1183
+ Notation.MANTOQ: mantoq_to_ipa,
1072
1184
  Notation.ARPA: arpa_to_ipa,
1073
1185
  Notation.XSAMPA: xsampa_to_ipa,
1074
1186
  Notation.LEXIQUE: lexique_to_ipa,
@@ -1194,6 +1306,12 @@ NOTATION_INFO: dict[Notation, NotationInfo] = {
1194
1306
  token_separated=False,
1195
1307
  reference="RFE phonetic alphabet (Revista de Filología Española, 1915)",
1196
1308
  ),
1309
+ Notation.MANTOQ: NotationInfo(
1310
+ Notation.MANTOQ, lossless_to_ipa=False, lossless_from_ipa=False,
1311
+ token_separated=False,
1312
+ reference="Phonetic alphabet of Nawar Halabi's Arabic-Phonetiser "
1313
+ "(mantoq G2P inventory); one-directional to IPA",
1314
+ ),
1197
1315
  Notation.BUCKWALTER: NotationInfo(
1198
1316
  Notation.BUCKWALTER, lossless_to_ipa=True, lossless_from_ipa=False,
1199
1317
  token_separated=False,
@@ -0,0 +1,2 @@
1
+ """Compatibility shim — the Mantoq → IPA converter lives in scriptconv.notation."""
2
+ from scriptconv.notation import mantoq_to_ipa # noqa: F401
@@ -27,7 +27,8 @@ class Alphabet(str, Enum):
27
27
  ERAAB = "eraab" # fa
28
28
  COTOVIA = "cotovia" # gl
29
29
  HANZI = "hanzi" # zh
30
- BUCKWALTER = "buckwalter" # ar
30
+ BUCKWALTER = "buckwalter"
31
+ MANTOQ = "mantoq" # ar — Halabi Arabic-Phonetiser inventory # ar
31
32
  CANGJIE = "cangjie" # zh (Cangjie input method)
32
33
  GRAPHEMES = "graphemes" # plain text / grapheme input (user-side)
33
34
 
@@ -2,7 +2,7 @@
2
2
  VERSION_MAJOR = 0
3
3
  VERSION_MINOR = 0
4
4
  VERSION_BUILD = 3
5
- VERSION_ALPHA = 17
5
+ VERSION_ALPHA = 19
6
6
  # END_VERSION_BLOCK
7
7
 
8
8
  VERSION_STR = f"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_BUILD}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptconv
3
- Version: 0.0.3a17
3
+ Version: 0.0.3a19
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
@@ -296,7 +296,8 @@ below describes the per-converter default.
296
296
  |----------|----------------------------------|----------------------------------|-------------------------|
297
297
  | **ARPABET** | **Lossless with `stress=True`** (digits ↔ IPA `ˈ`/`ˌ` before the stressed vowel; residues: extended-ARPABET `AX` normalises to CMUdict's `AH0` spelling, and `AH0 R` fuses to the r-colored `AXR0` — stable from the IPA side). Default `stress=False` drops digits and merges `AH0`↔`AX` | **Lossy** — ARPABET is an English-only inventory, so any IPA symbol outside it becomes the *unknown* placeholder | `arpa_to_ipa`: passed through unchanged. `ipa_to_arpa`: diacritics and suprasegmentals are dropped; other out-of-inventory symbols follow `errors=` (default `"replace"` → `?`) |
298
298
  | **X-SAMPA** | Exact for all canonical symbols | Exact except aliases (`f\`→ɸ, `&`→æ) normalise to their canonical spelling | Passed through unchanged |
299
- | **Buckwalter ↔ Arabic** | Exact (the `^` shadda alias normalises to canonical `~`; precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Passed through unchanged |
299
+ | **Buckwalter ↔ Arabic** | Exact (precomposed lam-alef ligatures decompose to two chars, visually identical) | Exact | Follows `errors=` (default: passed through) |
300
+ | **Mantoq → IPA** | One-directional (gemination/word markers consumed; no IPA→Mantoq) | — | Follows `errors=` (default: passed through) |
300
301
  | **Lexique ↔ IPA** | Exact except the `°`/`3` schwa pair (both → `ə`; reverse always → `°`) | Exact | Passed through unchanged |
301
302
  | **Kirshenbaum ↔ IPA** | Exact | **Lossy** — restricted ASCII inventory; IPA outside it passes through | Passed through unchanged |
302
303
  | **Cotovía ↔ IPA** | Exact except the three `L`/`Z`/`jj` symbols for `ʎ` normalise to `L` | **Lossy** — Galician/Spanish inventory; IPA outside it passes through | Passed through unchanged |
@@ -365,7 +365,7 @@ _BW_ARABIC_EXTENDED = [
365
365
  ("hamza on waw", "&", "ؤ"),
366
366
  ("hamza on ya", "}", "ئ"),
367
367
  ("alef madda", "|", "آ"),
368
- ("shadda", "~", "ّ"), # "~" is canonical Buckwalter shadda; "^" is an alias
368
+ ("shadda", "~", "ّ"), # "~" is the canonical (and only) Buckwalter shadda
369
369
  ("fatha", "a", "َ"),
370
370
  ("damma", "u", "ُ"),
371
371
  ("kasra", "i", "ِ"),
@@ -825,10 +825,11 @@ def test_buckwalter_tatweel():
825
825
 
826
826
 
827
827
  def test_buckwalter_shadda_alias():
828
- # Both "~" (canonical) and "^" (alias) map forward to shadda; the reverse
828
+ # "~" is the only shadda spelling; "^" is NOT Buckwalter (mantoq uses it
829
+ # for θ) and passes through as an unknown.
829
830
  # yields the canonical "~".
830
831
  assert buckwalter_to_arabic("~") == "ّ"
831
- assert buckwalter_to_arabic("^") == "ّ"
832
+ assert buckwalter_to_arabic("^") == "^"
832
833
  assert arabic_to_buckwalter("ّ") == "~"
833
834
 
834
835
 
@@ -947,7 +948,7 @@ def test_notation_enum_values():
947
948
 
948
949
 
949
950
  def test_notation_enum_count():
950
- assert len(Notation) == 9
951
+ assert len(Notation) == 10
951
952
 
952
953
 
953
954
  def test_notation_str():
@@ -1148,3 +1149,70 @@ def test_rfe_tap_trill_round_trip():
1148
1149
  def test_convert_routes_through_rfe():
1149
1150
  assert convert("š", "rfe", "x-sampa") == "S"
1150
1151
  assert convert("ʃ", "ipa", "rfe") == "š"
1152
+
1153
+
1154
+ # ---------------------------------------------------------------------------
1155
+ # Mantoq → IPA (Halabi Arabic-Phonetiser inventory; one-directional)
1156
+ # ---------------------------------------------------------------------------
1157
+
1158
+ from scriptconv.notation import mantoq_to_ipa, UnknownSymbolError # noqa: E402
1159
+
1160
+ def test_mantoq_basic_word():
1161
+ assert mantoq_to_ipa("salaam") == "salaːm"
1162
+
1163
+
1164
+ def test_mantoq_theta_is_caret_not_shadda():
1165
+ # mantoq's "^" is θ — the same character is NOT a Buckwalter shadda here
1166
+ assert mantoq_to_ipa("^") == "θ"
1167
+
1168
+
1169
+ def test_mantoq_gemination_marker():
1170
+ assert mantoq_to_ipa("b_dbl_a") == "bːa"
1171
+
1172
+
1173
+ def test_mantoq_dbl_after_vowel_is_inert():
1174
+ # upstream only emits _dbl_ after consonants (tokenization.py splits
1175
+ # doubled consonant phonemes); a vowel+_dbl_ input is left unchanged
1176
+ assert mantoq_to_ipa("aa_dbl_") == "aː"
1177
+
1178
+
1179
+ def test_mantoq_special_tokens():
1180
+ # inventory special tokens: _sil_ -> pause (space), _eos_/_pad_ dropped —
1181
+ # and never shredded into letter phonemes
1182
+ assert mantoq_to_ipa("m_sil_n") == "m n"
1183
+ assert mantoq_to_ipa("m a_eos_") == "m a"
1184
+ assert mantoq_to_ipa("_pad_b") == "b"
1185
+
1186
+
1187
+ def test_mantoq_word_separator_and_glottal():
1188
+ assert mantoq_to_ipa("m_+_<a") == "m ʔa"
1189
+
1190
+
1191
+ def test_mantoq_emphatics_and_superlong():
1192
+ assert mantoq_to_ipa("SaaaaD") == "sˤaːːdˤ"
1193
+
1194
+
1195
+ def test_mantoq_via_convert_facade():
1196
+ assert convert("salaam", "mantoq", "ipa") == "salaːm"
1197
+ assert can_convert("mantoq", "ipa") is True
1198
+ assert can_convert("ipa", "mantoq") is False
1199
+
1200
+
1201
+ def test_mantoq_chains_to_xsampa_via_graph():
1202
+ # mantoq -> ipa -> x-sampa multi-hop through the conversion graph
1203
+ out = convert("salaam", "mantoq", "x-sampa")
1204
+ assert "a:" in out
1205
+
1206
+
1207
+ def test_mantoq_errors_strict():
1208
+ import pytest as _pytest
1209
+ with _pytest.raises(UnknownSymbolError):
1210
+ mantoq_to_ipa("Q", errors="strict")
1211
+
1212
+
1213
+ def test_buckwalter_wasla_and_dagger_alef():
1214
+ from scriptconv.notation import arabic_to_buckwalter, buckwalter_to_arabic
1215
+ assert buckwalter_to_arabic("{") == "ٱ"
1216
+ assert buckwalter_to_arabic("`") == "ٰ"
1217
+ assert arabic_to_buckwalter("رحمٰن") == "rHm`n"
1218
+ assert arabic_to_buckwalter("ٱ") == "{"
@@ -23,7 +23,7 @@ class TestEnums(unittest.TestCase):
23
23
  self.assertEqual(Phonemizer.KOG2PK.value, "kog2p")
24
24
  self.assertEqual(Alphabet.XSAMPA.value, "x-sampa")
25
25
  self.assertEqual(len(list(Phonemizer)), 41)
26
- self.assertEqual(len(list(Alphabet)), 20)
26
+ self.assertEqual(len(list(Alphabet)), 21) # incl. MANTOQ
27
27
 
28
28
 
29
29
  class TestRegistryCompleteness(unittest.TestCase):
@@ -1,207 +0,0 @@
1
- # translate.py
2
- # Deterministic Mantoq → IPA converter
3
- # Assumes the input is already tokenized using the Mantoq inventory.
4
-
5
- # ---------------------------------------------------------------------------
6
- # Token → IPA maps
7
- # ---------------------------------------------------------------------------
8
-
9
- CONSONANTS = {
10
- "b": "b",
11
- "t": "t",
12
- "^": "θ",
13
- "j": "d͡ʒ",
14
- "H": "ħ",
15
- "x": "x",
16
- "d": "d",
17
- "*": "ð",
18
- "r": "r",
19
- "z": "z",
20
- "s": "s",
21
- "$": "ʃ",
22
- "S": "sˤ",
23
- "D": "dˤ",
24
- "T": "tˤ",
25
- "Z": "ðˤ",
26
- "E": "ʕ",
27
- "g": "ɣ",
28
- "f": "f",
29
- "q": "q",
30
- "k": "k",
31
- "l": "l",
32
- "m": "m",
33
- "n": "n",
34
- "h": "h",
35
- "w": "w",
36
- "y": "j",
37
- "v": "v"
38
- }
39
-
40
- VOWELS = {
41
- "a": "a",
42
- "aa": "aː",
43
- "aaaa": "aːː",
44
- "i": "i",
45
- "ii": "iː",
46
- "u": "u",
47
- "uu": "uː",
48
- }
49
-
50
-
51
- # Punctuation is passed through unchanged:
52
- PUNCTUATION = set(list(".,;:!?()[]{}\"'"))
53
-
54
- # ---------------------------------------------------------------------------
55
- # Tokenizer
56
- # ---------------------------------------------------------------------------
57
-
58
- def tokenize_mantoq(text):
59
- """
60
- Tokenize Mantoq string deterministically.
61
-
62
- Priority:
63
- 1. _dbl_
64
- 2. aaaa
65
- 3. ii / uu / aa
66
- 4. single char tokens
67
- 5. punctuation and raw chars are passed through
68
- """
69
- tokens = []
70
- i = 0
71
- L = len(text)
72
-
73
- while i < L:
74
-
75
- # doubling marker
76
- if text.startswith("_dbl_", i):
77
- tokens.append("_dbl_")
78
- i += 5
79
- continue
80
-
81
- # word separator
82
- if text.startswith("_+_", i):
83
- tokens.append("_+_")
84
- i += 3
85
- continue
86
-
87
- # longest vowel first
88
- if text.startswith("aaaa", i):
89
- tokens.append("aaaa")
90
- i += 4
91
- continue
92
-
93
- if text.startswith("aa", i):
94
- tokens.append("aa")
95
- i += 2
96
- continue
97
-
98
- if text.startswith("ii", i):
99
- tokens.append("ii")
100
- i += 2
101
- continue
102
-
103
- if text.startswith("uu", i):
104
- tokens.append("uu")
105
- i += 2
106
- continue
107
-
108
- # single-character consonant or vowel
109
- ch = text[i]
110
-
111
- if ch in CONSONANTS or ch in VOWELS:
112
- tokens.append(ch)
113
- i += 1
114
- continue
115
- if ch == "<":
116
- tokens.append("ʔ")
117
- i += 1
118
- continue
119
-
120
- # punctuation
121
- if ch in PUNCTUATION:
122
- tokens.append(ch)
123
- i += 1
124
- continue
125
-
126
- # fallback: pass through unknown characters
127
- tokens.append(ch)
128
- i += 1
129
-
130
- return tokens
131
-
132
- # ---------------------------------------------------------------------------
133
- # IPA Assembly
134
- # ---------------------------------------------------------------------------
135
-
136
- def apply_doubling(prev_token, prev_ipa):
137
- """
138
- Mantoq doubling rule:
139
- - If previous token is a vowel token: lengthen it.
140
- - If previous token is a consonant: mark gemination using ː.
141
- """
142
- if prev_token in VOWELS:
143
- # ensure single long marker; long tokens already contain ː
144
- if prev_ipa.endswith("ː"):
145
- return prev_ipa + "ː"
146
- return prev_ipa + "ː"
147
-
148
- if prev_token in CONSONANTS:
149
- # consonant gemination: use length mark, not duplication
150
- if prev_ipa.endswith("ː"):
151
- return prev_ipa # already geminated
152
- return prev_ipa + "ː"
153
-
154
- return prev_ipa
155
-
156
-
157
- def mantoq_to_ipa(text):
158
- tokens = tokenize_mantoq(text)
159
-
160
- ipa_out = []
161
- last_token = None
162
- last_ipa = None
163
-
164
- for tok in tokens:
165
-
166
- # doubling applies to the previous symbol
167
- if tok == "_dbl_":
168
- if last_token is None:
169
- continue
170
- new_ipa = apply_doubling(last_token, last_ipa)
171
- ipa_out[-1] = new_ipa
172
- last_ipa = new_ipa
173
- continue
174
-
175
- # explicit word separation
176
- if tok == "_+_":
177
- ipa_out.append(" ")
178
- last_token = tok
179
- last_ipa = " "
180
- continue
181
-
182
- # vowels
183
- if tok in VOWELS:
184
- ipa_val = VOWELS[tok]
185
- ipa_out.append(ipa_val)
186
- last_token = tok
187
- last_ipa = ipa_val
188
- continue
189
-
190
- # consonants
191
- if tok in CONSONANTS:
192
- ipa_val = CONSONANTS[tok]
193
- ipa_out.append(ipa_val)
194
- last_token = tok
195
- last_ipa = ipa_val
196
- continue
197
-
198
- # punctuation and fallthrough
199
- ipa_out.append(tok)
200
- last_token = tok
201
- last_ipa = tok
202
-
203
- return "".join(ipa_out)
204
-
205
-
206
- # backwards compat alias
207
- bw2ipa = mantoq_to_ipa
File without changes
File without changes