fontist 2.1.5 → 2.2.0

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rubocop_todo.yml +476 -11
  4. data/README.adoc +1 -1
  5. data/TODO.fontist-v5.md +196 -0
  6. data/fontist.gemspec +1 -1
  7. data/lib/fontist/cache/store.rb +17 -7
  8. data/lib/fontist/cli.rb +116 -0
  9. data/lib/fontist/errors.rb +61 -0
  10. data/lib/fontist/extract.rb +13 -0
  11. data/lib/fontist/font.rb +9 -1
  12. data/lib/fontist/font_collection.rb +1 -0
  13. data/lib/fontist/font_finder.rb +154 -0
  14. data/lib/fontist/font_installer.rb +172 -15
  15. data/lib/fontist/font_model.rb +1 -0
  16. data/lib/fontist/font_path.rb +4 -4
  17. data/lib/fontist/font_style.rb +17 -0
  18. data/lib/fontist/format_matcher.rb +176 -0
  19. data/lib/fontist/format_spec.rb +80 -0
  20. data/lib/fontist/formula.rb +126 -215
  21. data/lib/fontist/formula_picker.rb +39 -4
  22. data/lib/fontist/import/create_formula.rb +80 -3
  23. data/lib/fontist/import/formula_builder.rb +5 -1
  24. data/lib/fontist/import/google/font_database.rb +15 -153
  25. data/lib/fontist/import/google/formula_builder.rb +26 -0
  26. data/lib/fontist/import/google/formula_builders/base_formula_builder.rb +93 -0
  27. data/lib/fontist/import/google/formula_builders/formula_builder_v4.rb +155 -0
  28. data/lib/fontist/import/google/formula_builders/formula_builder_v5.rb +193 -0
  29. data/lib/fontist/import/google_fonts_importer.rb +17 -5
  30. data/lib/fontist/import/{macos.rb → macos_importer.rb} +4 -2
  31. data/lib/fontist/import/recursive_extraction.rb +2 -0
  32. data/lib/fontist/import/{sil_import.rb → sil_importer.rb} +3 -1
  33. data/lib/fontist/import/upgrade_formulas.rb +1 -1
  34. data/lib/fontist/import/v4_to_v5_migrator.rb +263 -0
  35. data/lib/fontist/import_cli.rb +20 -2
  36. data/lib/fontist/indexes/index_mixin.rb +8 -4
  37. data/lib/fontist/manifest.rb +27 -1
  38. data/lib/fontist/path_scanning.rb +1 -1
  39. data/lib/fontist/resource.rb +54 -0
  40. data/lib/fontist/resource_collection.rb +18 -0
  41. data/lib/fontist/system_index.rb +18 -9
  42. data/lib/fontist/utils/downloader.rb +0 -2
  43. data/lib/fontist/utils/github_client.rb +5 -2
  44. data/lib/fontist/utils/github_url.rb +4 -3
  45. data/lib/fontist/utils.rb +1 -1
  46. data/lib/fontist/version.rb +1 -1
  47. data/lib/fontist.rb +3 -2
  48. metadata +19 -8
@@ -4,17 +4,18 @@ require "paint"
4
4
 
5
5
  module Fontist
6
6
  module Import
7
- class Macos
7
+ class MacosImporter
8
8
  HOMEPAGE = "https://support.apple.com/en-om/HT211240#document".freeze
9
9
 
10
10
  def initialize(catalog_path, formulas_dir: nil, font_name: nil,
11
- force: false, verbose: false, import_cache: nil)
11
+ force: false, verbose: false, import_cache: nil, schema_version: 4)
12
12
  @catalog_path = catalog_path
13
13
  @custom_formulas_dir = formulas_dir
14
14
  @font_name_filter = font_name
15
15
  @force = force
16
16
  @verbose = verbose
17
17
  @import_cache = import_cache
18
+ @schema_version = schema_version
18
19
  @success_count = 0
19
20
  @failure_count = 0
20
21
  @skipped_count = 0
@@ -129,6 +130,7 @@ force: false, verbose: false, import_cache: nil)
129
130
  verbose: @verbose,
