alf 0.9.3 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +255 -129
- data/Gemfile +31 -1
- data/Gemfile.lock +17 -20
- data/LICENCE.md +1 -1
- data/Manifest.txt +2 -0
- data/README.md +37 -43
- data/TODO.md +1 -1
- data/alf.gemspec +10 -7
- data/alf.noespec +24 -13
- data/bin/alf +2 -2
- data/doc/commands/exec.md +16 -0
- data/doc/commands/help.md +11 -0
- data/doc/commands/main.md +33 -0
- data/doc/commands/show.md +19 -0
- data/doc/operators/non_relational/autonum.md +23 -0
- data/doc/operators/non_relational/clip.md +31 -0
- data/doc/operators/non_relational/coerce.md +15 -0
- data/doc/operators/non_relational/compact.md +20 -0
- data/doc/operators/non_relational/defaults.md +32 -0
- data/doc/operators/non_relational/generator.md +20 -0
- data/doc/operators/non_relational/sort.md +24 -0
- data/doc/operators/relational/extend.md +18 -0
- data/doc/operators/relational/group.md +27 -0
- data/doc/operators/relational/intersect.md +13 -0
- data/doc/operators/relational/join.md +27 -0
- data/doc/operators/relational/matching.md +20 -0
- data/doc/operators/relational/minus.md +12 -0
- data/doc/operators/relational/not-matching.md +20 -0
- data/doc/operators/relational/project.md +28 -0
- data/doc/operators/relational/quota.md +21 -0
- data/doc/operators/relational/rank.md +27 -0
- data/doc/operators/relational/rename.md +17 -0
- data/doc/operators/relational/restrict.md +25 -0
- data/doc/operators/relational/summarize.md +25 -0
- data/doc/operators/relational/ungroup.md +20 -0
- data/doc/operators/relational/union.md +14 -0
- data/doc/operators/relational/unwrap.md +20 -0
- data/doc/operators/relational/wrap.md +24 -0
- data/examples/csv/suppliers.csv +6 -0
- data/examples/logs/access.log +1000 -0
- data/examples/logs/combined.alf +2 -0
- data/examples/logs/hits.alf +14 -0
- data/examples/logs/not_found.alf +7 -0
- data/examples/logs/robots-cheating.alf +11 -0
- data/examples/logs/robots.alf +8 -0
- data/examples/northwind/customers.csv +92 -0
- data/examples/northwind/northwind.db +0 -0
- data/examples/northwind/orders.csv +831 -0
- data/examples/operators/clip.alf +1 -1
- data/examples/operators/database.alf +5 -6
- data/examples/operators/defaults.alf +1 -1
- data/examples/operators/group.alf +1 -1
- data/examples/operators/project.alf +2 -1
- data/examples/operators/pseudo-with.alf +2 -2
- data/examples/operators/quota.alf +2 -2
- data/examples/operators/summarize.alf +2 -2
- data/lib/alf/aggregator/aggregators.rb +77 -0
- data/lib/alf/aggregator/base.rb +95 -0
- data/lib/alf/aggregator/class_methods.rb +57 -0
- data/lib/alf/buffer/sorted.rb +48 -0
- data/lib/alf/command/class_methods.rb +27 -0
- data/lib/alf/command/doc_manager.rb +72 -0
- data/lib/alf/command/exec.rb +12 -0
- data/lib/alf/command/help.rb +31 -0
- data/lib/alf/command/main.rb +146 -0
- data/lib/alf/command/show.rb +33 -0
- data/lib/alf/environment/base.rb +37 -0
- data/lib/alf/environment/class_methods.rb +93 -0
- data/lib/alf/environment/explicit.rb +38 -0
- data/lib/alf/environment/folder.rb +62 -0
- data/lib/alf/extra/csv.rb +104 -0
- data/lib/alf/extra/logs.rb +100 -0
- data/lib/alf/extra/sequel.rb +77 -0
- data/lib/alf/{yaml.rb → extra/yaml.rb} +0 -0
- data/lib/alf/extra.rb +5 -0
- data/lib/alf/iterator/base.rb +38 -0
- data/lib/alf/iterator/class_methods.rb +22 -0
- data/lib/alf/iterator/proxy.rb +33 -0
- data/lib/alf/lispy/instance_methods.rb +157 -0
- data/lib/alf/operator/base.rb +74 -0
- data/lib/alf/operator/binary.rb +32 -0
- data/lib/alf/operator/cesure.rb +45 -0
- data/lib/alf/operator/class_methods.rb +132 -0
- data/lib/alf/operator/experimental.rb +9 -0
- data/lib/alf/operator/non_relational/autonum.rb +24 -0
- data/lib/alf/operator/non_relational/clip.rb +20 -0
- data/lib/alf/operator/non_relational/coerce.rb +21 -0
- data/lib/alf/operator/non_relational/compact.rb +62 -0
- data/lib/alf/operator/non_relational/defaults.rb +25 -0
- data/lib/alf/operator/non_relational/generator.rb +38 -0
- data/lib/alf/operator/non_relational/sort.rb +23 -0
- data/lib/alf/operator/nullary.rb +20 -0
- data/lib/alf/operator/relational/extend.rb +24 -0
- data/lib/alf/operator/relational/group.rb +32 -0
- data/lib/alf/operator/relational/intersect.rb +37 -0
- data/lib/alf/operator/relational/join.rb +106 -0
- data/lib/alf/operator/relational/matching.rb +45 -0
- data/lib/alf/operator/relational/minus.rb +37 -0
- data/lib/alf/operator/relational/not_matching.rb +45 -0
- data/lib/alf/operator/relational/project.rb +22 -0
- data/lib/alf/operator/relational/quota.rb +51 -0
- data/lib/alf/operator/relational/rank.rb +55 -0
- data/lib/alf/operator/relational/rename.rb +19 -0
- data/lib/alf/operator/relational/restrict.rb +20 -0
- data/lib/alf/operator/relational/summarize.rb +83 -0
- data/lib/alf/operator/relational/ungroup.rb +25 -0
- data/lib/alf/operator/relational/union.rb +32 -0
- data/lib/alf/operator/relational/unwrap.rb +21 -0
- data/lib/alf/operator/relational/wrap.rb +22 -0
- data/lib/alf/operator/shortcut.rb +53 -0
- data/lib/alf/operator/signature.rb +262 -0
- data/lib/alf/operator/transform.rb +27 -0
- data/lib/alf/operator/unary.rb +38 -0
- data/lib/alf/reader/alf_file.rb +24 -0
- data/lib/alf/reader/base.rb +119 -0
- data/lib/alf/reader/class_methods.rb +82 -0
- data/lib/alf/reader/rash.rb +28 -0
- data/lib/alf/relation/class_methods.rb +37 -0
- data/lib/alf/relation/instance_methods.rb +127 -0
- data/lib/alf/renderer/base.rb +72 -0
- data/lib/alf/renderer/class_methods.rb +58 -0
- data/lib/alf/renderer/rash.rb +19 -0
- data/lib/alf/{text.rb → renderer/text.rb} +1 -1
- data/lib/alf/tools/coerce.rb +14 -0
- data/lib/alf/tools/miscellaneous.rb +77 -0
- data/lib/alf/tools/to_lispy.rb +99 -0
- data/lib/alf/tools/to_ruby_literal.rb +14 -0
- data/lib/alf/tools/tuple_handle.rb +50 -0
- data/lib/alf/types/attr_list.rb +56 -0
- data/lib/alf/types/attr_name.rb +28 -0
- data/lib/alf/types/boolean.rb +12 -0
- data/lib/alf/types/heading.rb +96 -0
- data/lib/alf/types/ordering.rb +93 -0
- data/lib/alf/types/renaming.rb +57 -0
- data/lib/alf/types/summarization.rb +76 -0
- data/lib/alf/types/tuple_computation.rb +61 -0
- data/lib/alf/types/tuple_expression.rb +61 -0
- data/lib/alf/types/tuple_predicate.rb +49 -0
- data/lib/alf/version.rb +2 -2
- data/lib/alf.rb +193 -3714
- data/spec/integration/__database__/group.alf +1 -1
- data/spec/integration/__database__/suppliers_csv.csv +6 -0
- data/spec/integration/command/alf/alf.db +0 -0
- data/spec/integration/command/alf/alf_env_sqlite.cmd +1 -0
- data/spec/integration/command/alf/alf_env_sqlite.stdout +9 -0
- data/spec/integration/command/alf/alf_help.cmd +1 -0
- data/spec/integration/command/alf/alf_help.stdout +67 -0
- data/spec/integration/command/autonum/autonum_0.cmd +1 -1
- data/spec/integration/command/coerce/coerce_1.cmd +1 -0
- data/spec/integration/command/coerce/coerce_1.stdout +5 -0
- data/spec/integration/command/defaults/defaults_0.cmd +1 -1
- data/spec/integration/command/defaults/defaults_0.stdout +9 -9
- data/spec/integration/command/defaults/defaults_2.cmd +1 -0
- data/spec/integration/command/defaults/defaults_2.stdout +9 -0
- data/spec/integration/command/generator/generator_1.cmd +1 -0
- data/spec/integration/command/generator/generator_1.stdout +10 -0
- data/spec/integration/command/generator/generator_2.cmd +1 -0
- data/spec/integration/command/generator/generator_2.stdout +5 -0
- data/spec/integration/command/generator/generator_3.cmd +1 -0
- data/spec/integration/command/generator/generator_3.stdout +5 -0
- data/spec/integration/command/group/group_0.cmd +1 -1
- data/spec/integration/command/group/group_1.cmd +1 -1
- data/spec/integration/command/help/help_1.cmd +1 -0
- data/spec/integration/command/help/help_1.stdout +22 -0
- data/spec/integration/command/quota/quota_0.cmd +1 -1
- data/spec/integration/command/rank/rank_1.cmd +1 -1
- data/spec/integration/command/rank/rank_1.stdout +10 -10
- data/spec/integration/command/rank/rank_2.cmd +1 -1
- data/spec/integration/command/rank/rank_2.stdout +10 -10
- data/spec/integration/command/rank/rank_3.cmd +1 -1
- data/spec/integration/command/rank/rank_3.stdout +10 -10
- data/spec/integration/command/rank/rank_4.cmd +1 -1
- data/spec/integration/command/rank/rank_5.cmd +1 -1
- data/spec/integration/command/show/show_csv.cmd +1 -0
- data/spec/integration/command/show/show_csv.stdout +6 -0
- data/spec/integration/command/show/show_rash_2.cmd +1 -1
- data/spec/integration/command/show/show_rash_2.stdout +5 -5
- data/spec/integration/command/sort/sort_0.cmd +1 -1
- data/spec/integration/command/sort/sort_1.cmd +1 -1
- data/spec/integration/command/sort/sort_1.stdout +2 -2
- data/spec/integration/command/sort/sort_2.cmd +1 -0
- data/spec/integration/command/sort/sort_2.stdout +9 -0
- data/spec/integration/command/sort/sort_3.cmd +1 -0
- data/spec/integration/command/sort/sort_3.stdout +9 -0
- data/spec/integration/command/summarize/summarize_0.cmd +1 -1
- data/spec/integration/command/ungroup/ungroup_0.cmd +1 -1
- data/spec/integration/command/wrap/wrap_0.cmd +1 -1
- data/spec/integration/semantics/test_project.alf +5 -6
- data/spec/integration/semantics/test_rank.alf +16 -16
- data/spec/integration/test_command.rb +17 -6
- data/spec/integration/test_examples.rb +1 -1
- data/spec/regression/logs/apache_combined.log +5 -0
- data/spec/regression/logs/test_path_attribute.rb +25 -0
- data/spec/regression/relation/test_relation_allbut_all.rb +14 -0
- data/spec/shared/an_operator_class.rb +10 -5
- data/spec/spec_helper.rb +1 -7
- data/spec/unit/assumptions/test_set.rb +64 -0
- data/spec/unit/command/doc_manager/dynamic.md +1 -0
- data/spec/unit/command/doc_manager/example.md +1 -0
- data/spec/unit/command/doc_manager/example_1.txt +11 -0
- data/spec/unit/command/doc_manager/static.md +1 -0
- data/spec/unit/command/doc_manager/test_call.rb +49 -0
- data/spec/unit/csv/input.csv +3 -0
- data/spec/unit/csv/test_reader.rb +66 -0
- data/spec/unit/csv/test_renderer.rb +73 -0
- data/spec/unit/lispy/test_relation.rb +37 -0
- data/spec/unit/lispy/test_run.rb +40 -0
- data/spec/unit/lispy/test_tuple.rb +36 -0
- data/spec/unit/logs/apache_combined.log +5 -0
- data/spec/unit/logs/postgresql.log +29 -0
- data/spec/unit/logs/test_reader.rb +56 -0
- data/spec/unit/operator/non_relational/compact/{buffer_based.rb → test_buffer_based.rb} +0 -0
- data/spec/unit/operator/non_relational/test_clip.rb +1 -1
- data/spec/unit/operator/non_relational/test_coerce.rb +35 -0
- data/spec/unit/operator/non_relational/test_defaults.rb +15 -2
- data/spec/unit/operator/non_relational/test_generator.rb +78 -0
- data/spec/unit/operator/relational/join/test_hash_based.rb +4 -4
- data/spec/unit/operator/relational/matching/test_hash_based.rb +6 -6
- data/spec/unit/operator/relational/not_matching/test_hash_based.rb +4 -4
- data/spec/unit/operator/relational/summarize/test_hash_based.rb +10 -6
- data/spec/unit/operator/relational/summarize/test_sort_based.rb +18 -7
- data/spec/unit/operator/relational/test_group.rb +8 -8
- data/spec/unit/operator/relational/test_intersect.rb +3 -3
- data/spec/unit/operator/relational/test_minus.rb +3 -3
- data/spec/unit/operator/relational/test_project.rb +12 -2
- data/spec/unit/operator/relational/test_quota.rb +5 -6
- data/spec/unit/operator/relational/test_summarize.rb +9 -11
- data/spec/unit/operator/relational/test_union.rb +1 -1
- data/spec/unit/operator/relational/test_wrap.rb +1 -1
- data/spec/unit/operator/signature/test_collect_on.rb +45 -0
- data/spec/unit/operator/signature/test_initialize.rb +17 -0
- data/spec/unit/operator/signature/test_install.rb +56 -0
- data/spec/unit/operator/signature/test_option_parser.rb +36 -0
- data/spec/unit/operator/signature/test_parse_args.rb +60 -0
- data/spec/unit/operator/signature/test_parse_argv.rb +87 -0
- data/spec/unit/operator/signature/test_to_lispy.rb +102 -0
- data/spec/unit/operator/signature/test_to_shell.rb +103 -0
- data/spec/unit/operator/test_non_relational.rb +3 -1
- data/spec/unit/relation/test_relops.rb +20 -15
- data/spec/unit/sequel/alf.db +0 -0
- data/spec/unit/sequel/test_environment.rb +54 -0
- data/spec/unit/test_aggregator.rb +32 -22
- data/spec/unit/test_environment.rb +5 -0
- data/spec/unit/test_lispy.rb +4 -0
- data/spec/unit/test_relation.rb +5 -0
- data/spec/unit/text/test_cell.rb +6 -6
- data/spec/unit/text/test_row.rb +3 -3
- data/spec/unit/text/test_table.rb +6 -6
- data/spec/unit/tools/test_coalesce.rb +15 -0
- data/spec/unit/tools/test_coerce.rb +10 -0
- data/spec/unit/tools/test_to_lispy.rb +138 -0
- data/spec/unit/tools/test_to_ruby_literal.rb +10 -0
- data/spec/unit/tools/test_tuple_handle.rb +1 -59
- data/spec/unit/types/test_attr_list.rb +106 -0
- data/spec/unit/types/test_attr_name.rb +52 -0
- data/spec/unit/{test_heading.rb → types/test_heading.rb} +10 -0
- data/spec/unit/types/test_ordering.rb +127 -0
- data/spec/unit/types/test_renaming.rb +55 -0
- data/spec/unit/types/test_summarization.rb +63 -0
- data/spec/unit/types/test_tuple_computation.rb +60 -0
- data/spec/unit/types/test_tuple_expression.rb +64 -0
- data/spec/unit/types/test_tuple_predicate.rb +79 -0
- data/tasks/debug_mail.rake +1 -1
- data/tasks/debug_mail.txt +5 -0
- data/tasks/gh-pages.rake +63 -0
- metadata +325 -52
- data/spec/unit/operator/test_command_methods.rb +0 -38
- data/spec/unit/tools/test_ordering_key.rb +0 -94
- data/spec/unit/tools/test_parse_commandline_args.rb +0 -47
- data/spec/unit/tools/test_projection_key.rb +0 -83
@@ -0,0 +1,53 @@
|
|
1
|
+
module Alf
|
2
|
+
module Operator
|
3
|
+
#
|
4
|
+
# Specialization of Operator for operators that are shortcuts for longer
|
5
|
+
# expressions.
|
6
|
+
#
|
7
|
+
module Shortcut
|
8
|
+
include Operator
|
9
|
+
|
10
|
+
#
|
11
|
+
# Sets the operator input
|
12
|
+
#
|
13
|
+
def pipe(input, env = environment)
|
14
|
+
self.environment = env
|
15
|
+
self.datasets = input
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
# (see Operator#_each)
|
22
|
+
def _each
|
23
|
+
longexpr.each(&Proc.new)
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Compiles the longer expression and returns it.
|
28
|
+
#
|
29
|
+
# @return (Iterator) the compiled longer expression, typically another
|
30
|
+
# Operator instance
|
31
|
+
#
|
32
|
+
def longexpr
|
33
|
+
end
|
34
|
+
undef :longexpr
|
35
|
+
|
36
|
+
#
|
37
|
+
# This is an helper ala Lispy#chain for implementing (#longexpr).
|
38
|
+
#
|
39
|
+
# @param [Array] elements a list of Iterator-able
|
40
|
+
# @return [Operator] the first element of the list, but piped with the
|
41
|
+
# next one, and so on.
|
42
|
+
#
|
43
|
+
def chain(*elements)
|
44
|
+
elements = elements.reverse
|
45
|
+
elements[1..-1].inject(elements.first) do |c, elm|
|
46
|
+
elm.pipe(c, environment)
|
47
|
+
elm
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end # module Shortcut
|
52
|
+
end # module Operator
|
53
|
+
end # module Alf
|
@@ -0,0 +1,262 @@
|
|
1
|
+
module Alf
|
2
|
+
module Operator
|
3
|
+
# Provides an operator signature
|
4
|
+
class Signature
|
5
|
+
|
6
|
+
# @return [Class] the operator class to which this signature belongs
|
7
|
+
attr_reader :operator
|
8
|
+
|
9
|
+
# @return [Array] signature arguments
|
10
|
+
attr_reader :arguments
|
11
|
+
|
12
|
+
# @return [Array] signature options
|
13
|
+
attr_reader :options
|
14
|
+
|
15
|
+
#
|
16
|
+
# Creates a signature instance
|
17
|
+
#
|
18
|
+
def initialize(operator)
|
19
|
+
@operator = operator
|
20
|
+
@arguments = []
|
21
|
+
@options = []
|
22
|
+
yield(self) if block_given?
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Adds an argument to the signature
|
27
|
+
#
|
28
|
+
# @param [Symbol] name argument name
|
29
|
+
# @param [Class] domain argument domain
|
30
|
+
# @param [Object] default (optional) default value
|
31
|
+
#
|
32
|
+
def argument(name, domain, default = nil, descr = nil)
|
33
|
+
arguments << [name, domain, default, descr]
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Adds an option to the signature
|
38
|
+
#
|
39
|
+
# @param [Symbol] name argument name
|
40
|
+
# @param [Class] domain argument domain
|
41
|
+
# @param [Object] default (optional) default value
|
42
|
+
#
|
43
|
+
def option(name, domain, default = nil, descr = nil)
|
44
|
+
options << [name, domain, default, descr]
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Returns default options as a Hash
|
49
|
+
#
|
50
|
+
# @return [Hash] the default options
|
51
|
+
#
|
52
|
+
def default_options
|
53
|
+
@default_options ||=
|
54
|
+
Hash[options.select{|opt| !opt[2].nil? }.
|
55
|
+
collect{|opt| [opt[0], opt[2]]}]
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Fills an OptionParser instance according to signature options.
|
60
|
+
#
|
61
|
+
# @param [OptionParser] opt an parser instance, to fill with parse options
|
62
|
+
# @return [OptionParser] `opt`
|
63
|
+
#
|
64
|
+
def fill_option_parser(opt, receiver)
|
65
|
+
options.each do |option|
|
66
|
+
name, dom, defa, descr = option
|
67
|
+
opt.on(option_name(option), descr || "") do |val|
|
68
|
+
receiver.send(:"#{name}=", val)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
opt
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Returns an option parser instance bound to a given `receiver` object
|
76
|
+
#
|
77
|
+
# @return [OptionParser] an parser instance, ready to parse options and
|
78
|
+
# install them on `receiver`
|
79
|
+
#
|
80
|
+
def option_parser(receiver)
|
81
|
+
fill_option_parser(OptionParser.new, receiver)
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Installs the signature on the operator class
|
86
|
+
#
|
87
|
+
# This method installs an attr reader and an attr writer for each
|
88
|
+
# signature argument and each signature option.
|
89
|
+
#
|
90
|
+
# @return [Hash] the default options to use
|
91
|
+
#
|
92
|
+
def install
|
93
|
+
clazz = operator
|
94
|
+
code = (arguments + options).each{|siginfo|
|
95
|
+
name, domain, = siginfo
|
96
|
+
clazz.send(:attr_reader, name)
|
97
|
+
clazz.send(:define_method, :"#{name}=") do |val|
|
98
|
+
instance_variable_set(:"@#{name}", Tools.coerce(val, domain))
|
99
|
+
end
|
100
|
+
clazz.send(:private, :"#{name}=")
|
101
|
+
}
|
102
|
+
default_options
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Parses arguments `args` passed to the operator `initialize` and
|
107
|
+
# sets attributes accordingly on `receiver`.
|
108
|
+
#
|
109
|
+
# @param [Array] args an array of initialize arguments
|
110
|
+
# @param [Operator] receiver an operator instance
|
111
|
+
#
|
112
|
+
def parse_args(args, receiver)
|
113
|
+
invalid_args!(args) if args.size > (1+arguments.size)
|
114
|
+
|
115
|
+
# Merge default and provided options
|
116
|
+
optargs = default_options
|
117
|
+
if args.size == (1+arguments.size)
|
118
|
+
invalid_args!(args) unless args.last.is_a?(Hash)
|
119
|
+
optargs = optargs.merge(args.pop)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Set options
|
123
|
+
optargs.each_pair do |name,val|
|
124
|
+
receiver.send(:"#{name}=", val)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Parse other arguments now
|
128
|
+
parse_xxx(args, :coerce) do |name,val|
|
129
|
+
receiver.send(:"#{name}=", val)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Parses arguments `args` used to create an operator initialize from
|
135
|
+
# commandline arguments and sets attributes accordingly on `receiver`.
|
136
|
+
#
|
137
|
+
# @param [Array] argv an array of commandline arguments
|
138
|
+
# @param [Operator] receiver an operator instance
|
139
|
+
#
|
140
|
+
def parse_argv(argv, receiver)
|
141
|
+
# First split over --
|
142
|
+
argv = Quickl.split_commandline_args(argv)
|
143
|
+
|
144
|
+
# Set default options then parse those in first group
|
145
|
+
default_options.each_pair do |name,val|
|
146
|
+
receiver.send(:"#{name}=", val)
|
147
|
+
end
|
148
|
+
argv[0] = option_parser(receiver).parse!(argv[0])
|
149
|
+
|
150
|
+
# Remove operands
|
151
|
+
operands = argv.shift
|
152
|
+
|
153
|
+
# Parse the rest
|
154
|
+
parse_xxx(argv, :from_argv) do |name,val|
|
155
|
+
receiver.send(:"#{name}=", val)
|
156
|
+
end
|
157
|
+
|
158
|
+
operands
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Collects signature values on a given operator.
|
163
|
+
#
|
164
|
+
# This methods returns a triple `[datasets, arguments, options]` with
|
165
|
+
# the respective values collected on `op`.
|
166
|
+
#
|
167
|
+
# @param [Operator] op an operator, which should be an instance of
|
168
|
+
# `self.operator`
|
169
|
+
# @return [Array] a triple [datasets, arguments, options] with operands,
|
170
|
+
# then signature values
|
171
|
+
#
|
172
|
+
def collect_on(op)
|
173
|
+
oper = op.datasets
|
174
|
+
oper = [oper] unless oper.is_a?(Array)
|
175
|
+
args = arguments.collect{|name,_| op.send(name) }
|
176
|
+
opts = Hash[options.collect{|name,dom,defa,_|
|
177
|
+
val = op.send(name)
|
178
|
+
(val == defa) ? nil : [name, val]
|
179
|
+
}.compact]
|
180
|
+
[oper, args, opts]
|
181
|
+
end
|
182
|
+
|
183
|
+
#
|
184
|
+
# Returns a lispy synopsis for this signature
|
185
|
+
#
|
186
|
+
# Example:
|
187
|
+
#
|
188
|
+
# Alf::Operator::Relational::Project.signature.to_shell
|
189
|
+
# # => "(project operand, attributes:AttrList, {allbut: Boolean})"
|
190
|
+
#
|
191
|
+
def to_lispy
|
192
|
+
cmd = operator.command_name.to_s.gsub('-', '_')
|
193
|
+
oper = operator.nullary? ? "" :
|
194
|
+
(operator.unary? ? "operand" : "left, right")
|
195
|
+
|
196
|
+
args = arguments.collect{|name,dom,_|
|
197
|
+
dom.to_s =~ /::([A-Za-z]+)$/
|
198
|
+
"#{name}:#{$1}"
|
199
|
+
}.join(", ")
|
200
|
+
args = (args.empty? ? "#{oper}" : "#{oper}, #{args}").strip
|
201
|
+
|
202
|
+
opts = options.collect{|name,dom,_|
|
203
|
+
dom.to_s =~ /::([A-Za-z]+)$/
|
204
|
+
"#{name}: #{$1}"
|
205
|
+
}.join(', ')
|
206
|
+
opts = opts.empty? ? "" : "{#{opts}}"
|
207
|
+
|
208
|
+
argopt = [args, opts].select{|s| !s.empty?}.join(', ')
|
209
|
+
"(#{cmd} #{argopt}".strip + ")"
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
# Returns a shell synopsis for this signature.
|
214
|
+
#
|
215
|
+
# Example:
|
216
|
+
#
|
217
|
+
# Alf::Operator::Relational::Project.signature.to_shell
|
218
|
+
# # => "alf project [--allbut] [OPERAND] -- ATTRIBUTES"
|
219
|
+
#
|
220
|
+
def to_shell
|
221
|
+
oper = operator.nullary? ? "" :
|
222
|
+
(operator.unary? ? "[OPERAND]" : "[LEFT] RIGHT")
|
223
|
+
opts = options.collect{|opt| "[#{option_name(opt)}]" }.join(" ")
|
224
|
+
args = arguments.collect{|arg,_| "#{arg.to_s.upcase}" }.join(" -- ")
|
225
|
+
optargs = "#{opts} #{oper} " + (args.empty? ? "" : "-- #{args}")
|
226
|
+
"alf #{operator.command_name} #{optargs.strip}".strip
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
def option_name(option)
|
232
|
+
name, domain, defa, = option
|
233
|
+
domain == Boolean ? "--#{name}" : "--#{name}=#{name.to_s.upcase}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def parse_xxx(args, coercer)
|
237
|
+
arguments.zip(args).collect do |sigpart,subargs|
|
238
|
+
name, dom, default = sigpart
|
239
|
+
|
240
|
+
# coercion
|
241
|
+
val = if Array(subargs).empty?
|
242
|
+
Tools.coerce(default, dom)
|
243
|
+
else
|
244
|
+
dom.send(coercer, subargs)
|
245
|
+
end
|
246
|
+
|
247
|
+
# check and yield
|
248
|
+
if val.nil?
|
249
|
+
raise ArgumentError, "Invalid `#{subargs.inspect}` for #{sigpart.inspect}"
|
250
|
+
else
|
251
|
+
yield(name, val)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def invalid_args!(args)
|
257
|
+
raise ArgumentError, "Invalid `#{args.inspect}` for #{self}"
|
258
|
+
end
|
259
|
+
|
260
|
+
end # class Signature
|
261
|
+
end # module Operator
|
262
|
+
end # module Alf
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Alf
|
2
|
+
module Operator
|
3
|
+
#
|
4
|
+
# Specialization of Operator for operators that simply convert single tuples
|
5
|
+
# to single tuples.
|
6
|
+
#
|
7
|
+
module Transform
|
8
|
+
include Unary
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
# (see Operator#_each)
|
13
|
+
def _each
|
14
|
+
each_input_tuple do |tuple|
|
15
|
+
yield _tuple2tuple(tuple)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Transforms an input tuple to an output tuple
|
21
|
+
#
|
22
|
+
def _tuple2tuple(tuple)
|
23
|
+
end
|
24
|
+
|
25
|
+
end # module Transform
|
26
|
+
end # module Operator
|
27
|
+
end # module Alf
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Alf
|
2
|
+
module Operator
|
3
|
+
#
|
4
|
+
# Specialization of Operator for operators that work on a unary input
|
5
|
+
#
|
6
|
+
module Unary
|
7
|
+
include Operator
|
8
|
+
|
9
|
+
#
|
10
|
+
# Sets the operator input
|
11
|
+
#
|
12
|
+
def pipe(input, env = environment)
|
13
|
+
self.environment = env
|
14
|
+
self.datasets = [ input ]
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
#
|
21
|
+
# Simply returns the first dataset
|
22
|
+
#
|
23
|
+
def input
|
24
|
+
Iterator.coerce(datasets.first, environment)
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Yields the block with each input tuple.
|
29
|
+
#
|
30
|
+
# This method should be preferred to <code>input.each</code> when possible.
|
31
|
+
#
|
32
|
+
def each_input_tuple
|
33
|
+
input.each(&Proc.new)
|
34
|
+
end
|
35
|
+
|
36
|
+
end # module Unary
|
37
|
+
end # module Operator
|
38
|
+
end # module Alf
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Alf
|
2
|
+
class Reader
|
3
|
+
#
|
4
|
+
# Specialization of the Reader contrat for .alf files.
|
5
|
+
#
|
6
|
+
# A .alf file simply contains a query expression in the Lispy DSL. This
|
7
|
+
# reader decodes and compiles the expression and delegates the enumeration
|
8
|
+
# to the obtained operator.
|
9
|
+
#
|
10
|
+
# Note that an Environment must be wired at creation or piping time.
|
11
|
+
# NoSuchDatasetError will certainly occur otherwise.
|
12
|
+
#
|
13
|
+
class AlfFile < Reader
|
14
|
+
|
15
|
+
# (see Reader#each)
|
16
|
+
def each
|
17
|
+
op = Alf.lispy(environment).compile(input_text, input_path)
|
18
|
+
op.each(&Proc.new)
|
19
|
+
end
|
20
|
+
|
21
|
+
Reader.register(:alf, [".alf"], self)
|
22
|
+
end # module AlfFile
|
23
|
+
end # class Reader
|
24
|
+
end # module Alf
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Alf
|
2
|
+
class Reader
|
3
|
+
module Base
|
4
|
+
|
5
|
+
# Default reader options
|
6
|
+
DEFAULT_OPTIONS = {}
|
7
|
+
|
8
|
+
# @return [Environment] Wired environment
|
9
|
+
attr_accessor :environment
|
10
|
+
|
11
|
+
# @return [String or IO] Input IO, or file name
|
12
|
+
attr_accessor :input
|
13
|
+
|
14
|
+
# @return [Hash] Reader's options
|
15
|
+
attr_accessor :options
|
16
|
+
|
17
|
+
#
|
18
|
+
# Creates a reader instance.
|
19
|
+
#
|
20
|
+
# @param [String or IO] path to a file or IO object for input
|
21
|
+
# @param [Environment] environment wired environment, serving this reader
|
22
|
+
# @param [Hash] options Reader's options (see doc of subclasses)
|
23
|
+
#
|
24
|
+
def initialize(*args)
|
25
|
+
@input, @environment, @options = case args.first
|
26
|
+
when String, IO, StringIO
|
27
|
+
Tools.varargs(args, [args.first.class, Environment, Hash])
|
28
|
+
else
|
29
|
+
Tools.varargs(args, [String, Environment, Hash])
|
30
|
+
end
|
31
|
+
@options = self.class.const_get(:DEFAULT_OPTIONS).merge(@options || {})
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# (see Iterator#pipe)
|
36
|
+
#
|
37
|
+
def pipe(input, env = environment)
|
38
|
+
@input = input
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# (see Iterator#each)
|
44
|
+
#
|
45
|
+
# @private the default implementation reads lines of the input stream and
|
46
|
+
# yields the block with <code>line2tuple(line)</code> on each of them. This
|
47
|
+
# method may be overriden if this behavior does not fit reader's needs.
|
48
|
+
#
|
49
|
+
def each
|
50
|
+
each_input_line do |line|
|
51
|
+
tuple = line2tuple(line)
|
52
|
+
yield tuple unless tuple.nil?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
#
|
59
|
+
# Returns the input file path, or nil if this Reader is bound to an IO
|
60
|
+
# directly.
|
61
|
+
#
|
62
|
+
def input_path
|
63
|
+
input.is_a?(String) ? input : nil
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Coerces the input object to an IO and yields the block with it.
|
68
|
+
#
|
69
|
+
# StringIO and IO input are yield directly while file paths are first
|
70
|
+
# opened in read mode and then yield.
|
71
|
+
#
|
72
|
+
def with_input_io
|
73
|
+
case input
|
74
|
+
when IO, StringIO
|
75
|
+
yield input
|
76
|
+
when String
|
77
|
+
File.open(input, 'r'){|io| yield io}
|
78
|
+
else
|
79
|
+
raise "Unable to convert #{input} to an IO object"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Returns the whole input text.
|
85
|
+
#
|
86
|
+
# This feature should only be used by subclasses on inputs that are
|
87
|
+
# small enough to fit in memory. Consider implementing readers without this
|
88
|
+
# feature on files that could be larger.
|
89
|
+
#
|
90
|
+
def input_text
|
91
|
+
with_input_io{|io| io.readlines.join}
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Yields the block with each line of the input text in turn.
|
96
|
+
#
|
97
|
+
# This method is an helper for files that capture one tuple on each input
|
98
|
+
# line. It should be used in those cases, as the resulting reader will not
|
99
|
+
# load all input in memory but serve tuples on demand.
|
100
|
+
#
|
101
|
+
def each_input_line
|
102
|
+
with_input_io{|io| io.each_line(&Proc.new)}
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Converts a line previously read from the input stream to a tuple.
|
107
|
+
#
|
108
|
+
# The line is simply ignored is this method return nil. Errors should be
|
109
|
+
# properly handled by raising exceptions. This method MUST be implemented
|
110
|
+
# by subclasses unless each is overriden.
|
111
|
+
#
|
112
|
+
def line2tuple(line)
|
113
|
+
end
|
114
|
+
undef :line2tuple
|
115
|
+
|
116
|
+
end # module Base
|
117
|
+
include Base
|
118
|
+
end # class Reader
|
119
|
+
end # module Alf
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Alf
|
2
|
+
class Reader
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
#
|
6
|
+
# Returns registered readers
|
7
|
+
#
|
8
|
+
def readers
|
9
|
+
@readers ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Registers a reader class associated with specific file extensions
|
14
|
+
#
|
15
|
+
# Registered class must provide a constructor with the following signature
|
16
|
+
# <code>new(path_or_io, environment = nil)</code>. The name must be a symbol
|
17
|
+
# which can safely be used as a ruby method name. A factory class method of
|
18
|
+
# that name and same signature is automatically installed on the Reader
|
19
|
+
# class.
|
20
|
+
#
|
21
|
+
# @param [Symbol] name a name for the kind of data decoded
|
22
|
+
# @param [Array] extensions file extensions mapped to the registered reader
|
23
|
+
# class (should include the '.', e.g. '.foo')
|
24
|
+
# @param [Class] class Reader subclass used to decode this kind of files
|
25
|
+
#
|
26
|
+
def register(name, extensions, clazz)
|
27
|
+
readers << [name, extensions, clazz]
|
28
|
+
(class << self; self; end).
|
29
|
+
send(:define_method, name) do |*args|
|
30
|
+
clazz.new(*args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# When filepath is a String, returns a reader instance for a specific file
|
36
|
+
# whose path is given as argument. Otherwise, delegate the call to
|
37
|
+
# <code>coerce(filepath)</code>
|
38
|
+
#
|
39
|
+
# @param [String] filepath path to a file for which extension is recognized
|
40
|
+
# @param [Array] args optional additional arguments that must be passed at
|
41
|
+
# reader's class new method.
|
42
|
+
# @return [Reader] a reader instance
|
43
|
+
#
|
44
|
+
def reader(filepath, *args)
|
45
|
+
if filepath.is_a?(String)
|
46
|
+
ext = File.extname(filepath)
|
47
|
+
if registered = readers.find{|r| r[1].include?(ext)}
|
48
|
+
registered[2].new(filepath, *args)
|
49
|
+
else
|
50
|
+
raise "No registered reader for #{ext} (#{filepath})"
|
51
|
+
end
|
52
|
+
elsif args.empty?
|
53
|
+
coerce(filepath)
|
54
|
+
else
|
55
|
+
raise ArgumentError, "Unable to return a reader for #{filepath} and #{args}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Coerces an argument to a reader, using an optional environment to convert
|
61
|
+
# named datasets.
|
62
|
+
#
|
63
|
+
# This method automatically provides readers for Strings and Symbols through
|
64
|
+
# passed environment (**not** through the reader factory) and for IO objects
|
65
|
+
# (through Rash reader). It is part if Alf's internals and should be used
|
66
|
+
# with care.
|
67
|
+
#
|
68
|
+
def coerce(arg, environment = nil)
|
69
|
+
case arg
|
70
|
+
when Reader
|
71
|
+
arg
|
72
|
+
when IO
|
73
|
+
rash(arg, environment)
|
74
|
+
else
|
75
|
+
raise ArgumentError, "Unable to coerce #{arg.inspect} to a reader"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end # module ClassMethods
|
80
|
+
extend(ClassMethods)
|
81
|
+
end # class Reader
|
82
|
+
end # module Alf
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Alf
|
2
|
+
class Reader
|
3
|
+
#
|
4
|
+
# Specialization of the Reader contract for .rash files.
|
5
|
+
#
|
6
|
+
# A .rash file/stream contains one ruby hash literal on each line. This
|
7
|
+
# reader simply decodes each of them in turn with Kernel.eval, providing a
|
8
|
+
# state-less reader (that is, tuples are not all loaded in memory at once).
|
9
|
+
#
|
10
|
+
class Rash < Reader
|
11
|
+
|
12
|
+
# (see Reader#line2tuple)
|
13
|
+
def line2tuple(line)
|
14
|
+
begin
|
15
|
+
h = Kernel.eval(line)
|
16
|
+
raise "hash expected, got #{h}" unless h.is_a?(Hash)
|
17
|
+
rescue Exception => ex
|
18
|
+
$stderr << "Skipping #{line.strip}: #{ex.message}\n"
|
19
|
+
nil
|
20
|
+
else
|
21
|
+
return h
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Reader.register(:rash, [".rash"], self)
|
26
|
+
end # class Rash
|
27
|
+
end # class Reader
|
28
|
+
end # module Alf
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Alf
|
2
|
+
class Relation
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
#
|
6
|
+
# Coerces `val` to a relation.
|
7
|
+
#
|
8
|
+
# Recognized arguments are: Relation (identity coercion), Set of ruby hashes,
|
9
|
+
# Array of ruby hashes, Alf::Iterator.
|
10
|
+
#
|
11
|
+
# @return [Relation] a relation instance for the given set of tuples
|
12
|
+
# @raise [ArgumentError] when `val` is not recognized
|
13
|
+
#
|
14
|
+
def coerce(val)
|
15
|
+
case val
|
16
|
+
when Relation
|
17
|
+
val
|
18
|
+
when Set
|
19
|
+
Relation.new(val)
|
20
|
+
when Array
|
21
|
+
Relation.new val.to_set
|
22
|
+
when Iterator
|
23
|
+
Relation.new val.to_set
|
24
|
+
else
|
25
|
+
raise ArgumentError, "Unable to coerce #{val} to a Relation"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# (see Relation.coerce)
|
30
|
+
def [](*tuples)
|
31
|
+
coerce(tuples)
|
32
|
+
end
|
33
|
+
|
34
|
+
end # module ClassMethods
|
35
|
+
extend(ClassMethods)
|
36
|
+
end # class Relation
|
37
|
+
end # module Alf
|