fontist 1.7.0 → 1.8.2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +38 -0
  3. data/.github/workflows/rspec.yml +58 -0
  4. data/README.md +123 -32
  5. data/{bin → exe}/fontist +0 -0
  6. data/fontist.gemspec +5 -2
  7. data/lib/fontist.rb +10 -3
  8. data/lib/fontist/cli.rb +62 -41
  9. data/lib/fontist/errors.rb +14 -12
  10. data/lib/fontist/font.rb +29 -31
  11. data/lib/fontist/font_installer.rb +114 -0
  12. data/lib/fontist/fontist_font.rb +3 -49
  13. data/lib/fontist/formula.rb +88 -66
  14. data/lib/fontist/formula_paths.rb +43 -0
  15. data/lib/fontist/helpers.rb +7 -0
  16. data/lib/fontist/import/create_formula.rb +3 -2
  17. data/lib/fontist/import/google/skiplist.yml +3 -0
  18. data/lib/fontist/import/google_check.rb +1 -1
  19. data/lib/fontist/import/google_import.rb +3 -4
  20. data/lib/fontist/import/otfinfo_generate.rb +1 -1
  21. data/lib/fontist/import/recursive_extraction.rb +6 -2
  22. data/lib/fontist/import/sil_import.rb +99 -0
  23. data/lib/fontist/index.rb +72 -0
  24. data/lib/fontist/index_formula.rb +30 -0
  25. data/lib/fontist/manifest/install.rb +4 -9
  26. data/lib/fontist/manifest/locations.rb +28 -20
  27. data/lib/fontist/system_font.rb +20 -60
  28. data/lib/fontist/system_index.rb +92 -0
  29. data/lib/fontist/utils/exe_extractor.rb +1 -1
  30. data/lib/fontist/utils/zip_extractor.rb +1 -1
  31. data/lib/fontist/version.rb +1 -1
  32. metadata +57 -20
  33. data/.github/workflows/macosx.yml +0 -33
  34. data/.github/workflows/ubuntu.yml +0 -30
  35. data/.github/workflows/windows.yml +0 -32
  36. data/bin/check_google +0 -8
  37. data/bin/console +0 -11
  38. data/bin/convert_formulas +0 -8
  39. data/bin/generate_otfinfo +0 -8
  40. data/bin/import_google +0 -8
  41. data/bin/rspec +0 -29
  42. data/bin/setup +0 -7
  43. data/lib/fontist/font_formula.rb +0 -158
  44. data/lib/fontist/formula_template.rb +0 -122
  45. data/lib/fontist/formulas.rb +0 -56
  46. data/lib/fontist/registry.rb +0 -43
@@ -1,16 +1,18 @@
1
1
  module Fontist
2
2
  module Errors
3
- class LicensingError < StandardError; end
4
- class MissingFontError < StandardError; end
5
- class NonSupportedFontError < StandardError; end
6
- class TamperedFileError < StandardError; end
7
- class InvalidResourceError < StandardError; end
8
- class TimeoutError < StandardError; end
9
- class MissingAttributeError < StandardError; end
10
- class UnknownFontTypeError < StandardError; end
11
- class FontNotFoundError < StandardError; end
12
- class BinaryCallError < StandardError; end
13
- class ManifestCouldNotBeReadError < StandardError; end
14
- class ManifestCouldNotBeFoundError < StandardError; end
3
+ class GeneralError < StandardError; end
4
+ class BinaryCallError < GeneralError; end
5
+ class FontNotFoundError < GeneralError; end
6
+ class FormulaIndexNotFoundError < GeneralError; end
7
+ class InvalidResourceError < GeneralError; end
8
+ class LicensingError < GeneralError; end
9
+ class ManifestCouldNotBeFoundError < GeneralError; end
10
+ class ManifestCouldNotBeReadError < GeneralError; end
11
+ class MissingAttributeError < GeneralError; end
12
+ class MissingFontError < GeneralError; end
13
+ class NonSupportedFontError < GeneralError; end
14
+ class TamperedFileError < GeneralError; end
15
+ class TimeoutError < GeneralError; end
16
+ class UnknownFontTypeError < GeneralError; end
15
17
  end
16
18
  end
