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

sile-typesetter / sile / 13989845427

21 Mar 2025 10:15AM UTC coverage: 32.874% (+2.1%) from 30.742%
13989845427

push

github

alerque
chore(tooling): Hard code spelling fix to keep changelog chugging

6628 of 20162 relevant lines covered (32.87%)

1147.94 hits per line

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

84.25
/core/settings.lua
1
--- core settings instance
2
--- @module SILE.settings
3

4
local deprecator = function ()
5
   SU.deprecated("SILE.settings.*", "SILE.settings:*", "0.13.0", "0.15.0")
×
6
end
7

8
--- @type settings
9
local settings = pl.class()
83✔
10

11
function settings:_init ()
83✔
12
   self.state = {}
83✔
13
   self.declarations = {}
83✔
14
   self.stateQueue = {}
83✔
15
   self.defaults = {}
83✔
16
   self.hooks = {}
83✔
17

18
   self:declare({
166✔
19
      parameter = "document.parindent",
20
      type = "glue",
21
      default = SILE.types.node.glue("1bs"),
249✔
22
      help = "Glue at start of paragraph",
23
   })
24

25
   self:declare({
166✔
26
      parameter = "document.baselineskip",
27
      type = "vglue",
28
      default = SILE.types.node.vglue("1.2em plus 1pt"),
166✔
29
      help = "Leading",
30
   })
31

32
   self:declare({
166✔
33
      parameter = "document.lineskip",
34
      type = "vglue",
35
      default = SILE.types.node.vglue("1pt"),
166✔
36
      help = "Leading",
37
   })
38

39
   self:declare({
166✔
40
      parameter = "document.parskip",
41
      type = "vglue",
42
      default = SILE.types.node.vglue("0pt plus 1pt"),
166✔
43
      help = "Leading",
44
   })
45

46
   self:declare({
83✔
47
      parameter = "document.spaceskip",
48
      type = "length or nil",
49
      default = nil,
50
      help = "The length of a space (if nil, then measured from the font)",
51
   })
52

53
   self:declare({
83✔
54
      parameter = "document.rskip",
55
      type = "glue or nil",
56
      default = nil,
57
      help = "Skip to be added to right side of line",
58
   })
59

60
   self:declare({
83✔
61
      parameter = "document.lskip",
62
      type = "glue or nil",
63
      default = nil,
64
      help = "Skip to be added to left side of line",
65
   })
66

67
   self:declare({
83✔
68
      parameter = "document.zenkakuchar",
69
      default = "あ",
70
      type = "string",
71
      help = "The character measured to determine the length of a zenkaku width (全角幅)",
72
   })
73

74
   SILE.registerCommand(
166✔
75
      "set",
83✔
76
      function (options, content)
77
         local makedefault = SU.boolean(options.makedefault, false)
34✔
78
         local reset = SU.boolean(options.reset, false)
34✔
79
         local value = options.value
34✔
80
         if content and (type(content) == "function" or content[1]) then
34✔
81
            if makedefault then
8✔
82
               SU.warn(
×
83
                  "Are you sure meant to set default settings *and* pass content to ostensibly apply them to temporarily?"
84
               )
85
            end
86
            self:temporarily(function ()
16✔
87
               if options.parameter then
8✔
88
                  local parameter = SU.required(options, "parameter", "\\set command")
8✔
89
                  self:set(parameter, value, makedefault, reset)
8✔
90
               end
91
               SILE.process(content)
8✔
92
            end)
93
         else
94
            local parameter = SU.required(options, "parameter", "\\set command")
26✔
95
            self:set(parameter, value, makedefault, reset)
26✔
96
         end
97
      end,
98
      "Set a SILE parameter <parameter> to value <value> (restoring the value afterwards if <content> is provided)",
83✔
99
      nil,
83✔
100
      true
101
   )
83✔
102
end
103

104
--- Stash the current values of all settings in a stack to be returned to later
105
function settings:pushState ()
83✔
106
   if not self then
584✔
107
      return deprecator()
×
108
   end
109
   table.insert(self.stateQueue, self.state)
