line 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +26 -0
- data/README.md +50 -0
- data/bin/line +7 -0
- data/lib/line/matchers/and.rb +21 -0
- data/lib/line/matchers/helpers.rb +12 -0
- data/lib/line/matchers/index.rb +23 -0
- data/lib/line/matchers/match_everything.rb +17 -0
- data/lib/line/matchers/match_nothing.rb +17 -0
- data/lib/line/matchers/not.rb +23 -0
- data/lib/line/matchers/or.rb +21 -0
- data/lib/line/matchers/range.rb +30 -0
- data/lib/line/matchers.rb +7 -0
- data/lib/line/options.rb +51 -0
- data/lib/line/options_parser.rb +130 -0
- data/lib/line/queue_with_indexes.rb +65 -0
- data/lib/line/version.rb +3 -0
- data/lib/line.rb +96 -0
- data/line.gemspec +23 -0
- data/spec/integration_spec.rb +6 -0
- data/spec/line_spec.rb +114 -0
- data/spec/options_parser_spec.rb +189 -0
- data/spec/options_spec.rb +52 -0
- data/spec/queue_with_indexes_spec.rb +68 -0
- data/spec/spec_helper.rb +75 -0
- data/spec/spec_helper_spec.rb +42 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3e6ded1a189126b7fba919e66731a3707065fe38
|
4
|
+
data.tar.gz: 556d3175f3e8b67bf7f745424391ce94af4b5b55
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e74e2df08ffe42b3db39cd7b596d833bc5a5d9557b99423461680db4cc028b185ddec942c47de26c6384be3e041a4ae2aec14fc5212dbb85bb8ee4a63c6049e5
|
7
|
+
data.tar.gz: 92e7eedd6d1afe3dd0d1cf239167eb75dfa9ed8384d4ad7a415a4b1d86d9d544349e673c4a886a79f6120fb46b9b2d92ed2ffeb5a634fc896eadcfaf136291fd
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
line (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.1)
|
10
|
+
rspec (2.13.0)
|
11
|
+
rspec-core (~> 2.13.0)
|
12
|
+
rspec-expectations (~> 2.13.0)
|
13
|
+
rspec-mocks (~> 2.13.0)
|
14
|
+
rspec-core (2.13.1)
|
15
|
+
rspec-expectations (2.13.0)
|
16
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
17
|
+
rspec-mocks (2.13.0)
|
18
|
+
surrogate (0.7.0)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
line!
|
25
|
+
rspec
|
26
|
+
surrogate
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
Line
|
2
|
+
====
|
3
|
+
|
4
|
+
Command line tool to print the lines from stdinput that are matched by the matchers
|
5
|
+
e.g. `line 1` prints the first line
|
6
|
+
|
7
|
+
|
8
|
+
Usage: line [options] matchers
|
9
|
+
|
10
|
+
matchers:
|
11
|
+
2 matches the second line
|
12
|
+
-2 matches the second from the last line
|
13
|
+
^2 matches lines other than the second
|
14
|
+
1..10 matches lines 1 through 10 (the numbers can be negative)
|
15
|
+
^5..10 matches all lines before the fifth and all lines after the tenth
|
16
|
+
|
17
|
+
options:
|
18
|
+
-l, --line-numbers show line numbers in output
|
19
|
+
-s, --strip strip leading and tailing whitespace
|
20
|
+
-f, --force do not err when told to print a line number beyond the input
|
21
|
+
-c, --chomp no newlines between lines in the output
|
22
|
+
-h, --help this help screen
|
23
|
+
|
24
|
+
examples:
|
25
|
+
line 1 22 # prints lines 1 and 22
|
26
|
+
line -1 # prints the last line
|
27
|
+
line ^1 ^-1 # prints all lines but the first and the last
|
28
|
+
line 1..10 # prints lines 1 through 10
|
29
|
+
line 5..-5 # prints all lines except the first and last four
|
30
|
+
line ^5..10 # prins all lines except 5 through ten
|
31
|
+
line 5..10 ^6..8 # prints lines 5, 9, 10
|
32
|
+
line 5..10 ^7 # prints lines 5, 6, 8, 9
|
33
|
+
|
34
|
+
License
|
35
|
+
=======
|
36
|
+
|
37
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
38
|
+
Version 2, December 2004
|
39
|
+
|
40
|
+
Copyright (C) 2012 Josh Cheek <josh.cheek@gmail.com>
|
41
|
+
|
42
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
43
|
+
copies of this license document, and changing it is allowed as long
|
44
|
+
as the name is changed.
|
45
|
+
|
46
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
47
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
48
|
+
|
49
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
50
|
+
|
data/bin/line
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'line/matchers/helpers'
|
2
|
+
|
3
|
+
class Line
|
4
|
+
module Matchers
|
5
|
+
class And
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
def initialize(matcher1, matcher2)
|
9
|
+
@matcher1, @matcher2 = matcher1, matcher2
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(*args)
|
13
|
+
@matcher1.matches?(*args) && @matcher2.matches?(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect(parent=false)
|
17
|
+
inspect_helper parent, "#{@matcher1.inspect self} && #{@matcher2.inspect self}", true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Line
|
2
|
+
module Matchers
|
3
|
+
module Helpers
|
4
|
+
def inspect_helper(parent, inspected, has_children)
|
5
|
+
return inspected if parent && parent.class == self.class
|
6
|
+
return "(#{inspected})" if parent && has_children
|
7
|
+
return inspected if parent
|
8
|
+
"Matcher(#{inspected})"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'line/matchers/helpers'
|
2
|
+
|
3
|
+
class Line
|
4
|
+
module Matchers
|
5
|
+
class Index
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
attr_accessor :index
|
9
|
+
|
10
|
+
def initialize(index)
|
11
|
+
self.index = index
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(line, positive_index, negative_index)
|
15
|
+
positive_index == index || negative_index == index
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect(parent=false)
|
19
|
+
inspect_helper parent, index.to_s, false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'line/matchers/helpers'
|
2
|
+
|
3
|
+
class Line
|
4
|
+
module Matchers
|
5
|
+
class MatchEverything
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
def matches?(*)
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect(parent=false)
|
13
|
+
inspect_helper parent, "MatchEverything", false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'line/matchers/helpers'
|
2
|
+
|
3
|
+
class Line
|
4
|
+
module Matchers
|
5
|
+
class Not
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
attr_accessor :matcher
|
9
|
+
|
10
|
+
def initialize(matcher)
|
11
|
+
self.matcher = matcher
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(line, positive_index, negative_index)
|
15
|
+
!matcher.matches?(line, positive_index, negative_index)
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect(parent=false)
|
19
|
+
inspect_helper parent, "^#{matcher.inspect self}", true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'line/matchers/helpers'
|
2
|
+
|
3
|
+
class Line
|
4
|
+
module Matchers
|
5
|
+
class Or
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
def initialize(matcher1, matcher2)
|
9
|
+
@matcher1, @matcher2 = matcher1, matcher2
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(*args)
|
13
|
+
@matcher1.matches?(*args) || @matcher2.matches?(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect(parent=false)
|
17
|
+
inspect_helper parent, "#{@matcher1.inspect self} || #{@matcher2.inspect self}", true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'line/matchers/helpers'
|
2
|
+
|
3
|
+
class Line
|
4
|
+
module Matchers
|
5
|
+
class Range
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
attr_accessor :lower, :upper
|
9
|
+
|
10
|
+
def initialize(lower, upper)
|
11
|
+
self.lower, self.upper = lower, upper
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(line, positive_index, negative_index)
|
15
|
+
if negative_index && lower < 0 && upper < 0 then lower <= negative_index && negative_index <= upper
|
16
|
+
elsif negative_index && lower < 0 then lower <= negative_index && positive_index <= upper
|
17
|
+
elsif negative_index && upper < 0 then lower <= positive_index && negative_index <= upper
|
18
|
+
elsif 0 < lower && 0 < upper then lower <= positive_index && positive_index <= upper
|
19
|
+
elsif 0 < lower then lower <= positive_index
|
20
|
+
elsif 0 < upper then positive_index <= upper
|
21
|
+
else false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect(parent=false)
|
26
|
+
inspect_helper parent, "#{lower}..#{upper}", false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/line/options.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
class Line
|
2
|
+
class Options
|
3
|
+
attr_accessor :show_help, :strip, :force, :chomp, :indexes, :errors, :line_matcher, :line_numbers
|
4
|
+
attr_accessor :instream, :outstream, :errstream, :help_screen, :buffer_size, :debug
|
5
|
+
|
6
|
+
def initialize(attributes={})
|
7
|
+
update attributes
|
8
|
+
yield self if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def update(attributes)
|
12
|
+
attributes.each { |attribute, value| __send__ "#{attribute}=", value }
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
alias line_numbers? line_numbers
|
17
|
+
alias show_help? show_help
|
18
|
+
alias debug? debug
|
19
|
+
alias strip? strip
|
20
|
+
alias force? force
|
21
|
+
alias chomp? chomp
|
22
|
+
|
23
|
+
def help_screen
|
24
|
+
@help_screen ||= ''
|
25
|
+
end
|
26
|
+
|
27
|
+
def indexes
|
28
|
+
@indexes ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
def errors
|
32
|
+
@errors ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def instream
|
36
|
+
@instream ||= $stdin
|
37
|
+
end
|
38
|
+
|
39
|
+
def outstream
|
40
|
+
@outstream ||= $stdout
|
41
|
+
end
|
42
|
+
|
43
|
+
def errstream
|
44
|
+
@errstream ||= $stderr
|
45
|
+
end
|
46
|
+
|
47
|
+
def buffer_size
|
48
|
+
@buffer_size ||= 0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'line/options'
|
2
|
+
require 'line/matchers'
|
3
|
+
|
4
|
+
class Line
|
5
|
+
class OptionParser
|
6
|
+
def self.call(*args)
|
7
|
+
new(*args).call
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(arguments)
|
11
|
+
@arguments = arguments
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
return @options if @options
|
16
|
+
self.options = Options.new
|
17
|
+
|
18
|
+
@arguments.each do |arg|
|
19
|
+
case arg
|
20
|
+
when '-l', '--line-numbers' then options.line_numbers = true
|
21
|
+
when '-h', '--help' then options.show_help = true
|
22
|
+
when '-s', '--strip' then options.strip = true
|
23
|
+
when '-f', '--force' then options.force = true
|
24
|
+
when '-c', '--chomp' then options.chomp = true
|
25
|
+
when '-d', '--debug' then options.debug = true
|
26
|
+
when '0' then invalid_args << arg
|
27
|
+
# range
|
28
|
+
when /\A-?\d+..-?\d+/
|
29
|
+
lower, upper = arg.split('..').map(&:to_i)
|
30
|
+
options.indexes << lower << upper
|
31
|
+
positive_matchers << Matchers::Range.new(lower, upper)
|
32
|
+
# index
|
33
|
+
when /\A-?\d+\Z/
|
34
|
+
index = arg.to_i
|
35
|
+
options.indexes << index
|
36
|
+
positive_matchers << Matchers::Index.new(index)
|
37
|
+
# negated index
|
38
|
+
when /\A\^-?\d+\Z/
|
39
|
+
index = arg[1..-1].to_i
|
40
|
+
options.indexes << index
|
41
|
+
negative_matchers << Matchers::Not.new(Matchers::Index.new(index))
|
42
|
+
when /\A\^-?\d+..-?\d+\Z/
|
43
|
+
lower, upper = arg[1..-1].split('..').map(&:to_i)
|
44
|
+
options.indexes << lower << upper
|
45
|
+
negative_matchers << Matchers::Not.new(Matchers::Range.new(lower, upper))
|
46
|
+
else
|
47
|
+
invalid_args << arg
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
options.errors[:line_numbers] = error_msg_for_line_numbers if error_msg_for_line_numbers
|
52
|
+
options.line_matcher = consolidate_args_to_line_matcher
|
53
|
+
options.buffer_size = [0, *options.indexes].min.abs
|
54
|
+
options.help_screen = help_screen
|
55
|
+
options
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
attr_accessor :options
|
61
|
+
|
62
|
+
def error_msg_for_line_numbers
|
63
|
+
if invalid_args.size == 1
|
64
|
+
"#{invalid_args.first.inspect} is not a valid line number, offsets start from 1"
|
65
|
+
elsif invalid_args.size > 1
|
66
|
+
inspected_args = invalid_args.map(&:inspect)
|
67
|
+
inspected_args[-1] = "and #{inspected_args[-1]}"
|
68
|
+
"#{inspected_args.join ', '} are not valid line numbers, offsets start from 1"
|
69
|
+
elsif positive_matchers.empty? && negative_matchers.empty? && !options.line_numbers?
|
70
|
+
'No matchers provided'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def consolidate_args_to_line_matcher
|
75
|
+
positive_matcher = positive_matchers.inject(Matchers::MatchNothing.new) { |memo, current| Matchers::Or.new memo, current }
|
76
|
+
negative_matcher = negative_matchers.inject(Matchers::MatchEverything.new) { |memo, current| Matchers::And.new memo, current }
|
77
|
+
|
78
|
+
if positive_matchers.any? && negative_matchers.any? then Matchers::And.new positive_matcher, negative_matcher
|
79
|
+
elsif positive_matchers.any? then positive_matcher
|
80
|
+
elsif negative_matchers.any? then negative_matcher
|
81
|
+
else Matchers::MatchEverything.new
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def invalid_args
|
86
|
+
@invalid_args ||= []
|
87
|
+
end
|
88
|
+
|
89
|
+
def negative_matchers
|
90
|
+
@negative_matchers ||= []
|
91
|
+
end
|
92
|
+
|
93
|
+
def positive_matchers
|
94
|
+
@positive_matchers ||= []
|
95
|
+
end
|
96
|
+
|
97
|
+
def help_screen
|
98
|
+
<<-HELP.gsub(/^ /, '')
|
99
|
+
Usage: line [options] matchers
|
100
|
+
|
101
|
+
Prints the lines from stdinput that are matched by the matchers
|
102
|
+
e.g. `line 1` prints the first line
|
103
|
+
|
104
|
+
matchers:
|
105
|
+
2 matches the second line
|
106
|
+
-2 matches the second from the last line
|
107
|
+
^2 matches lines other than the second
|
108
|
+
1..10 matches lines 1 through 10 (the numbers can be negative)
|
109
|
+
^5..10 matches all lines before the fifth and all lines after the tenth
|
110
|
+
|
111
|
+
options:
|
112
|
+
-l, --line-numbers show line numbers in output
|
113
|
+
-s, --strip strip leading and tailing whitespace
|
114
|
+
-f, --force do not err when told to print a line number beyond the input
|
115
|
+
-c, --chomp no newlines between lines in the output
|
116
|
+
-h, --help this help screen
|
117
|
+
|
118
|
+
examples:
|
119
|
+
line 1 22 # prints lines 1 and 22
|
120
|
+
line -1 # prints the last line
|
121
|
+
line ^1 ^-1 # prints all lines but the first and the last
|
122
|
+
line 1..10 # prints lines 1 through 10
|
123
|
+
line 5..-5 # prints all lines except the first and last four
|
124
|
+
line ^5..10 # prins all lines except 5 through ten
|
125
|
+
line 5..10 ^6..8 # prints lines 5, 9, 10
|
126
|
+
line 5..10 ^7 # prints lines 5, 6, 8, 9
|
127
|
+
HELP
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class Line
|
2
|
+
class QueueWithIndexes
|
3
|
+
def initialize(num_negatives=0, &input_generator)
|
4
|
+
self.positive_index = 0
|
5
|
+
self.buffer = []
|
6
|
+
self.num_negatives = num_negatives
|
7
|
+
self.input_generator = input_generator
|
8
|
+
end
|
9
|
+
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
return to_enum :each unless block
|
14
|
+
fill_the_buffer until dry_generator? || full_buffer?
|
15
|
+
flow_through_buffer(&block) until dry_generator?
|
16
|
+
drain_the_buffer(&block) until empty?
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def empty?
|
21
|
+
fill_the_buffer
|
22
|
+
buffer.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_accessor :dry_generator, :positive_index, :buffer, :num_negatives, :input_generator
|
28
|
+
alias dry_generator? dry_generator
|
29
|
+
|
30
|
+
def full_buffer?
|
31
|
+
num_negatives <= buffer.size
|
32
|
+
end
|
33
|
+
|
34
|
+
def fill_the_buffer
|
35
|
+
input = generate_input
|
36
|
+
buffer << input unless dry_generator?
|
37
|
+
end
|
38
|
+
|
39
|
+
def flow_through_buffer(&block)
|
40
|
+
flow_in = generate_input
|
41
|
+
return if dry_generator?
|
42
|
+
buffer << flow_in
|
43
|
+
flow_out = buffer.shift
|
44
|
+
block.call [flow_out, increment_index, nil]
|
45
|
+
end
|
46
|
+
|
47
|
+
def drain_the_buffer(&block)
|
48
|
+
negative_index = -buffer.size
|
49
|
+
value = buffer.shift
|
50
|
+
block.call [value, increment_index, negative_index]
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_input
|
54
|
+
input_generator.call
|
55
|
+
rescue StopIteration
|
56
|
+
self.dry_generator = true
|
57
|
+
end
|
58
|
+
|
59
|
+
def increment_index
|
60
|
+
old_index = positive_index
|
61
|
+
self.positive_index = positive_index + 1
|
62
|
+
old_index
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/line/version.rb
ADDED
data/lib/line.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'line/queue_with_indexes'
|
2
|
+
|
3
|
+
class Line
|
4
|
+
def self.call(*args)
|
5
|
+
new(*args).call
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_accessor :options
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
print_matcher if options.debug?
|
16
|
+
|
17
|
+
if options.show_help?
|
18
|
+
print_help
|
19
|
+
return 0
|
20
|
+
end
|
21
|
+
|
22
|
+
if options.errors.any?
|
23
|
+
print_errors
|
24
|
+
return 1
|
25
|
+
end
|
26
|
+
|
27
|
+
print_lines
|
28
|
+
|
29
|
+
if unseen_indexes.any? && !options.force?
|
30
|
+
print_unseen_indexes
|
31
|
+
return 1
|
32
|
+
end
|
33
|
+
|
34
|
+
return 0
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_accessor :max_index
|
40
|
+
|
41
|
+
def unseen_indexes
|
42
|
+
@unseen_indexes ||= options.indexes.dup
|
43
|
+
end
|
44
|
+
|
45
|
+
def print_help
|
46
|
+
options.outstream.puts options.help_screen
|
47
|
+
end
|
48
|
+
|
49
|
+
def print_matcher
|
50
|
+
options.errstream.puts options.line_matcher.inspect
|
51
|
+
end
|
52
|
+
|
53
|
+
def high_indexes
|
54
|
+
@high_indexes ||= options.indexes.select { |index| index > max_index }
|
55
|
+
end
|
56
|
+
|
57
|
+
def print_errors
|
58
|
+
options.errors.each do |type, message|
|
59
|
+
options.errstream.puts message
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def print_unseen_indexes
|
64
|
+
options.errstream.puts "Only saw #{max_index} lines of input, can't print lines: #{unseen_indexes.join ', '}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def print_lines
|
68
|
+
each_line do |line, positive_index, negative_index|
|
69
|
+
unseen_indexes.delete positive_index
|
70
|
+
unseen_indexes.delete negative_index
|
71
|
+
next unless options.line_matcher.matches? line, positive_index, negative_index
|
72
|
+
line = line.strip if options.strip?
|
73
|
+
line = "#{positive_index}\t#{line}" if options.line_numbers?
|
74
|
+
if options.chomp?
|
75
|
+
options.outstream.print line.chomp
|
76
|
+
else
|
77
|
+
options.outstream.puts line
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def each_line
|
83
|
+
each_line = options.instream.each_line.method(:next)
|
84
|
+
QueueWithIndexes.new(options.buffer_size, &each_line).each do |line, positive_index, negative_index|
|
85
|
+
positive_index += 1
|
86
|
+
self.max_index = positive_index
|
87
|
+
debug_line line, positive_index, negative_index
|
88
|
+
yield line, positive_index, negative_index
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def debug_line(line, positive_index, negative_index)
|
93
|
+
return unless options.debug?
|
94
|
+
options.errstream.puts "#{line.inspect}, #{positive_index.inspect}, #{negative_index.inspect}"
|
95
|
+
end
|
96
|
+
end
|
data/line.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "line/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "line"
|
7
|
+
s.version = Line::VERSION
|
8
|
+
s.authors = ["Josh Cheek"]
|
9
|
+
s.email = ["josh.cheek@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/JoshCheek/surrogate"
|
11
|
+
s.summary = %q{Command line tool to filter lines of input based on index.}
|
12
|
+
s.description = %q{Command line tool to filter lines of input based on index.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "line"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
s.add_development_dependency "surrogate"
|
23
|
+
end
|
data/spec/line_spec.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Line do
|
4
|
+
let(:_stderr) { StringIO.new }
|
5
|
+
let(:_stdout) { StringIO.new }
|
6
|
+
let(:_stdin) { StringIO.new 100.times.map { |i| "line#{i.next}" }.join("\n") }
|
7
|
+
let(:args) { ['-l'] }
|
8
|
+
let(:options) { parse(args).update errstream: _stderr, outstream: _stdout, instream: _stdin }
|
9
|
+
let(:stderr) { exitstatus; _stderr.string }
|
10
|
+
let(:stdout) { exitstatus; _stdout.string }
|
11
|
+
let(:exitstatus) { described_class.new(options).call }
|
12
|
+
|
13
|
+
context 'when there are errors' do
|
14
|
+
it 'prints the errors to the error stream and has an exit status of 1' do
|
15
|
+
options.errors = {whatever: "MAH ERRAH"}
|
16
|
+
stderr.should == "MAH ERRAH\n"
|
17
|
+
exitstatus.should == 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when debug is set' do
|
22
|
+
before { args << '-d' }
|
23
|
+
it "prints the matcher's inspection to the errstream" do
|
24
|
+
stderr.should include 'Matcher'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "prints each line, and its indexes" do
|
28
|
+
_stdin.string = "a\nb\nc\n"
|
29
|
+
args << '-2'
|
30
|
+
stderr.should include '"a\n", 1, nil'
|
31
|
+
stderr.should include '"b\n", 2, -2'
|
32
|
+
stderr.should include '"c\n", 3, -1'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when there are no errors' do
|
37
|
+
it 'prints nothing to stderr, and has an exit status of 0' do
|
38
|
+
options.errors = {}
|
39
|
+
stderr.should == ""
|
40
|
+
exitstatus.should == 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
specify 'when show_help is set, it displays the help screen and exits with 0' do
|
45
|
+
options.help_screen = 'HELPME'
|
46
|
+
options.show_help = true
|
47
|
+
stderr.should == ''
|
48
|
+
stdout.should == "HELPME\n"
|
49
|
+
exitstatus.should == 0
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'prints the input lines at the specified indexes, starting at 1' do
|
53
|
+
args.replace %w[1 3]
|
54
|
+
stdout.should == "line1\nline3\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'prints negative indexes from the end' do
|
58
|
+
args.replace %w[98 100 -2]
|
59
|
+
stdout.should == "line98\nline99\nline100\n"
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when there are not enough lines to print the given indexes' do
|
63
|
+
it 'does not print an error and exits 0 when force is set' do
|
64
|
+
args.replace %w[-f 101]
|
65
|
+
exitstatus.should == 0
|
66
|
+
stderr.should be_empty
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'prints an error and exits 1 when force is not set' do
|
70
|
+
args.replace %w[100 101 102 -100 -101]
|
71
|
+
exitstatus.should == 1
|
72
|
+
stderr.should == "Only saw 100 lines of input, can't print lines: 101, 102, -101\n"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when there is whitespace wrapping the lines' do
|
77
|
+
before do
|
78
|
+
_stdin.string = " 1 \n2"
|
79
|
+
args.replace %w[1 2]
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'does not print the whitespace when strip is set' do
|
83
|
+
options.strip = true
|
84
|
+
stdout.should == "1\n2\n"
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'prints the whitespace when strip is not set' do
|
88
|
+
options.strip = false
|
89
|
+
stdout.should == " 1 \n2\n"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when chomp is set' do
|
94
|
+
it 'does not print newlines between the input lines' do
|
95
|
+
_stdin.string = " 1 \n2"
|
96
|
+
args.replace %w[-c 1 2]
|
97
|
+
stdout.should == " 1 2"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when line_numbers is set' do
|
102
|
+
it 'prints the line numbers in front of the lines' do
|
103
|
+
args.replace %w[1 2 -l]
|
104
|
+
_stdin.string = "line1\nline2"
|
105
|
+
stdout.should == "1\tline1\n2\tline2\n"
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'does not interfere with other options' do
|
109
|
+
args.replace %w[1 -s -l]
|
110
|
+
_stdin.string = ' 123 '
|
111
|
+
stdout.should == "1\t123\n"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Line::OptionParser do
|
4
|
+
specify 'it sets the help screen on the options' do
|
5
|
+
parse([]).help_screen.tap do |help|
|
6
|
+
help.should include '-h'
|
7
|
+
help.should include '-s'
|
8
|
+
help.should include '-f'
|
9
|
+
help.should include '-c'
|
10
|
+
help.should include '-l'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
specify '-h, --help sets show_help?' do
|
15
|
+
parse([]).show_help?.should be_false
|
16
|
+
parse(['-h']).show_help?.should be_true
|
17
|
+
parse(['--help']).show_help?.should be_true
|
18
|
+
end
|
19
|
+
|
20
|
+
specify '-s, --strip sets strip' do
|
21
|
+
parse([]).strip?.should be_false
|
22
|
+
parse(['-s']).strip?.should be_true
|
23
|
+
parse(['--strip']).strip?.should be_true
|
24
|
+
end
|
25
|
+
|
26
|
+
specify '-f, --force sets force' do
|
27
|
+
parse([]).force?.should be_false
|
28
|
+
parse(['-f']).force?.should be_true
|
29
|
+
parse(['--force']).force?.should be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
specify '-c, --chomp sets chomp' do
|
33
|
+
parse([]).chomp?.should be_false
|
34
|
+
parse(['-c']).chomp?.should be_true
|
35
|
+
parse(['--chomp']).chomp?.should be_true
|
36
|
+
end
|
37
|
+
|
38
|
+
specify '-l, --line-numbers sets the line numbers option' do
|
39
|
+
parse([]).line_numbers?.should be_false
|
40
|
+
parse(['-l']).line_numbers?.should be_true
|
41
|
+
parse(['--line-numbers']).line_numbers?.should be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
specify '-l matches everything when no other matchers are provided' do
|
45
|
+
parse(['-l']).line_matcher.should match_indexes *1..5
|
46
|
+
parse(['-l', '1']).line_matcher.should match_index 1
|
47
|
+
parse(['-l', '1']).line_matcher.should match_no_indexes 0, -1
|
48
|
+
end
|
49
|
+
|
50
|
+
specify '-d sets debug' do
|
51
|
+
parse([]).debug?.should be_false
|
52
|
+
parse(['-d']).debug?.should be_true
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'sets the buffer_size to 0 if there are no negative indexes' do
|
56
|
+
parse(['1']).buffer_size.should == 0
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'sets the buffer_size such that it can access all the negative numbers' do
|
60
|
+
parse(['1', '-1', '-3', '-2', '10']).buffer_size.should == 3
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'has errors when no matchers are provided and -l is not set' do
|
64
|
+
parse(['1']).errors.should_not have_key :line_numbers
|
65
|
+
parse(['-l']).errors.should_not have_key :line_numbers
|
66
|
+
parse([]).errors[:line_numbers].should == "No matchers provided"
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'has errors for 0, informing user that offset starts at 1' do
|
70
|
+
parse(['1']).errors.should_not have_key :line_numbers
|
71
|
+
parse(['0']).errors[:line_numbers].should == '"0" is not a valid line number, offsets start from 1'
|
72
|
+
parse(['0', 'a', 'b']).errors[:line_numbers].should == '"0", "a", and "b" are not valid line numbers, offsets start from 1'
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when given integral arguments (e.g. 1, -1)' do
|
76
|
+
it 'has no errors' do
|
77
|
+
parse(['1']).errors.should == {}
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'sets the buffer size' do
|
81
|
+
parse(['-5']).buffer_size.should == 5
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'treats them as as lines to match' do
|
85
|
+
parse(['-1', '1', '11']).indexes.should == [-1, 1, 11]
|
86
|
+
matcher = parse(['-1', '1', '11']).line_matcher
|
87
|
+
matcher.should match_indexes -1, 1, 11
|
88
|
+
matcher.should match_no_indexes -2, 0, 2, 10, 12
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when given ranges (e.g. 1..10, -5..-3)' do
|
93
|
+
it 'has no errors' do
|
94
|
+
parse(['1..10']).errors.should == {}
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'puts the edges in the expected indexes' do
|
98
|
+
parse(['5..8']).indexes.should == [5, 8]
|
99
|
+
parse(['-8..-5']).indexes.should == [-8, -5]
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'sets the buffer size' do
|
103
|
+
parse(['-8..-5']).buffer_size.should == 8
|
104
|
+
parse(['5..-5']).buffer_size.should == 5
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'creates a matcher that matches the range' do
|
108
|
+
matcher = parse(['5..8']).line_matcher
|
109
|
+
matcher.should match_indexes [5, -1], [5, nil], [5, -100000]
|
110
|
+
matcher.should match_indexes [8, -1], [8, nil], [8, -100000]
|
111
|
+
matcher.should match_indexes [6, -1], [6, nil], [6, -100000]
|
112
|
+
matcher.should match_no_indexes [4, -1], [9, nil]
|
113
|
+
|
114
|
+
matcher = parse(['-8..-5']).line_matcher
|
115
|
+
matcher.should match_indexes -8, -7, -6, -5
|
116
|
+
matcher.should match_no_indexes -9, -4
|
117
|
+
|
118
|
+
matcher = parse(['5..-5']).line_matcher
|
119
|
+
matcher.should match_indexes [5, -5], [6, nil], [6, -6]
|
120
|
+
matcher.should match_no_indexes [4, nil], [4, -4], [4, -5], [5, -4]
|
121
|
+
|
122
|
+
matcher = parse(['-5..5']).line_matcher
|
123
|
+
matcher.should match_indexes [5, -5], [5, -4], [4, -5], [5, nil], [4, nil]
|
124
|
+
matcher.should match_no_indexes [5, -6], [6, nil], [6, -5]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'when given negated ranges (e.g. ^1..10, ^-5..-3)' do
|
129
|
+
it 'has no errors' do
|
130
|
+
parse(['^1..10']).errors.should == {}
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'puts the edges in the expected indexes' do
|
134
|
+
parse(['^5..8']).indexes.should == [5, 8]
|
135
|
+
parse(['^-8..-5']).indexes.should == [-8, -5]
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'sets the buffer size' do
|
139
|
+
parse(['^-8..-5']).buffer_size.should == 8
|
140
|
+
parse(['^5..-5']).buffer_size.should == 5
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'creates a matcher that matches the range', t:true do
|
144
|
+
matcher = parse(['^5..8']).line_matcher
|
145
|
+
matcher.should match_no_indexes [5, -1], [5, nil], [5, -100000]
|
146
|
+
matcher.should match_no_indexes [8, -1], [8, nil], [8, -100000]
|
147
|
+
matcher.should match_no_indexes [6, -1], [6, nil], [6, -100000]
|
148
|
+
matcher.should match_indexes [4, -1], [9, nil]
|
149
|
+
|
150
|
+
matcher = parse(['^-8..-5']).line_matcher
|
151
|
+
matcher.should match_no_indexes -8, -7, -6, -5
|
152
|
+
matcher.should match_indexes -9, -4
|
153
|
+
|
154
|
+
matcher = parse(['^5..-5']).line_matcher
|
155
|
+
matcher.should match_no_indexes [5, -5], [6, nil], [6, -6]
|
156
|
+
matcher.should match_indexes [4, nil], [4, -4], [4, -5], [5, -4]
|
157
|
+
|
158
|
+
matcher = parse(['^-5..5']).line_matcher
|
159
|
+
matcher.should match_no_indexes [5, -5], [5, -4], [4, -5], [5, nil], [4, nil]
|
160
|
+
matcher.should match_indexes [5, -6], [6, nil], [6, -5]
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'when given negated numbers (e.g. ^1, ^-1)' do
|
166
|
+
it 'has no errors' do
|
167
|
+
parse(['^1']).errors.should == {}
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'puts them in the expected indexes' do
|
171
|
+
parse(['^1']).indexes.should == [1]
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'sets the buffer size' do
|
175
|
+
parse(['^-3']).buffer_size.should == 3
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'creates a matcher that matches their negation' do
|
179
|
+
parse(['^2', '^4', '^-2']).line_matcher.should match_indexes 1, 3, 5, -1, -3
|
180
|
+
parse(['^2', '^4', '^-2']).line_matcher.should match_no_indexes 2, 4, -2
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'ands them together, and then ors them with everything else' do
|
184
|
+
matcher = parse(['^6', '5..9', '^8', '11']).line_matcher
|
185
|
+
matcher.should match_indexes 5, 7, 9, 11
|
186
|
+
matcher.should match_no_indexes 4, 6, 8, 10, 12
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Line::Options do
|
4
|
+
let(:options) { described_class.new }
|
5
|
+
|
6
|
+
it 'takes a hash of key/value pairs that it sets' do
|
7
|
+
options = described_class.new(instream: 1, outstream: 2)
|
8
|
+
options.instream.should == 1
|
9
|
+
options.outstream.should == 2
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'sets the instream to stdin by default' do
|
13
|
+
options.instream.should == $stdin
|
14
|
+
options.instream = 123
|
15
|
+
options.instream.should == 123
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'sets the outstream to stdout by default' do
|
19
|
+
options.outstream.should == $stdout
|
20
|
+
options.outstream = 123
|
21
|
+
options.outstream.should == 123
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'sets the errstream to stderr by default' do
|
25
|
+
options.errstream.should == $stderr
|
26
|
+
options.errstream = 123
|
27
|
+
options.errstream.should == 123
|
28
|
+
end
|
29
|
+
|
30
|
+
specify 'indexes defaults to an empty collection' do
|
31
|
+
options.indexes.should == []
|
32
|
+
options.indexes.should equal options.indexes
|
33
|
+
end
|
34
|
+
|
35
|
+
specify 'errors defaults to an empty hash' do
|
36
|
+
options.errors.should == {}
|
37
|
+
options.errors.should equal options.errors
|
38
|
+
end
|
39
|
+
|
40
|
+
specify 'the help screen defaults to an empty string' do
|
41
|
+
options.help_screen.should == ''
|
42
|
+
options.help_screen = 'help screen'
|
43
|
+
options.help_screen.should == 'help screen'
|
44
|
+
end
|
45
|
+
|
46
|
+
specify 'buffer_size is 0 by default' do
|
47
|
+
options.buffer_size.should == 0
|
48
|
+
options.buffer_size = 123
|
49
|
+
options.buffer_size.should == 123
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Line::QueueWithIndexes do
|
4
|
+
def generator
|
5
|
+
values = ['a', 'b', 'c']
|
6
|
+
lambda { values.shift || raise(StopIteration) }
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'takes a function that generates values' do
|
10
|
+
described_class.new(&generator).to_a.should == [['a', 0, nil], ['b', 1, nil], ['c', 2, nil]]
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'stops reading from the generator once the input_generator raises StopIteration' do
|
14
|
+
values = %w[a b c]
|
15
|
+
queue = described_class.new do
|
16
|
+
if values.empty?
|
17
|
+
values = %w[a b c]
|
18
|
+
raise StopIteration
|
19
|
+
end
|
20
|
+
values.shift
|
21
|
+
end
|
22
|
+
queue.map(&:first).should == %w[a b c]
|
23
|
+
queue.map(&:first).should == []
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'can be given a size, to determine how much to buffer in order to be able to tell you negative indexes' do
|
27
|
+
described_class.new(2, &generator).to_a.should == [['a', 0, nil], ['b', 1, -2], ['c', 2, -1]]
|
28
|
+
end
|
29
|
+
|
30
|
+
example 'when the size given is greater than the number of values, every value has a negative index' do
|
31
|
+
described_class.new(4, &generator).to_a.should == [['a', 0, -3], ['b', 1, -2], ['c', 2, -1]]
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'is cool with each being called all multiple times and such' do
|
35
|
+
queue = described_class.new(2, &generator)
|
36
|
+
queue.take(1).should == [['a', 0, nil]]
|
37
|
+
queue.take(1).should == [['b', 1, -2]]
|
38
|
+
queue.take(1).should == [['c', 2, -1]]
|
39
|
+
queue.take(1).should == []
|
40
|
+
end
|
41
|
+
|
42
|
+
specify '#each returns the queue' do
|
43
|
+
queue = described_class.new(&generator)
|
44
|
+
queue.each {}.should equal queue
|
45
|
+
end
|
46
|
+
|
47
|
+
specify '#each is lazy' do
|
48
|
+
described_class.new(&generator).each.map { 1 }.should == [1, 1, 1]
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'empty?' do
|
52
|
+
it 'is true when there are no elements in the input' do
|
53
|
+
described_class.new { raise StopIteration }.should be_empty
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'is false when there are elements in the input' do
|
57
|
+
described_class.new(&generator).should_not be_empty
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'becomes true when it runs out of inputs' do
|
61
|
+
queue = described_class.new(2, &generator)
|
62
|
+
queue.take 2
|
63
|
+
queue.should_not be_empty
|
64
|
+
queue.take 1
|
65
|
+
queue.should be_empty
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'line'
|
2
|
+
require 'line/options_parser'
|
3
|
+
require 'surrogate/rspec'
|
4
|
+
|
5
|
+
module LineSpecHelpers
|
6
|
+
def parse(args)
|
7
|
+
Line::OptionParser.call(args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def index_matcher(index)
|
11
|
+
Line::Matchers::Index.new index
|
12
|
+
end
|
13
|
+
|
14
|
+
def index_matchers(*indexes)
|
15
|
+
indexes.map { |index| index_matcher index }
|
16
|
+
end
|
17
|
+
|
18
|
+
def universal_matcher
|
19
|
+
Line::Matchers::MatchEverything.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def match_indexes(*indexes)
|
23
|
+
MatchIndexes.new(true, *indexes)
|
24
|
+
end
|
25
|
+
|
26
|
+
def match_no_indexes(*indexes)
|
27
|
+
MatchIndexes.new(false, *indexes)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias match_index match_indexes
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
class MatchIndexes
|
35
|
+
def initialize(is_positive, *indexes)
|
36
|
+
@positive = is_positive
|
37
|
+
@indexes = indexes
|
38
|
+
@unmatched = []
|
39
|
+
@matched = []
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :indexes, :matched, :unmatched, :matcher, :positive
|
43
|
+
|
44
|
+
alias positive? positive
|
45
|
+
|
46
|
+
def matches?(matcher)
|
47
|
+
@matcher = matcher
|
48
|
+
|
49
|
+
self.indexes.each do |index|
|
50
|
+
index.kind_of?(Array) ? indexes = index :
|
51
|
+
index < 0 ? indexes = [0, index] :
|
52
|
+
indexes = [index, nil]
|
53
|
+
|
54
|
+
if matcher.matches? '', *indexes
|
55
|
+
matched << index
|
56
|
+
else
|
57
|
+
unmatched << index
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
positive? ? unmatched.empty? : matched.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
def failure_message_for_should(*)
|
65
|
+
if positive?
|
66
|
+
"#{matcher.inspect} should have matched #{unmatched.inspect}"
|
67
|
+
else
|
68
|
+
"#{matcher.inspect} shouldn't have matched #{matched.inspect}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
RSpec.configure do |config|
|
74
|
+
config.include LineSpecHelpers
|
75
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'match_indexes (fucking helpers are too complex -.-)' do
|
4
|
+
|
5
|
+
class MatcherInterface
|
6
|
+
Surrogate.endow self
|
7
|
+
define(:matches?) { |line, positive_index, negative_index| true }
|
8
|
+
define(:inspect) { |parent=nil| }
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:spy) { MatcherInterface.new }
|
12
|
+
|
13
|
+
specify 'Matchers implement the MatcherInterface' do
|
14
|
+
Line::Matchers::Index.should substitute_for MatcherInterface, subset: true, names: true
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns true when they match single positive indexes' do
|
18
|
+
spy.will_matches? true
|
19
|
+
match_indexes(1, 2).matches?(spy).should be_true
|
20
|
+
spy.will_matches? false
|
21
|
+
match_indexes(1, 2).matches?(spy).should be_false
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'passes positive indexes in the positive index parameter, and nil as the negative index' do
|
25
|
+
match_indexes(1, 2).matches?(spy).should be_true
|
26
|
+
spy.was asked_if(:matches?).with(anything, 1, nil)
|
27
|
+
spy.was asked_if(:matches?).with(anything, 2, nil)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'passes negative indexes in the negative index parameter' do
|
31
|
+
match_indexes(1, -2).matches?(spy).should be_true
|
32
|
+
spy.was asked_if(:matches?).with('', 1, nil)
|
33
|
+
spy.was asked_if(:matches?).with('', anything, -2)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'passes the first and second element in an array to the pos/neg index parameters' do
|
37
|
+
match_index([100, -100], [200, nil]).matches?(spy)
|
38
|
+
spy.was asked_if(:matches?).with('', 100, -100)
|
39
|
+
spy.was asked_if(:matches?).with('', 200, nil)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: line
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Josh Cheek
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-04-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: surrogate
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Command line tool to filter lines of input based on index.
|
42
|
+
email:
|
43
|
+
- josh.cheek@gmail.com
|
44
|
+
executables:
|
45
|
+
- line
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- Gemfile
|
50
|
+
- Gemfile.lock
|
51
|
+
- README.md
|
52
|
+
- bin/line
|
53
|
+
- lib/line.rb
|
54
|
+
- lib/line/matchers.rb
|
55
|
+
- lib/line/matchers/and.rb
|
56
|
+
- lib/line/matchers/helpers.rb
|
57
|
+
- lib/line/matchers/index.rb
|
58
|
+
- lib/line/matchers/match_everything.rb
|
59
|
+
- lib/line/matchers/match_nothing.rb
|
60
|
+
- lib/line/matchers/not.rb
|
61
|
+
- lib/line/matchers/or.rb
|
62
|
+
- lib/line/matchers/range.rb
|
63
|
+
- lib/line/options.rb
|
64
|
+
- lib/line/options_parser.rb
|
65
|
+
- lib/line/queue_with_indexes.rb
|
66
|
+
- lib/line/version.rb
|
67
|
+
- line.gemspec
|
68
|
+
- spec/integration_spec.rb
|
69
|
+
- spec/line_spec.rb
|
70
|
+
- spec/options_parser_spec.rb
|
71
|
+
- spec/options_spec.rb
|
72
|
+
- spec/queue_with_indexes_spec.rb
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
- spec/spec_helper_spec.rb
|
75
|
+
homepage: https://github.com/JoshCheek/surrogate
|
76
|
+
licenses: []
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project: line
|
94
|
+
rubygems_version: 2.0.0
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: Command line tool to filter lines of input based on index.
|
98
|
+
test_files:
|
99
|
+
- spec/integration_spec.rb
|
100
|
+
- spec/line_spec.rb
|
101
|
+
- spec/options_parser_spec.rb
|
102
|
+
- spec/options_spec.rb
|
103
|
+
- spec/queue_with_indexes_spec.rb
|
104
|
+
- spec/spec_helper.rb
|
105
|
+
- spec/spec_helper_spec.rb
|