fontist 1.20.0 → 1.21.1

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: b84dad2469698853e57c647d63890c7f054f888db3d7c53367e7c17ac3ca3951
4
- data.tar.gz: ebc3c36071d189946c187af9c7a1e340ed703dc37b1eb40b821ce7557b4f2b3a
3
+ metadata.gz: 0cc55cf30c5781a41e3ff4ff617acacb24367c3148dc6c1db104ff5a1aad8105
4
+ data.tar.gz: 118207311bf22488c6adbff254fd3646d24f5b1f09f09d8eaf319489798c5002
5
5
  SHA512:
6
- metadata.gz: 3cb8a0632dde13a856ac227b3dfd6af2082fa9e4a4c27357e94664cfb3861b26ed16af5dd0891eb1542d3ec2ae4d82dbc8f57c8ae593f39610c071885f6e45d3
7
- data.tar.gz: a792eceef4a821e48acce6ccb7d863a9fbc7602ac8197f488a927a1e1563947740ff4863a6162af004dbfcc40789433980ce31aed0b2a674d6b10d74009b3b7d
6
+ metadata.gz: aebf180824a2e45fc23e6d795e98fa8394e735d1a0c6f58d5ec5740ea2343e31787d9075db3060a071ce3a8dc0b193502e6b76e76fba58c345522cc47ddd4b74
7
+ data.tar.gz: e411af70f8dd20ab8f7a55540fa24b5741a37813420fd14b0e3518f30a1d0a477565add1da666f6f91207ec90343ab2eabc04cd791d69a22c3c6c08c01ea639f
@@ -16,6 +16,7 @@ env:
16
16
  # does not cause bundler gem reinstalls
17
17
  # bundler/rubygems 2.3.22 is a minimal requirement to support gnu/musl differentiation
18
18
  # https://github.com/rubygems/rubygems/pull/4488
19
+ GOOGLE_FONTS_API_KEY: ${{secrets.FONTIST_CI_GOOGLE_FONTS_API_KEY}}
19
20
 
20
21
  jobs:
21
22
  prepare:
@@ -83,7 +84,7 @@ jobs:
83
84
  needs: prepare
84
85
  if: needs.prepare.outputs.push-for-tag != 'true'
85
86
 
86
- continue-on-error: ${{ matrix.ruby.experimental || matrix.os == 'windows-latest' }} # workaround https://github.com/metanorma/metanorma/issues/288
87
+ continue-on-error: true # ${{ matrix.ruby.experimental || matrix.os == 'windows-latest' }} # workaround https://github.com/metanorma/metanorma/issues/288
87
88
  strategy:
88
89
  fail-fast: false
89
90
  max-parallel: 5
data/README.adoc CHANGED
@@ -1066,7 +1066,7 @@ Fontist's https://github.com/fontist/formulas[formula library] includes support
1066
1066
  for all openly-licensed fonts provided through Google Fonts, and maintains
1067
1067
  Fontist formulas for all such fonts.
1068
1068
 
1069
- https://github.com/fontist/formulas/blob/v3/.github/workflows/google.yml[A GHA
1069
+ https://github.com/fontist/formulas/blob/v4/.github/workflows/google.yml[A GHA
1070
1070
  workflow] checks for updated fonts on Google Fonts daily. In case an update is
1071
1071
  found, it's added to the repo by the workflow.
1072
1072
 
data/exe/fontist CHANGED
@@ -4,8 +4,7 @@ require "fontist"
4
4
  require "fontist/cli"
5
5
 
6
6
  fontist_cli = proc {
7
- status_code = Fontist::CLI.start(ARGV)
8
- exit status_code.is_a?(Integer) ? status_code : 1
7
+ Fontist::CLI.start(ARGV)
9
8
  }
10
9
 
11
10
  if ENV["SOCKS_PROXY"]