584✔
110
   self.state = pl.tablex.copy(self.state)
1,168✔
111
end
112

113
--- Return the most recently pushed set of values in the setting stack
114
function settings:popState ()
83✔
115
   if not self then
585✔
116
      return deprecator()
×
117
   end
118
   local previous = self.state
585✔
119
   self.state = table.remove(self.stateQueue)
1,170✔
120
   for parameter, oldvalue in pairs(previous) do
34,148✔
121
      if self.hooks[parameter] then
33,563✔
122
         local newvalue = self.state[parameter]
33,563✔
123
         if oldvalue ~= newvalue then
33,563✔
124
            self:runHooks(parameter, newvalue)
960✔
125
         end
126
      end
127
   end
128
end
129

130
--- Declare a new setting
131
--- @tparam table specs { parameter, type, default, help, hook, ... } declaration specification
132
function settings:declare (spec)
83✔
133
   if not spec then
4,809✔
134
      return deprecator()
×
135
   end
136
   if spec.name then
4,809✔
137
      SU.deprecated(
×
138
         "'name' argument of SILE.settings:declare",
139
         "'parameter' argument of SILE.settings:declare",
140
         "0.10.10",
141
         "0.11.0"
142
      )
143
   end
144
   if self.declarations[spec.parameter] then
4,809✔
145
      SU.debug("settings", "Attempt to re-declare setting:", spec.parameter)
600✔
146
      return
600✔
147
   end
148
   self.declarations[spec.parameter] = spec
4,209✔
149
   self.hooks[spec.parameter] = {}
4,209✔
150
   if spec.hook then
4,209✔
151
      self:registerHook(spec.parameter, spec.hook)
83✔
152
   end
153
   self:set(spec.parameter, spec.default, true)
4,209✔
154
end
155

156
--- Reset all settings to their registered default values.
157
function settings:reset ()
83✔
158
   if not self then
×
159
      return deprecator()
×
160
   end
161
   for k, _ in pairs(self.state) do
×
162
      self:set(k, self.defaults[k])
×
163
   end
164
end
165

166
--- Restore all settings to the value they had in the top-level state,
167
-- that is at the tap of the settings stack (normally the document level).
168
function settings:toplevelState ()
83✔
169
   if not self then
36✔
170
      return deprecator()
×
171
   end
172
   if #self.stateQueue ~= 0 then
36✔
173
      for parameter, _ in pairs(self.state) do
2,022✔
174
         -- Bypass self:set() as the latter performs some tests and a cast,
175
         -- but the setting might not have been defined in the top level state
176
         -- (in which case, assume the default value).
177
         self.state[parameter] = self.stateQueue[1][parameter] or self.defaults[parameter]
1,986✔
178
      end
179
   end
180
end
181

182
--- Get the value of a setting
183
-- @tparam string parameter The full name of the setting to fetch.
184
-- @return Value of setting
185
function settings:get (parameter)
83✔
186
   -- HACK FIXME https://github.com/sile-typesetter/sile/issues/1699
187
   -- See comment on set() below.
188
   if parameter == "current.parindent" then
97,747✔
189
      return SILE.typesetter and SILE.typesetter.state.parindent
464✔
190
   end
191
   if not parameter then
97,283✔
192
      return deprecator()
×
193
   end
194
   if not self.declarations[parameter] then
97,283✔
195
      SU.error("Undefined setting '" .. parameter .. "'")
×
196
   end
197
   if type(self.state[parameter]) ~= "nil" then
97,283✔
198
      return self.state[parameter]
78,504✔
199
   else
200
      return self.defaults[parameter]
18,779✔
201
   end
202
end
203

204
--- Set the value of a setting
205
-- @tparam string parameter The full name of the setting to change.
206
-- @param value The new value to change it to.
207
-- @tparam[opt=false] boolean makedefault Whether to make this the new default value.
208
-- @tparam[opt=false] boolean reset Whether to reset the value to the current default value.
209
function settings:set (parameter, value, makedefault, reset)
83✔
210
   -- HACK FIXME https://github.com/sile-typesetter/sile/issues/1699
