fontist 3.0.32 → 3.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/rake-metanorma.yaml +13 -3
- data/lib/fontist/import/helpers/hash_helper.rb +21 -1
- data/lib/fontist/repo.rb +5 -2
- data/lib/fontist/system_index.rb +66 -6
- data/lib/fontist/update.rb +39 -14
- data/lib/fontist/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1143907bf1a14a160d39b19244cdd714b337424825ad84dba942a8373ca7a60
|
|
4
|
+
data.tar.gz: 282f707e84083d28b1afdb8fb7c80576b35590e1050693fd1cd2d004eeeb1a0d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 870132c48632ccf2315fbdea22a61e1b6952f79cb43aaf769c7daeea7c82f5aab15fc194e26a0694b2f82636168b5710e2a1ed900c9b23759634b589ccefe410
|
|
7
|
+
data.tar.gz: db7f2844943dbf51fd87954ec6e8356a96528b47ea4d74c662af94b090e1c469f9cd1fee62c075252038d0fd5e799bc4aff84f39ac88e926a4a2ebfe1abb3d23
|
|
@@ -43,9 +43,19 @@ jobs:
|
|
|
43
43
|
with:
|
|
44
44
|
path: "fontist"
|
|
45
45
|
|
|
46
|
-
#
|
|
47
|
-
# the
|
|
48
|
-
|
|
46
|
+
# Point any existing fontist line at this checkout, appending only
|
|
47
|
+
# when the host Gemfile declares none (metanorma/issues/630 keeps
|
|
48
|
+
# the taste-stack gems it carries; since metanorma#652 its
|
|
49
|
+
# Gemfile.devel also pins fontist, which a blind append would
|
|
50
|
+
# duplicate into a version-conflict error).
|
|
51
|
+
- run: |
|
|
52
|
+
if grep -q '^gem "fontist"' Gemfile.devel Gemfile 2>/dev/null; then
|
|
53
|
+
sed -i.bak 's/^gem "fontist".*/gem "fontist", path: ".\/fontist"/' Gemfile.devel Gemfile 2>/dev/null || true
|
|
54
|
+
rm -f Gemfile.devel.bak Gemfile.bak
|
|
55
|
+
else
|
|
56
|
+
echo 'gem "fontist", path: "./fontist"' >> Gemfile.devel
|
|
57
|
+
fi
|
|
58
|
+
shell: bash
|
|
49
59
|
|
|
50
60
|
- uses: ruby/setup-ruby@v1
|
|
51
61
|
with:
|
|
@@ -3,8 +3,28 @@ module Fontist
|
|
|
3
3
|
module Helpers
|
|
4
4
|
module HashHelper
|
|
5
5
|
class << self
|
|
6
|
+
# Converts a formula hash to string-keyed plain data. Values
|
|
7
|
+
# that are lutaml-model models join the document through their
|
|
8
|
+
# own to_hash serializer (the json gem used to call to_json on
|
|
9
|
+
# them implicitly during traversal; Yeptris::JSON does not).
|
|
6
10
|
def stringify_keys(hash)
|
|
7
|
-
|
|
11
|
+
deep_stringify(hash)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def deep_stringify(value)
|
|
17
|
+
case value
|
|
18
|
+
when Hash then value.to_h { |k, v| [k.to_s, deep_stringify(v)] }
|
|
19
|
+
when Array then value.map { |v| deep_stringify(v) }
|
|
20
|
+
else deep_stringify_model(value)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def deep_stringify_model(value)
|
|
25
|
+
return value unless value.respond_to?(:to_hash)
|
|
26
|
+
|
|
27
|
+
deep_stringify(value.to_hash)
|
|
8
28
|
end
|
|
9
29
|
end
|
|
10
30
|
end
|
data/lib/fontist/repo.rb
CHANGED
|
@@ -115,16 +115,19 @@ module Fontist
|
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
def update(name)
|
|
118
|
+
def update(name, rebuild: true)
|
|
119
119
|
path = repo_path(name)
|
|
120
120
|
unless Dir.exist?(path)
|
|
121
121
|
raise(Errors::RepoNotFoundError, "No such repo '#{name}'.")
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
git = Git.open(path)
|
|
125
|
+
before = git.rev_parse("HEAD")
|
|
125
126
|
git.pull("origin", git.current_branch)
|
|
127
|
+
changed = git.rev_parse("HEAD") != before
|
|
126
128
|
|
|
127
|
-
rebuild_index_if_needed
|
|
129
|
+
rebuild_index_if_needed if rebuild && changed
|
|
130
|
+
changed
|
|
128
131
|
rescue StandardError => e
|
|
129
132
|
# Catch all git-related errors
|
|
130
133
|
if e.class.name.include?("Git") || e.message.match?(/git|pull|repository/i)
|
data/lib/fontist/system_index.rb
CHANGED
|
@@ -148,10 +148,12 @@ module Fontist
|
|
|
148
148
|
class SystemIndexMetadata < Lutaml::Model::Serializable
|
|
149
149
|
attribute :last_scan_time, :integer, default: -> { 0 }
|
|
150
150
|
attribute :directory_mtimes, :string, collection: true, default: -> { [] }
|
|
151
|
+
attribute :format_version, :integer, default: -> { 0 }
|
|
151
152
|
|
|
152
153
|
key_value do
|
|
153
154
|
map "last_scan_time", to: :last_scan_time
|
|
154
155
|
map "directory_mtimes", to: :directory_mtimes
|
|
156
|
+
map "format_version", to: :format_version
|
|
155
157
|
end
|
|
156
158
|
|
|
157
159
|
def self.load(path)
|
|
@@ -173,9 +175,12 @@ module Fontist
|
|
|
173
175
|
instances :fonts, SystemIndexFont
|
|
174
176
|
attr_accessor :path, :paths_loader
|
|
175
177
|
|
|
176
|
-
# Metadata for smart change detection
|
|
178
|
+
# Metadata for smart change detection. The format generation lives
|
|
179
|
+
# only in the companion metadata file (see restore_metadata); it is
|
|
180
|
+
# never serialized into the entry list itself.
|
|
177
181
|
attribute :last_scan_time, :integer, default: -> { 0 }
|
|
178
182
|
attribute :directory_mtimes, :string, collection: true, default: -> { [] }
|
|
183
|
+
attribute :format_version, :integer, default: -> { 0 }
|
|
179
184
|
|
|
180
185
|
key_value do
|
|
181
186
|
# A bare entry list: the scan metadata lives in a companion
|
|
@@ -188,6 +193,13 @@ module Fontist
|
|
|
188
193
|
# Don't rebuild index more frequently than this (in seconds)
|
|
189
194
|
INDEX_REBUILD_THRESHOLD = 30 * 60 # 30 minutes
|
|
190
195
|
|
|
196
|
+
# Index entry shape generation. v1 stored one entry per font file;
|
|
197
|
+
# v2 stores one entry per named instance of variable fonts (the
|
|
198
|
+
# FiraCode[wght].ttf class of files). Bump when the entry shape
|
|
199
|
+
# changes again: persisted indexes carrying an older generation
|
|
200
|
+
# cannot answer the current lookups and force a rebuild.
|
|
201
|
+
INDEX_FORMAT_VERSION = 2
|
|
202
|
+
|
|
191
203
|
def metadata_path
|
|
192
204
|
"#{@path}.metadata.yml"
|
|
193
205
|
end
|
|
@@ -197,19 +209,32 @@ module Fontist
|
|
|
197
209
|
|
|
198
210
|
SystemIndexMetadata
|
|
199
211
|
.new(last_scan_time: last_scan_time,
|
|
200
|
-
directory_mtimes: directory_mtimes
|
|
212
|
+
directory_mtimes: directory_mtimes,
|
|
213
|
+
format_version: INDEX_FORMAT_VERSION)
|
|
201
214
|
.save(metadata_path)
|
|
202
215
|
end
|
|
203
216
|
|
|
204
217
|
def restore_metadata
|
|
205
218
|
return unless @path
|
|
206
|
-
return unless last_scan_time.to_i.zero?
|
|
207
219
|
|
|
208
220
|
meta = SystemIndexMetadata.load(metadata_path)
|
|
209
|
-
return unless meta.last_scan_time.to_i.positive?
|
|
210
|
-
|
|
211
221
|
self.last_scan_time = meta.last_scan_time
|
|
212
222
|
self.directory_mtimes = meta.directory_mtimes
|
|
223
|
+
self.format_version = meta.format_version
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Whether the persisted index predates the current entry shape. A
|
|
227
|
+
# v1 index answers every lookup of a variable font's non-default
|
|
228
|
+
# families with "missing" (the file registered only under its
|
|
229
|
+
# default instance), and the directory-mtime fast paths would keep
|
|
230
|
+
# trusting it forever, so the generation mismatch itself is the
|
|
231
|
+
# staleness signal. A collection with no companion metadata file is
|
|
232
|
+
# fresh or in-process memory, not a persisted v1 volume: treating
|
|
233
|
+
# it as stale would discard every cached entry on the next rebuild.
|
|
234
|
+
def stale_format?
|
|
235
|
+
return false unless @path && File.exist?(metadata_path.to_s)
|
|
236
|
+
|
|
237
|
+
format_version.to_i != INDEX_FORMAT_VERSION
|
|
213
238
|
end
|
|
214
239
|
|
|
215
240
|
def set_path(path)
|
|
@@ -263,6 +288,7 @@ module Fontist
|
|
|
263
288
|
end
|
|
264
289
|
|
|
265
290
|
def find(font, style, format_spec: nil)
|
|
291
|
+
rebuild_if_stale_format!
|
|
266
292
|
current_fonts = index
|
|
267
293
|
|
|
268
294
|
return nil if current_fonts.nil? || current_fonts.empty?
|
|
@@ -285,6 +311,35 @@ module Fontist
|
|
|
285
311
|
found_fonts.empty? ? nil : found_fonts
|
|
286
312
|
end
|
|
287
313
|
|
|
314
|
+
# Rebuild once per process when the persisted index predates
|
|
315
|
+
# INDEX_FORMAT_VERSION. Runs even under read_only_mode: a stale
|
|
316
|
+
# generation answers every lookup of a variable font's non-default
|
|
317
|
+
# families with "missing", which is worse than a one-off rebuild.
|
|
318
|
+
# Only a VALID, non-empty persisted index heals: a fresh environment
|
|
319
|
+
# (no file yet) and a corrupt file (the corruption path explains the
|
|
320
|
+
# removal fix) take their normal routes.
|
|
321
|
+
def rebuild_if_stale_format!
|
|
322
|
+
return if @format_check_done
|
|
323
|
+
|
|
324
|
+
@format_check_done = true
|
|
325
|
+
return unless @path && File.exist?(@path.to_s)
|
|
326
|
+
return if fonts.nil? || fonts.empty?
|
|
327
|
+
|
|
328
|
+
# A corrupt index keeps its documented contract: the corruption
|
|
329
|
+
# error with the removal hint, not a silent heal.
|
|
330
|
+
check_index
|
|
331
|
+
|
|
332
|
+
restore_metadata
|
|
333
|
+
return unless stale_format?
|
|
334
|
+
|
|
335
|
+
Fontist.ui.warn(
|
|
336
|
+
"Font index format changed (v#{format_version.to_i} -> " \
|
|
337
|
+
"v#{INDEX_FORMAT_VERSION}); rebuilding once.",
|
|
338
|
+
)
|
|
339
|
+
build(forced: true)
|
|
340
|
+
mark_verified!
|
|
341
|
+
end
|
|
342
|
+
|
|
288
343
|
# Drops the cached family lookup; called when fonts are removed in
|
|
289
344
|
# place (a rebuild replaces the fonts array, which re-keys the
|
|
290
345
|
# lookup by itself)
|
|
@@ -573,7 +628,12 @@ module Fontist
|
|
|
573
628
|
end
|
|
574
629
|
|
|
575
630
|
def detect_paths(paths, verbose: false, stats: nil)
|
|
576
|
-
|
|
631
|
+
# A generation change invalidates every cached parse in one sweep:
|
|
632
|
+
# v1 entries (one per file) cannot be reused to build v2 lookups
|
|
633
|
+
# (one per named instance), and the per-file cache fast path would
|
|
634
|
+
# otherwise keep serving the stale entry for every unchanged file.
|
|
635
|
+
existing_fonts_by_path =
|
|
636
|
+
stale_format? ? {} : (fonts&.group_by(&:path) || {})
|
|
577
637
|
|
|
578
638
|
# Initialize stats if not provided
|
|
579
639
|
stats ||= IndexStats.new if verbose
|
data/lib/fontist/update.rb
CHANGED
|
@@ -9,23 +9,35 @@ module Fontist
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def call
|
|
12
|
-
update_main_repo
|
|
13
|
-
update_private_repos
|
|
12
|
+
main_changed = update_main_repo
|
|
13
|
+
private_changed = update_private_repos
|
|
14
14
|
ensure
|
|
15
|
-
|
|
15
|
+
# The index is a projection of the corpus: a pull that brought no
|
|
16
|
+
# changes leaves it valid, and a failed pull leaves the corpus
|
|
17
|
+
# untouched. Rebuilding costs a full pass over every formula, so
|
|
18
|
+
# it only runs when some repo actually moved — or when no index
|
|
19
|
+
# exists yet (a fresh clone never had one; lookups self-heal by
|
|
20
|
+
# rebuilding on demand, but an explicit update should leave one
|
|
21
|
+
# behind).
|
|
22
|
+
rebuild_index if main_changed || private_changed || index_missing?
|
|
16
23
|
end
|
|
17
24
|
|
|
18
25
|
private
|
|
19
26
|
|
|
27
|
+
def index_missing?
|
|
28
|
+
!File.exist?(Fontist.formula_filename_index_path)
|
|
29
|
+
end
|
|
30
|
+
|
|
20
31
|
def update_main_repo
|
|
21
32
|
dir = File.dirname(Fontist.formulas_repo_path)
|
|
22
33
|
FileUtils.mkdir_p(dir)
|
|
23
34
|
|
|
24
35
|
unless Dir.exist?(Fontist.formulas_repo_path)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
Git.clone(Fontist.formulas_repo_url,
|
|
37
|
+
Fontist.formulas_repo_path,
|
|
38
|
+
branch: @branch,
|
|
39
|
+
depth: 1)
|
|
40
|
+
return true
|
|
29
41
|
end
|
|
30
42
|
|
|
31
43
|
git = if Dir.exist?(Fontist.formulas_repo_path.join(".git"))
|
|
@@ -36,21 +48,34 @@ module Fontist
|
|
|
36
48
|
end
|
|
37
49
|
end
|
|
38
50
|
|
|
39
|
-
|
|
51
|
+
if git.current_branch != @branch
|
|
52
|
+
git.config_set("remote.origin.fetch",
|
|
53
|
+
"+refs/heads/#{@branch}:refs/remotes/origin/#{@branch}")
|
|
54
|
+
git.fetch
|
|
55
|
+
git.checkout(@branch)
|
|
56
|
+
git.pull("origin", @branch)
|
|
57
|
+
return true
|
|
58
|
+
end
|
|
40
59
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
git
|
|
44
|
-
git.checkout(@branch)
|
|
60
|
+
# A freshly init-ed repo has no commits yet; nil before the first
|
|
61
|
+
# pull means "changed", since the pull then creates the corpus
|
|
62
|
+
before = head_sha(git)
|
|
45
63
|
git.pull("origin", @branch)
|
|
64
|
+
head_sha(git) != before
|
|
46
65
|
end
|
|
47
66
|
|
|
48
67
|
def update_private_repos
|
|
49
|
-
Repo.list.
|
|
50
|
-
Repo.update(name)
|
|
68
|
+
Repo.list.reduce(false) do |changed, name|
|
|
69
|
+
Repo.update(name, rebuild: false) || changed
|
|
51
70
|
end
|
|
52
71
|
end
|
|
53
72
|
|
|
73
|
+
def head_sha(git)
|
|
74
|
+
git.rev_parse("HEAD")
|
|
75
|
+
rescue Git::FailedError, Git::GitError
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
54
79
|
def rebuild_index
|
|
55
80
|
Index.rebuild
|
|
56
81
|
end
|
data/lib/fontist/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fontist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.34
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-11-
|
|
11
|
+
date: 2026-11-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: down
|