cuke_sniffer 0.0.3 → 0.0.5
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/bin/{cuke_sniffer.rb → cuke_sniffer} +4 -1
 - data/lib/cuke_sniffer.rb +16 -15
 - data/lib/cuke_sniffer/cli.rb +319 -110
 - data/lib/cuke_sniffer/constants.rb +34 -22
 - data/lib/cuke_sniffer/feature.rb +77 -22
 - data/lib/cuke_sniffer/feature_rules_evaluator.rb +115 -69
 - data/lib/cuke_sniffer/hook.rb +160 -0
 - data/lib/cuke_sniffer/report/markup.rhtml +266 -308
 - data/lib/cuke_sniffer/rule_config.rb +192 -94
 - data/lib/cuke_sniffer/rules_evaluator.rb +70 -53
 - data/lib/cuke_sniffer/scenario.rb +238 -178
 - data/lib/cuke_sniffer/step_definition.rb +239 -202
 - metadata +6 -5
 
| 
         @@ -1,22 +1,34 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module CukeSniffer
         
     | 
| 
       2 
     | 
    
         
            -
               
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
                 
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
       12 
     | 
    
         
            -
                 
     | 
| 
       13 
     | 
    
         
            -
                 
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
                 
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
                 
     | 
| 
       21 
     | 
    
         
            -
               
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
      
 1 
     | 
    
         
            +
            module CukeSniffer
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Author::    Robert Cochran  (mailto:cochrarj@miamioh.edu)
         
     | 
| 
      
 3 
     | 
    
         
            +
              # Copyright:: Copyright (C) 2013 Robert Cochran
         
     | 
| 
      
 4 
     | 
    
         
            +
              # License::   Distributes under the MIT License
         
     | 
| 
      
 5 
     | 
    
         
            +
              # A collection of constants that are used throughout the gem
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Constants
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                FILE_IGNORE_LIST = %w(. .. .svn) # :nodoc:
         
     | 
| 
      
 9 
     | 
    
         
            +
                DATE_REGEX = /(?<date>\d{2}\/\d{2}\/\d{4})/ # :nodoc:
         
     | 
| 
      
 10 
     | 
    
         
            +
                COMMENT_REGEX = /#?\s*/ # :nodoc:
         
     | 
| 
      
 11 
     | 
    
         
            +
                TAG_REGEX = /(^|\s+)(?<tag>@\S*)/ # :nodoc:
         
     | 
| 
      
 12 
     | 
    
         
            +
                SCENARIO_TITLE_STYLES = /^\s*\#*\s*(?<type>Background|Scenario|Scenario Outline|Scenario Template):\s*/ # :nodoc:
         
     | 
| 
      
 13 
     | 
    
         
            +
                STEP_STYLES = /(?<style>Given|When|Then|And|Or|But|Transform|\*)\s+/ # :nodoc:
         
     | 
| 
      
 14 
     | 
    
         
            +
                STEP_REGEX = /^#{COMMENT_REGEX}#{STEP_STYLES}(?<step_string>.*)/ # :nodoc:
         
     | 
| 
      
 15 
     | 
    
         
            +
                STEP_DEFINITION_REGEX = /^#{STEP_STYLES}\/(?<step>.+)\/\sdo\s?(\|(?<parameters>.*)\|)?$/ # :nodoc:
         
     | 
| 
      
 16 
     | 
    
         
            +
                HOOK_STYLES = /(?<type>Before|After|AfterConfiguration|at_exit|Around|AfterStep)/ # :nodoc:
         
     | 
| 
      
 17 
     | 
    
         
            +
                HOOK_REGEX = /^#{HOOK_STYLES}(\((?<tags>.*)\)\sdo|\s+do)(\s\|(?<parameters>.*)\|)?/
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                # hash: Stores scores to compare against for determining if an object is good
         
     | 
| 
      
 20 
     | 
    
         
            +
                # * Key: String of the object name
         
     | 
| 
      
 21 
     | 
    
         
            +
                #  Project, Feature, Scenario, StepDefinition
         
     | 
| 
      
 22 
     | 
    
         
            +
                # * Value: Integer of the highest acceptable value an object can have
         
     | 
| 
      
 23 
     | 
    
         
            +
                # Customizable for a projects level of acceptable score
         
     | 
| 
      
 24 
     | 
    
         
            +
                #  CukeSniffer::Constants::THRESHOLDS["Project"] = 95000
         
     | 