130
131
  import_cache: @import_cache,
131
132
  name: family_name,
133
+ schema_version: @schema_version,
132
134
  ).call
133
135
 
134
136
  elapsed = Time.now - start_time
@@ -1,3 +1,5 @@
1
+ require "excavate"
2
+
1
3
  module Fontist
2
4
  module Import
3
5
  class RecursiveExtraction
@@ -3,7 +3,7 @@ require "paint"
3
3
 
4
4
  module Fontist
5
5
  module Import
6
- class SilImport
6
+ class SilImporter
7
7
  ROOT = "https://software.sil.org/fonts/".freeze
8
8
 
9
9
  def initialize(options = {})
@@ -12,6 +12,7 @@ module Fontist
12
12
  @verbose = options[:verbose]
13
13
  @import_cache = options[:import_cache]
14
14
  @force = options[:force]
15
+ @schema_version = options[:schema_version] || 4
15
16
  @success_count = 0
16
17
  @failure_count = 0
17
18
  @skipped_count = 0
@@ -301,6 +302,7 @@ module Fontist
301
302
  options[:import_source] = import_source if import_source
302
303
  options[:import_cache] = @import_cache if @import_cache
303
304
  options[:keep_existing] = !@force
305
+ options[:schema_version] = @schema_version
304
306
  # All SIL fonts use the SIL Open Font License
305
307
  options[:open_license] = "OFL-1.1"
306
308
 
@@ -19,7 +19,7 @@ module Fontist
19
19
  # - Detects variable font axes from filenames
20
20
  class UpgradeFormulas
21
21
  ARCHIVE_EXTENSIONS = %w[zip tar gz tgz bz2 7z rar].freeze
22
- FONT_EXTENSIONS = %w[ttf otf woff2 ttc otc].freeze
22
+ FONT_EXTENSIONS = %w[ttf otf woff woff2 ttc otc].freeze
23
23
 
24
24
  def initialize(formulas_path, options = {})
25
25
  @formulas_path = formulas_path
