diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..1ef5876 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,68 @@ +name: CI +on: + pull_request: + branches: + - master + push: + branches: + - master + tags: '*' +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - '1.0' + - '1' + - 'nightly' + os: + - ubuntu-latest + arch: + - x64 + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: actions/cache@v1 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v1 + with: + file: lcov.info +# docs: +# name: Documentation +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# - uses: julia-actions/setup-julia@v1 +# with: +# version: '1' +# - run: | +# julia --project=docs -e ' +# using Pkg +# Pkg.develop(PackageSpec(path=pwd())) +# Pkg.instantiate()' +# - run: | +# julia --project=docs -e ' +# using Documenter: doctest +# using ResourceBundles +# doctest(ResourceBundles)' +# - run: julia --project=docs docs/make.jl +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 0000000..7784f24 --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,26 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: "Install CompatHelper" + run: | + import Pkg + name = "CompatHelper" + uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" + version = "2" + Pkg.add(; name, uuid, version) + shell: julia --color=yes {0} + - name: "Run CompatHelper" + run: | + import CompatHelper + CompatHelper.main() + shell: julia --color=yes {0} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 0000000..f49313b --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,15 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2a22f42..0000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: julia -os: - - linux - - osx - - windows -julia: - - 1.3 - - nightly -notifications: - email: false -codecov: true diff --git a/Project.toml b/Project.toml index 1eac9f7..41b8362 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ResourceBundles" uuid = "a9a2f26c-b040-4a74-98fd-7b69a57a6ccd" -authors = ["Klaus Crusius "] -version = "0.1.0" +authors = ["Klaus Crusius and contributors"] +version = "0.2.0" [deps] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 1d8a333..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,47 +0,0 @@ -environment: - matrix: - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" - - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe" - - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe" - -## uncomment the following lines to allow failures on nightly julia -## (tests will run but not make your overall status red) -#matrix: -# allow_failures: -# - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe" -# - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe" - -branches: - only: - - master - - /release-.*/ - -notifications: - - provider: Email - on_build_success: false - on_build_failure: false - on_build_status_changed: false - -install: - - ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12" -# If there's a newer build queued for the same PR, cancel this one - - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` - https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` - Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` - throw "There are newer queued builds for this pull request, failing early." } -# Download most recent Julia Windows binary - - ps: (new-object net.webclient).DownloadFile( - $env:JULIA_URL, - "C:\projects\julia-binary.exe") -# Run installer silently, output to C:\projects\julia - - C:\projects\julia-binary.exe /S /D=C:\projects\julia - -build_script: -# Need to convert from shallow to complete for Pkg.clone to work - - IF EXIST .git\shallow (git fetch --unshallow) - - C:\projects\julia\bin\julia -e "versioninfo(); - Pkg.clone(pwd(), \"ResourceBundles\"); Pkg.build(\"ResourceBundles\")" - -test_script: - - C:\projects\julia\bin\julia -e "Pkg.test(\"ResourceBundles\")" diff --git a/src/poreader.jl b/src/poreader.jl index 2b6ed07..6b373ad 100644 --- a/src/poreader.jl +++ b/src/poreader.jl @@ -10,14 +10,16 @@ mutable struct TranslationItem end function init_ti!(ti::TranslationItem) - ti.id = ""; ti.plural = ""; ti.context = "" + ti.id = "" + ti.plural = "" + ti.context = "" empty!(ti.strings) end """ read_po_file'('f::AbstractString')' -Read a file, which contains text data according to the PO format of gettext +Read a file, which contains text data according to the PO format of gettext ref: //https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html Format is strictly line separated. A line staring with '#' is a comment line and ignored. @@ -31,8 +33,8 @@ two separate key-value pairs are generated, with identical '('typical array')' v """ function read_po_file(file::Union{AbstractString,IO}) - in_sequence = false; - in_keyword = false; + in_sequence = false + in_keyword = false dict = Vector{Pair{String,Union{String,Vector{String}}}}() buffer = IOBuffer() keyword = "" @@ -68,28 +70,35 @@ function read_po_file(file::Union{AbstractString,IO}) in_keyword = false process_keyword(keyword, index, line) end - - function process_keyword(key::AbstractString, index::Int, text::AbstractString) + + function process_keyword( + key::AbstractString, + index::Int, + text::AbstractString, + ) index == -1 || key == "msgstr" || error("not allowed $key[$index]") if key == "msgid" in_ti == 3 && process_translation_item(ti) in_ti <= 1 || error("unexpected $key '$text'") in_ti = 2 - isempty(ti.id) || error("several msgids in line ('$ti.id', '$text')") + isempty(ti.id) || + error("several msgids in line ('$ti.id', '$text')") ti.id = text elseif key == "msgstr" in_ti != 0 || error("unexpected $key '$text'") in_ti = 3 - ti.strings[max(index,0)] = text + ti.strings[max(index, 0)] = text elseif key == "msgid_plural" in_ti == 2 || error("unexpected $key '$text'") - isempty(ti.plural) || error("several msgid_plural in line ('$ti.plural', '$text')") + isempty(ti.plural) || + error("several msgid_plural in line ('$ti.plural', '$text')") ti.plural = text elseif key == "msgctxt" in_ti == 3 && process_translation_item(ti) in_ti == 0 || error("unexpected $key '$text'") in_ti = 1 - isempty(ti.context) || error("several msgctx in line ('$ti.context', '$text')") + isempty(ti.context) || + error("several msgctx in line ('$ti.context', '$text')") ti.context = text end end @@ -101,10 +110,10 @@ function read_po_file(file::Union{AbstractString,IO}) end for line in eachline(file) - if ( m = match(REG_KEYWORD, line) ) != nothing + if (m = match(REG_KEYWORD, line)) != nothing in_sequence && end_sequence() begin_keyword(m.captures[1], m.captures[3], m.captures[4]) - elseif ( m = match(REG_STRING, line) ) != nothing + elseif (m = match(REG_STRING, line)) != nothing continue_sequence(m.captures[1]) end end @@ -135,23 +144,26 @@ function read_mo_file(f::AbstractString) d = reinterpret(UInt32, data[1:28]) le_machine = ENDIAN_BOM == 0x04030201 le_file = d[1] == MAGIC - le_file || ntoh(d[1]) == MAGIC || error("wrong magic number - no MO-file format") + le_file || + ntoh(d[1]) == MAGIC || + error("wrong magic number - no MO-file format") conv = le_file ? ltoh : ntoh - le_machine != le_file && ( d = conv.(d) ) + le_machine != le_file && (d = conv.(d)) magic, rev, n, origp, tranp, hsize, hashp = d[1:7] revma, revmi = rev >> 16, rev & 0xffff - origp, tranp, hashp = origp÷4, tranp÷4, hashp÷4 - revma == 0 || error("revision id ($revma,$revmi) in MO-file - only supported: (0, x)") - datal >= (tranp+2n)*4 || error("file too short - no MO file format") + origp, tranp, hashp = origp ÷ 4, tranp ÷ 4, hashp ÷ 4 + revma == 0 || + error("revision id ($revma,$revmi) in MO-file - only supported: (0, x)") + datal >= (tranp + 2n) * 4 || error("file too short - no MO file format") d = reinterpret(Int32, data[5:(tranp+2n)*4]) - le_machine != le_file && ( d = conv.(d) ) + le_machine != le_file && (d = conv.(d)) for i = 0:2:2n-1 leno = d[origp+i] ptro = d[origp+i+1] lent = d[tranp+i] ptrt = d[tranp+i+1] - stro = String(data[ptro+1:ptro+leno]) - strt = String(data[ptrt+1:ptrt+lent]) + stro = String(data[ptro+1:ptro+leno]) + strt = String(data[ptrt+1:ptrt+lent]) ix = firstnn(findfirst(isequal(EOT), stro), 0) if ix > 0 ti.context = stro[1:prevind(stro, ix)] @@ -177,7 +189,7 @@ firstnn(a::Any, b::Any...) = a # add translation item to output vector function add_translation_item!(dict::Vector, ti::TranslationItem) - val = map(p->p.second, sort(collect(ti.strings))) + val = map(p -> p.second, sort(collect(ti.strings))) if length(val) == 1 && isempty(ti.plural) val = val[1] end @@ -188,7 +200,8 @@ function add_translation_item!(dict::Vector, ti::TranslationItem) end # Format strings with and without context information -key(ctx, id) = isempty(id) || isempty(ctx) ? id : string(SCONTEXT, ctx, SCONTEXT, id) +key(ctx, id) = + isempty(id) || isempty(ctx) ? id : string(SCONTEXT, ctx, SCONTEXT, id) skey(ti::TranslationItem) = key(ti.context, ti.id) pkey(ti::TranslationItem) = key(ti.context, ti.plural) @@ -200,7 +213,7 @@ Extract plural data (nplurals and function plural(n)) from string. function read_header(str::AbstractString) io = IOBuffer(str) for line in eachline(io) - if ( m = match(REG_PLURAL, line) ) != nothing + if (m = match(REG_PLURAL, line)) != nothing return translate_plural_data(m.captures[1]) end end @@ -209,7 +222,8 @@ end module Sandbox end # clip output of eval(ex(n)) to interval [0, m) -create_plural(ex::Expr, m) = n -> max(min(Base.invokelatest(Sandbox.eval(ex), n), m-1), 0) +create_plural(ex::Expr, m) = + n -> max(min(Base.invokelatest(Sandbox.eval(ex), n), m - 1), 0) # avoid the following error when calling f by invokelatest: # "MethodError: no method matching (::getfield(ResourceBundles, Symbol("...")))(::Int64) @@ -230,7 +244,7 @@ function translate_plural_data(str::AbstractString) plural = n -> n != 0 str = replace(str, ':' => " : ") # surround : by blanks str = replace(str, '?' => " ? ") # surround ? by blanks - str = replace(str, "/" => "÷") # use Julia integer division for / + str = replace(str, "/" => "÷") # use Julia integer division for / top = Meta.parse(str) isa(top, Expr) || return nplurals, plural for a in top.args @@ -245,7 +259,7 @@ function translate_plural_data(str::AbstractString) end end end - nplurals, plural + nplurals, plural end const REG_KEYWORD = r"""^\s*([a-zA-Z_]+)(\[(\d+)\])?\s+"(.*)"\s*$""" @@ -253,4 +267,3 @@ const REG_STRING = r"""^\s*"(.*)"\s*$""" const REG_COMMENT = r"""^\s*#""" const REG_PLURAL = r"""^\s*Plural-Forms:(.*)$""" - diff --git a/test/libc.jl b/test/libc.jl index 3a2f4c5..fdde22f 100644 --- a/test/libc.jl +++ b/test/libc.jl @@ -8,38 +8,33 @@ import .CLocales: newlocale_c, strcoll_c, nl_langinfo_c const P0 = Ptr{Nothing}(0) -if Sys.islinux() && get(Base.ENV, "NO_CLOCALE", "") != "1" +@test newlocale_c(LC._MASK_ALL, "invalidxxx", P0) == P0 -@test newlocale_c(LC._MASK_ALL, "invalidxxx", P0) == P0 -@test newlocale_c(LC._MASK_ALL, "en_US.utf8", P0) != P0 - -test_locale_C = newlocale_c(LC._MASK_ALL, "C", P0) -test_locale_ca = newlocale_c(LC._MASK_ALL, "en_US.utf8", P0) -@test duplocale(test_locale_ca) != P0 - -@test unsafe_string(nl_langinfo_c(Cint(0xffff), test_locale_ca)) == "en_US.utf8" - -COLL_TESTS_C = [ - ( "a", "b", -1 ), - ( "A", "b", -1 ), - ( "a", "B", +1 ), +COLL_C = [ + ("a", "b", -1), + ("A", "b", -1), + ("a", "B", +1), ] -COLL_TESTS_FR = [ - ( "a", "b", -1 ), - ( "A", "b", -1 ), - ( "a", "B", -1 ), +COLL_en = [ + ("a", "b", -1), + ("A", "b", -1), + ("a", "B", -1), ] -@testset "string comparisons '$a' ~ '$b'" for (a, b, r) in COLL_TESTS_C - @test sign(strcoll_c(a, b, test_locale_C)) == r -end +@testset "C-locale for $loc" for (loc, COLL) in (("C", COLL_C), ("en_US.utf8", COLL_en)) + test_locale = newlocale_c(LC._MASK_ALL, loc, P0) + if test_locale != P0 + @test duplocale(test_locale) != P0 + @test unsafe_string(nl_langinfo_c(Cint(0xffff), test_locale)) == loc -@testset "string comparisons '$a' ~ '$b'" for (a, b, r) in COLL_TESTS_FR - @test sign(strcoll_c(a, b, test_locale_ca)) == r -end + @testset "string comparisons '$a' ~ '$b'" for (a, b, r) in COLL + @test sign(strcoll_c(a, b, test_locale)) == r + end -@test freelocale(test_locale_ca) == nothing -freelocale(test_locale_C) + @test freelocale(test_locale) === nothing + else + @info "no C-locale defined for '$loc'" + end end #################################