diff --git a/.github/workflows/build-ruby.yml b/.github/workflows/build-ruby.yml new file mode 100644 index 0000000..db4afc0 --- /dev/null +++ b/.github/workflows/build-ruby.yml @@ -0,0 +1,22 @@ +name: Ruby + +on: + push: + tags: ['*'] + pull_request: + branches: [main] + paths: + - 'ruby/**/*' + - 'uklatn.gemspec' + +jobs: + + build-ruby: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Test + working-directory: ${{github.workspace}}/ruby + run: make test diff --git a/.gitignore b/.gitignore index dd92a00..9709d73 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ __pycache__/ *.egg-info/ *.so .swiftpm/ - +*.gem diff --git a/ruby/Makefile b/ruby/Makefile new file mode 100644 index 0000000..867ee25 --- /dev/null +++ b/ruby/Makefile @@ -0,0 +1,10 @@ +.PHONY: build +.PHONY: test + +all: test + +build: + @gem build uklatn.gemspec + +test: + @ruby test/uklatn/test_uklatn.rb diff --git a/ruby/lib/uklatn.rb b/ruby/lib/uklatn.rb new file mode 100644 index 0000000..c372743 --- /dev/null +++ b/ruby/lib/uklatn.rb @@ -0,0 +1,172 @@ +# Generated by gentables.py, do not edit. + +# Ukrainian Cyrillic transliteration to and from Latin script. +# +# Tables: +# - 'DSTU_9112_A': DSTU 9112:2021 System A +# - 'DSTU_9112_B': DSTU 9112:2021 System B +# - 'KMU_55': KMU 55:2010, not reversible +class UkrainianLatin + + # Transliterates a string of Ukrainian Cyrillic to Latin script. + def encode(text, table = 'DSTU_9112_A') + tr = TABLES[table][0] + return tr.transform(text) if tr + raise ArgumentError.new("invalid table #{table}") + end + + # Re-transliterates a string of Ukrainian Latin to Cyrillic script. + def decode(text, table = 'DSTU_9112_A') + tr = TABLES[table][1] + return tr.transform(text) if tr + raise ArgumentError.new("invalid table #{table}") + end + + class Uklatn_uk_uk_Latn_DSTU_9112_A # :nodoc: + def initialize() + @rx1 = /\b([Ьь])|([Ьь](?=[АаЕеУу])|[ЄЮЯ](?=\u0301?[а-щьюяєіїґ’])|(?<=[Б-ДЖЗК-НП-ТФ-Щб-джзк-нп-тф-щҐґ])[Йй])|([ЁЄІЇЎА-яёєіїўҐґ’])/ + @maps1 = [ + {"Ь"=>"Ĵ","ь"=>"ĵ"}, + {"Ь"=>"J'","ь"=>"j'","Є"=>"Je","Ю"=>"Ju","Я"=>"Ja","Й"=>"'J","й"=>"'j"}, + {"А"=>"A","а"=>"a","Б"=>"B","б"=>"b","В"=>"V","в"=>"v","Г"=>"Ğ","г"=>"ğ","Ґ"=>"G","ґ"=>"g","Д"=>"D","д"=>"d","Е"=>"E","е"=>"e","Є"=>"JE","є"=>"je","Ж"=>"Ž","ж"=>"ž","З"=>"Z","з"=>"z","И"=>"Y","и"=>"y","І"=>"I","і"=>"i","Ї"=>"Ï","ї"=>"ï","К"=>"K","к"=>"k","Л"=>"L","л"=>"l","М"=>"M","м"=>"m","Н"=>"N","н"=>"n","О"=>"O","о"=>"o","П"=>"P","п"=>"p","Р"=>"R","р"=>"r","С"=>"S","с"=>"s","Т"=>"T","т"=>"t","У"=>"U","у"=>"u","Ф"=>"F","ф"=>"f","Х"=>"X","х"=>"x","Ц"=>"C","ц"=>"c","Ч"=>"Č","ч"=>"č","Ш"=>"Š","ш"=>"š","Щ"=>"Ŝ","щ"=>"ŝ","Ю"=>"JU","ю"=>"ju","Я"=>"JA","я"=>"ja","Ь"=>"J","ь"=>"j","Й"=>"J","й"=>"j","’"=>"'","Ё"=>"Ö","ё"=>"ö","Ў"=>"Ŭ","ў"=>"ŭ","Ъ"=>"Ǒ","ъ"=>"ǒ","Ы"=>"Ȳ","ы"=>"ȳ","Э"=>"Ē","э"=>"ē"}, + ] + end + + def transform(text) + text = text.unicode_normalize(:nfc) + text = text.gsub(@rx1) do |m| + next @maps1[2].fetch($3, $3) unless $3.nil? + next @maps1[1].fetch($2, $2) unless $2.nil? + next @maps1[0].fetch($1, $1) unless $1.nil? + m + end + text = text.unicode_normalize(:nfc) + end + end + + class Uklatn_uk_uk_Latn_DSTU_9112_B # :nodoc: + def initialize() + @rx1 = /([Ьь](?=[АаЕеІіУу])|(?<=[Б-ДЖЗК-НП-ТФ-Щб-джзк-нп-тф-щҐґ])[Йй])|([ГЄЖЇХЩШЧЮЯЁЎЪЫЭ](?=\u0301?[а-яёєіїўґ’])|\b[Ьь])|([ЁЄІЇЎА-яёєіїўҐґ’])/ + @maps1 = [ + {"Ь"=>"J'","ь"=>"j'","Й"=>"'J","й"=>"'j"}, + {"Г"=>"Gh","Є"=>"Je","Ж"=>"Zh","Ї"=>"Ji","Х"=>"Kh","Щ"=>"Shch","Ш"=>"Sh","Ч"=>"Ch","Ю"=>"Ju","Я"=>"Ja","Ё"=>"Jow","Ў"=>"Uh","Ъ"=>"Oh","Ы"=>"Yw","Э"=>"Ehw","Ь"=>"Hj","ь"=>"hj"}, + {"А"=>"A","а"=>"a","Б"=>"B","б"=>"b","В"=>"V","в"=>"v","Г"=>"GH","г"=>"gh","Ґ"=>"G","ґ"=>"g","Д"=>"D","д"=>"d","Е"=>"E","е"=>"e","Є"=>"JE","є"=>"je","Ж"=>"ZH","ж"=>"zh","З"=>"Z","з"=>"z","И"=>"Y","и"=>"y","І"=>"I","і"=>"i","Ї"=>"JI","ї"=>"ji","Х"=>"KH","х"=>"kh","К"=>"K","к"=>"k","Л"=>"L","л"=>"l","М"=>"M","м"=>"m","Н"=>"N","н"=>"n","О"=>"O","о"=>"o","П"=>"P","п"=>"p","Р"=>"R","р"=>"r","Щ"=>"SHCH","щ"=>"shch","Ш"=>"SH","ш"=>"sh","С"=>"S","с"=>"s","Т"=>"T","т"=>"t","У"=>"U","у"=>"u","Ф"=>"F","ф"=>"f","Ч"=>"CH","ч"=>"ch","Ц"=>"C","ц"=>"c","Ю"=>"JU","ю"=>"ju","Я"=>"JA","я"=>"ja","Й"=>"J","й"=>"j","Ь"=>"J","ь"=>"j","’"=>"'","Ё"=>"JOW","ё"=>"jow","Ў"=>"UH","ў"=>"uh","Ъ"=>"OH","ъ"=>"oh","Ы"=>"YW","ы"=>"yw","Э"=>"EHW","э"=>"ehw"}, + ] + end + + def transform(text) + text = text.unicode_normalize(:nfc) + text = text.gsub(@rx1) do |m| + next @maps1[2].fetch($3, $3) unless $3.nil? + next @maps1[1].fetch($2, $2) unless $2.nil? + next @maps1[0].fetch($1, $1) unless $1.nil? + m + end + text = text.unicode_normalize(:nfc) + end + end + + class Uklatn_uk_uk_Latn_KMU_55 # :nodoc: + def initialize() + @rx1 = /(?<=[ЁЄІЇЎА-яёєіїўҐґ])([’\u0027])(?=[ЁЄІЇЎА-яёєіїўҐґ])/ + @maps1 = [ + {"’"=>"","'"=>""}, + ] + @rx2 = /\b([ЄЇЮЯ])(?=\u0301?[а-яёєіїўґ’])|\b([ЙйЄЇЮЯєїюя])|([Зз]Г|[ЖХЦЩШЧЄЇЮЯ])(?=\u0301?[а-яёєіїўґ’])|([Зз][Гг]|[ЄІЇА-ЩЬЮ-щьюяєіїҐґ’])/ + @maps2 = [ + {"Є"=>"Ye","Ї"=>"Yi","Ю"=>"Yu","Я"=>"Ya"}, + {"Й"=>"Y","й"=>"y","Є"=>"YE","є"=>"ye","Ї"=>"YI","ї"=>"yi","Ю"=>"YU","ю"=>"yu","Я"=>"YA","я"=>"ya"}, + {"ЗГ"=>"ZGh","зГ"=>"zGh","Ж"=>"Zh","Х"=>"Kh","Ц"=>"Ts","Щ"=>"Shch","Ш"=>"Sh","Ч"=>"Ch","Є"=>"Ie","Ї"=>"I","Ю"=>"Iu","Я"=>"Ia"}, + {"ЗГ"=>"ZGH","Зг"=>"Zgh","зГ"=>"zGH","зг"=>"zgh","А"=>"A","а"=>"a","Б"=>"B","б"=>"b","В"=>"V","в"=>"v","Г"=>"H","г"=>"h","Ґ"=>"G","ґ"=>"g","Д"=>"D","д"=>"d","Е"=>"E","е"=>"e","Є"=>"IE","є"=>"ie","Ж"=>"ZH","ж"=>"zh","З"=>"Z","з"=>"z","И"=>"Y","и"=>"y","І"=>"I","і"=>"i","Ї"=>"I","ї"=>"i","Х"=>"KH","х"=>"kh","К"=>"K","к"=>"k","Л"=>"L","л"=>"l","М"=>"M","м"=>"m","Н"=>"N","н"=>"n","О"=>"O","о"=>"o","П"=>"P","п"=>"p","Р"=>"R","р"=>"r","Щ"=>"SHCH","щ"=>"shch","Ш"=>"SH","ш"=>"sh","С"=>"S","с"=>"s","Т"=>"T","т"=>"t","У"=>"U","у"=>"u","Ф"=>"F","ф"=>"f","Ч"=>"CH","ч"=>"ch","Ц"=>"TS","ц"=>"ts","Ю"=>"IU","ю"=>"iu","Я"=>"IA","я"=>"ia","Й"=>"I","й"=>"i","Ь"=>"","ь"=>"","’"=>""}, + ] + end + + def transform(text) + text = text.unicode_normalize(:nfc) + text = text.gsub(@rx1) do |m| + next @maps1[0].fetch($1, $1) unless $1.nil? + m + end + text = text.gsub(@rx2) do |m| + next @maps2[3].fetch($4, $4) unless $4.nil? + next @maps2[2].fetch($3, $3) unless $3.nil? + next @maps2[1].fetch($2, $2) unless $2.nil? + next @maps2[0].fetch($1, $1) unless $1.nil? + m + end + text = text.unicode_normalize(:nfc) + end + end + + class Uklatn_uk_Latn_DSTU_9112_A_uk # :nodoc: + def initialize() + @rx1 = /([ÁáÉéÍíÓóÚúÝýḮḯ])/ + @maps1 = [ + {"Á"=>"Á","á"=>"á","É"=>"É","é"=>"é","Í"=>"Í","í"=>"í","Ó"=>"Ó","ó"=>"ó","Ú"=>"Ú","ú"=>"ú","Ý"=>"Ý","ý"=>"ý","Ḯ"=>"Ḯ","ḯ"=>"ḯ"}, + ] + @rx2 = /(J[Ee]|j[Ee]|J[Uu]|j[Uu]|J[Aa]|j[Aa]|[A-GIK-PR-VXYZa-gik-pr-vxyzÏÖïöČčĒēĞğĴĵŜŝŠšŬŭŽžǑǒȲȳ])|(?<=[BbCcDdFfGgKkLlMmNnPpRrSsTtVvXxZzČčĞğŜŝŠšŽž])([Jj]\u0027(?=[AaEeUu])|[Jj])|(\u0027[Jj](?![AaEeIiUu])|\u0027(?=[Jj])|[Jj])/ + @maps2 = [ + {"A"=>"А","a"=>"а","B"=>"Б","b"=>"б","V"=>"В","v"=>"в","Ğ"=>"Г","ğ"=>"г","G"=>"Ґ","g"=>"ґ","D"=>"Д","d"=>"д","E"=>"Е","e"=>"е","JE"=>"Є","Je"=>"Є","jE"=>"є","je"=>"є","Ž"=>"Ж","ž"=>"ж","Z"=>"З","z"=>"з","Y"=>"И","y"=>"и","I"=>"І","i"=>"і","Ï"=>"Ї","ï"=>"ї","K"=>"К","k"=>"к","L"=>"Л","l"=>"л","M"=>"М","m"=>"м","N"=>"Н","n"=>"н","O"=>"О","o"=>"о","P"=>"П","p"=>"п","R"=>"Р","r"=>"р","S"=>"С","s"=>"с","T"=>"Т","t"=>"т","U"=>"У","u"=>"у","F"=>"Ф","f"=>"ф","X"=>"Х","x"=>"х","C"=>"Ц","c"=>"ц","Č"=>"Ч","č"=>"ч","Š"=>"Ш","š"=>"ш","Ŝ"=>"Щ","ŝ"=>"щ","JU"=>"Ю","Ju"=>"Ю","jU"=>"ю","ju"=>"ю","JA"=>"Я","Ja"=>"Я","jA"=>"я","ja"=>"я","Ĵ"=>"Ь","ĵ"=>"ь","Ö"=>"Ё","ö"=>"ё","Ŭ"=>"Ў","ŭ"=>"ў","Ǒ"=>"Ъ","ǒ"=>"ъ","Ȳ"=>"Ы","ȳ"=>"ы","Ē"=>"Э","ē"=>"э"}, + {"J"=>"Ь","j"=>"ь","J'"=>"Ь","j'"=>"ь"}, + {"'J"=>"Й","'j"=>"й","'"=>"’","J"=>"Й","j"=>"й"}, + ] + end + + def transform(text) + text = text.unicode_normalize(:nfc) + text = text.gsub(@rx1) do |m| + next @maps1[0].fetch($1, $1) unless $1.nil? + m + end + text = text.gsub(@rx2) do |m| + next @maps2[2].fetch($3, $3) unless $3.nil? + next @maps2[1].fetch($2, $2) unless $2.nil? + next @maps2[0].fetch($1, $1) unless $1.nil? + m + end + text = text.unicode_normalize(:nfc) + end + end + + class Uklatn_uk_Latn_DSTU_9112_B_uk # :nodoc: + def initialize() + @rx1 = /([ÁáÉéÍíÓóÚúÝý])/ + @maps1 = [ + {"Á"=>"Á","á"=>"á","É"=>"É","é"=>"é","Í"=>"Í","í"=>"í","Ó"=>"Ó","ó"=>"ó","Ú"=>"Ú","ú"=>"ú","Ý"=>"Ý","ý"=>"ý"}, + ] + @rx2 = /([Jj][Oo][Ww]|[Ss][Hh][Cc][Hh]|[CcGgKkSsZzUuOo][Hh]|[Yy][Ww]|[Ee][Hh][Ww]|[Jj][EeIiUuAa]|[Hh][Jj]|[A-GIK-PR-VYZa-gik-pr-vyz])|(?<=[Ss][Hh][Cc][Hh])([Jj]\u0027(?=[AaEeIiUu])|[Jj])|(?<=[CcGgKkSsZz][Hh])([Jj]\u0027(?=[AaEeIiUu])|[Jj])|(?<=[BCDFGKLMNPRSTVZbcdfgklmnprstvzv])([Jj]\u0027(?=[AaEeIiUu])|[Jj])|(\u0027[Jj](?![AaEeIiUu])|\u0027(?=[Jj])|[Jj])/ + @maps2 = [ + {"A"=>"А","a"=>"а","B"=>"Б","b"=>"б","V"=>"В","v"=>"в","GH"=>"Г","Gh"=>"Г","gH"=>"г","gh"=>"г","G"=>"Ґ","g"=>"ґ","D"=>"Д","d"=>"д","E"=>"Е","e"=>"е","JE"=>"Є","Je"=>"Є","jE"=>"є","je"=>"є","ZH"=>"Ж","Zh"=>"Ж","zH"=>"ж","zh"=>"ж","Z"=>"З","z"=>"з","Y"=>"И","y"=>"и","I"=>"І","i"=>"і","JI"=>"Ї","Ji"=>"Ї","jI"=>"ї","ji"=>"ї","KH"=>"Х","Kh"=>"Х","kH"=>"х","kh"=>"х","K"=>"К","k"=>"к","L"=>"Л","l"=>"л","M"=>"М","m"=>"м","N"=>"Н","n"=>"н","O"=>"О","o"=>"о","P"=>"П","p"=>"п","R"=>"Р","r"=>"р","SHCH"=>"Щ","SHCh"=>"Щ","SHcH"=>"Щ","SHch"=>"Щ","ShCH"=>"Щ","ShCh"=>"Щ","ShcH"=>"Щ","Shch"=>"Щ","sHCH"=>"щ","sHCh"=>"щ","sHcH"=>"щ","sHch"=>"щ","shCH"=>"щ","shCh"=>"щ","shcH"=>"щ","shch"=>"щ","SH"=>"Ш","Sh"=>"Ш","sH"=>"ш","sh"=>"ш","S"=>"С","s"=>"с","T"=>"Т","t"=>"т","U"=>"У","u"=>"у","F"=>"Ф","f"=>"ф","CH"=>"Ч","Ch"=>"Ч","cH"=>"ч","ch"=>"ч","C"=>"Ц","c"=>"ц","JU"=>"Ю","Ju"=>"Ю","jU"=>"ю","ju"=>"ю","JA"=>"Я","Ja"=>"Я","jA"=>"я","ja"=>"я","HJ"=>"Ь","Hj"=>"Ь","hJ"=>"ь","hj"=>"ь","JOW"=>"Ё","JOw"=>"Ё","JoW"=>"Ё","Jow"=>"Ё","jOW"=>"ё","jOw"=>"ё","joW"=>"ё","jow"=>"ё","UH"=>"Ў","Uh"=>"Ў","uH"=>"ў","uh"=>"ў","OH"=>"Ъ","Oh"=>"Ъ","oH"=>"ъ","oh"=>"ъ","YW"=>"Ы","Yw"=>"Ы","yW"=>"ы","yw"=>"ы","EHW"=>"Э","EHw"=>"Э","EhW"=>"Э","Ehw"=>"Э","eHW"=>"э","eHw"=>"э","ehW"=>"э","ehw"=>"э"}, + {"J"=>"Ь","j"=>"ь","J'"=>"Ь","j'"=>"ь"}, + {"J"=>"Ь","j"=>"ь","J'"=>"Ь","j'"=>"ь"}, + {"J"=>"Ь","j"=>"ь","J'"=>"Ь","j'"=>"ь"}, + {"'J"=>"Й","'j"=>"й","'"=>"’","J"=>"Й","j"=>"й"}, + ] + end + + def transform(text) + text = text.unicode_normalize(:nfc) + text = text.gsub(@rx1) do |m| + next @maps1[0].fetch($1, $1) unless $1.nil? + m + end + text = text.gsub(@rx2) do |m| + next @maps2[4].fetch($5, $5) unless $5.nil? + next @maps2[3].fetch($4, $4) unless $4.nil? + next @maps2[2].fetch($3, $3) unless $3.nil? + next @maps2[1].fetch($2, $2) unless $2.nil? + next @maps2[0].fetch($1, $1) unless $1.nil? + m + end + text = text.unicode_normalize(:nfc) + end + end + + TABLES = Hash.new([nil, nil]) # :nodoc: + TABLES['DSTU_9112_A'] = [Uklatn_uk_uk_Latn_DSTU_9112_A.new(), Uklatn_uk_Latn_DSTU_9112_A_uk.new()] + TABLES['DSTU-9112-A'] = TABLES['DSTU_9112_A'] + TABLES['DSTU_9112_B'] = [Uklatn_uk_uk_Latn_DSTU_9112_B.new(), Uklatn_uk_Latn_DSTU_9112_B_uk.new()] + TABLES['DSTU-9112-B'] = TABLES['DSTU_9112_B'] + TABLES['KMU_55'] = [Uklatn_uk_uk_Latn_KMU_55.new(), nil] + TABLES['KMU-55'] = TABLES['KMU_55'] +end diff --git a/ruby/test/uklatn/test_uklatn.rb b/ruby/test/uklatn/test_uklatn.rb new file mode 100644 index 0000000..1ab62ac --- /dev/null +++ b/ruby/test/uklatn/test_uklatn.rb @@ -0,0 +1,346 @@ +# Generated by gentests.py, do not edit. + +require_relative '../../lib/uklatn.rb' + +class TestUkrainianLatin # :nodoc: + + def initialize + @tr = UkrainianLatin.new + end + + def assert_equal(expect, actual, input) + if expect != actual + s = actual.chars + arr = expect.chars.map {|c| c == s.shift ? ' ' : '^'}.join + raise "failed\n input: #{input}\nexpect: #{expect}\nactual: #{actual}\n #{arr}" + end + end + + def test_DSTU_9112_A() + + data_c2lr = [ + [ + "Україна, Хмельницький", + "Ukraïna, Xmeljnycjkyj" + ], + [ + "Щастям б’єш жук їх глицю в фон й ґедзь пріч.", + "Ŝastjam b'ješ žuk ïx ğlycju v fon j gedzj prič." + ], + [ + "ь Ь ль льє льї лью лья лье льі льу льа льйо льо", + "ĵ Ĵ lj ljje ljï ljju ljja lj'e lji lj'u lj'a ljjo ljo" + ], + [ + "Єл Їл Юл Ял", + "Jel Ïl Jul Jal" + ], + [ + "бь вь гь ґь дь жь зь кь ль мь нь пь рь сь ть фь хь ць чь шь щь", + "bj vj ğj gj dj žj zj kj lj mj nj pj rj sj tj fj xj cj čj šj ŝj" + ], + [ + "бя вя гя ґя дя жя зя кя ля мя ня пя ря ся тя фя хя ця чя шя щя", + "bja vja ğja gja dja žja zja kja lja mja nja pja rja sja tja fja xja cja čja šja ŝja" + ], + [ + "б’я в’я г’я ґ’я д’я ж’я з’я к’я л’я м’я н’я п’я р’я с’я т’я ф’я х’я ц’я ч’я ш’я щ’я", + "b'ja v'ja ğ'ja g'ja d'ja ž'ja z'ja k'ja l'ja m'ja n'ja p'ja r'ja s'ja t'ja f'ja x'ja c'ja č'ja š'ja ŝ'ja" + ], + [ + "бй бйо вй гй ґй дй жй зй кй лй мй нй пй рй сй тй фй хй цй чй шй щй", + "b'j b'jo v'j ğ'j g'j d'j ž'j z'j k'j l'j m'j n'j p'j r'j s'j t'j f'j x'j c'j č'j š'j ŝ'j" + ], + [ + "ня ньа н’я нь'н ньн", + "nja nj'a n'ja nj'n njn" + ], + [ + "рос дыня эзёдынъ. бр кроў.", + "ros dȳnja ēzödȳnǒ. br kroŭ." + ], + [ + "А́ а́ Е́ е́ Є́ є́ И́ и́ І́ і́ Ї́ ї́ О́ о́ У́ у́ Ю́ ю́ Я́ я́", + "Á á É é JÉ jé Ý ý Í í Ḯ ḯ Ó ó Ú ú JÚ jú JÁ já" + ], + [ + "Є́с сЄ́с є́с сє́с Ї́с сЇ́с ї́с сї́с Ю́с сЮ́с ю́с сю́с Я́с сЯ́с я́с ся́с", + "Jés sJés jés sjés Ḯs sḮs ḯs sḯs Jús sJús jús sjús Jás sJás jás sjás" + ], + [ + "' ім’я 'жук' \"жук\" ' '", + "' im'ja 'žuk' \"žuk\" ' '" + ], + [ + "Сонце світить майже білим світлом, однак через сильніше розсіювання і поглинання короткохвильової частини спектра атмосферою Землі пряме світло Сонця біля поверхні нашої планети набуває певного жовтого відтінку. Якщо небо ясне, то блакитний відтінок розсіяного світла складається з жовтуватим прямим сонячним світлом і загальне освітлення об’єктів на Землі стає білим.", + "Sonce svitytj majže bilym svitlom, odnak čerez syljniše rozsijuvannja i poğlynannja korotkoxvyljovoï častyny spektra atmosferoju Zemli prjame svitlo Soncja bilja poverxni našoï planety nabuvaje pevnoğo žovtoğo vidtinku. Jakŝo nebo jasne, to blakytnyj vidtinok rozsijanoğo svitla skladajetjsja z žovtuvatym prjamym sonjačnym svitlom i zağaljne osvitlennja ob'jektiv na Zemli staje bilym." + ], + [ + "дуб!дуб\"дуб#дуб$дуб%дуб&дуб'дуб(дуб)дуб*дуб+дуб,дуб-дуб.дуб/дуб:дуб;дуб<дуб=дуб>дуб?дуб@дуб[дуб\\дуб]дуб^дуб_дуб`дуб{дуб|дуб}дуб~дуб", + "dub!dub\"dub#dub$dub%dub&dub'dub(dub)dub*dub+dub,dub-dub.dub/dub:dub;dubdub?dub@dub[dub\\dub]dub^dub_dub`dub{dub|dub}dub~dub" + ], + [ + "бод бод\tбод\nбод\rбод", + "bod bod\tbod\nbod\rbod" + ], + [ + "об😎нап😘неп😭нєп🧐нїп😍нюп😀няп", + "ob😎nap😘nep😭njep🧐nïp😍njup😀njap" + ], + ] + + data_c2l = [ + [ + "в’я в'я", + "v'ja v'ja" + ], + [ + "Ї ї Й й Ё ё Ў ў", + "Ï ï J j Ö ö Ŭ ŭ" + ], + ] + + data_l2c = [ + [ + "я є ю", + "jA jE jU" + ], + [ + "Ї ї Ь ь Ч ч Г г Щ щ Ш ш Ж ж", + "Ï ï Ĵ ĵ Č č Ğ ğ Ŝ ŝ Š š Ž ž" + ], + [ + "Ё ё Ў ў Ъ ъ Ы ы Э э", + "Ö ö Ŭ ŭ Ǒ ǒ Ȳ ȳ Ē ē" + ], + [ + "А́ а́ Е́ е́ Є́ Є́ є́ є́ И́ и́ І́ і́ Ї́ ї́ О́ о́ У́ у́ Ю́ Ю́ ю́ ю́ Я́ Я́ я́ я́", + "Á á É é JÉ Jé jÉ jé Ý ý Í í Ḯ ḯ Ó ó Ú ú JÚ Jú jÚ jú JÁ Já jÁ já" + ], + [ + "Є́с сЄ́с є́с сє́с Ї́с сЇ́с ї́с сї́с Ю́с сЮ́с ю́с сю́с Я́с сЯ́с я́с ся́с", + "Jés sJés jés sjés Ḯs sḮs ḯs sḯs Jús sJús jús sjús Jás sJás jás sjás" + ], + ] + data_c2lr.each do |cyr,lat| + q = @tr.encode(cyr, 'DSTU_9112_A') + assert_equal(lat, q, cyr) + t = @tr.decode(lat, 'DSTU_9112_A') + assert_equal(cyr, t, lat); + end + puts "DSTU_9112_A: c2lr #{data_c2lr.length} tests passed" + data_c2l.each do |cyr,lat| + q = @tr.encode(cyr, 'DSTU_9112_A') + assert_equal(lat, q, cyr) + end + puts "DSTU_9112_A: c2l #{data_c2l.length} tests passed" + data_l2c.each do |cyr,lat| + q = @tr.decode(lat, 'DSTU_9112_A') + assert_equal(cyr, q, lat); + end + puts "DSTU_9112_A: l2c #{data_l2c.length} tests passed" + end + + def test_DSTU_9112_B() + + data_c2lr = [ + [ + "Україна, Хмельницький", + "Ukrajina, Khmeljnycjkyj" + ], + [ + "Щастям б’єш жук їх глицю в фон й ґедзь пріч.", + "Shchastjam b'jesh zhuk jikh ghlycju v fon j gedzj prich." + ], + [ + "ь Ь ль льє льї лью лья лье льі льу льа льйо льо", + "hj Hj lj ljje ljji ljju ljja lj'e lj'i lj'u lj'a ljjo ljo" + ], + [ + "Єл Їл Юл Ял", + "Jel Jil Jul Jal" + ], + [ + "бь вь гь ґь дь жь зь кь ль мь нь пь рь сь ть фь хь ць чь шь щь", + "bj vj ghj gj dj zhj zj kj lj mj nj pj rj sj tj fj khj cj chj shj shchj" + ], + [ + "бя вя гя ґя дя жя зя кя ля мя ня пя ря ся тя фя хя ця чя шя щя", + "bja vja ghja gja dja zhja zja kja lja mja nja pja rja sja tja fja khja cja chja shja shchja" + ], + [ + "б’я в’я г’я ґ’я д’я ж’я з’я к’я л’я м’я н’я п’я р’я с’я т’я ф’я х’я ц’я ч’я ш’я щ’я", + "b'ja v'ja gh'ja g'ja d'ja zh'ja z'ja k'ja l'ja m'ja n'ja p'ja r'ja s'ja t'ja f'ja kh'ja c'ja ch'ja sh'ja shch'ja" + ], + [ + "бй бйо вй гй ґй дй жй зй кй лй мй нй пй рй сй тй фй хй цй чй шй щй", + "b'j b'jo v'j gh'j g'j d'j zh'j z'j k'j l'j m'j n'j p'j r'j s'j t'j f'j kh'j c'j ch'j sh'j shch'j" + ], + [ + "ня ньа н’я нь'н ньн", + "nja nj'a n'ja nj'n njn" + ], + [ + "рос дыня эзёдынъ. бр кроў.", + "ros dywnja ehwzjowdywnoh. br krouh." + ], + [ + "А́ а́ Е́ е́ Є́ є́ И́ и́ І́ і́ Ї́ ї́ О́ о́ У́ у́ Ю́ ю́ Я́ я́", + "Á á É é JÉ jé Ý ý Í í JÍ jí Ó ó Ú ú JÚ jú JÁ já" + ], + [ + "Є́с сЄ́с є́с сє́с Ї́с сЇ́с ї́с сї́с Ю́с сЮ́с ю́с сю́с Я́с сЯ́с я́с ся́с", + "Jés sJés jés sjés Jís sJís jís sjís Jús sJús jús sjús Jás sJás jás sjás" + ], + [ + "' ім’я 'жук' \"жук\" ' '", + "' im'ja 'zhuk' \"zhuk\" ' '" + ], + [ + "Сонце світить майже білим світлом, однак через сильніше розсіювання і поглинання короткохвильової частини спектра атмосферою Землі пряме світло Сонця біля поверхні нашої планети набуває певного жовтого відтінку. Якщо небо ясне, то блакитний відтінок розсіяного світла складається з жовтуватим прямим сонячним світлом і загальне освітлення об’єктів на Землі стає білим.", + "Sonce svitytj majzhe bilym svitlom, odnak cherez syljnishe rozsijuvannja i poghlynannja korotkokhvyljovoji chastyny spektra atmosferoju Zemli prjame svitlo Soncja bilja poverkhni nashoji planety nabuvaje pevnogho zhovtogho vidtinku. Jakshcho nebo jasne, to blakytnyj vidtinok rozsijanogho svitla skladajetjsja z zhovtuvatym prjamym sonjachnym svitlom i zaghaljne osvitlennja ob'jektiv na Zemli staje bilym." + ], + [ + "дуб!дуб\"дуб#дуб$дуб%дуб&дуб'дуб(дуб)дуб*дуб+дуб,дуб-дуб.дуб/дуб:дуб;дуб<дуб=дуб>дуб?дуб@дуб[дуб\\дуб]дуб^дуб_дуб`дуб{дуб|дуб}дуб~дуб", + "dub!dub\"dub#dub$dub%dub&dub'dub(dub)dub*dub+dub,dub-dub.dub/dub:dub;dubdub?dub@dub[dub\\dub]dub^dub_dub`dub{dub|dub}dub~dub" + ], + [ + "бод бод\tбод\nбод\rбод", + "bod bod\tbod\nbod\rbod" + ], + [ + "об😎нап😘неп😭нєп🧐нїп😍нюп😀няп", + "ob😎nap😘nep😭njep🧐njip😍njup😀njap" + ], + ] + + data_c2l = [ + [ + "в’я в'я", + "v'ja v'ja" + ], + [ + "Ї ї Й й Ё ё Ў ў", + "JI ji J j JOW jow UH uh" + ], + ] + + data_l2c = [ + [ + "я ї є ю г ж х щ ш ч ь", + "jA jI jE jU gH zH kH sHcH sH cH hJ" + ], + [ + "А́ а́ Е́ е́ Є́ Є́ є́ є́ И́ и́ І́ і́ Ї́ Ї́ ї́ ї́ О́ о́ У́ у́ Ю́ Ю́ ю́ ю́ Я́ Я́ я́ я́", + "Á á É é JÉ Jé jÉ jé Ý ý Í í JÍ Jí jÍ jí Ó ó Ú ú JÚ Jú jÚ jú JÁ Já jÁ já" + ], + [ + "Є́с сЄ́с є́с сє́с Ї́с сЇ́с ї́с сї́с Ю́с сЮ́с ю́с сю́с Я́с сЯ́с я́с ся́с", + "Jés sJés jés sjés Jís sJís jís sjís Jús sJús jús sjús Jás sJás jás sjás" + ], + ] + data_c2lr.each do |cyr,lat| + q = @tr.encode(cyr, 'DSTU_9112_B') + assert_equal(lat, q, cyr) + t = @tr.decode(lat, 'DSTU_9112_B') + assert_equal(cyr, t, lat); + end + puts "DSTU_9112_B: c2lr #{data_c2lr.length} tests passed" + data_c2l.each do |cyr,lat| + q = @tr.encode(cyr, 'DSTU_9112_B') + assert_equal(lat, q, cyr) + end + puts "DSTU_9112_B: c2l #{data_c2l.length} tests passed" + data_l2c.each do |cyr,lat| + q = @tr.decode(lat, 'DSTU_9112_B') + assert_equal(cyr, q, lat); + end + puts "DSTU_9112_B: l2c #{data_l2c.length} tests passed" + end + + def test_KMU_55() + + data_c2l = [ + [ + "Україна, Хмельницький", + "Ukraina, Khmelnytskyi" + ], + [ + "Щастям б’єш жук їх глицю в фон й ґедзь пріч.", + "Shchastiam biesh zhuk yikh hlytsiu v fon y gedz prich." + ], + [ + "згин зГ зГин Згин Зг ЗГ ЗГИН", + "zghyn zGH zGhyn Zghyn Zgh ZGH ZGHYN" + ], + [ + "ь Ь ль льє льї лью лья лье льі льу льа льйо льо", + " l lie li liu lia le li lu la lio lo" + ], + [ + "Єл Їл Юл Ял", + "Yel Yil Yul Yal" + ], + [ + "бь вь гь ґь дь жь зь кь ль мь нь пь рь сь ть фь хь ць чь шь щь", + "b v h g d zh z k l m n p r s t f kh ts ch sh shch" + ], + [ + "бя вя гя ґя дя жя зя кя ля мя ня пя ря ся тя фя хя ця чя шя щя", + "bia via hia gia dia zhia zia kia lia mia nia pia ria sia tia fia khia tsia chia shia shchia" + ], + [ + "б’я в’я г’я ґ’я д’я ж’я з’я к’я л’я м’я н’я п’я р’я с’я т’я ф’я х’я ц’я ч’я ш’я щ’я", + "bia via hia gia dia zhia zia kia lia mia nia pia ria sia tia fia khia tsia chia shia shchia" + ], + [ + "бй бйо вй гй ґй дй жй зй кй лй мй нй пй рй сй тй фй хй цй чй шй щй", + "bi bio vi hi gi di zhi zi ki li mi ni pi ri si ti fi khi tsi chi shi shchi" + ], + [ + "А́ а́ Е́ е́ Є́ є́ И́ и́ І́ і́ Ї́ ї́ О́ о́ У́ у́ Ю́ ю́ Я́ я́", + "Á á É é YÉ yé Ý ý Í í YÍ yí Ó ó Ú ú YÚ yú YÁ yá" + ], + [ + "Є́с сЄ́с є́с сє́с Ї́с сЇ́с ї́с сї́с Ю́с сЮ́с ю́с сю́с Я́с сЯ́с я́с ся́с", + "Yés sIés yés siés Yís sÍs yís sís Yús sIús yús siús Yás sIás yás siás" + ], + [ + "' ім’я 'жук' \"жук\" ' '", + "' imia 'zhuk' \"zhuk\" ' '" + ], + [ + "Сонце світить майже білим світлом, однак через сильніше розсіювання і поглинання короткохвильової частини спектра атмосферою Землі пряме світло Сонця біля поверхні нашої планети набуває певного жовтого відтінку. Якщо небо ясне, то блакитний відтінок розсіяного світла складається з жовтуватим прямим сонячним світлом і загальне освітлення об’єктів на Землі стає білим.", + "Sontse svityt maizhe bilym svitlom, odnak cherez sylnishe rozsiiuvannia i pohlynannia korotkokhvylovoi chastyny spektra atmosferoiu Zemli priame svitlo Sontsia bilia poverkhni nashoi planety nabuvaie pevnoho zhovtoho vidtinku. Yakshcho nebo yasne, to blakytnyi vidtinok rozsiianoho svitla skladaietsia z zhovtuvatym priamym soniachnym svitlom i zahalne osvitlennia obiektiv na Zemli staie bilym." + ], + [ + "в’я в'я", + "via via" + ], + [ + "дуб!дуб\"дуб#дуб$дуб%дуб&дуб'дуб(дуб)дуб*дуб+дуб,дуб-дуб.дуб/дуб:дуб;дуб<дуб=дуб>дуб?дуб@дуб[дуб\\дуб]дуб^дуб_дуб`дуб{дуб|дуб}дуб~дуб", + "dub!dub\"dub#dub$dub%dub&dubdub(dub)dub*dub+dub,dub-dub.dub/dub:dub;dubdub?dub@dub[dub\\dub]dub^dub_dub`dub{dub|dub}dub~dub" + ], + [ + "бод бод\tбод\nбод\rбод", + "bod bod\tbod\nbod\rbod" + ], + [ + "об😎нап😘неп😭нєп🧐нїп😍нюп😀няп", + "ob😎nap😘nep😭niep🧐nip😍niup😀niap" + ], + ] + data_c2l.each do |cyr,lat| + q = @tr.encode(cyr, 'KMU_55') + assert_equal(lat, q, cyr) + end + puts "KMU_55: c2l #{data_c2l.length} tests passed" + end + +end + +test = TestUkrainianLatin.new +spec = TestUkrainianLatin.instance_methods(false).grep(/^test_/) +spec.each do |name| + test.send(name) +end diff --git a/ruby/uklatn.gemspec b/ruby/uklatn.gemspec new file mode 100644 index 0000000..6a33d42 --- /dev/null +++ b/ruby/uklatn.gemspec @@ -0,0 +1,16 @@ +Gem::Specification.new do |s| + s.name = 'uklatn' + s.version = '1.13.0' + s.license = 'MIT' + s.summary = 'Ukrainian Cyrillic transliteration to Latin script' + s.description = <<~EOS + Ukraine national transliteration schemes: + - DSTU 9112:2021, + - KMU 55:2010 (not reversible) + EOS + s.author = 'Pavlo Ivashkov' + s.homepage = 'https://github.com/paiv/uklatn' + s.metadata = { 'source_code_uri' => 'https://github.com/paiv/uklatn' } + s.required_ruby_version = '>= 2.6.0' + s.files = ['lib/uklatn.rb'] +end diff --git a/tools/Makefile b/tools/Makefile index 6b7e992..e3925ff 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -3,6 +3,7 @@ .PHONY: java .PHONY: js .PHONY: py +.PHONY: ruby .PHONY: swift PYTHON ?= python @@ -34,6 +35,12 @@ py: @echo "# Generated by gentests.py, do not edit.\n" > "$(PROJECT_ROOT)/python/tests/uklatn_tests.py" @$(PYTHON) gentests.py py >> "$(PROJECT_ROOT)/python/tests/uklatn_tests.py" +ruby: + @echo "# Generated by gentables.py, do not edit.\n" > "$(PROJECT_ROOT)/ruby/lib/uklatn.rb" + @$(PYTHON) gentables.py ruby >> "$(PROJECT_ROOT)/ruby/lib/uklatn.rb" + @echo "# Generated by gentests.py, do not edit.\n" > "$(PROJECT_ROOT)/ruby/test/uklatn/test_uklatn.rb" + @$(PYTHON) gentests.py ruby >> "$(PROJECT_ROOT)/ruby/test/uklatn/test_uklatn.rb" + swift: @echo "/* Generated by gentables.py, do not edit. */\n" > "$(PROJECT_ROOT)/swift/Sources/UkrainianLatin/UKLatn.swift" @$(PYTHON) gentables.py swift >> "$(PROJECT_ROOT)/swift/Sources/UkrainianLatin/UKLatn.swift" diff --git a/tools/gen/gen_ruby.py b/tools/gen/gen_ruby.py new file mode 100755 index 0000000..dc8438e --- /dev/null +++ b/tools/gen/gen_ruby.py @@ -0,0 +1,230 @@ +import io +import json +import logging +import re +import textwrap +from pathlib import Path + + +logger = logging.getLogger(Path(__file__).stem) + + +def gen_tests(fns): + def _parse_tests(fn): + def parse_kind(s): + match s.lower().split(): + case ['cyr', '<>', 'lat']: return 'c2lr' + case ['lat', '<>', 'cyr']: return 'l2cr' + case ['cyr', '>', 'lat']: return 'c2l' + case ['lat', '>', 'cyr']: return 'l2c' + case _: + raise Exception(f'unknown test kind: {s!r}') + with fn.open() as fp: + data = json.load(fp) + return [[parse_kind(obj['test']), obj['cyr'], obj['lat']] for obj in data] + + def table_name(s): + return re.sub(r'test_', '', s) + def _j(s): + return json.dumps(s, ensure_ascii=False) + + def _emit_testdata(kind, data, table, file): + data = [(cyr,lat) for k,cyr,lat in data if k == kind] + if not data: return + dump = ''.join(f'\n[\n {_j(cyr)},\n {_j(lat)}\n],' for cyr,lat in data) + so = f'\ndata_{kind} = [{dump}\n]' + so = textwrap.indent(so, ' ' * 8) + print(so, file=file) + + def _emit_tests(kind, data, table, file): + data = [(cyr,lat) for k,cyr,lat in data if k == kind] + if not data: return + print(f' data_{kind}.each do |cyr,lat|', file=file) + if kind[0] == 'c': + print(f' q = @tr.encode(cyr, {table!r})', file=file) + print(f' assert_equal(lat, q, cyr)', file=file) + else: + print(f' q = @tr.decode(lat, {table!r})', file=file) + print(f' assert_equal(cyr, q, lat);', file=file) + if kind[-1] == 'r': + if kind[0] == 'c': + print(f' t = @tr.decode(lat, {table!r})', file=file) + print(f' assert_equal(cyr, t, lat);', file=file) + else: + print(f' t = @tr.encode(cyr, {table!r})', file=file) + print(f' assert_equal(lat, t, cyr)', file=file) + print(' end', file=file) + print(f' puts "{table}: {kind} #{{data_{kind}.length}} tests passed"', file=file) + + def _emit_testset(data, table, file): + print(f'\n def test_{table}()', file=file) + _emit_testdata('c2lr', data, table, file=file) + _emit_testdata('l2cr', data, table, file=file) + _emit_testdata('c2l', data, table, file=file) + _emit_testdata('l2c', data, table, file=file) + _emit_tests('c2lr', data, table, file=file) + _emit_tests('l2cr', data, table, file=file) + _emit_tests('c2l', data, table, file=file) + _emit_tests('l2c', data, table, file=file) + print(' end', file=file) + + context = dict() + with io.StringIO() as so: + for fn in fns: + logger.info(f'processing {fn!s}') + name = fn.stem + table = table_name(name) + data = _parse_tests(fn) + _emit_testset(data, table, file=so) + context['test_cases'] = so.getvalue() + + context['test_raise'] = '"failed\\n input: #{input}\\nexpect: #{expect}\\nactual: #{actual}\\n #{arr}"' + + template = '''require_relative '../../lib/uklatn.rb' + +class TestUkrainianLatin + + def initialize + @tr = UkrainianLatin.new + end + + def assert_equal(expect, actual, input) + if expect != actual + s = actual.chars + arr = expect.chars.map {{|c| c == s.shift ? ' ' : '^'}}.join + raise {test_raise} + end + end +{test_cases} +end + +test = TestUkrainianLatin.new +spec = TestUkrainianLatin.instance_methods(false).grep(/^test_/) +spec.each do |name| + test.send(name) +end +''' + text = template.format(**context) + return text + + +def gen_transforms(fns, default_table=None): + def table_name(s): + s, = re.findall(r'uk_Latn_(.*?)(?:-uk)?\s*$', s, flags=re.I) + return s.replace('-', '_') + def class_name(s): + return 'Uklatn_' + s.replace('-', '_') + def _isdec(s): + return s.startswith('uk_Latn_') + def _j(s): + return json.dumps(s, ensure_ascii=False) + def _load_rules(data): + return [s if isinstance(s, str) else [ + '|'.join(r['regex'] for r in s), + [r['map'] for r in s] + ] for s in data] + + def _emit_trrules(rules, file): + so = '' + for sid, section in enumerate(rules): + if not isinstance(section, str): + rx, maps = section + gn = len(maps) + so += f'@rx{sid} = /{rx}/\n' + so += f'@maps{sid} = [\n' + for d in maps: + m = ','.join((_j(k) + '=>' + _j(v)) for k,v in d.items()) + so += f' {{{m}}},\n' + so += ']\n' + print(textwrap.indent(so, ' ' * 8), end='', file=file) + + def _emit_trbody(rules, file): + so = '' + for sid, section in enumerate(rules): + if isinstance(section, str): + if section not in ('NFC', 'NFD', 'NFKC', 'NFKD'): + raise Exception(f'invalid transform: {section!r}') + so += f'text = text.unicode_normalize(:{section.lower()})\n' + else: + rx, maps = section + gn = len(maps) + so += f'text = text.gsub(@rx{sid}) do |m|\n' + for mid in reversed(range(1, gn+1)): + so += f' next @maps{sid}[{mid-1}].fetch(${mid}, ${mid}) unless ${mid}.nil?\n' + so += ' m\n' + so += 'end\n' + print(textwrap.indent(so, ' ' * 8), end='', file=file) + + def _emit_tr(cname, rules, file): + rules = _load_rules(rules) + print(f'class {cname} # :nodoc:', file=file) + print(' def initialize()', file=file) + _emit_trrules(rules, file=file) + print(' end\n', file=file) + print(' def transform(text)', file=file) + _emit_trbody(rules, file=file) + print(' end', file=file) + print('end\n', file=file) + + context = dict() + tables = dict() + with io.StringIO() as so: + for fn in fns: + logger.info(f'processing {fn!s}') + with fn.open() as fp: + rules = json.load(fp) + name = fn.stem + table = table_name(name) + cname = class_name(name) + if table not in tables: + tables[table] = [None, None] + tables[table][_isdec(name)] = cname + _emit_tr(cname, rules, so) + classdefs_tables = textwrap.indent(so.getvalue(), ' ' * 4) + + def _emit_tabledef(tables): + so = '' + so += 'TABLES = Hash.new([nil, nil]) # :nodoc:' + for tid, (table, (enc, dec)) in enumerate(tables.items(), 1): + dasht = table.replace('_', '-') + enc = f'{enc}.new()' if enc else 'nil' + dec = f'{dec}.new()' if dec else 'nil' + so += f'\nTABLES[{table!r}] = [{enc}, {dec}]' + so += f'\nTABLES[{dasht!r}] = TABLES[{table!r}]' + so = textwrap.indent(so, ' ' * 4) + return so + + tabledef = _emit_tabledef(tables) + + context['global_tables'] = classdefs_tables + tabledef + context['default_table'] = default_table + + template = '''\ +# Ukrainian Cyrillic transliteration to and from Latin script. +# +# Tables: +# 'DSTU_9112_A': DSTU 9112:2021 System A +# 'DSTU_9112_B': DSTU 9112:2021 System B +# 'KMU_55': KMU 55:2010, not reversible +class UkrainianLatin + + # Transliterates a string of Ukrainian Cyrillic to Latin script. + def encode(text, table = {default_table!r}) + tr = TABLES[table][0] + return tr.transform(text) if tr + raise ArgumentError.new("invalid table #{{table}}") + end + + # Re-transliterates a string of Ukrainian Latin to Cyrillic script. + def decode(text, table = {default_table!r}) + tr = TABLES[table][1] + return tr.transform(text) if tr + raise ArgumentError.new("invalid table #{{table}}") + end + +{global_tables} +end +''' + text = template.format(**context) + return text + diff --git a/tools/gentables.py b/tools/gentables.py index b42d628..c9c4bd5 100755 --- a/tools/gentables.py +++ b/tools/gentables.py @@ -73,6 +73,17 @@ def gen_py(src): logger.info('PY generator end') +def gen_ruby(src): + logger.info('Ruby generator start') + from gen import gen_ruby + + source = _basegen(args, 'src/regex', 'uk*.json', gen_ruby.gen_transforms) + for text in source: + print(text, end='') + + logger.info('Ruby generator end') + + def gen_swift(src): logger.info('Swift generator start') from gen import gen_swift @@ -102,6 +113,10 @@ def gen_swift(src): parse_py.add_argument('source', nargs='*', help='source directory') parse_py.set_defaults(func=gen_py) + parse_ruby = subpar.add_parser('ruby', help='Ruby code generator') + parse_ruby.add_argument('source', nargs='*', help='source directory') + parse_ruby.set_defaults(func=gen_ruby) + parse_java = subpar.add_parser('java', help='Java code generator') parse_java.add_argument('source', nargs='*', help='source directory') parse_java.set_defaults(func=gen_java) diff --git a/tools/gentests.py b/tools/gentests.py index aecd008..fc5413e 100755 --- a/tools/gentests.py +++ b/tools/gentests.py @@ -70,6 +70,17 @@ def gen_py(args): logger.info('PY generator end') +def gen_ruby(args): + logger.info('Ruby generator start') + from gen import gen_ruby + + source = _basegen(args, 'src/tests', 'test*.json', gen_ruby.gen_tests) + for text in source: + print(text, end='') + + logger.info('Ruby generator end') + + def gen_swift(args): logger.info('Swift generator start') from gen import gen_swift @@ -103,6 +114,10 @@ def gen_swift(args): parse_java.add_argument('source', nargs='*', help='source directory') parse_java.set_defaults(func=gen_java) + parse_ruby = subpar.add_parser('ruby', help='Ruby code generator') + parse_ruby.add_argument('source', nargs='*', help='source directory') + parse_ruby.set_defaults(func=gen_ruby) + parse_swift = subpar.add_parser('swift', help='Swfit code generator') parse_swift.add_argument('source', nargs='*', help='source directory') parse_swift.set_defaults(func=gen_swift)