seeing_is_believing 0.0.9 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/Readme.md +5 -3
- data/features/examples.feature +45 -11
- data/features/flags.feature +70 -0
- data/lib/seeing_is_believing/binary/arg_parser.rb +92 -0
- data/lib/seeing_is_believing/binary/line_formatter.rb +47 -0
- data/lib/seeing_is_believing/binary/print_results_next_to_lines.rb +127 -0
- data/lib/seeing_is_believing/binary.rb +24 -25
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +9 -10
- data/lib/seeing_is_believing/expression_list.rb +8 -11
- data/lib/seeing_is_believing/version.rb +1 -1
- data/lib/seeing_is_believing.rb +42 -15
- data/spec/arg_parser_spec.rb +44 -24
- data/spec/evaluate_by_moving_files_spec.rb +0 -7
- data/spec/expression_list_spec.rb +35 -31
- data/spec/line_formatter_spec.rb +53 -0
- data/spec/seeing_is_believing_spec.rb +10 -5
- metadata +15 -12
- data/lib/seeing_is_believing/arg_parser.rb +0 -94
- data/lib/seeing_is_believing/print_results_next_to_lines.rb +0 -117
data/Gemfile.lock
CHANGED
data/Readme.md
CHANGED
@@ -82,9 +82,11 @@ TextMate Integration
|
|
82
82
|
|
83
83
|
Go to the bundle editor, create this new command in the Ruby bundle:
|
84
84
|
|
85
|
-
|
86
|
-
|
87
|
-
|
85
|
+
```shell
|
86
|
+
"${TM_RUBY}" -r seeing_is_believing/binary -e '
|
87
|
+
SeeingIsBelieving::Binary.new(ARGV, $stdin, $stdout, $stderr).call
|
88
|
+
' $TM_FILEPATH 2>/dev/null
|
89
|
+
```
|
88
90
|
|
89
91
|
It should look like this:
|
90
92
|
|
data/features/examples.feature
CHANGED
@@ -9,18 +9,20 @@ Feature: Running the binary successfully
|
|
9
9
|
Scenario: Some basic functionality
|
10
10
|
Given the file "basic_functionality.rb":
|
11
11
|
"""
|
12
|
+
# iteration
|
12
13
|
5.times do |i|
|
13
14
|
i * 2
|
14
15
|
end
|
15
16
|
|
17
|
+
# method and invocations
|
16
18
|
def meth(n)
|
17
19
|
n
|
18
20
|
end
|
19
21
|
|
20
|
-
# some invocations
|
21
22
|
meth "12"
|
22
23
|
meth "34"
|
23
24
|
|
25
|
+
# block style comments
|
24
26
|
=begin
|
25
27
|
I don't ever actually write
|
26
28
|
comments like this
|
@@ -36,26 +38,43 @@ Feature: Running the binary successfully
|
|
36
38
|
is a doc
|
37
39
|
HERE
|
38
40
|
|
41
|
+
# method invocation that occurs entirely on the next line
|
39
42
|
[*1..10]
|
40
43
|
.select(&:even?)
|
44
|
+
|
45
|
+
# mutliple levels of nesting
|
46
|
+
class User
|
47
|
+
def initialize(name)
|
48
|
+
@name = name
|
49
|
+
end
|
50
|
+
|
51
|
+
def name
|
52
|
+
@name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
User.new("Josh").name
|
57
|
+
User.new("Rick").name
|
41
58
|
"""
|
42
59
|
When I run "seeing_is_believing basic_functionality.rb"
|
43
60
|
Then stderr is empty
|
44
61
|
And the exit status is 0
|
45
62
|
And stdout is:
|
46
63
|
"""
|
64
|
+
# iteration
|
47
65
|
5.times do |i|
|
48
|
-
i * 2
|
49
|
-
end
|
66
|
+
i * 2 # => 0, 2, 4, 6, 8
|
67
|
+
end # => 5
|
50
68
|
|
69
|
+
# method and invocations
|
51
70
|
def meth(n)
|
52
|
-
n
|
53
|
-
end
|
71
|
+
n # => "12", "34"
|
72
|
+
end # => nil
|
54
73
|
|
55
|
-
#
|
56
|
-
meth "
|
57
|
-
meth "34" # => "34"
|
74
|
+
meth "12" # => "12"
|
75
|
+
meth "34" # => "34"
|
58
76
|
|
77
|
+
# block style comments
|
59
78
|
=begin
|
60
79
|
I don't ever actually write
|
61
80
|
comments like this
|
@@ -64,15 +83,30 @@ Feature: Running the binary successfully
|
|
64
83
|
# multilinezzz
|
65
84
|
"a
|
66
85
|
b
|
67
|
-
c"
|
86
|
+
c" # => "a\n b\n c"
|
68
87
|
|
69
88
|
# don't record heredocs b/c they're just too fucking different
|
70
89
|
<<HERE
|
71
90
|
is a doc
|
72
91
|
HERE
|
73
92
|
|
93
|
+
# method invocation that occurs entirely on the next line
|
74
94
|
[*1..10]
|
75
|
-
.select(&:even?)
|
95
|
+
.select(&:even?) # => [2, 4, 6, 8, 10]
|
96
|
+
|
97
|
+
# mutliple levels of nesting
|
98
|
+
class User
|
99
|
+
def initialize(name)
|
100
|
+
@name = name # => "Josh", "Rick"
|
101
|
+
end # => nil
|
102
|
+
|
103
|
+
def name
|
104
|
+
@name # => "Josh", "Rick"
|
105
|
+
end # => nil
|
106
|
+
end # => nil
|
107
|
+
|
108
|
+
User.new("Josh").name # => "Josh"
|
109
|
+
User.new("Rick").name # => "Rick"
|
76
110
|
"""
|
77
111
|
|
78
112
|
Scenario: Passing previous output back into input
|
@@ -140,7 +174,7 @@ Feature: Running the binary successfully
|
|
140
174
|
And the exit status is 0
|
141
175
|
And stdout is:
|
142
176
|
"""
|
143
|
-
__FILE__ # => "
|
177
|
+
__FILE__ # => "some_dir/uses_macros.rb"
|
144
178
|
__LINE__ # => 2
|
145
179
|
$stdout.puts "omg" # => nil
|
146
180
|
$stderr.puts "hi" # => nil
|
data/features/flags.feature
CHANGED
@@ -55,6 +55,76 @@ Feature: Using flags
|
|
55
55
|
4 + 4
|
56
56
|
"""
|
57
57
|
|
58
|
+
Scenario: --result-length sets the length of the portion including and after the # =>
|
59
|
+
Given the file "result_lengths.rb":
|
60
|
+
"""
|
61
|
+
$stdout.puts "a"*100
|
62
|
+
$stderr.puts "a"*100
|
63
|
+
|
64
|
+
"a"
|
65
|
+
"aa"
|
66
|
+
"aaa"
|
67
|
+
"aaaa"
|
68
|
+
|
69
|
+
raise "a"*100
|
70
|
+
"""
|
71
|
+
When I run "seeing_is_believing --result-length 10 result_lengths.rb"
|
72
|
+
Then stderr is empty
|
73
|
+
And stdout is:
|
74
|
+
"""
|
75
|
+
$stdout.puts "a"*100 # => nil
|
76
|
+
$stderr.puts "a"*100 # => nil
|
77
|
+
|
78
|
+
"a" # => "a"
|
79
|
+
"aa" # => "aa"
|
80
|
+
"aaa" # => "aaa"
|
81
|
+
"aaaa" # => "a...
|
82
|
+
|
83
|
+
raise "a"*100 # ~> Ru...
|
84
|
+
|
85
|
+
# >> aa...
|
86
|
+
|
87
|
+
# !> aa...
|
88
|
+
"""
|
89
|
+
|
90
|
+
Scenario: --line-length sets the total length of a given line
|
91
|
+
Given the file "line_lengths.rb":
|
92
|
+
"""
|
93
|
+
$stdout.puts "a"*100
|
94
|
+
$stderr.puts "a"*100
|
95
|
+
|
96
|
+
"aaa"
|
97
|
+
"aaaa"
|
98
|
+
|
99
|
+
raise "a"*100
|
100
|
+
"""
|
101
|
+
When I run "seeing_is_believing --line-length 32 line_lengths.rb"
|
102
|
+
Then stderr is empty
|
103
|
+
And stdout is:
|
104
|
+
"""
|
105
|
+
$stdout.puts "a"*100 # => nil
|
106
|
+
$stderr.puts "a"*100 # => nil
|
107
|
+
|
108
|
+
"aaa" # => "aaa"
|
109
|
+
"aaaa" # => "a...
|
110
|
+
|
111
|
+
raise "a"*100 # ~> Ru...
|
112
|
+
|
113
|
+
# >> aaaaaaaaaaaaaaaaaaaaaaaa...
|
114
|
+
|
115
|
+
# !> aaaaaaaaaaaaaaaaaaaaaaaa...
|
116
|
+
"""
|
117
|
+
Given the file "line_lengths2.rb":
|
118
|
+
"""
|
119
|
+
12345
|
120
|
+
"""
|
121
|
+
When I run "seeing_is_believing --line-length 1 line_lengths2.rb"
|
122
|
+
Then stdout is "12345"
|
123
|
+
When I run "seeing_is_believing --line-length 15 line_lengths2.rb"
|
124
|
+
Then stdout is "12345 # => ..."
|
125
|
+
When I run "seeing_is_believing --line-length 14 line_lengths2.rb"
|
126
|
+
Then stdout is "12345"
|
127
|
+
|
58
128
|
Scenario: --help
|
59
129
|
When I run "seeing_is_believing --help"
|
60
130
|
Then stderr is empty
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# note:
|
2
|
+
# reserve -e for script in an argument
|
3
|
+
# reserve -I for setting load path
|
4
|
+
# reserve -r for requiring another file
|
5
|
+
|
6
|
+
class SeeingIsBelieving
|
7
|
+
class Binary
|
8
|
+
class ArgParser
|
9
|
+
def self.parse(args)
|
10
|
+
new(args).call
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :args
|
14
|
+
|
15
|
+
def initialize(args)
|
16
|
+
self.args = args
|
17
|
+
self.filenames = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
@result ||= begin
|
22
|
+
until args.empty?
|
23
|
+
case (arg = args.shift)
|
24
|
+
when '-h', '--help' then options[:help] = self.class.help_screen
|
25
|
+
when '-l', '--start-line' then extract_positive_int_for :start_line, arg
|
26
|
+
when '-L', '--end-line' then extract_positive_int_for :end_line, arg
|
27
|
+
when '-d', '--line-length' then extract_positive_int_for :line_length, arg
|
28
|
+
when '-D', '--result-length' then extract_positive_int_for :result_length, arg
|
29
|
+
when /^-/ then options[:errors] << "Unknown option: #{arg.inspect}" # unknown flags
|
30
|
+
else # filenames
|
31
|
+
filenames << arg
|
32
|
+
options[:filename] = arg
|
33
|
+
end
|
34
|
+
end
|
35
|
+
normalize_and_validate
|
36
|
+
options
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_accessor :filenames
|
43
|
+
|
44
|
+
def normalize_and_validate
|
45
|
+
if 1 < filenames.size
|
46
|
+
options[:errors] << "Can only have one filename, but had: #{filenames.map(&:inspect).join ', '}"
|
47
|
+
end
|
48
|
+
|
49
|
+
if options[:end_line] < options[:start_line]
|
50
|
+
options[:start_line], options[:end_line] = options[:end_line], options[:start_line]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def options
|
55
|
+
@options ||= {
|
56
|
+
filename: nil,
|
57
|
+
errors: [],
|
58
|
+
start_line: 1,
|
59
|
+
line_length: Float::INFINITY,
|
60
|
+
end_line: Float::INFINITY,
|
61
|
+
result_length: Float::INFINITY,
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def extract_positive_int_for(key, flag)
|
66
|
+
string = args.shift
|
67
|
+
int = string.to_i
|
68
|
+
if int.to_s == string && 0 < int
|
69
|
+
options[key] = int
|
70
|
+
else
|
71
|
+
options[:errors] << "#{flag} expects a positive integer argument"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def ArgParser.help_screen
|
77
|
+
<<HELP_SCREEN
|
78
|
+
Usage: #{$0} [options] [filename]
|
79
|
+
|
80
|
+
#{$0} is a program and library that will evaluate a Ruby file and capture/display the results.
|
81
|
+
|
82
|
+
If no filename is provided, the binary will read the program from standard input.
|
83
|
+
|
84
|
+
-l, --start-line # line number to begin showing results on
|
85
|
+
-L, --end-line # line number to stop showing results on
|
86
|
+
-d, --line-length # max length of the entire line (only truncates results, not source lines)
|
87
|
+
-D, --result-length # max length of the portion after the "# => "
|
88
|
+
-h, --help # this help screen
|
89
|
+
HELP_SCREEN
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class SeeingIsBelieving
|
2
|
+
class Binary
|
3
|
+
class LineFormatter
|
4
|
+
attr_accessor :line, :separator, :result, :options
|
5
|
+
|
6
|
+
def initialize(line, separator, result, options)
|
7
|
+
self.line = line
|
8
|
+
self.separator = separator
|
9
|
+
self.result = result
|
10
|
+
self.options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
return line unless sep_plus_result.start_with? separator
|
15
|
+
return line unless formatted_line.start_with? "#{line_with_padding}#{separator}"
|
16
|
+
formatted_line
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def line_length
|
22
|
+
options.fetch :line_length, Float::INFINITY
|
23
|
+
end
|
24
|
+
|
25
|
+
def result_length
|
26
|
+
options.fetch :result_length, Float::INFINITY
|
27
|
+
end
|
28
|
+
|
29
|
+
def sep_plus_result
|
30
|
+
@sep_plus_result ||= truncate "#{separator}#{result}", result_length
|
31
|
+
end
|
32
|
+
|
33
|
+
def formatted_line
|
34
|
+
@formatted_line ||= truncate "#{line_with_padding}#{sep_plus_result}", line_length
|
35
|
+
end
|
36
|
+
|
37
|
+
def line_with_padding
|
38
|
+
"%-#{options[:source_length]}s" % line
|
39
|
+
end
|
40
|
+
|
41
|
+
def truncate(string, length)
|
42
|
+
return string if string.size <= length
|
43
|
+
string.slice(0, length).sub(/.{0,3}$/) { |last_chars| last_chars.gsub /./, '.' }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'seeing_is_believing/queue'
|
2
|
+
require 'seeing_is_believing/has_exception'
|
3
|
+
require 'seeing_is_believing/binary/line_formatter'
|
4
|
+
|
5
|
+
class SeeingIsBelieving
|
6
|
+
class Binary
|
7
|
+
class PrintResultsNextToLines
|
8
|
+
include HasException
|
9
|
+
|
10
|
+
STDOUT_PREFIX = '# >>'
|
11
|
+
STDERR_PREFIX = '# !>'
|
12
|
+
EXCEPTION_PREFIX = '# ~>'
|
13
|
+
RESULT_PREFIX = '# =>'
|
14
|
+
|
15
|
+
def self.remove_previous_output_from(string)
|
16
|
+
string.gsub(/\s+(#{EXCEPTION_PREFIX}|#{RESULT_PREFIX}).*?$/, '')
|
17
|
+
.gsub(/\n?(^#{STDOUT_PREFIX}[^\n]*\r?\n?)+/m, '')
|
18
|
+
.gsub(/\n?(^#{STDERR_PREFIX}[^\n]*\r?\n?)+/m, '')
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def self.method_from_options(*args)
|
23
|
+
define_method(args.first) { options.fetch *args }
|
24
|
+
end
|
25
|
+
|
26
|
+
method_from_options :filename, nil
|
27
|
+
method_from_options :start_line
|
28
|
+
method_from_options :end_line
|
29
|
+
method_from_options :line_length, Float::INFINITY
|
30
|
+
method_from_options :result_length, Float::INFINITY
|
31
|
+
|
32
|
+
|
33
|
+
def initialize(body, file_result, options={})
|
34
|
+
self.body = body
|
35
|
+
self.options = options
|
36
|
+
self.file_result = file_result
|
37
|
+
end
|
38
|
+
|
39
|
+
def new_body
|
40
|
+
@new_body ||= ''
|
41
|
+
end
|
42
|
+
|
43
|
+
def call
|
44
|
+
add_each_line_until_start_or_data_segment
|
45
|
+
add_lines_with_results_until_end_or_data_segment
|
46
|
+
add_lines_until_data_segment
|
47
|
+
add_stdout
|
48
|
+
add_stderr
|
49
|
+
add_remaining_lines
|
50
|
+
return new_body
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_accessor :body, :file_result, :options
|
56
|
+
|
57
|
+
def add_each_line_until_start_or_data_segment
|
58
|
+
line_queue.until { |line, line_number| line_number == start_line || start_of_data_segment?(line) }
|
59
|
+
.each { |line, line_number| new_body << line }
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_lines_with_results_until_end_or_data_segment
|
63
|
+
line_queue.until { |line, line_number| end_line < line_number || start_of_data_segment?(line) }
|
64
|
+
.each { |line, line_number| new_body << format_line(line.chomp, file_result[line_number]) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_lines_until_data_segment
|
68
|
+
line_queue.until { |line, line_number| start_of_data_segment?(line) }
|
69
|
+
.each { |line, line_number| new_body << line }
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_remaining_lines
|
73
|
+
line_queue.each { |line, line_number| new_body << line }
|
74
|
+
end
|
75
|
+
|
76
|
+
def line_queue
|
77
|
+
@line_queue ||= Queue.new &body.each_line.with_index(1).to_a.method(:shift)
|
78
|
+
end
|
79
|
+
|
80
|
+
def start_of_data_segment?(line)
|
81
|
+
line.chomp == '__END__'
|
82
|
+
end
|
83
|
+
|
84
|
+
# max line length of the lines to output (exempting coments) + 2 spaces for padding
|
85
|
+
def max_source_line_length
|
86
|
+
@max_source_line_length ||= 2 + body.each_line
|
87
|
+
.map(&:chomp)
|
88
|
+
.select.with_index(1) { |line, index| start_line <= index && index <= end_line }
|
89
|
+
.take_while { |line| not start_of_data_segment? line }
|
90
|
+
.select { |line| not (line == "=begin") .. (line == "=end") }
|
91
|
+
.reject { |line| SyntaxAnalyzer.ends_in_comment? line }
|
92
|
+
.map(&:length)
|
93
|
+
.max
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_stdout
|
97
|
+
return unless file_result.has_stdout?
|
98
|
+
new_body << "\n"
|
99
|
+
file_result.stdout.each_line do |line|
|
100
|
+
new_body << LineFormatter.new('', "#{STDOUT_PREFIX} ", line.chomp, options).call << "\n"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_stderr
|
105
|
+
return unless file_result.has_stderr?
|
106
|
+
new_body << "\n"
|
107
|
+
file_result.stderr.each_line do |line|
|
108
|
+
new_body << LineFormatter.new('', "#{STDERR_PREFIX} ", line.chomp, options).call << "\n"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def format_line(line, line_results)
|
113
|
+
options = options().merge source_length: max_source_line_length
|
114
|
+
formatted_line = if line_results.has_exception?
|
115
|
+
result = sprintf "%s: %s", line_results.exception.class, line_results.exception.message
|
116
|
+
LineFormatter.new(line, "#{EXCEPTION_PREFIX} ", result, options).call
|
117
|
+
elsif line_results.any?
|
118
|
+
LineFormatter.new(line, "#{RESULT_PREFIX} ", line_results.join(', '), options).call
|
119
|
+
else
|
120
|
+
line
|
121
|
+
end
|
122
|
+
formatted_line + "\n"
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|