| 
      
 25 
     | 
    
         
            +
                THRESHOLDS = {
         
     | 
| 
      
 26 
     | 
    
         
            +
                    "Project" => 1000,
         
     | 
| 
      
 27 
     | 
    
         
            +
                    "Feature" => 30,
         
     | 
| 
      
 28 
     | 
    
         
            +
                    "Scenario" => 30,
         
     | 
| 
      
 29 
     | 
    
         
            +
                    "StepDefinition" => 20,
         
     | 
| 
      
 30 
     | 
    
         
            +
                    "Hook" => 20
         
     | 
| 
      
 31 
     | 
    
         
            +
                }
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/cuke_sniffer/feature.rb
    CHANGED
    
    | 
         @@ -1,30 +1,52 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module CukeSniffer
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              # Author::    Robert Cochran  (mailto:cochrarj@miamioh.edu)
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Copyright:: Copyright (C) 2013 Robert Cochran
         
     | 
| 
      
 5 
     | 
    
         
            +
              # License::   Distributes under the MIT License
         
     | 
| 
      
 6 
     | 
    
         
            +
              # Handles feature files and disassembles and evaluates
         
     | 
| 
      
 7 
     | 
    
         
            +
              # its components.
         
     | 
| 
      
 8 
     | 
    
         
            +
              # Extends CukeSniffer::FeatureRulesEvaluator
         
     | 
| 
       2 
9 
     | 
    
         
             
              class Feature < FeatureRulesEvaluator
         
     | 
| 
       3 
     | 
    
         
            -
                include CukeSniffer::Constants
         
     | 
| 
       4 
     | 
    
         
            -
                include CukeSniffer::RuleConfig
         
     | 
| 
       5 
     | 
    
         
            -
                include ROXML
         
     | 
| 
       6 
10 
     | 
    
         | 
| 
       7 
11 
     | 
    
         
             
                xml_accessor :scenarios, :as => [CukeSniffer::FeatureRulesEvaluator], :in => "scenarios"
         
     | 
| 
       8 
12 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
                SCENARIO_TITLE_REGEX = /#{COMMENT_REGEX}#{SCENARIO_TITLE_STYLES}(?<name>.*)/
         
     | 
| 
      
 13 
     | 
    
         
            +
                SCENARIO_TITLE_REGEX = /#{COMMENT_REGEX}#{SCENARIO_TITLE_STYLES}(?<name>.*)/ # :nodoc:
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                # Scenario: The background of a Feature, created as a Scenario object
         
     | 
| 
      
 16 
     | 
    
         
            +
                attr_accessor :background
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # Scenario array: A list of all scenarios contained in a feature file
         
     | 
| 
      
 19 
     | 
    
         
            +
                attr_accessor :scenarios
         
     | 
| 
       10 
20 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
      
 21 
     | 
    
         
            +
                # int: Total score from all of the scenarios contained in the feature
         
     | 
| 
      
 22 
     | 
    
         
            +
                attr_accessor :scenarios_score
         
     | 
| 
       12 
23 
     | 
    
         | 
| 
      
 24 
     | 
    
         
            +
                # int: Total score of the feature and its scenarios
         
     | 
| 
      
 25 
     | 
    
         
            +
                attr_accessor :total_score
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                # file_name must be in the format of "file_path\file_name.feature"
         
     | 
| 
       13 
28 
     | 
    
         
             
                def initialize(file_name)
         
     | 
| 
       14 
29 
     | 
    
         
             
                  super(file_name)
         
     | 
| 
       15 
30 
     | 
    
         
             
                  @scenarios = []
         
     | 
| 
       16 
     | 
    
         
            -
                  @RULES_hash = {}
         
     | 
| 
       17 
31 
     | 
    
         
             
                  @scenarios_score = 0
         
     | 
| 
       18 
32 
     | 
    
         
             
                  @total_score = 0
         
     | 
| 
       19 
33 
     | 
    
         
             
                  feature_lines = extract_feature_from_file(file_name)
         
     | 
| 
       20 
34 
     | 
    
         
             
                  if feature_lines == []
         
     | 
| 
       21 
     | 
    
         
            -
                     
     | 
| 
      
 35 
     | 
    
         
            +
                    rule = RULES[:empty_feature]
         
     | 
| 
      
 36 
     | 
    
         
            +
                    store_rule(rule)
         
     | 
| 
       22 
37 
     | 
    
         
             
                  else
         
     | 
| 
       23 
38 
     | 
    
         
             
                    split_feature(file_name, feature_lines)
         
     | 
| 
       24 
39 
     | 
    
         
             
                    evaluate_score
         
     | 
| 
       25 
40 
     | 
    
         
             
                  end
         
     | 
| 
       26 
41 
     | 
    
         
             
                end
         
     | 
| 
       27 
