fontist 1.7.3 → 1.8.5

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 (63) 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 +48 -4
  5. data/{bin → exe}/fontist +0 -0
  6. data/fontist.gemspec +10 -7
  7. data/lib/fontist.rb +9 -2
  8. data/lib/fontist/cli.rb +64 -55
  9. data/lib/fontist/errors.rb +63 -12
  10. data/lib/fontist/font.rb +33 -64
  11. data/lib/fontist/font_installer.rb +118 -0
  12. data/lib/fontist/font_path.rb +29 -0
  13. data/lib/fontist/fontist_font.rb +3 -49
  14. data/lib/fontist/formula.rb +101 -35
  15. data/lib/fontist/formula_paths.rb +43 -0
  16. data/lib/fontist/helpers.rb +7 -0
  17. data/lib/fontist/import/create_formula.rb +3 -2
  18. data/lib/fontist/import/extractors.rb +4 -0
  19. data/lib/fontist/import/extractors/cpio_extractor.rb +39 -0
  20. data/lib/fontist/import/extractors/gzip_extractor.rb +27 -0
  21. data/lib/fontist/import/extractors/rpm_extractor.rb +45 -0
  22. data/lib/fontist/import/extractors/tar_extractor.rb +47 -0
  23. data/lib/fontist/import/google/skiplist.yml +3 -0
  24. data/lib/fontist/import/google_check.rb +1 -1
  25. data/lib/fontist/import/google_import.rb +3 -4
  26. data/lib/fontist/import/otfinfo_generate.rb +1 -1
  27. data/lib/fontist/import/recursive_extraction.rb +26 -8
  28. data/lib/fontist/import/sil_import.rb +99 -0
  29. data/lib/fontist/index.rb +11 -0
  30. data/lib/fontist/indexes/base_index.rb +82 -0
  31. data/lib/fontist/indexes/filename_index.rb +19 -0
  32. data/lib/fontist/indexes/font_index.rb +21 -0
  33. data/lib/fontist/indexes/index_formula.rb +36 -0
  34. data/lib/fontist/manifest/install.rb +4 -5
  35. data/lib/fontist/manifest/locations.rb +9 -1
  36. data/lib/fontist/system_font.rb +32 -62
  37. data/lib/fontist/system_index.rb +47 -5
  38. data/lib/fontist/utils.rb +5 -0
  39. data/lib/fontist/utils/cache.rb +12 -4
  40. data/lib/fontist/utils/cpio/cpio.rb +199 -0
  41. data/lib/fontist/utils/cpio_extractor.rb +47 -0
  42. data/lib/fontist/utils/exe_extractor.rb +1 -1
  43. data/lib/fontist/utils/gzip_extractor.rb +24 -0
  44. data/lib/fontist/utils/locking.rb +17 -0
  45. data/lib/fontist/utils/rpm_extractor.rb +37 -0
  46. data/lib/fontist/utils/tar_extractor.rb +61 -0
  47. data/lib/fontist/utils/zip_extractor.rb +1 -1
  48. data/lib/fontist/version.rb +1 -1
  49. metadata +74 -26
  50. data/.github/workflows/macosx.yml +0 -33
  51. data/.github/workflows/ubuntu.yml +0 -30
  52. data/.github/workflows/windows.yml +0 -32
  53. data/bin/check_google +0 -8
  54. data/bin/console +0 -11
  55. data/bin/convert_formulas +0 -8
  56. data/bin/generate_otfinfo +0 -8
  57. data/bin/import_google +0 -8
  58. data/bin/rspec +0 -29
  59. data/bin/setup +0 -7
  60. data/lib/fontist/font_formula.rb +0 -169
  61. data/lib/fontist/formula_template.rb +0 -122
  62. data/lib/fontist/formulas.rb +0 -56
  63. data/lib/fontist/registry.rb +0 -43
@@ -7,7 +7,7 @@ module Fontist
7
7
  class RecursiveExtraction
