fontist 1.7.2 → 1.8.4

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 (59) 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 +77 -14
  5. data/{bin → exe}/fontist +0 -0
  6. data/fontist.gemspec +10 -7
  7. data/lib/fontist.rb +5 -2
  8. data/lib/fontist/cli.rb +65 -41
  9. data/lib/fontist/errors.rb +63 -12
  10. data/lib/fontist/font.rb +23 -37
  11. data/lib/fontist/font_installer.rb +118 -0
  12. data/lib/fontist/fontist_font.rb +3 -49
  13. data/lib/fontist/formula.rb +101 -35
  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/extractors.rb +4 -0
  18. data/lib/fontist/import/extractors/cpio_extractor.rb +39 -0
  19. data/lib/fontist/import/extractors/gzip_extractor.rb +27 -0
  20. data/lib/fontist/import/extractors/rpm_extractor.rb +45 -0
  21. data/lib/fontist/import/extractors/tar_extractor.rb +47 -0
  22. data/lib/fontist/import/google/skiplist.yml +3 -0
  23. data/lib/fontist/import/google_check.rb +1 -1
  24. data/lib/fontist/import/google_import.rb +3 -4
  25. data/lib/fontist/import/otfinfo_generate.rb +1 -1
  26. data/lib/fontist/import/recursive_extraction.rb +26 -8
  27. data/lib/fontist/import/sil_import.rb +99 -0
  28. data/lib/fontist/index.rb +72 -0
  29. data/lib/fontist/index_formula.rb +30 -0
  30. data/lib/fontist/manifest/install.rb +4 -9
  31. data/lib/fontist/manifest/locations.rb +28 -20
  32. data/lib/fontist/system_font.rb +32 -62
  33. data/lib/fontist/system_index.rb +47 -5
  34. data/lib/fontist/utils.rb +5 -0
  35. data/lib/fontist/utils/cache.rb +12 -4
  36. data/lib/fontist/utils/cpio/cpio.rb +199 -0
  37. data/lib/fontist/utils/cpio_extractor.rb +47 -0
  38. data/lib/fontist/utils/exe_extractor.rb +1 -1
  39. data/lib/fontist/utils/gzip_extractor.rb +24 -0
  40. data/lib/fontist/utils/locking.rb +17 -0
  41. data/lib/fontist/utils/rpm_extractor.rb +37 -0
  42. data/lib/fontist/utils/tar_extractor.rb +61 -0
  43. data/lib/fontist/utils/zip_extractor.rb +1 -1
  44. data/lib/fontist/version.rb +1 -1
  45. metadata +68 -24
  46. data/.github/workflows/macosx.yml +0 -33
  47. data/.github/workflows/ubuntu.yml +0 -30
  48. data/.github/workflows/windows.yml +0 -32
  49. data/bin/check_google +0 -8
  50. data/bin/console +0 -11
  51. data/bin/convert_formulas +0 -8
  52. data/bin/generate_otfinfo +0 -8
  53. data/bin/import_google +0 -8
  54. data/bin/rspec +0 -29
  55. data/bin/setup +0 -7
  56. data/lib/fontist/font_formula.rb +0 -169
  57. data/lib/fontist/formula_template.rb +0 -122
  58. data/lib/fontist/formulas.rb +0 -56
  59. data/lib/fontist/registry.rb +0 -43
@@ -1,16 +1,67 @@
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
+ class UnknownArchiveError < GeneralError; end
18
+
19
+ class FontError < GeneralError
20
+ attr_reader :font, :style
21
+
22
+ def initialize(msg, font = nil, style = nil)
23
+ @font = font
24
+ @style = style
25
+
26
+ super(msg)
27
+ end
28
+
29
+ def name
30
+ messages = []
31
+ messages << "Font name: '#{@font}'"
32
+ messages << "Style: '#{@style}'" if @style
33
+ messages.join("; ")
34
+ end
35
+ end
36
+
37
+ class MissingFontError < FontError
38
+ def initialize(font, style = nil)
39
+ name = prepare_name(font, style)
40
+ msg = "#{name} font is missing, please run `fontist install '#{font}'` to download the font."
41
+
42
+ super(msg, font, style)
43
+ end
44
+
45
+ private
46
+
47
+ def prepare_name(font, style)
48
+ names = []
49
+ names << "'#{font}'"
50
+ names << "'#{style}'" if style
51
+ names.join(" ")
52
+ end
53
+ end
54
+
55
+ class UnsupportedFontError < FontError
56
+ def initialize(font)
57
+ msg = <<~MSG.chomp
58
+ Font '#{font}' not found locally nor available in the Fontist formula repository.
59
+ Perhaps it is available at the latest Fontist formula repository.
60
+ You can update the formula repository using the command `fontist update` and try again.
61
+ MSG
62
+
63
+ super(msg, font)
64
+ end
65
+ end
15
66
  end
