fontist 1.7.0 → 1.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) 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 +5 -2
  7. data/lib/fontist.rb +10 -3
  8. data/lib/fontist/cli.rb +62 -41
  9. data/lib/fontist/errors.rb +14 -12
  10. data/lib/fontist/font.rb +29 -31
  11. data/lib/fontist/font_installer.rb +114 -0
  12. data/lib/fontist/fontist_font.rb +3 -49
  13. data/lib/fontist/formula.rb +88 -66
  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 +20 -60
  28. data/lib/fontist/system_index.rb +92 -0
  29. data/lib/fontist/utils/exe_extractor.rb +1 -1
  30. data/lib/fontist/utils/zip_extractor.rb +1 -1
  31. data/lib/fontist/version.rb +1 -1
  32. metadata +57 -20
  33. data/.github/workflows/macosx.yml +0 -33
  34. data/.github/workflows/ubuntu.yml +0 -30
  35. data/.github/workflows/windows.yml +0 -32
  36. data/bin/check_google +0 -8
  37. data/bin/console +0 -11
  38. data/bin/convert_formulas +0 -8
  39. data/bin/generate_otfinfo +0 -8
  40. data/bin/import_google +0 -8
  41. data/bin/rspec +0 -29
  42. data/bin/setup +0 -7
  43. data/lib/fontist/font_formula.rb +0 -158
  44. data/lib/fontist/formula_template.rb +0 -122
  45. data/lib/fontist/formulas.rb +0 -56
  46. 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_style(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("Could not find font #{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,3 +1,6 @@
1
+ require_relative "system_index"
2
+ require_relative "formula_paths"
3
+
1
4
  module Fontist
2
5
  class SystemFont
3
6
  def initialize(font:, style: nil, sources: nil)
@@ -10,22 +13,23 @@ module Fontist
10
13
  new(font: font, sources: sources).find
11
14
  end
12
15
 
13
- def self.find_with_style(font, style)
14
- new(font: font, style: style).find_with_style
16
+ def self.find_with_name(font, style)
17
+ new(font: font, style: style).find_with_name
15
18
  end
16
19
 
17
20
  def find
18
- paths = grep_font_paths(font)
19
- paths = lookup_using_font_name || [] if paths.empty?
21
+ styles = find_styles
22
+ return unless styles
20
23
 
21
- paths.empty? ? nil : paths
24
+ styles.map { |x| x[:path] }
22
25
  end
23
26
 
24
- def find_with_style
25
- styles = Formula.find_styles_with_fonts(font, style)
27
+ def find_with_name
28
+ styles = find_styles
29
+ return { full_name: nil, paths: [] } unless styles
26
30
 
27
- { full_name: style_full_name(styles),
28
- paths: style_paths(styles) }
31
+ { full_name: styles.first[:full_name],
32
+ paths: styles.map { |x| x[:path] } }
29
33
  end
30
34
 
31
35
  private
@@ -42,25 +46,6 @@ module Fontist
42
46
  end
43
47
  end
44
48
 
45
- def grep_font_paths(font, style = nil)
46
- pattern = prepare_pattern(font, style)
47
-
48
- paths = font_paths.map { |path| [File.basename(path), path] }.to_h
49
- files = paths.keys
50
- matched = files.grep(pattern)
51
- paths.values_at(*matched).compact
52
- end
53
-
54
- def prepare_pattern(font, style = nil)
55
- style = nil if style&.casecmp?("regular")
56
-
57
- s = [font, style].compact.map { |x| Regexp.quote(x) }
58
- .join(".*")
59
- .gsub("\\ ", "\s?") # space independent
60
-
61
- Regexp.new(s, Regexp::IGNORECASE)
62
- end
63
-
64
49
  def font_paths
65
50
  @font_paths ||= Dir.glob((
66
51
  user_sources +
@@ -69,11 +54,6 @@ module Fontist
69
54
  ).flatten.uniq)
70
55
  end
71
56
 
72
- def lookup_using_font_name
73
- font_names = map_name_to_valid_font_names || []
74
- font_paths.grep(/#{font_names.join("|")}/i) unless font_names.empty?
75
- end
76
-
77
57
  def fontist_fonts_path
78
58
  @fontist_fonts_path ||= Fontist.fonts_path
79
59
  end
@@ -82,44 +62,24 @@ module Fontist
82
62
  Fontist::Utils::System.user_os
83
63
  end
84
64
 
85
- def map_name_to_valid_font_names
86
- fonts = Formula.find_fonts(font)
87
- fonts.map { |font| font.styles.map(&:font) }.flatten if fonts
88
- end
89
-
90
65
  def system_path_file
91
66
  File.open(Fontist.system_file_path)
92
67
  end
93
68
 
94
69
  def default_sources
95
- @default_sources ||= YAML.load(system_path_file)["system"][user_os.to_s]
70
+ @default_sources ||= YAML.safe_load(system_path_file)["system"][user_os.to_s]
96
71
  end
97
72
 
98
- def style_full_name(styles)
99
- return if styles.empty?
100
-
101
- s = styles.first
102
- s[:style]["full_name"] || s[:font]["name"]
73
+ def find_styles
74
+ find_by_index || find_by_formulas
103
75
  end
104
76
 
105
- def style_paths(styles)
106
- filenames = styles.map { |x| x[:style]["font"] }
107
- paths = lookup_using_filenames(filenames)
108
- return paths unless paths.empty?
109
-
110
- grep_font_paths(font, style)
77
+ def find_by_index
78
+ SystemIndex.new(font_paths).find(font, style)
111
79
  end
112
80
 
113
- def lookup_using_filenames(filenames)
114
- filenames.flat_map do |filename|
115
- search_font_paths(filename)
116
- end
117
- end
118
-
119
- def search_font_paths(filename)
120
- font_paths.select do |path|
121
- File.basename(path) == filename
122
- end
81
+ def find_by_formulas
82
+ FormulaPaths.new(font_paths).find(font, style)
123
83
  end
124
84
  end
125
85
  end