@@ -0,0 +1,263 @@
1
+ require "date"
2
+ require "fileutils"
3
+ require "json"
4
+ require "yaml"
5
+
6
+ module Fontist
7
+ module Import
8
+ # Migrate v4 formulas to v5 schema
9
+ #
10
+ # This script converts existing v4 formula files to v5 format by:
11
+ # 1. Adding schema_version: 5
12
+ # 2. Detecting format from file extensions in resources
13
+ # 3. Detecting variable fonts from filename patterns
14
+ #
15
+ # Usage:
16
+ # Fontist::Import::V4ToV5Migrator.new(input_path, output_path).migrate_all
17
+ #
18
+ class V4ToV5Migrator
19
+ FONT_EXTENSIONS = %w[ttf otf woff woff2 ttc otc dfont].freeze
20
+ ARCHIVE_EXTENSIONS = %w[zip tar gz tgz bz2 7z rar exe cab].freeze
21
+
22
+ def initialize(input_path, output_path = nil, options = {})
23
+ @input_path = input_path
24
+ @output_path = output_path || input_path
25
+ @verbose = options[:verbose]
26
+ @dry_run = options[:dry_run]
27
+ end
28
+
29
+ # Migrate all formulas in the input path
30
+ #
31
+ # @return [Hash] results with counts of migrated, skipped, failed
32
+ def migrate_all
33
+ results = { migrated: 0, skipped: 0, failed: 0, errors: [] }
34
+
35
+ files = formula_files
36
+ log "Found #{files.size} formula file(s) to process"
37
+
38
+ files.each do |path|
39
+ result = migrate_file(path)
40
+ case result
41
+ when :migrated
42
+ results[:migrated] += 1
43
+ when :skipped
44
+ results[:skipped] += 1
45
+ end
46
+ rescue StandardError => e
47
+ results[:failed] += 1
48
+ results[:errors] << { formula: path, error: e.message }
49
+ log "✗ Failed #{File.basename(path)}: #{e.message}"
50
+ end
51
+
52
+ log_summary(results)
53
+ results
54
+ end
55
+
56
+ # Migrate a single formula file
57
+ #
58
+ # @param path [String] path to formula file
59
+ # @return [Symbol] :migrated or :skipped
60
+ def migrate_file(path)
61
+ formula_data = load_yaml_without_aliases(path)
62
+ already_v5 = formula_data["schema_version"] == 5
63
+
64
+ # Add schema_version: 5
65
+ formula_data = add_schema_version(formula_data) unless already_v5
66
+
67
+ # Check if the raw file contains YAML aliases that need resolving
68
+ changed = file_has_yaml_aliases?(path)
69
+
70
+ changed |= upgrade_resources(formula_data) if formula_data["resources"]
71
+
72
+ # Nothing to do if already v5 and no fixes needed
73
+ if already_v5 && !changed
74
+ log " Already v5: #{File.basename(path)}"
75
+ return :skipped
76
+ end
77
+
78
+ # Calculate output path
79
+ output_file = output_path_for(path)
80
+
81
+ # Save if not dry run
82
+ if @dry_run
83
+ log " Would save: #{output_file}"
84
+ else
85
+ FileUtils.mkdir_p(File.dirname(output_file))
86
+ File.write(output_file, YAML.dump(formula_data))
87
+ log " Saved: #{output_file}"
88
+ end
89
+
90
+ :migrated
91
+ end
92
+
93
+ private
94
+
95
+ def load_yaml_without_aliases(path)
96
+ data = YAML.safe_load(File.read(path), aliases: true, permitted_classes: [Date])
97
+ JSON.parse(JSON.generate(data))
98
+ end
99
+
100
+ def file_has_yaml_aliases?(path)
101
+ File.read(path).match?(/^\s*- \*\d+/)
102
+ end
103
+
104
+ def formula_files
105
+ if File.file?(@input_path)
106
+ [@input_path]
107
+ elsif File.directory?(@input_path)
108
+ Dir.glob(File.join(@input_path, "**/*.yml")).sort
109
+ else
110
+ []
111
+ end
112
+ end
113
+
114
+ def output_path_for(input_file)
115
+ return input_file if @output_path.nil? || @input_path == @output_path
116
+
117
+ # If input is a file and output is a directory, use the same filename
118
+ if File.file?(@input_path) && File.directory?(@output_path)
119
+ return File.join(@output_path, File.basename(input_file))
120
+ end
121
+
122
+ # Calculate relative path and map to output
123
+ relative = input_file.sub(@input_path, "")
124
+ File.join(@output_path, relative).sub(%r{/+}, "/")
125
+ end
126
+
127
+ def add_schema_version(formula_data)
128
+ # Insert schema_version at the beginning for clean YAML output
129
+ { "schema_version" => 5 }.merge(formula_data)
130
+ end
131
+
132
+ def upgrade_resources(formula_data)
133
+ changed = false
134
+
135
+ formula_data["resources"].each do |resource_name, resource_data|
136
+ next unless resource_data.is_a?(Hash)
137
+
138
+ # Skip archives - they don't have format
139
+ next if archive_resource?(resource_name, resource_data)
140
+
141
+ # Add format if missing
142
+ unless resource_data["format"]
143
+ format = detect_format(resource_name, resource_data)
144
+ if format
145
+ resource_data["format"] = format
146
+ changed = true
147
+ end
148
+ end
149
+
150
+ # Add variable_axes if missing and detected
151
+ unless resource_data["variable_axes"]
152
+ axes = detect_variable_axes(resource_name, resource_data)
153
+ if axes&.any?
154
+ resource_data["variable_axes"] = axes
155
+ changed = true
156
+ end
157
+ end
158
+ end
159
+
160
+ changed
161
+ end
162
+
163
+ def archive_resource?(resource_name, resource_data)
164
+ return true if archive_extension?(resource_name)
165
+
166
+ urls = Array(resource_data["urls"] || resource_data["files"])
167
+ urls.any? { |url| archive_extension?(url) }
168
+ end
169
+
170
+ def archive_extension?(path)
171
+ path =~ /\.(#{ARCHIVE_EXTENSIONS.join('|')})(?:\?|$)/i
172
+ end
173
+
174
+ def detect_format(resource_name, resource_data)
175
+ # Try from resource name
176
+ format = format_from_name(resource_name)
177
+ return format if format
178
+
179
+ # Try from URLs
180
+ urls = Array(resource_data["urls"] || resource_data["files"])
181
+ urls.each do |url|
182
+ format = format_from_url(url)
183
+ return format if format
184
+ end
185
+
186
+ # Try from files array
187
+ files = Array(resource_data["files"])
188
+ files.each do |file|
189
+ format = format_from_name(file)
190
+ return format if format
191
+ end
192
+
193
+ nil
194
+ end
195
+
196
+ def format_from_name(name)
197
+ if name =~ /\.(\w+)(?:\?|$)/
198
+ ext = Regexp.last_match(1).downcase
199
+ return ext if FONT_EXTENSIONS.include?(ext)
200
+ end
201
+ nil
202
+ end
203
+
204
+ def format_from_url(url)
205
+ filename = url.split("/").last.split("?").first
206
+ format_from_name(filename)
207
+ end
208
+
209
+ def detect_variable_axes(resource_name, resource_data)
210
+ # Try from resource name
211
+ axes = axes_from_name(resource_name)
212
+ return axes if axes.any?
213
+
214
+ # Try from URLs
215
+ urls = Array(resource_data["urls"] || resource_data["files"])
216
+ urls.each do |url|
217
+ axes = axes_from_name(url)
218
+ return axes if axes.any?
219
+ end
220
+
221
+ # Try from files array
222
+ files = Array(resource_data["files"])
223
+ files.each do |file|
224
+ axes = axes_from_name(file)
225
+ return axes if axes.any?
226
+ end
227
+
228
+ []
229
+ end
230
+
231
+ def axes_from_name(name)
232
+ if name =~ /\[([^\]]+)\]/
233
+ Regexp.last_match(1).split(",").map(&:strip)
234
+ else
235
+ []
236
+ end
237
+ end
238
+
239
+ def log(message)
240
+ puts message if @verbose
241
+ end
242
+
243
+ def log_summary(results)
244
+ return unless @verbose
245
+
246
+ puts "\n#{'=' * 60}"
247
+ puts "Migration Summary"
248
+ puts "=" * 60
249
+ puts " Migrated: #{results[:migrated]}"
250
+ puts " Skipped: #{results[:skipped]}"
251
+ puts " Failed: #{results[:failed]}"
252
+
253
+ if results[:errors].any?
254
+ puts "\nErrors:"
255
+ results[:errors].each do |error|
256
+ puts " - #{error[:formula]}: #{error[:error]}"
257
+ end
258
+ end
259
+ puts "=" * 60
260
+ end
261
+ end
262
+ end
263
+ end
@@ -21,6 +21,10 @@ module Fontist
21
21
  option :import_cache,