42 
     | 
    
         | 
| 
      
 43 
     | 
    
         
            +
                def ==(comparison_object) # :nodoc:
         
     | 
| 
      
 44 
     | 
    
         
            +
                  super(comparison_object) &&
         
     | 
| 
      
 45 
     | 
    
         
            +
                      comparison_object.scenarios == scenarios
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                private
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
       28 
50 
     | 
    
         
             
                def extract_feature_from_file(file_name)
         
     | 
| 
       29 
51 
     | 
    
         
             
                  feature_lines = []
         
     | 
| 
       30 
52 
     | 
    
         
             
                  feature_file = File.open(file_name)
         
     | 
| 
         @@ -49,10 +71,20 @@ module CukeSniffer 
     | 
|
| 
       49 
71 
     | 
    
         
             
                  index_of_title = nil
         
     | 
| 
       50 
72 
     | 
    
         
             
                  code_block = []
         
     | 
| 
       51 
73 
     | 
    
         
             
                  until index >= feature_lines.length
         
     | 
| 
       52 
     | 
    
         
            -
                    if scenario_title_found and  
     | 
| 
       53 
     | 
    
         
            -
                       
     | 
| 
      
 74 
     | 
    
         
            +
                    if scenario_title_found and feature_lines[index].match SCENARIO_TITLE_REGEX
         
     | 
| 
      
 75 
     | 
    
         
            +
                      not_our_code = []
         
     | 
| 
      
 76 
     | 
    
         
            +
                      code_block.reverse.each do |line|
         
     | 
| 
      
 77 
     | 
    
         
            +
                        break if line =~ /#{SCENARIO_TITLE_STYLES}|#{STEP_STYLES}|^\|.*\||Examples:/
         
     | 
| 
      
 78 
     | 
    
         
            +
                        not_our_code << line
         
     | 
| 
      
 79 
     | 
    
         
            +
                      end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                      if not_our_code.empty?
         
     | 
| 
      
 82 
     | 
    
         
            +
                        add_scenario_to_feature(code_block, index_of_title)
         
     | 
| 
      
 83 
     | 
    
         
            +
                      else
         
     | 
| 
      
 84 
     | 
    
         
            +
                        add_scenario_to_feature(code_block[0...(-1 * not_our_code.length)], index_of_title)
         
     | 
| 
      
 85 
     | 
    
         
            +
                      end
         
     | 
| 
       54 
86 
     | 
    
         
             
                      scenario_title_found = false
         
     | 
| 
       55 
     | 
    
         
            -
                      code_block =  
     | 
| 
      
 87 
     | 
    
         
            +
                      code_block = not_our_code.reverse
         
     | 
| 
       56 
88 
     | 
    
         
             
                    end
         
     | 
| 
       57 
89 
     | 
    
         
             
                    code_block << feature_lines[index].strip
         
     | 
| 
       58 
90 
     | 
    
         
             
                    if feature_lines[index].match SCENARIO_TITLE_REGEX
         
     | 
| 
         @@ -67,6 +99,7 @@ module CukeSniffer 
     | 
|
| 
       67 
99 
     | 
    
         | 
| 
       68 
100 
     | 
    
         
             
                def add_scenario_to_feature(code_block, index_of_title)
         
     | 
| 
       69 
101 
     | 
    
         
             
                  scenario = CukeSniffer::Scenario.new(index_of_title, code_block)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  feature_applied_scenario_rules(scenario)
         
     | 
| 
       70 
103 
     | 
    
         
             
                  if scenario.type == "Background"
         
     | 
| 
       71 
104 
     | 
    
         
             
                    @background = scenario
         
     | 
| 
       72 
105 
     | 
    
         
             
                  else
         
     | 
| 
         @@ -74,30 +107,51 @@ module CukeSniffer 
     | 
|
| 
       74 
107 
     | 
    
         
             
                  end
         
     | 
| 
       75 
108 
     | 
    
         
             
                end
         
     | 
| 
       76 
109 
     | 
    
         | 
| 
       77 
     | 
    
         
            -
                def ==(comparison_object)
         
     | 
| 
       78 
     | 
    
         
            -
                  super(comparison_object)
         
     | 
| 
       79 
     | 
    
         
            -
                  comparison_object.scenarios == scenarios
         
     | 
| 
       80 
     | 
    
         
            -
                end
         
     | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
       82 
110 
     | 
    
         
             
                def evaluate_score
         
     | 
| 
       83 
111 
     | 
    
         
             
                  super
         
     | 
| 
       84 
112 
     | 
    
         
             
                  rule_no_scenarios
         
     | 
| 
       85 
