fontist 1.7.3 → 1.8.5

Sign up to get free protection for your applications and to get access to all the features.
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