line 1.0.0
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.
- 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
|