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.
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