22
22
  type: :string,
23
23
  desc: "Directory for import cache (default: ~/.fontist/import_cache)"
24
+ option :schema_version,
25
+ type: :numeric,
26
+ default: 4,
27
+ desc: "Formula schema version (4 or 5). v5 supports multi-format fonts."
24
28
 
25
29
  def google
26
30
  handle_class_options(options)
@@ -35,6 +39,7 @@ module Fontist
35
39
  force: options[:force],
36
40
  verbose: options[:verbose],
37
41
  import_cache: options[:import_cache],
42
+ schema_version: options[:schema_version],
38
43
  )
39
44
 
40
45
  result = importer.import
@@ -78,9 +83,14 @@ module Fontist
78
83
  option :import_cache,
79
84
  type: :string,
80
85
  desc: "Directory for import cache (default: ~/.fontist/import_cache)"
86
+ option :schema_version,
87
+ type: :numeric,
88
+ default: 4,
89
+ desc: "Formula schema version (4 or 5). v5 supports multi-format fonts."
81
90
 
82
91
  def macos
83
92
  handle_class_options(options)
93
+ require_relative "import/macos_importer"
84
94
 
85
95
  # Handle deprecated formulas_dir option
86
96
  output_dir = if options[:formulas_dir] && !options[:output_path]
