thor 1.2.2 → 1.3.0
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.
Potentially problematic release.
This version of thor might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/thor/actions/create_file.rb +2 -1
- data/lib/thor/actions/directory.rb +1 -1
- data/lib/thor/actions/empty_directory.rb +1 -1
- data/lib/thor/actions/file_manipulation.rb +6 -8
- data/lib/thor/actions/inject_into_file.rb +15 -4
- data/lib/thor/actions.rb +14 -15
- data/lib/thor/base.rb +136 -9
- data/lib/thor/command.rb +13 -4
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +4 -0
- data/lib/thor/error.rb +18 -23
- data/lib/thor/invocation.rb +1 -1
- data/lib/thor/nested_context.rb +2 -2
- data/lib/thor/parser/argument.rb +20 -1
- data/lib/thor/parser/arguments.rb +32 -16
- data/lib/thor/parser/option.rb +20 -5
- data/lib/thor/parser/options.rb +42 -4
- data/lib/thor/runner.rb +11 -11
- data/lib/thor/shell/basic.rb +25 -149
- data/lib/thor/shell/color.rb +4 -46
- data/lib/thor/shell/column_printer.rb +29 -0
- data/lib/thor/shell/html.rb +3 -45
- data/lib/thor/shell/lcs_diff.rb +49 -0
- data/lib/thor/shell/table_printer.rb +134 -0
- data/lib/thor/shell/terminal.rb +42 -0
- data/lib/thor/shell/wrapped_printer.rb +38 -0
- data/lib/thor/shell.rb +1 -1
- data/lib/thor/util.rb +4 -3
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +154 -7
- data/thor.gemspec +14 -10
- metadata +11 -6
    
        data/lib/thor/shell/html.rb
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            require_relative "basic"
         | 
| 2 | 
            +
            require_relative "lcs_diff"
         | 
| 2 3 |  | 
| 3 4 | 
             
            class Thor
         | 
| 4 5 | 
             
              module Shell
         | 
| @@ -6,6 +7,8 @@ class Thor | |
| 6 7 | 
             
                # Thor::Shell::Basic to see all available methods.
         | 
| 7 8 | 
             
                #
         | 
| 8 9 | 
             
                class HTML < Basic
         | 
| 10 | 
            +
                  include LCSDiff
         | 
| 11 | 
            +
             | 
| 9 12 | 
             
                  # The start of an HTML bold sequence.
         | 
| 10 13 | 
             
                  BOLD       = "font-weight: bold"
         | 
| 11 14 |  | 
| @@ -76,51 +79,6 @@ class Thor | |
| 76 79 | 
             
                  def can_display_colors?
         | 
| 77 80 | 
             
                    true
         | 
| 78 81 | 
             
                  end
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                  # Overwrite show_diff to show diff with colors if Diff::LCS is
         | 
| 81 | 
            -
                  # available.
         | 
| 82 | 
            -
                  #
         | 
| 83 | 
            -
                  def show_diff(destination, content) #:nodoc:
         | 
| 84 | 
            -
                    if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil?
         | 
| 85 | 
            -
                      actual  = File.binread(destination).to_s.split("\n")
         | 
| 86 | 
            -
                      content = content.to_s.split("\n")
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                      Diff::LCS.sdiff(actual, content).each do |diff|
         | 
| 89 | 
            -
                        output_diff_line(diff)
         | 
| 90 | 
            -
                      end
         | 
| 91 | 
            -
                    else
         | 
| 92 | 
            -
                      super
         | 
| 93 | 
            -
                    end
         | 
| 94 | 
            -
                  end
         | 
| 95 | 
            -
             | 
| 96 | 
            -
                  def output_diff_line(diff) #:nodoc:
         | 
| 97 | 
            -
                    case diff.action
         | 
| 98 | 
            -
                    when "-"
         | 
| 99 | 
            -
                      say "- #{diff.old_element.chomp}", :red, true
         | 
| 100 | 
            -
                    when "+"
         | 
| 101 | 
            -
                      say "+ #{diff.new_element.chomp}", :green, true
         | 
| 102 | 
            -
                    when "!"
         | 
| 103 | 
            -
                      say "- #{diff.old_element.chomp}", :red, true
         | 
| 104 | 
            -
                      say "+ #{diff.new_element.chomp}", :green, true
         | 
| 105 | 
            -
                    else
         | 
| 106 | 
            -
                      say "  #{diff.old_element.chomp}", nil, true
         | 
| 107 | 
            -
                    end
         | 
| 108 | 
            -
                  end
         | 
| 109 | 
            -
             | 
| 110 | 
            -
                  # Check if Diff::LCS is loaded. If it is, use it to create pretty output
         | 
