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

sile-typesetter / sile / 10621606353

29 Aug 2024 07:43PM UTC coverage: 66.23% (+3.6%) from 62.644%
10621606353

push

github

alerque
Merge tag 'v0.15.5' into develop

13 of 289 new or added lines in 17 files covered. (4.5%)

403 existing lines in 59 files now uncovered.

11585 of 17492 relevant lines covered (66.23%)

5713.06 hits per line

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

86.36
/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()
375✔
10

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

18
   self:declare({
750✔
19
      parameter = "document.language",
20
      type = "string",
21
      default = "en",
22
      hook = function (language)
23
         if SILE.scratch.loaded_languages and not SILE.scratch.loaded_languages[language] then
1,261✔
24
            SU.warn(([[Setting document.language to '%s', but support for '%s' has not been loaded!
42✔
25

26
  Consider invoking \language[main=%s] which loads language support before
27
  setting it or manually calling SILE.languageSupport.loadLanguage("%s").
28
            ]]):format(language, language, language, language))
21✔
29
         end
30
         fluent:set_locale(language)
1,261✔
31
      end,
32
      help = "Locale for localized language support",
33
   })
34

35
   self:declare({
750✔
36
      parameter = "document.parindent",
37
      type = "glue",
38
      default = SILE.types.node.glue("1bs"),
1,125✔
39
      help = "Glue at start of paragraph",
40
   })
41

42
   self:declare({
750✔
43
      parameter = "document.baselineskip",
44
      type = "vglue",
45
      default = SILE.types.node.vglue("1.2em plus 1pt"),
750✔
46
      help = "Leading",
47
   })
48

49
   self:declare({
750✔
50
      parameter = "document.lineskip",
51
      type = "vglue",
52
      default = SILE.types.node.vglue("1pt"),
750✔
53
      help = "Leading",
54
   })
55

56
   self:declare({
750✔
57
      parameter = "document.parskip",
58
      type = "vglue",
59
      default = SILE.types.node.vglue("0pt plus 1pt"),
750✔
60
      help = "Leading",
61
   })
62

63
   self:declare({
375✔
64
      parameter = "document.spaceskip",
65
      type = "length or nil",
66
      default = nil,
67
      help = "The length of a space (if nil, then measured from the font)",
68
   })
69

70
   self:declare({
375✔
71
      parameter = "document.rskip",
72
      type = "glue or nil",
73
      default = nil,
74
      help = "Skip to be added to right side of line",
75
   })
76

77
   self:declare({
375✔
78
      parameter = "document.lskip",
79
      type = "glue or nil",
80
      default = nil,
81
      help = "Skip to be added to left side of line",
82
   })
83

84
   self:declare({
375✔
85
      parameter = "document.zenkakuchar",
86
      default = "あ",
87
      type = "string",
88
      help = "The character measured to determine the length of a zenkaku width (全角幅)",
89
   })
90

91
   SILE.registerCommand(
750✔
92
      "set",
375✔
93
      function (options, content)
94
         local makedefault = SU.boolean(options.makedefault, false)
114✔
95
         local reset = SU.boolean(options.reset, false)
114✔
96
         local value = options.value
114✔
97
         if content and (type(content) == "function" or content[1]) then
114✔
98
            if makedefault then
10✔
99
               SU.warn(
×
100
                  "Are you sure meant to set default settings *and* pass content to ostensibly apply them to temporarily?"
101
               )
102
            end
103
            self:temporarily(function ()
20✔
104
               if options.parameter then
10✔
105
                  local parameter = SU.required(options, "parameter", "\\set command")
9✔
106
                  self:set(parameter, value, makedefault, reset)
9✔
107
               end
108
               SILE.process(content)
10✔
109
            end)
110
         else
111
            local parameter = SU.required(options, "parameter", "\\set command")
104✔
112
            self:set(parameter, value, makedefault, reset)
104✔
113
         end
114
      end,
115
      "Set a SILE parameter <parameter> to value <value> (restoring the value afterwards if <content> is provided)",
375✔
116
      nil,
375✔
117
      true
118
   )
375✔
119
end
120

121
--- Stash the current values of all settings in a stack to be returned to later
122
function settings:pushState ()
375✔
123
   if not self then
