fontist 3.0.29 → 3.0.30

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: e972a1c6d0f3be11f50cbb811780bdd52ed08be5ac1fb0e51c437872421fe539
4
- data.tar.gz: 04db98584e91f4edbba08f9934703f2e5f6dd9c361ae6dc3214cd5f0b9e4450f
3
+ metadata.gz: d858c57f16d09454000112d1047f120b1a752a76331e3788f65cb441fe7a668c
4
+ data.tar.gz: 60c5f0e33198e6b7b8ad24db123280a5fff7c4eaf8b56581d3ab8c9108114b44
5
5
  SHA512:
6
- metadata.gz: c3f3895e3e9293463b26957ac2be169cee7b6899c3c19e196ad1aaecf708322e419a1d560d6ec64c6dcac25adf4729cab339f0a17a709b25b80e020a3e2b8e88
7
- data.tar.gz: 7caab4cc37bc85e0a4b8ae7ff65e14f71083979acdf6078c59c0034b5e5fb51c22072a467a082cf5c6dbd2419556c6d346d080b26199b77bb50677f81c0b21f0
6
+ metadata.gz: 581ebfd99f20a966cea6574294b4a3de879cdcea6051f916645e84ac9bd5e9ee16d4d0bfcbbd82f0291b6d6ae3e72dcf1c5524d02f6e1d67d3fbcfd880b03d73
7
+ data.tar.gz: 7188a89be063579e76ffd351c5ddd0c7296836a4c6162c1974ca54f79ca5cddd6134a71b3d48d04455fbc1ac8debbe1e6f2922d414a16aea2e85c83a62742fc2
data/fontist.gemspec CHANGED
@@ -8,8 +8,10 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Ribose Inc."]
9
9
  spec.email = ["open.source@ribose.com"]
10
10
 
11
+ # rubocop:disable Layout/LineLength
11
12
  spec.summary = "Install openly-licensed fonts on Windows, Linux and Mac!"
12
13
  spec.description = "Install openly-licensed fonts on Windows, Linux and Mac!"
14
+ # rubocop:enable Layout/LineLength
13
15
  spec.homepage = "https://github.com/fontist/fontist"
14
16
  spec.license = "BSD-2-Clause"
15
17
 
@@ -34,11 +36,10 @@ Gem::Specification.new do |spec|
34
36
  spec.add_dependency "fontisan", "~> 0.4", ">= 0.4.47"
35
37
  spec.add_dependency "fuzzy_match", "~> 2.1"
36
38
  spec.add_dependency "git", "~> 6.0"
37
- spec.add_dependency "json", "~> 2.0"
38
39
  spec.add_dependency "lutaml-model", "~> 0.8.0"
39
40
  spec.add_dependency "marcel", "~> 1.0"
40
41
  spec.add_dependency "nokogiri", "~> 1.0"
41
- spec.add_dependency "octokit", "~> 4.0"
42
+ spec.add_dependency "octokit", "~> 10.0"
42
43
  spec.add_dependency "paint", "~> 2.3"
43
44
  spec.add_dependency "parallel", "~> 1.24"
44
45
  spec.add_dependency "plist", "~> 3.0"
@@ -40,7 +40,7 @@ module Fontist
40
40
  ensure_cache_dir
41
41
  entry = CacheEntry.new(value, ttl)
42
42
  write_entry(key, entry)
43
- rescue TypeError, JSON::GeneratorError
43
+ rescue TypeError, Fontist::JSON::DumpError
44
44
  # Values outside the JSON type system (Time, Date, arbitrary
45
45
  # objects) skip the write and read back as a miss
46
46
  false
@@ -113,9 +113,9 @@ module Fontist
113
113
  def read_entry(key)
114
114
  return nil unless File.exist?(cache_path(key))
115
115
 
116
- payload = JSON.parse(File.read(cache_path(key)))
116
+ payload = Fontist::JSON.parse(File.read(cache_path(key)))
117
117
  CacheEntry.new(payload["value"], expires_at: payload["expires_at"])
118
- rescue JSON::ParserError, TypeError, EOFError
118
+ rescue Fontist::JSON::ParseError, TypeError, EOFError
119
119
  # Corrupted file - delete it and return nil. This can happen
