fontist 1.19.0 → 1.21.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-pages.yml +48 -0
  3. data/.github/workflows/tebako-pack.yml +61 -0
  4. data/.github/workflows/test-and-release.yml +2 -1
  5. data/LICENSE.txt +1 -2
  6. data/README.adoc +24 -2
  7. data/docs/.gitignore +136 -0
  8. data/docs/.vitepress/config.ts +83 -0
  9. data/docs/guide/api-ruby.md +190 -0
  10. data/docs/guide/ci.md +29 -0
  11. data/docs/guide/fontconfig.md +23 -0
  12. data/docs/guide/index.md +67 -0
  13. data/docs/guide/proxy.md +47 -0
  14. data/docs/guide/why.md +7 -0
  15. data/docs/index.md +40 -0
  16. data/docs/package-lock.json +1791 -0
  17. data/docs/package.json +17 -0
  18. data/docs/public/hero.png +0 -0
  19. data/docs/public/logo.png +0 -0
  20. data/docs/reference/index.md +143 -0
  21. data/exe/fontist +1 -2
  22. data/fontist.gemspec +3 -0
  23. data/lib/fontist/cli/class_options.rb +7 -0
  24. data/lib/fontist/cli/thor_ext.rb +79 -0
  25. data/lib/fontist/cli.rb +2 -0
  26. data/lib/fontist/config.rb +2 -1
  27. data/lib/fontist/font.rb +55 -10
  28. data/lib/fontist/font_installer.rb +22 -51
  29. data/lib/fontist/formula.rb +77 -3
  30. data/lib/fontist/formula_suggestion.rb +55 -0
  31. data/lib/fontist/helpers.rb +2 -0
  32. data/lib/fontist/import/create_formula.rb +77 -35
  33. data/lib/fontist/import/formula_builder.rb +63 -81
  34. data/lib/fontist/import/google/api.rb +25 -0
  35. data/lib/fontist/import/google/create_google_formula.rb +89 -0
  36. data/lib/fontist/import/google_import.rb +63 -32
  37. data/lib/fontist/import/recursive_extraction.rb +0 -16
  38. data/lib/fontist/manifest/locations.rb +2 -0
  39. data/lib/fontist/resources/archive_resource.rb +55 -0
  40. data/lib/fontist/resources/google_resource.rb +64 -0
  41. data/lib/fontist/style_version.rb +4 -0
  42. data/lib/fontist/utils/cache.rb +16 -0
  43. data/lib/fontist/utils/downloader.rb +9 -2
  44. data/lib/fontist/utils/ui.rb +10 -2
  45. data/lib/fontist/version.rb +1 -1
  46. data/lib/fontist.rb +13 -1
  47. metadata +67 -6
  48. data/lib/fontist/import/google/new_fonts_fetcher.rb +0 -146
  49. data/lib/fontist/import/google/skiplist.yml +0 -12
  50. data/lib/fontist/import/google_check.rb +0 -27
