fontist 1.13.2 → 1.14.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/fontist/cli.rb CHANGED
@@ -2,7 +2,7 @@ require "thor"
2
2
  require "fontist/cli/class_options"
3
3
  require "fontist/repo_cli"
4
4
  require "fontist/import_cli"
5
- require "fontist/google_cli"
5
+ require "fontist/fontconfig_cli"
6
6
 
7
7
  module Fontist
8
8
  class CLI < Thor
@@ -22,6 +22,8 @@ module Fontist
22
22
  STATUS_MANUAL_FONT_ERROR = 11
23
23
  STATUS_SIZE_LIMIT_ERROR = 12
24
24
  STATUS_FORMULA_NOT_FOUND = 13
25
+ STATUS_FONTCONFIG_NOT_FOUND = 14
26
+ STATUS_FONTCONFIG_FILE_NOT_FOUND = 15
25
27
 
26
28
  ERROR_TO_STATUS = {
27
29
  Fontist::Errors::UnsupportedFontError => [STATUS_NON_SUPPORTED_FONT_ERROR],
@@ -44,6 +46,9 @@ module Fontist
44
46
  Fontist::Errors::RepoNotFoundError => [STATUS_REPO_NOT_FOUND],
45
47
  Fontist::Errors::MainRepoNotFoundError => [STATUS_MAIN_REPO_NOT_FOUND],
46
48
  Fontist::Errors::FormulaNotFoundError => [STATUS_FORMULA_NOT_FOUND],
49
+ Fontist::Errors::FontconfigNotFoundError => [STATUS_FONTCONFIG_NOT_FOUND],
50
+ Fontist::Errors::FontconfigFileNotFoundError =>
51
+ [STATUS_FONTCONFIG_FILE_NOT_FOUND],
47
52
  }.freeze
48
53
 
49
54
  def self.exit_on_failure?
@@ -63,9 +68,9 @@ module Fontist
63
68
 
64
69
  desc "install FONT", "Install font"
65
70
  option :force, type: :boolean, aliases: :f,
66
- desc: "Install even if it's already installed in system"
71
+ desc: "Install even if already installed in system"
67
72
  option :formula, type: :boolean, aliases: :F,
68
- desc: "Install by formula instead of font"
73
+ desc: "Install whole formula instead of a font"
69
74
  option :accept_all_licenses, type: :boolean,
70
75
  aliases: ["--confirm-license", :a],
71
76
  desc: "Accept all license agreements"
@@ -76,13 +81,15 @@ module Fontist
76
81
  option :version, type: :string, aliases: :V,
77
82
  desc: "Specify particular version of a font"
78
83
  option :smallest, type: :boolean, aliases: :s,
79
- desc: "Install the smallest formula if several"
84
+ desc: "Install the smallest font by file size if several"
80
85
  option :newest, type: :boolean, aliases: :n,
81
86
  desc: "Install the newest version of a font if several"
82
87
  option :size_limit,
83
88
  type: :numeric, aliases: :S,
84
- desc: "Specify size limit for formula " \
89
+ desc: "Specify upper limit for file size of a formula to be installed" \
85
90
  "(default is #{Fontist.formula_size_limit_in_megabytes} MB)"
91
+ option :update_fontconfig, type: :boolean, aliases: :u,
92
+ desc: "Update fontconfig"
86
93
  def install(font)
87
94
  handle_class_options(options)
88
95
  confirmation = options[:accept_all_licenses] ? "yes" : "no"
@@ -196,21 +203,14 @@ module Fontist
196
203
  STATUS_SUCCESS
197
204
  end
198
205
 
199
- desc "import-sil", "Import formulas from SIL"
200
- def import_sil
201
- handle_class_options(options)
202
- require "fontist/import/sil_import"
203
- Fontist::Import::SilImport.new.call
204
- end
205
-
206
206
  desc "repo SUBCOMMAND ...ARGS", "Manage custom repositories"
