seeing_is_believing 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|