fontist 1.13.0 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,141 +1,57 @@
1
1
  require "plist"
2
2
  require "nokogiri"
3
- require "fontist/import"
4
- require_relative "recursive_extraction"
5
- require_relative "helpers/hash_helper"
6
- require_relative "manual_formula_builder"
3
+ require "fontist/import/create_formula"
7
4
 
8
5
  module Fontist
9
6
  module Import
10
7
  class Macos
11
8
  FONT_XML = "/System/Library/AssetsV2/com_apple_MobileAsset_Font6/com_apple_MobileAsset_Font6.xml".freeze # rubocop:disable Layout/LineLength
12
- DESCRIPTION = "Fonts included with macOS %<name>s".freeze
13
-
14
- INSTRUCTIONS = <<~INSTRUCTIONS.freeze
15
- To download and enable any of these fonts:
16
-
17
- 1. Open Font Book, which is in your Applications folder.
18
- 2. Select All Fonts in the sidebar, or use the Search field to find the font that you want to download. Fonts that are not already downloaded appear dimmed in the list of fonts.
19
- 3. Select the dimmed font and choose Edit > Download, or Control-click it and choose Download from the pop-up menu.
20
- INSTRUCTIONS
21
-
22
- def initialize(options = {})
23
- @options = options
24
- end
9
+ HOMEPAGE = "https://support.apple.com/en-om/HT211240#document".freeze
25
10
 
26
11
  def call
27
- downloadable_fonts = fetch_fonts_list
28
- links = fetch_links(downloadable_fonts)
29
- archives = download(links)
30
- store_in_dir(archives) do |dir|
31
- create_formula(dir)
12
+ links.each do |link|
13
+ create_formula(link)
32
14
  end
33
- end
34
15
 
35
- private
36
-
37
- def fetch_fonts_list
38
- html = Net::HTTP.get(URI.parse(@options[:fonts_link]))
16
+ Fontist::Index.rebuild
39
17
 
40
- document = Nokogiri::HTML.parse(html)
41
- document.css("#sections div.grid2col:nth-of-type(3) div ul > li",
42
- "#sections div.grid2col:nth-of-type(4) div ul > li")
43
- .map(&:text)
18
+ Fontist.ui.success("Created #{links.size} formulas.")
44
19
  end
45
20
 
46
- def fetch_links(downloadable_fonts)
47
- data = Plist.parse_xml(FONT_XML)
48
- assets = downloadable_assets(data, downloadable_fonts)
49
- assets_links(assets)
50
- end
51
-
52
- def downloadable_assets(data, downloadable_fonts)
53
- data["Assets"].select do |x|
54
- x["FontInfo4"].any? do |i|
55
- downloadable_fonts.find do |d|
56
- d.start_with?(i["FontFamilyName"])
57
- end
58
- end
59
- end
60
- end
21
+ private
61
22
 
62
- def assets_links(assets)
63
- assets.map do |x|
23
+ def links
24
+ data = Plist.parse_xml(FONT_XML)
25
+ data["Assets"].map do |x|
64
26
  x.values_at("__BaseURL", "__RelativePath").join
65
27
  end
66
28
  end
67
29
 
68
- def download(links)
69
- links.map do |url|
70
- Fontist::Utils::Downloader.download(url, progress_bar: true).path
71
- end
72
- end
73
-
74
- def store_in_dir(archives)
75
- Dir.mktmpdir do |dir|
76
- archives.each do |archive|
77
- FileUtils.ln(archive, dir)
78
- end
79
-
80
- yield dir
81
- end
82
- end
83
-
84
- def create_formula(archives_dir)
85
- extractor = RecursiveExtraction.new(archives_dir)
86
- path = save(formula(archives_dir, extractor))
30
+ def create_formula(url)
31
+ path = Fontist::Import::CreateFormula.new(
32
+ url,
33
+ platforms: platforms,
34
+ homepage: homepage,
35
+ requires_license_agreement: license,
36
+ formula_dir: formula_dir,
37
+ keep_existing: true,
38
+ ).call
87
39
  Fontist.ui.success("Formula has been successfully created: #{path}")
88
40
 
89
41
  path
90
42
  end
91
43
 
