fontist 1.20.0 → 1.21.1

Sign up to get free protection for your applications and to get access to all the features.
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