@@ -1,9 +1,11 @@
1
+ require "fontist/font_installer"
2
+
1
3
  module Fontist
2
4
  class Font
3
5
  def initialize(options = {})
4
- @name = options.fetch(:name, nil)
5
- @confirmation = options.fetch(:confirmation, "no")
6
- @force = options.fetch(:force, false)
6
+ @name = options[:name]
7
+ @confirmation = options[:confirmation] || "no"
8
+ @force = options[:force] || false
7
9
 
8
10
  check_or_create_fontist_path!
9
11
  end
@@ -20,10 +22,6 @@ module Fontist
20
22
  new(name: name, confirmation: confirmation, force: force).install
21
23
  end
22
24
 
23
- def self.try_install(name, confirmation: "no")
24
- new(name: name, confirmation: confirmation).try_install
25
- end
26
-
27
25
  def self.uninstall(name)
28
26
  new(name: name).uninstall
29
27
  end
@@ -37,43 +35,31 @@ module Fontist
37
35
  end
38
36
 
39
37
  def find
40
- find_system_font || downloadable_font || raise(
41
- Fontist::Errors::NonSupportedFontError
42
- )
38
+ find_system_font || downloadable_font || raise_non_supported_font
43
39
  end
44
40
 
45
41
  def install
46
- (find_system_font unless @force) || download_font || raise(
47
- Fontist::Errors::NonSupportedFontError
48
- )
49
- end
50
-
51
- def try_install
52
- download_font
42
+ (find_system_font unless @force) || download_font || raise_non_supported_font
53
43
  end
54
44
 
55
45
  def uninstall
56
- uninstall_font || downloadable_font || raise(
57
- Fontist::Errors::NonSupportedFontError
58
- )
46
+ uninstall_font || downloadable_font || raise_non_supported_font
59
47
  end
60
48
 
61
49
  def status
62
50
  return installed_statuses unless @name
63
51
 
64
- font_status || downloadable_font || raise(
65
- Fontist::Errors::NonSupportedFontError
66
- )
52
+ font_status || downloadable_font || raise_non_supported_font
67
53
  end
68
54
 
69
55
  def list
70
56
  return all_list unless @name
71
57
 
72
- font_list || raise(Fontist::Errors::NonSupportedFontError)
58
+ font_list || raise_non_supported_font
73
59
  end
74
60
 
75
61
  def all
76
- Fontist::Formula.all.to_h.map { |_name, formula| formula.fonts }.flatten
62
+ Fontist::Formula.all.map(&:fonts).flatten
77
63
  end
78
64
 
79
65
  private
@@ -101,7 +87,7 @@ module Fontist
101
87
  end
102
88
 
103
89
  def font_installer(formula)
104
- Object.const_get(formula.installer)
90
+ FontInstaller.new(formula)
105
91
  end
106
92
 
107
93
  def formula
@@ -122,8 +108,7 @@ module Fontist
122
108
  def download_font
123
109
  if formula
124
110
  check_and_confirm_required_license(formula)
125
- paths = font_installer(formula).fetch_font(name,
126
- confirmation: confirmation)
111
+ paths = font_installer(formula).install(confirmation: confirmation)
127
112
 
128
113
  Fontist.ui.say("Fonts installed at:")
129
114
  paths.each do |path|
@@ -136,7 +121,7 @@ module Fontist
136
121
  if formula.license_required && !confirmation.casecmp("yes").zero?
137
122
  @confirmation = show_license_and_ask_for_input(formula.license)
138
123
 
139
- if !confirmation.casecmp("yes").zero?
124
+ unless confirmation&.casecmp?("yes")
140
125
  raise Fontist::Errors::LicensingError.new(
141
126
  "Fontist will not download these fonts unless you accept the terms."
142
127
  )
@@ -186,7 +171,7 @@ module Fontist
186
171
  end
187
172
 
188
173
  def all_formulas
189
- Fontist::Formula.all.to_h.values
174
+ Fontist::Formula.all
190
175
  end
191
176
 
192
177
  def font_status
@@ -213,7 +198,9 @@ module Fontist
213
198
  end
214
199
 
215
200
  def path(style)