207
207
  subcommand "repo", Fontist::RepoCLI
208
208
 
209
209
  desc "import SUBCOMMAND ...ARGS", "Manage imports"
210
210
  subcommand "import", Fontist::ImportCLI
211
211
 
212
- desc "google SUBCOMMAND ...ARGS", "Manage Google formulas"
213
- subcommand "google", Fontist::GoogleCLI
212
+ desc "fontconfig SUBCOMMAND ...ARGS", "Manage fontconfig"
213
+ subcommand "fontconfig", Fontist::FontconfigCLI
214
214
 
215
215
  private
216
216
 
@@ -4,6 +4,18 @@ module Fontist
4
4
 
5
5
  class BinaryCallError < GeneralError; end
6
6
 
7
+ class FontconfigNotFoundError < GeneralError
8
+ def initialize
9
+ super("Could not find fontconfig.")
10
+ end
11
+ end
12
+
13
+ class FontconfigFileNotFoundError < GeneralError
14
+ def initialize
15
+ super("Fontist file could not be found in fontconfig configuration.")
16
+ end
17
+ end
18
+
7
19
  class FontIndexCorrupted < GeneralError; end
8
20
 
9
21
  class FontNotFoundError < GeneralError; end
data/lib/fontist/font.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "fontist/font_installer"
2
2
  require "fontist/font_path"
3
3
  require "fontist/formula_picker"
4
+ require "fontist/fontconfig"
4
5
 
5
6
  module Fontist
6
7
  class Font
@@ -15,6 +16,7 @@ module Fontist
15
16
  @newest = options[:newest]
16
17
  @size_limit = options[:size_limit]
17
18
  @by_formula = options[:formula]
19
+ @update_fontconfig = options[:update_fontconfig]
18
20
 
19
21
  check_or_create_fontist_path!
20
22
  end
@@ -166,9 +168,13 @@ module Fontist
166
168
  def download_font
167
169
  return if sufficient_formulas.empty?
168
170
 
169
- sufficient_formulas.flat_map do |formula|
171
+ paths = sufficient_formulas.flat_map do |formula|
170
172
  request_formula_installation(formula)
171
173
  end
174
+
175
+ update_fontconfig
176
+
177
+ paths
172
178
  end
173
179
 
174
180
  def request_formula_installation(formula)
@@ -220,6 +226,12 @@ module Fontist
220
226
  MSG
221
227
  end
222
228
 
229
+ def update_fontconfig
230
+ return unless @update_fontconfig
231
+
232
+ Fontconfig.update
233
+ end
234
+
223
235
  def manual_font
224
236
  return if manual_formulas.empty?
225
237
 