data/docs/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "scripts": {
3
+ "dev": "vitepress dev",
4
+ "build": "vitepress build",
5
+ "preview": "vitepress preview",
6
+ "format": "prettier -w ."
7
+ },
8
+ "type": "module",
9
+ "devDependencies": {
10
+ "@types/node": "latest",
11
+ "prettier": "^3.2.4",
12
+ "typescript": "latest",
13
+ "vitepress": "^1.0.0-rc.44",
14
+ "vue": "^3.4.15",
15
+ "vue-tsc": "^1.8.27"
16
+ }
17
+ }
Binary file
Binary file
@@ -0,0 +1,143 @@
1
+ # Fontist CLI reference
2
+
3
+ <!-- This can be converted to a folder & multiple pages at any time. -->
4
+
5
+ ## `fontist cache`
6
+
7
+ The only subcommand available on `fontist cache` is `fontist cache clear`. It clears the `~/.fontist` cache.
8
+
9
+ ```sh
10
+ fontist cache clear
11
+ ```
12
+
13
+ ```
14
+ Cache has been successfully removed.
15
+ ```
16
+
17
+ ## `fontist config`
18
+
19
+ `fontist config` lets you edit the Fontist config file from the command line instead of opening an editor. There are four subcommands available:
20
+
21
+ - `fontist config delete <key>`
22
+ - `fontist config keys`
23
+ - `fontist config set <key> <value>`
24
+ - `fontist config show`
25
+
26
+ Here's an example of these commands being used to edit the config file:
27
+
28
+ ```sh
29
+ fontist config keys
30
+ fontist config set font_path /var/myfonts
31
+ fontist config delete font_path
32
+ fontist config show
33
+ ```
34
+
35
+ ```
36
+ $ fontist config keys
37
+ Available keys:
38
+ fonts_path (default: /home/octocat/.fontist/fonts)
39
+ open_timeout (default: 10)
40
+ read_timeout (default: 10)
41
+
42
+ $ fontist config set fonts_path /var/myfonts
43
+ 'fonts_path' set to '/var/myfonts'.
44
+
45
+ $ fontist config delete fonts_path
46
+ 'fonts_path' reset to default ('/home/octocat/.fontist/fonts').
47
+
48
+ $ fontist config show
49
+ Config is empty.
50
+ ```
51
+
52
+ ### Config reference
53
+
54
+ <!-- Move this into its own '/reference/config' page if this grows a lot. -->
55
+
56
+ - **`fonts_path`:** Where to put the `.ttf` files. Defaults to `~/.fontist/fonts`
57
+
58
+ - **`open_timeout`:** Defaults to 10.
59
+
60
+ - **`read_timeout`:** Defaults to 10.
61
+
62
+ ## `fontist status [font-name]`
63
+
64
+ Prints the paths to a particular installed font or all fonts if the `font-name` is omitted. This searches **all fonts available on your system** even those not managed by Fontist.
65
+
66
+ ```sh
67
+ fontist status
68
+ ```
69
+
70
+ ```
71
+ Fonts found at:
72
+ - /usr/share/fonts/truetype/ubuntu/UbuntuMono-B.ttf
73
+ - /usr/share/fonts/truetype/ubuntu/UbuntuMono-BI.ttf
74
+ - /usr/share/fonts/truetype/ubuntu/UbuntuMono-R.ttf
75
+ - /usr/share/fonts/truetype/ubuntu/UbuntuMono-RI.ttf
76
+ - /home/octocat/.fontist/fonts/Arial.ttf (from ms_truetype formula)
77
+ - /home/octocat/.fontist/fonts/ArialBI.ttf (from ms_truetype formula)
78
+ - /home/octocat/.fontist/fonts/ArialBd.ttf (from ms_truetype formula)
79
+ - /home/octocat/.fontist/fonts/ArialI.ttf (from ms_truetype formula)
80
+ ```
81
+
82
+ Here's an example narrowed to a specific font:
83
+
84
+ ```sh
85
+ fontist status "Open Sans"
86
+ ```
87
+
88
+ ```
89
+ Fonts found at:
90
+ - /home/octocat/.fontist/fonts/OpenSans-Bold.ttf (from open_sans formula)
91
+ - /home/octocat/.fontist/fonts/OpenSans-BoldItalic.ttf (from open_sans formula)
92
+ - /home/octocat/.fontist/fonts/OpenSans-Italic.ttf (from open_sans formula)
93
+ - /home/octocat/.fontist/fonts/OpenSans-Regular.ttf (from open_sans formula)
94
+ ```
95
+
96
+ ## `fontist list [font-name]`
97
+
98
+ Lists the installation status of `font-name` or all fonts if no font name provided.
99
+
100
+ ```sh
101
+ fontist status
102
+ ```
103
+
104
+ ```
105
+ Fonts found at:
106
+ - /usr/share/fonts/truetype/ubuntu/UbuntuMono-B.ttf
107
+ - /usr/share/fonts/truetype/ubuntu/UbuntuMono-BI.ttf
108
+ - /usr/share/fonts/truetype/ubuntu/UbuntuMono-R.ttf
109
+ - /usr/share/fonts/truetype/ubuntu/UbuntuMono-RI.ttf
110
+ - /home/octocat/.fontist/fonts/Arial.ttf (from ms_truetype formula)
111
+ - /home/octocat/.fontist/fonts/ArialBI.ttf (from ms_truetype formula)
112
+ - /home/octocat/.fontist/fonts/ArialBd.ttf (from ms_truetype formula)
113
+ - /home/octocat/.fontist/fonts/ArialI.ttf (from ms_truetype formula)
114
+ ```
115
+
116
+ Here's an example getting the status of a specific font:
117
+
118
+ ```sh
119
+ fontist status "Fira Mono"
120
+ ```
121
+
122
+ ```
123
+ Font "Fira Mono" not found locally.
124
+ 'Fira Mono' font is missing, please run `fontist install 'Fira Mono'` to download the font.
125
+ ```
126
+
127
+ ## Environment variables
128
+
129
+ ### `FONTIST_PATH`
130
+
131
+ By default Fontist uses the `~/.fontist` directory to store fonts and its files. It can be changed with the `FONTIST_PATH` environment variable.
132
+
133
+ ```sh
134
+ FONTIST_PATH=/var/fontist2 fontist update
135
+ ```
136
+
137
+ ## Excluded fonts
138
+
139
+ `fontist` excludes some fonts from usage when they break other software:
140
+
141
+ - `NISC18030.ttf` (GB18030 Bitmap) - macOS [fontist/fontist#344](https://github.com/fontist/fontist/issues/344)
142
+
143
+ [📑 View the up-to-date list of known problematic fonts on GitHub](https://github.com/fontist/fontist/blob/main/lib/fontist/exclude.yml)
data/exe/fontist CHANGED
@@ -4,8 +4,7 @@ require "fontist"
4
4
  require "fontist/cli"
5
5
 
6
6
  fontist_cli = proc {
7
- status_code = Fontist::CLI.start(ARGV)
8
- exit status_code.is_a?(Integer) ? status_code : 1
7
+ Fontist::CLI.start(ARGV)
9
8
  }
10
9
 
11
10
  if ENV["SOCKS_PROXY"]
data/fontist.gemspec CHANGED
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
 
32
32
  spec.add_runtime_dependency "down", "~> 5.0"
33
33
  spec.add_runtime_dependency "extract_ttc", "~> 0.1"
34
+ spec.add_runtime_dependency "fuzzy_match", "~> 2.1"
34
35
  spec.add_runtime_dependency "json", "~> 2.0"
35
36
  spec.add_runtime_dependency "nokogiri", "~> 1.0"
36
37
  spec.add_runtime_dependency "mime-types", "~> 3.0"
@@ -50,4 +51,6 @@ Gem::Specification.new do |spec|
50
51
  spec.add_development_dependency "rubocop", "~> 1.22.1"
51
52
  spec.add_development_dependency "rubocop-rails", "~> 2.9"
52
53
  spec.add_development_dependency "rubocop-performance", "~> 1.10"
54
+ spec.add_development_dependency "vcr"
55
+ spec.add_development_dependency "webmock"
53
56
  end
@@ -22,6 +22,12 @@ module Fontist
22
22
  type: :boolean,
23
23
  desc: "Avoid using cache during download"
24
24
 
25
+ base.class_option :interactive,
26
+ aliases: :i,
27
+ type: :boolean,
28
+ default: true,
29
+ desc: "Interactive mode"
30
+
25
31
  base.class_option :formulas_path,
26
32
  type: :string,
27
33
  desc: "Path to formulas"
@@ -32,6 +38,7 @@ module Fontist
32
38
  Fontist.preferred_family = options[:preferred_family]
33
39
  Fontist.log_level = log_level(options)
34
40
  Fontist.use_cache = !options[:no_cache]
41
+ Fontist.interactive = options[:interactive]
35
42
 
36
43
  if options[:formulas_path]
37
44
  Fontist.formulas_path = Pathname.new(options[:formulas_path])
@@ -0,0 +1,79 @@
1
+ require "fontist/utils/ui"
2
+
3
+ module Fontist
4
+ module ThorExt
5
+ # Sources:
6
+ # - https://github.com/mattbrictson/gem/blob/main/lib/example/thor_ext.rb
7
+ # - https://mattbrictson.com/blog/fixing-thor-cli-behavior
8
+ #
9
+ # Configures Thor to behave more like a typical CLI, with better help
10
+ # and error handling.
11
+ #
12
+ # - Passing -h or --help to a command will show help for that command.
13
+ # - Unrecognized options will be treated as errors.
14
+ # - Error messages will be printed in red to stderr, without stack trace.
15
+ # - Errors will cause Thor to exit with a non-zero status.
16
+ #
17
+ # To take advantage of this behavior, your CLI should subclass Thor
18
+ # and extend this module.
19
+ #
20
+ # class CLI < Thor
21
+ # extend ThorExt::Start
22
+ # end
23
+ #
24
+ # Start your CLI with:
25
+ #
26
+ # CLI.start
27
+ #
28
+ # In tests, prevent Kernel.exit from being called when an error occurs,
29
+ # like this:
30
+ #
31
+ # CLI.start(args, exit_on_failure: false)
32
+ module Start
33
+ def self.extended(base)
34
+ super
35
+ base.check_unknown_options!
36
+ end
37
+
38
+ def start(given_args = ARGV, config = {})
39
+ config[:shell] ||= Thor::Base.shell.new
40
+ handle_help_switches(given_args) do |args|
41
+ dispatch(nil, args, nil, config)
42
+ end
43
+ rescue StandardError => e
44
+ handle_exception_on_start(e, config)
45
+ end
46
+
47
+ private
48
+
49
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
50
+ def handle_help_switches(given_args)
51
+ yield(given_args.dup)
52
+ rescue Thor::UnknownArgumentError => e
53
+ retry_with_args = []
54
+
55
+ if given_args.first == "help"
56
+ retry_with_args = ["help"] if given_args.length > 1
57
+ elsif e.unknown.intersect?(%w[-h --help])
58
+ retry_with_args = ["help", (given_args - e.unknown).first]
59
+ end
60
+ raise unless retry_with_args.any?
61
+
62
+ yield(retry_with_args)
63
+ end
64
+
65
+ def handle_exception_on_start(error, config)
66
+ return if error.is_a?(Errno::EPIPE)
67
+ raise if Fontist.ui.debug? || !config.fetch(:exit_on_failure, true)
68
+
69
+ message = error.message.to_s
70
+ if message.empty? || !error.is_a?(Thor::Error)
71
+ message.prepend("[#{error.class}] ")
72
+ end
73
+ config[:shell]&.say_error(message, :red)
74
+ exit(false)
75
+ end
76
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
77
+ end
78
+ end
79
+ end
data/lib/fontist/cli.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "thor"
2
2
  require "fontist/cli/class_options"
3
+ require "fontist/cli/thor_ext"
3
4
  require "fontist/repo_cli"
4
5
  require "fontist/cache_cli"
5
6
  require "fontist/import_cli"
@@ -9,6 +10,7 @@ require "fontist/config_cli"
9
10
  module Fontist
10
11
  class CLI < Thor
11
12
  include ClassOptions
13
+ extend ThorExt::Start
12
14
 
13
15
  STATUS_SUCCESS = 0
14
16
  STATUS_UNKNOWN_ERROR = 1
@@ -44,7 +44,8 @@ module Fontist
44
44
  def default_values
45
45
  { fonts_path: Fontist.fontist_path.join("fonts"),
46
46
  open_timeout: 10,
47
- read_timeout: 10 }
47
+ read_timeout: 10,
48
+ google_fonts_key: nil }
48
49
  end
49
50
 
50
51
  def persist
data/lib/fontist/font.rb CHANGED
@@ -2,6 +2,7 @@ require "fontist/font_installer"
2
2
  require "fontist/font_path"
3
3
  require "fontist/formula_picker"
4
4
  require "fontist/fontconfig"
5
+ require "fontist/formula_suggestion"
5
6
 
6
7
  module Fontist
7
8
  class Font
@@ -109,17 +110,48 @@ module Fontist
109
110
  end
110
111
 
111
112
  def install_formula
112
- download_formula || raise_formula_not_found
113
+ download_formula || make_suggestions || raise_formula_not_found
113
114
  end
114
115
 
115
116
  def download_formula
116
- formula = Formula.find_by_key(@name)
117
+ formula = Formula.find_by_key_or_name(@name)
117
118
  return unless formula
118
119
  return unless formula.downloadable?
119
120
 
120
121
  request_formula_installation(formula)
121
122
  end
122
123
 
124
+ def make_suggestions
125
+ return unless Fontist.interactive?
126
+
127
+ suggestions = fuzzy_search_formulas
128
+ return if suggestions.empty?
129
+
130
+ choice = offer_to_choose(suggestions)
131
+ return unless choice
132
+
133
+ request_formula_installation(choice)
134
+ end
135
+
136
+ def fuzzy_search_formulas
137
+ @formula_suggestion ||= FormulaSuggestion.new
138
+ @formula_suggestion.find(@name)
139
+ end
140
+
141
+ def offer_to_choose(formulas)
142
+ Fontist.ui.say("Formula '#{@name}' not found. Did you mean?")
143
+
144
+ formulas.each_with_index do |formula, index|
145
+ Fontist.ui.say("[#{index}] #{formula.name}")
146
+ end
147
+
148
+ choice = Fontist.ui.ask("Please type number or " \
149
+ "press ENTER to skip installation:").chomp
150
+ return unless choice.to_i.to_s == choice
151
+
152
+ formulas[choice.to_i]
153
+ end
154
+
123
155
  def raise_formula_not_found
124
156
  raise Errors::FormulaNotFoundError.new(@name)
125
157
  end
@@ -184,6 +216,11 @@ module Fontist
184
216
  confirmation = check_and_confirm_required_license(formula)
185
217
  paths = font_installer(formula).install(confirmation: confirmation)
186
218
 
219
+ if paths.nil? || paths.empty?
220
+ Fontist.ui.error("Fonts not found in formula #{formula}")
221
+ return
222
+ end
223
+
187
224
  Fontist.ui.say("Fonts installed at:")
188
225
  paths.each do |path|
189
226
  Fontist.ui.say("- #{path}")
@@ -193,7 +230,7 @@ module Fontist
193
230
  def check_and_confirm_required_license(formula)
194
231
  return @confirmation unless formula.license_required
195
232
 
196
- show_license(formula.license) unless @hide_licenses
233
+ show_license(formula) unless @hide_licenses
197
234
  return @confirmation if @confirmation.casecmp?("yes")
198
235
 
199
236
  confirmation = ask_for_agreement
@@ -204,8 +241,8 @@ module Fontist
204
241
  )