| 111 | 
            -
                  # for diff.
         | 
| 112 | 
            -
                  #
         | 
| 113 | 
            -
                  def diff_lcs_loaded? #:nodoc:
         | 
| 114 | 
            -
                    return true if defined?(Diff::LCS)
         | 
| 115 | 
            -
                    return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
         | 
| 116 | 
            -
             | 
| 117 | 
            -
                    @diff_lcs_loaded = begin
         | 
| 118 | 
            -
                      require "diff/lcs"
         | 
| 119 | 
            -
                      true
         | 
| 120 | 
            -
                    rescue LoadError
         | 
| 121 | 
            -
                      false
         | 
| 122 | 
            -
                    end
         | 
| 123 | 
            -
                  end
         | 
| 124 82 | 
             
                end
         | 
| 125 83 | 
             
              end
         | 
| 126 84 | 
             
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            module LCSDiff
         | 
| 2 | 
            +
            protected
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              # Overwrite show_diff to show diff with colors if Diff::LCS is
         | 
| 5 | 
            +
              # available.
         | 
| 6 | 
            +
              def show_diff(destination, content) #:nodoc:
         | 
| 7 | 
            +
                if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil?
         | 
| 8 | 
            +
                  actual  = File.binread(destination).to_s.split("\n")
         | 
| 9 | 
            +
                  content = content.to_s.split("\n")
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  Diff::LCS.sdiff(actual, content).each do |diff|
         | 
| 12 | 
            +
                    output_diff_line(diff)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                else
         | 
| 15 | 
            +
                  super
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            private
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              def output_diff_line(diff) #:nodoc:
         | 
| 22 | 
            +
                case diff.action
         | 
| 23 | 
            +
                when "-"
         | 
| 24 | 
            +
                  say "- #{diff.old_element.chomp}", :red, true
         | 
| 25 | 
            +
                when "+"
         | 
| 26 | 
            +
                  say "+ #{diff.new_element.chomp}", :green, true
         | 
| 27 | 
            +
                when "!"
         | 
| 28 | 
            +
                  say "- #{diff.old_element.chomp}", :red, true
         | 
| 29 | 
            +
                  say "+ #{diff.new_element.chomp}", :green, true
         | 
| 30 | 
            +
                else
         | 
| 31 | 
            +
                  say "  #{diff.old_element.chomp}", nil, true
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              # Check if Diff::LCS is loaded. If it is, use it to create pretty output
         | 
| 36 | 
            +
              # for diff.
         | 
| 37 | 
            +
              def diff_lcs_loaded? #:nodoc:
         | 
| 38 | 
            +
                return true if defined?(Diff::LCS)
         | 
| 39 | 
            +
                return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                @diff_lcs_loaded = begin
         | 
| 42 | 
            +
                  require "diff/lcs"
         | 
| 43 | 
            +
                  true
         | 
| 44 | 
            +
                rescue LoadError
         | 
| 45 | 
            +
                  false
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,134 @@ | |
| 1 | 
            +
            require_relative "column_printer"
         | 
| 2 | 
            +
            require_relative "terminal"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Thor
         | 
| 5 | 
            +
              module Shell
         | 
| 6 | 
            +
                class TablePrinter < ColumnPrinter
         | 
| 7 | 
            +
                  BORDER_SEPARATOR = :separator
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def initialize(stdout, options = {})
         | 
| 10 | 
            +
                    super
         | 
| 11 | 
            +
                    @formats = []
         | 
| 12 | 
            +
                    @maximas = []
         | 
| 13 | 
            +
                    @colwidth = options[:colwidth]
         | 
| 14 | 
            +
                    @truncate = options[:truncate] == true ? Terminal.terminal_width : options[:truncate]
         | 
| 15 | 
            +
                    @padding = 1
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def print(array)
         | 
| 19 | 
            +
                    return if array.empty?
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    prepare(array)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    print_border_separator if options[:borders]
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    array.each do |row|
         | 
| 26 | 
            +
                      if options[:borders] && row == BORDER_SEPARATOR
         | 
| 27 | 
            +
                        print_border_separator
         | 
| 28 | 
            +
                        next
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      sentence = "".dup
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      row.each_with_index do |column, index|
         | 
| 34 | 
            +
                        sentence << format_cell(column, row.size, index)
         | 
| 35 | 
            +
                      end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      sentence = truncate(sentence)
         | 
| 38 | 
            +
                      sentence << "|" if options[:borders]
         | 
| 39 | 
            +
                      stdout.puts indentation + sentence
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                    print_border_separator if options[:borders]
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                private
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def prepare(array)
         | 
