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.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +38 -0
- data/.github/workflows/rspec.yml +58 -0
- data/README.md +48 -4
- data/{bin → exe}/fontist +0 -0
- data/fontist.gemspec +10 -7
- data/lib/fontist.rb +9 -2
- data/lib/fontist/cli.rb +64 -55
- data/lib/fontist/errors.rb +63 -12
- data/lib/fontist/font.rb +33 -64
- data/lib/fontist/font_installer.rb +118 -0
- data/lib/fontist/font_path.rb +29 -0
- data/lib/fontist/fontist_font.rb +3 -49
- data/lib/fontist/formula.rb +101 -35
- data/lib/fontist/formula_paths.rb +43 -0
- data/lib/fontist/helpers.rb +7 -0
- data/lib/fontist/import/create_formula.rb +3 -2
- data/lib/fontist/import/extractors.rb +4 -0
- data/lib/fontist/import/extractors/cpio_extractor.rb +39 -0
- data/lib/fontist/import/extractors/gzip_extractor.rb +27 -0
- data/lib/fontist/import/extractors/rpm_extractor.rb +45 -0
- data/lib/fontist/import/extractors/tar_extractor.rb +47 -0
- data/lib/fontist/import/google/skiplist.yml +3 -0
- data/lib/fontist/import/google_check.rb +1 -1
- data/lib/fontist/import/google_import.rb +3 -4
- data/lib/fontist/import/otfinfo_generate.rb +1 -1
- data/lib/fontist/import/recursive_extraction.rb +26 -8
- data/lib/fontist/import/sil_import.rb +99 -0
- data/lib/fontist/index.rb +11 -0
- data/lib/fontist/indexes/base_index.rb +82 -0
- data/lib/fontist/indexes/filename_index.rb +19 -0
- data/lib/fontist/indexes/font_index.rb +21 -0
- data/lib/fontist/indexes/index_formula.rb +36 -0
- data/lib/fontist/manifest/install.rb +4 -5
- data/lib/fontist/manifest/locations.rb +9 -1
- data/lib/fontist/system_font.rb +32 -62
- data/lib/fontist/system_index.rb +47 -5
- data/lib/fontist/utils.rb +5 -0
- data/lib/fontist/utils/cache.rb +12 -4
- data/lib/fontist/utils/cpio/cpio.rb +199 -0
- data/lib/fontist/utils/cpio_extractor.rb +47 -0
- data/lib/fontist/utils/exe_extractor.rb +1 -1
- data/lib/fontist/utils/gzip_extractor.rb +24 -0
- data/lib/fontist/utils/locking.rb +17 -0
- data/lib/fontist/utils/rpm_extractor.rb +37 -0
- data/lib/fontist/utils/tar_extractor.rb +61 -0
- data/lib/fontist/utils/zip_extractor.rb +1 -1
- data/lib/fontist/version.rb +1 -1
- metadata +74 -26
- data/.github/workflows/macosx.yml +0 -33
- data/.github/workflows/ubuntu.yml +0 -30
- data/.github/workflows/windows.yml +0 -32
- data/bin/check_google +0 -8
- data/bin/console +0 -11
- data/bin/convert_formulas +0 -8
- data/bin/generate_otfinfo +0 -8
- data/bin/import_google +0 -8
- data/bin/rspec +0 -29
- data/bin/setup +0 -7
- data/lib/fontist/font_formula.rb +0 -169
- data/lib/fontist/formula_template.rb +0 -122
- data/lib/fontist/formulas.rb +0 -56
- 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 = /(
|
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
|
-
|
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
|
86
|
-
when
|
89
|
+
case fetch_extension(archive).downcase
|
90
|
+
when "msi"
|
87
91
|
Extractors::OleExtractor.new(archive)
|
88
|
-
when
|
92
|
+
when "cab"
|
89
93
|
Extractors::CabExtractor.new(archive)
|
90
|
-
when
|
94
|
+
when "exe"
|
91
95
|
extractor = Extractors::SevenZipExtractor.new(archive)
|
92
96
|
extractor.try ? extractor : Extractors::CabExtractor.new(archive)
|
93
|
-
|
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
|
-
|
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,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 =
|
14
|
+
paths = find_font_with_name(font, style)
|
15
15
|
return paths unless paths["paths"].empty?
|
16
16
|
|
17
17
|
install_font(font)
|
18
|
-
|
18
|
+
|
19
|
+
find_font_with_name(font, style)
|
19
20
|
end
|
20
21
|
|
21
22
|
def install_font(font)
|
22
|
-
Fontist::Font.
|
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
|
-
|
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
|
data/lib/fontist/system_font.rb
CHANGED
@@ -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(
|
71
|
+
SystemIndex.new(all_paths).find(font, style)
|
78
72
|
end
|
79
73
|
|
80
74
|
def find_by_formulas
|
81
|
-
|
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
|
96
|
-
|
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
|