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

sile-typesetter / sile / 14958605596

11 May 2025 06:34PM UTC coverage: 31.311% (-25.4%) from 56.689%
14958605596

push

github

web-flow
Merge 3e53926d5 into 443551a3e

6301 of 20124 relevant lines covered (31.31%)

4203.99 hits per line

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

24.81
/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 ()
7✔
7
   SU.deprecated("SILE.formatCounter", "class:formatCounter", "0.13.0", "0.15.0")
×
8
end
9

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

44
function package:formatCounter (counter)
7✔
45
   return SU.formatNumber(counter.value, { system = counter.display })
24✔
46
end
47

48
function package:formatMultilevelCounter (counter, options)
7✔
49
   options = options or {}
×
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?
52
   local minlevel = options.minlevel and SU.min(SU.cast("integer", options.minlevel), #counter.value) or 1
×
53
   local out = {}
×
54
   if SU.boolean(options.noleadingzeros, false) then
×
55
      while counter.value[minlevel] == 0 do
×
56
         minlevel = minlevel + 1
×
57
      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)
11✔
67
   if not SILE.scratch.counters then
11✔
68
      SILE.scratch.counters = {}
7✔
69
   end
70
   self:export("getCounter", getCounter)
11✔
71
   self:export("getMultilevelCounter", getMultilevelCounter)
11✔
72
   self:deprecatedExport("formatCounter", self.formatCounter)
11✔
73
   self:deprecatedExport("formatMultilevelCounter", self.formatMultilevelCounter)
11✔
74
end
75

76
function package:registerCommands ()
7✔
77
   self:registerCommand("increment-counter", function (options, _)
14✔
78
      local id = SU.required(options, "id", "increment-counter")
×
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
×
88
         counter.display = options.display
×
89
      end
90
   end, "Increments the counter named by the <id> option")
7✔
91

92
   self:registerCommand(
14✔
93
      "set-counter",
7✔
94
      function (options, _)
95
         local id = SU.required(options, "id", "set-counter")
×
96
         local counter = self.class:getCounter(id)
×
97
         if options.value then
×
98
            counter.value = SU.cast("integer", options.value)
×
99
         end
100
         if options.display then
×
101
            counter.display = options.display
×
102
         end
103
      end,
104
      "Sets the counter named by the <id> option to <value>; sets its display type (roman/Roman/arabic) to type <display>."
105
   )
7✔
106

107
   self:registerCommand("show-counter", function (options, _)
14✔
108
      local id = SU.required(options, "id", "show-counter")
×
109
      local counter = self.class:getCounter(id)
×
110
      if options.display then
×
111
         SU.deprecated("\\show-counter[display=...]", "\\set-counter[display=...]", "0.14.4", "0.16.0")
×
112
         counter.display = options.display
×
113
      end
114
      SILE.typesetter:typeset(self:formatCounter(counter))
×
115
   end, "Outputs the value of counter <id>, optionally displaying it with the <display> format.")
7✔
116

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

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

196
   self:registerCommand("show-multilevel-counter", function (options, _)
14✔
197
      local id = SU.required(options, "id", "show-multilevel-counter")
×
198
      local counter = self.class:getMultilevelCounter(id)
×
199
      if options.display then
×
200
         SU.deprecated(
×
201
            "\\show-multilevel-counter[display=...]",
202
            "\\set-multilevel-counter[display=...]",
203
            "0.14.4",
204
            "0.16.0"
205
         )
206
         counter.display[#counter.value] = options.display
×
207
      end
208
      SILE.typesetter:typeset(self:formatMultilevelCounter(counter, options))
×
209
   end, "Outputs the value of the multilevel counter <id>.")
7✔
210
end
211

212
package.documentation = [[
213
\begin{document}
214

215
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.
216
The counters package allows you to set up, increment, and typeset named counters.
217
It provides the following commands:
218

219
\begin{itemize}
220
\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).}
221
\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.}
222
\item{\autodoc:command{\show-counter[id=<counter-name>]}: Typesets the value of the counter according to the counter’s declared display type.}
223
\end{itemize}
224

225

226
The available built-in display types are:
227

228
\begin{itemize}
229
\item{\code{arabic}, the default}
230
\item{\code{alpha}, for lower-case alphabetic counting}
231
\item{\code{Alpha}, for upper-case alphabetic counting}
232
\item{\code{roman}, for lower-case Roman numerals}
233
\item{\code{ROMAN}, for upper-case Roman numerals}
234
\item{\code{greek}, for Greek letters in alphabetical order (not Greek numerals)}
235
\end{itemize}
236

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

241
So, for example, the following SILE code:
242

243
\begin[type=autodoc:codeblock]{raw}
244
\set-counter[id=mycounter, value=2]
245
\show-counter[id=mycounter]
246

247
\increment-counter[id=mycounter, display=roman]
248
\show-counter[id=mycounter]
249
\end{raw}
250

251
produces:
252

253
\fullrule
254
\autodoc:example{
255
\noindent{}2
256

257
\noindent{}iii}
258
\par
259
\fullrule
260

261
The package also provides multi-level (hierarchical) counters, of the kind used in sectioning
262
commands:
263

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

284
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