Class: Obstinately::Command

Inherits:
CLAide::Command
  • Object
show all
Defined in:
lib/obstinately/command.rb

Defined Under Namespace

Classes: MissingExecutableError

Constant Summary collapse

DEFAULT_RETRIES =
'3'
DEFAULT_BACKOFF =
'1.5'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv) ⇒ Command

Returns a new instance of Command.



34
35
36
37
38
39
# File 'lib/obstinately/command.rb', line 34

def initialize(argv)
  @retries = argv.option('retries', DEFAULT_RETRIES)
  @backoff = argv.option('backoff', DEFAULT_BACKOFF)
  @command = argv.arguments!
  super
end

Class Method Details

.optionsObject



27
28
29
30
31
32
# File 'lib/obstinately/command.rb', line 27

def self.options
  super.concat([
                 ['--retries=RETRIES', "The number of retries. Defaults to #{DEFAULT_RETRIES}"],
                 ['--backoff=BACKOFF', "The number of seconds to wait after the first failure. Doubles after each subsequent failure. Defaults to #{DEFAULT_BACKOFF}"]
               ]).reject { |name, _| name == '--no-ansi' }
end

Instance Method Details

#messageObject



80
81
82
83
84
# File 'lib/obstinately/command.rb', line 80

def message
  return unless verbose?

  warn yield
end

#runObject



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/obstinately/command.rb', line 41

def run
  backoff = @backoff
  @retries.succ.times do |i|
    message { "Running `#{@command.shelljoin}`" }
    exe, *args = *@command

    begin
      subprocess = spawn([exe, exe], *args)
    rescue SystemCallError => e
      raise MissingExecutableError, "Unable to find executable `#{exe}`#{" (#{e.class})" if verbose?}"
    else
      Process.wait(subprocess)
      status = $CHILD_STATUS
    end

    break if status.success?

    if i == @retries
      message { "Running `#{@command.shelljoin}` failed #{i.succ} times" }
      exit status.exitstatus
    end

    message { "Running `#{@command.shelljoin}` failed#{", sleeping #{backoff} seconds, then" if backoff > 0} retrying (#{i.succ}/#{@retries.succ})" }

    sleep backoff
    backoff *= 2
  end
end

#validate!Object



70
71
72
73
74
75
76
77
78
# File 'lib/obstinately/command.rb', line 70

def validate!
  super

  help! "Must specify a positive number of retries, not #{@retries}" unless @retries =~ /\A\d+\z/ && (@retries = @retries.to_i) && (@retries >= 0)

  help! "Must specify a positive number of seconds for backoff, not #{@backoff}" unless @backoff =~ /\A(\d+)?\.?\d+\z/ && (@backoff = @backoff.to_f) && (@backoff >= 0)

  help! 'Must specify a command to run' if @command.empty?
end