113 
     | 
    
         
             
                  rule_too_many_scenarios
         
     | 
| 
       86 
114 
     | 
    
         
             
                  rule_background_with_no_scenarios
         
     | 
| 
       87 
115 
     | 
    
         
             
                  rule_background_with_one_scenario
         
     | 
| 
      
 116 
     | 
    
         
            +
                  rule_scenario_same_tag
         
     | 
| 
       88 
117 
     | 
    
         
             
                  get_scenarios_score
         
     | 
| 
       89 
118 
     | 
    
         
             
                  @total_score = score + @scenarios_score
         
     | 
| 
       90 
119 
     | 
    
         
             
                end
         
     | 
| 
       91 
120 
     | 
    
         | 
| 
      
 121 
     | 
    
         
            +
                def feature_applied_scenario_rules(scenario)
         
     | 
| 
      
 122 
     | 
    
         
            +
                  rule_feature_same_tags(scenario)
         
     | 
| 
      
 123 
     | 
    
         
            +
                end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                def rule_feature_same_tags(scenario)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  rule = RULES[:feature_same_tag]
         
     | 
| 
      
 127 
     | 
    
         
            +
                  tags.each do |tag|
         
     | 
| 
      
 128 
     | 
    
         
            +
                    scenario.store_rule(rule, rule[:phrase]) if scenario.tags.include?(tag)
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
                end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                def rule_scenario_same_tag
         
     | 
| 
      
 133 
     | 
    
         
            +
                  rule = RULES[:scenario_same_tag]
         
     | 
| 
      
 134 
     | 
    
         
            +
                  unless scenarios.empty?
         
     | 
| 
      
 135 
     | 
    
         
            +
                    base_tag_list = scenarios.first.tags.clone
         
     | 
| 
      
 136 
     | 
    
         
            +
                    scenarios.each do |scenario|
         
     | 
| 
      
 137 
     | 
    
         
            +
                      base_tag_list.each do |tag|
         
     | 
| 
      
 138 
     | 
    
         
            +
                        base_tag_list.delete(tag) unless scenario.tags.include?(tag)
         
     | 
| 
      
 139 
     | 
    
         
            +
                      end
         
     | 
| 
      
 140 
     | 
    
         
            +
                    end
         
     | 
| 
      
 141 
     | 
    
         
            +
                    base_tag_list.count.times do
         
     | 
| 
      
 142 
     | 
    
         
            +
                      store_rule(rule, rule[:phrase])
         
     | 
| 
      
 143 
     | 
    
         
            +
                    end
         
     | 
| 
      
 144 
     | 
    
         
            +
                  end
         
     | 
| 
      
 145 
     | 
    
         
            +
                end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
       92 
147 
     | 
    
         
             
                def get_scenarios_score
         
     | 
| 
       93 
148 
     | 
    
         
             
                  @scenarios_score += @background.score unless @background.nil?
         
     | 
| 
       94 
     | 
    
         
            -
                  @scenarios.each  
     | 
| 
       95 
     | 
    
         
            -
                    @scenarios_score += scenario.score
         
     | 
| 
       96 
     | 
    
         
            -
                  end
         
     | 
| 
      
 149 
     | 
    
         
            +
                  @scenarios.each { |scenario| @scenarios_score += scenario.score }
         
     | 
| 
       97 
150 
     | 
    
         
             
                end
         
     | 
| 
       98 
151 
     | 
    
         | 
| 
       99 
152 
     | 
    
         
             
                def rule_no_scenarios
         
     | 
| 
       100 
     | 
    
         
            -
                   
     | 
| 
      
 153 
     | 
    
         
            +
                  rule = RULES[:no_scenarios]
         
     | 
| 
      
 154 
     | 
    
         
            +
                  store_rule(rule) if @scenarios.empty?
         
     | 
| 
       101 
155 
     | 
    
         
             
                end
         
     | 
| 
       102 
156 
     | 
    
         | 
| 
       103 
157 
     | 
    
         
             
                def rule_too_many_scenarios
         
     | 
| 
         @@ -106,12 +160,13 @@ module CukeSniffer 
     | 
|
| 
       106 
160 
     | 
    
         
             
                end
         
     | 
| 
       107 
161 
     | 
    
         | 
| 
       108 
162 
     | 
    
         
             
                def rule_background_with_no_scenarios
         
     | 
| 
       109 
     | 
    
         
            -
                   
     | 
| 
      
 163 
     | 
    
         
            +
                  rule = RULES[:background_with_no_scenarios]
         
     | 
| 
      
 164 
     | 
    
         
            +
                  store_rule(rule) if @scenarios.empty? and !@background.nil?
         
     | 
