fontist 1.7.1 → 1.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) 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 +4 -2
  7. data/lib/fontist.rb +6 -3
  8. data/lib/fontist/cli.rb +66 -42
  9. data/lib/fontist/errors.rb +62 -12
  10. data/lib/fontist/font.rb +23 -37
  11. data/lib/fontist/font_installer.rb +114 -0
  12. data/lib/fontist/fontist_font.rb +3 -49
  13. data/lib/fontist/formula.rb +89 -63
  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 +32 -91
  28. data/lib/fontist/system_index.rb +47 -5
  29. data/lib/fontist/utils.rb +1 -0
  30. data/lib/fontist/utils/cache.rb +11 -3
  31. data/lib/fontist/utils/exe_extractor.rb +1 -1
  32. data/lib/fontist/utils/locking.rb +17 -0
  33. data/lib/fontist/utils/zip_extractor.rb +1 -1
  34. data/lib/fontist/version.rb +1 -1
  35. metadata +43 -20
  36. data/.github/workflows/macosx.yml +0 -33
  37. data/.github/workflows/ubuntu.yml +0 -30
  38. data/.github/workflows/windows.yml +0 -32
  39. data/bin/check_google +0 -8
  40. data/bin/console +0 -11
  41. data/bin/convert_formulas +0 -8
  42. data/bin/generate_otfinfo +0 -8
  43. data/bin/import_google +0 -8
  44. data/bin/rspec +0 -29
  45. data/bin/setup +0 -7
  46. data/lib/fontist/font_formula.rb +0 -158
  47. data/lib/fontist/formula_template.rb +0 -122
  48. data/lib/fontist/formulas.rb +0 -56
  49. data/lib/fontist/registry.rb +0 -43
@@ -1,16 +1,66 @@
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
+
5
+ class BinaryCallError < GeneralError; end
6
+ class FontIndexCorrupted < GeneralError; end
7
+ class FontNotFoundError < GeneralError; end
8
+ class FormulaIndexNotFoundError < GeneralError; end
9
+ class InvalidResourceError < GeneralError; end
10
+ class LicensingError < GeneralError; end
11
+ class ManifestCouldNotBeFoundError < GeneralError; end
12
+ class ManifestCouldNotBeReadError < GeneralError; end
13
+ class MissingAttributeError < GeneralError; end
14
+ class TamperedFileError < GeneralError; end
15
+ class TimeoutError < GeneralError; end
16
+ class UnknownFontTypeError < GeneralError; end
17
+
18
+ class FontError < GeneralError
19
+ attr_reader :font, :style
20
+
21
+ def initialize(msg, font = nil, style = nil)
22
+ @font = font
23
+ @style = style
24
+
25
+ super(msg)
26
+ end
27
+
28
+ def name
29
+ messages = []
30
+ messages << "Font name: '#{@font}'"
31
+ messages << "Style: '#{@style}'" if @style
32
+ messages.join("; ")
33
+ end
34
+ end
35
+
36
+ class MissingFontError < FontError
37
+ def initialize(font, style = nil)
38
+ name = prepare_name(font, style)
39
+ msg = "#{name} font is missing, please run `fontist install '#{font}'` to download the font."
40
+
41
+ super(msg, font, style)
42
+ end
43
+
44
+ private
45
+
46
+ def prepare_name(font, style)
47
+ names = []
48
+ names << "'#{font}'"
49
+ names << "'#{style}'" if style
50
+ names.join(" ")
51
+ end
52
+ end
53
+
54
+ class UnsupportedFontError < FontError
55
+ def initialize(font)
56
+ msg = <<~MSG.chomp
57
+ Font '#{font}' not found locally nor available in the Fontist formula repository.
58
+ Perhaps it is available at the latest Fontist formula repository.
59
+ You can update the formula repository using the command `fontist update` and try again.
60
+ MSG
61
+
62
+ super(msg, font)
63
+ end
64
+ end
15
65
  end
16
66
  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
@@ -110,20 +96,14 @@ module Fontist
110
96
 
111
97
  def downloadable_font
112
98
  if formula
113
- raise(
114
- Fontist::Errors::MissingFontError,
115
- "#{name} fonts are missing, please run " \
116
- "`fontist install '#{name}'` to " \
117
- "download the font."
118
- )
99
+ raise Fontist::Errors::MissingFontError.new(name)
119
100
  end
120
101
  end
121
102
 
122
103
  def download_font
123
104
  if formula
124
105
  check_and_confirm_required_license(formula)
125
- paths = font_installer(formula).fetch_font(name,
126
- confirmation: confirmation)
106
+ paths = font_installer(formula).install(confirmation: confirmation)
127
107
 
128
108
  Fontist.ui.say("Fonts installed at:")
129
109
  paths.each do |path|
@@ -136,7 +116,7 @@ module Fontist
136
116
  if formula.license_required && !confirmation.casecmp("yes").zero?
137
117
  @confirmation = show_license_and_ask_for_input(formula.license)
138
118
 
139
- if !confirmation.casecmp("yes").zero?
119
+ unless confirmation&.casecmp?("yes")
140
120
  raise Fontist::Errors::LicensingError.new(
141
121
  "Fontist will not download these fonts unless you accept the terms."
142
122
  )
@@ -186,7 +166,7 @@ module Fontist
186
166
  end
187
167
 
188
168
  def all_formulas
189
- Fontist::Formula.all.to_h.values
169
+ Fontist::Formula.all
190
170
  end
191
171
 
192
172
  def font_status
@@ -213,7 +193,9 @@ module Fontist
213
193
  end
214
194
 
215
195
  def path(style)
216
- font_paths.grep(/#{style.font}/i).first
196
+ font_paths.detect do |path|
197
+ File.basename(path) == style.font
198
+ end
217
199
  end
218
200
 
219
201
  def font_paths
@@ -247,5 +229,9 @@ module Fontist
247
229
  def installed(style)
248
230
  path(style) ? true : false
249
231
  end
232
+
233
+ def raise_non_supported_font
234
+ raise Fontist::Errors::UnsupportedFontError.new(@name)
235
+ end
250
236
  end
251
237
  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,108 +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(font, style)
23
- new(font_name: font, style_name: style).find_styles
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
41
- formulas.values.flat_map do |formula|
42
- formula.fonts.flat_map do |f|
43
- f.styles.select do |s|
44
- f.name.casecmp?(font_name) && s.type.casecmp?(style_name)
45
- end
46
- end
47
- end
63
+ def path
64
+ @path
48
65
  end
49
66
 
50
- private
67
+ def key
68
+ @data["key"] || default_key
69
+ end
51
70
 
52
- attr_reader :font_name, :style_name
71
+ def description
72
+ @data["description"]
73
+ end
53
74
 
54
- def find_formula
55
- find_by_key || find_by_font_name || find_by_font || []
75
+ def homepage
76
+ @data["homepage"]
56
77
  end
57
78
 
58
- def formulas
59
- @formulas ||= all.to_h
79
+ def copyright
80
+ @data["copyright"]
60
81
  end
61
82
 
62
- def take_fonts(formulas)
63
- formulas.map(&:fonts).flatten
83
+ def license_url
84
+ @data["license_url"]
64
85
  end
65
86
 
66
- def find_by_key
67
- matched_formulas = formulas.select do |key, _value|
68
- key.to_s.casecmp?(font_name)
69
- end
87
+ def license
88
+ @data["open_license"] || @data["requires_license_agreement"]
89
+ end
70
90
 
71
- matched_formulas.empty? ? nil : matched_formulas.values
91
+ def license_required
92
+ @data["requires_license_agreement"] ? true : false
72
93
  end
73
94
 
74
- def find_by_font_name
75
- matched_formulas = formulas.select do |key, value|
76
- !value.fonts.map(&:name).grep(/#{font_name}/i).empty?
77
- end
95
+ def extract
96
+ Helpers.parse_to_object(@data["extract"])
97
+ end
78
98
 
79
- matched_formulas.empty? ? nil : matched_formulas.values
99
+ def resources
100
+ Helpers.parse_to_object(@data["resources"].values)
80
101
  end
81
102
 
82
- # Note
83
- #
84
- # These interface recursively look into every single font styles,
85
- # so ideally try to avoid using it when possible, and that's why
86
- # we've added it as last option in formula finder.
87
- #
88
- def find_by_font
89
- matched_formulas = formulas.select do |key, value|
90
- match_in_font_styles?(value[:fonts])
91
- end
103
+ def fonts
104
+ @fonts ||= Helpers.parse_to_object(hash_collection_fonts + hash_fonts)
105
+ end
106
+
107
+ private
92
108
 
93
- 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$/, "")
94
112
  end
95
113
 
96
- def match_in_font_styles?(fonts)
97
- styles = fonts.select do |font|
98
- !font.styles.map(&:font).grep(/#{font_name}/i).empty?
99
- end
114
+ def hash_collection_fonts
115
+ return [] unless @data["font_collections"]
100
116
 
101
- styles.empty? ? false : true
117
+ @data["font_collections"].flat_map do |coll|
118
+ filenames = { "font" => coll["filename"],
119
+ "source_font" => coll["source_filename"] }
120
+
121
+ coll["fonts"].map do |font|
122
+ { "name" => font["name"],
123
+ "styles" => font["styles"].map { |s| filenames.merge(s) } }
124
+ end
125
+ end
102
126
  end
103
127
 
104
- def check_and_register_font_formulas
105
- $check_and_register_font_formulas ||= Fontist::Formulas.register_formulas
128
+ def hash_fonts
129
+ return [] unless @data["fonts"]
130
+
131
+ @data["fonts"]
106
132
  end
107
133
  end
108
134
  end