1,597✔
124
      return deprecator()
×
125
   end
126
   table.insert(self.stateQueue, self.state)
1,597✔
127
   self.state = pl.tablex.copy(self.state)
3,194✔
128
end
129

130
--- Return the most recently pushed set of values in the setting stack
131
function settings:popState ()
375✔
132
   if not self then
1,601✔
133
      return deprecator()
×
134
   end
135
   local previous = self.state
1,601✔
136
   self.state = table.remove(self.stateQueue)
3,202✔
137
   for parameter, oldvalue in pairs(previous) do
87,260✔
138
      if self.hooks[parameter] then
85,659✔
139
         local newvalue = self.state[parameter]
85,659✔
140
         if oldvalue ~= newvalue then
85,659✔
141
            self:runHooks(parameter, newvalue)
1,935✔
142
         end
143
      end
144
   end
145
end
146

147
--- Declare a new setting
148
--- @tparam table specs { parameter, type, default, help, hook, ... } declaration specification
149
function settings:declare (spec)
375✔
150
   if not spec then
19,799✔
151
      return deprecator()
×
152
   end
153
   if spec.name then
19,799✔
154
      SU.deprecated(
×
155
         "'name' argument of SILE.settings:declare",
156
         "'parameter' argument of SILE.settings:declare",
157
         "0.10.10",
158
         "0.11.0"
159
      )
160
   end
161
   if self.declarations[spec.parameter] then
19,799✔
162
      SU.debug("settings", "Attempt to re-declare setting:", spec.parameter)
1,729✔
163
      return
1,729✔
164
   end
165
   self.declarations[spec.parameter] = spec
18,070✔
166
   self.hooks[spec.parameter] = {}
18,070✔
167
   if spec.hook then
18,070✔
168
      self:registerHook(spec.parameter, spec.hook)
375✔
169
   end
170
   self:set(spec.parameter, spec.default, true)
18,070✔
171
end
172

173
--- Reset all settings to their registered default values.
174
function settings:reset ()
375✔
175
   if not self then
×
176
      return deprecator()
×
177
   end
178
   for k, _ in pairs(self.state) do
×
179
      self:set(k, self.defaults[k])
×
180
   end
181
end
182

183
--- Restore all settings to the value they had in the top-level state,
184
-- that is at the tap of the settings stack (normally the document level).
185
function settings:toplevelState ()
375✔
186
   if not self then
131✔
187
      return deprecator()
×
188
   end
189
   if #self.stateQueue ~= 0 then
131✔
190
      for parameter, _ in pairs(self.state) do
6,748✔
191
         -- Bypass self:set() as the latter performs some tests and a cast,
192
         -- but the setting might not have been defined in the top level state
193
         -- (in which case, assume the default value).
194
         self.state[parameter] = self.stateQueue[1][parameter] or self.defaults[parameter]
6,617✔
195
      end
196
   end
197
end
198

199
--- Get the value of a setting
200
-- @tparam string parameter The full name of the setting to fetch.
201
-- @return Value of setting
202
function settings:get (parameter)
375✔
203
   -- HACK FIXME https://github.com/sile-typesetter/sile/issues/1699
204
   -- See comment on set() below.
205
   if parameter == "current.parindent" then
254,828✔
206
      return SILE.typesetter and SILE.typesetter.state.parindent
1,672✔
207
   end
208
   if not parameter then
253,156✔
209
      return deprecator()
×
210
   end
211
   if not self.declarations[parameter] then
253,156✔
212
      SU.error("Undefined setting '" .. parameter .. "'")
×
213
   end
214
   if type(self.state[parameter]) ~= "nil" then
253,156✔
215
      return self.state[parameter]
165,144✔
216
   else
217
      return self.defaults[parameter]
88,012✔
218
   end
219
end
220

221
--- Set the value of a setting
222
-- @tparam string parameter The full name of the setting to change.
223
-- @param value The new value to change it to.
224
-- @tparam[opt=false] boolean makedefault Whether to make this the new default value.
225
-- @tparam[opt=false] boolean reset Whether to reset the value to the current default value.
226
function settings:set (parameter, value, makedefault, reset)
375✔
227
   -- HACK FIXME https://github.com/sile-typesetter/sile/issues/1699