@@ -95,13 +105,14 @@ module Fontist
95
105
  verbose = options[:verbose]
96
106
  font_name = options[:font_name]
97
107
 
98
- Import::Macos.new(
108
+ Import::MacosImporter.new(
99
109
  plist_path,
100
110
  formulas_dir: output_dir,
101
111
  font_name: font_name,
102
112
  force: force,
103
113
  verbose: verbose,
104
114
  import_cache: options[:import_cache],
115
+ schema_version: options[:schema_version],
105
116
  ).call
106
117
 
107
118
  CLI::STATUS_SUCCESS
@@ -127,16 +138,23 @@ module Fontist
127
138
  option :import_cache,
128
139
  type: :string,
129
140
  desc: "Directory for import cache (default: ~/.fontist/import_cache)"
141
+ option :schema_version,
142
+ type: :numeric,
143
+ default: 4,
144
+ desc: "Formula schema version (4 or 5). v5 supports multi-format fonts."
130
145
 
131
146
  def sil
132
147
  handle_class_options(options)
133
148
 
134
- importer = Fontist::Import::SilImport.new(
149
+ require "fontist/import/sil_importer"
150
+
151
+ importer = Fontist::Import::SilImporter.new(
135
152
  output_path: options[:output_path],
136
153
  font_name: options[:font_name],
137
154
  force: options[:force],
138
155
  verbose: options[:verbose],
139
156
  import_cache: options[:import_cache],
157
+ schema_version: options[:schema_version],
140
158
  )
141
159
 
142
160
  result = importer.call
@@ -49,9 +49,9 @@ module Fontist
49
49
 
50
50
  file_content = File.read(file_path).strip
51
51
 
52
- if file_content.empty?
53
- raise Fontist::Errors::FontIndexCorrupted,
54
- "Index file is empty: #{file_path}"
52
+ if file_content.empty? || file_content == "---"
53
+ # Return empty collection for empty index files
54
+ return new
55
55
  end
56
56
 
57
57
  from_yaml(file_content)
@@ -105,7 +105,11 @@ module Fontist
105
105
  end
106
106
 
107
107
  def add_formula(formula)
108
- raise unless formula.is_a?(Formula)
108
+ # Accept FormulaV4, FormulaV5, or any object that responds to all_fonts
109
+ unless formula.respond_to?(:all_fonts) && formula.respond_to?(:path)
110
+ raise ArgumentError,
111
+ "Expected formula-like object, got #{formula.class}"
112
+ end
109
113
 
110
114
  formula.all_fonts.each do |font|
111
115
  font.styles.each do |style|
@@ -1,10 +1,35 @@
1
1
  require "lutaml/model"
2
+ require_relative "format_spec"
2
3
 
3
4
  module Fontist
4
5
  class ManifestFont < Lutaml::Model::Serializable
5
6
  attribute :name, :string
6
7
  attribute :styles, :string, collection: true
7
8
 
9
+ # Format specification (if not available, will transcode using Fontisan)
10
+ attribute :format, :string
11
+ attribute :variable_axes, :string, collection: true
12
+ attribute :prefer_variable, :boolean, default: false
13
+
14
+ # Transcoding options
15
+ attribute :transcode_path, :string
16
+ attribute :keep_original, :boolean, default: true
17
+
18
+ # Collection options
19
+ attribute :collection_index, :integer
20
+
21
+ # Build FormatSpec from attributes
22
+ def format_spec
23
+ FormatSpec.new(
24
+ format: format,
25
+ variable_axes: variable_axes,
26
+ prefer_variable: prefer_variable,
27
+ transcode_path: transcode_path,
28
+ keep_original: keep_original,
29
+ collection_index: collection_index,
30
+ )
31
+ end
32
+
8
33
  def style_paths(locations: false)
9
34
  ary = Array(styles)
10
35
  (ary.empty? ? [nil] : ary).flat_map do |style|
@@ -29,7 +54,7 @@ module Fontist
29
54
  end
30
55
 
31
56
  def install(confirmation: "no", hide_licenses: false, no_progress: false,
32
- location: nil)
57
+ location: nil)
33
58
  validate_location_parameter!(location)
34
59
  validate_platform_compatibility!
35
60
 
@@ -40,6 +65,7 @@ location: nil)
40
65
  hide_licenses: hide_licenses,