8
8
  FONTS_PATTERN = "**/*.{ttf,otf,ttc}".freeze
9
9
  ARCHIVE_EXTENSIONS = %w[zip msi exe cab].freeze
10
- LICENSE_PATTERN = /(OFL\.txt|UFL\.txt|LICENSE\.txt|COPYING)$/i.freeze
10
+ LICENSE_PATTERN = /(ofl\.txt|ufl\.txt|licenses?\.txt|copying)$/i.freeze
11
11
 
12
12
  def initialize(archive, subarchive: nil, subdir: nil)
13
13
  @archive = archive
@@ -19,7 +19,7 @@ module Fontist
19
19
  end
20
20
 
21
21
  def extension
22
- File.extname(filename(@archive)).sub(/^\./, "")
22
+ fetch_extension(@archive)
23
23
  end
24
24
 
25
25
  def font_files
@@ -44,6 +44,10 @@ module Fontist
44
44
 
45
45
  private
46
46
 
47
+ def fetch_extension(file)
48
+ File.extname(filename(file)).sub(/^\./, "")
49
+ end
50
+
47
51
  def filename(file)
48
52
  if file.respond_to?(:original_filename)
49
53
  file.original_filename
@@ -82,16 +86,26 @@ module Fontist
82
86
 
83
87
  # rubocop:disable Metrics/MethodLength
84
88
  def choose_extractor(archive)
85
- case filename(archive)
86
- when /\.msi$/i
89
+ case fetch_extension(archive).downcase
90
+ when "msi"
87
91
  Extractors::OleExtractor.new(archive)
88
- when /\.cab$/i
92
+ when "cab"
89
93
  Extractors::CabExtractor.new(archive)
90
- when /\.exe$/i
94
+ when "exe"
91
95
  extractor = Extractors::SevenZipExtractor.new(archive)
92
96
  extractor.try ? extractor : Extractors::CabExtractor.new(archive)
93
- else
97
+ when "zip"
94
98
  Extractors::ZipExtractor.new(archive)
99
+ when "rpm"
100
+ Extractors::RpmExtractor.new(archive)
101
+ when "gz"
102
+ Extractors::GzipExtractor.new(archive)
103
+ when "cpio"
104
+ Extractors::CpioExtractor.new(archive)
105
+ when "tar"
106
+ Extractors::TarExtractor.new(archive)
107
+ else
108
+ raise Errors::UnknownArchiveError, "Could not unarchive `#{filename(archive)}`."
95
109
  end
96
110
  end
97
111
  # rubocop:enable Metrics/MethodLength
@@ -118,6 +132,9 @@ module Fontist
118
132
  def font_directory?(path, base_path)
119
133
  return true unless @subdir
120
134
 
135
+ # https://bugs.ruby-lang.org/issues/10011
136
+ base_path = Pathname.new(base_path)
137
+
121
138
  relative_path = Pathname.new(path).relative_path_from(base_path).to_s
122
139
  dirname = File.dirname(relative_path)
123
140
  normalized_pattern = @subdir.chomp("/")
@@ -147,7 +164,8 @@ module Fontist
147
164
  end
148
165
 
149
166
  def find_archive(path)
150
- paths = Dir.children(path).map { |file| File.join(path, file) }
167
+ children = Dir.entries(path) - [".", ".."] # ruby 2.4 compat
168
+ paths = children.map { |file| File.join(path, file) }
151
169
  by_subarchive(paths) || by_size(paths)
152
170
  end
153
171
 
