fontist 1.7.1 → 1.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) 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 +123 -32
  5. data/{bin → exe}/fontist +0 -0
  6. data/fontist.gemspec +4 -2
  7. data/lib/fontist.rb +6 -3
  8. data/lib/fontist/cli.rb +66 -42
  9. data/lib/fontist/errors.rb +62 -12
  10. data/lib/fontist/font.rb +23 -37
  11. data/lib/fontist/font_installer.rb +114 -0
  12. data/lib/fontist/fontist_font.rb +3 -49
  13. data/lib/fontist/formula.rb +89 -63
  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/google/skiplist.yml +3 -0
  18. data/lib/fontist/import/google_check.rb +1 -1
  19. data/lib/fontist/import/google_import.rb +3 -4
  20. data/lib/fontist/import/otfinfo_generate.rb +1 -1
  21. data/lib/fontist/import/recursive_extraction.rb +6 -2
  22. data/lib/fontist/import/sil_import.rb +99 -0
  23. data/lib/fontist/index.rb +72 -0
  24. data/lib/fontist/index_formula.rb +30 -0
  25. data/lib/fontist/manifest/install.rb +4 -9
  26. data/lib/fontist/manifest/locations.rb +28 -20
  27. data/lib/fontist/system_font.rb +32 -91
  28. data/lib/fontist/system_index.rb +47 -5
  29. data/lib/fontist/utils.rb +1 -0
  30. data/lib/fontist/utils/cache.rb +11 -3
  31. data/lib/fontist/utils/exe_extractor.rb +1 -1
  32. data/lib/fontist/utils/locking.rb +17 -0
  33. data/lib/fontist/utils/zip_extractor.rb +1 -1
  34. data/lib/fontist/version.rb +1 -1
  35. metadata +43 -20
  36. data/.github/workflows/macosx.yml +0 -33
  37. data/.github/workflows/ubuntu.yml +0 -30
  38. data/.github/workflows/windows.yml +0 -32
  39. data/bin/check_google +0 -8
  40. data/bin/console +0 -11
  41. data/bin/convert_formulas +0 -8
  42. data/bin/generate_otfinfo +0 -8
  43. data/bin/import_google +0 -8
  44. data/bin/rspec +0 -29
  45. data/bin/setup +0 -7
  46. data/lib/fontist/font_formula.rb +0 -158
  47. data/lib/fontist/formula_template.rb +0 -122
  48. data/lib/fontist/formulas.rb +0 -56
  49. 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
@@ -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
@@ -118,6 +118,9 @@ module Fontist
118
118
  def font_directory?(path, base_path)
119
119
  return true unless @subdir
120
120
 
121
+ # https://bugs.ruby-lang.org/issues/10011
122
+ base_path = Pathname.new(base_path)
123
+
121
124
  relative_path = Pathname.new(path).relative_path_from(base_path).to_s
122
125
  dirname = File.dirname(relative_path)
123
126
  normalized_pattern = @subdir.chomp("/")
@@ -147,7 +150,8 @@ module Fontist
147
150
  end
148
151
 
149
152
  def find_archive(path)
150
- paths = Dir.children(path).map { |file| File.join(path, file) }
153
+ children = Dir.entries(path) - [".", ".."] # ruby 2.4 compat
154
+ paths = children.map { |file| File.join(path, file) }
151
155
  by_subarchive(paths) || by_size(paths)
152
156
  end
153
157
 
@@ -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,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,35 +9,21 @@ module Fontist
8
9
  @user_sources = sources || []
9
10
  end
10
11
 
11
- def self.find(font, sources: [])
12
- new(font: font, sources: sources).find
13
- end
14
-
15
- def self.find_with_name(font, style)
16
- new(font: font, style: style).find_with_name
12
+ def self.font_paths
13
+ system_font_paths + fontist_font_paths
17
14
  end
18
15
 
19
- def find
20
- paths = grep_font_paths(font)
21
- paths = lookup_using_font_name || [] if paths.empty?
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)
22
21
 
23
- paths.empty? ? nil : paths
22
+ Dir.glob(patterns)
24
23
  end
25
24
 
26
- def find_with_name
27
- styles = find_styles
28
- return { full_name: nil, paths: [] } unless styles
29
-
30
- { full_name: styles.first[:full_name],
31
- paths: styles.map { |x| x[:path] } }
32
- end
33
-
34
- private
35
-
36
- attr_reader :font, :style, :user_sources
37
-
38
- def normalize_default_paths
39
- @normalize_default_paths ||= default_sources["paths"].map do |path|
25
+ def self.expand_paths(paths)
26
+ paths.map do |path|
40
27
  require "etc"
41
28
  passwd = Etc.getpwuid
42
29
  username = passwd ? passwd.name : Etc.getlogin
@@ -45,97 +32,51 @@ module Fontist
45
32
  end
46
33
  end
47
34
 
48
- def grep_font_paths(font, style = nil)
49
- pattern = prepare_pattern(font, style)
50
-
51
- paths = font_paths.map { |path| [File.basename(path), path] }.to_h
52
- files = paths.keys
53
- matched = files.grep(pattern)
54
- paths.values_at(*matched).compact
35
+ def self.fontist_font_paths
36
+ Dir.glob(Fontist.fonts_path.join("**"))
55
37
  end
56
38
 
57
- def prepare_pattern(font, style = nil)
58
- style = nil if style&.casecmp?("regular")
59
-
60
- s = [font, style].compact.map { |x| Regexp.quote(x) }
61
- .join(".*")
62
- .gsub("\\ ", "\s?") # space independent
63
-
64
- Regexp.new(s, Regexp::IGNORECASE)
39
+ def self.find(font, sources: [])
40
+ new(font: font, sources: sources).find
65
41
  end
66
42
 
67
- def font_paths
68
- @font_paths ||= Dir.glob((
69
- user_sources +
70
- normalize_default_paths +
71
- [fontist_fonts_path.join("**")]
72
- ).flatten.uniq)
43
+ def self.find_with_name(font, style)
44
+ new(font: font, style: style).find_with_name
73
45
  end
74
46
 
75
- def lookup_using_font_name
76
- font_names = map_name_to_valid_font_names || []
77
- font_paths.grep(/#{font_names.join("|")}/i) unless font_names.empty?
78
- end
47
+ def find
48
+ styles = find_styles
49
+ return unless styles
79
50
 
80
- def fontist_fonts_path
81
- @fontist_fonts_path ||= Fontist.fonts_path
51
+ styles.map { |x| x[:path] }
82
52
  end
83
53
 
84
- def user_os
85
- Fontist::Utils::System.user_os
86
- end
54
+ def find_with_name
55
+ styles = find_styles
56
+ return { full_name: nil, paths: [] } unless styles
87
57
 
88
- def map_name_to_valid_font_names
89
- fonts = Formula.find_fonts(font)
90
- fonts.map { |font| font.styles.map(&:font) }.flatten if fonts
58
+ { full_name: styles.first[:full_name],
59
+ paths: styles.map { |x| x[:path] } }
91
60
  end
92
61
 
93
- def system_path_file
94
- File.open(Fontist.system_file_path)
95
- end
62
+ private
96
63
 
97
- def default_sources
98
- @default_sources ||= YAML.load(system_path_file)["system"][user_os.to_s]
99
- end
64
+ attr_reader :font, :style, :user_sources
100
65
 
101
66
  def find_styles
102
67
  find_by_index || find_by_formulas
103
68
  end
104
69
 
105
70
  def find_by_index
106
- SystemIndex.new(font_paths).find(font, style)
71
+ SystemIndex.new(all_paths).find(font, style)
107
72
  end
108
73
 
109
74
  def find_by_formulas
110
- styles = find_styles_by_formulas(font, style)
111
- return if styles.empty?
112
-
113
- fonts = styles.uniq { |s| s["font"] }.flat_map do |s|
114
- paths = search_font_paths(s["font"])
115
- paths.map do |path|
116
- { full_name: s["full_name"],
117
- path: path }
118
- end
119
- end
120
-
121
- fonts.empty? ? nil : fonts
75
+ FormulaPaths.new(all_paths).find(font, style)
122
76
  end
123
77
 
124
- def find_styles_by_formulas(font, style)
125
- if style
126
- Formula.find_styles(font, style)
127
- else
128
- fonts = Formula.find_fonts(font)
129
- return [] unless fonts
130
-
131
- fonts.flat_map(&:styles)
132
- end
133
- end
134
-
135
- def search_font_paths(filename)
136
- font_paths.select do |path|
137
- File.basename(path) == filename
138
- end
78
+ def all_paths
79
+ @all_paths ||= Dir.glob(user_sources) + self.class.font_paths
139
80
  end
140
81
  end
141
82
  end