41
66
  no_progress: no_progress,
42
67
  location: location,
68
+ format_spec: format_spec,
43
69
  )
44
70
  rescue Fontist::Errors::PlatformMismatchError => e
45
71
  # Re-raise with clear context for manifest users
@@ -4,7 +4,7 @@ module Fontist
4
4
  module PathScanning
5
5
  FONT_EXTENSIONS = [
6
6
  ".ttf", ".TTF", ".ttc", ".TTC",
7
- ".otf", ".OTF",
7
+ ".otf", ".OTF", ".otc", ".OTC",
8
8
  ".woff", ".woff2", ".WOFF", ".WOFF2"
9
9
  ].freeze
10
10
 
@@ -0,0 +1,54 @@
1
+ require "lutaml/model"
2
+
3
+ module Fontist
4
+ # Resource - v5 resource with format metadata for multi-format support
5
+ class Resource < Lutaml::Model::Serializable
6
+ attribute :name, :string
7
+ attribute :source, :string
8
+ attribute :urls, :string, collection: true
9
+ attribute :sha256, :string, collection: true
10
+ attribute :file_size, :integer
11
+ attribute :family, :string
12
+ attribute :files, :string, collection: true
13
+
14
+ # v5 format metadata
15
+ attribute :format, :string # ttf, otf, woff2, ttc, otc
16
+ attribute :variable_axes, :string, collection: true # [wght], [ital,wght], etc.
17
+
18
+ key_value do
19
+ map "name", to: :name
20
+ map "source", to: :source
21
+ map "urls", to: :urls
22
+ map "sha256", to: :sha256
23
+ map "file_size", to: :file_size
24
+ map "family", to: :family
25
+ map "files", to: :files
26
+ map "format", to: :format
27
+ map "variable_axes", to: :variable_axes
28
+ end
29
+
30
+ def empty?
31
+ Array(urls).empty? && Array(files).empty?
32
+ end
33
+
34
+ def variable_font?
35
+ variable_axes && !variable_axes.empty?
36
+ end
37
+
38
+ def static_font?
39
+ !variable_font?
40
+ end
41
+
42
+ def axes_tags
43
+ Array(variable_axes).map(&:to_s)
44
+ end
45
+
46
+ def has_axis?(tag)
47
+ axes_tags.include?(tag.to_s)
48
+ end
49
+
50
+ def collection_file?
51
+ %w[ttc otc].include?(format&.to_s)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,18 @@
1
+ require "lutaml/model"
2
+ require_relative "resource"
3
+
4
+ module Fontist
5
+ # Resource Collection
6
+ class ResourceCollection < Lutaml::Model::Collection
7
+ instances :resources, Resource
8
+
9
+ key_value do
10
+ map_key to_instance: :name
11
+ map_instances to: :resources
12
+ end
13
+
14
+ def empty?
15
+ resources.nil? || Array(resources).all?(&:empty?)
16
+ end
17
+ end
18
+ end
@@ -117,6 +117,11 @@ module Fontist
117
117
  attribute :file_size, :integer
118
118
  attribute :file_mtime, :integer
119
119
 
120
+ # Format tracking (v5 schema support)
121
+ attribute :format, :string
122
+ attribute :variable_font, :boolean, default: false
123
+ attribute :variable_axes, :string, collection: true
124
+
120
125
  alias :type :subfamily
121
126
 
122
127
  key_value do
@@ -128,6 +133,9 @@ module Fontist
128
133
  map "preferred_subfamily_name", to: :preferred_subfamily_name
129
134
  map "file_size", to: :file_size
130
135
  map "file_mtime", to: :file_mtime
136
+ map "format", to: :format
137
+ map "variable_font", to: :variable_font
138
+ map "variable_axes", to: :variable_axes
131
139
  end
132
140
  end
133
141
 
@@ -195,7 +203,7 @@ module Fontist
195
203
  File.write(path, to_yaml)