16
67
  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,118 @@
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
+ include Utils::RpmExtractor
10
+ include Utils::GzipExtractor
11
+ include Utils::CpioExtractor
12
+ include Utils::TarExtractor
13
+
14
+ def initialize(formula)
15
+ @formula = formula
16
+ end
17
+
18
+ def install(confirmation:)
19
+ if @formula.license_required && !"yes".casecmp?(confirmation)
20
+ raise(Fontist::Errors::LicensingError)
21
+ end
22
+
23
+ reinitialize
24
+ install_font
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :formula
30
+
31
+ def reinitialize
32
+ @downloaded = false
33
+ end
34
+
35
+ def install_font
36
+ fonts_paths = run_in_temp_dir { extract }
37
+ fonts_paths.empty? ? nil : fonts_paths
38
+ end
39
+
40
+ def run_in_temp_dir
41
+ Dir.mktmpdir(nil, Dir.tmpdir) do |dir|
42
+ @temp_dir = Pathname.new(dir)
43
+
44
+ result = yield
45
+
46
+ @temp_dir = nil
47
+
48
+ result
49
+ end
50
+ end
51
+
52
+ def extract
53
+ resource = @formula.resources.first
54
+
55
+ [@formula.extract].flatten.each do |operation|
56
+ resource = extract_by_operation(operation, resource)
57
+ end
58
+
59
+ fonts_paths = resource
60
+
61
+ fonts_paths
62
+ end
63
+
64
+ def extract_by_operation(operation, resource)
65
+ method = "#{operation.format}_extract"
66
+ if operation.options
67
+ send(method, resource, **operation.options.to_h)
68
+ else
69
+ send(method, resource)
70
+ end
71
+ end
72
+
73
+ def fonts_path
74
+ Fontist.fonts_path
75
+ end
76
+
77
+ def download_file(source)
78
+ url = source.urls.first
79
+ Fontist.ui.say(%(Downloading font "#{@formula.key}" from #{url}))
80
+
81
+ downloaded_file = Fontist::Utils::Downloader.download(
82
+ url,
83
+ sha: source.sha256,
84
+ file_size: source.file_size,
85
+ progress_bar: true
86
+ )
87
+
88
+ @downloaded = true
89
+ downloaded_file
90
+ end
91
+
92
+ def font_file?(filename)
93
+ source_files.include?(filename)
94
+ end
95
+
96
+ def source_files
97
+ @source_files ||= @formula.fonts.flat_map do |font|
98
+ font.styles.map do |style|
99
+ style.source_font || style.font
100
+ end
101
+ end
102
+ end
103
+
104
+ def target_filename(source_filename)
105
+ target_filenames[source_filename]
106
+ end
107
+
108
+ def target_filenames
109
+ @target_filenames ||= @formula.fonts.flat_map do |font|
110
+ font.styles.map do |style|
111
+ source = style.source_font || style.font
112
+ target = style.font
113
+ [source, target]
114
+ end
115
+ end.to_h
116
+ end
117
+ end
118
+ 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,50 +1,43 @@
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
20
- end
21
-
22
- def self.find_styles(font, style)
23
- new(font_name: font, style_name: style).find_styles
24
- end
27
+ def self.find_fonts(font_name)
28
+ formulas = Index.from_yaml.load_formulas(font_name)
25
29
 
26
- def all
27
- @all ||= Fontist::Registry.instance.formulas
28
- end
29
-
30
- def find
31
- formulas.values.detect do |formula|
32
- formula.fonts.any? do |f|
33
- f.name.casecmp?(font_name)
34
- end
35
- end
36
- end
37
-
38
- def find_fonts
39
- formulas.values.map do |formula|
30
+ formulas.map do |formula|
40
31
  formula.fonts.select do |f|
41
32
  f.name.casecmp?(font_name)
42
33
  end
43
34
  end.flatten
44
35
  end
45
36
 
46
- def find_styles
47
- formulas.values.map do |formula|
37
+ def self.find_styles(font_name, style_name)
38
+ formulas = Index.from_yaml.load_formulas(font_name)
39
+
40
+ formulas.map do |formula|
48
41
  formula.fonts.map do |f|
49
42
  f.styles.select do |s|
50
43
  f.name.casecmp?(font_name) && s.type.casecmp?(style_name)
@@ -53,16 +46,89 @@ module Fontist
53
46
  end.flatten
54
47
  end
55
48
 
49
+ def self.new_from_file(path)
50
+ data = YAML.load_file(path)
51
+ new(data, path)
52
+ end
53
+
54
+ def initialize(data, path)
55
+ @data = data
56
+ @path = path
57
+ end
58
+
59
+ def to_index_formula
60
+ IndexFormula.new(path)
61
+ end
62
+
63
+ def path
64
+ @path
65
+ end
66
+
67
+ def key
68
+ @data["key"] || default_key
69
+ end
70
+
71
+ def description
72
+ @data["description"]
73
+ end
74
+
75
+ def homepage
76
+ @data["homepage"]
77
+ end
78
+
79
+ def copyright
80
+ @data["copyright"]
81
+ end
82
+
83
+ def license_url
84
+ @data["license_url"]
85
+ end
86
+
87
+ def license
88
+ @data["open_license"] || @data["requires_license_agreement"]
89
+ end
90
+
91
+ def license_required
92
+ @data["requires_license_agreement"] ? true : false
93
+ end
94
+
95
+ def extract
96
+ Helpers.parse_to_object(@data["extract"])
97
+ end
98
+
99
+ def resources
100
+ Helpers.parse_to_object(@data["resources"].values)
101
+ end
102
+
103
+ def fonts
104
+ @fonts ||= Helpers.parse_to_object(hash_collection_fonts + hash_fonts)
105
+ end
106
+
56
107
  private
57
108
 
58
- attr_reader :font_name, :style_name
109
+ def default_key
110
+ escaped = Regexp.escape(Fontist.formulas_path.to_s + "/")
111
+ @path.sub(Regexp.new("^" + escaped), "").sub(/\.yml$/, "")
112
+ end
113
+
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"] }
59
120
 
60
- def formulas
61
- @formulas ||= all.to_h
121
+ coll["fonts"].map do |font|
122
+ { "name" => font["name"],
123
+ "styles" => font["styles"].map { |s| filenames.merge(s) } }
124
+ end
125
+ end
62
126
  end
63
127
 
64
- def check_and_register_font_formulas
65
- $check_and_register_font_formulas ||= Fontist::Formulas.register_formulas
128
+ def hash_fonts
129
+ return [] unless @data["fonts"]
130
+
131
+ @data["fonts"]
66
132
  end
67
133
  end
68
134
  end