@@ -0,0 +1,87 @@
1
+ module Fontist
2
+ class Fontconfig
3
+ def self.update
4
+ new.update
5
+ end
6
+
7
+ def self.remove(options = {})
8
+ new(options).remove
9
+ end
10
+
11
+ def initialize(options = {})
12
+ @options = options
13
+ end
14
+
15
+ def update
16
+ ensure_fontconfig_installed
17
+ create_config
18
+ regenerate_fontconfig_cache
19
+ end
20
+
21
+ def remove
22
+ return handle_file_not_found unless config_exists?
23
+
24
+ regenerate_fontconfig_cache if fontconfig_installed?
25
+ remove_config
26
+ end
27
+
28
+ private
29
+
30
+ def ensure_fontconfig_installed
31
+ raise Errors::FontconfigNotFoundError unless fontconfig_installed?
32
+ end
33
+
34
+ def fontconfig_installed?
35
+ Utils::System.fontconfig_installed?
36
+ end
37
+
38
+ def create_config
39
+ return if File.exist?(config_path)
40
+
41
+ FileUtils.mkdir_p(File.dirname(config_path))
42
+ File.write(config_path, config_content)
43
+ end
44
+
45
+ def config_path
46
+ File.join(xdg_config_home, "fontconfig", "conf.d", "10-fontist.conf")
47
+ end
48
+
49
+ def xdg_config_home
50
+ ENV["XDG_CONFIG_HOME"] || File.join(Dir.home, ".config")
51
+ end
52
+
53
+ def config_content
54
+ <<~CONTENT
55
+ <?xml version='1.0'?>
56
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
57
+ <fontconfig>
58
+ <dir>#{Fontist.fonts_path}</dir>
59
+ </fontconfig>
60
+ CONTENT
61
+ end
62
+
63
+ def regenerate_fontconfig_cache
64
+ Helpers.run("fc-cache -f")
65
+ end
66
+
67
+ def ensure_file_exists
68
+ return if @options[:force]
69
+
70
+ raise Errors::FontconfigFileNotFoundError unless File.exist?(config_path)
71
+ end
72
+
73
+ def config_exists?
74
+ File.exist?(config_path)
75
+ end
76
+
77
+ def handle_file_not_found
78
+ return if @options[:force]
79
+
80
+ raise Errors::FontconfigFileNotFoundError
81
+ end
82
+
83
+ def remove_config
84
+ FileUtils.rm(config_path)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,29 @@
1
+ module Fontist
2
+ class FontconfigCLI < Thor
3
+ include CLI::ClassOptions
4
+
5
+ desc "update", "Update fontconfig configuration to use fontist fonts"
6
+ def update
7
+ handle_class_options(options)
8
+ Fontconfig.update
9
+ Fontist.ui.success("Fontconfig file has been successfully updated.")
10
+ CLI::STATUS_SUCCESS
11
+ rescue Errors::FontconfigNotFoundError => e
12
+ Fontist.ui.error(e.message)
13
+ CLI::STATUS_FONTCONFIG_NOT_FOUND
14
+ end
15
+
16
+ desc "remove", "Remove fontist file in fontconfig configuration"
17
+ option :force, type: :boolean, aliases: :f,
18
+ desc: "Proceed even if does not exist"
19
+ def remove
20
+ handle_class_options(options)
21
+ Fontconfig.remove(options)
22
+ Fontist.ui.success("Fontconfig file has been successfully removed.")
23
+ CLI::STATUS_SUCCESS
24
+ rescue Errors::FontconfigFileNotFoundError => e
25
+ Fontist.ui.error(e.message)
26
+ CLI::STATUS_FONTCONFIG_FILE_NOT_FOUND
27
+ end
28
+ end
29
+ end
@@ -3,5 +3,26 @@ module Fontist
3
3
  def self.parse_to_object(data)
4
4
  JSON.parse(data.to_json, object_class: OpenStruct)
5
5
  end
6
+
7
+ def self.run(command)
8
+ Fontist.ui.debug("Run `#{command}`")
9
+
10
+ result = `#{command}`
11
+ unless $CHILD_STATUS.to_i.zero?
12
+ raise Errors::BinaryCallError,
13
+ "Failed to run #{command}, status: #{$CHILD_STATUS}"
14
+ end
15
+
16
+ result
17
+ end
18
+
19
+ def self.silence_stream(stream)
20
+ old_stream = stream.dup
21
+ stream.reopen(RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ ? "NUL:" : "/dev/null") # rubocop:disable Performance/RegexpMatch, Metrics/LineLength
22
+ stream.sync = true
23
+ yield
24
+ ensure
25
+ stream.reopen(old_stream)
26
+ end
6
27
  end
7
28
  end
@@ -55,12 +55,32 @@ module Fontist
55
55
  end
56
56
 
57
57
  def save(builder)
58
- filename = Import.name_to_filename(builder.name)
59
- path = @options[:formula_dir] ? File.join(@options[:formula_dir], filename) : filename
58
+ path = vacant_path
60
59
  yaml = YAML.dump(Helpers::HashHelper.stringify_keys(builder.formula))