120
120
  # on Windows when a file is read while being written, or when
121
121
  # cache files from previous runs are corrupted
@@ -137,9 +137,16 @@ module Fontist
137
137
  end
138
138
 
139
139
  def entry_payload(entry)
140
- JSON.dump("version" => FORMAT_VERSION,
141
- "expires_at" => entry.expires_at,
142
- "value" => entry.value)
140
+ Fontist::JSON.dump("version" => FORMAT_VERSION,
141
+ "expires_at" => entry.expires_at,
142
+ "value" => entry.value)
143
+ rescue Fontist::JSON::DumpError
144
+ # Values outside the JSON type system (Time, Date, arbitrary
145
+ # objects) degrade to their string form, keeping the cache a
146
+ # best-effort store instead of a source of crashes
147
+ Fontist::JSON.dump("version" => FORMAT_VERSION,
148
+ "expires_at" => entry.expires_at,
149
+ "value" => entry.value.to_s)
143
150
  end
144
151
  end
145
152
  end
data/lib/fontist/cli.rb CHANGED
@@ -312,7 +312,7 @@ module Fontist
312
312
  end
313
313
 
314
314
  if options[:json]
315
- Fontist.ui.say(JSON.pretty_generate(results.map(&:to_h)))
315
+ Fontist.ui.say(Fontist::JSON.pretty(results.map(&:to_h)))
316
316
  else
317
317
  print_find_results(results)
318
318
  end
@@ -106,10 +106,16 @@ module Fontist
106
106
  # Parsing thousands of formula files dominates index rebuilds;
107
107
  # worker threads overlap the libyaml C parse, which releases
108
108
  # the GVL. The pool is capped like every other fontist pool, so
109
- # worker counts never scale with host core counts
109
+ # worker counts never scale with host core counts.
110
+ #
111
+ # The parses bypass the shared parse cache: a corpus sweep never
112
+ # hits it (the LRU holds 64 entries against thousands of files),
113
+ # so caching would only evict entries that lookups re-use and
114
+ # pin dozens of parsed formulas in long-lived host processes
115
+ # after the sweep ends
110
116
  workers = [Parallel.processor_count, 8].min
111
117
  formulas = Parallel.map(paths, in_threads: workers) do |path|
112
- Formula.from_file(path)
118
+ Formula.from_file(path, cache: false)
113
119
  end.compact
114
120
 
115
121
  FormulaCollection.new(formulas)
@@ -181,7 +187,7 @@ module Fontist
181
187
  find_by_key(key)
182
188
  end
183
189
 
184
- def from_file(path)
190
+ def from_file(path, cache: true)
185
191
  unless File.exist?(path)
186
192
  raise Fontist::Errors::FormulaNotFoundError,
187
193
  "Formula file not found: #{path}"
@@ -192,8 +198,13 @@ module Fontist
192
198
  # Parsing a formula costs ~10-20ms and the same formula is
193
199
  # re-read on every lookup, so the parsed instance is memoized
194
200
  # (mtime-validated) and shared between callers; treat as
195
- # read-only
196
- formula = Fontist::FromFileCache.memoize(path) { from_yaml(content) }
201
+ # read-only. Callers that sweep the whole corpus pass
202
+ # cache: false, since their parses would only thrash the cache
203
+ formula = if cache
204
+ Fontist::FromFileCache.memoize(path) { from_yaml(content) }
205
+ else
206
+ from_yaml(content)
207
+ end
197
208
 
198
209
  formula.tap do |f|
199
210
  f.path = path
@@ -41,6 +41,10 @@ module Fontist
41
41
  mutex.synchronize { cache.clear }
42
42
  end
43
43
 
44
+ def size
45
+ mutex.synchronize { cache.size }
46
+ end
47
+
44
48
  private
45
49
 
46
50
  attr_reader :mutex
@@ -3,7 +3,7 @@ module Fontist
3
3
  def self.url_object(request)
4
4
  return request unless request.include?("\"url\"")
5
5
 
6
- obj = JSON.parse(request.gsub("=>", ":"))
6
+ obj = Fontist::JSON.parse(request.gsub("=>", ":"))
7
7
  Struct.new(:url, :headers).new(obj["url"], obj["headers"])
