fontist 1.20.0 → 1.21.1

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.
@@ -0,0 +1,89 @@
1
+ require "fontist/import"
2
+ require "fontist/import/formula_builder"
3
+ require "fontist/import/otf/font_file"
4
+
5
+ module Fontist
6
+ module Import
7
+ module Google
8
+ class CreateGoogleFormula
9
+ REPO_PATH = Fontist.fontist_path.join("google", "fonts")
10
+ POSSIBLE_LICENSE_FILES = ["LICENSE.txt",
11
+ "LICENCE.txt",
12
+ "OFL.txt",
13
+ "UFL.txt"].freeze
14
+
15
+ def initialize(item, options = {})
16
+ @item = item
17
+ @options = options
18
+ end
19
+
20
+ def call
21
+ builder = FormulaBuilder.new
22
+ builder.options = options
23
+ builder.resources = resources
24
+ builder.font_files = font_files
25
+ builder.license_text = license_text
26
+ builder.save
27
+ end
28
+
29
+ private
30
+
31
+ def options
32
+ @options.merge(name: formula_name, open_license: true)
33
+ end
34
+
35
+ def formula_name
36
+ @item["family"]
37
+ end
38
+
39
+ def resources
40
+ {
41
+ @item["family"] => {
42
+ source: "google",
43
+ family: @item["family"],
44
+ files: @item["files"].values,
45
+ },
46
+ }
47
+ end
48
+
49
+ def font_files
50
+ @font_files ||= @item["files"].map do |_key, url|
51
+ font_file(url)
52
+ end
53
+ end
54
+
55
+ def license_text
56
+ @license_text ||= find_license_text
57
+ end
58
+
59
+ def font_file(url)
60
+ path = Utils::Downloader.download(url, use_content_length: false).path
61
+ Otf::FontFile.new(path)
62
+ end
63
+
64
+ def find_license_text
65
+ file = license_file
66
+ return unless file
67
+
68
+ File.read(file)
69
+ end
70
+
71
+ def license_file
72
+ dir = @item["family"].gsub(" ", "").downcase
73
+ path = repo_paths(dir).first
74
+ return unless path
75
+
76
+ full_paths = POSSIBLE_LICENSE_FILES.map { |f| File.join(path, f) }
77
+
78
+ Dir[*full_paths].first
79
+ end
80
+
81
+ def repo_paths(dir)
82
+ Dir[File.join(REPO_PATH, "apache", dir),
83
+ File.join(REPO_PATH, "ofl", dir),
84
+ File.join(REPO_PATH, "ufl", dir)]
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,54 +1,92 @@
1
- require "erb"
2
1
  require_relative "google"
3
- require_relative "google/new_fonts_fetcher"
4
- require_relative "create_formula"
2
+ require_relative "google/api"
3
+ require_relative "google/create_google_formula"
5
4
 
6
5
  module Fontist
7
6
  module Import
8
7
  class GoogleImport
8
+ REPO_PATH = Fontist.fontist_path.join("google", "fonts")
9
+ REPO_URL = "https://github.com/google/fonts.git".freeze
10
+
9
11
  def initialize(options)
10
12
  @max_count = options[:max_count] || Google::DEFAULT_MAX_COUNT
11
13
  end
12
14
 
13
15
  def call
14
- fonts = new_fonts
15
- create_formulas(fonts)
16
- rebuild_index unless fonts.empty?
16
+ update_repo
17
+ count = update_formulas
18
+ rebuild_index if count.positive?
17
19
  end
18
20
 
19
21
  private
20
22
 
21
- def new_fonts
22
- Fontist::Import::Google::NewFontsFetcher.new(logging: true,
23
- limit: @max_count).call
23
+ def update_repo
24
+ if Dir.exist?(REPO_PATH)
25
+ `cd #{REPO_PATH} && git pull`
26
+ else
27
+ FileUtils.mkdir_p(File.dirname(REPO_PATH))
28
+ `git clone --depth 1 #{REPO_URL} #{REPO_PATH}`
29
+ end
24
30
  end
25
31
 
26
- def create_formulas(fonts)
27
- return puts("Nothing to update") if fonts.empty?
32
+ def update_formulas
33
+ Fontist.ui.say "Updating formulas..."
34
+
35
+ items = api_items
28
36
 
