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
@@ -0,0 +1,43 @@
1
+ module Fontist
2
+ class FormulaPaths
3
+ attr_reader :font_paths
4
+
5
+ def initialize(font_paths)
6
+ @font_paths = font_paths
7
+ end
8
+
9
+ def find(font, style = nil)
10
+ styles = find_styles_by_formulas(font, style)
11
+ return if styles.empty?
12
+
13
+ fonts = styles.uniq { |s| s["font"] }.flat_map do |s|
14
+ paths = search_font_paths(s["font"])
15
+ paths.map do |path|
16
+ { full_name: s["full_name"],
17
+ path: path }
18
+ end
19
+ end
20
+
21
+ fonts.empty? ? nil : fonts
22
+ end
23
+
24
+ private
25
+
26
+ def find_styles_by_formulas(font, style)
27
+ if style
28
+ Formula.find_styles(font, style)
29
+ else
30
+ fonts = Formula.find_fonts(font)
31
+ return [] unless fonts
32
+
33
+ fonts.flat_map(&:styles)
34
+ end
35
+ end
36
+
37
+ def search_font_paths(filename)
38
+ font_paths.select do |path|
39
+ File.basename(path) == filename
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ module Fontist
2
+ module Helpers
3
+ def self.parse_to_object(data)
4
+ JSON.parse(data.to_json, object_class: OpenStruct)
5
+ end
6
+ end
7
+ end
@@ -50,9 +50,10 @@ module Fontist
50
50
 
51
51
  def save(hash)
52
52
  filename = Import.name_to_filename(hash[:name])
53
+ path = @options[:formula_dir] ? File.join(@options[:formula_dir], filename) : filename
53
54
  yaml = YAML.dump(Helpers::HashHelper.stringify_keys(hash))
54
- File.write(filename, yaml)
55
- filename
55
+ File.write(path, yaml)
56
+ path
56
57
  end
57
58
  end
58
59
  end
@@ -3,3 +3,7 @@ require_relative "extractors/zip_extractor"
3
3
  require_relative "extractors/ole_extractor"
4
4
  require_relative "extractors/seven_zip_extractor"
5
5
  require_relative "extractors/cab_extractor"
