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

sile-typesetter / sile / 6713098919

31 Oct 2023 10:21PM UTC coverage: 52.831% (-21.8%) from 74.636%
6713098919

push

github

web-flow
Merge d0a2a1ee9 into b185d4972

45 of 45 new or added lines in 3 files covered. (100.0%)

8173 of 15470 relevant lines covered (52.83%)

6562.28 hits per line

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

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

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

6
SILE.formatCounter = function (counter)
7✔
7
  SU.deprecated("SILE.formatCounter", "class:formatCounter", "0.13.0", "0.15.0")
×
8
  return package.formatCounter(nil, counter)
×
9
end
10

11
SILE.formatMultilevelCounter = function (counter, options)
7✔
12
  SU.deprecated("SILE.formatMultilevelCounter", "class:formatMultilevelCounter", "0.13.0", "0.15.0")
×
13
  return package.formatMultilevelCounter(nil, counter, options)
×
14
end
15

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

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

46
function package.formatCounter (_, counter)
7✔
47
  return SU.formatNumber(counter.value, { system = counter.display })
7✔
48
end
49

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

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

76
function package:registerCommands ()
7✔
77

78
  self:registerCommand("increment-counter", function (options, _)
14✔
79
    local id = SU.required(options, "id", "increment-counter")
×
80

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

92
  self:registerCommand("set-counter", function (options, _)
14✔
93
    local id = SU.required(options, "id", "set-counter")
×
94

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

100

101
  self:registerCommand("show-counter", function (options, _)
14✔
102
    local id = SU.required(options, "id", "show-counter")
×
103

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

112
  self:registerCommand("increment-multilevel-counter", function (options, _)
14✔
113
    local id = SU.required(options, "id", "increment-multilevel-counter")
×
114

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

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

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

187
  self:registerCommand("show-multilevel-counter", function (options, _)
14✔
188
    local id = SU.required(options, "id", "show-multilevel-counter")
×
189

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

196
    SILE.typesetter:typeset(self:formatMultilevelCounter(counter, options))
×
197
  end, "Outputs the value of the multilevel counter <id>.")
7✔
198

199
end
200

201
package.documentation = [[
202
\begin{document}
203

204
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.
205
The counters package allows you to set up, increment, and typeset named counters.
206
It provides the following commands:
207

208
\begin{itemize}
209
\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).}
210
\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.}
211
\item{\autodoc:command{\show-counter[id=<counter-name>]}: Typesets the value of the counter according to the counter’s declared display type.}
212
\end{itemize}
213

214

215
The available built-in display types are:
216

217
\begin{itemize}
218
\item{\code{arabic}, the default}
219
\item{\code{alpha}, for lower-case alphabetic counting}
220
\item{\code{Alpha}, for upper-case alphabetic counting}
221
\item{\code{roman}, for lower-case Roman numerals}
222
\item{\code{ROMAN} for upper-case Roman numerals}
223
\end{itemize}
224

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

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

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

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

239
produces:
240

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

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

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

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

272
return package
7✔
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