@@ -0,0 +1,79 @@
1
+ require "fontist/utils/ui"
2
+
3
+ module Fontist
4
+ module ThorExt
5
+ # Sources:
6
+ # - https://github.com/mattbrictson/gem/blob/main/lib/example/thor_ext.rb
7
+ # - https://mattbrictson.com/blog/fixing-thor-cli-behavior
8
+ #
9
+ # Configures Thor to behave more like a typical CLI, with better help
10
+ # and error handling.
11
+ #
12
+ # - Passing -h or --help to a command will show help for that command.
13
+ # - Unrecognized options will be treated as errors.
14
+ # - Error messages will be printed in red to stderr, without stack trace.
15
+ # - Errors will cause Thor to exit with a non-zero status.
16
+ #
17
+ # To take advantage of this behavior, your CLI should subclass Thor
18
+ # and extend this module.
19
+ #
20
+ # class CLI < Thor
21
+ # extend ThorExt::Start
22
+ # end
23
+ #
24
+ # Start your CLI with:
25
+ #
26
+ # CLI.start
27
+ #
28
+ # In tests, prevent Kernel.exit from being called when an error occurs,
29
+ # like this:
30
+ #
31
+ # CLI.start(args, exit_on_failure: false)
32
+ module Start
33
+ def self.extended(base)
34
+ super
35
+ base.check_unknown_options!
36
+ end
37
+
38
+ def start(given_args = ARGV, config = {})
39
+ config[:shell] ||= Thor::Base.shell.new
40
+ handle_help_switches(given_args) do |args|
41
+ dispatch(nil, args, nil, config)
42
+ end
43
+ rescue StandardError => e
44
+ handle_exception_on_start(e, config)
45
+ end
46
+
47
+ private
48
+
49
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
50
+ def handle_help_switches(given_args)
51
+ yield(given_args.dup)
52
+ rescue Thor::UnknownArgumentError => e
53
+ retry_with_args = []
54
+
55
+ if given_args.first == "help"
56
+ retry_with_args = ["help"] if given_args.length > 1
57
+ elsif e.unknown.intersect?(%w[-h --help])
58
+ retry_with_args = ["help", (given_args - e.unknown).first]
59
+ end
60
+ raise unless retry_with_args.any?
61
+
62
+ yield(retry_with_args)
63
+ end
64
+
65
+ def handle_exception_on_start(error, config)
66
+ return if error.is_a?(Errno::EPIPE)
67
+ raise if Fontist.ui.debug? || !config.fetch(:exit_on_failure, true)
68
+
69
+ message = error.message.to_s
70
+ if message.empty? || !error.is_a?(Thor::Error)
71
+ message.prepend("[#{error.class}] ")
72
+ end
73
+ config[:shell]&.say_error(message, :red)
74
+ exit(false)
75
+ end
76
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
77
+ end
78
+ end
79
+ end
data/lib/fontist/cli.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "thor"
2
2
  require "fontist/cli/class_options"
3
+ require "fontist/cli/thor_ext"
3
4
  require "fontist/repo_cli"
4
5
  require "fontist/cache_cli"
5
6
  require "fontist/import_cli"
@@ -9,6 +10,7 @@ require "fontist/config_cli"
9
10
  module Fontist
10
11
  class CLI < Thor
11
12
  include ClassOptions
13
+ extend ThorExt::Start
12
14
 
13
15
  STATUS_SUCCESS = 0
14
16
  STATUS_UNKNOWN_ERROR = 1
@@ -44,7 +44,8 @@ module Fontist
44
44
  def default_values
45
45
  { fonts_path: Fontist.fontist_path.join("fonts"),
46
46
  open_timeout: 10,
47
- read_timeout: 10 }
47
+ read_timeout: 10,
48
+ google_fonts_key: nil }
48
49
  end
49
50
 
50
51
  def persist
data/lib/fontist/font.rb CHANGED
@@ -216,6 +216,11 @@ module Fontist
216
216
  confirmation = check_and_confirm_required_license(formula)
217
217
  paths = font_installer(formula).install(confirmation: confirmation)
218
218
 
