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 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