• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sile-typesetter / sile / 14908152066

08 May 2025 01:49PM UTC coverage: 61.309% (-5.7%) from 67.057%
14908152066

push

github

alerque
chore(registries): Touchup command registry deprecation shims

1 of 2 new or added lines in 2 files covered. (50.0%)

1379 existing lines in 46 files now uncovered.

13556 of 22111 relevant lines covered (61.31%)

11834.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.81
/languages/unicode.lua
1
local base = require("languages.base")
42✔
2

3
local language = pl.class(base)
42✔
4
language._name = "unicode"
42✔
5

6
function language:_init (typesetter)
42✔
7
   base._init(self, typesetter)
133✔
8
end
9

10
function language:setupNodeMaker ()
42✔
11
   self.nodemaker = require("languages.unicode-nodemaker")
131✔
12
end
13

14
local icu = require("justenoughicu")
42✔
15

16
function language:_numberingMethod (input)
42✔
17
   return ("numberTo%s"):format(icu.case(input, "und", "title"))
96✔
18
end
19

20
-- Decent subset from unum.h
21
local icuStyles = {
42✔
22
   default = 0, -- UNUM_PATTERN_DECIMAL
23
   decimal = 1, -- UNUM_DECIMAL
24
   string = 5, -- UNUM_SPELLOUT
25
   ordinal = 6, -- UNUM_ORDINAL
26
}
27

28
-- Numbering system for which we _know_ that ICU doesn't have a
29
-- default(0) format style rule (i.e. spits out latin)
30
-- This table is just an optimization to avoid calling ICU twice when this
31
-- occurs, e.g. "roman" may be quite frequent as a numbering system.
32
local icuStyleBypass = {
42✔
33
   roman = true,
34
}
35

36
local icuFormat = function (num, lang, options)
37
   -- Consistency: further below we'll concatenate those, and an empty
38
   -- string is likely a user mistake.
39
   if not lang and not options.system then
96✔
40
      SU.warn("Number formatting needs a language or a numbering system")
×
41
      return tonumber(num)
×
42
   end
43

44
   -- ICU format style (enum)
45
   options.style = options.style or "default"
96✔
46
   local icustyle = options.style and icuStyles[options.style]
96✔
47
   if not icustyle then
96✔
48
      SU.warn("Number formatting style is unrecognized (using default as fallback)")
×
49
      icustyle = 0
×
50
   end
51

52
   -- ICU locale: see  https://unicode-org.github.io/icu/userguide/locale/
53
   -- Ex. "en", "en-US", "sr-Latn"...
54
   local iculocale = lang or ""
96✔
55
   -- ICU keyword for a numbering system specifier: @numbers=xxxx
56
   -- The specifiers are defined here:
57
   -- https://github.com/unicode-org/cldr/blob/main/common/bcp47/number.xml
58
   if options.system then
96✔
59
      options.system = options.system:lower()
192✔
60
      iculocale = iculocale .. "@numbers=" .. options.system
96✔
61
      if icuStyleBypass[options.system] then
96✔
UNCOV
62
         icustyle = 1
×
63
      end
64
   end
65

66
   local ok, result = pcall(icu.format_number, num, iculocale, icustyle)
96✔
67
   if ok and options.system and icustyle == 0 and options.system ~= "latn" and result == tostring(num) then
96✔
68
      -- There are valid cases where "@numbers=xxxx" with default(0) and decimal(1) styles both work.
69
      -- Typically, with num=1234
70
      --   "@numbers=arab" in default(0) --> ١٢٣٤
71
      --   "@numbers=arab", in decimal(1) --> ١٬٢٣٤
72
      -- But in many cases, ICU may fallback to latin, e.g. take "roman" (or "grek")
73
      --   "@numbers=roman" in default(0) --> 1234
74
      --   "@numbers=roman" in default(1) --> MCCXXXIV
75
      -- Be user friendly and attempt honoring the script.
UNCOV
76
      ok, result = pcall(icu.format_number, num, "@numbers=" .. options.system, 1)
×
77
   end
78
   if not ok then