196
204
  end
197
205
 
198
- def find(font, style)
206
+ def find(font, style, format_spec: nil)
199
207
  current_fonts = index
200
208
 
201
209
  return nil if current_fonts.nil? || current_fonts.empty?
@@ -210,6 +218,13 @@ module Fontist
210
218
  end
211
219
  end
212
220
 
221
+ # Apply format filtering if specified
222
+ if format_spec&.has_constraints? && found_fonts
223
+ require_relative "format_matcher"
224
+ matcher = FormatMatcher.new(format_spec)
225
+ found_fonts = matcher.filter_indexed_fonts(found_fonts)
226
+ end
227
+
213
228
  found_fonts.empty? ? nil : found_fonts
214
229
  end
215
230
 
@@ -295,12 +310,6 @@ module Fontist
295
310
  self
296
311
  end
297
312
 
298
- def update
299
- tap do |col|
300
- col.fonts = detect_paths(@paths_loader&.call || [])
301
- end
302
- end
303
-
304
313
  def update(verbose: false, stats: nil)
305
314
  tap do |col|
306
315
  col.fonts = detect_paths(@paths_loader&.call || [], verbose: verbose,
@@ -637,9 +646,9 @@ spinner_index = nil)
637
646
 
638
647
  def gather_fonts(path)
639
648
  case File.extname(path).gsub(/^\./, "").downcase
640
- when "ttf", "otf"
649
+ when "ttf", "otf", "woff", "woff2"
641
650
  detect_file_font(path)
642
- when "ttc"
651
+ when "ttc", "otc"
643
652
  detect_collection_fonts(path)
644
653
  else
645
654
  print_recognition_error(Errors::UnknownFontTypeError.new(path), path)
@@ -141,8 +141,6 @@ module Fontist
141
141
  {}
142
142
  end
143
143
 
144
- private
145
-
146
144
  def extract_raw_url
147
145
  obj = Helpers.url_object(@file)
148
146
  obj.respond_to?(:url) ? obj.url : obj
@@ -31,11 +31,14 @@ module Fontist
31
31
  end
32
32
 
33
33
  def fetch_release(client, parsed_url)
34
- client.release_for_tag("#{parsed_url.owner}/#{parsed_url.repo}", parsed_url.tag)
34
+ client.release_for_tag("#{parsed_url.owner}/#{parsed_url.repo}",
35
+ parsed_url.tag)
35
36
  end
36
37
 
37
38
  def find_asset_url(release, asset_name)
38
- release.assets.find { |asset| asset.name == asset_name }&.browser_download_url
39
+ release.assets.find do |asset|
40
+ asset.name == asset_name
41
+ end&.browser_download_url
39
42
  end
40
43
  end
41
44
  end
@@ -2,7 +2,7 @@ module Fontist
2
2
  module Utils
3
3
  class GitHubUrl
4
4
  GITHUB_RELEASE_PATTERN =
5
- %r{^https?://github\.com/(?<owner>[^/]+)/(?<repo>[^/]+)/releases/download/(?<tag>[^/]+)/(?<asset>.+)$}
5
+ %r{^https?://github\.com/(?<owner>[^/]+)/(?<repo>[^/]+)/releases/download/(?<tag>[^/]+)/(?<asset>.+)$}.freeze
6
6
 
7
7
  class << self
8
8
  def match?(url)
@@ -19,7 +19,7 @@ module Fontist
19
19
  repo: match[:repo],
20
20
  tag: match[:tag],
21
21
  asset: match[:asset],
22
- original_url: url_string
22
+ original_url: url_string,
23
23
  )
24
24
  else
25
25
  ParsedUrl.from_non_github_url(url_string)
@@ -39,7 +39,8 @@ module Fontist
39
39
  end
40
40
 
41
41
  def self.from_non_github_url(original_url)
42
- new(owner: nil, repo: nil, tag: nil, asset: nil, original_url: original_url)
42
+ new(owner: nil, repo: nil, tag: nil, asset: nil,
43
+ original_url: original_url)
43
44
  end
44
45
 
45
46
  def matched?