fontist 1.7.2 → 1.8.4

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,72 @@
1
+ require_relative "index_formula"
2
+
3
+ module Fontist
4
+ class Index
5
+ def self.from_yaml
6
+ unless File.exist?(Fontist.formula_index_path)
7
+ raise Errors::FormulaIndexNotFoundError.new("Please fetch index with `fontist update`.")
8
+ end
9
+
10
+ data = YAML.load_file(Fontist.formula_index_path)
11
+ new(data)
12
+ end
13
+
14
+ def self.rebuild
15
+ index = new
16
+ index.build
17
+ index.to_yaml
18
+ end
19
+
20
+ def initialize(data = {})
21
+ @index = {}
22
+
23
+ data.each_pair do |font, paths|
24
+ paths.each do |path|
25
+ add_index_formula(font, IndexFormula.new(path))
26
+ end
27
+ end
28
+ end
29
+
30
+ def build
31
+ Formula.all.each do |formula|
32
+ add_formula(formula)
33
+ end
34
+ end
35
+
36
+ def add_formula(formula)
37
+ formula.fonts.each do |font|
38
+ add_index_formula(font.name, formula.to_index_formula)
39
+ end
40
+ end
41
+
42
+ def add_index_formula(font_raw, index_formula)
43
+ font = normalize_font(font_raw)
44
+ @index[font] ||= []
45
+ @index[font] << index_formula unless @index[font].include?(index_formula)
46
+ end
47
+
48
+ def load_formulas(font)
49
+ index_formulas(font).map(&:to_full)
50
+ end
51
+
52
+ def to_yaml
53
+ File.write(Fontist.formula_index_path, YAML.dump(to_h))
54
+ end
55
+
56
+ def to_h
57
+ @index.map do |font, index_formulas|
58
+ [font, index_formulas.map(&:to_s)]
59
+ end.to_h
60
+ end
61
+
62
+ private
63
+
64
+ def index_formulas(font)
65
+ @index[normalize_font(font)] || []
66
+ end
67
+
68
+ def normalize_font(font)
69
+ font.downcase
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,30 @@
1
+ module Fontist
2
+ class IndexFormula
3
+ def initialize(path)
4
+ @path = path
5
+ end
6
+
7
+ def to_s
8
+ normalized
9
+ end
10
+
11
+ def to_full
12
+ Formula.new_from_file(full_path)
13
+ end
14
+
15
+ def ==(other)
16
+ to_s == other.to_s
17
+ end
18
+
19
+ private
20
+
21
+ def normalized
22
+ escaped = Regexp.escape(Fontist.formulas_path.to_s + "/")
23
+ @path.sub(Regexp.new("^" + escaped), "")
24
+ end
25
+
26
+ def full_path
27
+ Fontist.formulas_path.join(normalized).to_s
28
+ end
29
+ end
30
+ end
@@ -8,24 +8,19 @@ module Fontist
8
8
  @confirmation = confirmation
9
9
  end
10
10
 
11
- def self.call(manifest, confirmation: "no")
12
- new(manifest, confirmation: confirmation).call
13
- end
14
-
15
11
  private
16
12
 
17
13
  def file_paths(font, style)
18
- paths = super
14
+ paths = find_font_with_name(font, style)
19
15
  return paths unless paths["paths"].empty?
20
16
 
21
17
  install_font(font)
22
- super
18
+
19
+ find_font_with_name(font, style)
23
20
  end
24
21
 
25
22
  def install_font(font)
26
- Fontist::Font.try_install(font, confirmation: @confirmation)
27
- rescue Fontist::Errors::LicensingError
28
- [] # try to install other fonts
23
+ Fontist::Font.install(font, force: true, confirmation: @confirmation)
29
24
  end
30
25
  end
31
26
  end
@@ -5,8 +5,21 @@ module Fontist
5
5
  @manifest = manifest
6
6
  end
7
7
 
8
- def self.call(manifest)
9
- new(manifest).call
8
+ def self.from_file(file, **keywords)
9
+ raise Fontist::Errors::ManifestCouldNotBeFoundError unless File.exist?(file)
10
+
11
+ manifest = YAML.load_file(file)
12
+ raise Fontist::Errors::ManifestCouldNotBeReadError unless manifest.is_a?(Hash)
13
+
14
+ from_hash(manifest, **keywords)
15
+ end
16
+
17
+ def self.from_hash(manifest, **keywords)
18
+ if keywords.empty?
19
+ new(manifest).call
20
+ else
21
+ new(manifest, **keywords).call
22
+ end
10
23
  end
11
24
 
12
25
  def call
@@ -15,27 +28,14 @@ module Fontist
15
28
 
16
29
  private
17
30
 
18
- def font_names
19
- fonts.keys
20
- end
21
-
22
- def fonts
23
- @fonts ||= begin
24
- unless File.exist?(@manifest)
25
- raise Fontist::Errors::ManifestCouldNotBeFoundError
26
- end
31
+ attr_reader :manifest
27
32
 