216
- font_paths.grep(/#{style.font}/i).first
201
+ font_paths.detect do |path|
202
+ File.basename(path) == style.font
203
+ end
217
204
  end
218
205
 
219
206
  def font_paths
@@ -247,5 +234,16 @@ module Fontist
247
234
  def installed(style)
248
235
  path(style) ? true : false
249
236
  end
237
+
238
+ def raise_non_supported_font
239
+ raise Fontist::Errors::NonSupportedFontError.new(
240
+ "Font '#{@name}' not found locally nor available in the Fontist " \
241
+ "formula repository.\n" \
242
+ "Perhaps it is available at the latest Fontist formula " \
243
+ "repository.\n" \
244
+ "You can update the formula repository using the command " \
245
+ "`fontist update` and try again."
246
+ )
247
+ end
250
248
  end
251
249
  end
@@ -0,0 +1,114 @@
1
+ require "fontist/utils"
2
+
3
+ module Fontist
4
+ class FontInstaller
5
+ include Utils::ZipExtractor
6
+ include Utils::ExeExtractor
7
+ include Utils::MsiExtractor
8
+ include Utils::SevenZipExtractor
9
+
10
+ def initialize(formula)
11
+ @formula = formula
12
+ end
13
+
14
+ def install(confirmation:)
15
+ if @formula.license_required && !"yes".casecmp?(confirmation)
16
+ raise(Fontist::Errors::LicensingError)
17
+ end
18
+
19
+ reinitialize
20
+ install_font
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :formula
26
+
27
+ def reinitialize
28
+ @downloaded = false
29
+ end
30
+
31
+ def install_font
32
+ fonts_paths = run_in_temp_dir { extract }
33
+ fonts_paths.empty? ? nil : fonts_paths
34
+ end
35
+
36
+ def run_in_temp_dir
37
+ Dir.mktmpdir(nil, Dir.tmpdir) do |dir|
38
+ @temp_dir = Pathname.new(dir)
39
+
40
+ result = yield
41
+
42
+ @temp_dir = nil
43
+
44
+ result
45
+ end
46
+ end
47
+
48
+ def extract
49
+ resource = @formula.resources.first
50
+
51
+ [@formula.extract].flatten.each do |operation|
52
+ resource = extract_by_operation(operation, resource)
53
+ end
54
+
55
+ fonts_paths = resource
56
+
57
+ fonts_paths
58
+ end
59
+
60
+ def extract_by_operation(operation, resource)
61
+ method = "#{operation.format}_extract"
62
+ if operation.options
63
+ send(method, resource, **operation.options.to_h)
64
+ else
65
+ send(method, resource)
66
+ end
67
+ end
68
+
69
+ def fonts_path
70
+ Fontist.fonts_path
71
+ end
72
+
73
+ def download_file(source)
74
+ url = source.urls.first
75
+ Fontist.ui.say(%(Downloading font "#{@formula.key}" from #{url}))
76
+
77
+ downloaded_file = Fontist::Utils::Downloader.download(
78
+ url,
79
+ sha: source.sha256,
80
+ file_size: source.file_size,
81
+ progress_bar: true
82
+ )
83
+
84
+ @downloaded = true
85
+ downloaded_file
86
+ end
87
+
88
+ def font_file?(filename)
89
+ source_files.include?(filename)
90
+ end
91
+
92
+ def source_files
93
+ @source_files ||= @formula.fonts.flat_map do |font|
94
+ font.styles.map do |style|
95
+ style.source_font || style.font
96
+ end
97
+ end
98
+ end
99
+
100
+ def target_filename(source_filename)
101
+ target_filenames[source_filename]
102
+ end
103
+
104
+ def target_filenames
105
+ @target_filenames ||= @formula.fonts.flat_map do |font|
106
+ font.styles.map do |style|
107
+ source = style.source_font || style.font
108
+ target = style.font
109
+ [source, target]
110
+ end
111
+ end.to_h
112
+ end
113
+ end
114
+ end
@@ -2,8 +2,6 @@ module Fontist
2
2
  class FontistFont
3
3
  def initialize(font_name:)
4
4
  @font_name = font_name
5
-
6
- check_and_register_font_formulas
7
5
  end
8
6
 
9
7
  def self.find(name)
@@ -11,60 +9,16 @@ module Fontist
11
9
  end
12
10
 
13
11
  def find
14
- return unless @font_name
15
-
16
- filenames = fonts_filenames
17
- return if filenames.empty?
18
-
19
- paths = font_paths.select do |path|
20
- filenames.any? { |f| File.basename(path).casecmp?(f) }
21
- end
12
+ styles = FormulaPaths.new(font_paths).find(@font_name)
13
+ return unless styles
22
14
 
23
- paths.empty? ? nil : paths
15
+ styles.map { |x| x[:path] }
24
16
  end
25
17
 
26
18
  private
27
19
 
28
- def fonts_filenames
29
- fonts.map { |font| font.styles.map(&:font) }.flatten
30
- end
31
-
32
- def fonts
33
- by_key || by_name || []
34
- end
35
-
36
- def by_key
37
- _key, formula = formulas.detect do |key, _value|
38
- key.to_s.casecmp?(@font_name)
39
- end
40
-
41
- return unless formula
42
-
43
- formula.fonts
44
- end
45
-
46
- def by_name
47
- _key, formula = formulas.detect do |_key, value|
48
- value.fonts.map(&:name).map(&:downcase).include?(@font_name.downcase)
49
- end
50
-
51
- return unless formula
52
-
53
- formula.fonts.select do |font|
54
- font.name.casecmp?(@font_name)
55
- end
56
- end
57
-
58
- def formulas
59
- @formulas ||= Fontist::Registry.instance.formulas.to_h
60
- end
61
-
62
20
  def font_paths
63
21
  Dir.glob(Fontist.fonts_path.join("**"))
64
22
  end
65
-
66
- def check_and_register_font_formulas
67
- $check_and_register_font_formulas ||= Fontist::Formulas.register_formulas
68
- end
69
23
  end
70
24
  end
@@ -1,112 +1,134 @@
1
+ require "fontist/index"
2
+ require "fontist/helpers"
3
+ require "git"
4
+
1
5
  module Fontist
2
6
  class Formula
3
- def initialize(options = {})
4
- @font_name = options.fetch(:font_name, nil)
5
- @style_name = options.fetch(:style_name, nil)
6
-
7
- check_and_register_font_formulas
7
+ def self.update_formulas_repo
8
+ if Dir.exist?(Fontist.formulas_repo_path)
9
+ Git.open(Fontist.formulas_repo_path).pull
10
+ else
11
+ Git.clone(Fontist.formulas_repo_url,
12
+ Fontist.formulas_repo_path,
13
+ depth: 1)
14
+ end
8
15
  end
9
16
 
10
17
  def self.all
11
- new.all
18
+ Dir[Fontist.formulas_path.join("**/*.yml").to_s].map do |path|
19
+ Formula.new_from_file(path)
20
+ end
12
21
  end
13
22
 
14
23
  def self.find(font_name)
15
- new(font_name: font_name).find
24
+ Index.from_yaml.load_formulas(font_name).first
16
25
  end
17
26
 
18
- def self.find_fonts(name)
19
- new(font_name: name).find_fonts
27
+ def self.find_fonts(font_name)
28
+ formulas = Index.from_yaml.load_formulas(font_name)
29
+
30
+ formulas.map do |formula|
31
+ formula.fonts.select do |f|
32
+ f.name.casecmp?(font_name)
33
+ end
34
+ end.flatten
20
35
  end
21
36
 
22
- def self.find_styles_with_fonts(font, style)
23
- new(font_name: font, style_name: style).find_styles_with_fonts
37
+ def self.find_styles(font_name, style_name)
38
+ formulas = Index.from_yaml.load_formulas(font_name)
39
+
40
+ formulas.map do |formula|
41
+ formula.fonts.map do |f|
42
+ f.styles.select do |s|
43
+ f.name.casecmp?(font_name) && s.type.casecmp?(style_name)
44
+ end
45
+ end
46
+ end.flatten
24
47
  end
25
48
 
26
- def all
27
- @all ||= Fontist::Registry.instance.formulas
49
+ def self.new_from_file(path)
50
+ data = YAML.load_file(path)
51
+ new(data, path)
28
52
  end
29
53
 
30
- def find
31
- [find_formula].flatten.first
54
+ def initialize(data, path)
55
+ @data = data
56
+ @path = path
32
57
  end
33
58
 
34
- def find_fonts
35
- formulas = [find_formula].flatten
36
- fonts = take_fonts(formulas)
37
- fonts.empty? ? nil : fonts
59
+ def to_index_formula
60
+ IndexFormula.new(path)
38
61
  end
39
62
 
40
- def find_styles_with_fonts
41
- formulas.values.flat_map do |formula|
42
- formula.fonts.flat_map do |f|
43
- selected = f.styles.select do |s|
44
- f.name.casecmp?(font_name) && s.type.casecmp?(style_name)
45
- end
63
+ def path
64
+ @path
65
+ end
46
66
 
47
- selected.map do |s|
48
- { font: f, style: s }
49
- end
50
- end
51
- end
67
+ def key
68
+ @data["key"] || default_key
52
69
  end
53
70
 
54
- private
71
+ def description
72
+ @data["description"]
73
+ end
55
74
 
56
- attr_reader :font_name, :style_name
75
+ def homepage
76
+ @data["homepage"]
77
+ end
57
78
 
58
- def find_formula
59
- find_by_key || find_by_font_name || find_by_font || []
79
+ def copyright
80
+ @data["copyright"]
60
81
  end
61
82
 
62
- def formulas
63
- @formulas ||= all.to_h
83
+ def license_url
84
+ @data["license_url"]
64
85
  end
65
86
 
66
- def take_fonts(formulas)
67
- formulas.map(&:fonts).flatten
87
+ def license
88
+ @data["open_license"] || @data["requires_license_agreement"]
68
89
  end
69
90
 
70
- def find_by_key
71
- matched_formulas = formulas.select do |key, _value|
72
- key.to_s.casecmp?(font_name)
73
- end
91
+ def license_required
92
+ @data["requires_license_agreement"] ? true : false
93
+ end
74
94
 
75
- matched_formulas.empty? ? nil : matched_formulas.values
95
+ def extract
96
+ Helpers.parse_to_object(@data["extract"])
76
97
  end
77
98
 
78
- def find_by_font_name
79
- matched_formulas = formulas.select do |key, value|
80
- !value.fonts.map(&:name).grep(/#{font_name}/i).empty?
81
- end
99
+ def resources
100
+ Helpers.parse_to_object(@data["resources"].values)
101
+ end
82
102
 
83
- matched_formulas.empty? ? nil : matched_formulas.values
103
+ def fonts
104
+ @fonts ||= Helpers.parse_to_object(hash_collection_fonts + hash_fonts)
84
105
  end
85
106
 
86
- # Note
87
- #
88
- # These interface recursively look into every single font styles,
89
- # so ideally try to avoid using it when possible, and that's why
90
- # we've added it as last option in formula finder.
91
- #
92
- def find_by_font
93
- matched_formulas = formulas.select do |key, value|
94
- match_in_font_styles?(value[:fonts])
95
- end
107
+ private
96
108
 
97
- matched_formulas.empty? ? nil : matched_formulas.values
109
+ def default_key
110
+ escaped = Regexp.escape(Fontist.formulas_path.to_s + "/")
111
+ @path.sub(Regexp.new("^" + escaped), "").sub(/\.yml$/, "")
98
112
  end
99
113
 
100
- def match_in_font_styles?(fonts)
101
- styles = fonts.select do |font|
102
- !font.styles.map(&:font).grep(/#{font_name}/i).empty?
103
- end
114
+ def hash_collection_fonts
115
+ return [] unless @data["font_collections"]
116
+
117
+ @data["font_collections"].flat_map do |coll|
118
+ filenames = { "font" => coll["filename"],
119
+ "source_font" => coll["source_filename"] }
104
120
 
105
- styles.empty? ? false : true
121
+ coll["fonts"].map do |font|
122
+ { "name" => font["name"],
123
+ "styles" => font["styles"].map { |s| filenames.merge(s) } }
124
+ end
125
+ end
106
126
  end
107
127
 
108
- def check_and_register_font_formulas
109
- $check_and_register_font_formulas ||= Fontist::Formulas.register_formulas
128
+ def hash_fonts
129
+ return [] unless @data["fonts"]
130
+
131
+ @data["fonts"]
110
132
  end
111
133
  end
112
134
  end