| 48 | 
            +
                    array = array.reject{|row| row == BORDER_SEPARATOR }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    @formats << "%-#{@colwidth + 2}s".dup if @colwidth
         | 
| 51 | 
            +
                    start = @colwidth ? 1 : 0
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    colcount = array.max { |a, b| a.size <=> b.size }.size
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    start.upto(colcount - 1) do |index|
         | 
| 56 | 
            +
                      maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                      @maximas << maxima
         | 
| 59 | 
            +
                      @formats << if options[:borders]
         | 
| 60 | 
            +
                         "%-#{maxima}s".dup
         | 
| 61 | 
            +
                      elsif index == colcount - 1
         | 
| 62 | 
            +
                        # Don't output 2 trailing spaces when printing the last column
         | 
| 63 | 
            +
                        "%-s".dup
         | 
| 64 | 
            +
                      else
         | 
| 65 | 
            +
                        "%-#{maxima + 2}s".dup
         | 
| 66 | 
            +
                      end
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    @formats << "%s"
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def format_cell(column, row_size, index)
         | 
| 73 | 
            +
                    maxima = @maximas[index]
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    f = if column.is_a?(Numeric)
         | 
| 76 | 
            +
                      if options[:borders]
         | 
| 77 | 
            +
                        # With borders we handle padding separately
         | 
| 78 | 
            +
                        "%#{maxima}s"
         | 
| 79 | 
            +
                      elsif index == row_size - 1
         | 
| 80 | 
            +
                        # Don't output 2 trailing spaces when printing the last column
         | 
| 81 | 
            +
                        "%#{maxima}s"
         | 
| 82 | 
            +
                      else
         | 
| 83 | 
            +
                        "%#{maxima}s  "
         | 
| 84 | 
            +
                      end
         | 
| 85 | 
            +
                    else
         | 
| 86 | 
            +
                      @formats[index]
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    cell = "".dup
         | 
| 90 | 
            +
                    cell << "|" + " " * @padding if options[:borders]
         | 
| 91 | 
            +
                    cell << f % column.to_s
         | 
| 92 | 
            +
                    cell << " " * @padding if options[:borders]
         | 
| 93 | 
            +
                    cell
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  def print_border_separator
         | 
| 97 | 
            +
                    separator = @maximas.map do |maxima|
         | 
| 98 | 
            +
                      "+" + "-" * (maxima + 2 * @padding)
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                    stdout.puts indentation + separator.join + "+"
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  def truncate(string)
         | 
| 104 | 
            +
                    return string unless @truncate
         | 
| 105 | 
            +
                    as_unicode do
         | 
| 106 | 
            +
                      chars = string.chars.to_a
         | 
| 107 | 
            +
                      if chars.length <= @truncate
         | 
| 108 | 
            +
                        chars.join
         | 
| 109 | 
            +
                      else
         | 
| 110 | 
            +
                        chars[0, @truncate - 3 - @indent].join + "..."
         | 
| 111 | 
            +
                      end
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  def indentation
         | 
| 116 | 
            +
                    " " * @indent
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  if "".respond_to?(:encode)
         | 
| 120 | 
            +
                    def as_unicode
         | 
| 121 | 
            +
                      yield
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
                  else
         | 
| 124 | 
            +
                    def as_unicode
         | 
| 125 | 
            +
                      old = $KCODE # rubocop:disable Style/GlobalVars
         | 
| 126 | 
            +
                      $KCODE = "U" # rubocop:disable Style/GlobalVars
         | 
| 127 | 
            +
                      yield
         | 
| 128 | 
            +
                    ensure
         | 
| 129 | 
            +
                      $KCODE = old # rubocop:disable Style/GlobalVars
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
              end
         | 
| 134 | 
            +
            end
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            class Thor
         | 
| 2 | 
            +
              module Shell
         | 
| 3 | 
            +
                module Terminal
         | 
| 4 | 
            +
                  DEFAULT_TERMINAL_WIDTH = 80
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  class << self
         | 
| 7 | 
            +
                    # This code was copied from Rake, available under MIT-LICENSE
         | 
| 8 | 
            +
                    # Copyright (c) 2003, 2004 Jim Weirich
         | 
| 9 | 
            +
                    def terminal_width
         | 
| 10 | 
            +
                      result = if ENV["THOR_COLUMNS"]
         | 
| 11 | 
            +
                        ENV["THOR_COLUMNS"].to_i
         | 
| 12 | 
            +
                      else
         | 
| 13 | 
            +
                        unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
                      result < 10 ? DEFAULT_TERMINAL_WIDTH : result
         | 
| 16 | 
            +
                    rescue
         | 
| 17 | 
            +
                      DEFAULT_TERMINAL_WIDTH
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    def unix?
         | 