28
- fonts = YAML.load_file(@manifest)
29
- unless fonts.is_a?(Hash)
30
- raise Fontist::Errors::ManifestCouldNotBeReadError
31
- end
32
-
33
- fonts
34
- end
33
+ def font_names
34
+ manifest.keys
35
35
  end
36
36
 
37
37
  def font_paths
38
- fonts.map do |font, styles|
38
+ manifest.map do |font, styles|
39
39
  styles_to_ary = [styles].flatten
40
40
  style_paths_map(font, styles_to_ary)
41
41
  end
@@ -53,7 +53,15 @@ module Fontist
53
53
  end
54
54
 
55
55
  def file_paths(font, style)
56
- Fontist::SystemFont.find_with_name(font, style).transform_keys(&:to_s)
56
+ find_font_with_name(font, style).tap do |x|
57
+ if x["paths"].empty?
58
+ raise Errors::MissingFontError.new(font, style)
59
+ end
60
+ end
61
+ end
62
+
63
+ def find_font_with_name(font, style)
64
+ Fontist::SystemFont.find_with_name(font, style).map { |k, v| [k.to_s, v] }.to_h
57
65
  end
58
66
  end
59
67
  end
@@ -1,4 +1,5 @@
1
1
  require_relative "system_index"
2
+ require_relative "formula_paths"
2
3
 
3
4
  module Fontist
4
5
  class SystemFont
@@ -8,6 +9,33 @@ module Fontist
8
9
  @user_sources = sources || []
9
10
  end
10
11
 
12
+ def self.font_paths
13
+ system_font_paths + fontist_font_paths
14
+ end
15
+
16
+ def self.system_font_paths
17
+ config_path = Fontist.system_file_path
18
+ os = Fontist::Utils::System.user_os.to_s
19
+ templates = YAML.load_file(config_path)["system"][os]["paths"]
20
+ patterns = expand_paths(templates)
21
+
22
+ Dir.glob(patterns)
23
+ end
24
+
25
+ def self.expand_paths(paths)
26
+ paths.map do |path|
27
+ require "etc"
28
+ passwd = Etc.getpwuid
29
+ username = passwd ? passwd.name : Etc.getlogin
30
+
31
+ username ? path.gsub("{username}", username) : path
32
+ end
33
+ end
34
+
35
+ def self.fontist_font_paths
36
+ Dir.glob(Fontist.fonts_path.join("**"))
37
+ end
38
+
11
39
  def self.find(font, sources: [])
12
40
  new(font: font, sources: sources).find
13
41
  end
@@ -35,78 +63,20 @@ module Fontist
35
63
 
36
64
  attr_reader :font, :style, :user_sources
37
65
 
38
- def normalize_default_paths
39
- @normalize_default_paths ||= default_sources["paths"].map do |path|
40
- require "etc"
41
- passwd = Etc.getpwuid
42
- username = passwd ? passwd.name : Etc.getlogin
43
-
44
- username ? path.gsub("{username}", username) : path
45
- end
46
- end
47
-
48
- def font_paths
49
- @font_paths ||= Dir.glob((
50
- user_sources +
51
- normalize_default_paths +
52
- [fontist_fonts_path.join("**")]
53
- ).flatten.uniq)
54
- end
55
-
56
- def fontist_fonts_path
57
- @fontist_fonts_path ||= Fontist.fonts_path
58
- end
59
-
60
- def user_os
61
- Fontist::Utils::System.user_os
62
- end
63
-
64
- def system_path_file
65
- File.open(Fontist.system_file_path)
66
- end
67
-
68
- def default_sources
69
- @default_sources ||= YAML.safe_load(system_path_file)["system"][user_os.to_s]
70
- end
71
-
72
66
  def find_styles
73
67
  find_by_index || find_by_formulas
74
68
  end
75
69
 
76
70
  def find_by_index
77
- SystemIndex.new(font_paths).find(font, style)
71
+ SystemIndex.new(all_paths).find(font, style)
78
72
  end
79
73
 
80
74
  def find_by_formulas
81
- styles = find_styles_by_formulas(font, style)
82
- return if styles.empty?
83
-
84
- fonts = styles.uniq { |s| s["font"] }.flat_map do |s|
85
- paths = search_font_paths(s["font"])
86
- paths.map do |path|
87
- { full_name: s["full_name"],
88
- path: path }
89
- end
90
- end
91
-
92
- fonts.empty? ? nil : fonts
75
+ FormulaPaths.new(all_paths).find(font, style)
93
76
  end
94
77
 
95
- def find_styles_by_formulas(font, style)
96
- if style
97
- Formula.find_styles(font, style)
98
- else
99
- fonts = Formula.find_fonts(font)
100
- return [] unless fonts
101
-
102
- fonts.flat_map(&:styles)
103
- end
104
- end
105
-
106
- def search_font_paths(filename)
107
- font_paths.select do |path|
108
- File.basename(path) == filename
109
- end
78
+ def all_paths
79
+ @all_paths ||= Dir.glob(user_sources) + self.class.font_paths
110
80
  end
111
81
  end
112
82
  end
@@ -2,8 +2,18 @@ require "ttfunk"
2
2
 
3
3
  module Fontist
4
4
  class SystemIndex
