fontist 1.19.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.
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