| 21 | 
            +
                      RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris)/i
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  private
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    # Calculate the dynamic width of the terminal
         | 
| 27 | 
            +
                    def dynamic_width
         | 
| 28 | 
            +
                      @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def dynamic_width_stty
         | 
| 32 | 
            +
                      `stty size 2>/dev/null`.split[1].to_i
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def dynamic_width_tput
         | 
| 36 | 
            +
                      `tput cols 2>/dev/null`.to_i
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            require_relative "column_printer"
         | 
| 2 | 
            +
            require_relative "terminal"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Thor
         | 
| 5 | 
            +
              module Shell
         | 
| 6 | 
            +
                class WrappedPrinter < ColumnPrinter
         | 
| 7 | 
            +
                  def print(message)
         | 
| 8 | 
            +
                    width = Terminal.terminal_width - @indent
         | 
| 9 | 
            +
                    paras = message.split("\n\n")
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    paras.map! do |unwrapped|
         | 
| 12 | 
            +
                      words = unwrapped.split(" ")
         | 
| 13 | 
            +
                      counter = words.first.length
         | 
| 14 | 
            +
                      words.inject do |memo, word|
         | 
| 15 | 
            +
                        word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n")
         | 
| 16 | 
            +
                        counter = 0 if word.include? "\n"
         | 
| 17 | 
            +
                        if (counter + word.length + 1) < width
         | 
| 18 | 
            +
                          memo = "#{memo} #{word}"
         | 
| 19 | 
            +
                          counter += (word.length + 1)
         | 
| 20 | 
            +
                        else
         | 
| 21 | 
            +
                          memo = "#{memo}\n#{word}"
         | 
| 22 | 
            +
                          counter = word.length
         | 
| 23 | 
            +
                        end
         | 
| 24 | 
            +
                        memo
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                    end.compact!
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    paras.each do |para|
         | 
| 29 | 
            +
                      para.split("\n").each do |line|
         | 
| 30 | 
            +
                        stdout.puts line.insert(0, " " * @indent)
         | 
| 31 | 
            +
                      end
         | 
| 32 | 
            +
                      stdout.puts unless para == paras.last
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| 38 | 
            +
             | 
    
        data/lib/thor/shell.rb
    CHANGED
    
    
    
        data/lib/thor/util.rb
    CHANGED
    
    | @@ -130,9 +130,10 @@ class Thor | |
| 130 130 | 
             
                  #
         | 
| 131 131 | 
             
                  def find_class_and_command_by_namespace(namespace, fallback = true)
         | 
| 132 132 | 
             
                    if namespace.include?(":") # look for a namespaced command
         | 
| 133 | 
            -
                      pieces  = namespace.split(":")
         | 
| 134 | 
            -
                       | 
| 135 | 
            -
                       | 
| 133 | 
            +
                      *pieces, command  = namespace.split(":")
         | 
| 134 | 
            +
                      namespace = pieces.join(":")
         | 
| 135 | 
            +
                      namespace = "default" if namespace.empty?
         | 
| 136 | 
            +
                      klass = Thor::Base.subclasses.detect { |thor| thor.namespace == namespace && thor.commands.keys.include?(command) }
         | 
| 136 137 | 
             
                    end
         | 
| 137 138 | 
             
                    unless klass # look for a Thor::Group with the right name
         | 
| 138 139 | 
             
                      klass = Thor::Util.find_by_namespace(namespace)
         | 
    
        data/lib/thor/version.rb
    CHANGED
    
    
    
        data/lib/thor.rb
    CHANGED
    
    | @@ -65,8 +65,15 @@ class Thor | |
| 65 65 |  | 
| 66 66 | 
             
                # Defines the long description of the next command.
         | 
| 67 67 | 
             
                #
         | 
| 68 | 
            +
                # Long description is by default indented, line-wrapped and repeated whitespace merged.
         | 
| 69 | 
            +
                # In order to print long description verbatim, with indentation and spacing exactly
         | 
| 70 | 
            +
                # as found in the code, use the +wrap+ option
         | 
| 71 | 
            +
                #
         | 
| 72 | 
            +
                #   long_desc 'your very long description', wrap: false
         | 
| 73 | 
            +
                #
         | 
| 68 74 | 
             
                # ==== Parameters
         | 
| 69 75 | 
             
                # long description<String>
         | 
| 76 | 
            +
                # options<Hash>
         | 
| 70 77 | 
             
                #
         | 
| 71 78 | 
             
                def long_desc(long_description, options = {})
         | 
| 72 79 | 
             
                  if options[:for]
         | 
