cql 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/cql.rb +86 -34
- data/lib/cql/dsl.rb +152 -0
- data/lib/cql/feature_filters.rb +14 -0
- data/lib/cql/filters.rb +74 -0
- data/lib/cql/map_reduce.rb +48 -0
- data/lib/cql/sso_filters.rb +26 -0
- data/lib/cql/version.rb +3 -0
- data/spec/dsl_spec.rb +51 -0
- data/spec/filter_example_spec.rb +65 -0
- data/spec/filter_feature_dsl_spec.rb +13 -12
- data/spec/filter_sso_spec.rb +54 -14
- data/spec/line_count_filterable_specs.rb +4 -4
- data/spec/map_reduce_spec.rb +13 -11
- data/spec/multiple_queries_spec.rb +5 -9
- data/spec/name_filterable_specs.rb +1 -1
- data/spec/repository_spec.rb +17 -0
- data/spec/select_feature_dsl_spec.rb +28 -106
- data/spec/select_scen_outline_dsl_spec.rb +75 -166
- data/spec/select_scenario_dsl_spec.rb +37 -72
- data/spec/spec_helper.rb +4 -1
- data/spec/tag_filterable_specs.rb +5 -5
- metadata +79 -12
- checksums.yaml +0 -15
- data/lib/dsl.rb +0 -111
- data/lib/feature_filters.rb +0 -89
- data/lib/map_reduce.rb +0 -104
- data/lib/sso_filters.rb +0 -82
data/lib/cql.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
if RUBY_VERSION < '1.9.2'
|
2
|
+
require 'backports/1.9.2/array/rotate'
|
3
|
+
end
|
4
|
+
|
1
5
|
require 'cuke_modeler'
|
2
|
-
require
|
6
|
+
require 'cql/dsl'
|
3
7
|
|
4
8
|
module CQL
|
5
9
|
|
@@ -7,65 +11,113 @@ module CQL
|
|
7
11
|
include Dsl
|
8
12
|
attr_reader :data, :what
|
9
13
|
|
10
|
-
def
|
11
|
-
|
14
|
+
def format_data data
|
15
|
+
Array.new.tap do |result_array|
|
16
|
+
data.each do |element|
|
17
|
+
result_array << Hash.new.tap do |result|
|
18
|
+
@what.each_with_index do |attribute, index|
|
19
|
+
key = determine_key(attribute, index)
|
20
|
+
value = determine_value(element, attribute, index)
|
12
21
|
|
13
|
-
|
14
|
-
|
15
|
-
if e.class.to_s =~ /CukeModeler/
|
16
|
-
result[i][w]=e.raw_element
|
17
|
-
else
|
18
|
-
result[i][w]=e
|
22
|
+
result[key] = value
|
23
|
+
end
|
19
24
|
end
|
20
25
|
end
|
21
26
|
end
|
22
27
|
|
23
|
-
result
|
24
28
|
end
|
25
29
|
|
26
|
-
def initialize
|
27
|
-
|
28
|
-
@data =
|
30
|
+
def initialize(directory, &block)
|
31
|
+
# Set root object
|
32
|
+
@data = directory
|
29
33
|
|
30
|
-
#
|
31
|
-
|
34
|
+
# Populate configurables from DSL block
|
35
|
+
self.instance_eval(&block)
|
32
36
|
|
33
|
-
|
37
|
+
# Gather relevant objects from root object and filters
|
38
|
+
@data= CQL::MapReduce.gather_objects(@data, @from, @filters)
|
39
|
+
|
40
|
+
# Extract properties from gathered objects
|
41
|
+
@data= format_output(@data)
|
34
42
|
end
|
35
|
-
end
|
36
43
|
|
37
44
|
|
38
|
-
|
39
|
-
attr_reader :parsed_feature_files
|
45
|
+
private
|
40
46
|
|
41
|
-
|
42
|
-
|
47
|
+
|
48
|
+
def format_output(data)
|
49
|
+
format_data(data)
|
43
50
|
end
|
44
51
|
|
45
|
-
def
|
46
|
-
|
52
|
+
def determine_key(attribute, index)
|
53
|
+
key = transform_stuff(@name_transforms, attribute, index) if @name_transforms
|
47
54
|
|
48
|
-
|
55
|
+
key || attribute
|
49
56
|
end
|
50
57
|
|
58
|
+
def determine_value(element, attribute, index)
|
59
|
+
original_value = attribute.is_a?(Symbol) ? special_value(element, attribute) : element.send(attribute)
|
51
60
|
|
52
|
-
|
61
|
+
if @value_transforms
|
62
|
+
value = transform_stuff(@value_transforms, attribute, index)
|
63
|
+
value = value.call(original_value) if value.is_a?(Proc)
|
64
|
+
end
|
53
65
|
|
66
|
+
value || original_value
|
67
|
+
end
|
54
68
|
|
55
|
-
def
|
56
|
-
|
69
|
+
def special_value(element, attribute)
|
70
|
+
# todo - Not sure what other special values to have but this could be expanded upon later.
|
71
|
+
case attribute
|
72
|
+
when :self
|
73
|
+
val = element
|
74
|
+
else
|
75
|
+
# todo - error message?
|
76
|
+
end
|
77
|
+
|
78
|
+
val
|
57
79
|
end
|
58
80
|
|
59
|
-
|
60
|
-
|
61
|
-
|
81
|
+
def transform_stuff(transforms, attribute, location)
|
82
|
+
case
|
83
|
+
when transforms.is_a?(Array)
|
84
|
+
value = transforms[location]
|
85
|
+
when transforms.is_a?(Hash)
|
86
|
+
if transforms[attribute]
|
87
|
+
value = transforms[attribute].first
|
88
|
+
transforms[attribute].rotate!
|
89
|
+
end
|
90
|
+
else
|
91
|
+
# todo - add error message
|
92
|
+
end
|
62
93
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
94
|
+
value
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
class Repository
|
101
|
+
|
102
|
+
def initialize(repository_root)
|
103
|
+
case
|
104
|
+
when repository_root.is_a?(String)
|
105
|
+
@target_directory = CukeModeler::Directory.new(repository_root)
|
106
|
+
when repository_root.is_a?(Class)
|
107
|
+
# todo - stop assuming
|
108
|
+
# Assume valid CukeModeler class for now
|
109
|
+
@target_directory = repository_root
|
110
|
+
else
|
111
|
+
# todo - raise error?
|
67
112
|
end
|
68
113
|
end
|
69
114
|
|
115
|
+
def query &block
|
116
|
+
# A quick 'deep clone'
|
117
|
+
new_repo = Marshal::load(Marshal.dump(@target_directory))
|
118
|
+
|
119
|
+
Query.new(new_repo, &block).data
|
120
|
+
end
|
121
|
+
|
70
122
|
end
|
71
123
|
end
|
data/lib/cql/dsl.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'cql/map_reduce'
|
2
|
+
|
3
|
+
module CQL
|
4
|
+
module Dsl
|
5
|
+
|
6
|
+
def method_missing(method_name)
|
7
|
+
method_name.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def transform(*attribute_transforms, &block)
|
11
|
+
# todo - Still feels like some as/transform code duplication but I think that it would get too meta if I
|
12
|
+
# reduced it any further. Perhaps change how the transforms are handled so that there doesn't have to be
|
13
|
+
# an array/hash difference in the first place?
|
14
|
+
prep_variable('value_transforms', attribute_transforms) unless @value_transforms
|
15
|
+
|
16
|
+
# todo - what if they pass in a hash transform and a block?
|
17
|
+
attribute_transforms << block if block
|
18
|
+
add_transforms(attribute_transforms, @value_transforms)
|
19
|
+
end
|
20
|
+
|
21
|
+
def as(*name_transforms)
|
22
|
+
prep_variable('name_transforms', name_transforms) unless @name_transforms
|
23
|
+
|
24
|
+
add_transforms(name_transforms, @name_transforms)
|
25
|
+
end
|
26
|
+
|
27
|
+
#Select clause
|
28
|
+
def select *what
|
29
|
+
@what ||= []
|
30
|
+
@what.concat(what)
|
31
|
+
end
|
32
|
+
|
33
|
+
def name *args
|
34
|
+
return 'name' if args.size == 0
|
35
|
+
CQL::NameFilter.new args[0]
|
36
|
+
end
|
37
|
+
|
38
|
+
def line *args
|
39
|
+
return 'line' if args.size == 0
|
40
|
+
CQL::LineFilter.new args.first
|
41
|
+
end
|
42
|
+
|
43
|
+
#from clause
|
44
|
+
def from(*targets)
|
45
|
+
@from ||= []
|
46
|
+
|
47
|
+
# todo - todo test the intermixing of shorthand and full classes as arguments
|
48
|
+
targets.map! { |target| target.is_a?(String) ? determine_class(target) : target }
|
49
|
+
|
50
|
+
@from.concat(targets)
|
51
|
+
end
|
52
|
+
|
53
|
+
#with clause
|
54
|
+
def with(*conditions, &block)
|
55
|
+
@filters ||= []
|
56
|
+
|
57
|
+
@filters << block if block
|
58
|
+
@filters.concat(conditions)
|
59
|
+
end
|
60
|
+
|
61
|
+
class Comparison
|
62
|
+
attr_accessor :operator, :amount
|
63
|
+
|
64
|
+
def initialize operator, amount
|
65
|
+
@operator = operator
|
66
|
+
@amount = amount
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def tc comparison
|
72
|
+
TagCountFilter.new 'tc', comparison
|
73
|
+
end
|
74
|
+
|
75
|
+
def lc comparison
|
76
|
+
CQL::SsoLineCountFilter.new('lc', comparison)
|
77
|
+
end
|
78
|
+
|
79
|
+
def ssoc comparison
|
80
|
+
TestCountFilter.new([CukeModeler::Scenario, CukeModeler::Outline], comparison)
|
81
|
+
end
|
82
|
+
|
83
|
+
def sc comparison
|
84
|
+
TestCountFilter.new([CukeModeler::Scenario], comparison)
|
85
|
+
end
|
86
|
+
|
87
|
+
def soc comparison
|
88
|
+
TestCountFilter.new([CukeModeler::Outline], comparison)
|
89
|
+
end
|
90
|
+
|
91
|
+
def gt amount
|
92
|
+
Comparison.new '>', amount
|
93
|
+
end
|
94
|
+
|
95
|
+
def gte amount
|
96
|
+
Comparison.new '>=', amount
|
97
|
+
end
|
98
|
+
|
99
|
+
def lt amount
|
100
|
+
Comparison.new '<', amount
|
101
|
+
end
|
102
|
+
|
103
|
+
def lte amount
|
104
|
+
Comparison.new '<=', amount
|
105
|
+
end
|
106
|
+
|
107
|
+
def tags *tags
|
108
|
+
return "tags" if tags.size == 0
|
109
|
+
|
110
|
+
TagFilter.new tags
|
111
|
+
end
|
112
|
+
|
113
|
+
def translate_shorthand(where)
|
114
|
+
where.split('_').map(&:capitalize).join
|
115
|
+
end
|
116
|
+
|
117
|
+
def prep_variable(var_name, transforms)
|
118
|
+
starting_value = transforms.first.is_a?(Hash) ? {} : []
|
119
|
+
instance_variable_set("@#{var_name}".to_sym, starting_value)
|
120
|
+
end
|
121
|
+
|
122
|
+
def add_transforms(new_transforms, transform_set)
|
123
|
+
# todo - accept either array or a hash
|
124
|
+
if new_transforms.first.is_a?(Hash)
|
125
|
+
additional_transforms = new_transforms.first
|
126
|
+
|
127
|
+
additional_transforms.each do |key, value|
|
128
|
+
if transform_set.has_key?(key)
|
129
|
+
transform_set[key] << value
|
130
|
+
else
|
131
|
+
transform_set[key] = [value]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
else
|
135
|
+
|
136
|
+
transform_set.concat(new_transforms)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def determine_class(where)
|
141
|
+
# Translate shorthand Strings to final class
|
142
|
+
where = translate_shorthand(where)
|
143
|
+
|
144
|
+
# Check for exact class match first because it should take precedence
|
145
|
+
return CukeModeler.const_get(where) if CukeModeler.const_defined?(where)
|
146
|
+
|
147
|
+
# Check for pluralization of class match (i.e. remove the final 's')
|
148
|
+
return CukeModeler.const_get(where.chop) if CukeModeler.const_defined?(where.chop)
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
data/lib/cql/filters.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module CQL
|
2
|
+
|
3
|
+
class TagFilter
|
4
|
+
attr_reader :tags
|
5
|
+
|
6
|
+
def initialize tags
|
7
|
+
@tags = tags
|
8
|
+
end
|
9
|
+
|
10
|
+
def has_tags?(object, tags)
|
11
|
+
tags.all? { |tag| object.tags.include?(tag) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute objects
|
15
|
+
objects.find_all { |object| has_tags?(object, tags) }
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class ContentMatchFilter
|
21
|
+
attr_reader :pattern
|
22
|
+
|
23
|
+
def initialize(pattern)
|
24
|
+
@pattern = pattern
|
25
|
+
end
|
26
|
+
|
27
|
+
def content_match?(content)
|
28
|
+
if pattern.is_a?(String)
|
29
|
+
content.any? { |thing| thing == pattern }
|
30
|
+
elsif pattern.is_a?(Regexp)
|
31
|
+
content.any? { |thing| thing =~ pattern }
|
32
|
+
else
|
33
|
+
# todo - Raise exception?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
class TypeCountFilter
|
41
|
+
attr_reader :types, :comparison
|
42
|
+
|
43
|
+
def initialize types, comparison
|
44
|
+
@types = types
|
45
|
+
@comparison = comparison
|
46
|
+
end
|
47
|
+
|
48
|
+
def execute input
|
49
|
+
input.find_all do |object|
|
50
|
+
type_count(object).send(comparison.operator, comparison.amount)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
class NameFilter < ContentMatchFilter
|
57
|
+
|
58
|
+
def execute(input)
|
59
|
+
input.find_all do |object|
|
60
|
+
content_match?([object.name])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
class TagCountFilter < TypeCountFilter
|
67
|
+
|
68
|
+
def type_count(test)
|
69
|
+
test.tags.size
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'cql/dsl'
|
2
|
+
require 'cql/feature_filters'
|
3
|
+
require 'cql/sso_filters'
|
4
|
+
|
5
|
+
|
6
|
+
module CQL
|
7
|
+
|
8
|
+
class MapReduce
|
9
|
+
|
10
|
+
def self.gather_objects(current_object, target_classes, filters)
|
11
|
+
gathered_objects = Array.new.tap { |gathered_objects| collect_all_in(target_classes, current_object, gathered_objects) }
|
12
|
+
|
13
|
+
if filters
|
14
|
+
filters.each do |filter|
|
15
|
+
if filter.is_a?(Proc)
|
16
|
+
gathered_objects.select!(&filter)
|
17
|
+
else
|
18
|
+
gathered_objects = filter.execute(gathered_objects)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
gathered_objects
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
class << self
|
28
|
+
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
|
33
|
+
# Recursively gathers all objects of the given class(es) found in the passed object (including itself).
|
34
|
+
def collect_all_in(targeted_classes, current_object, accumulated_objects)
|
35
|
+
accumulated_objects << current_object if targeted_classes.any? { |targeted_class| current_object.is_a?(targeted_class) }
|
36
|
+
|
37
|
+
if current_object.respond_to?(:contains)
|
38
|
+
current_object.contains.each do |child_object|
|
39
|
+
collect_all_in(targeted_classes, child_object, accumulated_objects)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|