92
- def formula(archive, extractor)
93
- builder = ManualFormulaBuilder.new
94
- setup_strings(builder, archive)
95
- setup_files(builder, extractor)
96
- builder.formula
97
- end
98
-
99
- def setup_strings(builder, archive)
100
- builder.url = archive
101
- builder.archive = archive
102
- builder.platforms = platforms
103
- builder.instructions = instructions
104
- builder.description = description
105
- builder.options = builder_options
106
- end
107
-
108
44
  def platforms
109
- major_version = Sys::Uname.release.split(".").first
110
-
111
- ["macos-#{major_version}"]
112
- end
113
-
114
- def instructions
115
- INSTRUCTIONS.strip
116
- end
117
-
118
- def description
119
- format(DESCRIPTION, name: @options[:name])
120
- end
121
-
122
- def builder_options
123
- @options.merge(homepage: @options[:fonts_link])
45
+ ["macos"]
124
46
  end
125
47
 
126
- def setup_files(builder, extractor)
127
- builder.extractor = extractor
128
- builder.font_files = extractor.font_files
129
- builder.font_collection_files = extractor.font_collection_files
130
- builder.license_text = extractor.license_text
48
+ def homepage
49
+ HOMEPAGE
131
50
  end
132
51
 
133
- def save(hash)
134
- filename = Import.name_to_filename(@options[:name])
135
- path = File.join(formula_dir, filename)
136
- yaml = YAML.dump(Helpers::HashHelper.stringify_keys(hash))
137
- File.write(path, yaml)
138
- path
52
+ def license
53
+ @license ||= File.read(File.expand_path("macos/macos_license.txt",
54
+ __dir__))
139
55
  end
140
56
 
141
57
  def formula_dir
@@ -94,12 +94,22 @@ module Fontist
94
94
  def match_font(path)
95
95
  case Files::FontDetector.detect(path)
96
96
  when :font
97
- @font_files << Otf::FontFile.new(path)
97
+ file = Otf::FontFile.new(path)
98
+ @font_files << file unless already_exist?(file)
98
99
  when :collection
99
100
  @collection_files << Files::CollectionFile.new(path)
100
101
  end
101
102
  end
102
103
 
104
+ def already_exist?(candidate)
105
+ @font_files.any? do |file|
106
+ file.family_name == candidate.family_name &&
107
+ file.type == candidate.type &&
108
+ file.version == candidate.version &&
109
+ file.font == candidate.font
110
+ end
111
+ end
112
+
103
113
  def font_directory?(path)
104
114
  return true unless subdirectory_pattern
105
115
 
@@ -20,7 +20,7 @@ module Fontist
20
20
  min, max = strs.minmax
21
21
  idx = min.size.times { |i| break i if min[i] != max[i] }
22
22
  prefix = min[0...idx].strip
23
- return if prefix.empty?
23
+ return if prefix.size < 2
24
24
 
25
25
  prefix
26
26
  end
@@ -1,18 +1,28 @@
1
1
  module Fontist
2
2
  class ImportCLI < Thor
3
+ include CLI::ClassOptions
4
+
5
+ desc "google", "Import Google fonts"
6
+ def google
7
+ handle_class_options(options)
8
+ require "fontist/import/google_import"
9
+ Fontist::Import::GoogleImport.new.call
10
+ CLI::STATUS_SUCCESS
11
+ end
12
+
3
13
  desc "macos", "Create formula for on-demand macOS fonts"
4
- option :name, desc: "Example: Big Sur", required: true
5
- option :fonts_link,
6
- desc: "A link to a list of available fonts in a current OS",
7
- required: true
8
- option :formulas_path, type: :string, desc: "Path to formulas"
9
14
  def macos
10
- if options[:formulas_path]
11
- Fontist.formulas_path = Pathname.new(options[:formulas_path])
12
- end
13
-
15
+ handle_class_options(options)
14
16
  require_relative "import/macos"
15
- Import::Macos.new(options).call
17
+ Import::Macos.new.call
18
+ CLI::STATUS_SUCCESS
19
+ end
20
+
21
+ desc "sil", "Import formulas from SIL"
22
+ def sil
23
+ handle_class_options(options)
24
+ require "fontist/import/sil_import"
25
+ Fontist::Import::SilImport.new.call
16
26
  CLI::STATUS_SUCCESS
