fontist 1.7.0 → 1.8.2

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