rawk 0.1.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.
- data/bin/rawk +11 -0
- data/lib/rawk/rawk.rb +94 -0
- data/spec/rawk/line_spec.rb +37 -0
- data/spec/rawk/rawk_spec.rb +146 -0
- data/spec/spec_helper.rb +15 -0
- metadata +49 -0
data/bin/rawk
ADDED
data/lib/rawk/rawk.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'observer'
|
3
|
+
|
4
|
+
module Rawk
|
5
|
+
class Program
|
6
|
+
attr_reader :fs
|
7
|
+
|
8
|
+
def initialize(io)
|
9
|
+
@start, @every, @finish = Set.new, Set.new, Set.new
|
10
|
+
@input_stream = InputStream.new(io)
|
11
|
+
@input_stream.add_observer(self)
|
12
|
+
initialize_builtins!
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def initialize_builtins!
|
17
|
+
@fs = " "
|
18
|
+
@nr = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
public
|
22
|
+
def on_new_line
|
23
|
+
@nr += 1
|
24
|
+
end
|
25
|
+
alias :update :on_new_line
|
26
|
+
|
27
|
+
def run(code = "", &block)
|
28
|
+
load!(code, &block)
|
29
|
+
execute_code!
|
30
|
+
end
|
31
|
+
|
32
|
+
# DSL
|
33
|
+
def start(&block)
|
34
|
+
@start << block
|
35
|
+
end
|
36
|
+
|
37
|
+
def every(&block)
|
38
|
+
@every << block
|
39
|
+
end
|
40
|
+
|
41
|
+
def finish(&block)
|
42
|
+
@finish << block
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def load!(code, &block)
|
47
|
+
if code.empty?
|
48
|
+
instance_eval(&block)
|
49
|
+
else
|
50
|
+
instance_eval(code)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute_code!
|
55
|
+
@start.each {|b| b.call}
|
56
|
+
@input_stream.each_line do |row|
|
57
|
+
@every.each {|b| b.call(Line.new(row, @fs))}
|
58
|
+
end
|
59
|
+
@finish.each {|b| b.call}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class InputStream
|
64
|
+
include Observable
|
65
|
+
|
66
|
+
def initialize(io)
|
67
|
+
@io = io
|
68
|
+
end
|
69
|
+
|
70
|
+
def each_line
|
71
|
+
@io.each_line do |line|
|
72
|
+
changed
|
73
|
+
notify_observers
|
74
|
+
yield line
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Line < String
|
80
|
+
def initialize(str, fs)
|
81
|
+
self.replace(str.chomp)
|
82
|
+
@fs = fs
|
83
|
+
end
|
84
|
+
|
85
|
+
def cols
|
86
|
+
split(@fs)
|
87
|
+
end
|
88
|
+
alias :c :cols
|
89
|
+
|
90
|
+
def nf
|
91
|
+
cols.length
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Rawk
|
4
|
+
describe Line do
|
5
|
+
before do
|
6
|
+
@fs = " "
|
7
|
+
@data = "a b c\n"
|
8
|
+
@line = Line.new(@data, @fs)
|
9
|
+
end
|
10
|
+
it "is a string" do
|
11
|
+
@line.is_a?(String).should be_true
|
12
|
+
end
|
13
|
+
it "chomps itself on creation" do
|
14
|
+
@line.should == "a b c"
|
15
|
+
end
|
16
|
+
it "finds space-delimited columns by" do
|
17
|
+
@line.cols.should == ["a","b","c"]
|
18
|
+
@line.c.should == @line.cols
|
19
|
+
end
|
20
|
+
it "calculates the number of fields" do
|
21
|
+
@line.nf.should == 3
|
22
|
+
end
|
23
|
+
|
24
|
+
context "with a field separator ','" do
|
25
|
+
it "splits the line into columns using a comma" do
|
26
|
+
line = Line.new("a,b,c d", ",")
|
27
|
+
line.c.should == ["a","b","c d"]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
context "with a regular expression field separator" do
|
31
|
+
it "splits the line into columns using the regular expression" do
|
32
|
+
line = Line.new("a,b|c", /[,|]/)
|
33
|
+
line.c.should == ["a","b","c"]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Rawk
|
5
|
+
describe Program do
|
6
|
+
include TestHelpers
|
7
|
+
|
8
|
+
before do
|
9
|
+
@data = StringIO.new("a b\nc d\ne f\n")
|
10
|
+
@program = Program.new(@data)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when passed code in a string" do
|
14
|
+
it "runs the string as ruby code" do
|
15
|
+
code = "every {puts 'foo'}"
|
16
|
+
out = capture_stdout { @program.run code }.string
|
17
|
+
out.should == "foo\nfoo\nfoo\n"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when passed code in ruby blocks" do
|
22
|
+
it "executes the blocks in order" do
|
23
|
+
start_block, every_block, finish_block = lambda {}, lambda {}, lambda {}
|
24
|
+
start_block.should_receive(:call).once
|
25
|
+
every_block.should_receive(:call).exactly(3).times
|
26
|
+
finish_block.should_receive(:call).once
|
27
|
+
|
28
|
+
@program.run do
|
29
|
+
start &start_block
|
30
|
+
every &every_block
|
31
|
+
finish &finish_block
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "includes the scope of the calling program" do
|
36
|
+
result = []
|
37
|
+
@program.run do
|
38
|
+
every {|l| result << l}
|
39
|
+
end
|
40
|
+
result.should == ["a b","c d","e f"]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "condition execution semantics" do
|
45
|
+
describe "start condition" do
|
46
|
+
it "can have many start conditions" do
|
47
|
+
block1, block2 = lambda {}, lambda {}
|
48
|
+
block1.should_receive(:call).once
|
49
|
+
block2.should_receive(:call).once
|
50
|
+
@program.run do
|
51
|
+
start &block1
|
52
|
+
start &block2
|
53
|
+
end
|
54
|
+
end
|
55
|
+
it "only calls a duplicate start block once" do
|
56
|
+
block = lambda {}
|
57
|
+
block.should_receive(:call).once
|
58
|
+
@program.run do
|
59
|
+
start &block
|
60
|
+
start &block
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "every condition" do
|
66
|
+
it "runs once for each line of input data" do
|
67
|
+
block = lambda {}
|
68
|
+
block.should_receive(:call).once.with("a b").ordered
|
69
|
+
block.should_receive(:call).once.with("c d").ordered
|
70
|
+
block.should_receive(:call).once.with("e f").ordered
|
71
|
+
@program.run {every &block}
|
72
|
+
end
|
73
|
+
|
74
|
+
it "runs multiple every conditions" do
|
75
|
+
block1, block2 = lambda {}, lambda {}
|
76
|
+
block1.should_receive(:call).exactly(3).times
|
77
|
+
block2.should_receive(:call).exactly(3).times
|
78
|
+
@program.run do
|
79
|
+
every &block1
|
80
|
+
every &block2
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "only runs once when specified many times" do
|
85
|
+
block = lambda {}
|
86
|
+
block.should_receive(:call).exactly(3).times
|
87
|
+
@program.run do
|
88
|
+
every &block
|
89
|
+
every &block
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "finish condition" do
|
95
|
+
it "can have many finish conditions" do
|
96
|
+
block1, block2 = lambda {}, lambda {}
|
97
|
+
block1.should_receive(:call).once
|
98
|
+
block2.should_receive(:call).once
|
99
|
+
@program.run do
|
100
|
+
finish &block1
|
101
|
+
finish &block2
|
102
|
+
end
|
103
|
+
end
|
104
|
+
it "only runs once when specified many times" do
|
105
|
+
block = lambda {}
|
106
|
+
block.should_receive(:call).once
|
107
|
+
@program.run do
|
108
|
+
finish &block
|
109
|
+
finish &block
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "support for standard awk built-in variables" do
|
116
|
+
it "calculates the current record num as nr" do
|
117
|
+
record_nums = []
|
118
|
+
@program.run do
|
119
|
+
start {record_nums << @nr}
|
120
|
+
every {record_nums << @nr}
|
121
|
+
finish {record_nums << @nr}
|
122
|
+
end
|
123
|
+
record_nums.should == [0,1,2,3,3]
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "fs" do
|
127
|
+
it "holds the current field separator expression" do
|
128
|
+
@program.fs.should == " "
|
129
|
+
end
|
130
|
+
it "is applied to each line of data" do
|
131
|
+
data = "line"
|
132
|
+
Line.should_receive(:new).with(data, " ")
|
133
|
+
Program.new(data).run {every {|l| nil}}
|
134
|
+
end
|
135
|
+
it "can be changed by the user's program" do
|
136
|
+
data = "line"
|
137
|
+
Line.should_receive(:new).with(data, ",").and_return("dummy")
|
138
|
+
Program.new(data).run do
|
139
|
+
start {@fs = ','}
|
140
|
+
every {|l| nil}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
PROJECT_DIR=File.expand_path(File.dirname(__FILE__), '..')
|
2
|
+
$: << File.join(PROJECT_DIR, 'lib')
|
3
|
+
|
4
|
+
require 'rawk/rawk'
|
5
|
+
|
6
|
+
module Rawk::TestHelpers
|
7
|
+
def capture_stdout
|
8
|
+
out = StringIO.new
|
9
|
+
$stdout = out
|
10
|
+
yield
|
11
|
+
return out
|
12
|
+
ensure
|
13
|
+
$stdout = STDOUT
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rawk
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adrian Mowat
|
9
|
+
autorequire: rawk/rawk
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-14 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email:
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- bin/rawk
|
21
|
+
- lib/rawk/rawk.rb
|
22
|
+
- spec/rawk/line_spec.rb
|
23
|
+
- spec/rawk/rawk_spec.rb
|
24
|
+
- spec/spec_helper.rb
|
25
|
+
homepage:
|
26
|
+
licenses: []
|
27
|
+
post_install_message:
|
28
|
+
rdoc_options: []
|
29
|
+
require_paths:
|
30
|
+
- - lib
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
+
none: false
|
33
|
+
requirements:
|
34
|
+
- - ! '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 1.8.6
|
46
|
+
signing_key:
|
47
|
+
specification_version: 3
|
48
|
+
summary: An awk-inspired ruby DSL
|
49
|
+
test_files: []
|