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 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
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
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,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path '../../lib', __FILE__
4
+ require 'line'
5
+ require 'line/options_parser'
6
+
7
+ exit Line.call Line::OptionParser.call ARGV
@@ -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,17 @@
1
+ require 'line/matchers/helpers'
2
+
3
+ class Line
4
+ module Matchers
5
+ class MatchNothing
6
+ include Helpers
7
+
8
+ def matches?(*)
9
+ false
10
+ end
11
+
12
+ def inspect(parent=false)
13
+ inspect_helper parent, "MatchNothing", 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
@@ -0,0 +1,7 @@
1
+ require 'line/matchers/or'
2
+ require 'line/matchers/not'
3
+ require 'line/matchers/and'
4
+ require 'line/matchers/index'
5
+ require 'line/matchers/range'
6
+ require 'line/matchers/match_nothing'
7
+ require 'line/matchers/match_everything'
@@ -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
@@ -0,0 +1,3 @@
1
+ class Line
2
+ VERSION = '1.0.0'
3
+ end
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
@@ -0,0 +1,6 @@
1
+ describe 'integration' do
2
+ it 'is actually all hooked together correctly' do
3
+ bin = File.expand_path '../../bin/line', __FILE__
4
+ `echo 1 | #{bin} 1`.should == "1\n"
5
+ end
6
+ 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
@@ -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