fontist 1.10.0 → 1.11.3

Sign up to get free protection for your applications and to get access to all the features.
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