205
242
  end
206
243
 
207
- def show_license(license)
208
- Fontist.ui.say(license_agrement_message(license))
244
+ def show_license(formula)
245
+ Fontist.ui.say(license_agrement_message(formula))
209
246
  end
210
247
 
211
248
  def ask_for_agreement
@@ -215,20 +252,28 @@ module Fontist
215
252
  )
216
253
  end
217
254
 
218
- def license_agrement_message(license)
255
+ def license_agrement_message(formula)
256
+ human_name = human_name(formula)
257
+
219
258
  <<~MSG
220
- FONT LICENSE ACCEPTANCE REQUIRED FOR "#{name}":
259
+ FONT LICENSE ACCEPTANCE REQUIRED FOR "#{human_name}":
221
260
 
222
261
  Fontist can install this font if you accept its licensing conditions.
223
262
 
224
- FONT LICENSE BEGIN ("#{name}")
263
+ FONT LICENSE BEGIN ("#{human_name}")
225
264
  -----------------------------------------------------------------------
226
- #{license}
265
+ #{formula.license}
227
266
  -----------------------------------------------------------------------
228
- FONT LICENSE END ("#{name}")
267
+ FONT LICENSE END ("#{human_name}")
229
268
  MSG
230
269
  end
231
270
 
271
+ def human_name(formula)
272
+ return formula.name if @by_formula
273
+
274
+ formula.font_by_name(@name).name
275
+ end
276
+
232
277
  def update_fontconfig