211
   -- Anything dubbed current.xxx should likely NOT be a "setting" (subject
212
   -- to being pushed/popped via temporary stacking) and actually has its
213
   -- own lifecycle (e.g. reset for the next paragraph).
214
   -- These should be rather typesetter states, or something to that extent
215
   -- yet to clarify. Notably, current.parindent falls in that category,
216
   -- BUT probably current.hangAfter and current.hangIndent too.
217
   -- To avoid breaking too much code yet without being sure of the solution,
218
   -- we implement a hack of sorts for current.parindent only.
219
   -- Note moreover that current.parindent is currently probably a bad concept
220
   -- anyway:
221
   --   - It can be nil (= document.parindent applies)
222
   --   - It can be a zero-glue (\noindent, ragged environments, etc.)
223
   --   - It can be a valued glue set to document.parindent
224
   --     (e.g. from \indent, and document.parindent thus applies)
225
   --   - It could be another valued glue (uh, use case to ascertain)
226
   -- What we would _likely_ only need to track is whether document.parindent
227
   -- applies or not on the paragraph just composed afterwards...
228
   if parameter == "current.parindent" then
10,036✔
229
      if SILE.typesetter and not SILE.typesetter.state.hmodeOnly then
698✔
230
         SILE.typesetter.state.parindent = SU.cast("glue or nil", value)
1,312✔
231
      end
232
      return
698✔
233
   end
234
   if type(self) ~= "table" then
9,338✔
235
      return deprecator()
×
236
   end
237
   if not self.declarations[parameter] then
9,338✔
238
      SU.error("Undefined setting '" .. parameter .. "'")
×
239
   end
240
   if reset then
9,338✔
241
      if makedefault then
×
242
         SU.error("Can't set a new default and revert to and old default setting at the same time")
×
243
      end
244
      value = self.defaults[parameter]
×
245
   else
246
      value = SU.cast(self.declarations[parameter].type, value)
18,676✔
247
   end
248
   self.state[parameter] = value
9,338✔
249
   if makedefault then
9,338✔
250
      self.defaults[parameter] = value
4,243✔
251
   end
252
   self:runHooks(parameter, value)
9,338✔
253
end
254

255
--- Register a callback hook to be run when a setting changes.
256
-- @tparam string parameter Name of the setting to add a hook to.
257
-- @tparam function func Callback function accepting one argument (the new value).
258
function settings:registerHook (parameter, func)
83✔
259
   table.insert(self.hooks[parameter], func)
83✔
260
end
261

262
--- Trigger execution of callback hooks for a given setting.
263
-- @tparam string parameter The name of the parameter changes.
264
-- @param value The new value of the setting, passed as the first argument to the hook function.
265
function settings:runHooks (parameter, value)
83✔
266
   if self.hooks[parameter] then
10,298✔
267
      for _, func in ipairs(self.hooks[parameter]) do
10,772✔
268
         SU.debug("classhooks", "Running setting hook for", parameter)
474✔
269
         func(value)
474✔
270
      end
271
   end
272
end
273

274
--- Isolate a block of processing so that setting changes made during the block don't last past the block.
275
-- (Under the hood this just uses `:pushState()`, the processes the function, then runs `:popState()`)
276
-- @tparam function func A function wrapping the actions to take without affecting settings for future use.
277
function settings:temporarily (func)
83✔
278
   if not func then
524✔
279
      return deprecator()
×
280
   end
281
   self:pushState()
524✔
282
   func()
524✔
283
   self:popState()
524✔
284
end
285

286
--- Create a settings wrapper function that applies current settings to later content processing.
287
--- @treturn function a closure function accepting one argument (content) to process using
288
--- typesetter settings as they are at the time of closure creation.
289
function settings:wrap ()
83✔
290
   if not self then
2✔
291
      return deprecator()
×
292
   end
293
   local clSettings = pl.tablex.copy(self.state)
2✔
294
   return function (content)
295
      table.insert(self.stateQueue, self.state)
1✔
296
      self.state = clSettings
1✔
297
      SILE.process(content)
1✔
298
      self:popState()
1✔
299
   end
300
end
301

302
return settings
83✔
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