fontist 3.0.14 → 3.0.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06a9b4c9a5923cb458a0a5312fd4877c4615e6663dbd12dc292bed425c084ac4
4
- data.tar.gz: 5032ff75c1ac724280b7529b1c24b7975c092624d6aadd74358255242cb950c9
3
+ metadata.gz: 3871228b469c6c8ee43f6bd04d6b9726feddac6fb2245f3f2bf5533b05a2baf9
4
+ data.tar.gz: 9e6d136e1ceaa16dd7a3907fe346bbc0a90f46a51458334a5192b5a2dea085e9
5
5
  SHA512:
6
- metadata.gz: '093f4d9ea5fcaefd488ebe05b2fa90f9179fe997cf78d54ecf06b36f8cf4d05487dcd557ef6162a61844919b2f80b6bbf7ecf190afcfc7b68426b81cb0989742'
7
- data.tar.gz: aa94716daea9eec2786bde7875eb036ed43afe20abc8e78c8aecd03ef65189cc8c79c2a1fb5b1f4e0e30ec628a7ea0f5c66448d5a09aa3018636be8b89f581c0
6
+ metadata.gz: a201906009471c3844a708096fea687db8aa5a8eb56efc3e13076580dfefd1fea95e12edbbe2707928f5b3ed77db326a5147bf6e043965033fc5b27586166dc2
7
+ data.tar.gz: b4ff402ff006a766f1daee41ffbed825f826b24c88997e9ea92a91c435fcf88545ffdf921f7091371a247b489a2dc4d0ee92faad6b11a1dfae5f2967ddcd038d
@@ -1,4 +1,5 @@
1
1
  require "lutaml/model"
2
+ require "parallel"
2
3
  require_relative "format_matcher"
3
4
  require "git"
4
5
 
@@ -100,9 +101,14 @@ module Fontist
100
101
  end
101
102
 
102
103
  def all
103
- formulas = Dir[Fontist.formulas_path.join("**/*.yml").to_s].filter_map do |path|
104
+ paths = Dir[Fontist.formulas_path.join("**/*.yml").to_s]
105
+
106
+ # Parsing thousands of formula files dominates index rebuilds;
107
+ # worker threads overlap the libyaml C parse, which releases
108
+ # the GVL
109
+ formulas = Parallel.map(paths) do |path|
104
110
  Formula.from_file(path)
105
- end
111
+ end.compact
106
112
 
107
113
  FormulaCollection.new(formulas)
108
114
  end
@@ -181,9 +187,15 @@ module Fontist
181
187
 
182
188
  content = File.read(path)
183
189
 
184
- from_yaml(content).tap do |formula|
185
- formula.path = path
186
- formula.name = titleize(formula.key_from_path) if formula.name.nil?
190
+ # Parsing a formula costs ~10-20ms and the same formula is
191
+ # re-read on every lookup, so the parsed instance is memoized
192
+ # (mtime-validated) and shared between callers; treat as
193
+ # read-only
194
+ formula = Fontist::FromFileCache.memoize(path) { from_yaml(content) }
195
+
196
+ formula.tap do |f|
197
+ f.path = path
198
+ f.name = titleize(f.key_from_path) if f.name.nil?
187
199
  end
188
200
  rescue Lutaml::Model::Error, TypeError, ArgumentError,
189
201
  Psych::BadAlias, Psych::SyntaxError => e
@@ -0,0 +1,52 @@
1
+ module Fontist
2
+ # Thread-safe, mtime-validated memoization for parsed YAML artifacts
3
+ # (formula index, formula files), which cost hundreds of milliseconds to
4
+ # parse and are re-read on every font lookup.
5
+ #
6
+ # Returned instances are shared between all callers and must be treated
7
+ # as read-only. Entries re-resolve automatically when the file's mtime
8
+ # changes (e.g. after `fontist update` rewrites the formulas repo).
9
+ module FromFileCache
10
+ def self.memoize(path)
11
+ mtime = File.mtime(path)
12
+
13
+ hit = current_value(path, mtime)
14
+ return hit if hit
15
+
16
+ # Parse without holding the lock so parallel readers (e.g.
17
+ # Formula.all over worker threads) overlap; concurrent misses on
18
+ # the same path may duplicate work but write identical values
19
+ value = yield
20
+
21
+ store(path, mtime, value)
22
+
23
+ value
24
+ end
25
+
26
+ def self.clear
27
+ mutex.synchronize { cache.clear }
28
+ end
29
+
30
+ def self.current_value(path, mtime)
31
+ hit = mutex.synchronize { cache[path] }
32
+ hit&.fetch(:mtime, nil) == mtime ? hit[:value] : nil
33
+ end
34
+
35
+ def self.store(path, mtime, value)
36
+ mutex.synchronize do
37
+ existing = cache[path]
38
+ if existing.nil? || existing[:mtime] != mtime
39
+ cache[path] = { mtime: mtime, value: value }
40
+ end
41
+ end
42
+ end
43
+
44
+ def self.mutex
45
+ @mutex ||= Mutex.new
46
+ end
47
+
48
+ def self.cache
49
+ @cache ||= {}
50
+ end
51
+ end
52
+ end
@@ -132,6 +132,10 @@ module Fontist
132
132
  # @param verbose [Boolean] Show detailed progress information