96✔
79
      SU.warn("Number formatting failed: " .. tostring(result))
×
80
   end
81
   return tostring(ok and result or num)
96✔
82
end
83

84
--- Formats a number according to options, and optional case
85
--- Options:
86
--- - system: a numbering system string, e.g. "latn" (= "arabic"), "roman", "arab", etc.
87
---   With the addition of "alpha".
88
---   Casing is guessed from the system (e.g. roman, Roman, ROMAN) unless specified
89
--- - style: a format style string, i.e. "default", "decimal", "ordinal", "string")
90
---   E.g. in English and latin script:   1234        1,234     1,124th    one thousand...
91
---   Possibly extended by additional language-specific formatting rules.
92
--- Obviously, some combinations of system, style, and case won't do anything of worth.
93
function language:formatNumber (num, options, case)
42✔
94
   if math.abs(num) > 9223372036854775807 then
96✔
95
      SU.warn("Integers larger than 64 bits do not reproduce properly in all formats")
×
96
   end
97
   options = options or {}
96✔
98
   -- "arabic" is the weirdly name, but quite friendly, used e.g. in counters and
99
   -- in several other places, let's keep it as a compatibility alias.
100
   if options.system == "arabic" then
96✔
101
      options.system = "latn"
96✔
102
   end
103
   local system = options.system
96✔
104
   -- Attempt to derive a case from the casing of system if none otherwise specified
105
   if not case then
96✔
106
      if system then
96✔
107
         if system:match("^%l") then
96✔
108
            case = "lower"
96✔
UNCOV
109
         elseif system:match("^.%l") then
×
110
            case = "title"
×
111
         else
UNCOV
112
            case = "upper"
×
113
         end
114
      else
115
         case = "lower"
×
116
      end
117
   end
118
   -- Now that we're done guessing the case, normalize system to lower for further use
119
   system = system and system:lower()
192✔
120
   local lang = system and system == "roman" and "la" or self._name
96✔
121
   local style = options.style
96✔
122
   local result
123
   if style and type(getmetatable(self)[self:_numberingMethod(style)]) == "function" then
96✔
124
      result = self[self:_numberingMethod(style)](self, num, options)
×
125
   elseif system and type(self[self:_numberingMethod(system)]) == "function" then
192✔
UNCOV
126
      result = self[self:_numberingMethod(system)](self, num, options)
×
127
   else
128
      result = icuFormat(num, lang, options)
192✔
129
   end
130
   return icu.case(result, lang, case)
96✔
131
end
132

133
-- Alpha is a special case (a numbering system, though this table is for formatting style hooks normally)
134
function language:numberToAlpha (num)
42✔
UNCOV
135
   local out = ""
×
UNCOV
136
   local a = string.byte("a")
×
137
   repeat
UNCOV
138
      num = num - 1
×
UNCOV
139
      out = string.char(num % 26 + a) .. out
×
UNCOV
140
      num = (num - num % 26) / 26
×
UNCOV
141
   until num < 1
×
UNCOV
142
   return out
×
143
end
144

145
-- Greek is another special case:
146
-- There are books where one wants to number items with Greek letters in sequence, e.g. annotations in biblical material
147
-- etc. as in "α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω". We can't use ICU "grek" or "greklow" numbering systems
148
-- because they are non-arithmetical, e.g. 6 is a digamma (ϝ´), 11 is iota alpha (ια´), etc. and they are also all
149
-- followed by a numeric marker ("keraia").
150
function language:numberToGreek (num)
42✔
151
   local out = ""
×
152
   local a = SU.codepoint("α") -- alpha
×
153
   if num < 18 then
×
154
      -- alpha to rho
155
      out = luautf8.char(num + a - 1)
×
156
   elseif num < 25 then
×
157
      -- sigma to omega (unicode has two sigmas here, we skip one)
158
      out = luautf8.char(num + a)
×
159
   else
160
      -- Don't try to be too clever
161
      SU.error("Greek numbering is only supported up to 24")
×
162
   end
163
   return out
×
164
end
165

166
return language
42✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc