fontist 1.10.0 → 1.11.3

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.
data/exe/fontist CHANGED
@@ -3,5 +3,27 @@
3
3
  require "fontist"
4
4
  require "fontist/cli"
5
5
 
6
- status_code = Fontist::CLI.start(ARGV)
7
- exit status_code.is_a?(Integer) ? status_code : 1
6
+ fontist_cli = proc {
7
+ status_code = Fontist::CLI.start(ARGV)
8
+ exit status_code.is_a?(Integer) ? status_code : 1
9
+ }
10
+
11
+ if ENV["SOCKS_PROXY"]
12
+ require "socksify"
13
+ require "uri"
14
+ begin
15
+ proxy = URI.parse(ENV["SOCKS_PROXY"])
16
+ if proxy.userinfo
17
+ user, pass = proxy.userinfo.split(":")
18
+ TCPSocket::socks_username = user
19
+ TCPSocket::socks_password = pass
20
+ end
21
+ Socksify::proxy(proxy.host, proxy.port, &fontist_cli)
22
+ rescue URI::InvalidURIError
23
+ warn "Value of ENV.SOCKS_PROXY=#{ENV['SOCKS_PROXY']} is invalid! Droping it"
24
+ ENV.delete("SOCKS_PROXY")
25
+ fontist_cli.call
26
+ end
27
+ else
28
+ fontist_cli.call
29
+ end
data/fontist.gemspec CHANGED
@@ -28,20 +28,22 @@ Gem::Specification.new do |spec|
28
28
  spec.test_files = `git ls-files -- {spec}/*`.split("\n")
29
29
 
30
30
  spec.add_runtime_dependency "down", "~> 5.0"
31
+ spec.add_runtime_dependency "extract_ttc", "~> 0.1"
31
32
  spec.add_runtime_dependency "thor", "~> 1.0.1"
32
33
  spec.add_runtime_dependency "git", "~> 1.0"
33
34
  spec.add_runtime_dependency "ttfunk", "~> 1.6"
34
35
  spec.add_runtime_dependency "excavate", "~> 0.1"
35
36
 
36
- spec.add_development_dependency "extract_ttc", "~> 0.1"
37
37
  spec.add_development_dependency "pry"
38
38
  spec.add_development_dependency "bundler", "~> 2.0"
39
39
  spec.add_development_dependency "gem-release"
40
40
  spec.add_development_dependency "nokogiri", "~> 1.0"
41
41
  spec.add_development_dependency "rake", "~> 13"
42
42
  spec.add_development_dependency "rspec", "~> 3.0"
43
+ spec.add_development_dependency "rspec-benchmark", "~> 0.6"
43
44
  spec.add_development_dependency "rubocop", "1.5.2"
44
45
  spec.add_development_dependency "rubocop-rails"
45
46
  spec.add_development_dependency "rubocop-performance"
46
- spec.add_development_dependency "ruby-protocol-buffers", "~> 1.0"
47
+
48
+ spec.add_runtime_dependency "socksify"
47
49
  end
data/lib/fontist/cli.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "thor"
2
2
  require "fontist/repo_cli"
3
+ require "fontist/google_cli"
3
4
 
4
5
  module Fontist
5
6
  class CLI < Thor
@@ -149,18 +150,9 @@ module Fontist
149
150
  It is done automatically when formulas are updated, or private formulas
150
151
  are set up.
151
152
  LONGDESC
152
- option :main_repo, type: :boolean,
153
- desc: "Updates indexes in the main repo (for backward " \
154
- "compatibility with versions prior to 1.9)"
155
153
  def rebuild_index
156
154
  handle_class_options(options)
157
-
158
- if options[:main_repo]
159
- Fontist::Index.rebuild_for_main_repo
160
- else
161
- Fontist::Index.rebuild
162
- end
163
-
155
+ Fontist::Index.rebuild
164
156
  Fontist.ui.say("Formula index has been rebuilt.")
165
157
  STATUS_SUCCESS
166
158
  end
@@ -175,6 +167,9 @@ module Fontist
175
167
  desc "repo SUBCOMMAND ...ARGS", "Manage custom repositories"
176
168
  subcommand "repo", Fontist::RepoCLI
177
169
 
170
+ desc "google SUBCOMMAND ...ARGS", "Manage Google formulas"
171
+ subcommand "google", Fontist::GoogleCLI
172
+
178
173
  private
179
174
 
180
175
  def handle_class_options(options)
@@ -103,6 +103,10 @@ module Fontist
103
103
  @fonts ||= Helpers.parse_to_object(hash_collection_fonts + hash_fonts)
104
104
  end
105
105
 
106
+ def digest
107
+ @data["digest"]
108
+ end
109
+
106
110
  private
107
111
 
108
112
  def default_key
@@ -0,0 +1,29 @@
1
+ module Fontist
2
+ class GoogleCLI < Thor
3
+ class_option :formulas_path, type: :string, desc: "Path to formulas"
4
+
5
+ desc "check", "Check Google fonts for updates"
6
+ def check
7
+ handle_class_options(options)
8
+ require "fontist/import/google_check"
9
+ Fontist::Import::GoogleCheck.new.call
10
+ CLI::STATUS_SUCCESS
11
+ end
12
+
13
+ desc "import", "Import Google fonts"
14
+ def import
15
+ handle_class_options(options)
16
+ require "fontist/import/google_import"
17
+ Fontist::Import::GoogleImport.new.call
18
+ CLI::STATUS_SUCCESS
19
+ end
20
+
21
+ private
22
+
23
+ def handle_class_options(options)
24
+ if options[:formulas_path]
25
+ Fontist.formulas_path = Pathname.new(options[:formulas_path])
26
+ end
27
+ end
28
+ end
29
+ end
@@ -11,7 +11,7 @@ module Fontist
11
11
  def initialize(path)
12
12
  @path = path
13
13
  @fonts = read
14
- @extension = "ttc"
14
+ @extension = detect_extension
15
15
  end
16
16
 
17
17
  def filename
@@ -46,6 +46,15 @@ module Fontist
46
46
  File.join(tmp_dir, filename)
47
47
  end
48
48
  end
49
+
50
+ def detect_extension
51
+ base_extension = "ttc"
52
+
53
+ file_extension = File.extname(File.basename(@path)).sub(/^\./, "")
54
+ return file_extension if file_extension.casecmp?(base_extension)
55
+
56
+ base_extension
57
+ end
49
58
  end
50
59
  end
51
60
  end
@@ -1,3 +1,4 @@
1
+ require "shellwords"
1
2
  require_relative "text_helper"
2
3
 
3
4
  module Fontist
@@ -5,7 +6,7 @@ module Fontist
5
6
  class FormulaBuilder
6
7
  FORMULA_ATTRIBUTES = %i[name description homepage resources
7
8
  font_collections fonts extract copyright
8
- license_url open_license].freeze
9
+ license_url open_license digest command].freeze
9
10
 
10
11
  attr_accessor :archive,
11
12
  :url,
@@ -49,7 +50,7 @@ module Fontist
49
50
  end
50
51
 
51
52
  def homepage
52
- both_fonts.first.homepage
53
+ both_fonts.map(&:homepage).compact.first
53
54
  end
54
55
 
55
56
  def resources
@@ -59,6 +60,18 @@ module Fontist
59
60
  end
60
61
 
61
62
  def resource_options
63
+ if @options[:skip_sha]
64
+ resource_options_without_sha
65
+ else
66
+ resource_options_with_sha
67
+ end
68
+ end
69
+
70
+ def resource_options_without_sha
71
+ { urls: [@url] + mirrors }
72
+ end
73
+
74
+ def resource_options_with_sha
62
75
  urls = []
63
76
  sha = []
64
77
  downloads do |url, path|
@@ -146,11 +159,11 @@ module Fontist
146
159
  end
147
160
 
148
161
  def copyright
149
- both_fonts.first.copyright
162
+ both_fonts.map(&:copyright).compact.first
150
163
  end
151
164
 
152
165
  def license_url
153
- both_fonts.first.license_url
166
+ both_fonts.map(&:license_url).compact.first
154
167
  end
155
168
 
156
169
  def open_license
@@ -165,6 +178,14 @@ module Fontist
165
178
 
166
179
  TextHelper.cleanup(@license_text)
167
180
  end
181
+
182
+ def digest
183
+ @options[:digest]
184
+ end
185
+
186
+ def command
187
+ Shellwords.shelljoin(ARGV)
188
+ end
168
189
  end
169
190
  end
170
191
  end
@@ -1,4 +1,3 @@
1
- require_relative "fonts_public.pb"
2
1
  require_relative "../google"
3
2
  require_relative "../otf_parser"
4
3
 
@@ -6,7 +5,7 @@ module Fontist
6
5
  module Import
7
6
  module Google
8
7
  class NewFontsFetcher
9
- REPO_PATH = Fontist.root_path.join("tmp", "fonts")
8
+ REPO_PATH = Fontist.fontist_path.join("google", "fonts")
10
9
  REPO_URL = "https://github.com/google/fonts.git".freeze
11
10
  SKIPLIST_PATH = File.expand_path("skiplist.yml", __dir__)
12
11
 
@@ -25,7 +24,8 @@ module Fontist
25
24
  if Dir.exist?(REPO_PATH)
26
25
  `cd #{REPO_PATH} && git pull`
27
26
  else
28
- `git clone #{REPO_URL} #{REPO_PATH}`
27
+ FileUtils.mkdir_p(File.dirname(REPO_PATH))
28
+ `git clone --depth 1 #{REPO_URL} #{REPO_PATH}`
29
29
  end
30
30
  end
31
31
 
@@ -53,23 +53,11 @@ module Fontist
53
53
  end
54
54
 
55
55
  def new?(path)
56
- metadata = fetch_metadata(path)
57
- return unless metadata
58
-
59
- font_new?(metadata, path)
60
- end
61
-
62
- def fetch_metadata(path)
63
- metadata_path = File.join(path, "METADATA.pb")
64
- return unless File.exists?(metadata_path)
65
-
66
- ::Google::Fonts::FamilyProto.parse_from_text(File.read(metadata_path))
67
- end
68
-
69
- def font_new?(metadata, path)
70
- return if in_skiplist?(metadata.name)
71
- return if up_to_date?(metadata, path)
72
- return unless downloadable?(metadata.name)
56
+ metadata_name = Google.metadata_name(path)
57
+ return unless metadata_name
58
+ return if in_skiplist?(metadata_name)
59
+ return if up_to_date?(metadata_name, path)
60
+ return unless downloadable?(metadata_name)
73
61
 
74
62
  true
75
63
  end
@@ -79,29 +67,47 @@ module Fontist
79
67
  @skiplist.include?(name)
80
68
  end
81
69
 
82
- def up_to_date?(metadata, path)
83
- formula = formula(metadata.name)
70
+ def up_to_date?(metadata_name, path)
71
+ formula = formula(metadata_name)
84
72
  return false unless formula
85
73
 
86
- styles = formula.fonts.map(&:styles).flatten
74
+ repo_digest_up_to_date?(formula, path) ||
75
+ fonts_up_to_date?(formula, path)
76
+ end
77
+
78
+ def repo_digest_up_to_date?(formula, path)
79
+ return unless formula.digest
87
80
 
88
- styles.all? do |style|
89
- style.version == otfinfo_version(font_path(style.font, path))
81
+ formula.digest == Google.digest(path)
82
+ end
83
+
84
+ def fonts_up_to_date?(formula, path)
85
+ styles = formula_styles(formula)
86
+ repo_fonts(path).all? do |font|
87
+ style = styles.find { |s| s.font == repo_to_archive_name(font) }
88
+ return false unless style
89
+
90
+ otfinfo_version(font) == style.version
90
91
  end
91
92
  end
92
93
 
93
- def formula(font_name)
94
- klass = font_name.gsub(/ /, "").sub(/\S/, &:upcase)
95
- @formulas ||= Fontist::Formula.all
96
- @formulas["Fontist::Formulas::#{klass}Font"]
94
+ def formula_styles(formula)
95
+ formula.fonts.map(&:styles).flatten
97
96
  end
98
97
 
99
- def font_path(filename, directory)
100
- File.join(directory, fix_variable_filename(filename))
98
+ def repo_fonts(path)
99
+ Dir.glob(File.join(path, "*.{ttf,otf}"))
101
100
  end
102
101
 
103
- def fix_variable_filename(filename)
104
- filename.sub("-VariableFont_wght", "[wght]")
102
+ def repo_to_archive_name(font_path)
103
+ File.basename(font_path)
104
+ .sub("[wght]", "-VariableFont_wght")
105
+ .sub("[opsz]", "-Regular-VariableFont_opsz")
106
+ end
107
+
108
+ def formula(font_name)
109
+ path = Fontist::Import::Google.formula_path(font_name)
110
+ Formula.new_from_file(path) if File.exist?(path)
105
111
  end
106
112
 
107
113
  def otfinfo_version(path)
@@ -110,10 +116,15 @@ module Fontist
110
116
  end
111
117
 
112
118
  def downloadable?(name)
119
+ retries ||= 0
120
+ retries += 1
113
121
  Down.open("https://fonts.google.com/download?family=#{name}")
114
122
  true
115
123
  rescue Down::NotFound
116
124
  false
125
+ rescue Down::TimeoutError
126
+ retry unless retries >= 3
127
+ false
117
128
  end
118
129
  end
119
130
  end
@@ -1,11 +1,29 @@
1
1
  module Fontist
2
2
  module Import
3
3
  module Google
4
+ def self.metadata_name(path)
5
+ metadata_path = File.join(path, "METADATA.pb")
6
+ return unless File.exists?(metadata_path)
7
+
8
+ File.foreach(metadata_path) do |line|
9
+ name = line.match(/^name: "(.+)"/)
10
+ return name[1] if name
11
+ end
12
+ end
13
+
4
14
  def self.formula_path(name)
5
15
  filename = name.downcase.gsub(" ", "_") + ".yml"
6
16
  Fontist.formulas_path.join("google", filename)
7
17
  end
8
18
 
19
+ def self.digest(path)
20
+ checksums = Dir.glob(File.join(path, "*.{ttf,otf,ttc}"))
21
+ .sort
22
+ .map { |x| Digest::SHA256.file(x).to_s }
23
+
24
+ Digest::SHA256.hexdigest(checksums.to_s)
25
+ end
26
+
9
27
  def self.style_version(text)
10
28
  return unless text
11
29
 
@@ -4,17 +4,12 @@ module Fontist
4
4
  module Import
5
5
  class GoogleCheck
6
6
  def call
7
- fetch_formulas
8
7
  fonts = new_fonts
9
8
  indicate(fonts)
10
9
  end
11
10
 
12
11
  private
13
12
 
14
- def fetch_formulas
15
- Formula.update_formulas_repo
16
- end
17
-
18
13
  def new_fonts
19
14
  Fontist::Import::Google::NewFontsFetcher.new(logging: true).call
20
15
  end
@@ -26,8 +21,6 @@ module Fontist
26
21
  new_paths.each do |path|
27
22
  puts path
28
23
  end
29
-
30
- abort
31
24
  end
32
25
  end
33
26
  end
@@ -1,11 +1,7 @@
1
1
  require "erb"
2
2
  require_relative "google"
3
3
  require_relative "google/new_fonts_fetcher"
4
- require_relative "google/fonts_public.pb"
5
- require_relative "template_helper"
6
- require_relative "text_helper"
7
- require_relative "otf_parser"
8
- require_relative "otf_style"
4
+ require_relative "create_formula"
9
5
 
10
6
  module Fontist
11
7
  module Import
@@ -13,6 +9,7 @@ module Fontist
13
9
  def call
14
10
  fonts = new_fonts
15
11
  create_formulas(fonts)
12
+ rebuild_index
16
13
  end
17
14
 
18
15
  private
@@ -22,161 +19,48 @@ module Fontist
22
19
  end
23
20
 
24
21
  def create_formulas(fonts)
22
+ return puts("Nothing to update") if fonts.empty?
23
+
25
24
  puts "Creating formulas..."
26
25
  fonts.each do |path|
27
26
  create_formula(path)
28
27
  end
29
28
  end
30
29
 
31
- def create_formula(path)
32
- puts path
33
- metadata = fetch_metadata(path)
34
- font = build_font(metadata, path)
35
- save_formula(font)
36
- end
30
+ def create_formula(font_path)
31
+ puts font_path
37
32
 
38
- def fetch_metadata(path)
39
- protobuf = File.read(File.join(path, "METADATA.pb"))
40
- ::Google::Fonts::FamilyProto.parse_from_text(protobuf)
41
- end
33
+ path = Fontist::Import::CreateFormula.new(
34
+ url(font_path),
35
+ name: Google.metadata_name(font_path),
36
+ formula_dir: formula_dir,
37
+ skip_sha: variable_style?(font_path),
38
+ digest: Google.digest(font_path),
39
+ ).call
42
40
 
43
- def build_font(metadata, path)
44
- h = from_metadata(metadata)
45
- .merge(from_otfinfo(path))
46
- .merge(styles: styles_from_otfinfo(path, metadata.fonts))
47
- .merge(from_license(path))
48
-
49
- OpenStruct.new(h)
41
+ Fontist.ui.success("Formula has been successfully created: #{path}")
50
42
  end
51
43
 
52
- def from_metadata(metadata)
53
- copyright = metadata.fonts.first.copyright
54
-
55
- Hash.new.tap do |h|
56
- h[:fullname] = metadata.name
57
- h[:cleanname] = metadata.name.gsub(/ /, "")
58
- h[:sha256] = sha256(metadata.name) unless variable_style?(metadata)
59
- h[:copyright] = Fontist::Import::TextHelper.cleanup(copyright)
60
- end
44
+ def url(path)
45
+ name = Google.metadata_name(path)
46
+ "https://fonts.google.com/download?family=#{ERB::Util.url_encode(name)}"
61
47
  end