29
- puts "Creating formulas..."
30
- fonts.each do |path|
31
- create_formula(path)
37
+ count = 0
38
+ items.each do |item|
39
+ break if count >= @max_count
40
+
41
+ path = update_formula(item)
42
+ count += 1 if path
32
43
  end
44
+
45
+ count
46
+ end
47
+
48
+ def api_items
49
+ Google::Api.items
50
+ end
51
+
52
+ def update_formula(item)
53
+ family = item["family"]
54
+ Fontist.ui.say "Checking #{family}"
55
+ unless new_changes?(item)
56
+ Fontist.ui.say "Skip, no changes"
57
+ return
58
+ end
59
+
60
+ create_formula(item)
33
61
  end
34
62
 
35
- def create_formula(font_path)
36
- puts font_path
63
+ def new_changes?(item)
64
+ formula = formula(item["family"])
65
+ return true unless formula
37
66
 
38
- path = Fontist::Import::CreateFormula.new(
39
- url(font_path),
40
- name: Google.metadata_name(font_path),
67
+ item["files"].values != formula.resources.first.files
68
+ end
69
+
70
+ def formula(font_name)
71
+ path = formula_path(font_name)
72
+ Formula.new_from_file(path) if File.exist?(path)
73
+ end
74
+
75
+ def formula_path(name)
76
+ snake_case = name.downcase.gsub(" ", "_")
77
+ filename = "#{snake_case}.yml"
78
+ Fontist.formulas_path.join("google", filename)
79
+ end
80
+
81
+ def create_formula(item)
82
+ path = Google::CreateGoogleFormula.new(
83
+ item,
41
84
  formula_dir: formula_dir,
42
- skip_sha: variable_style?(font_path),
43
- digest: Google.digest(font_path),
44
85
  ).call
45
86
 
46
87
  Fontist.ui.success("Formula has been successfully created: #{path}")
47
- end
48
88
 
49
- def url(path)
50
- name = Google.metadata_name(path)
51
- "https://fonts.google.com/download?family=#{ERB::Util.url_encode(name)}"
89
+ path
52
90
  end
53
91
 
54
92
  def formula_dir
@@ -57,13 +95,6 @@ module Fontist
57
95
  end
58
96
  end
59
97
 
60
- def variable_style?(path)
61
- fonts = Dir.glob(File.join(path, "*.{ttf,otf}"))
62
- fonts.any? do |font|
63
- File.basename(font).match?(/\[(.+,)?(wght|opsz)\]/)
64
- end
65
- end
66
-
67
98
  def rebuild_index
68
99
  Fontist::Index.rebuild
69
100
  end
@@ -19,10 +19,6 @@ module Fontist
19
19
  save_operation_subdir
20
20
  end
21
21
 
22
- def extension
23
- fetch_extension(@archive)
24
- end
25
-
26
22
  def font_files
27
23
  ensure_extracted
28
24
  @font_files
@@ -52,18 +48,6 @@ module Fontist
52
48
  @operations[:options][:fonts_sub_dir] = @subdir
53
49
  end
54
50
 
55
- def fetch_extension(file)
56
- File.extname(filename(file)).sub(/^\./, "")
57
- end
58
-
59
- def filename(file)
60
- if file.respond_to?(:original_filename)
61
- file.original_filename
62
- else
63
- File.basename(file)
64
- end
65
- end
66
-
67
51
  def ensure_extracted
68
52
  return if @extracted
69
53
 
@@ -2,6 +2,8 @@ module Fontist
2
2
  module Manifest
3
3
  class Locations
4
4
  def initialize(manifest)
5
+ Fontist.ui.debug("Manifest: #{manifest}")
6
+
5
7
  @manifest = manifest
6
8
  end
7
9
 
@@ -0,0 +1,55 @@
1
+ module Fontist
2
+ module Resources
3
+ class ArchiveResource
4
+ def initialize(resource, options = {})
5
+ @resource = resource
6
+ @options = options
7
+ end
8
+
9
+ def files(_source_names, &block)
10
+ excavate.files(recursive_packages: true, &block)
11
+ end
12
+
13
+ private
14
+
15
+ def excavate
16
+ Excavate::Archive.new(archive.path)
17
+ end
18
+
19
+ def archive
20
+ download_file(@resource)
21
+ end
22
+
23
+ def download_file(source)
24
+ errors = []
25
+ source.urls.each do |request|
26
+ result = try_download_file(request, source)
27
+ return result unless result.is_a?(Errors::InvalidResourceError)
28
+
29
+ errors << result
30
+ end
31
+
32
+ raise Errors::InvalidResourceError, errors.join(" ")
33
+ end
34
+
35
+ def try_download_file(request, source)
36
+ info_log(request)
37
+
38
+ Fontist::Utils::Downloader.download(
39
+ request,
40
+ sha: source.sha256,
41
+ file_size: source.file_size,
42
+ progress_bar: !@options[:no_progress],
43
+ )
44
+ rescue Errors::InvalidResourceError => e
45
+ Fontist.ui.say(e.message)
46
+ e
47
+ end
48
+
49
+ def info_log(request)
50
+ url = request.respond_to?(:url) ? request.url : request
51
+ Fontist.ui.say(%(Downloading from #{url}))
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,64 @@
1
+ module Fontist
2
+ module Resources
3
+ class GoogleResource
4
+ def initialize(resource, options = {})
5
+ @resource = resource
6
+ @options = options
7
+ end
8
+
9
+ def files(source_names)
10
+ cached_paths = download_fonts(source_names)
11
+
12
+ cached_paths.map do |path|
13
+ Dir.mktmpdir do |dir|
14
+ FileUtils.cp(path, dir)
15
+
16
+ yield File.join(dir, File.basename(path))
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def download_fonts(source_names)
24
+ urls = font_urls(source_names)
25
+
26
+ urls.map do |url|
27
+ download(url)
28
+ end
29
+ end
30
+
31
+ def font_urls(source_names)
32
+ @resource.files.select do |url|
33
+ source_names.include?(path_to_source_file(url))
34
+ end
35
+ end
36
+
37
+ def path_to_source_file(path)
38
+ format_filename(File.basename(path))
39
+ end
40
+
41
+ # TODO: remove duplication, another in Cache
42
+ def format_filename(filename)
43
+ return filename unless filename.length > 255
44
+
45
+ ext = File.extname(filename)
46
+ target_size = 255 - ext.length
47
+ cut_filename = filename.slice(0, target_size)
48
+ "#{cut_filename}#{ext}"
49
+ end
50
+
51
+ def download(url)
52
+ Fontist.ui.say(%(Downloading from #{url}))
53
+
54
+ file = Utils::Downloader.download(
55
+ url,
56
+ use_content_length: false,
57
+ progress_bar: !@options[:no_progress],
58
+ )
59
+
60
+ file.path
61
+ end
62
+ end
63
+ end
64
+ end
@@ -4,6 +4,10 @@ module Fontist
4
4
  @text = text
5
5
  end
6
6
 
7
+ def to_s
8
+ value.join(" . ")
9
+ end
10
+
7
11
  def value
8
12
  @value ||= numbers || default_value
9
13
  end
@@ -1,6 +1,8 @@
1
1
  module Fontist
2
2
  module Utils
3
3
  class Cache
4
+ MAX_FILENAME_SIZE = 255
5
+
4
6
  include Locking
5
7
 
6
8
  def self.lock_path(path)
@@ -110,6 +112,11 @@ module Fontist
110
112
  end
111
113
 
112
114
  def filename(source)
115
+ filename = response_to_filename(source)
116
+ format_filename(filename)
117
+ end
118
+
119
+ def response_to_filename(source)
113
120
  if File.extname(source.original_filename).empty? && source.content_type
114
121
  require "mime/types"
115
122
  ext = MIME::Types[source.content_type].first&.preferred_extension
@@ -119,6 +126,15 @@ module Fontist
119
126
  source.original_filename
120
127
  end
121
128
 
129
+ def format_filename(filename)
130
+ return filename unless filename.length > MAX_FILENAME_SIZE
131
+
132
+ ext = File.extname(filename)
133
+ target_size = MAX_FILENAME_SIZE - ext.length
134
+ cut_filename = filename.slice(0, target_size)
135
+ "#{cut_filename}#{ext}"
136
+ end
137
+
122
138
  def move(source_file, target_path)
123
139
  # Windows requires file descriptors to be closed before files are moved
124
140
  source_file.close
@@ -10,12 +10,17 @@ module Fontist
10
10
  ruby2_keywords :download if respond_to?(:ruby2_keywords, true)
11
11
  end
12
12
 
13
- def initialize(file, file_size: nil, sha: nil, progress_bar: nil)
13
+ def initialize(file,
14
+ file_size: nil,
15
+ sha: nil,
16
+ progress_bar: nil,
17
+ use_content_length: true)
14
18
  # TODO: If the first mirror fails, try the second one
15
19
  @file = file
16
20
  @sha = [sha].flatten.compact
17
21
  @file_size = file_size.to_i if file_size
18
22
  @progress_bar = progress_bar
23
+ @use_content_length = use_content_length
19
24
  @cache = Cache.new
20
25
  end
21
26
 
@@ -85,7 +90,9 @@ module Fontist
85
90
  max_redirects: 10,
86
91
  headers: headers,
87
92
  content_length_proc: ->(content_length) {
88
- progress_bar.total = content_length if content_length
93
+ if @use_content_length && content_length
94
+ progress_bar.total = content_length
95
+ end
89
96
  },
90
97
  progress_proc: -> (progress) {
91
98
  progress_bar.increment(progress)
@@ -16,7 +16,15 @@ module Fontist
16
16
  end
17
17
 
18
18
  def self.level
19
- @level || default_level
19
+ @level ||= env_level || default_level
20
+ end
21
+
22
+ def self.env_level
23
+ ENV["FONTIST_LOG"]&.to_sym
24
+ end
25
+
26
+ def self.debug?
27
+ log_levels.include?(:debug)
20
28
  end
21
29
 
22
30
  def self.default_level
@@ -50,7 +58,7 @@ module Fontist
50
58
  end
51
59
 
52
60
  def self.debug(message)
53
- new.say(message) if log_levels.include?(:debug)
61
+ new.say(message) if debug?
54
62
  end
55
63
 
56
64
  def self.log_levels
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "1.20.0".freeze
2
+ VERSION = "1.21.1".freeze
3
3
  end
data/lib/fontist.rb CHANGED
@@ -49,7 +49,7 @@ module Fontist
49
49
  end
50
50
 
51
51
  def self.formulas_version
52
- "v3"
52
+ "v4"
53
53
  end
54
54
 
55
55
  def self.formulas_repo_url
@@ -159,4 +159,8 @@ module Fontist
159
159
  def self.interactive=(bool)
160
160
  @interactive = bool
161
161
  end
162
+
163
+ def self.google_fonts_key
164
+ ENV["GOOGLE_FONTS_API_KEY"] || config[:google_fonts_key]
165
+ end
162
166
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontist
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.20.0
4
+ version: 1.21.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-05 00:00:00.000000000 Z
11
+ date: 2024-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -377,6 +377,7 @@ files:
377
377
  - lib/fontist/cache_cli.rb
378
378
  - lib/fontist/cli.rb
379
379
  - lib/fontist/cli/class_options.rb
380
+ - lib/fontist/cli/thor_ext.rb
380
381
  - lib/fontist/collection_file.rb
381
382
  - lib/fontist/config.rb
382
383
  - lib/fontist/config_cli.rb
@@ -401,9 +402,8 @@ files:
401
402
  - lib/fontist/import/formula_builder.rb
402
403
  - lib/fontist/import/formula_serializer.rb
403
404
  - lib/fontist/import/google.rb
404
- - lib/fontist/import/google/new_fonts_fetcher.rb
405
- - lib/fontist/import/google/skiplist.yml
406
- - lib/fontist/import/google_check.rb
405
+ - lib/fontist/import/google/api.rb
406
+ - lib/fontist/import/google/create_google_formula.rb
407
407
  - lib/fontist/import/google_import.rb
408
408
  - lib/fontist/import/helpers/hash_helper.rb
409
409
  - lib/fontist/import/helpers/system_helper.rb
@@ -433,6 +433,8 @@ files:
433
433
  - lib/fontist/manifest/locations.rb
434
434
  - lib/fontist/repo.rb
435
435
  - lib/fontist/repo_cli.rb
436
+ - lib/fontist/resources/archive_resource.rb
437
+ - lib/fontist/resources/google_resource.rb
436
438
  - lib/fontist/style_version.rb
437
439
  - lib/fontist/system.yml
438
440
  - lib/fontist/system_font.rb
@@ -468,7 +470,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
468
470
  - !ruby/object:Gem::Version
469
471
  version: '0'
470
472
  requirements: []
471
- rubygems_version: 3.3.26
473
+ rubygems_version: 3.3.27
472
474
  signing_key:
473
475
  specification_version: 4
474
476
  summary: Install openly-licensed fonts on Windows, Linux and Mac!