cql 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 File.dirname(__FILE__) + "/dsl"
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 format_to_ary_of_hsh data
11
- result = Array.new(data.size).map { |e| {} }
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
- @what.each do |w|
14
- CQL::MapReduce.send(w, data).each_with_index do |e, i|
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 features, &block
27
- @data = features
28
- @data = self.instance_eval(&block)
30
+ def initialize(directory, &block)
31
+ # Set root object
32
+ @data = directory
29
33
 
30
- #getting the children of features
31
- @data= CQL::MapReduce.feature_children(@data, 'what'=>@from[0, @from.size-1]) if @from != "features"
34
+ # Populate configurables from DSL block
35
+ self.instance_eval(&block)
32
36
 
33
- @data= format_to_ary_of_hsh(@data)
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
- class Repository
39
- attr_reader :parsed_feature_files
45
+ private
40
46
 
41
- def initialize features_home_dir
42
- @parsed_feature_files = collect_feature_models(CukeModeler::Directory.new(features_home_dir))
47
+
48
+ def format_output(data)
49
+ format_data(data)
43
50
  end
44
51
 
45
- def query &block
46
- new_repo = Marshal::load(Marshal.dump(parsed_feature_files))
52
+ def determine_key(attribute, index)
53
+ key = transform_stuff(@name_transforms, attribute, index) if @name_transforms
47
54
 
48
- Query.new(new_repo, &block).data
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
- private
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 collect_feature_models(directory_model)
56
- Array.new.tap { |accumulated_features| collect_all_in(:features, directory_model, accumulated_features) }
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
- # Recursively gathers all things of the given type found in the passed container.
60
- def collect_all_in(type_of_thing, container, accumulated_things)
61
- accumulated_things.concat container.send(type_of_thing) if container.respond_to?(type_of_thing)
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
- if container.respond_to?(:contains)
64
- container.contains.each do |child_container|
65
- collect_all_in(type_of_thing, child_container, accumulated_things)
66
- end
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
@@ -0,0 +1,14 @@
1
+ require 'cql/filters'
2
+
3
+
4
+ module CQL
5
+
6
+ class TestCountFilter < TypeCountFilter
7
+
8
+ def type_count(feature)
9
+ feature.tests.find_all { |test| types.include?(test.class) }.size
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -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