6
+ require_relative "extractors/rpm_extractor"
7
+ require_relative "extractors/gzip_extractor"
8
+ require_relative "extractors/cpio_extractor"
9
+ require_relative "extractors/tar_extractor"
@@ -0,0 +1,39 @@
1
+ module Fontist
2
+ module Import
3
+ module Extractors
4
+ class CpioExtractor < Extractor
5
+ def extract
6
+ dir = Dir.mktmpdir
7
+ extract_cpio(@archive, dir)
8
+ dir
9
+ end
10
+
11
+ def format
12
+ "cpio"
13
+ end
14
+
15
+ private
16
+
17
+ def extract_cpio(archive, dir)
18
+ archive_file = File.open(archive, "rb")
19
+
20
+ reader_class.new(archive_file).each do |entry, file|
21
+ path = File.join(dir, entry.name)
22
+ if entry.directory?
23
+ FileUtils.mkdir_p(path)
24
+ else
25
+ File.write(path, file.read)
26
+ end
27
+ end
28
+ end
29
+
30
+ def reader_class
31
+ @reader_class ||= begin
32
+ require "fontist/utils/cpio/cpio"
33
+ CPIO::ASCIIReader
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ module Fontist
2
+ module Import
3
+ module Extractors
4
+ class GzipExtractor < Extractor
5
+ def extract
6
+ dir = Dir.mktmpdir
7
+ extract_gzip(@archive, dir)
8
+ dir
9
+ end
10
+
11
+ def format
12
+ "gzip"
13
+ end
14
+
15
+ private
16
+
17
+ def extract_gzip(archive, dir)
18
+ Zlib::GzipReader.open(archive) do |gz|
19
+ basename = File.basename(archive, ".*")
20
+ path = File.join(dir, basename)
21
+ File.write(path, gz.read)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ module Fontist
2
+ module Import
3
+ module Extractors
4
+ class RpmExtractor < Extractor
5
+ def extract
6
+ dir = Dir.mktmpdir
7
+ extract_rpm(@archive, dir)
8
+ dir
9
+ end
10
+
11
+ def format
12
+ "rpm"
13
+ end
14
+
15
+ private
16
+
17
+ def extract_rpm(archive, dir)
18
+ file = File.open(archive, "rb")
19
+ rpm = rpm_class.new(file)
20
+ content = rpm.payload.read
21
+ path = target_path(archive, rpm.tags, dir)
22
+
23
+ File.write(path, content)
24
+ ensure
25
+ file.close
26
+ end
27
+
28
+ def rpm_class
29
+ @rpm_class ||= begin
30
+ require "arr-pm"
31
+ RPM::File
32
+ end
33
+ end
34
+
35
+ def target_path(archive, tags, dir)
36
+ archive_format = tags[:payloadformat]
37
+ compression_format = tags[:payloadcompressor] == "gzip" ? "gz" : tags[:payloadcompressor]
38
+ basename = File.basename(archive, ".*")
39
+ filename = basename + "." + archive_format + "." + compression_format
40
+ File.join(dir, filename)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,47 @@
1
+ module Fontist
2
+ module Import
3
+ module Extractors
4
+ class TarExtractor < Extractor
5
+ def extract
6
+ dir = Dir.mktmpdir
7
+ extract_tar(@archive, dir)
8
+ dir
9
+ end
10
+
11
+ def format
12
+ "tar"
13
+ end
14
+
15
+ private
16
+
17
+ def extract_tar(archive, dir)
18
+ archive_file = File.open(archive, "rb")
19
+ reader_class.new(archive_file) do |tar|
20
+ tar.each do |tarfile|
21
+ save_tar_file(tarfile, dir)
22
+ end
23
+ end
24
+ end
25
+
26
+ def reader_class
27
+ @reader_class ||= begin
28
+ require "rubygems/package"
29
+ Gem::Package::TarReader
30
+ end
31
+ end
32
+
33
+ def save_tar_file(file, dir)
34
+ path = File.join(dir, file.full_name)
35
+
36
+ if file.directory?
37
+ FileUtils.mkdir_p(path)
38
+ else
39
+ File.open(path, "wb") do |f|
40
+ f.print(file.read)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -6,3 +6,6 @@
6
6
  - Open Sans
7
7
  - Work Sans
8
8
  - Fira Code
9
+ - Andika
10
+ - Harmattan
11
+ - Padauk
@@ -12,7 +12,7 @@ module Fontist
12
12
  private
13
13
 
14
14
  def fetch_formulas
15
- Formulas.fetch_formulas
15
+ Formula.update_formulas_repo
16
16
  end
17
17
 
18
18
  def new_fonts
@@ -156,10 +156,9 @@ module Fontist
156
156
  h.merge!(family_name: style.family_name,
157
157
  type: style.style,
158
158
  full_name: style.full_name)
159
- h.merge!(style.to_h.slice(:post_script_name,
160
- :version,
161
- :description,
162
- :copyright).compact)
159
+ h.merge!(style.to_h.select do |k, _|
160
+ %i(post_script_name version description copyright).include?(k)
161
+ end.compact)
163
162
  h.merge!(font: fix_variable_filename(style.filename))
164
163
  end
165
164
  end
@@ -25,7 +25,7 @@ module Fontist
25
25
  def font_paths(font)
26
26
  formula = Fontist::Formula.find(font)
27
27
  font_formula = Object.const_get(formula.installer)
28
- font_formula.fetch_font(nil, confirmation: "yes")
28
+ font_formula.install(confirmation: "yes")
29
29
  end
30
30
 
31
31
  def generate_styles(paths)
@@ -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