233
278
  return unless @update_fontconfig
234
279
 
@@ -1,5 +1,7 @@
1
1
  require "fontist/utils"
2
2
  require "excavate"
3
+ require_relative "resources/archive_resource"
4
+ require_relative "resources/google_resource"
3
5
 
4
6
  module Fontist
5
7
  class FontInstaller
@@ -48,63 +50,32 @@ module Fontist
48
50
  end
49
51
 
50
52
  def install_font
51
- fonts_paths = run_in_temp_dir { extract }
53
+ fonts_paths = do_install_font
52
54
  fonts_paths.empty? ? nil : fonts_paths
53
55
  end
54
56
 
55
- def run_in_temp_dir
56
- Dir.mktmpdir(nil, Dir.tmpdir) do |dir|
57
- @temp_dir = Pathname.new(dir)
58
-
59
- result = yield
60
-
61
- @temp_dir = nil
62
-
63
- result
64
- end
65
- end
66
-
67
- def extract
68
- archive = download_file(@formula.resources.first)
69
-
70
- install_fonts_from_archive(archive)
71
- end
72
-
73
- def install_fonts_from_archive(archive)
74
- Fontist.ui.say(%(Installing font "#{@formula.key}".))
57
+ def do_install_font
58
+ Fontist.ui.say(%(Installing from formula "#{@formula.key}".))
75
59
 