| @@ -74,6 +81,7 @@ class Thor | |
| 74 81 | 
             
                    command.long_description = long_description if long_description
         | 
| 75 82 | 
             
                  else
         | 
| 76 83 | 
             
                    @long_desc = long_description
         | 
| 84 | 
            +
                    @long_desc_wrap = options[:wrap] != false
         | 
| 77 85 | 
             
                  end
         | 
| 78 86 | 
             
                end
         | 
| 79 87 |  | 
| @@ -133,7 +141,7 @@ class Thor | |
| 133 141 | 
             
                #     # magic
         | 
| 134 142 | 
             
                #   end
         | 
| 135 143 | 
             
                #
         | 
| 136 | 
            -
                #   method_option :foo | 
| 144 | 
            +
                #   method_option :foo, :for => :previous_command
         | 
| 137 145 | 
             
                #
         | 
| 138 146 | 
             
                #   def next_command
         | 
| 139 147 | 
             
                #     # magic
         | 
| @@ -153,6 +161,9 @@ class Thor | |
| 153 161 | 
             
                # :hide     - If you want to hide this option from the help.
         | 
| 154 162 | 
             
                #
         | 
| 155 163 | 
             
                def method_option(name, options = {})
         | 
| 164 | 
            +
                  unless [ Symbol, String ].any? { |klass| name.is_a?(klass) }
         | 
| 165 | 
            +
                    raise ArgumentError, "Expected a Symbol or String, got #{name.inspect}"
         | 
| 166 | 
            +
                  end
         | 
| 156 167 | 
             
                  scope = if options[:for]
         | 
| 157 168 | 
             
                    find_and_refresh_command(options[:for]).options
         | 
| 158 169 | 
             
                  else
         | 
| @@ -163,6 +174,81 @@ class Thor | |
| 163 174 | 
             
                end
         | 
| 164 175 | 
             
                alias_method :option, :method_option
         | 
| 165 176 |  | 
| 177 | 
            +
                # Adds and declares option group for exclusive options in the
         | 
| 178 | 
            +
                # block and arguments. You can declare options as the outside of the block.
         | 
| 179 | 
            +
                #
         | 
| 180 | 
            +
                # If :for is given as option, it allows you to change the options from
         | 
| 181 | 
            +
                # a previous defined command.
         | 
| 182 | 
            +
                #
         | 
| 183 | 
            +
                # ==== Parameters
         | 
| 184 | 
            +
                # Array[Thor::Option.name]
         | 
| 185 | 
            +
                # options<Hash>:: :for is applied for previous defined command.
         | 
| 186 | 
            +
                #
         | 
| 187 | 
            +
                # ==== Examples
         | 
| 188 | 
            +
                #
         | 
| 189 | 
            +
                #   exclusive do
         | 
| 190 | 
            +
                #     option :one
         | 
| 191 | 
            +
                #     option :two
         | 
| 192 | 
            +
                #   end
         | 
| 193 | 
            +
                #
         | 
| 194 | 
            +
                # Or
         | 
| 195 | 
            +
                #
         | 
| 196 | 
            +
                #   option :one
         | 
| 197 | 
            +
                #   option :two
         | 
| 198 | 
            +
                #   exclusive :one, :two
         | 
| 199 | 
            +
                #
         | 
| 200 | 
            +
                # If you give "--one" and "--two" at the same time ExclusiveArgumentsError
         | 
| 201 | 
            +
                # will be raised.
         | 
| 202 | 
            +
                #
         | 
| 203 | 
            +
                def method_exclusive(*args, &block)
         | 
| 204 | 
            +
                  register_options_relation_for(:method_options,
         | 
| 205 | 
            +
                                                :method_exclusive_option_names, *args, &block)
         | 
| 206 | 
            +
                end
         | 
| 207 | 
            +
                alias_method :exclusive, :method_exclusive
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                # Adds and declares option group for required at least one of options in the
         | 
| 210 | 
            +
                # block of arguments. You can declare options as the outside of the block.
         | 
| 211 | 
            +
                #
         | 
| 212 | 
            +
                # If :for is given as option, it allows you to change the options from
         | 
| 213 | 
            +
                # a previous defined command.
         | 
| 214 | 
            +
                #
         | 
| 215 | 
            +
                # ==== Parameters
         | 
| 216 | 
            +
                # Array[Thor::Option.name]
         | 
| 217 | 
            +
                # options<Hash>:: :for is applied for previous defined command.
         | 
| 218 | 
            +
                #
         | 
| 219 | 
            +
                # ==== Examples
         | 
| 220 | 
            +
                #
         | 
| 221 | 
            +
                #   at_least_one do
         | 
| 222 | 
            +
                #     option :one
         | 