5
+ include Utils::Locking
6
+
5
7
  attr_reader :font_paths
6
8
 
9
+ def self.find(font, style)
10
+ new(SystemFont.font_paths).find(font, style)
11
+ end
12
+
13
+ def self.rebuild
14
+ new(SystemFont.font_paths).rebuild
15
+ end
16
+
7
17
  def initialize(font_paths)
8
18
  @font_paths = font_paths
9
19
  end
@@ -17,6 +27,10 @@ module Fontist
17
27
  fonts.empty? ? nil : fonts
18
28
  end
19
29
 
30
+ def rebuild
31
+ build_system_index
32
+ end
33
+
20
34
  private
21
35
 
22
36
  def system_index
@@ -24,28 +38,56 @@ module Fontist
24
38
  end
25
39
 
26
40
  def build_system_index
41
+ lock(lock_path) do
42
+ do_build_system_index
43
+ end
44
+ end
45
+
46
+ def lock_path
47
+ Fontist.system_index_path.to_s + ".lock"
48
+ end
49
+
50
+ def do_build_system_index
27
51
  previous_index = load_system_index
28
52
  updated_index = detect_paths(font_paths, previous_index)
29
53
  updated_index.tap do |index|
30
- save_index(index)
54
+ save_index(index) if changed?(updated_index, previous_index)
31
55
  end
32
56
  end
33
57
 
58
+ def changed?(this, other)
59
+ this.map { |x| x[:path] }.uniq.sort != other.map { |x| x[:path] }.uniq.sort
60
+ end
61
+
34
62
  def load_system_index
35
63
  index = File.exist?(Fontist.system_index_path) ? YAML.load_file(Fontist.system_index_path) : []
36
- index.group_by { |x| x[:path] }
64
+
65
+ index.each do |item|
66
+ missing_keys = %i[path full_name family_name type] - item.keys
67
+ unless missing_keys.empty?
68
+ raise(Errors::FontIndexCorrupted, <<~MSG.chomp)
69
+ Font index is corrupted.
70
+ Item #{item.inspect} misses required attributes: #{missing_keys.join(', ')}.
71
+ You can remove the index file (#{Fontist.system_index_path}) and try again.
72
+ MSG
73
+ end
74
+ end
75
+
76
+ index
37
77
  end
38
78
 
39
- def detect_paths(paths, indexed)
79
+ def detect_paths(paths, index)
80
+ by_path = index.group_by { |x| x[:path] }
81
+
40
82
  paths.flat_map do |path|
41
- next indexed[path] if indexed[path]
83
+ next by_path[path] if by_path[path]
42
84
 
43
85
  detect_fonts(path)
44
86
  end
45
87
  end
46
88
 
47
89
  def detect_fonts(path)
48
- case File.extname(path).delete_prefix(".").downcase
90
+ case File.extname(path).gsub(/^\./, "").downcase
49
91
  when "ttf", "otf"
50
92
  detect_file_font(path)
51
93
  when "ttc"
@@ -1,4 +1,5 @@
1
1
  require "fontist/utils/ui"
2
+ require "fontist/utils/locking"
2
3
  require "fontist/utils/system"
3
4
  require "fontist/utils/dsl"
4
5
  require "fontist/utils/dsl/font"
@@ -8,6 +9,10 @@ require "fontist/utils/zip_extractor"
8
9
  require "fontist/utils/exe_extractor"
9
10
  require "fontist/utils/msi_extractor"
10
11
  require "fontist/utils/seven_zip_extractor"
12
+ require "fontist/utils/rpm_extractor"
13
+ require "fontist/utils/gzip_extractor"
14
+ require "fontist/utils/cpio_extractor"
15
+ require "fontist/utils/tar_extractor"
11
16
 
12
17
  module Fontist
13
18
  module Utils
@@ -1,6 +1,8 @@
1
1
  module Fontist
2
2
  module Utils
3
3
  class Cache
4
+ include Locking
5
+
4
6
  def fetch(key, bar: nil)
5
7
  map = load_cache
6
8
  if cache_exist?(map[key])
@@ -26,7 +28,7 @@ module Fontist
26
28
  end
27
29
 
28
30
  def downloaded_file(path)
29
- File.new(downloaded_path(path))
31
+ File.new(downloaded_path(path), "rb")
30
32
  end
31
33
 
32
34
  def cache_exist?(path)
@@ -48,13 +50,19 @@ module Fontist
48
50
  def save_cache(generated_file, key)
49
51
  path = move_to_downloads(generated_file)
50
52
 
51
- map = load_cache
52
- map[key] = path
53
- File.write(cache_map_path, YAML.dump(map))
53
+ lock(lock_path) do
54
+ map = load_cache
55
+ map[key] = path
56
+ File.write(cache_map_path, YAML.dump(map))
57
+ end
54
58
 
55
59
  path
56
60
  end
57
61
 
62
+ def lock_path
63
+ cache_map_path.to_s + ".lock"
64
+ end
65
+
58
66
  def move_to_downloads(source)
59
67
  create_downloads_directory
60
68
  path = generate_file_path(source)