76
60
  Array.new.tap do |fonts_paths|
77
- Excavate::Archive.new(archive.path).files(recursive_packages: true) do |path|
61
+ resource.files(source_files) do |path|
78
62
  fonts_paths << install_font_file(path) if font_file?(path)
79
63
  end
80
64
  end
81
65
  end
82
66
 
83
- def download_file(source)
84
- errors = []
85
- source.urls.each do |request|
86
- url = request.respond_to?(:url) ? request.url : request
87
- Fontist.ui.say(%(Downloading font "#{@formula.key}" from #{url}))
88
-
89
- result = try_download_file(request, source)
90
- return result unless result.is_a?(Errors::InvalidResourceError)
91
-
92
- errors << result
93
- end
67
+ def resource
68
+ resource_class = if @formula.source == "google"
69
+ Resources::GoogleResource
70
+ else
71
+ Resources::ArchiveResource
72
+ end
94
73
 
95
- raise Errors::InvalidResourceError, errors.join(" ")
74
+ resource_class.new(resource_options, no_progress: @no_progress)
96
75
  end
97
76
 
98
- def try_download_file(request, source)
99
- Fontist::Utils::Downloader.download(
100
- request,
101
- sha: source.sha256,
102
- file_size: source.file_size,
103
- progress_bar: !@no_progress
104
- )
105
- rescue Errors::InvalidResourceError => e
106
- Fontist.ui.say(e.message)
107
- e
77
+ def resource_options
78
+ @formula.resources.first
108
79
  end
109
80
 
110
81
  def font_file?(path)
@@ -116,16 +87,16 @@ module Fontist
116
87
  end
117
88
 
118
89
  def source_files
119
- @source_files ||= @formula.fonts.flat_map do |font|
120
- next [] if @font_name && !font.name.casecmp?(@font_name)
121
-
122
- font_files(font)
90
+ @source_files ||= fonts.flat_map do |font|
91
+ font.styles.map do |style|
92
+ style.source_font || style.font
93
+ end
123
94
  end
124
95
  end
125
96
 
126
- def font_files(font)
127
- font.styles.map do |style|
128
- style.source_font || style.font
97
+ def fonts
98
+ @formula.fonts.select do |font|
99
+ @font_name.nil? || font.name.casecmp?(@font_name)
129
100
  end
130
101
  end
131
102
 
@@ -5,6 +5,11 @@ require "git"
5
5
 
6
6
  module Fontist
7
7
  class Formula
8
+ NAMESPACES = {
9
+ "sil" => "SIL",
10
+ "macos" => "macOS",
11
+ }.freeze
12
+
8
13
  def self.update_formulas_repo
9
14
  Update.call
10
15
  end
@@ -15,6 +20,12 @@ module Fontist
15
20
  end
16
21
  end
17
22
 
23
+ def self.all_keys
24
+ Dir[Fontist.formulas_path.join("**/*.yml").to_s].map do |path|
25
+ path.sub("#{Fontist.formulas_path}/", "").sub(".yml", "")
26
+ end
27
+ end
28
+
18
29
  def self.find(font_name)
19
30
  Indexes::FontIndex.from_yaml.load_formulas(font_name).first
20
31
  end
@@ -45,6 +56,10 @@ module Fontist
45
56
  end.flatten
46
57
  end
47
58
 
59
+ def self.find_by_key_or_name(name)
60
+ find_by_key(name) || find_by_name(name)
61
+ end
62
+
48
63
  def self.find_by_key(key)
49
64
  path = Fontist.formulas_path.join("#{key}.yml")
50
65
  return unless File.exist?(path)
@@ -52,6 +67,16 @@ module Fontist
52
67
  new_from_file(path)
53
68
  end
54
69
 
70
+ def self.find_by_name(name)
71
+ key = name_to_key(name)
72
+
73
+ find_by_key(key)
74
+ end
75
+
76
+ def self.name_to_key(name)
77
+ name.downcase.gsub(" ", "_")
78
+ end
79
+
55
80
  def self.find_by_font_file(font_file)
56
81
  key = Indexes::FilenameIndex
57
82
  .from_yaml
@@ -69,7 +94,7 @@ module Fontist
69
94
 
70
95
  def initialize(data, path)
71
96
  @data = data
72
- @path = path
97
+ @path = real_path(path)
73
98
  end
74
99
 
75
100
  def to_index_formula
@@ -84,12 +109,24 @@ module Fontist
84
109
  @data.key?("resources")
85
110
  end
86
111
 
112
+ def source
113
+ return unless @data["resources"]
114
+
115
+ @data["resources"].values.first["source"]
116
+ end
117
+
87
118
  def path
88
119
  @path
89
120
  end
90
121
 
91
122
  def key
92
- key_from_path
123
+ @key ||= {}
124
+ @key[@path] ||= key_from_path
125
+ end
126
+
127
+ def name
128
+ @name ||= {}
129
+ @name[key] ||= namespace.empty? ? base_name : "#{namespace}/#{base_name}"
93
130
  end
94
131
 
95
132
  def description
@@ -142,6 +179,12 @@ module Fontist
142
179
  @data["instructions"]
143
180
  end
144
181
 
182
+ def font_by_name(name)
183
+ fonts.find do |font|
184
+ font.name.casecmp?(name)
185
+ end
186
+ end
187
+
145
188
  def fonts_by_name(name)
146
189
  fonts.select do |font|
147
190
  font.name.casecmp?(name)
@@ -166,9 +209,40 @@ module Fontist
166
209
 
167
210
  private
168
211
 
212
+ def real_path(path)
213
+ Dir.glob(path).first
214
+ end
215
+
169
216
  def key_from_path
170
217
  escaped = Regexp.escape("#{Fontist.formulas_path}/")
171
- @path.sub(Regexp.new("^#{escaped}"), "").sub(/\.yml$/, "")
218
+ @path.sub(Regexp.new("^#{escaped}"), "").sub(/\.yml$/, "").to_s
219
+ end
220
+
221
+ def namespace
222
+ namespace_from_mappings || namespace_from_key
223
+ end
224
+
225
+ def namespace_from_mappings
226
+ parts = key.split("/")
227
+ namespace_from_key = parts.take(parts.size - 1).join("/")
228
+ NAMESPACES[namespace_from_key]
229
+ end
230
+
231
+ def namespace_from_key
232
+ parts = key.downcase.gsub("_", " ").split("/")
233
+ parts.take(parts.size - 1).map do |namespace|
234
+ namespace.split.map(&:capitalize).join(" ")
235
+ end.join("/")
236
+ end
237
+
238
+ def base_name
239
+ @data["name"] || base_name_from_key
240
+ end
241
+
242
+ def base_name_from_key
243
+ key.split("/").last
244
+ .downcase.gsub("_", " ")
245
+ .split.map(&:capitalize).join(" ")
172
246
  end
173
247
 
174
248
  def fonts_by_family