61
60
  File.write(path, yaml)
62
61
  path
63
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
64
84
  end
65
85
  end
66
86
  end
@@ -26,9 +26,9 @@ module Fontist
26
26
 
27
27
  def read
28
28
  switch_to_temp_dir do |tmp_dir|
29
- extract_ttfs(tmp_dir).map do |path|
30
- Otf::FontFile.new(path)
31
- end
29
+ extract_ttfs(tmp_dir)
30
+ .map { |path| Otf::FontFile.new(path) }
31
+ .reject { |font_file| hidden_or_pua_encoded?(font_file) }
32
32
  end
33
33
  end
34
34
 
@@ -47,6 +47,10 @@ module Fontist
47
47
  end
48
48
  end
49
49
 
50
+ def hidden_or_pua_encoded?(font_file)
51
+ font_file.family_name.start_with?(".")
52
+ end
53
+
50
54
  def detect_extension
51
55
  base_extension = "ttc"
52
56
 
@@ -4,9 +4,10 @@ require_relative "text_helper"
4
4
  module Fontist
5
5
  module Import
6
6
  class FormulaBuilder
7
- FORMULA_ATTRIBUTES = %i[description homepage resources
7
+ FORMULA_ATTRIBUTES = %i[platforms description homepage resources
8
8
  font_collections fonts extract copyright
9
- license_url open_license digest command].freeze
9
+ license_url requires_license_agreement
10
+ open_license digest command].freeze
10
11
 
11
12
  attr_writer :archive,
12
13
  :url,
@@ -28,9 +29,15 @@ module Fontist
28
29
  def name
29
30
  return @options[:name] if @options[:name]
30
31
 
31
- unique_names = both_fonts.map(&:family_name).uniq
32
- TextHelper.longest_common_prefix(unique_names) ||
33
- both_fonts.first.family_name
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(" ")
38
+ return common unless common.empty?
39
+
40
+ both_fonts.map(&:family_name).first
34
41
  end
35
42
 
36
43
  private
@@ -50,6 +57,10 @@ module Fontist
50
57
  files
51
58
  end
52
59
 
60
+ def platforms
61
+ @options[:platforms]
62
+ end
63
+
53
64
  def description
54
65
  name
55
66
  end
@@ -175,12 +186,17 @@ module Fontist
175
186
  both_fonts.map(&:license_url).compact.first
176
187
  end
177
188
 
189
+ def requires_license_agreement
190
+ @options[:requires_license_agreement]
191
+ end
192
+
178
193
  def open_license
179
- unless @license_text
194
+ unless @license_text || requires_license_agreement
180
195
  Fontist.ui.error("WARN: please add license manually")
181
- return
182
196
  end
183
197
 
198
+ return unless @license_text
199
+
184
200
  Fontist.ui.error("WARN: ensure it's an open license, otherwise " \
185
201
  "change the 'open_license' attribute to " \
186
202
  "'requires_license_agreement'")
@@ -121,6 +121,10 @@ module Fontist
121
121
  Down.open("https://fonts.google.com/download?family=#{name}")
122
122
  true
123
123
  rescue Down::NotFound
124
+ false
125
+ rescue Down::ClientError => e
126
+ raise unless e.message == "403 Forbidden"
127
+
124
128
  false
125
129
  rescue Down::TimeoutError
126
130
  retry unless retries >= 3
@@ -4,15 +4,7 @@ module Fontist
4
4
  module SystemHelper
5
5
  class << self
6
6
  def run(command)
7
- Fontist.ui.debug("Run `#{command}`")
8
-
9
- result = `#{command}`
10
- unless $CHILD_STATUS.to_i.zero?
11
- raise Errors::BinaryCallError,
12
- "Failed to run #{command}, status: #{$CHILD_STATUS}"
13
- end
14
-
15
- result
7
+ Fontist::Helpers.run(command)
16
8
  end
17
9
  end
18
10
  end