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 CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- seeing_is_believing (0.0.8)
4
+ seeing_is_believing (0.0.9)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
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
- "${TM_RUBY}" -r seeing_is_believing/binary -e '
86
- SeeingIsBelieving::Binary.new(ARGV, $stdin, $stdout, $stderr).call
87
- ' $TM_FILEPATH 2>/dev/null
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
 
@@ -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 # => 0, 2, 4, 6, 8
49
- end # => 5
66
+ i * 2 # => 0, 2, 4, 6, 8
67
+ end # => 5
50
68
 
69
+ # method and invocations
51
70
  def meth(n)
52
- n # => "12", "34"
53
- end # => nil
71
+ n # => "12", "34"
72
+ end # => nil
54
73
 
55
- # some invocations
56
- meth "12" # => "12"
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" # => "a\n b\n 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?) # => [2, 4, 6, 8, 10]
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__ # => "{{CommandLineHelpers.path_to 'some_dir/uses_macros.rb'}}"
177
+ __FILE__ # => "some_dir/uses_macros.rb"
144
178
  __LINE__ # => 2
145
179
  $stdout.puts "omg" # => nil
146
180
  $stderr.puts "hi" # => nil
@@ -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