alf 0.9.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/CHANGELOG.md +5 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +42 -0
- data/LICENCE.md +22 -0
- data/Manifest.txt +15 -0
- data/README.md +769 -0
- data/Rakefile +23 -0
- data/TODO.md +26 -0
- data/alf.gemspec +191 -0
- data/alf.noespec +30 -0
- data/bin/alf +31 -0
- data/examples/autonum.alf +6 -0
- data/examples/cities.rash +4 -0
- data/examples/clip.alf +3 -0
- data/examples/compact.alf +2 -0
- data/examples/database.alf +6 -0
- data/examples/defaults.alf +3 -0
- data/examples/extend.alf +3 -0
- data/examples/group.alf +3 -0
- data/examples/intersect.alf +4 -0
- data/examples/join.alf +2 -0
- data/examples/minus.alf +8 -0
- data/examples/nest.alf +2 -0
- data/examples/nulls.rash +3 -0
- data/examples/parts.rash +6 -0
- data/examples/project.alf +2 -0
- data/examples/quota.alf +4 -0
- data/examples/rename.alf +3 -0
- data/examples/restrict.alf +2 -0
- data/examples/runall.sh +26 -0
- data/examples/schema.yaml +28 -0
- data/examples/sort.alf +4 -0
- data/examples/summarize.alf +16 -0
- data/examples/suppliers.rash +5 -0
- data/examples/supplies.rash +12 -0
- data/examples/ungroup.alf +4 -0
- data/examples/union.alf +3 -0
- data/examples/unnest.alf +4 -0
- data/examples/with.alf +23 -0
- data/lib/alf.rb +2984 -0
- data/lib/alf/loader.rb +1 -0
- data/lib/alf/renderer/text.rb +153 -0
- data/lib/alf/renderer/yaml.rb +22 -0
- data/lib/alf/version.rb +14 -0
- data/spec/aggregator_spec.rb +62 -0
- data/spec/alf_spec.rb +47 -0
- data/spec/assumptions_spec.rb +15 -0
- data/spec/environment/explicit_spec.rb +15 -0
- data/spec/environment/folder_spec.rb +30 -0
- data/spec/examples_spec.rb +26 -0
- data/spec/lispy_spec.rb +23 -0
- data/spec/operator/command_methods_spec.rb +38 -0
- data/spec/operator/non_relational/autonum_spec.rb +61 -0
- data/spec/operator/non_relational/clip_spec.rb +49 -0
- data/spec/operator/non_relational/compact/buffer_based.rb +30 -0
- data/spec/operator/non_relational/compact/sort_based_spec.rb +30 -0
- data/spec/operator/non_relational/compact_spec.rb +38 -0
- data/spec/operator/non_relational/defaults_spec.rb +55 -0
- data/spec/operator/non_relational/sort_spec.rb +66 -0
- data/spec/operator/relational/extend_spec.rb +34 -0
- data/spec/operator/relational/group_spec.rb +54 -0
- data/spec/operator/relational/intersect_spec.rb +58 -0
- data/spec/operator/relational/join/hash_based_spec.rb +63 -0
- data/spec/operator/relational/minus_spec.rb +56 -0
- data/spec/operator/relational/nest_spec.rb +32 -0
- data/spec/operator/relational/project_spec.rb +65 -0
- data/spec/operator/relational/quota_spec.rb +44 -0
- data/spec/operator/relational/rename_spec.rb +32 -0
- data/spec/operator/relational/restrict_spec.rb +56 -0
- data/spec/operator/relational/summarize/sort_based_spec.rb +31 -0
- data/spec/operator/relational/summarize_spec.rb +41 -0
- data/spec/operator/relational/ungroup_spec.rb +35 -0
- data/spec/operator/relational/union_spec.rb +35 -0
- data/spec/operator/relational/unnest_spec.rb +32 -0
- data/spec/reader/alf_file_spec.rb +15 -0
- data/spec/reader/input.rb +2 -0
- data/spec/reader/rash_spec.rb +31 -0
- data/spec/reader_spec.rb +27 -0
- data/spec/renderer/text/cell_spec.rb +34 -0
- data/spec/renderer/text/row_spec.rb +30 -0
- data/spec/renderer/text/table_spec.rb +39 -0
- data/spec/renderer_spec.rb +42 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/tools/ordering_key_spec.rb +81 -0
- data/spec/tools/projection_key_spec.rb +83 -0
- data/spec/tools/tools_spec.rb +25 -0
- data/spec/tools/tuple_handle_spec.rb +78 -0
- data/tasks/debug_mail.rake +78 -0
- data/tasks/debug_mail.txt +13 -0
- data/tasks/gem.rake +68 -0
- data/tasks/spec_test.rake +79 -0
- data/tasks/unit_test.rake +77 -0
- data/tasks/yard.rake +51 -0
- metadata +282 -0
data/lib/alf/loader.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "quickl"
|
@@ -0,0 +1,153 @@
|
|
1
|
+
class Alf::Renderer
|
2
|
+
class Text < Alf::Renderer
|
3
|
+
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
def looks_a_relation?(value)
|
7
|
+
value.is_a?(Alf::Iterator) or
|
8
|
+
(value.is_a?(Array) && !value.empty? && value.all?{|v| v.is_a?(Hash)})
|
9
|
+
end
|
10
|
+
|
11
|
+
def max(x, y)
|
12
|
+
return y if x.nil?
|
13
|
+
return x if y.nil?
|
14
|
+
x > y ? x : y
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
include Utils
|
19
|
+
|
20
|
+
class Cell
|
21
|
+
include Utils
|
22
|
+
|
23
|
+
def initialize(value)
|
24
|
+
@value = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def min_width
|
28
|
+
@min_width ||= rendering_lines.inject(0) do |maxl,line|
|
29
|
+
max(maxl,line.size)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def rendering_lines(size = nil)
|
34
|
+
if size.nil?
|
35
|
+
text_rendering.split(/\n/)
|
36
|
+
elsif @value.is_a?(Numeric)
|
37
|
+
rendering_lines(nil).collect{|l| "%#{size}s" % l}
|
38
|
+
else
|
39
|
+
rendering_lines(nil).collect{|l| "%-#{size}s" % l}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def text_rendering
|
44
|
+
@text_rendering ||= case (value = @value)
|
45
|
+
when NilClass
|
46
|
+
"[nil]"
|
47
|
+
when Symbol
|
48
|
+
value.inspect
|
49
|
+
when Float
|
50
|
+
"%.7f" % value
|
51
|
+
when Hash
|
52
|
+
value.inspect
|
53
|
+
when Alf::Iterator
|
54
|
+
Text.render(value, "")
|
55
|
+
when Array
|
56
|
+
array_rendering(value)
|
57
|
+
else
|
58
|
+
value.to_s
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def array_rendering(value)
|
63
|
+
if looks_a_relation?(value)
|
64
|
+
Text.render(value, "")
|
65
|
+
elsif value.empty?
|
66
|
+
"[]"
|
67
|
+
else
|
68
|
+
values = value.collect{|x| Cell.new(x).text_rendering}
|
69
|
+
if values.inject(0){|memo,s| memo + s.size} < 20
|
70
|
+
"[" + values.join(", ") + "]"
|
71
|
+
else
|
72
|
+
"[" + values.join(",\n ") + "]"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end # class Cell
|
78
|
+
|
79
|
+
class Row
|
80
|
+
include Utils
|
81
|
+
|
82
|
+
def initialize(values)
|
83
|
+
@cells = values.collect{|v| Cell.new(v)}
|
84
|
+
end
|
85
|
+
|
86
|
+
def min_widths
|
87
|
+
@cells.collect{|cell| cell.min_width}
|
88
|
+
end
|
89
|
+
|
90
|
+
def rendering_lines(sizes = min_widths)
|
91
|
+
nb_lines = 0
|
92
|
+
by_cell = @cells.zip(sizes).collect do |cell,size|
|
93
|
+
lines = cell.rendering_lines(size)
|
94
|
+
nb_lines = max(nb_lines, lines.size)
|
95
|
+
lines
|
96
|
+
end
|
97
|
+
grid = (0...nb_lines).collect do |line_i|
|
98
|
+
"| " + by_cell.zip(sizes).collect{|cell_lines, size|
|
99
|
+
cell_lines[line_i] || " "*size
|
100
|
+
}.join(" | ") + " |"
|
101
|
+
end
|
102
|
+
grid.empty? ? ["| |"] : grid
|
103
|
+
end
|
104
|
+
|
105
|
+
end # class Row
|
106
|
+
|
107
|
+
class Table
|
108
|
+
include Utils
|
109
|
+
|
110
|
+
def initialize(records, attributes)
|
111
|
+
@header = Row.new(attributes)
|
112
|
+
@rows = records.collect{|r| Row.new(r)}
|
113
|
+
end
|
114
|
+
|
115
|
+
def render(buffer = "")
|
116
|
+
sizes = @rows.inject(@header.min_widths) do |memo,row|
|
117
|
+
memo.zip(row.min_widths).collect{|x,y| max(x,y)}
|
118
|
+
end
|
119
|
+
sep = '+-' << sizes.collect{|s| '-' * s}.join('-+-') << '-+'
|
120
|
+
buffer << sep << "\n"
|
121
|
+
buffer << @header.rendering_lines(sizes).first << "\n"
|
122
|
+
buffer << sep << "\n"
|
123
|
+
@rows.each do |row|
|
124
|
+
row.rendering_lines(sizes).each do |line|
|
125
|
+
buffer << line << "\n"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
buffer << sep << "\n"
|
129
|
+
buffer
|
130
|
+
end
|
131
|
+
|
132
|
+
end # class Table
|
133
|
+
|
134
|
+
protected
|
135
|
+
|
136
|
+
def render(input, output)
|
137
|
+
relation = input.to_a
|
138
|
+
attrs = relation.inject([]){|memo,t|
|
139
|
+
memo | t.keys
|
140
|
+
}
|
141
|
+
records = relation.collect{|t|
|
142
|
+
attrs.collect{|a| t[a]}
|
143
|
+
}
|
144
|
+
Table.new(records, attrs).render(output)
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.render(input, output)
|
148
|
+
new(input).execute(output)
|
149
|
+
end
|
150
|
+
|
151
|
+
Alf::Renderer.register(:text, "as a text table", self)
|
152
|
+
end # class Text
|
153
|
+
end # class Alf
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "yaml"
|
2
|
+
module Alf
|
3
|
+
module Iterator
|
4
|
+
|
5
|
+
def to_yaml(*args, &block)
|
6
|
+
to_a.to_yaml(*args, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
10
|
+
class Renderer::YAML < Renderer
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
# (see Alf::Renderer#render)
|
15
|
+
def render(input, output)
|
16
|
+
output << input.to_a.to_yaml << "\n"
|
17
|
+
output
|
18
|
+
end
|
19
|
+
|
20
|
+
Renderer.register(:yaml, "as a yaml output", self)
|
21
|
+
end # class YAML
|
22
|
+
end # module Alf
|
data/lib/alf/version.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
describe Aggregator do
|
4
|
+
|
5
|
+
let(:input){[
|
6
|
+
{:a => 1, :sign => -1},
|
7
|
+
{:a => 2, :sign => 1 },
|
8
|
+
{:a => 3, :sign => -1},
|
9
|
+
{:a => 1, :sign => -1},
|
10
|
+
]}
|
11
|
+
|
12
|
+
it "should behave correctly on count" do
|
13
|
+
Aggregator.count(:a).aggregate(input).should == 4
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should behave correctly on sum" do
|
17
|
+
Aggregator.sum(:a).aggregate(input).should == 7
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should behave correctly on avg" do
|
21
|
+
Aggregator.avg(:a).aggregate(input).should == 7.0 / 4.0
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should behave correctly on min" do
|
25
|
+
Aggregator.min(:a).aggregate(input).should == 1
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should behave correctly on max" do
|
29
|
+
Aggregator.max(:a).aggregate(input).should == 3
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should behave correctly on concat" do
|
33
|
+
Aggregator.concat(:a).aggregate(input).should == "1231"
|
34
|
+
Aggregator.concat(:a, :between => " ").aggregate(input).should == "1 2 3 1"
|
35
|
+
Aggregator.concat(:a, :before => "[", :after => "]").aggregate(input).should == "[1231]"
|
36
|
+
Aggregator.concat(:before => "[", :after => "]"){ a }.aggregate(input).should == "[1231]"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should behave correctly on collect" do
|
40
|
+
Aggregator.collect(:a).aggregate(input).should == [1, 2, 3, 1]
|
41
|
+
Aggregator.collect{ {:a => a, :sign => sign} }.aggregate(input).should == input
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should behave correctly on group" do
|
45
|
+
Aggregator.group(:a).aggregate(input).should == [
|
46
|
+
{:a => 1},
|
47
|
+
{:a => 2},
|
48
|
+
{:a => 3},
|
49
|
+
]
|
50
|
+
Aggregator.group(:a, :sign).aggregate(input).should == [
|
51
|
+
{:a => 1, :sign => -1},
|
52
|
+
{:a => 2, :sign => 1 },
|
53
|
+
{:a => 3, :sign => -1},
|
54
|
+
]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should allow specific tuple computations" do
|
58
|
+
Aggregator.sum{ 1.0 * a * sign }.aggregate(input).should == -3.0
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
data/spec/alf_spec.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Alf do
|
3
|
+
|
4
|
+
let(:lispy){ Alf.lispy(Alf::Environment.examples) }
|
5
|
+
|
6
|
+
let(:expected){[
|
7
|
+
{:status => 20, :sid=>"S1", :name=>"Smith", :city=>"London"},
|
8
|
+
{:status => 20, :sid=>"S4", :name=>"Clark", :city=>"London"}
|
9
|
+
]}
|
10
|
+
|
11
|
+
it "should have a version number" do
|
12
|
+
Alf.const_defined?(:VERSION).should be_true
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should allow running a commandline like command" do
|
16
|
+
op = lispy.run(['restrict', 'suppliers', '--', "city == 'London'"])
|
17
|
+
op.to_a.should == expected
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should allow compiling lispy expressions" do
|
21
|
+
lispy.compile{
|
22
|
+
(restrict :suppliers, lambda{ city == 'London'})
|
23
|
+
}.to_a.should == expected
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should allow defining temporary expressions" do
|
27
|
+
lispy.compile{
|
28
|
+
with(:sup => (dataset :suppliers)) do
|
29
|
+
(restrict :sup, lambda{ city == 'London'})
|
30
|
+
end
|
31
|
+
}.to_a.should == expected
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should allow reusing temporary expressions" do
|
35
|
+
op = lispy.compile do
|
36
|
+
(restrict :suppliers, lambda{ status > 20 })
|
37
|
+
end
|
38
|
+
projection = lispy.with(:kept_suppliers => op) do
|
39
|
+
(project :kept_suppliers, [:city])
|
40
|
+
end
|
41
|
+
projection.to_a.should == [
|
42
|
+
{:city => 'Paris'},
|
43
|
+
{:city => 'Athens'}
|
44
|
+
]
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe :instance_eval do
|
3
|
+
|
4
|
+
let(:bar){ lambda{10} }
|
5
|
+
let(:foo) { Object.new }
|
6
|
+
|
7
|
+
if RUBY_VERSION <= "1.9"
|
8
|
+
subject{ foo.instance_eval(&bar) }
|
9
|
+
it { should == 10 }
|
10
|
+
else
|
11
|
+
subject{ foo.instance_exec(&bar) }
|
12
|
+
it { should == 10 }
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
describe Environment::Explicit do
|
4
|
+
|
5
|
+
it "should allow branching easily" do
|
6
|
+
env = Environment::Explicit.new(:hello => "world")
|
7
|
+
env.dataset(:hello).should == "world"
|
8
|
+
env = env.branch(:hello => "world2")
|
9
|
+
env.dataset(:hello).should == "world2"
|
10
|
+
env = env.unbranch
|
11
|
+
env.dataset(:hello).should == "world"
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
describe Environment::Folder do
|
4
|
+
|
5
|
+
let(:path){ File.expand_path('../../../examples', __FILE__) }
|
6
|
+
let(:env){ Environment::Folder.new(path) }
|
7
|
+
|
8
|
+
describe "dataset" do
|
9
|
+
|
10
|
+
subject{ env.dataset(name) }
|
11
|
+
|
12
|
+
describe "when called on explicit file" do
|
13
|
+
let(:name){ "suppliers.rash" }
|
14
|
+
it{ should be_a(Reader::Rash) }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "when called on existing" do
|
18
|
+
let(:name){ "suppliers" }
|
19
|
+
it{ should be_a(Reader::Rash) }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "when called on unexisting" do
|
23
|
+
let(:name){ "notavalidone" }
|
24
|
+
specify{ lambda{ subject }.should raise_error }
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
describe "Alf's examples" do
|
4
|
+
|
5
|
+
shared_examples_for "An example" do
|
6
|
+
|
7
|
+
let(:env){ Environment.examples }
|
8
|
+
|
9
|
+
it "should work when executed with a Alf" do
|
10
|
+
lambda{
|
11
|
+
Alf.lispy(env).compile(File.read(subject)).to_a
|
12
|
+
}.should_not raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
end # An example
|
16
|
+
|
17
|
+
|
18
|
+
Dir["#{File.expand_path('../../examples', __FILE__)}/**/*.alf"].each do |file|
|
19
|
+
describe file do
|
20
|
+
subject{ file }
|
21
|
+
it_should_behave_like "An example"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/spec/lispy_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
describe Lispy do
|
4
|
+
include Lispy
|
5
|
+
|
6
|
+
let(:input){[
|
7
|
+
{:tested => 1, :other => "b"},
|
8
|
+
{:tested => 30, :other => "a"},
|
9
|
+
]}
|
10
|
+
|
11
|
+
let(:expected){[
|
12
|
+
{:tested => 30, :other => "a", :upcase => "A"},
|
13
|
+
]}
|
14
|
+
|
15
|
+
it "should allow chaining operators 'ala' LISP" do
|
16
|
+
operator = (extend \
|
17
|
+
(restrict input, lambda{ tested > 10 }),
|
18
|
+
:upcase => lambda{ other.upcase })
|
19
|
+
operator.to_a.should == expected
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Alf
|
3
|
+
describe Operator::CommandMethods do
|
4
|
+
|
5
|
+
let(:cmd){ Object.new.extend(Operator::CommandMethods) }
|
6
|
+
|
7
|
+
describe "split_command_args" do
|
8
|
+
|
9
|
+
subject{ cmd.send(:split_command_args, args) }
|
10
|
+
|
11
|
+
describe "when applied to both operands and args" do
|
12
|
+
let(:args) { %w{op1 op2 -- a b c} }
|
13
|
+
let(:expected){ [ %w{op1 op2}, %w{a b c} ] }
|
14
|
+
it { should == expected }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "when applied to one operand only" do
|
18
|
+
let(:args) { %w{op1} }
|
19
|
+
let(:expected){ [ %w{op1}, %w{} ] }
|
20
|
+
it { should == expected }
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "when applied to two operands only" do
|
24
|
+
let(:args) { %w{op1 op2} }
|
25
|
+
let(:expected){ [ %w{op1 op2}, %w{} ] }
|
26
|
+
it { should == expected }
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "when applied to args only" do
|
30
|
+
let(:args) { %w{-- a b c} }
|
31
|
+
let(:expected){ [ [$stdin], %w{a b c} ] }
|
32
|
+
it { should == expected }
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|