| 223 | 
            +
                #     option :two
         | 
| 224 | 
            +
                #   end
         | 
| 225 | 
            +
                #
         | 
| 226 | 
            +
                # Or
         | 
| 227 | 
            +
                #
         | 
| 228 | 
            +
                #   option :one
         | 
| 229 | 
            +
                #   option :two
         | 
| 230 | 
            +
                #   at_least_one :one, :two
         | 
| 231 | 
            +
                #
         | 
| 232 | 
            +
                # If you do not give "--one" and "--two" AtLeastOneRequiredArgumentError
         | 
| 233 | 
            +
                # will be raised.
         | 
| 234 | 
            +
                #
         | 
| 235 | 
            +
                # You can use at_least_one and exclusive at the same time.
         | 
| 236 | 
            +
                #
         | 
| 237 | 
            +
                #    exclusive do
         | 
| 238 | 
            +
                #      at_least_one do
         | 
| 239 | 
            +
                #        option :one
         | 
| 240 | 
            +
                #        option :two
         | 
| 241 | 
            +
                #      end
         | 
| 242 | 
            +
                #    end
         | 
| 243 | 
            +
                #
         | 
| 244 | 
            +
                # Then it is required either only one of "--one" or "--two".
         | 
| 245 | 
            +
                #
         | 
| 246 | 
            +
                def method_at_least_one(*args, &block)
         | 
| 247 | 
            +
                  register_options_relation_for(:method_options,
         | 
| 248 | 
            +
                                                :method_at_least_one_option_names, *args, &block)
         | 
| 249 | 
            +
                end
         | 
| 250 | 
            +
                alias_method :at_least_one, :method_at_least_one
         | 
| 251 | 
            +
             | 
| 166 252 | 
             
                # Prints help information for the given command.
         | 
| 167 253 | 
             
                #
         | 
| 168 254 | 
             
                # ==== Parameters
         | 
| @@ -178,9 +264,16 @@ class Thor | |
| 178 264 | 
             
                  shell.say "  #{banner(command).split("\n").join("\n  ")}"
         | 
| 179 265 | 
             
                  shell.say
         | 
| 180 266 | 
             
                  class_options_help(shell, nil => command.options.values)
         | 
| 267 | 
            +
                  print_exclusive_options(shell, command)
         | 
| 268 | 
            +
                  print_at_least_one_required_options(shell, command)
         | 
| 269 | 
            +
             | 
| 181 270 | 
             
                  if command.long_description
         | 
| 182 271 | 
             
                    shell.say "Description:"
         | 
| 183 | 
            -
                     | 
| 272 | 
            +
                    if command.wrap_long_description
         | 
| 273 | 
            +
                      shell.print_wrapped(command.long_description, indent: 2)
         | 
| 274 | 
            +
                    else
         | 
| 275 | 
            +
                      shell.say command.long_description
         | 
| 276 | 
            +
                    end
         | 
| 184 277 | 
             
                  else
         | 
| 185 278 | 
             
                    shell.say command.description
         | 
| 186 279 | 
             
                  end
         | 
| @@ -197,7 +290,7 @@ class Thor | |
| 197 290 | 
             
                  Thor::Util.thor_classes_in(self).each do |klass|
         | 
| 198 291 | 
             
                    list += klass.printable_commands(false)
         | 
| 199 292 | 
             
                  end
         | 
| 200 | 
            -
                  list | 
| 293 | 
            +
                  sort_commands!(list)
         | 
| 201 294 |  | 
| 202 295 | 
             
                  if defined?(@package_name) && @package_name
         | 
| 203 296 | 
             
                    shell.say "#{@package_name} commands:"
         | 
| @@ -205,9 +298,11 @@ class Thor | |
| 205 298 | 
             
                    shell.say "Commands:"
         | 
| 206 299 | 
             
                  end
         | 
