csdl 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: