fontist 1.7.1 → 1.8.3

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 (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