841229715e
The file called 274-dev.md was never dev notes — it was the roster. It now sits alongside 274-lookup.csv and 274-lookup.geojson under the same stem, since all three carry the same table. 274-dev.md -> 274-lookup.md the roster, source of truth 274-method.md -> 274-dev.md how it was built References updated: DOC in 274-romaji.py, the roster path in 274-transect-build.py, and the docstrings in both. 274.md now links to the dev notes and the reference tables separately; each of the two docs links to the other and back to the song page. Titles follow: "274 - reference tables" and "274 - how the roster was built". Verified after renaming: 274-romaji.py --check reports no changes and 274-transect-build.py rebuilds 168 entries, so neither script lost its input. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S6MpcBisZYhX45oDGRRJoz
131 lines
5.7 KiB
Python
Executable File
131 lines
5.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Fill the Romaji column of 274-lookup.md from its Kana column.
|
|
|
|
Kana is the source of truth. Romaji is generated wāpuro-style — no macrons,
|
|
since they are awkward to type: おう→ou, ゅう→yuu, っ doubles the following
|
|
consonant, ー repeats the preceding vowel, ん→n' before a vowel or y.
|
|
|
|
The trailing generic is split off and lowercased (Kawabata hashi, Houshin jinja,
|
|
Takinoue kosenkyou); 丁目 take a hyphen (Shinmachi 3-choume). Compound proper
|
|
stems stay as one token, because kana carries no word boundary — add them to
|
|
MANUAL below if you want a break.
|
|
|
|
python3 274-romaji.py # rewrite the Romaji column
|
|
python3 274-romaji.py --check # report differences, change nothing
|
|
|
|
Rows with no kana are left with an empty Romaji cell rather than a guess.
|
|
"""
|
|
import io, os, re, sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
DOC = os.path.join(HERE, '274-lookup.md')
|
|
|
|
# stems that must not have a trailing 川 split off — it is part of the name
|
|
NOSPLIT = {'のぼりかわ', 'にいかわ', 'しんかわ', 'みかわ', 'たきさわがわ'}
|
|
# hand-set forms that the mechanical rules cannot derive
|
|
MANUAL = {}
|
|
|
|
|
|
import re
|
|
Y={'きゃ':'kya','きゅ':'kyu','きょ':'kyo','しゃ':'sha','しゅ':'shu','しょ':'sho',
|
|
'ちゃ':'cha','ちゅ':'chu','ちょ':'cho','にゃ':'nya','にゅ':'nyu','にょ':'nyo',
|
|
'ひゃ':'hya','ひゅ':'hyu','ひょ':'hyo','みゃ':'mya','みゅ':'myu','みょ':'myo',
|
|
'りゃ':'rya','りゅ':'ryu','りょ':'ryo','ぎゃ':'gya','ぎゅ':'gyu','ぎょ':'gyo',
|
|
'じゃ':'ja','じゅ':'ju','じょ':'jo','びゃ':'bya','びゅ':'byu','びょ':'byo',
|
|
'ぴゃ':'pya','ぴゅ':'pyu','ぴょ':'pyo','ぢゃ':'ja','ぢゅ':'ju','ぢょ':'jo',
|
|
'しぇ':'she','ちぇ':'che','じぇ':'je','てぃ':'ti','でぃ':'di','ふぁ':'fa',
|
|
'ふぃ':'fi','ふぇ':'fe','ふぉ':'fo','うぃ':'wi','うぇ':'we','うぉ':'wo','とぅ':'tu'}
|
|
S={'あ':'a','い':'i','う':'u','え':'e','お':'o','か':'ka','き':'ki','く':'ku','け':'ke','こ':'ko',
|
|
'さ':'sa','し':'shi','す':'su','せ':'se','そ':'so','た':'ta','ち':'chi','つ':'tsu','て':'te','と':'to',
|
|
'な':'na','に':'ni','ぬ':'nu','ね':'ne','の':'no','は':'ha','ひ':'hi','ふ':'fu','へ':'he','ほ':'ho',
|
|
'ま':'ma','み':'mi','む':'mu','め':'me','も':'mo','や':'ya','ゆ':'yu','よ':'yo',
|
|
'ら':'ra','り':'ri','る':'ru','れ':'re','ろ':'ro','わ':'wa','を':'o','ん':'n',
|
|
'が':'ga','ぎ':'gi','ぐ':'gu','げ':'ge','ご':'go','ざ':'za','じ':'ji','ず':'zu','ぜ':'ze','ぞ':'zo',
|
|
'だ':'da','ぢ':'ji','づ':'zu','で':'de','ど':'do','ば':'ba','び':'bi','ぶ':'bu','べ':'be','ぼ':'bo',
|
|
'ぱ':'pa','ぴ':'pi','ぷ':'pu','ぺ':'pe','ぽ':'po',
|
|
'ぁ':'a','ぃ':'i','ぅ':'u','ぇ':'e','ぉ':'o','ゃ':'ya','ゅ':'yu','ょ':'yo'}
|
|
VOW='aiueo'
|
|
|
|
def kana2romaji(s):
|
|
out=[];i=0
|
|
while i<len(s):
|
|
c=s[i]
|
|
if c.isdigit(): out.append(c); i+=1; continue
|
|
if c=='ー':
|
|
if out and out[-1] and out[-1][-1] in VOW: out.append(out[-1][-1])
|
|
i+=1; continue
|
|
if c=='っ':
|
|
j=i+1
|
|
if j<len(s):
|
|
nxt=Y.get(s[j:j+2]) or S.get(s[j],'')
|
|
if nxt and nxt[0] not in VOW: out.append(nxt[0])
|
|
i+=1; continue
|
|
two=s[i:i+2]
|
|
if two in Y: out.append(Y[two]); i+=2; continue
|
|
if c in S:
|
|
r=S[c]
|
|
if c=='ん':
|
|
nx=s[i+1:i+2]
|
|
nr=Y.get(s[i+1:i+3]) or S.get(nx,'')
|
|
if nr and (nr[0] in VOW or nr[0]=='y'): r="n'"
|
|
out.append(r); i+=1; continue
|
|
out.append(c); i+=1
|
|
return ''.join(out)
|
|
|
|
GENERIC=['しんごうじょう','こせんきょう','りくきょう','ほどうきょう','ちゅうがっこう','しょうがっこう','ゆうびんきょく','はつでんしょ',
|
|
'へんでんしょ','ちゅうしゃじょう','しょうぼうしょ','としょかん','きねんかん','こうみんかん',
|
|
'きゃんぷじょう','しぇるたー','とんねる','じんじゃ','だいばし','おおはし','ばし','はし',
|
|
'がわ','かわ','だむ','やくば','こうこう','こうえん','かいかん','ほーる','えき','てん','ちょうめ']
|
|
|
|
|
|
def split_generic(kana):
|
|
if kana in NOSPLIT: return kana,''
|
|
for g in GENERIC:
|
|
if kana.endswith(g) and len(kana)>len(g):
|
|
return kana[:-len(g)], g
|
|
return kana, ''
|
|
|
|
import re as _re
|
|
def romanise(kana):
|
|
m=_re.match(r'^(.*?)(\d+)ちょうめ$',kana)
|
|
if m:
|
|
a=kana2romaji(m.group(1)); a=a[:1].upper()+a[1:]
|
|
return '%s %s-choume'%(a,m.group(2))
|
|
stem,gen=split_generic(kana)
|
|
a=kana2romaji(stem); b=kana2romaji(gen) if gen else ''
|
|
a=a[:1].upper()+a[1:]
|
|
return (a+' '+b).strip() if b else a
|
|
|
|
|
|
def process(text):
|
|
out, filled, changed = [], 0, 0
|
|
for line in text.split('\n'):
|
|
if line.startswith('| ') and line.count('|') >= 13:
|
|
c = [x.strip() for x in line.strip('|').split('|')]
|
|
if re.match(r'^\d+\.\d+$', c[0]):
|
|
kana = c[3]
|
|
new = MANUAL.get(kana, romanise(kana)) if kana else ''
|
|
if new != c[4]:
|
|
if not c[4]:
|
|
filled += 1
|
|
else:
|
|
changed += 1
|
|
c[4] = new
|
|
line = '| ' + ' | '.join(c) + ' |'
|
|
out.append(line)
|
|
return '\n'.join(out), filled, changed
|
|
|
|
|
|
def main():
|
|
text = io.open(DOC, encoding='utf-8').read()
|
|
new, filled, changed = process(text)
|
|
if '--check' in sys.argv:
|
|
print('%d blank cells would be filled, %d existing would change' % (filled, changed))
|
|
return
|
|
io.open(DOC, 'w', encoding='utf-8').write(new)
|
|
print('%d filled, %d normalised' % (filled, changed))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|