csdl 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5f8bac9da29e72abe7b9ed3d735c9e43dd0299f8
4
- data.tar.gz: fbddc9f5fe3edd6c023acfad56e2dac01e3baff6
3
+ metadata.gz: a90262fd16ee8936dd0fe2de172ca90a443603f6
4
+ data.tar.gz: 89cb93fa4bdcb617dbd066edc004bde34f33c1af
5
5
  SHA512:
6
- metadata.gz: bbe665e31c04cacc5922e9f66a6e09835b63516cf900b785df53ade0939aa78e170f29b37723ebe88b3fca8d905467a438a26eff9ab604dc6d899376928c711e
7
- data.tar.gz: cdff5b7766ac63008c2c0421f84294a9046bc6d4657149f15675e2768c94fdf24014c8b4daffab0f98f246602979bb7f01bba0c19cf9feff7656e6f5b70326b1
6
+ metadata.gz: 485e7164255da714fb1d5bc59e4a9d01b7568e6a58acb5c6b865d85bcef56063cc4c904482482e45c9bdbcb82fab90f097a99fbc2778f2b2b8b46c49c041f494
7
+ data.tar.gz: 55d987844a46743cc6013402225279458aa33419ea38b38e5226ea1ea665debff6124f8671030d76df24236f4d506e8f16fef13129c63ecc5470159a48b7530b
data/README.md CHANGED
@@ -3,7 +3,10 @@
3
3
  CSDL is a gem for producing Abstract Syntax Trees for the [DataSift CSDL Filter Language](http://dev.datasift.com/docs/csdl).
4
4
  Working with an AST instead of raw strings provides a simpler way to test and validate any given CSDL filter.
5
5
 
6
+ [![Gem Version](https://badge.fury.io/rb/csdl.svg)](http://badge.fury.io/rb/csdl)
6
7
  [![Build Status](https://travis-ci.org/localshred/csdl.svg)](https://travis-ci.org/localshred/csdl)
8
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/csdl)
9
+ [![Inline docs](http://inch-ci.org/github/localshred/csdl.svg?branch=master)](http://inch-ci.org/github/localshred/csdl)
7
10
 
8
11
  ## Installation
9
12
 
@@ -25,41 +28,47 @@ Or install it yourself as:
25
28
 
26
29
  Use the DSL provided by `CSDL::Builder` to produce an AST representation of your query, and use `CSDL::Processor` to turn your AST into a raw CSDL string.
27
30
 
28
- Valid builder methods are `closure`, `filter`, `_and`, `_or`, and `_not`. The last three are prefixed to avoid keyword collision.
31
+ Be sure to read the [Processor](http://www.rubydoc.info/gems/csdl/CSDL/Processor) and [Processor](http://www.rubydoc.info/gems/csdl/CSDL/Builder) docs if you get stuck.
32
+
33
+ Valid builder methods are:
34
+
35
+ - `_and` - `AND`s two or more child statements together.
36
+ - `_not` - Negates a `condition` statement.
37
+ - `_or` - `OR`s two or more child statements together.
38
+ - `_return` - Creates a return statement with an implicit `statement_scope`.
39
+ - `condition` - Builds a `target + operator + argument` group. Ensures `target` and `operator` are valid.
40
+ - `logical_group` - Create a parenthetical grouping for nested statements. Optionally takes a logical operator as the first argument since we commonly want to wrap OR'd or AND'd statements in a logical group.
41
+ - `statement_scope` - Create a braced grouping for nested statements used by tag and return blocks.
42
+ - `tag_tree` - Builds a tag tree classifier (e.g. `tag.movies "Video" { ... }`).
43
+ - `tag` - Builds a tag classifier (e.g. `tag "Desire" { ... }`).
44
+
45
+ Methods prefixed with "\_" are to avoid ruby keyword collisions.
29
46
 
30
47
  ```ruby
31
48
  builder = ::CSDL::Builder.new._or do
32
49
  [
33
- closure {
34
- _and {
35
- [
36
- closure {
37
- _or {
38
- [
39
- filter("fb.content", :contains_any, "ebola"),
40
- filter("fb.parent.content", :contains_any, "ebola")
41
- ]
42
- }
43
- },
44
- _not("fb.content", :contains_any, "government,politics"),
45
- filter("fb.author.country_code", :in, "GB")
46
- ]
47
- }
50
+ logical_group(:and) {
51
+ [
52
+ logical_group(:or) {
53
+ [
54
+ condition("fb.content", :contains_any, "ebola"),
55
+ condition("fb.parent.content", :contains_any, "ebola")
56
+ ]
57
+ },
58
+ _not("fb.content", :contains_any, "government,politics"),
59
+ condition("fb.author.country_code", :in, "GB")
60
+ ]
48
61
  },
49
- closure {
50
- _and {
51
- [
52
- closure {
53
- _or {
54
- [
55
- filter("fb.content", :contains_any, "malta,malta island,#malta"),
56
- filter("fb.parent.content", :contains_any, "malta,malta island,#malta")
57
- ]
58
- }
59
- },
60
- _not("fb.content", :contains_any, "vacation,poker awards")
61
- ]
62
- }
62
+ logical_group(:and) {
63
+ [
64
+ logical_group(:or) {
65
+ [
66
+ condition("fb.content", :contains_any, "malta,malta island,#malta"),
67
+ condition("fb.parent.content", :contains_any, "malta,malta island,#malta")
68
+ ]
69
+ },
70
+ _not("fb.content", :contains_any, "vacation,poker awards")
71
+ ]
63
72
  }
64
73
  ]
65
74
  end
@@ -78,16 +87,16 @@ The previous script produces the following output:
78
87
  ```
79
88
  Builder...
80
89
  (or
81
- (closure
90
+ (logical_group
82
91
  (and
83
- (closure
92
+ (logical_group
84
93
  (or
85
- (filter
94
+ (condition
86
95
  (target "fb.content")
87
96
  (operator :contains_any)
88
97
  (argument
89
98
  (string "ebola")))
90
- (filter
99
+ (condition
91
100
  (target "fb.parent.content")
92
101
  (operator :contains_any)
93
102
  (argument
@@ -97,21 +106,21 @@ Builder...
97
106
  (operator :contains_any)
98
107
  (argument
99
108
  (string "government,politics")))
100
- (filter
109
+ (condition
101
110
  (target "fb.author.country_code")
102
111
  (operator :in)
103
112
  (argument
104
113
  (string "GB")))))
105
- (closure
114
+ (logical_group
106
115
  (and
107
- (closure
116
+ (logical_group
108
117
  (or
109
- (filter
118
+ (condition
110
119
  (target "fb.content")
111
120
  (operator :contains_any)
112
121
  (argument
113
122
  (string "malta,malta island,#malta")))
114
- (filter
123
+ (condition
115
124
  (target "fb.parent.content")
116
125
  (operator :contains_any)
117
126
  (argument
data/csdl.gemspec CHANGED
@@ -5,13 +5,17 @@ require 'csdl/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "csdl"
8
- spec.version = Csdl::VERSION
8
+ spec.version = CSDL::VERSION
9
9
  spec.authors = ["BJ Neilsen"]
10
10
  spec.email = ["bj.neilsen@gmail.com"]
11
11
 
12
12
  spec.summary = %q{AST Processor and Query Builder for DataSift's CSDL language}
13
- spec.description = spec.summary
14
- spec.homepage = "https://localshred.github.io"
13
+ spec.description = %q{
14
+ CSDL is a gem for producing Abstract Syntax Trees for the [DataSift CSDL Filter Language](http://dev.datasift.com/docs/csdl).
15
+ Working with an AST instead of raw strings provides a simpler way to test and validate any given CSDL filter.
16
+ }
17
+
18
+ spec.homepage = "https://github.com/localshred/csdl"
15
19
 
16
20
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
21
  spec.require_paths = ["lib"]
@@ -21,4 +25,5 @@ Gem::Specification.new do |spec|
21
25
  spec.add_development_dependency "bundler", "~> 1.10"
22
26
  spec.add_development_dependency "rake", "~> 10.0"
23
27
  spec.add_development_dependency "minitest"
28
+ spec.add_development_dependency "yard"
24
29
  end
data/lib/csdl.rb CHANGED
@@ -1,6 +1,15 @@
1
1
  require "ast"
2
2
  require "csdl/version"
3
3
 
4
+ # CSDL is a library for manipulating Abstract Syntax Trees that represent raw CSDL defintions. Using an AST
5
+ # makes it much simpler to manipulate, traverse, validate, and test complex CSDL queries.
6
+ #
7
+ # Use the DSL {Builder} class to produce a tree of nodes, then process those nodes
8
+ # with {Processor}, {InteractionFilterProcessor}, or {QueryFilterProcessor}. See
9
+ # those individuals classes for usage documentation.
10
+ #
11
+ # @see http://dev.datasift.com/docs/csdl DataSift CSDL Language Documentation
12
+ #
4
13
  module CSDL
5
14
  end
6
15
 
data/lib/csdl/builder.rb CHANGED
@@ -1,30 +1,143 @@
1
1
  module CSDL
2
+
3
+ # {Builder} is a class used to produce {http://www.rubydoc.info/gems/ast/AST/Node AST::Node} objects built to be processed
4
+ # by any one of {Processor}, {InteractionFilterProcessor}, or {QueryFilterProcessor}.
5
+ #
6
+ # @example
7
+ # # Generate your CSDL nodes using the Builder
8
+ # root_node = CSDL::Builder.new.logical_group(:or) do
9
+ # #...
10
+ # condition("fb.content", :contains, "match this string")
11
+ # #...
12
+ # end
13
+ #
14
+ # # Process the root node and its children calling the instance method #process
15
+ # CSDL::Processor.new.process(root_node)
16
+ #
17
+ # @see http://dev.datasift.com/docs/csdl DataSift CSDL Language Documentation
18
+ #
2
19
  class Builder
3
20
  include ::AST::Sexp
4
21
 
5
- def __one_or_more_child_nodes(&block)
6
- children = instance_eval(&block)
7
- [ children ].flatten
8
- end
9
-
10
- def _or(&block)
11
- s(:or, *__one_or_more_child_nodes(&block))
12
- end
13
-
22
+ # Logically AND two or more child nodes together. Does not implicitly create a logical group with parentheses.
23
+ # If you want to logically group the ANDs, see {#logical_group}.
24
+ #
25
+ # @example
26
+ # nodes = CSDL::Builder.new._and do
27
+ # [
28
+ # condition("fb.content", :contains, "this is a string"),
29
+ # condition("fb.parent.content", :contains, "this is a string"),
30
+ # ]
31
+ # end
32
+ # CSDL::Processor.new.process(nodes) # => 'fb.content contains "this is a string" AND fb.parent.content contains "this is a string"'
33
+ #
34
+ # @param block [Proc] Block to return child nodes to apply to this :and node. Block is evaluated against the builder instance.
35
+ # @yieldreturn [AST::Node, Array<AST::Node>] An AST node or array of AST nodes to be wrapped by an :and node.
36
+ #
37
+ # @return [AST::Node] An AST :and node with its children being the node(s) returned by the block.
38
+ #
39
+ # @see #logical_group
40
+ #
14
41
  def _and(&block)
15
42
  s(:and, *__one_or_more_child_nodes(&block))
16
43
  end
17
44
 
45
+ # Negate a condition. Analogous to {#condition} with NOT prepended to the condition.
46
+ #
47
+ # @example
48
+ # node = CSDL::Builder.new._not("fb.content", :contains, "do not match this string")
49
+ # CSDL::Processor.new.process(node) # => 'NOT fb.content contains "do not match this string"'
50
+ #
51
+ # @example Multiple negations ANDed together
52
+ # nodes = CSDL::Builder.new._and do
53
+ # [
54
+ # _not("fb.content", :contains, "this is a string"),
55
+ # _not("fb.parent.content", :contains, "this is a string"),
56
+ # ]
57
+ # end
58
+ # CSDL::Processor.new.process(nodes) # => 'NOT fb.content contains "this is a string" AND NOT fb.parent.content contains "this is a string"'
59
+ #
60
+ # @param target [#to_s] A valid Target specifier (see {CSDL::TARGETS}).
61
+ # @param operator [#to_s] A valid Operator specifier (see {CSDL::OPERATORS}).
62
+ # @param argument [String, Numeric, nil] The comparator value, if applicable for the given operator.
63
+ #
64
+ # @return [AST::Node] An AST :not node with child target, operator, and argument nodes.
65
+ #
66
+ # @see #condition
67
+ #
18
68
  def _not(target, operator, argument = nil)
19
- node = filter(target, operator, argument)
69
+ node = condition(target, operator, argument)
20
70
  node.updated(:not)
21
71
  end
22
72
 
73
+ # Logically OR two or more child nodes together. Does not implicitly create a logical group with parentheses.
74
+ # If you want to logically group the ORs, see {#logical_group}.
75
+ #
76
+ # @example
77
+ # nodes = CSDL::Builder.new._or do
78
+ # [
79
+ # condition("fb.content", :contains, "this is a string"),
80
+ # condition("fb.parent.content", :contains, "this is a string")
81
+ # ]
82
+ # end
83
+ # CSDL::Processor.new.process(nodes) # => 'fb.content contains "this is a string" OR fb.parent.content contains "this is a string"'
84
+ #
85
+ # @param block [Proc] Block to return child nodes to apply to this :or node. Block is evaluated against the builder instance.
86
+ # @yieldreturn [AST::Node, Array<AST::Node>] An AST node or array of AST nodes to be wrapped by an :or node.
87
+ # @return [AST::Node] An AST :or node with its children being the node(s) returned by the block.
88
+ #
89
+ # @see #logical_group
90
+ #
91
+ def _or(&block)
92
+ s(:or, *__one_or_more_child_nodes(&block))
93
+ end
94
+
95
+ # Wrap child nodes in a return statement scope.
96
+ #
97
+ # @note The base {Processor} will not process return statement nodes, use {InteractionFilterProcessor} instead.
98
+ #
99
+ # @example
100
+ # nodes = CSDL::Builder.new._return do
101
+ # condition("fb.content", :contains, "this is a string")
102
+ # end
103
+ # CSDL::InteractionFilterProcessor.new.process(nodes) # => 'return {fb.content contains "this is a string"}'
104
+ #
105
+ # @param block [Proc] Block to return child nodes to apply to a :statement_scope node. Block is evaluated against the builder instance.
106
+ # @yieldreturn [AST::Node, Array<AST::Node>] An AST node or array of AST nodes to be wrapped by a :statement_scope node.
107
+ #
108
+ # @return [AST::Node] An AST :return node with a single child :statement_scope node, its children being the node(s) returned by the block.
109
+ #
110
+ # @see #statement_scope
111
+ #
23
112
  def _return(&block)
24
113
  s(:return, statement_scope(&block))
25
114
  end
26
115
 
27
- def filter(target, operator, argument = nil)
116
+ # Create a "target + operator[ + argument]" CSDL condition. This method is the workhorse of any CSDL Filter.
117
+ # See {#_not} if you wish to negate a single condition.
118
+ #
119
+ # @example
120
+ # node = CSDL::Builder.new.condition("fb.content", :contains, "match this string")
121
+ # CSDL::Processor.new.process(node) # => 'fb.content contains "match this string"'
122
+ #
123
+ # @example Multiple conditions ANDed together
124
+ # nodes = CSDL::Builder.new._and do
125
+ # [
126
+ # condition("fb.content", :contains, "this is a string"),
127
+ # condition("fb.parent.content", :contains, "this is a string"),
128
+ # ]
129
+ # end
130
+ # CSDL::Processor.new.process(nodes) # => 'fb.content contains "this is a string" AND fb.parent.content contains "this is a string"'
131
+ #
132
+ # @param target [#to_s] A valid Target specifier (see {CSDL::TARGETS}).
133
+ # @param operator [#to_s] A valid Operator specifier (see {CSDL::OPERATORS}).
134
+ # @param argument [String, Numeric, nil] The comparator value, if applicable for the given operator.
135
+ #
136
+ # @return [AST::Node] An AST :condition node with child target, operator, and argument nodes.
137
+ #
138
+ # @see #_not
139
+ #
140
+ def condition(target, operator, argument = nil)
28
141
  target_node = s(:target, target)
29
142
  operator_node = s(:operator, operator)
30
143
  argument_node = nil
@@ -35,9 +148,72 @@ module CSDL
35
148
  argument_node = s(:argument, child_argument_node)
36
149
  end
37
150
 
38
- s(:filter, *[target_node, operator_node, argument_node].compact)
151
+ s(:condition, *[target_node, operator_node, argument_node].compact)
39
152
  end
40
153
 
154
+ # Wrap any child nodes in a logical grouping with parentheses. Additionally specify a logical
155
+ # operator to wrap all block child nodes. See {#_or} and {#_and}.
156
+ #
157
+ # @example
158
+ # nodes = CSDL::Builder.new.logical_group do
159
+ # condition("fb.content", :contains, "this is a string")
160
+ # end
161
+ # CSDL::Processor.new.process(nodes) # => '(fb.content contains "this is a string")'
162
+ #
163
+ # @example Without logical operator argument (default)
164
+ # nodes = CSDL::Builder.new.logical_group do
165
+ # _or do
166
+ # [
167
+ # condition("fb.content", :contains, "this is a string"),
168
+ # condition("fb.parent.content", :contains, "this is a string")
169
+ # ]
170
+ # end
171
+ # end
172
+ # CSDL::Processor.new.process(nodes) # => '(fb.content contains "this is a string" OR fb.parent.content contains "this is a string")'
173
+ #
174
+ # @example With logical operator argument, notice removal of _or block from previous example
175
+ # nodes = CSDL::Builder.new.logical_group(:or) do
176
+ # [
177
+ # condition("fb.content", :contains, "this is a string"),
178
+ # condition("fb.parent.content", :contains, "this is a string")
179
+ # ]
180
+ # end
181
+ # CSDL::Processor.new.process(nodes) # => '(fb.content contains "this is a string" OR fb.parent.content contains "this is a string")'
182
+ #
183
+ # @example Complex example
184
+ # nodes = CSDL::Builder.new._and do
185
+ # [
186
+ # logical_group(:or) {
187
+ # [
188
+ # condition("fb.content", :contains, "this is a string"),
189
+ # condition("fb.parent.content", :contains, "this is a string")
190
+ # ]
191
+ # },
192
+ # logical_group(:or) {
193
+ # [
194
+ # condition("fb.author.age", :==, "25-34"),
195
+ # condition("fb.parent.author.age", :==, "25-34")
196
+ # ]
197
+ # },
198
+ # logical_group(:or) {
199
+ # [
200
+ # condition("fb.author.gender", :==, "male"),
201
+ # condition("fb.parent.author.gender", :==, "male")
202
+ # ]
203
+ # },
204
+ # condition("fb.author.region", :==, "texas")
205
+ # ]
206
+ # end
207
+ # CSDL::Processor.new.process(nodes) # => '(fb.content contains "this is a string" OR fb.parent.content contains "this is a string") AND (fb.author.age == "25-34" OR fb.parent.author.age == "25-34") AND (fb.author.gender == "male" OR fb.parent.author.gender == "male") AND fb.author.region == "texas"'
208
+ #
209
+ # @param block [Proc] Block to return child nodes to apply to this :logical_group node. Block is evaluated against the builder instance.
210
+ # @yieldreturn [AST::Node, Array<AST::Node>] An AST node or array of AST nodes to be wrapped by a :logical_group node (and possibly also a node for the logical operator).
211
+ #
212
+ # @return [AST::Node] An AST :logical_operator node with its children being the node(s) returned by the block.
213
+ #
214
+ # @see #_and
215
+ # @see #_or
216
+ #
41
217
  def logical_group(logical_operator = nil, &block)
42
218
  if logical_operator.nil?
43
219
  s(:logical_group, *__one_or_more_child_nodes(&block))
@@ -46,10 +222,80 @@ module CSDL
46
222
  end
47
223
  end
48
224
 
225
+ # Wrap child nodes in a root node. Useful for building CSDL with tagging and a return statement.
226
+ #
227
+ # @example
228
+ # nodes = CSDL::Builder.new.root do
229
+ # [
230
+ # tag_tree(["movies"], "Video") { condition("links.url", :any, "youtube.com,vimeo.com") },
231
+ # tag_tree(["movies"], "Social Networks") { condition("links.url", :any, "twitter.com,facebook.com") },
232
+ #
233
+ # return {
234
+ # _or {
235
+ # [
236
+ # condition("fb.topics.category", :in, "Movie,Film,TV"),
237
+ # condition("fb.parent.topics.category", :in, "Movie,Film,TV")
238
+ # ]
239
+ # }
240
+ # }
241
+ # ]
242
+ # end
243
+ # CSDL::InteractionFilterProcessor.new.process(nodes) # => 'tag.movies "Video" {links.url any "youtube.com,vimeo.com"} tag.movies "Social Networks" {links.url any "twitter.com,facebook.com"} return {fb.topics.category in "Movie,Film,TV" OR fb.parent.topics.cateogry in "Movie,Film,TV"}'
244
+ #
245
+ # @param block [Proc] Block to return child nodes to apply to this :statement_scope node. Block is evaluated against the builder instance.
246
+ # @yieldreturn [AST::Node, Array<AST::Node>] An AST node or array of AST nodes to be wrapped by a :statement_scope node.
247
+ #
248
+ # @return [AST::Node] An AST :statement_scope node with its children being the node(s) returned by the block.
249
+ #
250
+ # @see #_return
251
+ # @see #tag_tree
252
+ #
253
+ def root(&block)
254
+ s(:root, *__one_or_more_child_nodes(&block))
255
+ end
256
+
257
+ # Wrap child nodes in braces. @note Generally not useful on its own, see {#_return}, {#tag}, or {#tag_tree} usage.
258
+ #
259
+ # @note The base {Processor} will not process statement_scope nodes, use {InteractionFilterProcessor} instead.
260
+ #
261
+ # @example
262
+ # nodes = CSDL::Builder.new.statement_scope do
263
+ # condition("fb.content", :contains, "this is a string")
264
+ # end
265
+ # CSDL::InteractionFilterProcessor.new.process(nodes) # => '{fb.content contains "this is a string"}'
266
+ #
267
+ # @param block [Proc] Block to return child nodes to apply to this :statement_scope node. Block is evaluated against the builder instance.
268
+ # @yieldreturn [AST::Node, Array<AST::Node>] An AST node or array of AST nodes to be wrapped by a :statement_scope node.
269
+ #
270
+ # @return [AST::Node] An AST :statement_scope node with its children being the node(s) returned by the block.
271
+ #
272
+ # @see #_return
273
+ # @see #tag
274
+ # @see #tag_tree
275
+ #
49
276
  def statement_scope(&block)
50
277
  s(:statement_scope, *__one_or_more_child_nodes(&block))
51
278
  end
52
279
 
280
+ # Wrap child nodes in a VEDO tag classification.
281
+ #
282
+ # @note The base {Processor} will not process tag nodes, use {InteractionFilterProcessor} instead.
283
+ #
284
+ # @example
285
+ # nodes = CSDL::Builder.new.tag("MyTag") do
286
+ # condition("fb.content", :contains, "this is a string")
287
+ # end
288
+ # CSDL::InteractionFilterProcessor.new.process(nodes) # => 'tag "MyTag" {fb.content contains "this is a string"}'
289
+ #
290
+ # @param tag_class [#to_s] The tag classification.
291
+ # @param block [Proc] Block to return child nodes to apply to a :statement_scope node nested under the :tag node. Block is evaluated against the builder instance.
292
+ # @yieldreturn [AST::Node, Array<AST::Node>] An AST node or array of AST nodes to be wrapped by a :statement_scope node, to be a child of the returned :tag node.
293
+ #
294
+ # @return [AST::Node] An AST :tag node with a :tag_class child node and :statement_scope child node.
295
+ #
296
+ # @see #statement_scope
297
+ # @see #tag_tree
298
+ #
53
299
  def tag(tag_class, &block)
54
300
  s(:tag,
55
301
  s(:tag_class,
@@ -57,17 +303,45 @@ module CSDL
57
303
  statement_scope(&block))
58
304
  end
59
305
 
60
- def tag_tree(tag_nodes, tag_class, &block)
61
- tag_node_nodes = tag_nodes.map do |tag_node|
62
- s(:tag_node, tag_node)
306
+ # Wrap child nodes in a VEDO tag classification tree.
307
+ #
308
+ # @note The base {Processor} will not process tag_tree nodes, use {InteractionFilterProcessor} instead.
309
+ #
310
+ # @example
311
+ # nodes = CSDL::Builder.new.tag_tree(%w(foo bar), "MyTag") do
312
+ # condition("fb.content", :contains, "this is a string")
313
+ # end
314
+ # CSDL::InteractionFilterProcessor.new.process(nodes) # => 'tag.foo.bar "MyTag" {fb.content contains "this is a string"}'
315
+ #
316
+ # @param tag_namespaces [Array<#to_s>] List of classification namespaces.
317
+ # @param tag_class [#to_s] The tag classification.
318
+ # @param block [Proc] Block to return child nodes to apply to a :statement_scope node nested under the :tag node. Block is evaluated against the builder instance.
319
+ # @yieldreturn [AST::Node, Array<AST::Node>] An AST node or array of AST nodes to be wrapped by a :statement_scope node, to be a child of the returned :tag node.
320
+ #
321
+ # @return [AST::Node] An AST :tag node with a :tag_namespaces child node, :tag_class child node, and :statement_scope child node.
322
+ #
323
+ # @see #statement_scope
324
+ # @see #tag
325
+ #
326
+ def tag_tree(tag_namespaces, tag_class, &block)
327
+ tag_namespace_nodes = tag_namespaces.map do |tag_namespace|
328
+ s(:tag_namespace, tag_namespace)
63
329
  end
64
330
 
65
331
  s(:tag,
66
- s(:tag_nodes,
67
- *tag_node_nodes),
332
+ s(:tag_namespaces,
333
+ *tag_namespace_nodes),
68
334
  s(:tag_class,
69
335
  s(:string, tag_class)),
70
336
  statement_scope(&block))
71
337
  end
338
+
339
+ private
340
+
341
+ def __one_or_more_child_nodes(&block)
342
+ children = instance_eval(&block)
343
+ [ children ].flatten
344
+ end
345
+
72
346
  end
73
347
  end
@@ -1,6 +1,54 @@
1
1
  module CSDL
2
+
3
+ # {InteractionFilterProcessor} is a class that inherits from {Processor}, providing additional methods
4
+ # for building CSDL specifically for Interaction Filters.
5
+ #
6
+ # Additional DSL methods provide the return statement, curly brace scopes (statement scopes), and VEDO tagging.
7
+ #
8
+ # @example
9
+ # nodes = CSDL::Builder.new.root do
10
+ # [
11
+ # tag_tree(%w(movies), "Video") {
12
+ # condition("links.url", :any, "youtube.com,vimeo.com")
13
+ # },
14
+ # tag_tree(%w(movies), "Social Networks") {
15
+ # condition("links.url", :any, "twitter.com,facebook.com")
16
+ # },
17
+ #
18
+ # return {
19
+ # _or {
20
+ # [
21
+ # condition("fb.topics.category", :in, "Movie,Film,TV"),
22
+ # condition("fb.parent.topics.category", :in, "Movie,Film,TV")
23
+ # ]
24
+ # }
25
+ # }
26
+ # ]
27
+ # end
28
+ # CSDL::InteractionFilterProcessor.new.process(nodes) # => 'tag.movies "Video" {links.url any "youtube.com,vimeo.com"} tag.movies "Social Networks" {links.url any "twitter.com,facebook.com"} return {fb.topics.category in "Movie,Film,TV" OR fb.parent.topics.cateogry in "Movie,Film,TV"}'
29
+ #
30
+ # @see Processor
31
+ # @see Builder
32
+ # @see http://www.rubydoc.info/gems/ast/AST/Processor AST::Processor
33
+ # @see http://www.rubydoc.info/gems/ast/AST/Node AST::Node
34
+ # @see http://dev.datasift.com/docs/csdl DataSift CSDL Language Documentation
35
+ #
2
36
  class InteractionFilterProcessor < ::CSDL::Processor
3
37
 
38
+ # Generate a return statement by processing the child statement_scope node.
39
+ #
40
+ # @raise [MissingReturnStatementScopeError] When the :return node is missing a :statement_scope child node.
41
+ #
42
+ # @example
43
+ # node = s(:return,
44
+ # s(:statement_scope,
45
+ # s(:string, "foo")))
46
+ # CSDL::InteractionFilterProcessor.new.process(node) # => 'return {"foo"}'
47
+ #
48
+ # @param node [AST::Node] The :return node to be processed.
49
+ #
50
+ # @return [String] The processed :statement_scope child node, prepended by the "return" keyword.
51
+ #
4
52
  def on_return(node)
5
53
  statement_scope = node.children.find { |child| child.type == :statement_scope }
6
54
 
@@ -11,12 +59,55 @@ module CSDL
11
59
  "return #{process(statement_scope)}"
12
60
  end
13
61
 
62
+ # Wrap child nodes in braces. Generally not useful on its own, see {#on_return} or {#on_tag} for integrated usage.
63
+ #
64
+ # @example
65
+ # node = s(:statement_scope,
66
+ # s(:string, "foo"))
67
+ # CSDL::InteractionFilterProcessor.new.process(node) # => '{"foo"}'
68
+ #
69
+ # @param node [AST::Node] The :statement_scope node to be processed.
70
+ #
71
+ # @return [String] The processed child nodes, joined by an empty space and wrapped in braces.
72
+ #
73
+ # @see #on_return
74
+ # @see #on_tag
75
+ #
14
76
  def on_statement_scope(node)
15
77
  "{" + process_all(node.children).join(" ") + "}"
16
78
  end
17
79
 
80
+ # Process :tag node with it's child nodes :tag_namespaces (optional), :tag_class, and :statement_scope.
81
+ #
82
+ # @example Tag Classification
83
+ # node = s(:tag,
84
+ # s(:tag_class,
85
+ # s(:string, "MyTag")),
86
+ # s(:statement_scope,
87
+ # s(:string, "foo")))
88
+ # CSDL::InteractionFilterProcessor.new.process(node) # => 'tag "MyTag" {"foo"}'
89
+ #
90
+ # @example Tag Tree Classification
91
+ # node = s(:tag,
92
+ # s(:tag_namespaces,
93
+ # s(:tag_namespace, "foo"),
94
+ # s(:tag_namespace, "bar"),
95
+ # s(:tag_namespace, "baz")),
96
+ # s(:tag_class,
97
+ # s(:string, "MyTag")),
98
+ # s(:statement_scope,
99
+ # s(:string, "value")))
100
+ # CSDL::InteractionFilterProcessor.new.process(node) # => 'tag.foo.bar.baz "MyTag" {"value"}'
101
+ #
102
+ # @param node [AST::Node] The :tag node to be processed.
103
+ #
104
+ # @return [String] The tag classifier raw CSDL.
105
+ #
106
+ # @raise [MissingTagClassError] When we don't have a first-level :tag_namespaces child.
107
+ # @raise [MissingTagStatementScopeError] When we don't have a first-level :statement_scope child.
108
+ #
18
109
  def on_tag(node)
19
- tag_nodes = node.children.find { |child| child.type == :tag_nodes }
110
+ tag_namespaces = node.children.find { |child| child.type == :tag_namespaces }
20
111
  tag_class = node.children.find { |child| child.type == :tag_class }
21
112
  statement_scope = node.children.find { |child| child.type == :statement_scope }
22
113
 
@@ -29,32 +120,78 @@ module CSDL
29
120
  end
30
121
 
31
122
  tag_namespace = "tag"
32
- unless tag_nodes.nil?
33
- tag_namespace += process(tag_nodes)
123
+ unless tag_namespaces.nil?
124
+ tag_namespace += process(tag_namespaces)
34
125
  end
35
126
 
36
127
  children = [tag_namespace] + process_all([ tag_class, statement_scope ])
37
128
  children.join(" ")
38
129
  end
39
130
 
131
+ # Process the first child of the :tag_class node.
132
+ #
133
+ # @param node [AST::Node] The :tag_class node to be processed.
134
+ #
135
+ # @return [String] The processed value of the first child node.
136
+ #
137
+ # @see #on_tag
138
+ #
40
139
  def on_tag_class(node)
41
140
  process(node.children.first)
42
141
  end
43
142
 
44
- def on_tag_node(node)
143
+ # Process the terminal value of the :tag_namespace node.
144
+ #
145
+ # @param node [AST::Node] The :tag_namespace node to be processed.
146
+ #
147
+ # @return [String] Terminal value as a string.
148
+ #
149
+ # @see #on_tag_namespaces
150
+ #
151
+ def on_tag_namespace(node)
45
152
  node.children.first.to_s
46
153
  end
47
154
 
48
- def on_tag_nodes(node)
49
- child_tag_nodes = node.children.select { |child| child.type == :tag_node }
50
-
51
- if child_tag_nodes.empty?
52
- fail ::CSDL::MissingTagNodesError, "Invalid CSDL AST: A :tag_nodes node must have at least one :tag_node child"
155
+ # Process the :tag_namespace child nodes of a :tag_namespaces node.
156
+ #
157
+ # @example
158
+ # node = s(:tag_namespaces,
159
+ # s(:tag_namespace, "foo"),
160
+ # s(:tag_namespace, "bar"),
161
+ # s(:tag_namespace, "baz"))
162
+ # CSDL::InteractionFilterProcessor.new.process(node) # => '.foo.bar.baz'
163
+ #
164
+ # @param node [AST::Node] The :tag_namespaces node to be processed.
165
+ #
166
+ # @return [String] Dot-delimited tag node namespace.
167
+ #
168
+ # @raise [MissingTagNodesError] When there aren't any first-level :tag_namespace child nodes.
169
+ #
170
+ def on_tag_namespaces(node)
171
+ child_tag_namespaces = node.children.select { |child| child.type == :tag_namespace }
172
+
173
+ if child_tag_namespaces.empty?
174
+ fail ::CSDL::MissingTagNodesError, "Invalid CSDL AST: A :tag_namespaces node must have at least one :tag_namespace child"
53
175
  end
54
176
 
55
- "." + process_all(child_tag_nodes).join(".")
177
+ "." + process_all(child_tag_namespaces).join(".")
56
178
  end
57
179
 
180
+ # Raises an {InvalidInteractionTargetError} if the target isn't a valid CSDL target for interaction filters. Will
181
+ # be called from the base class when given a :condition node with a :target node.
182
+ #
183
+ # @example
184
+ # CSDL::InteractionFilterProcessorProcessor.new.validate_target!("fake") # => raises InvalidInteractionTargetError
185
+ #
186
+ # @param target_key [String] The target to validate.
187
+ #
188
+ # @return [void]
189
+ #
190
+ # @raise [InvalidInteractionTargetError] When the terminator value is not a valid ineraction filter target. See {CSDL.interaction_target?}.
191
+ #
192
+ # @see Processor#on_condition
193
+ # @see Processor#on_target
194
+ #
58
195
  def validate_target!(target_key)
59
196
  unless ::CSDL.interaction_target?(target_key)
60
197
  fail ::CSDL::InvalidInteractionTargetError, "Interaction filters cannot use target '#{target_key}'"
@@ -1,8 +1,19 @@
1
1
  module CSDL
2
2
 
3
+ # A CSDL Operator definition with indication as to valid data types to be used with the operator.
4
+ #
5
+ # @attr name [String] The name of the operator.
6
+ # @attr argument_types [Array<Symbol>] List of valid argument node types that can be associated with an operator.
7
+ #
8
+ # @see OPERATORS
9
+ #
3
10
  Operator = Struct.new(:name, :argument_types)
4
11
 
5
- raw_operators = [
12
+ # A raw array of operators with their valid argument types.
13
+ #
14
+ # @return [Array<String, Array<Symbol>>] Array of operators used to produce {OPERATORS} hash.
15
+ #
16
+ RAW_OPERATORS = [
6
17
  [ "contains" , [ :string ] ],
7
18
  [ "cs contains" , [ :string ] ],
8
19
  [ "substr" , [ :string ] ],
@@ -39,11 +50,26 @@ module CSDL
39
50
  [ "geo_polygon" , [ :string ] ]
40
51
  ]
41
52
 
42
- OPERATORS = raw_operators.reduce({}) do |accumulator, (operator_name, argument_types)|
53
+ # All possible operators.
54
+ #
55
+ # @return [Hash<String, Operator>] Hash of {Operator} structs, keyed by the string name of the operator.
56
+ #
57
+ OPERATORS = RAW_OPERATORS.reduce({}) do |accumulator, (operator_name, argument_types)|
43
58
  accumulator[operator_name] = Operator.new(operator_name, argument_types)
44
59
  accumulator
45
60
  end.freeze
46
61
 
62
+ # Check if the given target is a valid operator.
63
+ #
64
+ # @example
65
+ # CSDL.operator?("fake") # => false
66
+ # CSDL.operator?("contains") # => true
67
+ # CSDL.operator?(">=") # => true
68
+ #
69
+ # @param operator [String] The name of the target.
70
+ #
71
+ # @return [Boolean] Whether or not the value is a valid CSDL Operator.
72
+ #
47
73
  def self.operator?(operator)
48
74
  OPERATORS.key?(operator)
49
75
  end
@@ -1,28 +1,142 @@
1
1
  module CSDL
2
+
3
+ # {Processor} is a class that can take a tree of AST::Node objects built with {Builder}
4
+ # and produce a valid CSDL string representation according to DataSift's CSDL specification.
5
+ #
6
+ # @example
7
+ # # Generate your CSDL nodes using the Builder
8
+ # root_node = CSDL::Builder.new.logical_group(:or) do
9
+ # #...
10
+ # condition("fb.content", :contains, "match this string")
11
+ # #...
12
+ # end
13
+ #
14
+ # # Process the root node and its children calling the instance method #process
15
+ # CSDL::Processor.new.process(root_node)
16
+ #
17
+ # @see Builder
18
+ # @see http://www.rubydoc.info/gems/ast/AST/Processor AST::Processor
19
+ # @see http://www.rubydoc.info/gems/ast/AST/Node AST::Node
20
+ # @see http://dev.datasift.com/docs/csdl DataSift CSDL Language Documentation
21
+ #
2
22
  class Processor < ::AST::Processor
3
23
 
24
+ # AND two or more child nodes together.
25
+ #
26
+ # @example
27
+ # node = s(:and,
28
+ # s(:string, "foo"),
29
+ # s(:string, "bar"),
30
+ # s(:string, "baz"))
31
+ # CSDL::Processor.new.process(node) # => '"foo" AND "bar" AND "baz"'
32
+ #
33
+ # @param node [AST::Node] The :and node to be processed.
34
+ #
35
+ # @raise [MissingChildNodesError] When less than 2 child nodes are present.
36
+ #
37
+ # @return [String] Processed child nodes ANDed together into a raw CSDL string representation.
38
+ #
4
39
  def on_and(node)
5
- initial = process(node.children.first)
6
- rest = node.children.drop(1)
7
-
8
- rest.reduce(initial) do |csdl, child|
9
- csdl += " AND " + process(child)
10
- csdl
11
- end
40
+ logically_join_nodes("AND", node.children)
12
41
  end
13
42
 
43
+ # Process the first child node as the "argument" in a condition node tree (target + operator + argument).
44
+ #
45
+ # @example
46
+ # node = s(:argument,
47
+ # s(:string, "foo"))
48
+ # CSDL::Processor.new.process(node) # => '"foo"'
49
+ #
50
+ # @param node [AST::Node] The :argument node to be processed.
51
+ #
52
+ # @return [String] The first child node, processed by its node type into a raw CSDL string representation.
53
+ #
54
+ # @todo Raise if the node doesn't have any children.
55
+ #
14
56
  def on_argument(node)
15
57
  process(node.children.first)
16
58
  end
17
59
 
60
+ # Process :condition node and it's expected children :target, :operator, and :argument nodes.
61
+ #
62
+ # @example
63
+ # node = s(:condition,
64
+ # s(:target, "fb.content"),
65
+ # s(:operator, :contains_any),
66
+ # s(:argument,
67
+ # s(:string, "foo")))
68
+ # CSDL::Processor.new.process(node) # => 'fb.content contains_any "foo"'
69
+ #
70
+ # @param node [AST::Node] The :condition node to be processed.
71
+ #
72
+ # @return [String] The child nodes :target, :operator, and :argument, processed and joined.
73
+ #
74
+ # @todo Raise when we don't have a target node.
75
+ # @todo Raise when we don't have a operator node.
76
+ # @todo Raise when we don't have a argument node, assuming the operator is binary.
77
+ # @todo Raise if the argument node's child is not of a valid node type for the given operator.
78
+ #
79
+ def on_condition(node)
80
+ target = node.children.find { |child| child.type == :target }
81
+ operator = node.children.find { |child| child.type == :operator }
82
+ argument = node.children.find { |child| child.type == :argument }
83
+ process_all([ target, operator, argument ].compact).join(" ")
84
+ end
85
+
86
+ # Wrap all processed child nodes in parentheses.
87
+ #
88
+ # @example
89
+ # node = s(:logical_group,
90
+ # s(:or,
91
+ # s(:string, "foo"),
92
+ # s(:string, "bar"),
93
+ # s(:string, "baz")))
94
+ # CSDL::Processor.new.process(node) # => '("foo" OR "bar" OR "baz")'
95
+ #
96
+ # @param node [AST::Node] The :logical_group node to be processed.
97
+ #
98
+ # @return [String] All child nodes processed by their node types into a raw CSDL string representation, wrapped in parentheses.
99
+ #
18
100
  def on_logical_group(node)
19
101
  "(" + process_all(node.children).join(" ") + ")"
20
102
  end
21
103
 
104
+ # Process :not node as a :condition node, prepending the logical operator NOT to the processed :condition node.
105
+ #
106
+ # @example
107
+ # node = s(:not,
108
+ # s(:target, "fb.content"),
109
+ # s(:operator, :contains_any),
110
+ # s(:argument,
111
+ # s(:string, "foo")))
112
+ # CSDL::Processor.new.process(node) # => 'NOT fb.content contains_any "foo"'
113
+ #
114
+ # @param node [AST::Node] The :not node to be processed.
115
+ #
116
+ # @return [String] The child nodes :target, :operator, and :argument, processed and joined.
117
+ #
118
+ # @todo Raise when we don't have a target node.
119
+ # @todo Raise when we don't have a operator node.
120
+ # @todo Raise when we don't have a argument node, assuming the operator is binary.
121
+ # @todo Raise if the argument node's child is not of a valid node type for the given operator.
122
+ # @todo Support negating logical groupings.
123
+ #
22
124
  def on_not(node)
23
- "NOT " + process(node.updated(:filter))
125
+ "NOT " + process(node.updated(:condition))
24
126
  end
25
127
 
128
+ # Process :operator nodes, ensuring the the given terminator value is a valid operator.
129
+ #
130
+ # @example
131
+ # node = s(:operator, :contains)
132
+ # CSDL::Processor.new.process(node) # => 'contains'
133
+ #
134
+ # @param node [AST::Node] The :operator node to be processed.
135
+ #
136
+ # @return [String] The first child, stringified.
137
+ #
138
+ # @raise [UnknownOperatorError] When the terminator value is not a valid operator. See {CSDL.operator?}.
139
+ #
26
140
  def on_operator(node)
27
141
  operator = node.children.first.to_s
28
142
  unless ::CSDL.operator?(operator)
@@ -31,44 +145,104 @@ module CSDL
31
145
  operator
32
146
  end
33
147
 
148
+ # OR two or more child nodes together.
149
+ #
150
+ # @example
151
+ # node = s(:or,
152
+ # s(:string, "foo"),
153
+ # s(:string, "bar"),
154
+ # s(:string, "baz"))
155
+ # CSDL::Processor.new.process(node) # => '"foo" OR "bar" OR "baz"'
156
+ #
157
+ # @param node [AST::Node] The :or node to be processed.
158
+ #
159
+ # @raise [MissingChildNodesError] When less than 2 child nodes are present.
160
+ #
161
+ # @return [String] Processed child nodes OR'd together into a raw CSDL string representation.
162
+ #
34
163
  def on_or(node)
35
- if node.children.empty?
36
- fail ::CSDL::MissingChildNodesError, "Invalid CSDL AST: 'or' nodes must contain at least two child nodes. Expected >= 2, got 0"
37
- end
38
-
39
- initial = process(node.children.first)
40
- rest = node.children.drop(1)
41
-
42
- if rest.empty?
43
- fail ::CSDL::MissingChildNodesError, "Invalid CSDL AST: 'or' nodes must contain at least two child nodes. Expected >= 2, got #{node.children.size}"
44
- end
164
+ logically_join_nodes("OR", node.children)
165
+ end
45
166
 
46
- rest.reduce(initial) do |csdl, child|
47
- csdl += " OR " + process(child)
48
- csdl
49
- end
167
+ # Process all child nodes. Useful for grouping child nodes without any syntax introduction.
168
+ #
169
+ # @see InteractionFilterProcessor#_return
170
+ # @see InteractionFilterProcessor#tag
171
+ # @see InteractionFilterProcessor#tag_tree
172
+ #
173
+ def on_root(node)
174
+ process_all(node.children).join(" ")
50
175
  end
51
176
 
177
+ # Wrap the stringified terminal value in quotes.
178
+ #
179
+ # @example
180
+ # node = s(:string, "foo")
181
+ # CSDL::Processor.new.process(node) # => '"foo"'
182
+ #
183
+ # @param node [AST::Node] The :string node to be processed.
184
+ #
185
+ # @return [String] The first child node, processed by its node type into a raw CSDL string representation.
186
+ #
187
+ # @todo Raise if the node doesn't have any children.
188
+ #
52
189
  def on_string(node)
53
190
  '"' + node.children.first.to_s.gsub(/"/, '\"') + '"'
54
191
  end
55
192
 
193
+ # Process :target nodes, ensuring the the given terminator value is a valid operator.
194
+ #
195
+ # @example
196
+ # node = s(:target, "fb.content")
197
+ # CSDL::Processor.new.process(node) # => 'fb.content'
198
+ #
199
+ # @param node [AST::Node] The :target node to be processed.
200
+ #
201
+ # @return [String] The first child, stringified.
202
+ #
203
+ # @raise [UnknownTargetError] When the terminator value is not a valid operator. See {#validate_target!}.
204
+ #
205
+ # @see #validate_target!
206
+ #
56
207
  def on_target(node)
57
208
  target = node.children.first.to_s
58
209
  validate_target!(target)
59
210
  target
60
211
  end
61
212
 
62
- def on_filter(node)
63
- target = node.children.find { |child| child.type == :target }
64
- operator = node.children.find { |child| child.type == :operator }
65
- argument = node.children.find { |child| child.type == :argument }
66
- process_all([ target, operator, argument ].compact).join(" ")
67
- end
68
-
213
+ # Raises an {UnknownTargetError} if the target isn't a valid CSDL target. Useful for implenting a
214
+ # child processor that can ensure the target is known and valid for a given use-case
215
+ # (e.g. {InteractionFilterProcessor Interaction Filters} vs {QueryFilterProcessor Query Filters}).
216
+ # Generally not useful to be called directly, use {CSDL.target?} instead.
217
+ #
218
+ # @example
219
+ # CSDL::Processor.new.validate_target!("fake") # => raises UnknownTargetError
220
+ #
221
+ # @param target_key [String] The target to validate.
222
+ #
223
+ # @return [void]
224
+ #
225
+ # @raise [UnknownTargetError] When the terminator value is not a valid operator. See {CSDL.operator?}.
226
+ #
69
227
  def validate_target!(target_key)
70
228
  unless ::CSDL.target?(target_key)
71
- fail ::CSDL::InvalidQueryTargetError, "Query filters cannot use target '#{target_key}'"
229
+ fail ::CSDL::UnknownTargetError, "Target '#{target_key}' is not a known target type."
230
+ end
231
+ end
232
+
233
+ private
234
+
235
+ def logically_join_nodes(logical_operator, child_nodes)
236
+ if child_nodes.size < 2
237
+ fail ::CSDL::MissingChildNodesError, ":#{logical_operator} nodes must contain at least two child nodes. Expected >= 2, got #{child_nodes.size}"
238
+ end
239
+
240
+ initial = process(child_nodes.first)
241
+ rest = child_nodes.drop(1)
242
+
243
+ rest.reduce(initial) do |csdl, child|
244
+ csdl += " #{logical_operator.upcase} " + process(child)
245
+ csdl
72
246
  end
73
247
  end
74
248
 
@@ -1,6 +1,31 @@
1
1
  module CSDL
2
+
3
+ # {QueryFilterProcessor} is a class that inherits from {Processor}, providing additional methods
4
+ # for building CSDL specifically for Query Filters.
5
+ #
6
+ # @see Processor
7
+ # @see Builder
8
+ # @see http://www.rubydoc.info/gems/ast/AST/Processor AST::Processor
9
+ # @see http://www.rubydoc.info/gems/ast/AST/Node AST::Node
10
+ # @see http://dev.datasift.com/docs/csdl DataSift CSDL Language Documentation
11
+ #
2
12
  class QueryFilterProcessor < ::CSDL::Processor
3
13
 
14
+ # Raises an {InvalidQueryTargetError} if the target isn't a valid CSDL target for query filters. Will
15
+ # be called from the base class when given a :condition node with a :target node.
16
+ #
17
+ # @example
18
+ # CSDL::QueryFilterProcessorProcessor.new.validate_target!("fake") # => raises InvalidQueryTargetError
19
+ #
20
+ # @param target_key [String] The target to validate.
21
+ #
22
+ # @return [void]
23
+ #
24
+ # @raise [InvalidQueryTargetError] When the terminator value is not a valid query filter target. See {CSDL.query_target?}.
25
+ #
26
+ # @see Processor#on_condition
27
+ # @see Processor#on_target
28
+ #
4
29
  def validate_target!(target_key)
5
30
  unless ::CSDL.query_target?(target_key)
6
31
  fail ::CSDL::InvalidQueryTargetError, "Query filters cannot use target '#{target_key}'"
data/lib/csdl/targets.rb CHANGED
@@ -1,8 +1,21 @@
1
1
  module CSDL
2
2
 
3
+ # A CSDL Target definition with indication as to where the target can be used.
4
+ #
5
+ # @attr name [String] The name of the target.
6
+ # @attr interaction? [Boolean] True if the target is availble for use in an Interaction Filter.
7
+ # @attr analysis? [Boolean] True if the target is availble for use as an Analysis Target.
8
+ # @attr query? [Boolean] True if the target is availble for use in a Query Filter.
9
+ #
10
+ # @see TARGETS
11
+ #
3
12
  Target = Struct.new(:name, :interaction?, :analysis?, :query?)
4
13
 
5
- raw_targets = [
14
+ # A raw array of targets with their usage flags.
15
+ #
16
+ # @return [Array<String, Boolean, Boolean, Boolean>] Array of targets used to produce {TARGETS} hash.
17
+ #
18
+ RAW_TARGETS = [
6
19
 
7
20
  [ "fb.author.age" , true , true , true ] ,
8
21
  [ "fb.author.country" , true , true , true ] ,
@@ -67,7 +80,11 @@ module CSDL
67
80
  [ "links.url" , true , true , true ]
68
81
  ]
69
82
 
70
- TARGETS = raw_targets.reduce({}) do |accumulator, (target_name, interaction, analysis, query)|
83
+ # All possible targets.
84
+ #
85
+ # @return [Hash<String, Target>] Hash of {Target} structs, keyed by the string name of the target.
86
+ #
87
+ TARGETS = RAW_TARGETS.reduce({}) do |accumulator, (target_name, interaction, analysis, query)|
71
88
  accumulator[target_name] = Target.new(target_name, interaction, analysis, query)
72
89
  accumulator
73
90
  end.freeze
@@ -76,18 +93,58 @@ module CSDL
76
93
  ANALYSIS_TARGETS = TARGETS.select { |_, target| target.analysis? }
77
94
  QUERY_TARGETS = TARGETS.select { |_, target| target.query? }
78
95
 
96
+ # Check if the given target is a valid target.
97
+ #
98
+ # @example
99
+ # CSDL.target?("fake") # => false
100
+ # CSDL.target?("fb.content") # => true
101
+ #
102
+ # @param target_name [String] The name of the target.
103
+ #
104
+ # @return [Boolean] Whether or not the value is a valid CSDL Target.
105
+ #
79
106
  def self.target?(target_name)
80
107
  TARGETS.key?(target_name)
81
108
  end
82
109
 
110
+ # Check if the given target is a valid interaction target.
111
+ #
112
+ # @example
113
+ # CSDL.interaction_target?("interaction.tags") # => false
114
+ # CSDL.interaction_target?("interaction.content") # => true
115
+ #
116
+ # @param target_name [String] The name of the target.
117
+ #
118
+ # @return [Boolean] Whether or not the value is a valid CSDL Interaction Filter Target.
119
+ #
83
120
  def self.interaction_target?(target_name)
84
121
  INTERACTION_TARGETS.key?(target_name)
85
122
  end
86
123
 
124
+ # Check if the given target is a valid analysis target.
125
+ #
126
+ # @example
127
+ # CSDL.analysis_target?("interaction.content") # => false
128
+ # CSDL.analysis_target?("interaction.tags") # => true
129
+ #
130
+ # @param target_name [String] The name of the target.
131
+ #
132
+ # @return [Boolean] Whether or not the value is a valid CSDL Analysis Target.
133
+ #
87
134
  def self.analysis_target?(target_name)
88
135
  ANALYSIS_TARGETS.key?(target_name)
89
136
  end
90
137
 
138
+ # Check if the given target is a valid query target.
139
+ #
140
+ # @example
141
+ # CSDL.query_target?("fb.topics.website") # => false
142
+ # CSDL.query_target?("fb.topic_ids") # => true
143
+ #
144
+ # @param target_name [String] The name of the target.
145
+ #
146
+ # @return [Boolean] Whether or not the value is a valid CSDL Query Filter Target.
147
+ #
91
148
  def self.query_target?(target_name)
92
149
  QUERY_TARGETS.key?(target_name)
93
150
  end
data/lib/csdl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
- module Csdl
2
- VERSION = "0.1.0"
1
+ module CSDL
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: csdl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BJ Neilsen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-16 00:00:00.000000000 Z
11
+ date: 2015-06-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ast
@@ -66,7 +66,24 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- description: AST Processor and Query Builder for DataSift's CSDL language
69
+ - !ruby/object:Gem::Dependency
70
+ name: yard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: |2
84
+
85
+ CSDL is a gem for producing Abstract Syntax Trees for the [DataSift CSDL Filter Language](http://dev.datasift.com/docs/csdl).
86
+ Working with an AST instead of raw strings provides a simpler way to test and validate any given CSDL filter.
70
87
  email:
71
88
  - bj.neilsen@gmail.com
72
89
  executables: []
@@ -89,7 +106,7 @@ files:
89
106
  - lib/csdl/query_filter_processor.rb
90
107
  - lib/csdl/targets.rb
91
108
  - lib/csdl/version.rb
92
- homepage: https://localshred.github.io
109
+ homepage: https://github.com/localshred/csdl
93
110
  licenses: []
94
111
  metadata: {}
95
112
  post_install_message:
@@ -113,3 +130,4 @@ signing_key:
113
130
  specification_version: 4
114
131
  summary: AST Processor and Query Builder for DataSift's CSDL language
115
132
  test_files: []
133
+ has_rdoc: