lib/cql/dsl.rb
module CQL
# The Domain Specific Language used for performing queries.
module Dsl
# Any undefined method is assumed to mean its String equivalent, thus allowing a more convenient query syntax.
def method_missing(method_name) # rubocop:disable Style/MethodMissing - All it does is make a string. No real risk.
method_name.to_s
end
# Adds a *transform* clause to the query. See the corresponding Cucumber documentation for details.
def transform(*attribute_transforms, &block)
# TODO: Still feels like some as/transform code duplication but I think that it would get too meta if I
# reduced it any further. Perhaps change how the transforms are handled so that there doesn't have to be
# an array/hash difference in the first place?
prep_variable('value_transforms', attribute_transforms) unless @value_transforms
# TODO: what if they pass in a hash transform and a block?
attribute_transforms << block if block
add_transforms(attribute_transforms, @value_transforms)
end
# Adds an *as* clause to the query. See the corresponding Cucumber documentation for details.
def as(*name_transforms)
prep_variable('name_transforms', name_transforms) unless @name_transforms
add_transforms(name_transforms, @name_transforms)
end
# Adds a *select* clause to the query. See the corresponding Cucumber documentation for details.
def select(*what)
what = [:self] if what.empty?
@what ||= []
@what.concat(what)
end
# Adds a *name* filter to the query. See the corresponding Cucumber documentation for details.
def name(*args)
return 'name' if args.empty?
CQL::NameFilter.new args[0]
end
# Adds a *line* filter to the query. See the corresponding Cucumber documentation for details.
def line(*args)
return 'line' if args.empty?
CQL::LineFilter.new args.first
end
# Adds a *from* clause to the query. See the corresponding Cucumber documentation for details.
def from(*targets)
@from ||= []
targets.map! { |target| target.is_a?(String) ? determine_class(target) : target }
@from.concat(targets)
end
# Adds a *with* clause to the query. See the corresponding Cucumber documentation for details.
def with(*conditions, &block)
@filters ||= []
@filters << { negate: false, filter: block } if block
conditions.each do |condition|
@filters << { negate: false, filter: condition }
end
end
# Adds a *without* clause to the query. See the corresponding Cucumber documentation for details.
def without(*conditions, &block)
@filters ||= []
@filters << { negate: true, filter: block } if block
conditions.each do |condition|
@filters << { negate: true, filter: condition }
end
end
# Not a part of the public API. Subject to change at any time.
class Comparison
attr_accessor :operator, # the operator used for comparison
:amount # value that will be compared against
# Creates a new comparison object
def initialize(operator, amount)
@operator = operator
@amount = amount
end
end
# Adds a *tc* filter to the query. See the corresponding Cucumber documentation for details.
def tc(comparison)
TagCountFilter.new 'tc', comparison
end
# Adds a *lc* filter to the query. See the corresponding Cucumber documentation for details.
def lc(comparison)
CQL::SsoLineCountFilter.new('lc', comparison)
end
# Adds an *ssoc* filter to the query. See the corresponding Cucumber documentation for details.
def ssoc(comparison)
TestCountFilter.new([CukeModeler::Scenario, CukeModeler::Outline], comparison)
end
# Adds an *sc* filter to the query. See the corresponding Cucumber documentation for details.
def sc(comparison)
TestCountFilter.new([CukeModeler::Scenario], comparison)
end
# Adds an *soc* filter to the query. See the corresponding Cucumber documentation for details.
def soc(comparison)
TestCountFilter.new([CukeModeler::Outline], comparison)
end
# Adds a *gt* filter operator to the query. See the corresponding Cucumber documentation for details.
def gt(amount)
Comparison.new '>', amount
end
# Adds a *gte* filter operator to the query. See the corresponding Cucumber documentation for details.
def gte(amount)
Comparison.new '>=', amount
end
# Adds an *lt* filter operator to the query. See the corresponding Cucumber documentation for details.
def lt(amount)
Comparison.new '<', amount
end
# Adds an *lte* filter operator to the query. See the corresponding Cucumber documentation for details.
def lte(amount)
Comparison.new '<=', amount
end
# Adds an *eq* filter operator to the query. See the corresponding Cucumber documentation for details.
def eq(amount)
Comparison.new '==', amount
end
# Adds a *tags* filter to the query. See the corresponding Cucumber documentation for details.
def tags(*tags)
return 'tags' if tags.empty?
TagFilter.new tags
end
private
def translate_shorthand(where)
where.split('_').map(&:capitalize).join
end
def prep_variable(var_name, transforms)
starting_value = transforms.first.is_a?(Hash) ? {} : []
instance_variable_set("@#{var_name}".to_sym, starting_value)
end
def add_transforms(new_transforms, transform_set)
# TODO: accept either array or a hash
if new_transforms.first.is_a?(Hash)
additional_transforms = new_transforms.first
additional_transforms.each do |key, value|
if transform_set.key?(key)
transform_set[key] << value
else
transform_set[key] = [value]
end
end
else
transform_set.concat(new_transforms)
end
end
def determine_class(where)
# Translate shorthand Strings to final class
where = translate_shorthand(where)
# Check for exact class match first because it should take precedence
return CukeModeler.const_get(where) if CukeModeler.const_defined?(where)
# Check for pluralization of class match (i.e. remove the final 's')
return CukeModeler.const_get(where.chop) if CukeModeler.const_defined?(where.chop)
# Then the class must not be a CukeModeler class
raise(ArgumentError, "Class 'CukeModeler::#{where}' does not exist")
end
end
end