# frozen_string_literal: true require "thor" require "erb" require "active_support/core_ext/string/filters" require "active_support/core_ext/string/inflections" require "jets/command/actions" module Jets module Command class Base < Thor extend Memoist class Error < Thor::Error # :nodoc: end class CorrectableError < Error # :nodoc: attr_reader :key, :options def initialize(message, key, options) @key = key @options = options super(message) end if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable) include DidYouMean::Correctable def corrections @corrections ||= DidYouMean::SpellChecker.new(dictionary: options).correct(key) end end end include Actions include AwsHelpers include ApiHelpers no_commands do cattr_accessor :full_namespace end class << self def long_desc(long_description, options = {}) options[:wrap] = false super end def exit_on_failure? # :nodoc: false end # Returns true when the app is a Jets engine. def engine? defined?(ENGINE_ROOT) end # Tries to get the description from a USAGE file one folder above the command # root. def desc(usage = nil, description = nil, options = {}) if usage super else @desc ||= ERB.new(File.read(usage_path), trim_mode: "-").result(binding) if usage_path end end # Convenience method to get the namespace from the class name. It's the # same as Thor default except that the Command at the end of the class # is removed. def namespace(name = nil) if name super else @namespace ||= super.chomp("_command").sub(/:command:/, ":") end end # Convenience method to hide this command from the available ones when # running jets command. def hide_command! Jets::Command.hidden_commands << self end def inherited(base) # :nodoc: super if base.name && !base.name.end_with?("Base") Jets::Command.subclasses << base end end def perform(full_namespace, command, args, config) # :nodoc: if Jets::Command::HELP_MAPPINGS.include?(args.first) command, args = "help", [] self.full_namespace = full_namespace # store for help. clean:log => log end dispatch(command, args.dup, nil, config) rescue Thor::InvocationError => e puts e.message.color(:red) # message already has ERROR prefix self.full_namespace = full_namespace # store for help. clean:log => log dispatch("help", [], nil, config) exit 1 end def printing_commands namespaced_commands end def executable "jets #{full_namespace || command_name}" end # Use Jets' default banner. def banner(*) command_name = full_namespace ? full_namespace.split(':').last : command_name command = commands[command_name] options_arg = '[options]' unless command && command.options.empty? output = "#{executable} #{arguments.map(&:usage).join(' ')} #{options_arg}".squish " #{output}" # add 2 more spaces in front end # Sets the base_name taking into account the current class namespace. # # Jets::Command::TestCommand.base_name # => 'jets' def base_name @base_name ||= if base = name.to_s.split("::").first base.underscore end end # Return command name without namespaces. # # Jets::Command::TestCommand.command_name # => 'test' def command_name @command_name ||= if command = name.to_s.split("::").last command.chomp!("Command") command.underscore end end # Path to lookup a USAGE description in a file. def usage_path if default_command_root path = File.join(default_command_root, "USAGE") path if File.exist?(path) end end # Default file root to place extra files a command might need, placed # one folder above the command file. # # For a Jets::Command::TestCommand placed in jets/command/test_command.rb # would return jets/test. def default_command_root path = File.expand_path(relative_command_path, __dir__) path if File.exist?(path) end private # Allow the command method to be called perform. def create_command(meth) if meth == "perform" alias_method command_name, meth else # Prevent exception about command without usage. # Some commands define their documentation differently. @usage ||= "" @desc ||= "" super end end def command_root_namespace (namespace.split(":") - %w(jets)).join(":") end def relative_command_path File.join("../commands", *command_root_namespace.split(":")) end def namespaced_commands commands.keys.map do |key| if command_root_namespace.match?(/(\A|:)#{key}\z/) command_root_namespace else "#{command_root_namespace}:#{key}" end end end end def help if full_namespace = self.class.full_namespace command_name = full_namespace.split(':').last # clean:log => log self.class.command_help(shell, command_name) elsif command_name = self.class.command_name self.class.command_help(shell, command_name) else super end end end end end