alf 0.9.3 → 0.10.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 +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
|