jawshooah-overcommit 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/overcommit +8 -0
- data/config/default.yml +275 -0
- data/config/starter.yml +31 -0
- data/lib/overcommit.rb +20 -0
- data/lib/overcommit/cli.rb +205 -0
- data/lib/overcommit/configuration.rb +183 -0
- data/lib/overcommit/configuration_loader.rb +49 -0
- data/lib/overcommit/configuration_validator.rb +40 -0
- data/lib/overcommit/constants.rb +8 -0
- data/lib/overcommit/exceptions.rb +35 -0
- data/lib/overcommit/git_repo.rb +147 -0
- data/lib/overcommit/hook/base.rb +174 -0
- data/lib/overcommit/hook/commit_msg/base.rb +11 -0
- data/lib/overcommit/hook/commit_msg/gerrit_change_id.rb +18 -0
- data/lib/overcommit/hook/commit_msg/hard_tabs.rb +13 -0
- data/lib/overcommit/hook/commit_msg/russian_novel.rb +14 -0
- data/lib/overcommit/hook/commit_msg/single_line_subject.rb +12 -0
- data/lib/overcommit/hook/commit_msg/text_width.rb +38 -0
- data/lib/overcommit/hook/commit_msg/trailing_period.rb +12 -0
- data/lib/overcommit/hook/post_checkout/base.rb +11 -0
- data/lib/overcommit/hook/post_checkout/index_tags.rb +26 -0
- data/lib/overcommit/hook/post_commit/base.rb +11 -0
- data/lib/overcommit/hook/post_commit/git_guilt.rb +9 -0
- data/lib/overcommit/hook/pre_commit/author_email.rb +18 -0
- data/lib/overcommit/hook/pre_commit/author_name.rb +17 -0
- data/lib/overcommit/hook/pre_commit/base.rb +70 -0
- data/lib/overcommit/hook/pre_commit/berksfile_check.rb +20 -0
- data/lib/overcommit/hook/pre_commit/brakeman.rb +12 -0
- data/lib/overcommit/hook/pre_commit/broken_symlinks.rb +15 -0
- data/lib/overcommit/hook/pre_commit/bundle_check.rb +25 -0
- data/lib/overcommit/hook/pre_commit/chamber_security.rb +11 -0
- data/lib/overcommit/hook/pre_commit/coffee_lint.rb +11 -0
- data/lib/overcommit/hook/pre_commit/css_lint.rb +11 -0
- data/lib/overcommit/hook/pre_commit/go_lint.rb +12 -0
- data/lib/overcommit/hook/pre_commit/haml_lint.rb +19 -0
- data/lib/overcommit/hook/pre_commit/hard_tabs.rb +14 -0
- data/lib/overcommit/hook/pre_commit/image_optim.rb +41 -0
- data/lib/overcommit/hook/pre_commit/js_hint.rb +13 -0
- data/lib/overcommit/hook/pre_commit/jscs.rb +22 -0
- data/lib/overcommit/hook/pre_commit/json_syntax.rb +22 -0
- data/lib/overcommit/hook/pre_commit/jsx_hint.rb +13 -0
- data/lib/overcommit/hook/pre_commit/jsxcs.rb +20 -0
- data/lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb +14 -0
- data/lib/overcommit/hook/pre_commit/merge_conflicts.rb +14 -0
- data/lib/overcommit/hook/pre_commit/pry_binding.rb +14 -0
- data/lib/overcommit/hook/pre_commit/python_flake8.rb +11 -0
- data/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb +45 -0
- data/lib/overcommit/hook/pre_commit/reek.rb +22 -0
- data/lib/overcommit/hook/pre_commit/rubocop.rb +19 -0
- data/lib/overcommit/hook/pre_commit/scss_lint.rb +19 -0
- data/lib/overcommit/hook/pre_commit/shell_check.rb +19 -0
- data/lib/overcommit/hook/pre_commit/trailing_whitespace.rb +13 -0
- data/lib/overcommit/hook/pre_commit/travis_lint.rb +11 -0
- data/lib/overcommit/hook/pre_commit/yaml_syntax.rb +22 -0
- data/lib/overcommit/hook_context.rb +17 -0
- data/lib/overcommit/hook_context/base.rb +69 -0
- data/lib/overcommit/hook_context/commit_msg.rb +32 -0
- data/lib/overcommit/hook_context/post_checkout.rb +26 -0
- data/lib/overcommit/hook_context/post_commit.rb +19 -0
- data/lib/overcommit/hook_context/pre_commit.rb +148 -0
- data/lib/overcommit/hook_context/run_all.rb +39 -0
- data/lib/overcommit/hook_loader/base.rb +36 -0
- data/lib/overcommit/hook_loader/built_in_hook_loader.rb +12 -0
- data/lib/overcommit/hook_loader/plugin_hook_loader.rb +61 -0
- data/lib/overcommit/hook_runner.rb +129 -0
- data/lib/overcommit/hook_signer.rb +79 -0
- data/lib/overcommit/installer.rb +148 -0
- data/lib/overcommit/interrupt_handler.rb +87 -0
- data/lib/overcommit/logger.rb +79 -0
- data/lib/overcommit/message_processor.rb +132 -0
- data/lib/overcommit/printer.rb +116 -0
- data/lib/overcommit/subprocess.rb +46 -0
- data/lib/overcommit/utils.rb +163 -0
- data/lib/overcommit/version.rb +4 -0
- data/libexec/gerrit-change-id +174 -0
- data/libexec/index-tags +17 -0
- data/template-dir/hooks/commit-msg +81 -0
- data/template-dir/hooks/overcommit-hook +81 -0
- data/template-dir/hooks/post-checkout +81 -0
- data/template-dir/hooks/pre-commit +81 -0
- metadata +184 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
# Provides a handler for interrupt signals (SIGINT), allowing the application to
|
4
|
+
# finish what it's currently working on.
|
5
|
+
class InterruptHandler
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
attr_accessor :isolate_signals, :signal_received, :reenable_on_interrupt
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
self.isolate_signals = false
|
12
|
+
self.signal_received = false
|
13
|
+
self.reenable_on_interrupt = false
|
14
|
+
|
15
|
+
Signal.trap('INT') do
|
16
|
+
if isolate_signals
|
17
|
+
self.signal_received = true
|
18
|
+
else
|
19
|
+
if reenable_on_interrupt
|
20
|
+
self.reenable_on_interrupt = false
|
21
|
+
self.isolate_signals = true
|
22
|
+
end
|
23
|
+
|
24
|
+
raise Interrupt # Allow interrupt to propagate to code
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
# Provide a way to allow a single Ctrl-C interrupt to happen and atomically
|
31
|
+
# re-enable interrupt protections once that interrupt is propagated.
|
32
|
+
#
|
33
|
+
# This prevents a race condition where code like the following:
|
34
|
+
#
|
35
|
+
# begin
|
36
|
+
# InterruptHandler.disable!
|
37
|
+
# ... do stuff ...
|
38
|
+
# rescue Interrupt
|
39
|
+
# ... handle it ...
|
40
|
+
# ensure
|
41
|
+
# InterruptHandler.enable!
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# ...could have the `enable!` call to the interrupt handler not called in
|
45
|
+
# the event another interrupt was received in between the interrupt being
|
46
|
+
# handled and the `ensure` block being entered.
|
47
|
+
#
|
48
|
+
# Thus you should always write:
|
49
|
+
#
|
50
|
+
# begin
|
51
|
+
# InterruptHandler.disable_until_finished_or_interrupted do
|
52
|
+
# ... do stuff ...
|
53
|
+
# end
|
54
|
+
# rescue Interrupt
|
55
|
+
# ... handle it ...
|
56
|
+
# rescue
|
57
|
+
# ... handle any other exceptions ...
|
58
|
+
# end
|
59
|
+
def disable_until_finished_or_interrupted
|
60
|
+
instance.reenable_on_interrupt = true
|
61
|
+
instance.isolate_signals = false
|
62
|
+
yield
|
63
|
+
ensure
|
64
|
+
instance.isolate_signals = true
|
65
|
+
end
|
66
|
+
|
67
|
+
def disable!
|
68
|
+
instance.isolate_signals = false
|
69
|
+
end
|
70
|
+
|
71
|
+
def enable!
|
72
|
+
instance.isolate_signals = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def isolate_from_interrupts
|
76
|
+
instance.signal_received = false
|
77
|
+
instance.isolate_signals = true
|
78
|
+
result = yield
|
79
|
+
instance.isolate_signals = false
|
80
|
+
result
|
81
|
+
end
|
82
|
+
|
83
|
+
def signal_received?
|
84
|
+
instance.signal_received
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Overcommit
|
2
|
+
# Encapsulates all communication to an output source.
|
3
|
+
class Logger
|
4
|
+
# Helper for creating a logger which outputs nothing.
|
5
|
+
def self.silent
|
6
|
+
new(File.open('/dev/null', 'w'))
|
7
|
+
end
|
8
|
+
|
9
|
+
# Creates a logger that will write to the given output stream.
|
10
|
+
#
|
11
|
+
# @param out [IO]
|
12
|
+
def initialize(out)
|
13
|
+
@out = out
|
14
|
+
end
|
15
|
+
|
16
|
+
# Write output without a trailing newline.
|
17
|
+
def partial(*args)
|
18
|
+
@out.print(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Prints a newline character (alias for readability).
|
22
|
+
def newline
|
23
|
+
log
|
24
|
+
end
|
25
|
+
|
26
|
+
# Write a line of output.
|
27
|
+
#
|
28
|
+
# A newline character will always be appended.
|
29
|
+
def log(*args)
|
30
|
+
@out.puts(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Write a line of output that is intended to be emphasized.
|
34
|
+
def bold(*args)
|
35
|
+
color('1', *args)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Write a line of output indicating a problem or error.
|
39
|
+
def error(*args)
|
40
|
+
color(31, *args)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Write a line of output indicating a problem or error which is emphasized
|
44
|
+
# over a regular problem or error.
|
45
|
+
def bold_error(*args)
|
46
|
+
color('1;31', *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Write a line of output indicating a successful or noteworthy event.
|
50
|
+
def success(*args)
|
51
|
+
color(32, *args)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Write a line of output indicating a potential cause for concern, but not
|
55
|
+
# an actual error.
|
56
|
+
def warning(*args)
|
57
|
+
color(33, *args)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Write a line of output indicating a potential cause for concern, but with
|
61
|
+
# greater emphasize compared to other warnings.
|
62
|
+
def bold_warning(*args)
|
63
|
+
color('1;33', *args)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# Outputs text wrapped in ANSI escape code necessary to produce a given
|
69
|
+
# color/text display.
|
70
|
+
#
|
71
|
+
# @param code [String] ANSI escape code, e.g. '1;33' for "bold yellow"
|
72
|
+
# @param str [String] string to wrap
|
73
|
+
# @param partial [true,false] whether to omit a newline
|
74
|
+
def color(code, str, partial = false)
|
75
|
+
send(partial ? :partial : :log,
|
76
|
+
@out.tty? ? "\033[#{code}m#{str}\033[0m" : str)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Overcommit
|
2
|
+
# Utility class that encapsulates the handling of hook messages and whether
|
3
|
+
# they affect lines the user has modified or not.
|
4
|
+
#
|
5
|
+
# This class exposes an endpoint that extracts an appropriate hook/status
|
6
|
+
# output tuple from an array of {Overcommit::Hook::Message}s, respecting the
|
7
|
+
# configuration settings for the given hook.
|
8
|
+
class MessageProcessor
|
9
|
+
ERRORS_MODIFIED_HEADER = 'Errors on modified lines:'
|
10
|
+
WARNINGS_MODIFIED_HEADER = 'Warnings on modified lines:'
|
11
|
+
ERRORS_UNMODIFIED_HEADER = "Errors on lines you didn't modify:"
|
12
|
+
WARNINGS_UNMODIFIED_HEADER = "Warnings on lines you didn't modify:"
|
13
|
+
|
14
|
+
# @param hook [Overcommit::Hook::Base]
|
15
|
+
# @param unmodified_lines_setting [String] how to treat messages on
|
16
|
+
# unmodified lines
|
17
|
+
def initialize(hook, unmodified_lines_setting)
|
18
|
+
@hook = hook
|
19
|
+
@setting = unmodified_lines_setting
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a hook status/output tuple from the messages this processor was
|
23
|
+
# initialized with.
|
24
|
+
#
|
25
|
+
# @return [Array<Symbol,String>]
|
26
|
+
def hook_result(messages)
|
27
|
+
status, output = basic_status_and_output(messages)
|
28
|
+
|
29
|
+
# Nothing to do if there are no problems to begin with
|
30
|
+
return [status, output] if status == :pass
|
31
|
+
|
32
|
+
# Return as-is if this type of hook doesn't have the concept of modified lines
|
33
|
+
return [status, output] unless @hook.respond_to?(:modified_lines_in_file)
|
34
|
+
|
35
|
+
handle_modified_lines(messages, status)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def handle_modified_lines(messages, status)
|
41
|
+
messages = remove_ignored_messages(messages)
|
42
|
+
|
43
|
+
messages_on_modified_lines, messages_on_unmodified_lines =
|
44
|
+
messages.partition { |message| message_on_modified_line?(message) }
|
45
|
+
|
46
|
+
output = print_messages(
|
47
|
+
messages_on_modified_lines,
|
48
|
+
ERRORS_MODIFIED_HEADER,
|
49
|
+
WARNINGS_MODIFIED_HEADER
|
50
|
+
)
|
51
|
+
output += print_messages(
|
52
|
+
messages_on_unmodified_lines,
|
53
|
+
ERRORS_UNMODIFIED_HEADER,
|
54
|
+
WARNINGS_UNMODIFIED_HEADER
|
55
|
+
)
|
56
|
+
|
57
|
+
[transform_status(status, messages_on_modified_lines), output]
|
58
|
+
end
|
59
|
+
|
60
|
+
def transform_status(status, messages_on_modified_lines)
|
61
|
+
# `report` indicates user wants the original status
|
62
|
+
return status if @setting == 'report'
|
63
|
+
|
64
|
+
error_messages, warning_messages =
|
65
|
+
messages_on_modified_lines.partition { |msg| msg.type == :error }
|
66
|
+
|
67
|
+
if can_upgrade_to_warning?(status, error_messages)
|
68
|
+
status = :warn
|
69
|
+
end
|
70
|
+
|
71
|
+
if can_upgrade_to_passing?(status, warning_messages)
|
72
|
+
status = :pass
|
73
|
+
end
|
74
|
+
|
75
|
+
status
|
76
|
+
end
|
77
|
+
|
78
|
+
def can_upgrade_to_warning?(status, error_messages)
|
79
|
+
status == :fail && error_messages.empty?
|
80
|
+
end
|
81
|
+
|
82
|
+
def can_upgrade_to_passing?(status, warning_messages)
|
83
|
+
status == :warn && @setting == 'ignore' && warning_messages.empty?
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns status and output for messages assuming no special treatment of
|
87
|
+
# messages occurring on unmodified lines.
|
88
|
+
def basic_status_and_output(messages)
|
89
|
+
status =
|
90
|
+
if messages.any? { |message| message.type == :error }
|
91
|
+
:fail
|
92
|
+
elsif messages.any? { |message| message.type == :warning }
|
93
|
+
:warn
|
94
|
+
else
|
95
|
+
:pass
|
96
|
+
end
|
97
|
+
|
98
|
+
output = ''
|
99
|
+
if messages.any?
|
100
|
+
output += messages.join("\n") + "\n"
|
101
|
+
end
|
102
|
+
|
103
|
+
[status, output]
|
104
|
+
end
|
105
|
+
|
106
|
+
def print_messages(messages, error_heading, warning_heading)
|
107
|
+
output = ''
|
108
|
+
errors, warnings = messages.partition { |msg| msg.type == :error }
|
109
|
+
|
110
|
+
if errors.any?
|
111
|
+
output += "#{error_heading}\n#{errors.join("\n")}\n"
|
112
|
+
end
|
113
|
+
|
114
|
+
if warnings.any?
|
115
|
+
output += "#{warning_heading}\n#{warnings.join("\n")}\n"
|
116
|
+
end
|
117
|
+
|
118
|
+
output
|
119
|
+
end
|
120
|
+
|
121
|
+
def remove_ignored_messages(messages)
|
122
|
+
# If user wants to ignore messages on unmodified lines, simply remove them
|
123
|
+
return messages unless @setting == 'ignore'
|
124
|
+
|
125
|
+
messages.select { |message| message_on_modified_line?(message) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def message_on_modified_line?(message)
|
129
|
+
@hook.modified_lines_in_file(message.file).include?(message.line)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Overcommit
|
4
|
+
# Provide a set of callbacks which can be executed as events occur during the
|
5
|
+
# course of {HookRunner#run}.
|
6
|
+
class Printer
|
7
|
+
attr_reader :log
|
8
|
+
|
9
|
+
def initialize(logger, context)
|
10
|
+
@log = logger
|
11
|
+
@context = context
|
12
|
+
end
|
13
|
+
|
14
|
+
# Executed at the very beginning of running the collection of hooks.
|
15
|
+
def start_run
|
16
|
+
log.bold "Running #{hook_script_name} hooks"
|
17
|
+
end
|
18
|
+
|
19
|
+
def nothing_to_run
|
20
|
+
log.success "✓ No applicable #{hook_script_name} hooks to run"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Executed at the start of an individual hook run.
|
24
|
+
def start_hook(hook)
|
25
|
+
unless hook.quiet?
|
26
|
+
print_header(hook)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def hook_skipped(hook)
|
31
|
+
log.warning "Skipping #{hook.name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def required_hook_not_skipped(hook)
|
35
|
+
log.warning "Cannot skip #{hook.name} since it is required"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Executed at the end of an individual hook run.
|
39
|
+
def end_hook(hook, status, output)
|
40
|
+
# Want to print the header for quiet hooks only if the result wasn't good
|
41
|
+
# so that the user knows what failed
|
42
|
+
print_header(hook) if hook.quiet? && ![:good, :pass].include?(status)
|
43
|
+
|
44
|
+
print_result(hook, status, output)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Executed when a hook run was interrupted/cancelled by user.
|
48
|
+
def run_interrupted
|
49
|
+
log.newline
|
50
|
+
log.warning '⚠ Hook run interrupted by user'
|
51
|
+
log.newline
|
52
|
+
end
|
53
|
+
|
54
|
+
# Executed when one or more hooks by the end of the run.
|
55
|
+
def run_failed
|
56
|
+
log.newline
|
57
|
+
log.error "✗ One or more #{hook_script_name} hooks failed"
|
58
|
+
log.newline
|
59
|
+
end
|
60
|
+
|
61
|
+
# Executed when no hooks failed by the end of the run.
|
62
|
+
def run_succeeded
|
63
|
+
log.newline
|
64
|
+
log.success "✓ All #{hook_script_name} hooks passed"
|
65
|
+
log.newline
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def print_header(hook)
|
71
|
+
hook_name = "[#{hook.name}] "
|
72
|
+
log.partial hook.description
|
73
|
+
log.partial '.' * [70 - hook.description.length - hook_name.length, 0].max
|
74
|
+
log.partial hook_name
|
75
|
+
end
|
76
|
+
|
77
|
+
def print_result(hook, status, output) # rubocop:disable CyclomaticComplexity, MethodLength
|
78
|
+
case status
|
79
|
+
when :pass
|
80
|
+
log.success 'OK' unless hook.quiet?
|
81
|
+
when :good
|
82
|
+
log.success 'OK'
|
83
|
+
log.bold_error 'Hook returned a status of `:good`. This is deprecated ' \
|
84
|
+
'in favor of `:pass` and will be removed in a future ' \
|
85
|
+
'version of Overcommit'
|
86
|
+
when :warn
|
87
|
+
log.warning 'WARNING'
|
88
|
+
print_report(output, :bold_warning)
|
89
|
+
when :bad
|
90
|
+
log.error 'FAILED'
|
91
|
+
log.bold_error 'Hook returned a status of `:bad`. This is deprecated ' \
|
92
|
+
'in favor of `:fail` and will be removed in a future ' \
|
93
|
+
'version of Overcommit'
|
94
|
+
print_report(output, :bold_error)
|
95
|
+
when :fail
|
96
|
+
log.error 'FAILED'
|
97
|
+
print_report(output, :bold_error)
|
98
|
+
when :interrupt
|
99
|
+
log.error 'INTERRUPTED'
|
100
|
+
print_report(output, :bold_error)
|
101
|
+
else
|
102
|
+
log.error '???'
|
103
|
+
print_report("Hook returned unknown status `#{status.inspect}` -- ignoring.",
|
104
|
+
:bold_error)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def print_report(output, format = :log)
|
109
|
+
log.send(format, output) unless output.nil? || output.empty?
|
110
|
+
end
|
111
|
+
|
112
|
+
def hook_script_name
|
113
|
+
@context.hook_script_name
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'childprocess'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module Overcommit
|
5
|
+
# Manages execution of a child process, collecting the exit status and
|
6
|
+
# standard out/error output.
|
7
|
+
class Subprocess
|
8
|
+
# Encapsulates the result of a process.
|
9
|
+
Result = Struct.new(:status, :stdout, :stderr) do
|
10
|
+
def success?
|
11
|
+
status == 0
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Spawns a new process using the given array of arguments (the first
|
17
|
+
# element is the command).
|
18
|
+
def spawn(args)
|
19
|
+
process = ChildProcess.build(*args)
|
20
|
+
|
21
|
+
out, err = assign_output_streams(process)
|
22
|
+
|
23
|
+
process.start
|
24
|
+
process.wait
|
25
|
+
|
26
|
+
err.rewind
|
27
|
+
out.rewind
|
28
|
+
|
29
|
+
Result.new(process.exit_code, out.read, err.read)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# @param process [ChildProcess]
|
35
|
+
# @return [Array<IO>]
|
36
|
+
def assign_output_streams(process)
|
37
|
+
%w[out err].map do |stream_name|
|
38
|
+
::Tempfile.new(stream_name).tap do |stream|
|
39
|
+
stream.sync = true
|
40
|
+
process.io.send("std#{stream_name}=", stream)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|