62
48
 
63
- def variable_style?(metadata)
64
- metadata.fonts.any? do |s|
65
- s.filename.match?(/\[(.+,)?wght\]/)
49
+ def formula_dir
50
+ @formula_dir ||= Fontist.formulas_path.join("google").tap do |path|
51
+ FileUtils.mkdir_p(path) unless File.exist?(path)
66
52
  end
67
53
  end
68
54
 
69
- def sha256(name)
70
- file = Down.download("https://fonts.google.com/download?family=#{name}",
71
- open_timeout: 10,
72
- read_timeout: 10)
73
-
74
- Digest::SHA256.file(file).to_s
75
- end
76
-
77
- def from_license(path)
78
- file = Dir.glob(File.join(path, "{OFL.txt,UFL.txt,LICENSE.txt}")).first
79
- print "warn, no license, " unless file
80
- return { license: "" } unless file
81
-
82
- { license: cleanup_text(File.read(file)) }
83
- end
84
-
85
- def cleanup_text(text)
86
- text.rstrip
87
- .gsub("\r\n", "\n")
88
- .lines
89
- .map(&:rstrip)
90
- .drop_while(&:empty?)
91
- .join("\n")
92
- end
93
-
94
- def from_otfinfo(path)
95
- font_file = Dir.glob(File.join(path, "*.ttf")).first
96
- otf = OtfParser.new(font_file).call
97
-
98
- { homepage: otf["Vendor URL"],
99
- license_url: otf["License URL"] }
100
- end
101
-
102
- def styles_from_otfinfo(path, fonts)
103
- fonts.map do |f|
104
- file_path = File.join(path, f.filename)
105
- info = OtfParser.new(file_path).call
106
- OtfStyle.new(info, file_path).call
55
+ def variable_style?(path)
56
+ fonts = Dir.glob(File.join(path, "*.{ttf,otf}"))
57
+ fonts.any? do |font|
58
+ File.basename(font).match?(/\[(.+,)?(wght|opsz)\]/)
107
59
  end
108
60
  end
109
61
 
110
- def save_formula(font)
111
- hash = formula_hash(font)
112
- path = formula_path(font.fullname)
113
- save_to_path(hash, path)
114
- end
115
-
116
- def formula_hash(font)
117
- stringify_keys(name: font.cleanname.sub(/\S/, &:upcase),
118
- description: font.fullname,
119
- homepage: font.homepage,
120
- resources: formula_resource(font),
121
- fonts: [yaml_font(font)],
122
- extract: { format: :zip },
123
- copyright: font.copyright,
124
- license_url: font.license_url,
125
- open_license: font.license)
126
- end
127
-
128
- def stringify_keys(hash)
129
- JSON.parse(hash.to_json)
130
- end
131
-
132
- def formula_resource(font)
133
- encoded_name = ERB::Util.url_encode(font.fullname)
134
- url = "https://fonts.google.com/download?family=#{encoded_name}"
135
-
136
- options = {}
137
- options[:urls] = [url]
138
- options[:sha256] = font.sha256 if font.sha256
139
-
140
- { "#{font.cleanname}.zip" => options }
141
- end
142
-
143
- def yaml_font(font)
144
- { name: font.fullname,
145
- styles: yaml_styles(font.styles) }
146
- end
147
-
148
- def yaml_styles(styles)
149
- styles.map do |s|
150
- yaml_style(s)
151
- end
152
- end
153
-
154
- def yaml_style(style)
155
- Hash.new.tap do |h|
156
- h.merge!(family_name: style.family_name,
157
- type: style.style,
158
- full_name: style.full_name)
159
- if style.preferred_family_name
160
- h[:preferred_family_name] = style.preferred_family_name
161
- end
162
- h[:preferred_type] = style.preferred_style if style.preferred_style
163
- h.merge!(style.to_h.select do |k, _|
164
- %i(post_script_name version description copyright).include?(k)
165
- end.compact)
166
- h.merge!(font: fix_variable_filename(style.filename))
167
- end
168
- end
169
-
170
- def fix_variable_filename(filename)
171
- filename.sub("[wght]", "-VariableFont_wght")
172
- end
173
-
174
- def formula_path(name)
175
- Fontist::Import::Google.formula_path(name)
176
- end
177
-
178
- def save_to_path(hash, path)
179
- File.write(path, YAML.dump(hash))
62
+ def rebuild_index
63
+ Fontist::Index.rebuild
180
64
  end
181
65
  end
182
66
  end