| 
       110 
165 
     | 
    
         
             
                end
         
     | 
| 
       111 
166 
     | 
    
         | 
| 
       112 
167 
     | 
    
         
             
                def rule_background_with_one_scenario
         
     | 
| 
       113 
     | 
    
         
            -
                   
     | 
| 
      
 168 
     | 
    
         
            +
                  rule = RULES[:background_with_one_scenario]
         
     | 
| 
      
 169 
     | 
    
         
            +
                  store_rule(rule) if @scenarios.size == 1 and !@background.nil?
         
     | 
| 
       114 
170 
     | 
    
         
             
                end
         
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
       116 
171 
     | 
    
         
             
              end
         
     | 
| 
       117 
172 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,69 +1,115 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require 'cuke_sniffer/constants'
         
     | 
| 
       2 
     | 
    
         
            -
            require 'cuke_sniffer/rule_config'
         
     | 
| 
       3 
     | 
    
         
            -
            require 'cuke_sniffer/rules_evaluator'
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
     | 
    
         
            -
            module CukeSniffer
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
                 
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
                 
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                 
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                   
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                   
     | 
| 
       38 
     | 
    
         
            -
                   
     | 
| 
       39 
     | 
    
         
            -
                   
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
                   
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
                   
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                 
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
                   
     | 
| 
       59 
     | 
    
         
            -
                   
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
             
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
            end
         
     | 
| 
      
 1 
     | 
    
         
            +
            require 'cuke_sniffer/constants'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'cuke_sniffer/rule_config'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'cuke_sniffer/rules_evaluator'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module CukeSniffer
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              # Author::    Robert Cochran  (mailto:cochrarj@miamioh.edu)
         
     | 
| 
      
 8 
     | 
    
         
            +
              # Copyright:: Copyright (C) 2013 Robert Cochran
         
     | 
| 
      
 9 
     | 
    
         
            +
              # License::   Distributes under the MIT License
         
     | 
| 
      
 10 
     | 
    
         
            +
              # Parent class for Feature and Scenario objects
         
     | 
| 
      
 11 
     | 
    
         
            +
              # holds shared attributes and rules.
         
     | 
| 
      
 12 
     | 
    
         
            +
              # Extends CukeSniffer::RulesEvaluator
         
     | 
| 
      
 13 
     | 
    
         
            +
              class FeatureRulesEvaluator < RulesEvaluator
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                # string array: Contains all tags attached to a Feature or Scenario
         
     | 
| 
      
 16 
     | 
    
         
            +
                attr_accessor :tags
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # string: Name of the Feature or Scenario
         
     | 
| 
      
 19 
     | 
    
         
            +
                attr_accessor :name
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                # Location must be in the format of "file_path\file_name.rb:line_number"
         
     | 
| 
      
 22 
     | 
    
         
            +
                def initialize(location)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @name = ""
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @tags = []
         
     | 
| 
      
 25 
     | 
    
         
            +
                  super(location)
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def == (comparison_object) # :nodoc:
         
     | 
| 
      
 29 
     | 
    
         
            +
                  super(comparison_object) &&
         
     | 
| 
      
 30 
     | 
    
         
            +
                  comparison_object.name == name &&
         
     | 
