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

sile-typesetter / sile / 6934957716

20 Nov 2023 07:35PM UTC coverage: 57.468% (-3.2%) from 60.703%
6934957716

push

github

web-flow
Merge c91d9a7d4 into 34e2e5335

60 of 79 new or added lines in 1 file covered. (75.95%)

717 existing lines in 27 files now uncovered.

8957 of 15586 relevant lines covered (57.47%)

5715.38 hits per line

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

24.22
/packages/counters/init.lua
1
local base = require("packages.base")
13✔
2

3
local package = pl.class(base)
13✔
4
package._name = "counters"
13✔
5

6
SILE.formatCounter = function ()
13✔
7
  SU.deprecated("SILE.formatCounter", "class:formatCounter", "0.13.0", "0.15.0")
×
8
end
9

10
SILE.formatMultilevelCounter = function ()
13✔
11
  SU.deprecated("SILE.formatMultilevelCounter", "class:formatMultilevelCounter", "0.13.0", "0.15.0")
×
12
end
13

14
local function getCounter (_, id)
15
  local counter = SILE.scratch.counters[id]
×
16
  if not counter then
×
17
    counter = {
×
18
      value = 0,
19
      display = "arabic",
20
      format = package.formatCounter
×
21
    }
22
    SILE.scratch.counters[id] = counter
×
23
  elseif type(counter.value) ~= "number" then
×
24
    SU.error("Counter " .. id .. " is not a single-level counter")
×
25
  end
26
  return counter
×
27
end
28

29
local function getMultilevelCounter (_, id)
UNCOV
30
  local counter = SILE.scratch.counters[id]
×
UNCOV
31
  if not counter then
×
UNCOV
32
    counter = {
×
33
      value = { 0 },
34
      display = { "arabic" },
UNCOV
35
      format = package.formatMultilevelCounter
×
36
    }
UNCOV
37
    SILE.scratch.counters[id] = counter
×
UNCOV
38
  elseif type(counter.value) ~= "table" then
×
39
    SU.error("Counter " .. id .. " is not a multi-level counter")
×
40
  end
UNCOV
41
  return counter
×
42
end
43

44
function package.formatCounter (_, counter)
13✔
45
  return SU.formatNumber(counter.value, { system = counter.display })
30✔
46
end
47

48
function package:formatMultilevelCounter (counter, options)
13✔
UNCOV
49
  options = options or {}