8
8
  end
9
9
 
@@ -56,7 +56,7 @@ module Fontist
56
56
  end
57
57
 
58
58
  def stringify_keys(hash)
59
- JSON.parse(hash.to_json)
59
+ Fontist::JSON.parse(Fontist::JSON.dump(hash))
60
60
  end
61
61
  end
62
62
  end
@@ -58,8 +58,8 @@ module Fontist
58
58
  raise "API request failed: #{response.code} #{response.message}"
59
59
  end
60
60
 
61
- JSON.parse(response.body)
62
- rescue JSON::ParserError => e
61
+ Fontist::JSON.parse(response.body)
62
+ rescue Fontist::JSON::ParseError => e
63
63
  raise "Failed to parse API response: #{e.message}"
64
64
  rescue StandardError => e
65
65
  raise "Failed to fetch from API: #{e.message}"
@@ -90,7 +90,7 @@ module Fontist
90
90
  # @param item [Hash] the raw item data
91
91
  # @return [FontFamily] the parsed font family
92
92
  def parse_item(item)
93
- Models::FontFamily.from_json(item.to_json)
93
+ Models::FontFamily.from_json(Fontist::JSON.dump(item))
94
94
  end
95
95
  end
96
96
  end
@@ -59,15 +59,15 @@ module Fontist
59
59
 
60
60
  # Try to parse as JSON first
61
61
  begin
62
- JSON.parse(files_data)
63
- rescue JSON::ParserError, TypeError
62
+ Fontist::JSON.parse(files_data)
63
+ rescue Fontist::JSON::ParseError, TypeError
64
64
  # If it fails, convert Ruby hash syntax to JSON and parse
65
65
  if files_data.is_a?(String)
66
66
  # Convert Ruby hash syntax (=>) to JSON syntax (:)
67
67
  json_str = files_data.gsub("=>", ":")
68
68
  begin
69
- return JSON.parse(json_str)
70
- rescue JSON::ParserError
69
+ return Fontist::JSON.parse(json_str)
70
+ rescue Fontist::JSON::ParseError
71
71
  {}
72
72
  end
73
73
  end
@@ -4,7 +4,7 @@ module Fontist
4
4
  module HashHelper
5
5
  class << self
6
6
  def stringify_keys(hash)
7
- JSON.parse(hash.to_json)
7
+ Fontist::JSON.parse(Fontist::JSON.dump(hash))
8
8
  end
9
9
  end
10
10
  end
@@ -95,7 +95,7 @@ module Fontist
95
95
  def load_yaml_without_aliases(path)
96
96
  data = YAML.safe_load(File.read(path), aliases: true,
97
97
  permitted_classes: [Date])
98
- JSON.parse(JSON.generate(data))
98
+ Fontist::JSON.parse(Fontist::JSON.dump(data))
99
99
  end
100
100
 
101
101
  def file_has_yaml_aliases?(path)
@@ -205,7 +205,7 @@ module Fontist
205
205
  case options[:format].downcase
206
206
  when "json"
207
207
  require "json"
208
- puts JSON.pretty_generate(fonts_data)
208
+ puts Fontist::JSON.pretty(fonts_data)
209
209
  when "yaml"
210
210
  require "yaml"
211
211
  puts YAML.dump(fonts_data)
@@ -103,13 +103,13 @@ module Fontist
103
103
  return entries if entries
104
104
 
105
105
  parse_sidecar_entries(sidecar_path)
106
- rescue JSON::ParserError, TypeError
106
+ rescue Fontist::JSON::ParseError, TypeError
107
107
  nil
108
108
  end
109
109
 
110
110
  def rebuild_sidecar_entries
111
111
  entries_hash = from_file(path).entries_hash
112
- File.write("#{path}.json", JSON.dump("entries" => entries_hash))
112
+ File.write("#{path}.json", Fontist::JSON.dump("entries" => entries_hash))
113
113
  entries_hash
114
114
  end
115
115
 
@@ -135,7 +135,7 @@ module Fontist
135
135
  # as read-only
136
136
  def parse_sidecar_entries(sidecar_path)
137
137
  mtime = File.mtime(sidecar_path)