| 
      
 31 
     | 
    
         
            +
                  comparison_object.tags == tags
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                private
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def create_name(line, filter)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  line.gsub!(/#{COMMENT_REGEX}#{filter}/, "")
         
     | 
| 
      
 38 
     | 
    
         
            +
                  line.strip!
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @name += " " unless @name.empty? or line.empty?
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @name += line
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def update_tag_list(line)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  comment_start = (line =~ /([^@\w]#)|(^#)/)
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  if comment_start
         
     | 
| 
      
 47 
     | 
    
         
            +
                    line[0...comment_start].split.each { |single_tag| @tags << single_tag }
         
     | 
| 
      
 48 
     | 
    
         
            +
                    @tags << line[comment_start..line.length].strip
         
     | 
| 
      
 49 
     | 
    
         
            +
                  else
         
     | 
| 
      
 50 
     | 
    
         
            +
                    line.split.each { |single_tag| @tags << single_tag }
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def evaluate_score
         
     | 
| 
      
 55 
     | 
    
         
            +
                  cls_name = self.class.to_s.gsub('CukeSniffer::', '')
         
     | 
| 
      
 56 
     | 
    
         
            +
                  rule_too_many_tags(cls_name)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  rule_no_description(cls_name)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  rule_numbers_in_name(cls_name)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  rule_long_name(cls_name)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  rule_commas_in_description(cls_name)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  rule_comment_after_tag(cls_name)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  rule_commented_tag(cls_name)
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                def rule_too_many_tags(type)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  rule = RULES[:too_many_tags]
         
     | 
| 
      
 67 
     | 
    
         
            +
                  rule_phrase = rule[:phrase].gsub(/{.*}/, type)
         
     | 
| 
      
 68 
     | 
    
         
            +
                  store_rule(rule, rule_phrase) if tags.size >= rule[:max]
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                def rule_no_description(type)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  rule = RULES[:no_description]
         
     | 
| 
      
 73 
     | 
    
         
            +
                  rule_phrase = rule[:phrase].gsub(/{.*}/, type)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  store_rule(rule, rule_phrase) if name.empty?
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                def rule_numbers_in_name(type)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  rule = RULES[:numbers_in_description]
         
     | 
| 
      
 79 
     | 
    
         
            +
                  rule_phrase = rule[:phrase].gsub(/{.*}/, type)
         
     | 
| 
      
 80 
     | 
    
         
            +
                  store_rule(rule, rule_phrase) if name =~ /\d/
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                def rule_long_name(type)
         
     | 
| 
      
 84 
     | 
    
         
            +
                  rule = RULES[:long_name]
         
     | 
| 
      
 85 
     | 
    
         
            +
                  rule_phrase = rule[:phrase].gsub(/{.*}/, type)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  store_rule(rule, rule_phrase) if name.size >= rule[:max]
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                def rule_commas_in_description(type)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  rule = RULES[:commas_in_description]
         
     | 
| 
      
 91 
     | 
    
         
            +
                  rule_phrase = rule[:phrase].gsub(/{.*}/, type)
         
     | 
| 
      
 92 
     | 
    
         
            +
                  store_rule(rule, rule_phrase) if name.include?(',')
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                def rule_comment_after_tag(type)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  rule = RULES[:comment_after_tag]
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                  last_comment_index = tags.rindex { |single_tag| is_comment?(single_tag) }
         
     | 
| 
      
 99 
     | 
    
         
            +
                  if last_comment_index
         
     | 
| 
      
 100 
     | 
    
         
            +
                    comment_after_tag = tags[0...last_comment_index].any? { |single_tag| !is_comment?(single_tag) }
         
     | 
| 
      
 101 
     | 
    
         
            +
                    rule_phrase = rule[:phrase].gsub(/{.*}/, type)
         
     | 
| 
      
 102 
     | 
    
         
            +
                    store_rule(rule, rule_phrase) if comment_after_tag
         
     | 
| 
      
 103 
     | 
    
         
            +
                  end
         
     | 
| 
      
 104 
     | 
    
         
            +
                end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                def rule_commented_tag(type)
         
     | 
| 
      
 107 
     | 
    
         
            +
                  rule = RULES[:commented_tag]
         
     | 
| 
      
 108 
     | 
    
         
            +
                  tags.each do |tag|
         
     | 
| 
      
 109 
     | 
    
         
            +
                    store_rule(rule, rule[:phrase].gsub(/{.*}/, type)) if is_comment?(tag) && tag.match(/@\S*/)
         
     | 
| 
      
 110 
     | 
    
         
            +
                  end
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
              end
         
     | 
| 
      
 114 
     | 
    
         
            +
            end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,160 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'roxml'
         
     | 
| 
      
 2 
     | 
    
         
            +
            module CukeSniffer
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              # Author::    Robert Cochran  (mailto:cochrarj@miamioh.edu)
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Copyright:: Copyright (C) 2013 Robert Cochran
         
     | 
| 
      
 6 
     | 
    
         
            +
              # License::   Distributes under the MIT License
         
     | 
| 
      
 7 
     | 
    
         
            +
              # Cucumber Hook class used for evaluating rules
         
     | 
| 
      
 8 
     | 
    
         
            +
              # Extends CukeSniffer::RulesEvaluator
         
     | 
| 
      
 9 
     | 
    
         
            +
              class Hook < RulesEvaluator
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                xml_accessor :start_line
         
     | 
| 
      
 12 
     | 
    
         
            +
                xml_accessor :type
         
     | 
| 
      
 13 
     | 
    
         
            +
                xml_accessor :tags, :as => [], :in => "tags"
         
     | 
| 
      
 14 
     | 
    
         
            +
                xml_accessor :parameters, :as => [], :in => "parameters"
         
     | 
| 
      
 15 
     | 
    
         
            +
                xml_accessor :code, :as => [], :in => "code"
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # The type of the hook: AfterConfiguration, After, AfterStep, Around, Before, at_exit
         
     | 
| 
      
 18 
     | 
    
         
            +
                attr_accessor :type
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                # The list of tags used as a filter for the hook
         
     | 
| 
      
 21 
     | 
    
         
            +
                attr_accessor :tags
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                # The parameters that are declared on the hook
         
     | 
| 
      
 24 
     | 
    
         
            +
                attr_accessor :parameters
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # Integer of the line in which the hook was found
         
     | 
| 
      
 27 
     | 
    
         
            +
                attr_accessor :start_line
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                # Array of strings that contain the code kept in the hook
         
     | 
| 
      
 30 
     | 
    
         
            +
                attr_accessor :code
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                # location must be in the format of "file_path\file_name.rb:line_number"
         
     | 
| 
      
 34 
     | 
    
         
            +
                # raw_code is an array of strings that represents the step definition
         
     | 
| 
      
 35 
     | 
    
         
            +
                # must contain the hook declaration line and the pairing end
         
     | 
| 
      
 36 
     | 
    
         
            +
                def initialize(location, raw_code)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  super(location)
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  @start_line = location.match(/:(?<line>\d*)$/)[:line].to_i
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @type = nil
         
     | 
| 
      
 41 
     | 
    
         
            +
                  @tags = []
         
     | 
| 
      
 42 
     | 
    
         
            +
                  @parameters = []
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  end_match_index = (raw_code.size - 1) - raw_code.reverse.index("end")
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @code = raw_code[1...end_match_index]
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  raw_code.each do |line|
         
     | 
| 
      
 48 
     | 
    
         
            +
                    if line =~ HOOK_REGEX
         
     | 
| 
      
 49 
     | 
    
         
            +
                      matches = HOOK_REGEX.match(line)
         
     | 
| 
      
 50 
     | 
    
         
            +
                      @type = matches[:type]
         
     | 
| 
      
 51 
     | 
    
         
            +
                      hook_tag_regexp = /["']([^"']*)["']/
         
     | 
| 
      
 52 
     | 
    
         
            +
                      matches[:tags].scan(hook_tag_regexp).each { |tag| @tags << tag[0] } if matches[:tags]
         
     | 
| 
      
 53 
     | 
    
         
            +
                      @parameters = matches[:parameters].split(/,\s*/) if matches[:parameters]
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                  evaluate_score
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def ==(comparison_object) # :nodoc:
         
     | 
| 
      
 60 
     | 
    
         
            +
                  super(comparison_object) &&
         
     | 
| 
      
 61 
     | 
    
         
            +
                      comparison_object.type == type &&
         
     | 
| 
      
 62 
     | 
    
         
            +
                      comparison_object.tags == tags &&
         
     | 
| 
      
 63 
     | 
    
         
            +
                      comparison_object.parameters == parameters &&
         
     | 
| 
      
 64 
     | 
    
         
            +
                      comparison_object.code == code
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                private
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                def evaluate_score
         
     | 
| 
      
 70 
     | 
    
         
            +
                  rule_empty_hook
         
     | 
| 
      
 71 
     | 
    
         
            +
                  rule_hook_not_in_hooks_file
         
     | 
| 
      
 72 
     | 
    
         
            +
                  rule_no_debugging
         
     | 
| 
      
 73 
     | 
    
         
            +
                  rule_all_comments
         
     | 
| 
      
 74 
     | 
    
         
            +
                  rule_conflicting_tags
         
     | 
| 
      
 75 
     | 
    
         
            +
                  rule_duplicate_tags
         
     | 
| 
      
 76 
     | 
    
         
            +
                  if @type == "Around"
         
     | 
| 
      
 77 
     | 
    
         
            +
                    rule_around_hook_without_2_parameters
         
     | 
| 
      
 78 
     | 
    
         
            +
                    rule_around_hook_no_block_call
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                def rule_empty_hook
         
     | 
| 
      
 83 
     | 
    
         
            +
                  rule = RULES[:empty_hook]
         
     | 
| 
      
 84 
     | 
    
         
            +
                  store_rule(rule) if @code == []
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                def rule_hook_not_in_hooks_file
         
     | 
| 
      
 88 
     | 
    
         
            +
                  rule = RULES[:hook_not_in_hooks_file]
         
     | 
| 
      
 89 
     | 
    
         
            +
                  store_rule(rule) unless @location.include?(rule[:file])
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def rule_around_hook_without_2_parameters
         
     | 
| 
      
 93 
     | 
    
         
            +
                  rule = RULES[:around_hook_without_2_parameters]
         
     | 
| 
      
 94 
     | 
    
         
            +
                  store_rule(rule) unless @parameters.count == 2
         
     | 
| 
      
 95 
     | 
    
         
            +
                end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                def rule_around_hook_no_block_call
         
     | 
| 
      
 98 
     | 
    
         
            +
                  return if rule_stored?(:around_hook_without_2_parameters)
         
     | 
| 
      
 99 
     | 
    
         
            +
                  rule = RULES[:around_hook_no_block_call]
         
     | 
| 
      
 100 
     | 
    
         
            +
                  block_call = "#{@parameters[1]}.call"
         
     | 
| 
      
 101 
     | 
    
         
            +
                  @code.each do |line|
         
     | 
| 
      
 102 
     | 
    
         
            +
                    return if line.include?(block_call)
         
     | 
| 
      
 103 
     | 
    
         
            +
                  end
         
     | 
| 
      
 104 
     | 
    
         
            +
                  store_rule(rule)
         
     | 
| 
      
 105 
     | 
    
         
            +
                end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                def rule_stored?(rule_symbol)
         
     | 
| 
      
 108 
     | 
    
         
            +
                  @rules_hash.keys.include?(RULES[rule_symbol][:phrase])
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                def rule_no_debugging
         
     | 
| 
      
 112 
     | 
    
         
            +
                  return if rule_stored?(:empty_hook)
         
     | 
| 
      
 113 
     | 
    
         
            +
                  rule = RULES[:hook_no_debugging]
         
     | 
| 
      
 114 
     | 
    
         
            +
                  begin_found = false
         
     | 
| 
      
 115 
     | 
    
         
            +
                  rescue_found = false
         
     | 
| 
      
 116 
     | 
    
         
            +
                  @code.each do |line|
         
     | 
| 
      
 117 
     | 
    
         
            +
                    begin_found = true if line.include?("begin")
         
     | 
| 
      
 118 
     | 
    
         
            +
                    rescue_found = true if line.include?("rescue")
         
     | 
| 
      
 119 
     | 
    
         
            +
                    break if begin_found and rescue_found
         
     | 
| 
      
 120 
     | 
    
         
            +
                  end
         
     | 
| 
      
 121 
     | 
    
         
            +
                  store_rule(rule) unless begin_found and rescue_found
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                def rule_all_comments
         
     | 
| 
      
 125 
     | 
    
         
            +
                  rule = RULES[:hook_all_comments]
         
     | 
| 
      
 126 
     | 
    
         
            +
                  @code.each do |line|
         
     | 
| 
      
 127 
     | 
    
         
            +
                    return unless is_comment?(line)
         
     | 
| 
      
 128 
     | 
    
         
            +
                  end
         
     | 
| 
      
 129 
     | 
    
         
            +
                  store_rule(rule)
         
     | 
| 
      
 130 
     | 
    
         
            +
                end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                def rule_conflicting_tags
         
     | 
| 
      
 133 
     | 
    
         
            +
                  rule = RULES[:hook_conflicting_tags]
         
     | 
| 
      
 134 
     | 
    
         
            +
                  all_tags = flatten_tags
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                  all_tags.each do |single_tag|
         
     | 
| 
      
 137 
     | 
    
         
            +
                    tag = single_tag.gsub("~", "")
         
     | 
| 
      
 138 
     | 
    
         
            +
                    if all_tags.include?(tag) and all_tags.include?("~#{tag}")
         
     | 
| 
      
 139 
     | 
    
         
            +
                      store_rule(rule)
         
     | 
| 
      
 140 
     | 
    
         
            +
                      return
         
     | 
| 
      
 141 
     | 
    
         
            +
                    end
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
                end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                def flatten_tags
         
     | 
| 
      
 146 
     | 
    
         
            +
                  all_tags = []
         
     | 
| 
      
 147 
     | 
    
         
            +
                  @tags.each { |single_tag| all_tags << single_tag.split(',') }
         
     | 
| 
      
 148 
     | 
    
         
            +
                  all_tags.flatten
         
     | 
| 
      
 149 
     | 
    
         
            +
                end
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                def rule_duplicate_tags
         
     | 
| 
      
 152 
     | 
    
         
            +
                  rule = RULES[:hook_duplicate_tags]
         
     | 
| 
      
 153 
     | 
    
         
            +
                  all_tags = flatten_tags
         
     | 
| 
      
 154 
     | 
    
         
            +
                  unique_tags = all_tags.uniq
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
                  store_rule(rule) unless all_tags == unique_tags
         
     | 
| 
      
 157 
     | 
    
         
            +
                end
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
              end
         
     | 
| 
      
 160 
     | 
    
         
            +
            end
         
     |