×
UNCOV
50
  local maxlevel = options.level and SU.min(SU.cast("integer", options.level), #counter.value) or #counter.value
×
51
  -- Option minlevel is undocumented and should perhaps be deprecated: is there a real use case for it?
UNCOV
52
  local minlevel = options.minlevel and SU.min(SU.cast("integer", options.minlevel, #counter.value)) or 1
×
UNCOV
53
  local out = {}
×
UNCOV
54
  if SU.boolean(options.noleadingzeros, false) then
×
55
    while counter.value[minlevel] == 0 do minlevel = minlevel + 1 end -- skip leading zeros
×
56
  end
UNCOV
57
  for x = minlevel, maxlevel do
×
UNCOV
58
    out[x - minlevel + 1] = self:formatCounter({ display = counter.display[x], value = counter.value[x] })
×
59
  end
UNCOV
60
  return table.concat(out, ".")
×
61
end
62

63
function package:_init ()
13✔
64
  base._init(self)
17✔
65
  if not SILE.scratch.counters then
17✔
66
    SILE.scratch.counters = {}
13✔
67
  end
68
  self:export("getCounter", getCounter)
17✔
69
  self:export("getMultilevelCounter", getMultilevelCounter)
17✔
70
  self:deprecatedExport("formatCounter", self.formatCounter)
17✔
71
  self:deprecatedExport("formatMultilevelCounter", self.formatMultilevelCounter)
17✔
72
end
73

74
function package:registerCommands ()
13✔
75

76
  self:registerCommand("increment-counter", function (options, _)
26✔
77
    local id = SU.required(options, "id", "increment-counter")
×
78

79
    local counter = self.class:getCounter(id)
×
80
    if (options["set-to"]) then
×
81
      SU.deprecated("\\increment-counter[set-to=...]", '\\set-counter[value=...]', "0.14.4", "0.16.0")
×
82
      -- An increment command that does a set is plain weird...
83
      counter.value = SU.cast("integer", options["set-to"])
×
84
    else
85
      counter.value = counter.value + 1
×
86
    end
87
    if options.display then counter.display = options.display end
×
88
  end, "Increments the counter named by the <id> option")
13✔
89

90
  self:registerCommand("set-counter", function (options, _)
26✔
91
    local id = SU.required(options, "id", "set-counter")
×
92

93
    local counter = self.class:getCounter(id)
×
94
    if options.value then counter.value = SU.cast("integer", options.value) end
×
95
    if options.display then counter.display = options.display end
×
96
  end, "Sets the counter named by the <id> option to <value>; sets its display type (roman/Roman/arabic) to type <display>.")
13✔
97

98

99
  self:registerCommand("show-counter", function (options, _)
26✔
100
    local id = SU.required(options, "id", "show-counter")
×
101

102
    local counter = self.class:getCounter(id)
×
103
    if options.display then
×
104
      SU.deprecated("\\show-counter[display=...]", '\\set-counter[display=...]', "0.14.4", "0.16.0")
×
105
      counter.display = options.display
×
106
    end
107
    SILE.typesetter:typeset(self:formatCounter(counter))
×
108
  end, "Outputs the value of counter <id>, optionally displaying it with the <display> format.")
13✔
109

110
  self:registerCommand("increment-multilevel-counter", function (options, _)
26✔
UNCOV
111
    local id = SU.required(options, "id", "increment-multilevel-counter")
×
112

UNCOV
113
    local counter = self.class:getMultilevelCounter(id)
×
UNCOV
114
    local currentLevel = #counter.value
×
UNCOV
115
    local level = SU.cast("integer", options.level or currentLevel)
×
UNCOV
116
    local reset = SU.boolean(options.reset, true)
×
117
    -- Option reset=false is undocumented and was previously somewhat broken.
118
    -- It should perhaps be deprecated: is there a real use case for it?
UNCOV
119
    if level == currentLevel then
×
UNCOV
120
      counter.value[level] = counter.value[level] + 1
×
UNCOV
121
    elseif level > currentLevel then
×
UNCOV
122
      while level - 1 > currentLevel do
×
123
        currentLevel = currentLevel + 1
×
124
        counter.value[currentLevel] = 0
×
125
        counter.display[currentLevel] = counter.display[currentLevel - 1]
×
126
      end
UNCOV
127
      currentLevel = currentLevel + 1
×
UNCOV
128
      counter.value[level] = 1
×
UNCOV
129
      counter.display[level] = counter.display[currentLevel - 1]
×
130
    else -- level < currentLevel
131
      counter.value[level] = counter.value[level] + 1
×
132
      while currentLevel > level do
×
133
        if reset then
×
134
          counter.value[currentLevel] = nil
×
135
          counter.display[currentLevel] = nil
×
136
        end
137
        currentLevel = currentLevel - 1
×
138
      end
139
    end
UNCOV
140
    if options.display then counter.display[currentLevel] = options.display end
×
141
  end, "Increments the value of the multilevel counter <id> at the given <level> or the current level.")
13✔
142

143
  self:registerCommand("set-multilevel-counter", function (options, _)
26✔
144
    local level = SU.cast("integer", SU.required(options, "level", "set-multilevel-counter"))
×
145
    local id = SU.required(options, "id", "set-multilevel-counter")
×
146

147
    local counter = self.class:getMultilevelCounter(id)
×
148
    local currentLevel = #counter.value
×
149
    if options.value then
×
150
      local value = SU.cast("integer", options.value)
×
151
      if level == currentLevel then
×
152
        -- e.g. set to x the level 3 of 1.2.3 => 1.2.x
153
        counter.value[level] = value
×
154
      elseif level > currentLevel then
×
155
        -- Fill all missing levels in-between, assuming same display format.
156
        -- e.g. set to x the level 3 of 1 => 1.0.x
157
        while level - 1 > currentLevel do
×
158
          currentLevel = currentLevel + 1
×
159
          counter.value[currentLevel] = 0
×
160
          counter.display[currentLevel] = counter.display[currentLevel - 1]
×
161
        end
162
        currentLevel = currentLevel + 1
×
163
        counter.value[level] = value
×
164
        counter.display[level] = counter.display[currentLevel - 1]
×
165
      else -- level < currentLevel
166
        -- Reset all upper levels
167
        -- e.g. set to x the level 2 of 1.2.3 => 1.x
168
        counter.value[level] = value
×
169
        while currentLevel > level do
×
170
          counter.value[currentLevel] = nil
×
171
          counter.display[currentLevel] = nil
×
172
          currentLevel = currentLevel - 1
×
173
        end
174
      end
175
    end
176
    if options.display then
×
177
      if level <= #counter.value then
×
178
         counter.display[level] = options.display
×
179
      else
180
        SU.warn("Ignoring attempt to set the display of a multilevel counter beyond its level")
×
181
      end
182
     end
183
  end, "Sets the multilevel counter named by the <id> option to <value> at level <level>; optionally sets its display type at that level to <display>.")
13✔
184

185
  self:registerCommand("show-multilevel-counter", function (options, _)
26✔
UNCOV
186
    local id = SU.required(options, "id", "show-multilevel-counter")
×
187

UNCOV
188
    local counter = self.class:getMultilevelCounter(id)
×
UNCOV
189
    if options.display then
×
190
      SU.deprecated("\\show-multilevel-counter[display=...]", '\\set-multilevel-counter[display=...]', "0.14.4", "0.16.0")
×
191
      counter.display[#counter.value] = options.display
×
192
    end
193

UNCOV
194
    SILE.typesetter:typeset(self:formatMultilevelCounter(counter, options))
×
195
  end, "Outputs the value of the multilevel counter <id>.")
13✔
196

197
end
198

199
package.documentation = [[
200
\begin{document}
201

202
Various parts of SILE such as the \autodoc:package{footnotes} package and the sectioning commands keep a counter of things going on: the current footnote number, the chapter number, and so on.
203
The counters package allows you to set up, increment, and typeset named counters.
204
It provides the following commands:
205

206
\begin{itemize}
207
\item{\autodoc:command{\set-counter[id=<counter-name>, value=<value>]}: Sets the counter with the specified name to the given value. The command takes an optional \autodoc:parameter{display=<display-type>} parameter to set the display type of the counter (see below).}
208
\item{\autodoc:command{\increment-counter[id=<counter-name>]}: Increments the counter by one. The command creates the counter if it does not exist and also accepts setting the display type.}
209
\item{\autodoc:command{\show-counter[id=<counter-name>]}: Typesets the value of the counter according to the counter’s declared display type.}
210
\end{itemize}
211

212

213
The available built-in display types are:
214

215
\begin{itemize}
216
\item{\code{arabic}, the default}
217
\item{\code{alpha}, for lower-case alphabetic counting}
218
\item{\code{Alpha}, for upper-case alphabetic counting}
219
\item{\code{roman}, for lower-case Roman numerals}
220
\item{\code{ROMAN}, for upper-case Roman numerals}
221
\item{\code{greek}, for Greek letters in alphabetical order (not Greek numerals)}
222
\end{itemize}
223

224
The ICU library also provides ways of formatting numbers in global (non-Latin) scripts.
225
You can use any of the display types in this list: \url{http://www.unicode.org/repos/cldr/tags/latest/common/bcp47/number.xml}.
226
For example, \autodoc:parameter{display=beng} will format your numbers in Bengali digits.
227

228
So, for example, the following SILE code:
229

230
\begin[type=autodoc:codeblock]{raw}
231
\set-counter[id=mycounter, value=2]
232
\show-counter[id=mycounter]
233

234
\increment-counter[id=mycounter, display=roman]
235
\show-counter[id=mycounter]
236
\end{raw}
237

238
produces:
239

240
\fullrule
241
\autodoc:example{
242
\noindent{}2
243

244
\noindent{}iii}
245
\par
246
\fullrule
247

248
The package also provides multi-level (hierarchical) counters, of the kind used in sectioning
249
commands:
250

251
\begin{itemize}
252
\item{\autodoc:command{\set-multilevel-counter[id=<counter-name>, level=<level>, value=<value>]}:
253
Sets the multi-level counter with the specified name to the given value at the given level.
254
The command also takes an optional \autodoc:parameter{display=<display-type>}, also acting at the given level.}
255
\item{\autodoc:command{\increment-multilevel-counter[id=<counter-name>]}:
256
Increments the counter by one at its current (deepest) level.
257
The command creates the counter if it does not exist.
258
If given the \autodoc:parameter{level=<level>} parameter, the command increments that level,
259
clearing any lower level (and filling previous levels with zeros, if they weren’t properly set).
260
It also accepts setting the display type at the target level.}
261
\item{\autodoc:command{\show-multilevel-counter[id=<counter-name>]}:
262
Typesets the value of the multi-level counter according to the counter’s declared display types
263
at each level. By default, all levels are output; option \autodoc:parameter{level=<level>} may be
264
used to display the counter up to a given level. Option \autodoc:parameter{noleadingzeros=true}
265
skips any leading zero (which may happen if a counter is at some level, without previous levels
266
having been set).}
267
\end{itemize}
268
\end{document}
269
]]
13✔
270

271
return package
13✔
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

© 2026 Coveralls, Inc