228
   -- Anything dubbed current.xxx should likely NOT be a "setting" (subject
229
   -- to being pushed/popped via temporary stacking) and actually has its
230
   -- own lifecycle (e.g. reset for the next paragraph).
231
   -- These should be rather typesetter states, or something to that extent
232
   -- yet to clarify. Notably, current.parindent falls in that category,
233
   -- BUT probably current.hangAfter and current.hangIndent too.
234
   -- To avoid breaking too much code yet without being sure of the solution,
235
   -- we implement a hack of sorts for current.parindent only.
236
   -- Note moreover that current.parindent is currently probably a bad concept
237
   -- anyway:
238
   --   - It can be nil (= document.parindent applies)
239
   --   - It can be a zero-glue (\noindent, ragged environments, etc.)
240
   --   - It can be a valued glue set to document.parindent
241
   --     (e.g. from \indent, and document.parindent thus applies)
242
   --   - It could be another valued glue (uh, use case to ascertain)
243
   -- What we would _likely_ only need to track is whether document.parindent
244
   -- applies or not on the paragraph just composed afterwards...
245
   if parameter == "current.parindent" then
31,426✔
246
      if SILE.typesetter and not SILE.typesetter.state.hmodeOnly then
2,733✔
247
         SILE.typesetter.state.parindent = SU.cast("glue or nil", value)
5,058✔
248
      end
249
      return
2,733✔
250
   end
251
   if type(self) ~= "table" then
28,693✔
252
      return deprecator()
×
253
   end
254
   if not self.declarations[parameter] then
28,693✔
255
      SU.error("Undefined setting '" .. parameter .. "'")
×
256
   end
257
   if reset then
28,693✔
258
      if makedefault then
5✔
259
         SU.error("Can't set a new default and revert to and old default setting at the same time!")
×
260
      end
261
      value = self.defaults[parameter]
5✔
262
   else
263
      value = SU.cast(self.declarations[parameter].type, value)
57,376✔
264
   end
265
   self.state[parameter] = value
28,693✔
266
   if makedefault then
28,693✔
267
      self.defaults[parameter] = value
18,150✔
268
   end
269
   self:runHooks(parameter, value)
28,693✔
270
end
271

272
--- Register a callback hook to be run when a setting changes.
273
-- @tparam string parameter Name of the setting to add a hook to.
274
-- @tparam function func Callback function accepting one argument (the new value).
275
function settings:registerHook (parameter, func)
375✔
276
   table.insert(self.hooks[parameter], func)
375✔
277
end
278

279
--- Trigger execution of callback hooks for a given setting.
280
-- @tparam string parameter The name of the parameter changes.
281
-- @param value The new value of the setting, passed as the first argument to the hook function.
282
function settings:runHooks (parameter, value)
375✔
283
   if self.hooks[parameter] then
30,628✔
284
      for _, func in ipairs(self.hooks[parameter]) do
31,889✔
285
         SU.debug("classhooks", "Running setting hook for", parameter)
1,261✔
286
         func(value)
1,261✔
287
      end
288
   end
289
end
290

291
--- Isolate a block of processing so that setting changes made during the block don't last past the block.
292
-- (Under the hood this just uses `:pushState()`, the processes the function, then runs `:popState()`)
293
-- @tparam function func A function wrapping the actions to take without affecting settings for future use.
294
function settings:temporarily (func)
375✔
295
   if not func then
1,199✔
296
      return deprecator()
×
297
   end
298
   self:pushState()
1,199✔
299
   func()
1,199✔
300
   self:popState()
1,199✔
301
end
302

303
--- Create a settings wrapper function that applies current settings to later content processing.
304
--- @treturn function a closure function accepting one argument (content) to process using
305
--- typesetter settings as they are at the time of closure creation.
306
function settings:wrap ()
375✔
307
   if not self then
13✔
UNCOV
308
      return deprecator()
×
309
   end
310
   local clSettings = pl.tablex.copy(self.state)
13✔
311
   return function (content)
312
      table.insert(self.stateQueue, self.state)
4✔
313
      self.state = clSettings
4✔
314
      SILE.process(content)
4✔
315
      self:popState()
4✔
316
   end
317
end
318

319
return settings
375✔
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