Module: CML::TagLogic
- Included in:
- Tag
- Defined in:
- lib/cml/logic.rb
Overview
Logic behavior included in CML::Tag
Defined Under Namespace
Classes: Error, NodeNotFound
Constant Summary collapse
- Or =
'||'.freeze
- And =
'&&'.freeze
- CombinatorDefault =
Or
- CombinatorDict =
{ '||' => Or, '++' => And }
- CombinatorExp =
'(\+\+||\|\||)'.freeze
- OrCombinatorExp =
'(\|\||)'.freeze
- GroupExp =
'\(([^\(\)]+)\)'.freeze
- AndPhraseExp =
'([^\(\)\|]+\+\+[^\(\)\|]+)'.freeze
- TokenExp =
'([^\(\)\+\|]+)'.freeze
- PrecedenceRegexp =
/#{CombinatorExp}#{GroupExp}|#{CombinatorExp}#{AndPhraseExp}|#{CombinatorExp}#{TokenExp}/
- TokenRegexp =
/#{CombinatorExp}#{TokenExp}/
Instance Attribute Summary collapse
-
#errors ⇒ Object
Returns the value of attribute errors.
-
#has_grouped_logic ⇒ Object
Returns the value of attribute has_grouped_logic.
-
#has_liquid_logic ⇒ Object
Returns the value of attribute has_liquid_logic.
-
#logic_tree ⇒ Object
Returns the value of attribute logic_tree.
Class Method Summary collapse
- .parse_expression(logic_expression) ⇒ Object
-
.resolve_combinator(parsed_expression, i, parent_combinator = nil) ⇒ Object
Return the effective boolean combinator for the given index in the parsed logic.
Instance Method Summary collapse
-
#dependencies_on_fields(&block) ⇒ Object
A hash of the Tag’s dependencies with “only-if” logic.
-
#dependencies_through_cml_group(&block) ⇒ Object
The <cml::group> only-if logic dependencies.
-
#depends_on_fields ⇒ Object
Returns array of field names that this tag depends on with “only-if” logic.
- #describe_logic_token(logic_token) ⇒ Object
-
#detect_grouped_logic(only_if) ⇒ Object
Detect parenthetical grouping in only-if logic expressions.
-
#detect_liquid_logic(only_if) ⇒ Object
Detect Liquid tags in only-if logic expressions.
- #each_logic_token_in(logic_expression) ⇒ Object
-
#expand_logic(only_if, &block) ⇒ Object
Generate the full logic structure for an only-if.
-
#expand_parsed_expression(parsed_expression, &block) ⇒ Object
Generate the full logic structure for a parsed only-if.
-
#in_logic_graph? ⇒ Boolean
Override in Tags classes that should be omitted from the logic graph.
- #keep_merge!(hash, target) ⇒ Object
- #only_if ⇒ Object
Instance Attribute Details
#errors ⇒ Object
Returns the value of attribute errors.
147 148 149 |
# File 'lib/cml/logic.rb', line 147 def errors @errors end |
#has_grouped_logic ⇒ Object
Returns the value of attribute has_grouped_logic.
147 148 149 |
# File 'lib/cml/logic.rb', line 147 def has_grouped_logic @has_grouped_logic end |
#has_liquid_logic ⇒ Object
Returns the value of attribute has_liquid_logic.
147 148 149 |
# File 'lib/cml/logic.rb', line 147 def has_liquid_logic @has_liquid_logic end |
#logic_tree ⇒ Object
Returns the value of attribute logic_tree.
147 148 149 |
# File 'lib/cml/logic.rb', line 147 def logic_tree @logic_tree end |
Class Method Details
.parse_expression(logic_expression) ⇒ Object
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/cml/logic.rb', line 258 def self.parse_expression(logic_expression) parsed_expression = [] logic_expression.scan( PrecedenceRegexp ) do |group_combinator, group_phrase, and_combinator, and_phrase, combinator, token| if group_phrase parsed_expression << [group_combinator, parse_expression(group_phrase)] elsif and_phrase ands = [] and_phrase.scan( TokenRegexp ) do |precedence_combinator, precedence_token| ands << [precedence_combinator, precedence_token] end parsed_expression << [and_combinator, ands] else parsed_expression << [combinator, token] end end parsed_expression end |
.resolve_combinator(parsed_expression, i, parent_combinator = nil) ⇒ Object
Return the effective boolean combinator for the given index in the parsed logic.
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/cml/logic.rb', line 230 def self.resolve_combinator(parsed_expression, i, parent_combinator=nil) combinator = parsed_expression[i] && parsed_expression[i][0] selected = if (combinator.nil? || combinator.size==0) && parent_combinator CombinatorDict[parent_combinator] elsif !combinator.nil? && combinator.size>0 # Use the current phrase's combinator. CombinatorDict[combinator] else if parsed_expression[i+1] # Use the next phrase's combinator CombinatorDict[ parsed_expression[i+1][0] ] else CombinatorDefault end end raise(RuntimeError, "Combinator for index #{i} could not be selected from: #{parsed_expression.inspect}") if selected.nil? || selected.size==0 selected end |
Instance Method Details
#dependencies_on_fields(&block) ⇒ Object
A hash of the Tag’s dependencies with “only-if” logic.
Resolves indexed logic selector keys (e.g. ‘only-if=“pick_a_number:”`) into actual values from parents.
The optional block can be used as a flat collector of all fields; its return value is ignored.
283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/cml/logic.rb', line 283 def dependencies_on_fields(&block) return @dependencies_on_fields if @dependencies_on_fields && !block_given? @dependencies_on_fields = {} keep_merge!(dependencies_through_cml_group(&block), @dependencies_on_fields) return @dependencies_on_fields if only_if.empty? || detect_liquid_logic(only_if) || @logic_tree.nil? detect_grouped_logic(only_if) = (only_if, &block) keep_merge!(, @dependencies_on_fields) @dependencies_on_fields end |
#dependencies_through_cml_group(&block) ⇒ Object
The <cml::group> only-if logic dependencies
The optional block can be used as a flat collector of all fields; its return value is ignored.
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/cml/logic.rb', line 315 def dependencies_through_cml_group(&block) return @dependencies_through_cml_group if @dependencies_through_cml_group @dependencies_through_cml_group = {} @logic_tree.parser.each_cml_group_descendant do |group_node, cml_tag, i| next if group_node.attributes['only-if'].nil? || group_node.attributes['only-if'].value.size <= 0 || self != cml_tag group_only_if = group_node.attributes['only-if'].value.to_s = (group_only_if, &block) keep_merge!(, @dependencies_through_cml_group) end @dependencies_through_cml_group end |
#depends_on_fields ⇒ Object
Returns array of field names that this tag depends on with “only-if” logic.
300 301 302 303 304 305 306 307 308 309 |
# File 'lib/cml/logic.rb', line 300 def depends_on_fields return @depends_on_fields if @depends_on_fields @depends_on_fields = [] dependencies_on_fields do |name, descs| @depends_on_fields << name end @depends_on_fields end |
#describe_logic_token(logic_token) ⇒ Object
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
# File 'lib/cml/logic.rb', line 334 def describe_logic_token(logic_token) field_name, match_key = logic_token.split( ":" ) # detect NOT logic is_not = if /^!/===field_name field_name = field_name.gsub( /^!/, '' ) true else false end unless @logic_tree.nodes[field_name] raise CML::TagLogic::NodeNotFound, "CML element '#{name}' contains only-if logic that references a missing field '#{field_name}'." end descs = [] if match_key # unwrap the match key from it's square brackets match_key = match_key.gsub( /^\[([^\]]+)\]$/, "\\1") # when an integer index, set `match_key` to the literal value in the parent tag if /^\d+$/===match_key unless @logic_tree.nodes[field_name].tag.children raise CML::TagLogic::NodeNotFound, "CML element '#{name}' contains only-if logic that references a child index, '#{field_name}:[#{match_key}]', but '#{field_name}' contains no child elements." end if (tag_at_index = @logic_tree.nodes[field_name].tag.children[$~.to_s.to_i]) descs << { :is_not => is_not, :match_key => tag_at_index.value } end # when 'unchecked' logic "is not the checkbox's value or any of the checkboxes/checkbox values"; # is_not logic gets inverted instead of just setting true, just incase we're notting a not elsif 'unchecked'==match_key if @logic_tree.nodes[field_name].tag.tag == 'checkbox' checkbox = @logic_tree.nodes[field_name].tag descs << { :is_not => !is_not, :match_key => checkbox.value } else unless @logic_tree.nodes[field_name].tag.children raise CML::TagLogic::NodeNotFound, "CML element '#{name}' contains only-if logic that references a checked value, '#{field_name}:#{match_key}', but '#{field_name}' is not a checkbox nor contains checkboxes." end @logic_tree.nodes[field_name].tag.children.each do |child| next unless child.tag == 'checkbox' descs << { :is_not => !is_not, :match_key => child.value } end end end end if descs.empty? # When matcher is nil, invert the is_not logic to be semantically accurate. # # i.e. a nil match key actually means "is not blank," # while a negated nil matcher actually means "is blank" # is_not_with_nil = match_key.nil? ? !is_not : is_not descs << { :is_not => is_not_with_nil, :match_key => match_key } end [field_name, descs] end |
#detect_grouped_logic(only_if) ⇒ Object
Detect parenthetical grouping in only-if logic expressions
396 397 398 399 400 401 |
# File 'lib/cml/logic.rb', line 396 def detect_grouped_logic(only_if) @errors ||= [] if /\([^\(\)]+\)/===only_if @has_grouped_logic = true end end |
#detect_liquid_logic(only_if) ⇒ Object
Detect Liquid tags in only-if logic expressions
404 405 406 407 408 409 410 |
# File 'lib/cml/logic.rb', line 404 def detect_liquid_logic(only_if) @errors ||= [] if /\{\{/===only_if @errors << "The logic tree cannot be constructed when only-if contains a Liquid tag: #{only_if}" @has_liquid_logic = true end end |
#each_logic_token_in(logic_expression) ⇒ Object
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/cml/logic.rb', line 165 def each_logic_token_in(logic_expression) return unless block_given? logic_expression.split( /\+\+|\|\|/ ).each do |logic_token| field_name, match_key = logic_token.split( ":" ) # prune the possibly-dangling open paren from logic grouping field_name.gsub!( /^\(/, '' ) # prune the possibly-dangling close paren from logic grouping match_key.gsub!( /\)$/, '') if match_key # detect NOT logic is_not = if /^!/===field_name field_name = field_name.gsub( /^!/, '' ) true else false end yield field_name, match_key, is_not end end |
#expand_logic(only_if, &block) ⇒ Object
Generate the full logic structure for an only-if.
The optional block can be used as a flat collector of all fields; its return value is ignored.
191 192 193 194 |
# File 'lib/cml/logic.rb', line 191 def (only_if, &block) parsed = CML::TagLogic.parse_expression(only_if) (parsed, &block) end |
#expand_parsed_expression(parsed_expression, &block) ⇒ Object
Generate the full logic structure for a parsed only-if.
The optional block can be used as a flat collector of all fields; its return value is ignored.
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/cml/logic.rb', line 200 def (parsed_expression, &block) = {} parsed_expression.each_with_index do |expression_array, i| combinator, phrase = expression_array combinator_key = CML::TagLogic.resolve_combinator(parsed_expression, i) branch = [combinator_key] ||= [] if Array===phrase # Recurse for grouped logic branch << (phrase, &block) else name, descs = describe_logic_token(phrase) yield(name, descs) if block_given? value ||= descs branch << { name => value } end end end |
#in_logic_graph? ⇒ Boolean
Override in Tags classes that should be omitted from the logic graph.
155 156 157 |
# File 'lib/cml/logic.rb', line 155 def in_logic_graph? true end |
#keep_merge!(hash, target) ⇒ Object
413 414 415 416 417 418 419 420 421 422 |
# File 'lib/cml/logic.rb', line 413 def keep_merge!(hash, target) hash.keys.each do |key| if hash[key].is_a? Hash and self[key].is_a? Hash target[key] = target[key].keep_merge(hash[key]) next end target.update(hash) { |key, *values| values.flatten.uniq } end target end |
#only_if ⇒ Object
159 160 161 |
# File 'lib/cml/logic.rb', line 159 def only_if @only_if ||= @attrs["only-if"].to_s end |