| 207 300 |  | 
| 208 | 
            -
                  shell.print_table(list, : | 
| 301 | 
            +
                  shell.print_table(list, indent: 2, truncate: true)
         | 
| 209 302 | 
             
                  shell.say
         | 
| 210 303 | 
             
                  class_options_help(shell)
         | 
| 304 | 
            +
                  print_exclusive_options(shell)
         | 
| 305 | 
            +
                  print_at_least_one_required_options(shell)
         | 
| 211 306 | 
             
                end
         | 
| 212 307 |  | 
| 213 308 | 
             
                # Returns commands ready to be printed.
         | 
| @@ -238,7 +333,7 @@ class Thor | |
| 238 333 |  | 
| 239 334 | 
             
                  define_method(subcommand) do |*args|
         | 
| 240 335 | 
             
                    args, opts = Thor::Arguments.split(args)
         | 
| 241 | 
            -
                    invoke_args = [args, opts, {: | 
| 336 | 
            +
                    invoke_args = [args, opts, {invoked_via_subcommand: true, class_options: options}]
         | 
| 242 337 | 
             
                    invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h")
         | 
| 243 338 | 
             
                    invoke subcommand_class, *invoke_args
         | 
| 244 339 | 
             
                  end
         | 
| @@ -346,6 +441,24 @@ class Thor | |
| 346 441 |  | 
| 347 442 | 
             
              protected
         | 
| 348 443 |  | 
| 444 | 
            +
                # Returns this class exclusive options array set.
         | 
| 445 | 
            +
                #
         | 
| 446 | 
            +
                # ==== Returns
         | 
| 447 | 
            +
                # Array[Array[Thor::Option.name]]
         | 
| 448 | 
            +
                #
         | 
| 449 | 
            +
                def method_exclusive_option_names #:nodoc:
         | 
| 450 | 
            +
                  @method_exclusive_option_names ||= []
         | 
| 451 | 
            +
                end
         | 
| 452 | 
            +
             | 
| 453 | 
            +
                # Returns this class at least one of required options array set.
         | 
| 454 | 
            +
                #
         | 
| 455 | 
            +
                # ==== Returns
         | 
| 456 | 
            +
                # Array[Array[Thor::Option.name]]
         | 
| 457 | 
            +
                #
         | 
| 458 | 
            +
                def method_at_least_one_option_names #:nodoc:
         | 
| 459 | 
            +
                  @method_at_least_one_option_names ||= []
         | 
| 460 | 
            +
                end
         | 
| 461 | 
            +
             | 
| 349 462 | 
             
                def stop_on_unknown_option #:nodoc:
         | 
| 350 463 | 
             
                  @stop_on_unknown_option ||= []
         | 
| 351 464 | 
             
                end
         | 
| @@ -355,6 +468,28 @@ class Thor | |
| 355 468 | 
             
                  @disable_required_check ||= [:help]
         | 
| 356 469 | 
             
                end
         | 
| 357 470 |  | 
| 471 | 
            +
                def print_exclusive_options(shell, command = nil) # :nodoc:
         | 
| 472 | 
            +
                  opts = []
         | 
| 473 | 
            +
                  opts  = command.method_exclusive_option_names unless command.nil?
         | 
| 474 | 
            +
                  opts += class_exclusive_option_names
         | 
| 475 | 
            +
                  unless opts.empty?
         | 
| 476 | 
            +
                    shell.say "Exclusive Options:"
         | 
| 477 | 
            +
                    shell.print_table(opts.map{ |ex| ex.map{ |e| "--#{e}"}}, indent: 2 )
         | 
| 478 | 
            +
                    shell.say
         | 
| 479 | 
            +
                  end
         | 
| 480 | 
            +
                end
         | 
| 481 | 
            +
             | 
| 482 | 
            +
                def print_at_least_one_required_options(shell, command = nil) # :nodoc:
         | 
| 483 | 
            +
                  opts = []
         | 
| 484 | 
            +
                  opts = command.method_at_least_one_option_names unless command.nil?
         | 
| 485 | 
            +
                  opts += class_at_least_one_option_names
         | 
| 486 | 
            +
                  unless opts.empty?
         | 
| 487 | 
            +
                    shell.say "Required At Least One:"
         | 
| 488 | 
            +
                    shell.print_table(opts.map{ |ex| ex.map{ |e| "--#{e}"}}, indent: 2 )
         | 
| 489 | 
            +
                    shell.say
         | 
| 490 | 
            +
                  end
         | 
| 491 | 
            +
                end
         | 
| 492 | 
            +
             | 
| 358 493 | 
             
                # The method responsible for dispatching given the args.
         | 
| 359 494 | 
             
                def dispatch(meth, given_args, given_opts, config) #:nodoc:
         | 
| 360 495 | 
             
                  meth ||= retrieve_command_name(given_args)
         | 
| @@ -415,12 +550,16 @@ class Thor | |
| 415 550 | 
             
                  @usage ||= nil
         | 
| 416 551 | 
             
                  @desc ||= nil
         | 
| 417 552 | 
             
                  @long_desc ||= nil
         | 
| 553 | 
            +
                  @long_desc_wrap ||= nil
         | 
| 418 554 | 
             
                  @hide ||= nil
         | 
| 419 555 |  | 
| 420 556 | 
             
                  if @usage && @desc
         | 
| 421 557 | 
             
                    base_class = @hide ? Thor::HiddenCommand : Thor::Command
         | 
| 422 | 
            -
                     | 
| 423 | 
            -
             | 
| 558 | 
            +
                    relations = {exclusive_option_names: method_exclusive_option_names,
         | 
| 559 | 
            +
                      at_least_one_option_names: method_at_least_one_option_names}
         | 
| 560 | 
            +
                    commands[meth] = base_class.new(meth, @desc, @long_desc, @long_desc_wrap, @usage, method_options, relations)
         | 
| 561 | 
            +
                    @usage, @desc, @long_desc, @long_desc_wrap, @method_options, @hide = nil
         | 
| 562 | 
            +
                    @method_exclusive_option_names, @method_at_least_one_option_names = nil
         | 
| 424 563 | 
             
                    true
         | 
| 425 564 | 
             
                  elsif all_commands[meth] || meth == "method_missing"
         | 
| 426 565 | 
             
                    true
         | 
| @@ -495,6 +634,14 @@ class Thor | |
| 495 634 | 
             
            "
         | 
| 496 635 | 
             
                end
         | 
| 497 636 | 
             
                alias_method :subtask_help, :subcommand_help
         | 
| 637 | 
            +
             | 
| 638 | 
            +
                # Sort the commands, lexicographically by default.
         | 
| 639 | 
            +
                #
         | 
| 640 | 
            +
                # Can be overridden in the subclass to change the display order of the
         | 
| 641 | 
            +
                # commands.
         | 
| 642 | 
            +
                def sort_commands!(list)
         | 
| 643 | 
            +
                  list.sort! { |a, b| a[0] <=> b[0] }
         | 
| 644 | 
            +
                end
         | 
| 498 645 | 
             
              end
         | 
| 499 646 |  | 
| 500 647 | 
             
              include Thor::Base
         | 
    
        data/thor.gemspec
    CHANGED
    
    | @@ -4,15 +4,15 @@ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib) | |
| 4 4 | 
             
            require "thor/version"
         | 
| 5 5 |  | 
| 6 6 | 
             
            Gem::Specification.new do |spec|
         | 
| 7 | 
            -
              spec. | 
| 7 | 
            +
              spec.name = "thor"
         | 
| 8 | 
            +
              spec.version = Thor::VERSION
         | 
| 9 | 
            +
              spec.licenses = %w(MIT)
         | 
| 8 10 | 
             
              spec.authors = ["Yehuda Katz", "José Valim"]
         | 
| 9 | 
            -
              spec.description = "Thor is a toolkit for building powerful command-line interfaces."
         | 
| 10 11 | 
             
              spec.email = "ruby-thor@googlegroups.com"
         | 
| 11 | 
            -
              spec.executables = %w(thor)
         | 
| 12 | 
            -
              spec.files = %w(.document thor.gemspec) + Dir["*.md", "bin/*", "lib/**/*.rb"]
         | 
| 13 12 | 
             
              spec.homepage = "http://whatisthor.com/"
         | 
| 14 | 
            -
              spec. | 
| 15 | 
            -
              spec. | 
| 13 | 
            +
              spec.description = "Thor is a toolkit for building powerful command-line interfaces."
         | 
| 14 | 
            +
              spec.summary = spec.description
         | 
| 15 | 
            +
             | 
| 16 16 | 
             
              spec.metadata = {
         | 
| 17 17 | 
             
                "bug_tracker_uri" => "https://github.com/rails/thor/issues",
         | 
| 18 18 | 
             
                "changelog_uri" => "https://github.com/rails/thor/releases/tag/v#{Thor::VERSION}",
         | 
| @@ -21,9 +21,13 @@ Gem::Specification.new do |spec| | |
| 21 21 | 
             
                "wiki_uri" => "https://github.com/rails/thor/wiki",
         | 
| 22 22 | 
             
                "rubygems_mfa_required" => "true",
         | 
| 23 23 | 
             
              }
         | 
| 24 | 
            -
             | 
| 25 | 
            -
              spec.required_ruby_version = ">= 2. | 
| 24 | 
            +
             | 
| 25 | 
            +
              spec.required_ruby_version = ">= 2.6.0"
         | 
| 26 26 | 
             
              spec.required_rubygems_version = ">= 1.3.5"
         | 
| 27 | 
            -
             | 
| 28 | 
            -
              spec. | 
| 27 | 
            +
             | 
| 28 | 
            +
              spec.files = %w(.document thor.gemspec) + Dir["*.md", "bin/*", "lib/**/*.rb"]
         | 
| 29 | 
            +
              spec.executables = %w(thor)
         | 
| 30 | 
            +
              spec.require_paths = %w(lib)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              spec.add_development_dependency "bundler", ">= 1.0", "< 3"
         | 
| 29 33 | 
             
            end
         |