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