Class: Fontist::CLI

Inherits:
Thor
  • Object
show all
Extended by:
ThorExt::Start
Includes:
ClassOptions
Defined in:
lib/fontist/cli.rb,
lib/fontist/cli/class_options.rb

Defined Under Namespace

Modules: ClassOptions

Constant Summary collapse

STATUS_SUCCESS =
0
STATUS_UNKNOWN_ERROR =
1
STATUS_NON_SUPPORTED_FONT_ERROR =
2
STATUS_MISSING_FONT_ERROR =
3
STATUS_LICENSING_ERROR =
4
STATUS_MANIFEST_COULD_NOT_BE_FOUND_ERROR =
5
STATUS_MANIFEST_COULD_NOT_BE_READ_ERROR =
6
STATUS_FONT_INDEX_CORRUPTED =
7
STATUS_REPO_NOT_FOUND =
8
STATUS_MAIN_REPO_NOT_FOUND =
9
STATUS_REPO_COULD_NOT_BE_UPDATED =
10
STATUS_MANUAL_FONT_ERROR =
11
STATUS_SIZE_LIMIT_ERROR =
12
STATUS_FORMULA_NOT_FOUND =
13
STATUS_FONTCONFIG_NOT_FOUND =
14
STATUS_FONTCONFIG_FILE_NOT_FOUND =
15
STATUS_FONTIST_VERSION_ERROR =
15
STATUS_INVALID_CONFIG_ATTRIBUTE =
16
ERROR_TO_STATUS =
{
  Fontist::Errors::UnsupportedFontError => [STATUS_NON_SUPPORTED_FONT_ERROR],
  Fontist::Errors::MissingFontError => [STATUS_MISSING_FONT_ERROR],
  Fontist::Errors::SizeLimitError => [
    STATUS_SIZE_LIMIT_ERROR,
    :append,
    "Please specify higher `--size-limit`, or use the `--newest` or " \
    "`--smallest` options.",
  ],
  Fontist::Errors::ManualFontError => [STATUS_MANUAL_FONT_ERROR],
  Fontist::Errors::LicensingError => [STATUS_LICENSING_ERROR],
  Fontist::Errors::ManifestCouldNotBeFoundError => [STATUS_MANIFEST_COULD_NOT_BE_FOUND_ERROR,
                                                    :overwrite,
                                                    "Manifest could not be found."],
  Fontist::Errors::ManifestCouldNotBeReadError => [STATUS_MANIFEST_COULD_NOT_BE_READ_ERROR,
                                                   :overwrite,
                                                   "Manifest could not be read."],
  Fontist::Errors::FontIndexCorrupted => [STATUS_FONT_INDEX_CORRUPTED],
  Fontist::Errors::RepoNotFoundError => [STATUS_REPO_NOT_FOUND],
  Fontist::Errors::MainRepoNotFoundError => [STATUS_MAIN_REPO_NOT_FOUND],
  Fontist::Errors::FormulaNotFoundError => [STATUS_FORMULA_NOT_FOUND],
  Fontist::Errors::FontconfigNotFoundError => [STATUS_FONTCONFIG_NOT_FOUND],
  Fontist::Errors::FontconfigFileNotFoundError =>
    [STATUS_FONTCONFIG_FILE_NOT_FOUND],
  Fontist::Errors::FontistVersionError => [STATUS_FONTIST_VERSION_ERROR],
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ThorExt::Start

extended, start

Methods included from ClassOptions

#handle_class_options, included, #log_level

Class Method Details

.exit_on_failure?Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/fontist/cli.rb', line 56

def self.exit_on_failure?
  false
end

Instance Method Details

#create_formula(url) ⇒ Object



342
343
344
345
346
347
# File 'lib/fontist/cli.rb', line 342

def create_formula(url)
  handle_class_options(options)
  name = Fontist::Import::CreateFormula.new(url, options).call
  Fontist.ui.say("#{name} formula has been successfully created")
  success
end

#findObject



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/fontist/cli.rb', line 255

def find
  handle_class_options(options)

  require_relative "font_finder"
  require_relative "format_spec"

  finder = FontFinder.new(
    format_spec: FormatSpec.new(format: options[:format]),
    category: options[:category],
  )

  results = if options[:variable]
              finder.variable_fonts
            elsif options[:axes]
              axes = options[:axes].split(",").map(&:strip)
              finder.by_axes(axes)
            elsif options[:category]
              finder.by_category(options[:category])
            else
              error("Please specify --axes, --variable, or --category",
                    STATUS_UNKNOWN_ERROR)
              return
            end

  if options[:json]
    Fontist.ui.say(JSON.pretty_generate(results.map(&:to_h)))
  else
    print_find_results(results)
  end

  success
rescue Fontist::Errors::GeneralError => e
  handle_error(e)
end

#install(*fonts) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/fontist/cli.rb', line 144

def install(*fonts)
  handle_class_options(options)

  if fonts.empty?
    return error("Please specify at least one font to install.",
                 STATUS_UNKNOWN_ERROR)
  end

  # For backward compatibility, use original behavior for single font
  if fonts.size == 1
    confirmation = options[:accept_all_licenses] ? "yes" : "no"
    install_options = options.to_h.transform_keys(&:to_sym)
    install_options[:confirmation] = confirmation
    if options[:location]
      install_options[:location] =
        options[:location]&.to_sym
    end

    Fontist::Font.install(fonts.first, install_options)
    return success
  end

  # Multi-font installation
  confirmation = options[:accept_all_licenses] ? "yes" : "no"
  install_options = options.to_h.transform_keys(&:to_sym)
  install_options[:confirmation] = confirmation
  if options[:location]
    install_options[:location] =
      options[:location]&.to_sym
  end

  result = Fontist::Font.install_many(fonts, install_options)

  # Report results
  if result[:successes].any?
    Fontist.ui.success("Successfully installed #{result[:successes].size} font(s): #{result[:successes].join(', ')}")
  end

  if result[:failures].any?
    Fontist.ui.error("Failed to install #{result[:failures].size} font(s):")
    result[:failures].each do |failure|
      _, mode, message = ERROR_TO_STATUS[failure[:error].class]
      text = if message && mode == :overwrite
               message
             elsif message
               "#{failure[:error].message} #{message}"
             else
               failure[:error].message
             end
      Fontist.ui.error("  - #{failure[:font]}: #{text}")
    end
  end

  # Return appropriate status code
  return STATUS_SUCCESS if result[:failures].empty?

  # If all failed, return the status of the first error
  first_error = result[:failures].first[:error]
  status, = ERROR_TO_STATUS[first_error.class]
  status || STATUS_UNKNOWN_ERROR
rescue Fontist::Errors::GeneralError => e
  handle_error(e)
end

#list(font = nil) ⇒ Object



235
236
237
238
239
240
241
242
# File 'lib/fontist/cli.rb', line 235

def list(font = nil)
  handle_class_options(options)
  formulas = Fontist::Font.list(font)
  print_list(formulas)
  success
rescue Fontist::Errors::GeneralError => e
  handle_error(e)
end

#macos_catalogsObject



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/fontist/cli.rb', line 364

def macos_catalogs
  handle_class_options(options)

  catalogs = Fontist::Macos::Catalog::CatalogManager.available_catalogs

  if catalogs.empty?
    Fontist.ui.error("No macOS font catalogs found.")
    Fontist.ui.say("Expected location: /System/Library/AssetsV2/")
    Fontist.ui.say("\nYou can specify a catalog manually with:")
    Fontist.ui.say("  fontist import macos --plist path/to/com_apple_MobileAsset_FontX.xml")
    return STATUS_UNKNOWN_ERROR
  end

  Fontist.ui.say("Available macOS Font Catalogs:")
  catalogs.each do |catalog_path|
    version = Fontist::Macos::Catalog::CatalogManager.detect_version(catalog_path)
    size = File.size(catalog_path)
    size_str = format_bytes(size)

    Fontist.ui.say("  Font#{version}: #{catalog_path} (#{size_str})")
  end

  Fontist.ui.say("\nTo import a catalog:")
  Fontist.ui.say("  fontist import macos --plist <path>")

  STATUS_SUCCESS
end

#migrate_formulas(input, output = nil) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/fontist/cli.rb', line 306

def migrate_formulas(input, output = nil)
  handle_class_options(options)

  require_relative "import/v4_to_v5_migrator"

  migrator = Fontist::Import::V4ToV5Migrator.new(input, output,
                                                 verbose: options[:verbose],
                                                 dry_run: options[:dry_run])

  results = migrator.migrate_all

  if results[:failed].positive?
    Fontist.ui.error("Migration completed with #{results[:failed]} error(s)")
    STATUS_UNKNOWN_ERROR
  else
    Fontist.ui.success("Migrated #{results[:migrated]} formula(s), " \
                       "skipped #{results[:skipped]} already v5 formula(s)")
    success
  end
rescue Fontist::Errors::GeneralError => e
  handle_error(e)
end

#rebuild_indexObject



356
357
358
359
360
361
# File 'lib/fontist/cli.rb', line 356

def rebuild_index
  handle_class_options(options)
  Fontist::Index.rebuild
  Fontist.ui.say("Formula index has been rebuilt.")
  STATUS_SUCCESS
end

#status(font = nil) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/fontist/cli.rb', line 221

def status(font = nil)
  handle_class_options(options)
  paths = Fontist::Font.status(font)
  if paths.empty?
    return error("No font is installed.",
                 STATUS_MISSING_FONT_ERROR)
  end

  success
rescue Fontist::Errors::GeneralError => e
  handle_error(e)
end

#uninstall(font) ⇒ Object



209
210
211
212
213
214
215
216
217
# File 'lib/fontist/cli.rb', line 209

def uninstall(font)
  handle_class_options(options)
  fonts_paths = Fontist::Font.uninstall(font)
  Fontist.ui.success("These fonts are removed:")
  Fontist.ui.success(fonts_paths.join("\n"))
  success
rescue Fontist::Errors::GeneralError => e
  handle_error(e)
end

#updateObject



291
292
293
294
295
296
297
298
299
# File 'lib/fontist/cli.rb', line 291

def update
  handle_class_options(options)
  Formula.update_formulas_repo
  Fontist.ui.success("Formulas have been successfully updated.")
  success
rescue Fontist::Errors::RepoCouldNotBeUpdatedError => e
  Fontist.ui.error(e.message)
  STATUS_REPO_COULD_NOT_BE_UPDATED
end

#versionObject



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/fontist/cli.rb', line 61

def version
  handle_class_options(options)
  Fontist.ui.say("fontist: #{Fontist::VERSION}")

  # Show formulas repository information if available
  if Dir.exist?(Fontist.formulas_repo_path)
    begin
      require "git"
      repo = Git.open(Fontist.formulas_repo_path)
      repo_url = repo.config["remote.origin.url"] || Fontist.formulas_repo_url
      branch = repo.current_branch
      # Use execute.first for git gem ~> 4.0 compatibility
      log_entry = repo.log(1).execute.first
      revision = log_entry.sha[0..6]
      updated = repo.gcommit(log_entry.sha).date.strftime("%Y-%m-%d")

      Fontist.ui.say("formulas:")
      Fontist.ui.say("  repo: #{repo_url}")
      Fontist.ui.say("  version: #{Fontist.formulas_version}")
      Fontist.ui.say("  branch: #{branch}")
      Fontist.ui.say("  commit: #{revision}")
      Fontist.ui.say("  updated: #{updated}")
    rescue StandardError => e
      Fontist.ui.debug("Could not read formulas repository info: #{e.message}")
    end
  end

  STATUS_SUCCESS
end