219
+ if paths.nil? || paths.empty?
220
+ Fontist.ui.error("Fonts not found in formula #{formula}")
221
+ return
222
+ end
223
+
219
224
  Fontist.ui.say("Fonts installed at:")
220
225
  paths.each do |path|
221
226
  Fontist.ui.say("- #{path}")
@@ -1,5 +1,7 @@
1
1
  require "fontist/utils"
2
2
  require "excavate"
3
+ require_relative "resources/archive_resource"
4
+ require_relative "resources/google_resource"
3
5
 
4
6
  module Fontist
5
7
  class FontInstaller
@@ -48,63 +50,32 @@ module Fontist
48
50
  end
49
51
 
50
52
  def install_font
51
- fonts_paths = run_in_temp_dir { extract }
53
+ fonts_paths = do_install_font
52
54
  fonts_paths.empty? ? nil : fonts_paths
53
55
  end
54
56
 
55
- def run_in_temp_dir
56
- Dir.mktmpdir(nil, Dir.tmpdir) do |dir|
57
- @temp_dir = Pathname.new(dir)
58
-
59
- result = yield
60
-
61
- @temp_dir = nil
62
-
63
- result
64
- end
65
- end
66
-
67
- def extract
68
- archive = download_file(@formula.resources.first)
69
-
70
- install_fonts_from_archive(archive)
71
- end
72
-
73
- def install_fonts_from_archive(archive)
74
- Fontist.ui.say(%(Installing font "#{@formula.key}".))
57
+ def do_install_font
58
+ Fontist.ui.say(%(Installing from formula "#{@formula.key}".))
75
59
 
76
60
  Array.new.tap do |fonts_paths|
77
- Excavate::Archive.new(archive.path).files(recursive_packages: true) do |path|
61
+ resource.files(source_files) do |path|
78
62
  fonts_paths << install_font_file(path) if font_file?(path)
79
63
  end
80
64
  end
81
65
  end
82
66
 
