alf 0.9.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. data/CHANGELOG.md +255 -129
  2. data/Gemfile +31 -1
  3. data/Gemfile.lock +17 -20
  4. data/LICENCE.md +1 -1
  5. data/Manifest.txt +2 -0
  6. data/README.md +37 -43
  7. data/TODO.md +1 -1
  8. data/alf.gemspec +10 -7
  9. data/alf.noespec +24 -13
  10. data/bin/alf +2 -2
  11. data/doc/commands/exec.md +16 -0
  12. data/doc/commands/help.md +11 -0
  13. data/doc/commands/main.md +33 -0
  14. data/doc/commands/show.md +19 -0
  15. data/doc/operators/non_relational/autonum.md +23 -0
  16. data/doc/operators/non_relational/clip.md +31 -0
  17. data/doc/operators/non_relational/coerce.md +15 -0
  18. data/doc/operators/non_relational/compact.md +20 -0
  19. data/doc/operators/non_relational/defaults.md +32 -0
  20. data/doc/operators/non_relational/generator.md +20 -0
  21. data/doc/operators/non_relational/sort.md +24 -0
  22. data/doc/operators/relational/extend.md +18 -0
  23. data/doc/operators/relational/group.md +27 -0
  24. data/doc/operators/relational/intersect.md +13 -0
  25. data/doc/operators/relational/join.md +27 -0
  26. data/doc/operators/relational/matching.md +20 -0
  27. data/doc/operators/relational/minus.md +12 -0
  28. data/doc/operators/relational/not-matching.md +20 -0
  29. data/doc/operators/relational/project.md +28 -0
  30. data/doc/operators/relational/quota.md +21 -0
  31. data/doc/operators/relational/rank.md +27 -0
  32. data/doc/operators/relational/rename.md +17 -0
  33. data/doc/operators/relational/restrict.md +25 -0
  34. data/doc/operators/relational/summarize.md +25 -0
  35. data/doc/operators/relational/ungroup.md +20 -0
  36. data/doc/operators/relational/union.md +14 -0
  37. data/doc/operators/relational/unwrap.md +20 -0
  38. data/doc/operators/relational/wrap.md +24 -0
  39. data/examples/csv/suppliers.csv +6 -0
  40. data/examples/logs/access.log +1000 -0
  41. data/examples/logs/combined.alf +2 -0
  42. data/examples/logs/hits.alf +14 -0
  43. data/examples/logs/not_found.alf +7 -0
  44. data/examples/logs/robots-cheating.alf +11 -0
  45. data/examples/logs/robots.alf +8 -0
  46. data/examples/northwind/customers.csv +92 -0
  47. data/examples/northwind/northwind.db +0 -0
  48. data/examples/northwind/orders.csv +831 -0
  49. data/examples/operators/clip.alf +1 -1
  50. data/examples/operators/database.alf +5 -6
  51. data/examples/operators/defaults.alf +1 -1
  52. data/examples/operators/group.alf +1 -1
  53. data/examples/operators/project.alf +2 -1
  54. data/examples/operators/pseudo-with.alf +2 -2
  55. data/examples/operators/quota.alf +2 -2
  56. data/examples/operators/summarize.alf +2 -2
  57. data/lib/alf/aggregator/aggregators.rb +77 -0
  58. data/lib/alf/aggregator/base.rb +95 -0
  59. data/lib/alf/aggregator/class_methods.rb +57 -0
  60. data/lib/alf/buffer/sorted.rb +48 -0
  61. data/lib/alf/command/class_methods.rb +27 -0
  62. data/lib/alf/command/doc_manager.rb +72 -0
  63. data/lib/alf/command/exec.rb +12 -0
  64. data/lib/alf/command/help.rb +31 -0
  65. data/lib/alf/command/main.rb +146 -0
  66. data/lib/alf/command/show.rb +33 -0
  67. data/lib/alf/environment/base.rb +37 -0
  68. data/lib/alf/environment/class_methods.rb +93 -0
  69. data/lib/alf/environment/explicit.rb +38 -0
  70. data/lib/alf/environment/folder.rb +62 -0
  71. data/lib/alf/extra/csv.rb +104 -0
  72. data/lib/alf/extra/logs.rb +100 -0
  73. data/lib/alf/extra/sequel.rb +77 -0
  74. data/lib/alf/{yaml.rb → extra/yaml.rb} +0 -0
  75. data/lib/alf/extra.rb +5 -0
  76. data/lib/alf/iterator/base.rb +38 -0
  77. data/lib/alf/iterator/class_methods.rb +22 -0
  78. data/lib/alf/iterator/proxy.rb +33 -0
  79. data/lib/alf/lispy/instance_methods.rb +157 -0
  80. data/lib/alf/operator/base.rb +74 -0
  81. data/lib/alf/operator/binary.rb +32 -0
  82. data/lib/alf/operator/cesure.rb +45 -0
  83. data/lib/alf/operator/class_methods.rb +132 -0
  84. data/lib/alf/operator/experimental.rb +9 -0
  85. data/lib/alf/operator/non_relational/autonum.rb +24 -0
  86. data/lib/alf/operator/non_relational/clip.rb +20 -0
  87. data/lib/alf/operator/non_relational/coerce.rb +21 -0
  88. data/lib/alf/operator/non_relational/compact.rb +62 -0
  89. data/lib/alf/operator/non_relational/defaults.rb +25 -0
  90. data/lib/alf/operator/non_relational/generator.rb +38 -0
  91. data/lib/alf/operator/non_relational/sort.rb +23 -0
  92. data/lib/alf/operator/nullary.rb +20 -0
  93. data/lib/alf/operator/relational/extend.rb +24 -0
  94. data/lib/alf/operator/relational/group.rb +32 -0
  95. data/lib/alf/operator/relational/intersect.rb +37 -0
  96. data/lib/alf/operator/relational/join.rb +106 -0
  97. data/lib/alf/operator/relational/matching.rb +45 -0
  98. data/lib/alf/operator/relational/minus.rb +37 -0
  99. data/lib/alf/operator/relational/not_matching.rb +45 -0
  100. data/lib/alf/operator/relational/project.rb +22 -0
  101. data/lib/alf/operator/relational/quota.rb +51 -0
  102. data/lib/alf/operator/relational/rank.rb +55 -0
  103. data/lib/alf/operator/relational/rename.rb +19 -0
  104. data/lib/alf/operator/relational/restrict.rb +20 -0
  105. data/lib/alf/operator/relational/summarize.rb +83 -0
  106. data/lib/alf/operator/relational/ungroup.rb +25 -0
  107. data/lib/alf/operator/relational/union.rb +32 -0
  108. data/lib/alf/operator/relational/unwrap.rb +21 -0
  109. data/lib/alf/operator/relational/wrap.rb +22 -0
  110. data/lib/alf/operator/shortcut.rb +53 -0
  111. data/lib/alf/operator/signature.rb +262 -0
  112. data/lib/alf/operator/transform.rb +27 -0
  113. data/lib/alf/operator/unary.rb +38 -0
  114. data/lib/alf/reader/alf_file.rb +24 -0
  115. data/lib/alf/reader/base.rb +119 -0
  116. data/lib/alf/reader/class_methods.rb +82 -0
  117. data/lib/alf/reader/rash.rb +28 -0
  118. data/lib/alf/relation/class_methods.rb +37 -0
  119. data/lib/alf/relation/instance_methods.rb +127 -0
  120. data/lib/alf/renderer/base.rb +72 -0
  121. data/lib/alf/renderer/class_methods.rb +58 -0
  122. data/lib/alf/renderer/rash.rb +19 -0
  123. data/lib/alf/{text.rb → renderer/text.rb} +1 -1
  124. data/lib/alf/tools/coerce.rb +14 -0
  125. data/lib/alf/tools/miscellaneous.rb +77 -0
  126. data/lib/alf/tools/to_lispy.rb +99 -0
  127. data/lib/alf/tools/to_ruby_literal.rb +14 -0
  128. data/lib/alf/tools/tuple_handle.rb +50 -0
  129. data/lib/alf/types/attr_list.rb +56 -0
  130. data/lib/alf/types/attr_name.rb +28 -0
  131. data/lib/alf/types/boolean.rb +12 -0
  132. data/lib/alf/types/heading.rb +96 -0
  133. data/lib/alf/types/ordering.rb +93 -0
  134. data/lib/alf/types/renaming.rb +57 -0
  135. data/lib/alf/types/summarization.rb +76 -0
  136. data/lib/alf/types/tuple_computation.rb +61 -0
  137. data/lib/alf/types/tuple_expression.rb +61 -0
  138. data/lib/alf/types/tuple_predicate.rb +49 -0
  139. data/lib/alf/version.rb +2 -2
  140. data/lib/alf.rb +193 -3714
  141. data/spec/integration/__database__/group.alf +1 -1
  142. data/spec/integration/__database__/suppliers_csv.csv +6 -0
  143. data/spec/integration/command/alf/alf.db +0 -0
  144. data/spec/integration/command/alf/alf_env_sqlite.cmd +1 -0
  145. data/spec/integration/command/alf/alf_env_sqlite.stdout +9 -0
  146. data/spec/integration/command/alf/alf_help.cmd +1 -0
  147. data/spec/integration/command/alf/alf_help.stdout +67 -0
  148. data/spec/integration/command/autonum/autonum_0.cmd +1 -1
  149. data/spec/integration/command/coerce/coerce_1.cmd +1 -0
  150. data/spec/integration/command/coerce/coerce_1.stdout +5 -0
  151. data/spec/integration/command/defaults/defaults_0.cmd +1 -1
  152. data/spec/integration/command/defaults/defaults_0.stdout +9 -9
  153. data/spec/integration/command/defaults/defaults_2.cmd +1 -0
  154. data/spec/integration/command/defaults/defaults_2.stdout +9 -0
  155. data/spec/integration/command/generator/generator_1.cmd +1 -0
  156. data/spec/integration/command/generator/generator_1.stdout +10 -0
  157. data/spec/integration/command/generator/generator_2.cmd +1 -0
  158. data/spec/integration/command/generator/generator_2.stdout +5 -0
  159. data/spec/integration/command/generator/generator_3.cmd +1 -0
  160. data/spec/integration/command/generator/generator_3.stdout +5 -0
  161. data/spec/integration/command/group/group_0.cmd +1 -1
  162. data/spec/integration/command/group/group_1.cmd +1 -1
  163. data/spec/integration/command/help/help_1.cmd +1 -0
  164. data/spec/integration/command/help/help_1.stdout +22 -0
  165. data/spec/integration/command/quota/quota_0.cmd +1 -1
  166. data/spec/integration/command/rank/rank_1.cmd +1 -1
  167. data/spec/integration/command/rank/rank_1.stdout +10 -10
  168. data/spec/integration/command/rank/rank_2.cmd +1 -1
  169. data/spec/integration/command/rank/rank_2.stdout +10 -10
  170. data/spec/integration/command/rank/rank_3.cmd +1 -1
  171. data/spec/integration/command/rank/rank_3.stdout +10 -10
  172. data/spec/integration/command/rank/rank_4.cmd +1 -1
  173. data/spec/integration/command/rank/rank_5.cmd +1 -1
  174. data/spec/integration/command/show/show_csv.cmd +1 -0
  175. data/spec/integration/command/show/show_csv.stdout +6 -0
  176. data/spec/integration/command/show/show_rash_2.cmd +1 -1
  177. data/spec/integration/command/show/show_rash_2.stdout +5 -5
  178. data/spec/integration/command/sort/sort_0.cmd +1 -1
  179. data/spec/integration/command/sort/sort_1.cmd +1 -1
  180. data/spec/integration/command/sort/sort_1.stdout +2 -2
  181. data/spec/integration/command/sort/sort_2.cmd +1 -0
  182. data/spec/integration/command/sort/sort_2.stdout +9 -0
  183. data/spec/integration/command/sort/sort_3.cmd +1 -0
  184. data/spec/integration/command/sort/sort_3.stdout +9 -0
  185. data/spec/integration/command/summarize/summarize_0.cmd +1 -1
  186. data/spec/integration/command/ungroup/ungroup_0.cmd +1 -1
  187. data/spec/integration/command/wrap/wrap_0.cmd +1 -1
  188. data/spec/integration/semantics/test_project.alf +5 -6
  189. data/spec/integration/semantics/test_rank.alf +16 -16
  190. data/spec/integration/test_command.rb +17 -6
  191. data/spec/integration/test_examples.rb +1 -1
  192. data/spec/regression/logs/apache_combined.log +5 -0
  193. data/spec/regression/logs/test_path_attribute.rb +25 -0
  194. data/spec/regression/relation/test_relation_allbut_all.rb +14 -0
  195. data/spec/shared/an_operator_class.rb +10 -5
  196. data/spec/spec_helper.rb +1 -7
  197. data/spec/unit/assumptions/test_set.rb +64 -0
  198. data/spec/unit/command/doc_manager/dynamic.md +1 -0
  199. data/spec/unit/command/doc_manager/example.md +1 -0
  200. data/spec/unit/command/doc_manager/example_1.txt +11 -0
  201. data/spec/unit/command/doc_manager/static.md +1 -0
  202. data/spec/unit/command/doc_manager/test_call.rb +49 -0
  203. data/spec/unit/csv/input.csv +3 -0
  204. data/spec/unit/csv/test_reader.rb +66 -0
  205. data/spec/unit/csv/test_renderer.rb +73 -0
  206. data/spec/unit/lispy/test_relation.rb +37 -0
  207. data/spec/unit/lispy/test_run.rb +40 -0
  208. data/spec/unit/lispy/test_tuple.rb +36 -0
  209. data/spec/unit/logs/apache_combined.log +5 -0
  210. data/spec/unit/logs/postgresql.log +29 -0
  211. data/spec/unit/logs/test_reader.rb +56 -0
  212. data/spec/unit/operator/non_relational/compact/{buffer_based.rb → test_buffer_based.rb} +0 -0
  213. data/spec/unit/operator/non_relational/test_clip.rb +1 -1
  214. data/spec/unit/operator/non_relational/test_coerce.rb +35 -0
  215. data/spec/unit/operator/non_relational/test_defaults.rb +15 -2
  216. data/spec/unit/operator/non_relational/test_generator.rb +78 -0
  217. data/spec/unit/operator/relational/join/test_hash_based.rb +4 -4
  218. data/spec/unit/operator/relational/matching/test_hash_based.rb +6 -6
  219. data/spec/unit/operator/relational/not_matching/test_hash_based.rb +4 -4
  220. data/spec/unit/operator/relational/summarize/test_hash_based.rb +10 -6
  221. data/spec/unit/operator/relational/summarize/test_sort_based.rb +18 -7
  222. data/spec/unit/operator/relational/test_group.rb +8 -8
  223. data/spec/unit/operator/relational/test_intersect.rb +3 -3
  224. data/spec/unit/operator/relational/test_minus.rb +3 -3
  225. data/spec/unit/operator/relational/test_project.rb +12 -2
  226. data/spec/unit/operator/relational/test_quota.rb +5 -6
  227. data/spec/unit/operator/relational/test_summarize.rb +9 -11
  228. data/spec/unit/operator/relational/test_union.rb +1 -1
  229. data/spec/unit/operator/relational/test_wrap.rb +1 -1
  230. data/spec/unit/operator/signature/test_collect_on.rb +45 -0
  231. data/spec/unit/operator/signature/test_initialize.rb +17 -0
  232. data/spec/unit/operator/signature/test_install.rb +56 -0
  233. data/spec/unit/operator/signature/test_option_parser.rb +36 -0
  234. data/spec/unit/operator/signature/test_parse_args.rb +60 -0
  235. data/spec/unit/operator/signature/test_parse_argv.rb +87 -0
  236. data/spec/unit/operator/signature/test_to_lispy.rb +102 -0
  237. data/spec/unit/operator/signature/test_to_shell.rb +103 -0
  238. data/spec/unit/operator/test_non_relational.rb +3 -1
  239. data/spec/unit/relation/test_relops.rb +20 -15
  240. data/spec/unit/sequel/alf.db +0 -0
  241. data/spec/unit/sequel/test_environment.rb +54 -0
  242. data/spec/unit/test_aggregator.rb +32 -22
  243. data/spec/unit/test_environment.rb +5 -0
  244. data/spec/unit/test_lispy.rb +4 -0
  245. data/spec/unit/test_relation.rb +5 -0
  246. data/spec/unit/text/test_cell.rb +6 -6
  247. data/spec/unit/text/test_row.rb +3 -3
  248. data/spec/unit/text/test_table.rb +6 -6
  249. data/spec/unit/tools/test_coalesce.rb +15 -0
  250. data/spec/unit/tools/test_coerce.rb +10 -0
  251. data/spec/unit/tools/test_to_lispy.rb +138 -0
  252. data/spec/unit/tools/test_to_ruby_literal.rb +10 -0
  253. data/spec/unit/tools/test_tuple_handle.rb +1 -59
  254. data/spec/unit/types/test_attr_list.rb +106 -0
  255. data/spec/unit/types/test_attr_name.rb +52 -0
  256. data/spec/unit/{test_heading.rb → types/test_heading.rb} +10 -0
  257. data/spec/unit/types/test_ordering.rb +127 -0
  258. data/spec/unit/types/test_renaming.rb +55 -0
  259. data/spec/unit/types/test_summarization.rb +63 -0
  260. data/spec/unit/types/test_tuple_computation.rb +60 -0
  261. data/spec/unit/types/test_tuple_expression.rb +64 -0
  262. data/spec/unit/types/test_tuple_predicate.rb +79 -0
  263. data/tasks/debug_mail.rake +1 -1
  264. data/tasks/debug_mail.txt +5 -0
  265. data/tasks/gh-pages.rake +63 -0
  266. metadata +325 -52
  267. data/spec/unit/operator/test_command_methods.rb +0 -38
  268. data/spec/unit/tools/test_ordering_key.rb +0 -94
  269. data/spec/unit/tools/test_parse_commandline_args.rb +0 -47
  270. 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