17
27
  end
18
28
  end
@@ -1,9 +1,12 @@
1
1
  module Fontist
2
2
  class RepoCLI < Thor
3
+ include CLI::ClassOptions
4
+
3
5
  desc "setup NAME URL",
4
6
  "Setup a custom fontist repo named NAME for the repository at URL " \
5
7
  "and fetches its formulas"
6
8
  def setup(name, url)
9
+ handle_class_options(options)
7
10
  Repo.setup(name, url)
8
11
  Fontist.ui.success(
9
12
  "Fontist repo '#{name}' from '#{url}' has been successfully set up.",
@@ -13,6 +16,7 @@ module Fontist
13
16
 
14
17
  desc "update NAME", "Update formulas in a fontist repo named NAME"
15
18
  def update(name)
19
+ handle_class_options(options)
16
20
  Repo.update(name)
17
21
  Fontist.ui.success(
18
22
  "Fontist repo '#{name}' has been successfully updated.",
@@ -24,6 +28,7 @@ module Fontist
24
28
 
25
29
  desc "remove NAME", "Remove fontist repo named NAME"
26
30
  def remove(name)
31
+ handle_class_options(options)
27
32
  Repo.remove(name)
28
33
  Fontist.ui.success(
29
34
  "Fontist repo '#{name}' has been successfully removed.",
@@ -35,6 +40,7 @@ module Fontist
35
40
 
36
41
  desc "list", "List fontist repos"
37
42
  def list
43
+ handle_class_options(options)
38
44
  Repo.list.each do |name|
39
45
  Fontist.ui.say(name)
40
46
  end
@@ -24,7 +24,7 @@ module Fontist
24
24
  download_file
25
25
  end
26
26
 
27
- raise_if_tampered(file)
27
+ check_tampered(file)
28
28
 
29
29
  file
30
30
  end
@@ -33,15 +33,12 @@ module Fontist
33
33
 
34
34
  attr_reader :file, :sha, :file_size
35
35
 
36
- def raise_if_tampered(file)
36
+ def check_tampered(file)
37
37
  file_checksum = Digest::SHA256.file(file).to_s
38
38
  if !sha.empty? && !sha.include?(file_checksum)
39
- raise(
40
- Fontist::Errors::TamperedFileError.new(
41
- "The downloaded file from #{@file} doesn't " \
42
- "match with the expected sha256 checksum (#{file_checksum})!\n" \
43
- "Beginning of content: #{File.read(file, 3000)}",
44
- ),
39
+ Fontist.ui.error(
40
+ "SHA256 checksum mismatch for #{url}: #{file_checksum}, " \
41
+ "should be #{sha.join(', or ')}.",
45
42
  )
46
43
  end
47
44
  end
@@ -28,6 +28,14 @@ module Fontist
28
28
  def self.match?(platform)
29
29
  user_os_with_version.start_with?(platform)
30
30
  end
31
+
32
+ def self.fontconfig_installed?
33
+ Helpers.silence_stream($stderr) do
34
+ !!Helpers.run("fc-cache -V")
35
+ end
36
+ rescue Errno::ENOENT
37
+ false
38
+ end
31
39
  end
32
40
  end
33
41
  end
@@ -3,16 +3,36 @@ require "thor"
3
3
  module Fontist
4
4
  module Utils
5
5
  class UI < Thor
6
+ ALL_LEVELS = %i[debug info warn error fatal unknown].freeze
7
+
8
+ def self.level=(level)
9
+ unless ALL_LEVELS.include?(level)
10
+ raise Errors::GeneralError,
11
+ "Unknown log level: #{level.inspect}. " \
12
+ "Supported levels are #{ALL_LEVELS.map(&:inspect).join(', ')}."
13
+ end
14
+
15
+ @level = level
16
+ end
17
+
18
+ def self.level
19
+ @level || default_level
20
+ end
21
+
22
+ def self.default_level
23
+ :fatal
24
+ end
25
+
6
26
  def self.success(message)
7
- new.say(message, :green)
27
+ new.say(message, :green) if log_levels.include?(:info)
8
28
  end
9
29
 
10
30
  def self.error(message)
11
- new.say(message, :red)
31
+ new.say(message, :red) if log_levels.include?(:warn)
12
32
  end
13
33
 
14
34
  def self.say(message)
15
- new.say(message)
35
+ new.say(message) if log_levels.include?(:info)
16
36
  end
17
37
 
18
38
  def self.ask(message, options = {})
@@ -20,7 +40,16 @@ module Fontist
20
40
  end
21
41
 
22
42
  def self.print(message)
23
- super
43
+ super if log_levels.include?(:info)
44
+ end
45
+
46
+ def self.debug(message)
47
+ new.say(message) if log_levels.include?(:debug)
48
+ end
49
+
50
+ def self.log_levels
51
+ @log_levels ||= {}
52
+ @log_levels[@level] ||= ALL_LEVELS.drop_while { |l| l != level }
24
53
  end
25
54
  end
26
55
  end
data/lib/fontist/utils.rb CHANGED
@@ -1,9 +1,6 @@
1
1
  require "fontist/utils/ui"
2
2
  require "fontist/utils/locking"
3
3
  require "fontist/utils/system"
4
- require "fontist/utils/dsl"
5
- require "fontist/utils/dsl/font"
6
- require "fontist/utils/dsl/collection_font"
7
4
  require "fontist/utils/downloader"
8
5
 
9
6
  module Fontist
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "1.13.0".freeze
2
+ VERSION = "1.14.0".freeze
3
3
  end
data/lib/fontist.rb CHANGED
@@ -119,11 +119,7 @@ module Fontist
119
119
  @preferred_family = bool
120
120
  end
121
121
 
122
- def self.debug?
123
- @debug || false
124
- end
125
-
126
- def self.debug=(bool)
127
- @debug = bool
122
+ def self.log_level=(level)
123
+ Fontist.ui.level = level
128
124
  end
129
125
  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.13.0
4
+ version: 1.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-07 00:00:00.000000000 Z
11
+ date: 2022-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -299,13 +299,15 @@ files:
299
299
  - fontist.gemspec
300
300
  - lib/fontist.rb
301
301
  - lib/fontist/cli.rb
302
+ - lib/fontist/cli/class_options.rb
302
303
  - lib/fontist/errors.rb
303
304
  - lib/fontist/font.rb
304
305
  - lib/fontist/font_installer.rb
305
306
  - lib/fontist/font_path.rb
307
+ - lib/fontist/fontconfig.rb
308
+ - lib/fontist/fontconfig_cli.rb
306
309
  - lib/fontist/formula.rb
307
310
  - lib/fontist/formula_picker.rb
308
- - lib/fontist/google_cli.rb
309
311
  - lib/fontist/helpers.rb
310
312
  - lib/fontist/import.rb
311
313
  - lib/fontist/import/convert_formulas.rb
@@ -323,6 +325,7 @@ files:
323
325
  - lib/fontist/import/helpers/hash_helper.rb
324
326
  - lib/fontist/import/helpers/system_helper.rb
325
327
  - lib/fontist/import/macos.rb
328
+ - lib/fontist/import/macos/macos_license.txt
326
329
  - lib/fontist/import/manual_formula_builder.rb
327
330
  - lib/fontist/import/otf/font_file.rb
328
331
  - lib/fontist/import/otf_parser.rb
@@ -355,9 +358,6 @@ files:
355
358
  - lib/fontist/utils.rb
356
359
  - lib/fontist/utils/cache.rb
357
360
  - lib/fontist/utils/downloader.rb
358
- - lib/fontist/utils/dsl.rb
359
- - lib/fontist/utils/dsl/collection_font.rb
360
- - lib/fontist/utils/dsl/font.rb
361
361
  - lib/fontist/utils/locking.rb
362
362
  - lib/fontist/utils/system.rb
363
363
  - lib/fontist/utils/ui.rb
@@ -1,29 +0,0 @@
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
@@ -1,36 +0,0 @@
1
- module Fontist
2
- module Utils
3
- module Dsl
4
- class CollectionFont
5
- REQUIRED_ATTRIBUTES = %i[style].freeze
6
-
7
- attr_reader :attributes
8
-
9
- def initialize(attributes)
10
- REQUIRED_ATTRIBUTES.each do |required_attribute|
11
- unless attributes[required_attribute]
12
- raise(Fontist::Errors::MissingAttributeError.new(
13
- "Missing attribute: #{required_attribute}"
14
- ))
15
- end
16
- end
17
-
18
- self.attributes = attributes
19
- end
20
-
21
- def attributes=(attrs)
22
- @attributes = { family_name: attrs[:family_name],
23
- type: attrs[:style],
24
- collection: attrs[:full_name],
25
- full_name: attrs[:full_name],
26
- post_script_name: attrs[:post_script_name],
27
- version: attrs[:version],
28
- description: attrs[:description],
29
- copyright: attrs[:copyright],
30
- font: attrs[:filename],
31
- source_font: attrs[:source_filename] }
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,38 +0,0 @@
1
- module Fontist
2
- module Utils
3
- module Dsl
4
- class Font
5
- REQUIRED_ATTRIBUTES = %i[family_name
6
- style
7
- full_name
8
- filename].freeze
9
-
10
- attr_reader :attributes
11
-
12
- def initialize(attributes)
13
- REQUIRED_ATTRIBUTES.each do |required_attribute|
14
- unless attributes[required_attribute]
15
- raise(Fontist::Errors::MissingAttributeError.new(
16
- "Missing attribute: #{required_attribute}"
17
- ))
18
- end
19
- end
20
-
21
- self.attributes = attributes
22
- end
23
-
24
- def attributes=(attrs)
25
- @attributes = { family_name: attrs[:family_name],
26
- type: attrs[:style],
27
- full_name: attrs[:full_name],
28
- post_script_name: attrs[:post_script_name],
29
- version: attrs[:version],
30
- description: attrs[:description],
31
- copyright: attrs[:copyright],
32
- font: attrs[:filename],
33
- source_font: attrs[:source_filename] }
34
- end
35
- end
36
- end
37
- end
38
- end
@@ -1,85 +0,0 @@
1
- module Fontist
2
- module Utils
3
- module Dsl
4
- def key(key)
5
- instance.key = key
6
- end
7
-
8
- def desc(description)
9
- instance.description = description
10
- end
11
-
12
- def homepage(homepage)
13
- instance.homepage = homepage
14
- end
15
-
16
- def resource(resource_name, &block)
17
- instance.resources[resource_name] ||= {}
18
- instance.temp_resource = instance.resources[resource_name]
19
-
20
- yield(block) if block_given?
21
- instance.temp_resource = {}
22
- end
23
-
24
- def url(url)
25
- instance.temp_resource.merge!(urls: [url])
26
- end
27
-
28
- def urls(urls = [])
29
- instance.temp_resource.merge!(urls: urls)
30
- end
31
-
32
- def sha256(sha256)
33
- instance.temp_resource.merge!(sha256: sha256)
34
- end
35
-
36
- def file_size(file_size)
37
- instance.temp_resource.merge!(file_size: file_size )
38
- end
39
-
40
- def provides_font_collection(name = nil, &block)
41
- instance.temp_resource = {}
42
- yield(block) if block_given?
43
- instance.temp_resource = {}
44
- end
45
-
46
- def filename(name)
47
- instance.temp_resource.merge!(filename: name)
48
- end
49
-
50
- def source_filename(name)
51
- instance.temp_resource.merge!(source_filename: name)
52
- end
53
-
54
- def provides_font(font, options = {})
55
- font_styles = instance.extract_font_styles(options)
56
- instance.font_list.push(name: font, styles: font_styles)
57
- end
58
-
59
- def test
60
- end
61
-
62
- def requires_license_agreement(license)
63
- instance.license = license
64
- instance.license_required = true
65
- end
66
-
67
- def open_license(license)
68
- instance.license = license
69
- instance.license_required = false
70
- end
71
-
72
- def copyright(copyright)
73
- instance.copyright = copyright
74
- end
75
-
76
- def license_url(url)
77
- instance.license_url = url
78
- end
79
-
80
- def display_progress_bar(value )
81
- instance.options = (instance.options || {}).merge(progress_bar: value )
82
- end
83
- end
84
- end
85
- end