133
133
  # @return [void]
134
134
  def rebuild(verbose: false)
135
+ # A rebuild is an explicit request for a fresh scan, so any
136
+ # memoized font_paths must be dropped first
137
+ reset_scan_cache
138
+
135
139
  collection.rebuild(verbose: verbose)
136
140
  end
137
141
 
@@ -37,10 +37,17 @@ module Fontist
37
37
  # Uses case-insensitive glob patterns that work on all platforms,
38
38
  # including Linux where File::FNM_CASEFOLD is ignored
39
39
  #
40
+ # Delegates to the memoized fontist-repo scan on SystemFont; the
41
+ # memo is invalidated through reset_scan_cache when fonts are
42
+ # installed or removed mid-process
43
+ #
40
44
  # @return [Array<String>] Array of font file paths
41
45
  def font_paths
42
- patterns = Fontist::Utils.font_file_patterns(Fontist.fonts_path.join("**").to_s)
43
- patterns.flat_map { |pattern| Dir.glob(pattern) }
46
+ SystemFont.fontist_font_paths
47
+ end
48
+
49
+ def reset_scan_cache
50
+ SystemFont.reset_fontist_font_paths_cache
44
51
  end
45
52
  end
46
53
  end
@@ -49,12 +49,13 @@ module Fontist
49
49
 
50
50
  file_content = File.read(file_path).strip
51
51
 
52
- if file_content.empty? || file_content == "---"
53
- # Return empty collection for empty index files
54
- return new
55
- end
52
+ # Return empty collection for empty index files
53
+ return new if file_content.empty? || file_content == "---"
56
54
 
57
- from_yaml(file_content)
55
+ # Parsing the index YAML costs hundreds of milliseconds and
56
+ # happens on every font lookup, so the parsed instance is
57
+ # memoized (mtime-validated) and shared between callers
58
+ Fontist::FromFileCache.memoize(file_path) { from_yaml(file_content) }
58
59
  end
59
60
 
60
61
  def rebuild
@@ -67,6 +68,10 @@ module Fontist
67
68
  end
68
69
 
69
70
  def reset_cache
71
+ # Drop the memoized parsed instance along with the index file,
72
+ # to force a fresh parse on next access
73
+ Fontist::FromFileCache.clear
74
+
70
75
  # Delete the index file to force rebuild on next access
71
76
  # This is important for tests to ensure clean state
72
77
  FileUtils.rm_f(path)
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "3.0.14".freeze
2
+ VERSION = "3.0.16".freeze
3
3
  end
data/lib/fontist.rb CHANGED
@@ -7,6 +7,7 @@ require "lutaml/model"
7
7
  require_relative "fontist/errors"
8
8
  require_relative "fontist/version"
9
9
  require_relative "fontist/cache"
10
+ require_relative "fontist/from_file_cache"
10
11
  require_relative "fontist/memoizable"
11
12
  require_relative "fontist/path_scanning"
12
13
  require_relative "fontist/utils"
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.14
4
+ version: 3.0.16
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-10-31 00:00:00.000000000 Z
11
+ date: 2026-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -375,6 +375,7 @@ files:
375
375
  - lib/fontist/formula.rb
376
376
  - lib/fontist/formula_picker.rb
377
377
  - lib/fontist/formula_suggestion.rb
378
+ - lib/fontist/from_file_cache.rb
378
379
  - lib/fontist/google_import_source.rb
379
380
  - lib/fontist/helpers.rb
380
381
  - lib/fontist/import.rb