138
- entries = JSON.parse(File.read(sidecar_path))["entries"]
138
+ entries = Fontist::JSON.parse(File.read(sidecar_path))["entries"]
139
139
 
140
140
  sidecar_cache_mutex.synchronize do
141
141
  sidecar_cache[sidecar_path] = { mtime: mtime, entries: entries }
@@ -244,7 +244,7 @@ module Fontist
244
244
 
245
245
  def write_lookup_sidecar(file_path)
246
246
  File.write("#{file_path}.json",
247
- JSON.dump("entries" => entries_hash))
247
+ Fontist::JSON.dump("entries" => entries_hash))
248
248
  end
249
249
 
250
250
  # key -> formula paths, in the sidecar's JSON-friendly shape
@@ -0,0 +1,76 @@
1
+ require "yeptris"
2
+ require "yeptris/json"
3
+
4
+ module Fontist
5
+ # JSON encoding and decoding through yeptris's native engine, replacing
6
+ # the json gem as fontist's JSON backend. The yeptris parser and
7
+ # generator produce and accept the same plain Ruby structures as the
8
+ # json gem (string keys, arrays, scalars), so this module is a
9
+ # drop-in surface; call sites rescue the module's error classes
10
+ # instead of JSON::ParserError and JSON::GeneratorError.
11
+ module JSON
12
+ class Error < StandardError; end
13
+ class ParseError < Error; end
14
+ class DumpError < Error; end
15
+
16
+ class << self
17
+ def parse(source)
18
+ unless source.is_a?(String)
19
+ raise ParseError,
20
+ "cannot parse #{source.class}"
21
+ end
22
+
23
+ Yeptris::JSON.load(source)
24
+ rescue Yeptris::Error, TypeError => e
25
+ raise ParseError, e.message
26
+ end
27
+
28
+ def dump(object)
29
+ Yeptris::JSON.dump(object)
30
+ rescue Yeptris::Error, TypeError => e
31
+ raise DumpError, e.message
32
+ end
33
+
34
+ # Two-space-indented JSON for human-facing CLI output; yeptris's
35
+ # generator is compact-only, so the indentation is applied here
36
+ def pretty(object)
37
+ pretty_value(object, 0)
38
+ end
39
+
40
+ private
41
+
42
+ def pretty_value(value, depth)
43
+ case value
44
+ when Hash then pretty_object(value, depth)
45
+ when Array then pretty_array(value, depth)
46
+ else dump(value)
47
+ end
48
+ end
49
+
50
+ def pretty_object(hash, depth)
51
+ return "{}" if hash.empty?
52
+
53
+ inner = hash.map do |key, value|
54
+ "#{indent(depth + 1)}#{dump(key.to_s)}: #{pretty_value(value,
55
+ depth + 1)}"
56
+ end
57
+
58
+ "{\n#{inner.join(",\n")}\n#{indent(depth)}}"
59
+ end
60
+
61
+ def pretty_array(array, depth)
62
+ return "[]" if array.empty?
63
+
64
+ inner = array.map do |value|
65
+ "#{indent(depth + 1)}#{pretty_value(value, depth + 1)}"
66
+ end
67
+
68
+ "[\n#{inner.join(",\n")}\n#{indent(depth)}]"
69
+ end
70
+
71
+ def indent(depth)
72
+ " " * depth
73
+ end
74
+ end
75
+ end
76
+ end
@@ -6,7 +6,12 @@ module Fontist
6
6
  attribute :name, :string
7
7
  attribute :source, :string
8
8
  attribute :urls, :string, collection: true
9
- attribute :sha256, :string, collection: true
9
+ # Declared scalar: the corpus writes one digest per resource almost
10
+ # everywhere, and consumers render it verbatim. A handful of
11
+ # resources pair one digest with each mirror URL and write a list;
12
+ # a scalar-typed attribute keeps both forms as written instead of
13
+ # normalizing every single digest into a one-element Array.
14
+ attribute :sha256, :string
10
15
  attribute :file_size, :integer
11
16
  attribute :family, :string
12
17
  attribute :files, :string, collection: true
@@ -189,7 +189,7 @@ module Fontist
189
189
 
190
190
  def output_json_report(report, output_file)
191
191
  require "json"
