fontist 3.0.24 → 3.0.26
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/lib/fontist/font_path.rb +1 -1
- data/lib/fontist/formula.rb +5 -5
- data/lib/fontist/indexes/font_index.rb +8 -0
- data/lib/fontist/indexes/index_mixin.rb +48 -2
- data/lib/fontist/indexes/lookup_index.rb +34 -0
- data/lib/fontist/system_index.rb +71 -6
- data/lib/fontist/version.rb +1 -1
- data/lib/fontist.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 215b46de2d4feadfbd1d3644dd060cc5afdd54afd30ba1f6a50e428c5c2be4ef
|
|
4
|
+
data.tar.gz: 7eea12f0743488fbfbd4c5d924a2bfce06de918aaa9cf4bbc24b92fbe02d937f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 93ddd5d5863d137e0ed95f9e8829b2bb84797b7887f8d9a122f8259f920bde832f3d810b1057539504b1f7b584ba7e4d245726943cdcdc1a874c9656c52b125c
|
|
7
|
+
data.tar.gz: 2af73abb7b5d07aa4a5699aa309fbeec7e8053b7944f902a26a7e70804e3ff71c511dda0e0bb68e4e1e7e5f2e841e265eb08c06095de5f8390dd82e312190b79
|
data/lib/fontist/font_path.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Fontist
|
|
|
14
14
|
|
|
15
15
|
def formulas
|
|
16
16
|
@formulas ||= if fontist_font?
|
|
17
|
-
Indexes::FilenameIndex.
|
|
17
|
+
Indexes::FilenameIndex.lookup_index.load_index_formulas(File.basename(@path)).flat_map(&:name)
|
|
18
18
|
else
|
|
19
19
|
[]
|
|
20
20
|
end
|
data/lib/fontist/formula.rb
CHANGED
|
@@ -122,15 +122,15 @@ module Fontist
|
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
def find(font_name)
|
|
125
|
-
Indexes::FontIndex.
|
|
125
|
+
Indexes::FontIndex.lookup_index.load_formulas(font_name).first
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
def find_many(font_name)
|
|
129
|
-
Indexes::FontIndex.
|
|
129
|
+
Indexes::FontIndex.lookup_index.load_formulas(font_name)
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
def find_fonts(font_name)
|
|
133
|
-
formulas = Indexes::FontIndex.
|
|
133
|
+
formulas = Indexes::FontIndex.lookup_index.load_formulas(font_name)
|
|
134
134
|
|
|
135
135
|
formulas.map do |formula|
|
|
136
136
|
formula.all_fonts.select do |f|
|
|
@@ -140,7 +140,7 @@ module Fontist
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def find_styles(font_name, style_name)
|
|
143
|
-
formulas = Indexes::FontIndex.
|
|
143
|
+
formulas = Indexes::FontIndex.lookup_index.load_formulas(font_name)
|
|
144
144
|
|
|
145
145
|
formulas.map do |formula|
|
|
146
146
|
formula.all_fonts.map do |f|
|
|
@@ -173,7 +173,7 @@ module Fontist
|
|
|
173
173
|
end
|
|
174
174
|
|
|
175
175
|
def find_by_font_file(font_file)
|
|
176
|
-
key = Indexes::FilenameIndex.
|
|
176
|
+
key = Indexes::FilenameIndex.lookup_index
|
|
177
177
|
.load_index_formulas(File.basename(font_file))
|
|
178
178
|
.flat_map(&:name)
|
|
179
179
|
.first
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
1
3
|
module Fontist
|
|
2
4
|
module Indexes
|
|
5
|
+
autoload :LookupIndex, "#{__dir__}/lookup_index"
|
|
6
|
+
|
|
3
7
|
# IndexMixin provides common functionality for font index classes.
|
|
4
8
|
#
|
|
5
9
|
# == Performance Optimization (Tech Debt)
|
|
@@ -72,9 +76,38 @@ module Fontist
|
|
|
72
76
|
# to force a fresh parse on next access
|
|
73
77
|
Fontist::FromFileCache.clear
|
|
74
78
|
|
|
75
|
-
# Delete the index file
|
|
76
|
-
# This is important for tests to
|
|
79
|
+
# Delete the index file and its lookup sidecar to force a
|
|
80
|
+
# rebuild on next access. This is important for tests to
|
|
81
|
+
# ensure clean state
|
|
77
82
|
FileUtils.rm_f(path)
|
|
83
|
+
FileUtils.rm_f("#{path}.json")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Read-only lookups over the sidecar JSON beside the index
|
|
87
|
+
# file, which skips binding every entry into model objects. The
|
|
88
|
+
# sidecar is (re)written whenever the index file is written,
|
|
89
|
+
# and regenerated here when it is missing or stale.
|
|
90
|
+
def lookup_index
|
|
91
|
+
entries_hash = sidecar_entries || rebuild_sidecar_entries
|
|
92
|
+
normalizer = new.method(:normalize_key)
|
|
93
|
+
LookupIndex.new(entries_hash, normalizer: normalizer)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def sidecar_entries
|
|
97
|
+
sidecar_path = "#{path}.json"
|
|
98
|
+
return unless File.exist?(sidecar_path)
|
|
99
|
+
return if File.exist?(path) &&
|
|
100
|
+
File.mtime(sidecar_path) < File.mtime(path)
|
|
101
|
+
|
|
102
|
+
JSON.parse(File.read(sidecar_path))["entries"]
|
|
103
|
+
rescue JSON::ParserError, TypeError
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def rebuild_sidecar_entries
|
|
108
|
+
entries_hash = from_file(path).entries_hash
|
|
109
|
+
File.write("#{path}.json", JSON.dump("entries" => entries_hash))
|
|
110
|
+
entries_hash
|
|
78
111
|
end
|
|
79
112
|
end
|
|
80
113
|
|
|
@@ -158,6 +191,19 @@ module Fontist
|
|
|
158
191
|
|
|
159
192
|
FileUtils.mkdir_p(File.dirname(file_path))
|
|
160
193
|
File.write(file_path, to_yaml)
|
|
194
|
+
write_lookup_sidecar(file_path)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def write_lookup_sidecar(file_path)
|
|
198
|
+
File.write("#{file_path}.json",
|
|
199
|
+
JSON.dump("entries" => entries_hash))
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# key -> formula paths, in the sidecar's JSON-friendly shape
|
|
203
|
+
def entries_hash
|
|
204
|
+
Array(entries).to_h do |entry|
|
|
205
|
+
[normalize_key(entry.key), entry.formula_path]
|
|
206
|
+
end
|
|
161
207
|
end
|
|
162
208
|
|
|
163
209
|
private
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Fontist
|
|
2
|
+
module Indexes
|
|
3
|
+
# Read-only lookups over the JSON sidecar written beside an index
|
|
4
|
+
# file. Binding every index entry into model objects dominates the
|
|
5
|
+
# cost of loading an index; the sidecar holds the key-to-path map
|
|
6
|
+
# as plain data, and FormulaKeyToPath entries are materialized only
|
|
7
|
+
# for the requested key.
|
|
8
|
+
class LookupIndex
|
|
9
|
+
def initialize(entries_hash, normalizer:)
|
|
10
|
+
@entries_hash = entries_hash
|
|
11
|
+
@normalizer = normalizer
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def load_index_formulas(key)
|
|
15
|
+
[
|
|
16
|
+
FormulaKeyToPath.new(
|
|
17
|
+
key: key,
|
|
18
|
+
formula_path: paths_for(@normalizer.call(key)),
|
|
19
|
+
),
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def load_formulas(key)
|
|
24
|
+
load_index_formulas(key).flat_map(&:to_full)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def paths_for(normalized_key)
|
|
30
|
+
@entries_hash[normalized_key] || []
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/fontist/system_index.rb
CHANGED
|
@@ -140,6 +140,33 @@ module Fontist
|
|
|
140
140
|
end
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
+
# Scan metadata for a system index collection, persisted in a
|
|
144
|
+
# companion file beside the index (an index file is a flat entry
|
|
145
|
+
# list and cannot carry scalars). Restoring it across processes is
|
|
146
|
+
# what makes the 30-minute scan threshold and the directory-mtime
|
|
147
|
+
# change check work at all.
|
|
148
|
+
class SystemIndexMetadata < Lutaml::Model::Serializable
|
|
149
|
+
attribute :last_scan_time, :integer, default: -> { 0 }
|
|
150
|
+
attribute :directory_mtimes, :string, collection: true, default: -> { [] }
|
|
151
|
+
|
|
152
|
+
key_value do
|
|
153
|
+
map "last_scan_time", to: :last_scan_time
|
|
154
|
+
map "directory_mtimes", to: :directory_mtimes
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def self.load(path)
|
|
158
|
+
return new unless File.exist?(path)
|
|
159
|
+
|
|
160
|
+
from_yaml(File.read(path))
|
|
161
|
+
rescue Lutaml::Model::Error, Psych::SyntaxError, TypeError
|
|
162
|
+
new
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def save(path)
|
|
166
|
+
File.write(path, to_yaml)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
143
170
|
class SystemIndexFontCollection < Lutaml::Model::Collection
|
|
144
171
|
include Utils::Locking
|
|
145
172
|
|
|
@@ -151,14 +178,40 @@ module Fontist
|
|
|
151
178
|
attribute :directory_mtimes, :string, collection: true, default: -> { [] }
|
|
152
179
|
|
|
153
180
|
key_value do
|
|
181
|
+
# A bare entry list: the scan metadata lives in a companion
|
|
182
|
+
# metadata file (see SystemIndexMetadata), because a flat list
|
|
183
|
+
# cannot carry scalars and lutaml ignores scalar maps declared
|
|
184
|
+
# alongside map_instances
|
|
154
185
|
map_instances to: :fonts
|
|
155
|
-
map "last_scan_time", to: :last_scan_time
|
|
156
|
-
map "directory_mtimes", to: :directory_mtimes
|
|
157
186
|
end
|
|
158
187
|
|
|
159
188
|
# Don't rebuild index more frequently than this (in seconds)
|
|
160
189
|
INDEX_REBUILD_THRESHOLD = 30 * 60 # 30 minutes
|
|
161
190
|
|
|
191
|
+
def metadata_path
|
|
192
|
+
"#{@path}.metadata.yml"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def persist_metadata
|
|
196
|
+
return unless @path
|
|
197
|
+
|
|
198
|
+
SystemIndexMetadata
|
|
199
|
+
.new(last_scan_time: last_scan_time,
|
|
200
|
+
directory_mtimes: directory_mtimes)
|
|
201
|
+
.save(metadata_path)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def restore_metadata
|
|
205
|
+
return unless @path
|
|
206
|
+
return unless last_scan_time.to_i.zero?
|
|
207
|
+
|
|
208
|
+
meta = SystemIndexMetadata.load(metadata_path)
|
|
209
|
+
return unless meta.last_scan_time.to_i.positive?
|
|
210
|
+
|
|
211
|
+
self.last_scan_time = meta.last_scan_time
|
|
212
|
+
self.directory_mtimes = meta.directory_mtimes
|
|
213
|
+
end
|
|
214
|
+
|
|
162
215
|
def set_path(path)
|
|
163
216
|
@path = path
|
|
164
217
|
end
|
|
@@ -172,12 +225,17 @@ module Fontist
|
|
|
172
225
|
return new.set_content(path, paths_loader) unless File.exist?(path)
|
|
173
226
|
|
|
174
227
|
from_yaml(File.read(path)).set_content(path, paths_loader)
|
|
228
|
+
rescue Lutaml::Model::Error, Psych::SyntaxError, TypeError
|
|
229
|
+
# Corrupt or unreadable index files degrade to an empty
|
|
230
|
+
# collection; the next rebuild rewrites them
|
|
231
|
+
new.set_content(path, paths_loader)
|
|
175
232
|
end
|
|
176
233
|
|
|
177
234
|
def set_content(path, paths_loader)
|
|
178
235
|
tap do |content|
|
|
179
236
|
content.set_path(path)
|
|
180
237
|
content.set_path_loader(paths_loader)
|
|
238
|
+
content.restore_metadata
|
|
181
239
|
end
|
|
182
240
|
end
|
|
183
241
|
|
|
@@ -351,7 +409,8 @@ module Fontist
|
|
|
351
409
|
|
|
352
410
|
def build(forced: false, verbose: false, stats: nil)
|
|
353
411
|
if forced
|
|
354
|
-
return rebuild_with_lock(verbose: verbose, stats: stats
|
|
412
|
+
return rebuild_with_lock(verbose: verbose, stats: stats,
|
|
413
|
+
honor_recent: false)
|
|
355
414
|
end
|
|
356
415
|
|
|
357
416
|
previous_index = load_index
|
|
@@ -372,14 +431,18 @@ module Fontist
|
|
|
372
431
|
|
|
373
432
|
private
|
|
374
433
|
|
|
375
|
-
def rebuild_with_lock(verbose: false, stats: nil)
|
|
434
|
+
def rebuild_with_lock(verbose: false, stats: nil, honor_recent: true)
|
|
376
435
|
# Use file locking to prevent concurrent rebuilds across processes
|
|
377
436
|
lock_path = index_lock_path
|
|
378
437
|
start_time = Time.now
|
|
379
438
|
|
|
380
439
|
lock(lock_path) do
|
|
381
|
-
# Re-check if another process already rebuilt while we waited
|
|
382
|
-
|
|
440
|
+
# Re-check if another process already rebuilt while we waited
|
|
441
|
+
# for lock. Forced rebuilds (add_font, explicit rebuild) carry
|
|
442
|
+
# write intent and must always rescan: honoring the recent
|
|
443
|
+
# rebuild here served the pre-write index and silently dropped
|
|
444
|
+
# the font that triggered the rebuild
|
|
445
|
+
if honor_recent && File.exist?(@path)
|
|
383
446
|
existing = self.class.from_file(path: @path,
|
|
384
447
|
paths_loader: @paths_loader)
|
|
385
448
|
|
|
@@ -407,6 +470,7 @@ module Fontist
|
|
|
407
470
|
self.last_scan_time = Time.now.to_i
|
|
408
471
|
self.directory_mtimes = scan_directory_mtimes.map { |k, v| "#{k}:#{v}" }
|
|
409
472
|
to_file(@path)
|
|
473
|
+
persist_metadata
|
|
410
474
|
|
|
411
475
|
# Show completion message for large collections
|
|
412
476
|
elapsed = Time.now - start_time
|
|
@@ -505,6 +569,7 @@ module Fontist
|
|
|
505
569
|
|
|
506
570
|
# Save to file
|
|
507
571
|
to_file(@path)
|
|
572
|
+
persist_metadata
|
|
508
573
|
end
|
|
509
574
|
|
|
510
575
|
def detect_paths(paths, verbose: false, stats: nil)
|
data/lib/fontist/version.rb
CHANGED
data/lib/fontist.rb
CHANGED
|
@@ -25,6 +25,7 @@ module Fontist
|
|
|
25
25
|
autoload :FontPath, "#{__dir__}/fontist/font_path"
|
|
26
26
|
autoload :FontStyle, "#{__dir__}/fontist/font_style"
|
|
27
27
|
autoload :Fontconfig, "#{__dir__}/fontist/fontconfig"
|
|
28
|
+
autoload :FormatSpec, "#{__dir__}/fontist/format_spec"
|
|
28
29
|
autoload :Formula, "#{__dir__}/fontist/formula"
|
|
29
30
|
autoload :FormulaCollection, "#{__dir__}/fontist/formula"
|
|
30
31
|
autoload :FormulaPicker, "#{__dir__}/fontist/formula_picker"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.26
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -452,6 +452,7 @@ files:
|
|
|
452
452
|
- lib/fontist/indexes/incremental_index_updater.rb
|
|
453
453
|
- lib/fontist/indexes/incremental_scanner.rb
|
|
454
454
|
- lib/fontist/indexes/index_mixin.rb
|
|
455
|
+
- lib/fontist/indexes/lookup_index.rb
|
|
455
456
|
- lib/fontist/indexes/preferred_family_font_index.rb
|
|
456
457
|
- lib/fontist/indexes/system_index.rb
|
|
457
458
|
- lib/fontist/indexes/user_index.rb
|