83
- def download_file(source)
84
- errors = []
85
- source.urls.each do |request|
86
- url = request.respond_to?(:url) ? request.url : request
87
- Fontist.ui.say(%(Downloading font "#{@formula.key}" from #{url}))
88
-
89
- result = try_download_file(request, source)
90
- return result unless result.is_a?(Errors::InvalidResourceError)
91
-
92
- errors << result
93
- end
67
+ def resource
68
+ resource_class = if @formula.source == "google"
69
+ Resources::GoogleResource
70
+ else
71
+ Resources::ArchiveResource
72
+ end
94
73
 
95
- raise Errors::InvalidResourceError, errors.join(" ")
74
+ resource_class.new(resource_options, no_progress: @no_progress)
96
75
  end
97
76
 
98
- def try_download_file(request, source)
99
- Fontist::Utils::Downloader.download(
100
- request,
101
- sha: source.sha256,
102
- file_size: source.file_size,
103
- progress_bar: !@no_progress
104
- )
105
- rescue Errors::InvalidResourceError => e
106
- Fontist.ui.say(e.message)
107
- e
77
+ def resource_options
78
+ @formula.resources.first
108
79
  end
109
80
 
110
81
  def font_file?(path)
@@ -116,16 +87,16 @@ module Fontist
116
87
  end
117
88
 
118
89
  def source_files
119
- @source_files ||= @formula.fonts.flat_map do |font|
120
- next [] if @font_name && !font.name.casecmp?(@font_name)
121
-
122
- font_files(font)
90
+ @source_files ||= fonts.flat_map do |font|
91
+ font.styles.map do |style|
92
+ style.source_font || style.font
93
+ end
123
94
  end
124
95
  end
125
96
 
126
- def font_files(font)
127
- font.styles.map do |style|
128
- style.source_font || style.font
97
+ def fonts
98
+ @formula.fonts.select do |font|
99
+ @font_name.nil? || font.name.casecmp?(@font_name)
129
100
  end
130
101
  end
131
102
 
@@ -109,6 +109,12 @@ module Fontist
109
109
  @data.key?("resources")
110
110
  end
111
111
 
112
+ def source
113
+ return unless @data["resources"]
114
+
115
+ @data["resources"].values.first["source"]
116
+ end
117
+
112
118
  def path
113
119
  @path
114
120
  end
@@ -1,3 +1,5 @@
1
+ require "ostruct"
2
+
1
3
  module Fontist
2
4
  module Helpers
3
5
  def self.parse_to_object(data)
@@ -1,6 +1,5 @@
1
1
  require "fontist/import"
2
2
  require_relative "recursive_extraction"
3
- require_relative "helpers/hash_helper"
4
3
  require_relative "formula_builder"
5
4
 
6
5
  module Fontist
@@ -12,31 +11,102 @@ module Fontist
12
11
  end
13
12
 
14
13
  def call
15
- save(builder)
14
+ builder.save
16
15
  end
17
16
 
18
17
  private
19
18
 
20
19
  def builder
21
20
  builder = FormulaBuilder.new
22
- setup_strings(builder, archive)
21
+ setup_strings(builder)
23
22
  setup_files(builder)
24
23
  builder
25
24
  end
26
25
 
27
- def setup_strings(builder, archive)
28
- builder.archive = archive
29
- builder.url = @url
26
+ def setup_strings(builder)
30
27
  builder.options = @options
28
+ builder.resources = resources
31
29
  end
32
30
 
33
31
  def setup_files(builder)
34
- builder.extractor = extractor
32
+ builder.operations = extractor.operations
35
33
  builder.font_files = extractor.font_files
36
34
  builder.font_collection_files = extractor.font_collection_files
37
35
  builder.license_text = extractor.license_text
38
36
  end
39
37
 
38
+ def resources
39
+ @resources ||= { filename(archive) => resource_options }
40
+ end
41
+
42
+ def filename(file)
43
+ if file.respond_to?(:original_filename)
44
+ file.original_filename
45
+ else
46
+ File.basename(file)
47
+ end
48
+ end
49
+
50
+ def resource_options
51
+ if @options[:skip_sha]
52
+ resource_options_without_sha
53
+ else
54
+ resource_options_with_sha
55
+ end
56
+ end
57
+
58
+ def resource_options_without_sha
59
+ { urls: [@url] + mirrors, file_size: file_size }
60
+ end
61
+
62
+ def resource_options_with_sha
63
+ urls = []
64
+ sha = []
65
+ downloads do |url, path|
66
+ urls << url
67
+ sha << Digest::SHA256.file(path).to_s
68
+ end
69
+
70
+ sha = prepare_sha256(sha)
71
+
72
+ { urls: urls, sha256: sha, file_size: file_size }
73
+ end
74
+
75
+ def downloads
76
+ yield @url, archive
77
+
78
+ mirrors.each do |url|
79
+ path = download_mirror(url)
80
+ next unless path
81
+
82
+ yield url, path
83
+ end
84
+ end
85
+
86
+ def mirrors
87
+ @options[:mirror] || []
88
+ end
89
+
90
+ def download_mirror(url)
91
+ Fontist::Utils::Downloader.download(url, progress_bar: true).path
92
+ rescue Errors::InvalidResourceError
93
+ Fontist.ui.error("WARN: a mirror is not found '#{url}'")
94
+ nil
95
+ end
96
+
97
+ def prepare_sha256(input)
98
+ output = input.uniq
99
+ return output.first if output.size == 1
100
+
101
+ checksums = output.join(", ")
102
+ Fontist.ui.error("WARN: SHA256 differs (#{checksums})")
103
+ output
104
+ end
105
+
106
+ def file_size
107
+ File.size(archive)
108
+ end
109
+
40
110
  def extractor
41
111
  @extractor ||=
42
112
  RecursiveExtraction.new(archive,
@@ -53,34 +123,6 @@ module Fontist
53
123
 
54
124
  Fontist::Utils::Downloader.download(url, progress_bar: true).path
55
125
  end
56
-
57
- def save(builder)
58
- path = vacant_path
59
- yaml = YAML.dump(Helpers::HashHelper.stringify_keys(builder.formula))
60
- File.write(path, yaml)
61
- path
62
- end
63
-
64
- def vacant_path
65
- path = path_from_name
66
- return path unless @options[:keep_existing] && File.exist?(path)
67
-
68
- 2.upto(9) do |i|
69
- candidate = path.sub(/\.yml$/, "#{i}.yml")
70
- return candidate unless File.exist?(candidate)
71
- end
72
-
73
- raise Errors::GeneralError, "Formula #{path} already exists."
74
- end
75
-
76
- def path_from_name
77
- filename = Import.name_to_filename(builder.name)
78
- if @options[:formula_dir]
79
- File.join(@options[:formula_dir], filename)
80
- else
81
- filename
82
- end
83
- end
84
126
  end
85
127
  end
86
128
  end
@@ -1,49 +1,70 @@
1
1
  require "shellwords"
2
2
  require_relative "text_helper"
3
+ require_relative "helpers/hash_helper"
3
4
 
4
5
  module Fontist
5
6
  module Import
6
7
  class FormulaBuilder
7
- FORMULA_ATTRIBUTES = %i[platforms description homepage resources
8
+ FORMULA_ATTRIBUTES = %i[name platforms description homepage resources
8
9
  font_collections fonts extract copyright
9
10
  license_url requires_license_agreement
10
11
  open_license digest command].freeze
11
12
 
12
- attr_writer :archive,
13
- :url,
14
- :extractor,
13
+ attr_writer :resources,
15
14
  :options,
16
15
  :font_files,
17
16
  :font_collection_files,
18
17
  :license_text,
19
- :homepage
18
+ :operations
20
19
 
21
20
  def initialize
22
21
  @options = {}
22
+ @font_files = []
23
+ @font_collection_files = []
23
24
  end
24
25
 
25
26
  def formula
26
27
  formula_attributes.map { |name| [name, send(name)] }.to_h.compact
27
28
  end
28
29
 
30
+ def save
31
+ path = vacant_path
32
+ yaml = YAML.dump(Helpers::HashHelper.stringify_keys(formula))
33
+ File.write(path, yaml)
34
+ path
35
+ end
36
+
37
+ private
38
+
39
+ def formula_attributes
40
+ FORMULA_ATTRIBUTES
41
+ end
42
+
29
43
  def name
44
+ @name ||= generate_name
45
+ end
46
+
47
+ def generate_name
30
48
  return @options[:name] if @options[:name]
31
49
 
32
- common = %i[family_name type]
33
- .map { |attr| both_fonts.map(&attr).uniq }
34
- .map { |names| TextHelper.longest_common_prefix(names) }
35
- .map { |prefix| prefix unless prefix == "Regular" }
36
- .compact
37
- .join(" ")
50
+ common = common_prefix
38
51
  return common unless common.empty?
39
52
 
40
53
  both_fonts.map(&:family_name).first
41
54
  end
42
55
 
43
- private
56
+ def common_prefix
57
+ family_prefix = common_prefix_by_attr(:family_name)
58
+ style_prefix = common_prefix_by_attr(:type)
44
59
 
45
- def formula_attributes
46
- FORMULA_ATTRIBUTES
60
+ [family_prefix, style_prefix].compact.join(" ")
61
+ end
62
+
63
+ def common_prefix_by_attr(attr)
64
+ names = both_fonts.map(&attr).uniq
65
+ prefix = TextHelper.longest_common_prefix(names)
66
+
67
+ prefix unless prefix == "Regular"
47
68
  end
48
69
 
49
70
  def both_fonts
@@ -70,69 +91,7 @@ module Fontist
70
91
  end
71
92
 
72
93
  def resources
73
- filename = name.gsub(" ", "_") + "." + @extractor.extension
74
-
75
- { filename => resource_options }
76
- end
77
-
78
- def resource_options
79
- if @options[:skip_sha]
80
- resource_options_without_sha
81
- else
82
- resource_options_with_sha
83
- end
84
- end
85
-
86
- def resource_options_without_sha
87
- { urls: [@url] + mirrors, file_size: file_size }
88
- end
89
-
90
- def resource_options_with_sha
91
- urls = []
92
- sha = []
93
- downloads do |url, path|
94
- urls << url
95
- sha << Digest::SHA256.file(path).to_s
96
- end
97
-
98
- sha = prepare_sha256(sha)
99
-
100
- { urls: urls, sha256: sha, file_size: file_size }
101
- end
102
-
103
- def downloads
104
- yield @url, @archive
105
-
106
- mirrors.each do |url|
107
- path = download(url)
108
- next unless path
109
-
110
- yield url, path
111
- end
112
- end
113
-
114
- def mirrors
115
- @options[:mirror] || []
116
- end
117
-
118
- def download(url)
119
- Fontist::Utils::Downloader.download(url, progress_bar: true).path
120
- rescue Errors::InvalidResourceError
121
- Fontist.ui.error("WARN: a mirror is not found '#{url}'")
122
- nil
123
- end
124
-
125
- def prepare_sha256(input)
126
- output = input.uniq
127
- return output.first if output.size == 1
128
-
129
- checksums = output.join(", ")
130
- Fontist.ui.error("WARN: SHA256 differs (#{checksums})")
131
- output
132
- end
133
-
134
- def file_size
135
- File.size(@archive)
94
+ @resources || raise("Resources should be set.")
136
95
  end
137
96
 
138
97
  def font_collections
@@ -175,7 +134,7 @@ module Fontist
175
134
  end
176
135
 
177
136
  def extract
178
- @extractor.operations
137
+ @operations || {}
179
138
  end
180
139
 
181
140
  def copyright
@@ -197,9 +156,11 @@ module Fontist
197
156
 
198
157
  return unless @license_text
199
158
 
200
- Fontist.ui.error("WARN: ensure it's an open license, otherwise " \
201
- "change the 'open_license' attribute to " \
202
- "'requires_license_agreement'")
159
+ unless @options[:open_license]
160
+ Fontist.ui.error("WARN: ensure it's an open license, otherwise " \
161
+ "change the 'open_license' attribute to " \
162
+ "'requires_license_agreement'")
163
+ end
203
164
 
204
165
  TextHelper.cleanup(@license_text)
205
166
  end
@@ -211,6 +172,27 @@ module Fontist
211
172
  def command
212
173
  Shellwords.shelljoin(ARGV)
213
174
  end
175
+
176
+ def vacant_path
177
+ path = path_from_name
178
+ return path unless @options[:keep_existing] && File.exist?(path)
179
+
180
+ 2.upto(9) do |i|
181
+ candidate = path.sub(/\.yml$/, "#{i}.yml")
182
+ return candidate unless File.exist?(candidate)
183
+ end
184
+
185
+ raise Errors::GeneralError, "Formula #{path} already exists."
186
+ end
187
+
188
+ def path_from_name
189
+ filename = Import.name_to_filename(name)
190
+ if @options[:formula_dir]
191
+ File.join(@options[:formula_dir], filename)
192
+ else
193
+ filename
194
+ end
195
+ end
214
196
  end
215
197
  end
216
198
  end
@@ -0,0 +1,25 @@
1
+ module Fontist
2
+ module Import
3
+ module Google
4
+ class Api
5
+ class << self
6
+ def items
7
+ db["items"]
8
+ end
9
+
10
+ def db
11
+ @db ||= JSON.parse(Net::HTTP.get(URI(url)))
12
+ end
13
+
14
+ def url
15
+ "https://www.googleapis.com/webfonts/v1/webfonts?key=#{api_key}"
16
+ end
17
+
18
+ def api_key
19
+ Fontist.google_fonts_key
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end