192
- json_content = JSON.pretty_generate(report.to_hash)
192
+ json_content = Fontist::JSON.pretty(report.to_hash)
193
193
 
194
194
  if output_file
195
195
  File.write(output_file, json_content)
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "3.0.29".freeze
2
+ VERSION = "3.0.30".freeze
3
3
  end
@@ -14,13 +14,26 @@ module Fontist
14
14
  ENABLED_VALUE = "yeptris"
15
15
 
16
16
  class << self
17
- # Returns the activated adapter class, or nil when the standard
18
- # engine stays in place (flag unset, or yeptris unavailable)
17
+ # Pins the YAML adapter explicitly: lutaml-model auto-selects
18
+ # yeptris for a format whenever the yeptris gem is in the bundle,
19
+ # which would silently switch YAML parsing and dumping (and its
20
+ # line-wrap behavior) for every model in the process. The yeptris
21
+ # YAML engine activates only when explicitly enabled with
22
+ # FONTIST_YAML=yeptris; otherwise the standard Psych engine is
23
+ # pinned, because the yeptris x86_64-linux and Windows builds
24
+ # currently mis-parse some plain scalars as single-element Arrays
25
+ # (leptris/yeptris#863). Flip the default once that parity fix
26
+ # lands.
27
+ #
28
+ # Returns the activated adapter class.
19
29
  def activate!
20
- return nil unless enabled?
21
- return nil unless yeptris_available?
30
+ adapter =
31
+ if enabled? && yeptris_available?
32
+ Yaml::YeptrisAdapter
33
+ else
34
+ Lutaml::Yaml::Adapter::StandardAdapter
35
+ end
22
36
 
23
- adapter = Yaml::YeptrisAdapter
24
37
  Lutaml::Model::AdapterResolver.set_adapter_class(:yaml, adapter)
25
38
 
26
39
  adapter
data/lib/fontist.rb CHANGED
@@ -18,6 +18,7 @@ module Fontist
18
18
  # Core classes
19
19
  autoload :Config, "#{__dir__}/fontist/config"
20
20
  autoload :Css, "#{__dir__}/fontist/css"
21
+ autoload :JSON, "#{__dir__}/fontist/json"
21
22
  autoload :Font, "#{__dir__}/fontist/font"
22
23
  autoload :FontCollection, "#{__dir__}/fontist/font_collection"
23
24
  autoload :FontFile, "#{__dir__}/fontist/font_file"
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.29
4
+ version: 3.0.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -92,20 +92,6 @@ dependencies:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
94
  version: '6.0'
95
- - !ruby/object:Gem::Dependency
96
- name: json
97
- requirement: !ruby/object:Gem::Requirement
98
- requirements:
99
- - - "~>"
100
- - !ruby/object:Gem::Version
101
- version: '2.0'
102
- type: :runtime
103
- prerelease: false
104
- version_requirements: !ruby/object:Gem::Requirement
105
- requirements:
106
- - - "~>"
107
- - !ruby/object:Gem::Version
108
- version: '2.0'
109
95
  - !ruby/object:Gem::Dependency
110
96
  name: lutaml-model
111
97
  requirement: !ruby/object:Gem::Requirement
@@ -154,14 +140,14 @@ dependencies:
154
140
  requirements:
155
141
  - - "~>"
156
142
  - !ruby/object:Gem::Version
157
- version: '4.0'
143
+ version: '10.0'
158
144
  type: :runtime
159
145
  prerelease: false
160
146
  version_requirements: !ruby/object:Gem::Requirement
161
147
  requirements:
162
148
  - - "~>"
163
149
  - !ruby/object:Gem::Version
164
- version: '4.0'
150
+ version: '10.0'
165
151
  - !ruby/object:Gem::Dependency
166
152
  name: paint
167
153
  requirement: !ruby/object:Gem::Requirement
@@ -473,6 +459,7 @@ files:
473
459
  - lib/fontist/install_locations/fontist_location.rb
474
460
  - lib/fontist/install_locations/system_location.rb
475
461
  - lib/fontist/install_locations/user_location.rb
462
+ - lib/fontist/json.rb
476
463
  - lib/fontist/macos.rb
477
464
  - lib/fontist/macos/catalog.rb
478
465
  - lib/fontist/macos/catalog/asset.rb