Class: Masamune::Commands::Shell

Inherits:
SimpleDelegator
  • Object
show all
Defined in:
lib/masamune/commands/shell.rb

Constant Summary collapse

SIGINT_EXIT_STATUS =
130
PIPE_TIMEOUT =
10

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(delegate, opts = {}) ⇒ Shell

Returns a new instance of Shell.



35
36
37
38
39
40
41
42
# File 'lib/masamune/commands/shell.rb', line 35

def initialize(delegate, opts = {})
  super delegate
  @delegate       = delegate
  self.safe       = opts.fetch(:safe, false)
  self.fail_fast  = opts.fetch(:fail_fast, true)
  @stdout_line_no = 0
  @stderr_line_no = 0
end

Instance Attribute Details

#fail_fastObject

Returns the value of attribute fail_fast.



33
34
35
# File 'lib/masamune/commands/shell.rb', line 33

def fail_fast
  @fail_fast
end

#safeObject

Returns the value of attribute safe.



33
34
35
# File 'lib/masamune/commands/shell.rb', line 33

def safe
  @safe
end

Instance Method Details

#after_executeObject



71
72
73
# File 'lib/masamune/commands/shell.rb', line 71

def after_execute
  @delegate.after_execute if @delegate.respond_to?(:after_execute)
end

#around_execute(&block) ⇒ Object



75
76
77
78
79
80
81
82
83
# File 'lib/masamune/commands/shell.rb', line 75

def around_execute(&block)
  return OpenStruct.new(success?: true) if configuration.dry_run && !safe

  if @delegate.respond_to?(:around_execute)
    @delegate.around_execute(&block)
  else
    yield
  end
end

#before_executeObject



65
66
67
68
69
# File 'lib/masamune/commands/shell.rb', line 65

def before_execute
  trace(command_args) if configuration.verbose

  @delegate.before_execute if @delegate.respond_to?(:before_execute)
end

#command_argsObject



89
90
91
92
# File 'lib/masamune/commands/shell.rb', line 89

def command_args
  raise 'no command_args' unless @delegate.respond_to?(:command_args) && @delegate.command_args
  Array.wrap(@delegate.command_args).flatten.compact.map(&:to_s)
end

#command_binObject



94
95
96
# File 'lib/masamune/commands/shell.rb', line 94

def command_bin
  command_args.first
end

#command_envObject



85
86
87
# File 'lib/masamune/commands/shell.rb', line 85

def command_env
  (@delegate.respond_to?(:command_env) ? @delegate.command_env : {}).merge('TZ' => 'UTC')
end

#executeObject



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/masamune/commands/shell.rb', line 98

def execute
  status = OpenStruct.new(success?: false, exitstatus: 1)

  before_execute
  status = around_execute do
    execute_block
  end
  after_execute

  handle_failure(exit_code(status)) unless status.success?
  status
rescue Interrupt
  handle_failure(SIGINT_EXIT_STATUS)
rescue SystemExit
  handle_failure(exit_code(status))
end

#execute_blockObject



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/masamune/commands/shell.rb', line 115

def execute_block
  logger.debug("execute: #{command_info}")

  Open3.popen3(command_env, *command_args) do |p_stdin, p_stdout, p_stderr, t_stdin|
    p_stdin.wait_writable(PIPE_TIMEOUT) || raise("IO stdin not ready for write in #{PIPE_TIMEOUT}")

    Thread.new do
      if @delegate.respond_to?(:stdin)
        @delegate.stdin.rewind
        until @delegate.stdin.eof?
          line = @delegate.stdin.gets
          trace(line.chomp)
          p_stdin.puts line
          p_stdin.flush
        end
      else
        until p_stdin.closed?
          input = Readline.readline('', true).strip
          p_stdin.puts input
          p_stdin.flush
        end
      end
      p_stdin.close unless p_stdin.closed?
    end

    t_stderr = Thread.new do
      handle_stderr(p_stderr) until p_stderr.eof?
      p_stderr.close unless p_stderr.closed?
    end

    t_stdout = Thread.new do
      handle_stdout(p_stdout) until p_stdout.eof?
      p_stdout.close unless p_stdout.closed?
    end

    [t_stderr, t_stdout, t_stdin].compact.each(&:join)
    logger.debug("status: #{t_stdin.value}")
    t_stdin.value
  end
end

#failure_message(status) ⇒ Object



183
184
185
186
187
188
189
# File 'lib/masamune/commands/shell.rb', line 183

def failure_message(status)
  if @delegate.respond_to?(:failure_message)
    @delegate.failure_message(status)
  else
    "fail_fast: #{command_args.join(' ')}"
  end
end

#handle_failure(status) ⇒ Object



178
179
180
181
# File 'lib/masamune/commands/shell.rb', line 178

def handle_failure(status)
  @delegate.handle_failure(status) if @delegate.respond_to?(:handle_failure)
  raise failure_message(status) if fail_fast
end

#handle_stderr(io) ⇒ Object



167
168
169
170
171
172
173
174
175
176
# File 'lib/masamune/commands/shell.rb', line 167

def handle_stderr(io)
  line = io.gets.chomp
  return unless line
  if @delegate.respond_to?(:handle_stderr)
    @delegate.handle_stderr(line, @stderr_line_no)
  else
    logger.debug(line)
  end
  @stderr_line_no += 1
end

#handle_stdout(io) ⇒ Object



156
157
158
159
160
161
162
163
164
165
# File 'lib/masamune/commands/shell.rb', line 156

def handle_stdout(io)
  line = io.gets.chomp
  return unless line
  if @delegate.respond_to?(:handle_stdout)
    @delegate.handle_stdout(line, @stdout_line_no)
  else
    logger.debug(line)
  end
  @stdout_line_no += 1
end

#replace(opts = {}) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/masamune/commands/shell.rb', line 44

def replace(opts = {})
  logger.debug("replace: #{command_info}")
  before_execute
  around_execute do
    pid = Process.fork
    if pid
      Signal.trap('INT') {} # Ensure SIGINT is handled by child process exec
      if opts.fetch(:detach, true)
        detach do
          Process.waitpid(pid)
        end
      else
        Process.waitpid(pid)
      end
      exit
    else
      exec(command_env, *command_args)
    end
  end
end