@@ -10,6 +10,15 @@ Task = Struct.new(:number, :command, :started_at, :ended_at, :exit_code, keyword
1010end
1111
1212class RParallel
13+ COLORS = {
14+ bold : "\e [1m" ,
15+ dim : "\e [2m" ,
16+ green : "\e [32m" ,
17+ red : "\e [31m" ,
18+ cyan : "\e [36m" ,
19+ reset : "\e [0m"
20+ } . freeze
21+
1322 def self . run ( argv , stdin : $stdin, stdout : $stdout, stderr : $stderr)
1423 return usage ( stdout ) if argv == [ "--help" ] || argv == [ "-h" ]
1524
@@ -21,20 +30,22 @@ class RParallel
2130 commands = stdin . each_line . map ( &:strip ) . reject ( &:empty? )
2231 tasks = commands . each_with_index . map { |command , index | Task . new ( number : index + 1 , command :) }
2332
24- tasks . map { |task | Thread . new { run_task ( task , stdout , stderr ) } } . each ( &:join )
33+ color = color_enabled?
34+ output_mutex = Mutex . new
35+ tasks . map { |task | Thread . new { run_task ( task , stdout , stderr , output_mutex , color ) } } . each ( &:join )
2536 print_report ( tasks , stdout )
2637
2738 tasks . all? { _1 . exit_code . zero? } ? 0 : 1
2839 end
2940
30- def self . run_task ( task , stdout , stderr )
41+ def self . run_task ( task , stdout , stderr , output_mutex , color )
3142 task . started_at = monotonic_time
3243
3344 Open3 . popen3 ( "sh" , "-c" , task . command ) do |child_stdin , child_stdout , child_stderr , wait_thread |
3445 child_stdin . close
3546 readers = [
36- Thread . new { IO . copy_stream ( child_stdout , stdout ) } ,
37- Thread . new { IO . copy_stream ( child_stderr , stderr ) }
47+ Thread . new { stream_output ( child_stdout , stdout , task . number , output_mutex ) } ,
48+ Thread . new { stream_output ( child_stderr , stderr , task . number , output_mutex , color : :red , enabled : color ) }
3849 ]
3950 readers . each ( &:join )
4051 task . exit_code = wait_thread . value . exitstatus || 1
@@ -46,26 +57,61 @@ class RParallel
4657 task . ended_at = monotonic_time
4758 end
4859
60+ def self . stream_output ( source , target , job_number , output_mutex , color : nil , enabled : false )
61+ source . each_line do |line |
62+ output = "[Job #{ job_number } ] #{ line . chomp } "
63+ output_mutex . synchronize { target . puts color ? colorize ( output , color , enabled ) : output }
64+ end
65+ end
66+
4967 def self . print_report ( tasks , stdout )
5068 rows = tasks . map do |task |
5169 [ task . number . to_s , task . status , task . exit_code . to_s , format ( "%.3fs" , task . duration ) , task . command ]
5270 end
53- widths = column_widths ( [ [ "#" , "status" , "exit" , "duration" , "command" ] , *rows ] )
71+ header = [ "#" , "status" , "exit" , "duration" , "command" ]
72+ widths = column_widths ( [ header , *rows ] )
73+ color = color_enabled?
5474
5575 stdout . puts
56- stdout . puts "rparallel report"
57- stdout . puts format_row ( [ "#" , "status" , "exit" , "duration" , "command" ] , widths )
58- rows . each { stdout . puts format_row ( _1 , widths ) }
76+ stdout . puts colorize ( "rparallel report" , :bold , color )
77+ stdout . puts colorize ( border ( widths ) , :dim , color )
78+ stdout . puts format_row ( header , widths , color :, header : true )
79+ stdout . puts colorize ( border ( widths ) , :dim , color )
80+ rows . each { stdout . puts format_row ( _1 , widths , color :) }
81+ stdout . puts colorize ( border ( widths ) , :dim , color )
5982 end
6083
6184 def self . column_widths ( rows )
6285 rows . transpose . map { _1 . map ( &:length ) . max }
6386 end
6487
65- def self . format_row ( row , widths )
66- row . each_with_index . map do |value , index |
67- index == row . length - 1 ? value : value . ljust ( widths [ index ] )
68- end . join ( " " )
88+ def self . format_row ( row , widths , color :, header : false )
89+ cells = row . each_with_index . map do |value , index |
90+ padded = value . ljust ( widths [ index ] )
91+ header ? colorize ( padded , :cyan , color ) : colorize_report_cell ( padded , row , index , color )
92+ end
93+
94+ "| #{ cells . join ( ' | ' ) } |"
95+ end
96+
97+ def self . border ( widths )
98+ "+-#{ widths . map { '-' * _1 } . join ( '-+-' ) } -+"
99+ end
100+
101+ def self . colorize_report_cell ( value , row , index , color )
102+ return colorize ( value , row [ 1 ] == "ok" ? :green : :red , color ) if [ 1 , 2 ] . include? ( index )
103+
104+ value
105+ end
106+
107+ def self . colorize ( value , color_name , enabled )
108+ return value unless enabled
109+
110+ "#{ COLORS . fetch ( color_name ) } #{ value } #{ COLORS . fetch ( :reset ) } "
111+ end
112+
113+ def self . color_enabled?
114+ ENV [ "NO_COLOR" ] . nil? && ENV [ "RPARALLEL_NO_COLOR" ] . nil?
69115 end
70116
71117 def self . usage ( stdout )
@@ -77,7 +123,8 @@ class RParallel
77123
78124 def self . monotonic_time = Process . clock_gettime ( Process ::CLOCK_MONOTONIC )
79125
80- private_class_method :run_task , :print_report , :column_widths , :format_row , :usage , :monotonic_time
126+ private_class_method :run_task , :stream_output , :print_report , :column_widths , :format_row , :border ,
127+ :colorize_report_cell , :colorize , :color_enabled? , :usage , :monotonic_time
81128end
82129
83130exit RParallel . run ( ARGV )
0 commit comments