@@ -0,0 +1,99 @@
1
+ require "nokogiri"
2
+ require "fontist/import/create_formula"
3
+
4
+ module Fontist
5
+ module Import
6
+ class SilImport
7
+ ROOT = "https://software.sil.org/fonts/".freeze
8
+
9
+ def call
10
+ links = font_links
11
+ Fontist.ui.success("Found #{links.size} links.")
12
+
13
+ paths = []
14
+ links.each do |link|
15
+ path = create_formula_by_page_link(link)
16
+ paths << path if path
17
+ end
18
+
19
+ Fontist::Index.rebuild
20
+
21
+ Fontist.ui.success("Created #{paths.size} formulas.")
22
+ end
23
+
24
+ private
25
+
26
+ def font_links
27
+ html = URI.parse(ROOT).open.read
28
+ document = Nokogiri::HTML.parse(html)
29
+ document.css("table.products div.title > a")
30
+ end
31
+
32
+ def create_formula_by_page_link(link)
33
+ url = find_archive_url_by_page_link(link)
34
+ return unless url
35
+
36
+ create_formula_by_archive_url(url)
37
+ end
38
+
39
+ def create_formula_by_archive_url(url)
40
+ path = Fontist::Import::CreateFormula.new(url, formula_dir: formula_dir).call
41
+ Fontist.ui.success("Formula has been successfully created: #{path}")
42
+
43
+ path
44
+ end
45
+
46
+ def find_archive_url_by_page_link(link)
47
+ Fontist.ui.print("Searching for an archive of #{link.content}... ")
48
+
49
+ page_uri = URI.join(ROOT, link[:href])
50
+ archive_uri = find_archive_url_by_page_uri(page_uri)
51
+ unless archive_uri
52
+ Fontist.ui.error("NOT FOUND")
53
+ return
54
+ end
55
+
56
+ Fontist.ui.success("DONE")
57
+
58
+ archive_uri.to_s
59
+ end
60
+
61
+ def find_archive_url_by_page_uri(uri)
62
+ response = uri.open
63
+ current_url = response.base_uri
64
+ html = response.read
65
+ document = Nokogiri::HTML.parse(html)
66
+ link = find_archive_link(document)
67
+ return URI.join(current_url, link[:href]) if link
68
+
69
+ page_link = find_download_page(document)
70
+ return unless page_link
71
+
72
+ page_uri = URI.join(current_url, page_link[:href])
73
+ find_archive_url_by_page_uri(page_uri)
74
+ end
75
+
76
+ def find_archive_link(document)
77
+ links = document.css("a.btn-download")
78
+ download_links = links.select { |tag| tag.content.include?("DOWNLOAD CURRENT VERSION") }
79
+ return download_links.first unless download_links.empty?
80
+
81
+ links = document.css("a")
82
+ download_links = links.select { |tag| tag.content.match?(/Download.*\.zip/) }
83
+ download_links.first
84
+ end
85
+
86
+ def find_download_page(document)
87
+ links = document.css("a.btn-download")
88
+ page_links = links.select { |tag| tag.content == "DOWNLOADS" }
89
+ page_links.first
90
+ end
91
+
92
+ def formula_dir
93
+ @formula_dir ||= Fontist.formulas_path.join("sil").tap do |path|
94
+ FileUtils.mkdir_p(path) unless File.exist?(path)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,11 @@
1
+ require_relative "indexes/font_index"
2
+ require_relative "indexes/filename_index"
3
+
4
+ module Fontist
5
+ class Index
6
+ def self.rebuild
7
+ Fontist::Indexes::FontIndex.rebuild
8
+ Fontist::Indexes::FilenameIndex.rebuild
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,82 @@
1
+ require_relative "index_formula"
2
+
3
+ module Fontist
4
+ module Indexes
5
+ class BaseIndex
6
+ def self.from_yaml
7
+ @from_yaml ||= begin
8
+ unless File.exist?(path)
9
+ raise Errors::FormulaIndexNotFoundError.new("Please fetch `#{path}` index with `fontist update`.")
10
+ end
11
+
12
+ data = YAML.load_file(path)
13
+ new(data)
14
+ end
15
+ end
16
+
17
+ def self.path
18
+ raise NotImplementedError, "Please define path of an index"
19
+ end
20
+
21
+ def self.rebuild
22
+ index = new
23
+ index.build
24
+ index.to_yaml
25
+ end
26
+
27
+ def initialize(data = {})
28
+ @index = {}
29
+
30
+ data.each_pair do |key, paths|
31
+ paths.each do |path|
32
+ add_index_formula(key, IndexFormula.new(path))
33
+ end
34
+ end
35
+ end
36
+
37
+ def build
38
+ Formula.all.each do |formula|
39
+ add_formula(formula)
40
+ end
41
+ end
42
+
43
+ def add_formula(_formula)
44
+ raise NotImplementedError, "Please define how to add formula to an index, use #add_index_formula"
45
+ end
46
+
47
+ def add_index_formula(key_raw, index_formula)
48
+ key = normalize_key(key_raw)
49
+ @index[key] ||= []
50
+ @index[key] << index_formula unless @index[key].include?(index_formula)
51
+ end
52
+
53
+ def load_formulas(key)
54
+ index_formulas(key).map(&:to_full)
55
+ end
56
+
57
+ def load_index_formulas(key)
58
+ index_formulas(key)
59
+ end
60
+
61
+ def to_yaml
62
+ File.write(self.class.path, YAML.dump(to_h))
63
+ end
64
+
65
+ def to_h
66
+ @index.map do |key, index_formulas|
67
+ [key, index_formulas.map(&:to_s)]
68
+ end.to_h
69
+ end
70
+
71
+ private
72
+
73
+ def index_formulas(key)
74
+ @index[normalize_key(key)] || []
75
+ end
76
+
77
+ def normalize_key(key)
78
+ key
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "base_index"
2
+
3
+ module Fontist
4
+ module Indexes
5
+ class FilenameIndex < BaseIndex
6
+ def self.path
7
+ Fontist.formula_filename_index_path
8
+ end
9
+
10
+ def add_formula(formula)
11
+ formula.fonts.each do |font|
12
+ font.styles.each do |style|
13
+ add_index_formula(style.font, formula.to_index_formula)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ require_relative "base_index"
2
+
3
+ module Fontist
4
+ module Indexes
5
+ class FontIndex < BaseIndex
6
+ def self.path
7
+ Fontist.formula_index_path
8
+ end
9
+
10
+ def add_formula(formula)
11
+ formula.fonts.each do |font|
12
+ add_index_formula(font.name, formula.to_index_formula)
13
+ end
14
+ end
15
+
16
+ def normalize_key(key)
17
+ key.downcase
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ module Fontist
2
+ module Indexes
3
+ class IndexFormula
4
+ def initialize(path)
5
+ @path = path
6
+ end
7
+
8
+ def name
9
+ normalized.sub(/\.yml$/, "")
10
+ end
11
+
12
+ def to_s
13
+ normalized
14
+ end
15
+
16
+ def to_full
17
+ Formula.new_from_file(full_path)
18
+ end
19
+
20
+ def ==(other)
21
+ to_s == other.to_s
22
+ end
23
+
24
+ private
25
+
26
+ def normalized
27
+ escaped = Regexp.escape(Fontist.formulas_path.to_s + "/")
28
+ @path.sub(Regexp.new("^" + escaped), "")
29
+ end
30
+
31
+ def full_path
32
+ Fontist.formulas_path.join(normalized).to_s
33
+ end
34
+ end
35
+ end
36
+ end
@@ -11,17 +11,16 @@ module Fontist
11
11
  private
12
12
 
13
13
  def file_paths(font, style)
14
- paths = super
14
+ paths = find_font_with_name(font, style)
15
15
  return paths unless paths["paths"].empty?
16
16
 
17
17
  install_font(font)
18
- super
18
+
19
+ find_font_with_name(font, style)
19
20
  end
20
21
 
21
22
  def install_font(font)
22
- Fontist::Font.try_install(font, confirmation: @confirmation)
23
- rescue Fontist::Errors::LicensingError
24
- [] # try to install other fonts
23
+ Fontist::Font.install(font, force: true, confirmation: @confirmation)
25
24
  end
26
25
  end
27
26
  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