Skip to content

Commit

Permalink
Merge pull request #111 from arineng/0.8.1-alpha1
Browse files Browse the repository at this point in the history
0.8.1 alpha1 branch for 0.8.1 release
anewton1998 authored Dec 17, 2017
2 parents 8dae67b + 285c67d commit 910d183
Showing 24 changed files with 947 additions and 192 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ matrix:
exclude:
- rvm: 2.1.3
os: osx
- rvm: jruby-9.1
- rvm: jruby-9.1.9.0
os: osx
script:
- "bundle exec rake test"
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -122,6 +122,15 @@ which can be found [here](https://raw.githubusercontent.com/arineng/jcr/09/draft
* Much better CLI and programmatic validation failure information and structures
* Fixes to print errors when the JCR fails to parse
* 0.8.0 - Adds the --process-parts command line option
* 0.8.1
* Various issues with stack traces at the command line instead of proper errors
* --process-parts now takes an optional directory
* --process-parts now creates an XML entity reference file snippet
* override rules can now reference rules in the original ruleset
* more readable failure report
* more readable verbose messages
* @{not} annotation on targer rules were not honored but now fixed
* better checking for groups referenced from arrays and objects

The current version of the JCR specification can be found
[here](https://raw.githubusercontent.com/arineng/jcr/07/draft-newton-json-content-rules.txt)
@@ -180,7 +189,7 @@ Options
-r FILE file containing ruleset
-R STRING string containing ruleset. Should probably be quoted
--test-jcr parse and test the JCR only
--process-parts creates smaller files for specification writing
--process-parts [DIRECTORY] creates smaller files for specification writing
-S STRING name of root rule. All roots will be tried if none is specified
-o FILE file containing overide ruleset (option can be repeated)
-O STRING string containing overide rule (option can be repeated)
@@ -191,11 +200,11 @@ Options
Return codes:
0 = success
1 = parsing or other bad condition
2 = fall through bad condition
1 = bad JCR parsing or other bad condition
2 = invalid option or bad use of command
3 = unsuccessful evaluation of JSON
JCR Version 0.8.0
JCR Version 0.8.1
```

## Usage as a Library
@@ -222,8 +231,8 @@ The `callback.rb` demonstrates the usage of custom code for evaluation of rules.

The `--process-parts` option extracts parts of a JCR file into multiple files based
on comments in the file. It can also create a new file without the
comments. This is useful for JCR going into specification documents
where it is nice to break the JCR up for illustrative purposes in
comments. This is useful for rulesets going into specification documents
where it is nice to break the rulesets up for illustrative purposes in
the specification but to also have one JCR file for programmatic
testing purposes.

@@ -241,6 +250,9 @@ though leading whitespace is allowed if desired.
To get a new file with all parts but these comments, use this

; all_parts FILENAME

The `--process-parts` parameter will also take an optional directory name where it
will write the files.

## Building

18 changes: 4 additions & 14 deletions lib/jcr/check_groups.rb
Original file line number Diff line number Diff line change
@@ -78,8 +78,6 @@ def self.check_member_for_group node, mapping
disallowed_group_in_member?( trule, mapping )
elsif node[:group_rule]
disallowed_group_in_member?( node[:group_rule], mapping )
else
check_groups( node, mapping )
end
end

@@ -117,8 +115,6 @@ def self.check_array_for_group node, mapping
disallowed_group_in_array?(trule, mapping)
elsif node[:group_rule]
disallowed_group_in_array?(node[:group_rule], mapping)
else
check_groups(node, mapping)
end
end
end
@@ -132,9 +128,7 @@ def self.disallowed_group_in_array? node, mapping
disallowed_group_in_array?( groupee[:group_rule], mapping )
elsif groupee[:target_rule_name]
trule = get_name_mapping( groupee[:target_rule_name][:rule_name], mapping )
if trule[:group_rule]
disallowed_group_in_array?( trule[:group_rule], mapping )
end
disallowed_group_in_array?( trule, mapping )
elsif groupee[:member_rule]
raise_group_error( "groups in array rules cannot have member rules", groupee[:member_rule] )
else
@@ -154,8 +148,6 @@ def self.check_object_for_group node, mapping
disallowed_group_in_object?(trule, mapping)
elsif node[:group_rule]
disallowed_group_in_object?(node[:group_rule], mapping)
else
check_groups(node, mapping)
end
end
end
@@ -169,9 +161,7 @@ def self.disallowed_group_in_object? node, mapping
disallowed_group_in_object?( groupee[:group_rule], mapping )
elsif groupee[:target_rule_name]
trule = get_name_mapping( groupee[:target_rule_name][:rule_name], mapping )
if trule[:group_rule]
disallowed_group_in_object?( trule[:group_rule], mapping )
end
disallowed_group_in_object?( trule, mapping )
elsif groupee[:array_rule]
raise_group_error( "groups in object rules cannot have array rules", groupee[:member_rule] )
elsif groupee[:object_rule]
@@ -188,9 +178,9 @@ def self.raise_group_error str, node
if node.is_a?( Parslet::Slice )
pos = node.line_and_column
name = node.to_str
raise "group rule error at line " + pos[0].to_s + " column " + pos[1].to_s + " name '" + name + "' :" + str
raise JCR::JcrValidatorError, "group rule error at line " + pos[0].to_s + " column " + pos[1].to_s + " name '" + name + "' :" + str
else
raise "group rule error with '" + node.to_s + "' :" + str
raise JCR::JcrValidatorError, "group rule error with '" + node.to_s + "' :" + str
end
end

67 changes: 41 additions & 26 deletions lib/jcr/evaluate_array_rules.rb
Original file line number Diff line number Diff line change
@@ -45,19 +45,28 @@ def initialize( current_behavior = nil )
end
end

def self.evaluate_array_rule jcr, rule_atom, data, econs, behavior = nil
def self.evaluate_array_rule jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil

push_trace_stack( econs, jcr )
trace( econs, "Evaluating array rule starting at #{slice_to_s(jcr)} against", data )
trace_def( econs, "array", jcr, data )
retval = evaluate_array( jcr, rule_atom, data, econs, behavior )
trace_eval( econs, "Array", retval, jcr, data, "array" )
if behavior
trace( econs, "Evaluating group in array rule starting at #{slice_to_s(jcr)} against", data )
trace_def( econs, "array group", jcr, data )
else
trace( econs, "Evaluating array rule starting at #{slice_to_s(jcr)} against", data )
trace_def( econs, "array", jcr, data )
end
retval = evaluate_array( jcr, rule_atom, data, econs, behavior, target_annotations )
if behavior
trace_eval( econs, "Array group", retval, jcr, data, "array" )
else
trace_eval( econs, "Array", retval, jcr, data, "array" )
end
pop_trace_stack( econs )
return retval

end

def self.evaluate_array jcr, rule_atom, data, econs, behavior = nil
def self.evaluate_array jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil

rules, annotations = get_rules_and_annotations( jcr )

@@ -76,20 +85,24 @@ def self.evaluate_array jcr, rule_atom, data, econs, behavior = nil

# if the data is not an array
return evaluate_not( annotations,
Evaluation.new( false, "#{data} is not an array #{raised_rule(jcr,rule_atom)}"), econs ) unless data.is_a? Array
Evaluation.new( false, "#{data} is not an array #{raised_rule(jcr,rule_atom)}"),
econs, target_annotations ) unless data.is_a? Array

# if the array is zero length and there are zero sub-rules (it is suppose to be empty)
return evaluate_not( annotations,
Evaluation.new( true, nil ), econs ) if rules.empty? && data.empty?
Evaluation.new( true, nil ), econs, target_annotations ) if rules.empty? && data.empty?

# if the array is not empty and there are zero sub-rules (it is suppose to be empty)
return evaluate_not( annotations,
Evaluation.new( false, "Non-empty array for #{raised_rule(jcr,rule_atom)}" ), econs ) if rules.empty? && data.length != 0
Evaluation.new( false, "Non-empty array for #{raised_rule(jcr,rule_atom)}" ),
econs, target_annotations ) if rules.empty? && data.length != 0

if ordered
return evaluate_not( annotations, evaluate_array_rule_ordered( rules, rule_atom, data, econs, behavior ), econs )
return evaluate_not( annotations, evaluate_array_rule_ordered( rules, rule_atom, data, econs, behavior ),
econs, target_annotations )
else
return evaluate_not( annotations, evaluate_array_rule_unordered( rules, rule_atom, data, econs, behavior ), econs )
return evaluate_not( annotations, evaluate_array_rule_unordered( rules, rule_atom, data, econs, behavior ),
econs, target_annotations )
end
end

@@ -115,7 +128,8 @@ def self.evaluate_array_rule_ordered jcr, rule_atom, data, econs, behavior = nil
# groups require the effects of the evaluation to be discarded if they are false
# groups must also be given the entire array

if (grule = get_group(rule, econs))
grule, target_annotations = get_group( rule, econs )
if grule

if repeat_min == 0
retval = Evaluation.new( true, nil )
@@ -126,7 +140,7 @@ def self.evaluate_array_rule_ordered jcr, rule_atom, data, econs, behavior = nil
else
group_behavior = ArrayBehavior.new( behavior )
group_behavior.last_index = array_index
retval = evaluate_rule( grule, rule_atom, data, econs, group_behavior )
retval = evaluate_rule( grule, rule_atom, data, econs, group_behavior, target_annotations )
if retval.success
behavior.checked_hash.merge!( group_behavior.checked_hash )
array_index = group_behavior.last_index
@@ -141,7 +155,7 @@ def self.evaluate_array_rule_ordered jcr, rule_atom, data, econs, behavior = nil
break if array_index == data.length
group_behavior = ArrayBehavior.new( behavior )
group_behavior.last_index = array_index
e = evaluate_rule( grule, rule_atom, data, econs, group_behavior )
e = evaluate_rule( grule, rule_atom, data, econs, group_behavior, target_annotations )
if e.success
behavior.checked_hash.merge!( group_behavior.checked_hash )
array_index = group_behavior.last_index
@@ -187,7 +201,7 @@ def self.evaluate_array_rule_ordered jcr, rule_atom, data, econs, behavior = nil
behavior.last_index = array_index

if data.length > array_index && behavior.extra_prohibited
retval = Evaluation.new( false, "More itmes in array than specified for #{raised_rule(jcr,rule_atom)}" )
retval = Evaluation.new( false, "More items in array (#{data.length}) than specified (#{array_index}) for #{raised_rule(jcr,rule_atom)}" )
end

return retval
@@ -218,14 +232,15 @@ def self.evaluate_array_rule_unordered jcr, rule_atom, data, econs, behavior = n
# groups require the effects of the evaluation to be discarded if they are false
# groups must also be given the entire array

if (grule = get_group(rule, econs))
grule,target_annotations = get_group(rule, econs)
if grule

successes = 0
for i in 0..repeat_max-1
group_behavior = ArrayBehavior.new( behavior )
group_behavior.last_index = highest_index
group_behavior.ordered = false
e = evaluate_rule( grule, rule_atom, data, econs, group_behavior )
e = evaluate_rule( grule, rule_atom, data, econs, group_behavior, target_annotations )
if e.success
highest_index = group_behavior.last_index
behavior.checked_hash.merge!( group_behavior.checked_hash )
@@ -236,13 +251,13 @@ def self.evaluate_array_rule_unordered jcr, rule_atom, data, econs, behavior = n
end

if successes == 0 && repeat_min > 0
retval = Evaluation.new( false, "array does not contain #{rule} for #{raised_rule(jcr,rule_atom)}")
retval = Evaluation.new( false, "array does not contain #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}")
elsif successes < repeat_min
retval = Evaluation.new( false, "array does not have enough #{rule} for #{raised_rule(jcr,rule_atom)}")
retval = Evaluation.new( false, "array does not have enough #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}")
elsif successes > repeat_max
retval = Evaluation.new( false, "array has too many #{rule} for #{raised_rule(jcr,rule_atom)}")
retval = Evaluation.new( false, "array has too many #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}")
elsif repeat_step && ( successes - repeat_min ) % repeat_step != 0
retval = Evaluation.new( false, "array matches (#{successes}) do not meet repetition step of #{repeat_max} % #{repeat_step} with #{rule} for #{raised_rule(jcr,rule_atom)}")
retval = Evaluation.new( false, "array matches (#{successes}) do not meet repetition step of #{repeat_max} % #{repeat_step} with #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}")
else
retval = Evaluation.new( true, nil )
end
@@ -263,13 +278,13 @@ def self.evaluate_array_rule_unordered jcr, rule_atom, data, econs, behavior = n
end

if successes == 0 && repeat_min > 0
retval = Evaluation.new( false, "array does not contain #{rule} for #{raised_rule(jcr,rule_atom)}")
retval = Evaluation.new( false, "array does not contain #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}")
elsif successes < repeat_min
retval = Evaluation.new( false, "array does not have enough #{rule} for #{raised_rule(jcr,rule_atom)}")
retval = Evaluation.new( false, "array does not have enough #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}")
elsif successes > repeat_max
retval = Evaluation.new( false, "array has too many #{rule} for #{raised_rule(jcr,rule_atom)}")
retval = Evaluation.new( false, "array has too many #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}")
elsif repeat_step && ( successes - repeat_min ) % repeat_step != 0
retval = Evaluation.new( false, "array matches (#{successes}) do not meet repetition step of #{repeat_max} % #{repeat_step} with #{rule} for #{raised_rule(jcr,rule_atom)}")
retval = Evaluation.new( false, "array matches (#{successes}) do not meet repetition step of #{repeat_max} % #{repeat_step} with #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}")
else
retval = Evaluation.new( true, nil)
end
@@ -281,7 +296,7 @@ def self.evaluate_array_rule_unordered jcr, rule_atom, data, econs, behavior = n
behavior.last_index = highest_index

if data.length > behavior.checked_hash.length && behavior.extra_prohibited
retval = Evaluation.new( false, "More itmes in array than specified for #{raised_rule(jcr,rule_atom)}" )
retval = Evaluation.new( false, "More items in array than specified for #{raised_rule(jcr,rule_atom)}" )
end

return retval
12 changes: 6 additions & 6 deletions lib/jcr/evaluate_group_rules.rb
Original file line number Diff line number Diff line change
@@ -26,34 +26,34 @@

module JCR

def self.evaluate_group_rule jcr, rule_atom, data, econs, behavior = nil
def self.evaluate_group_rule jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil

push_trace_stack( econs, jcr )
trace( econs, "Evaluating group rule against ", data )
trace_def( econs, "group", jcr, data )
retval = evaluate_group( jcr, rule_atom, data, econs, behavior )
retval = evaluate_group( jcr, rule_atom, data, econs, behavior, target_annotations )
trace_eval( econs, "Group", retval, jcr, data, "group" )
pop_trace_stack( econs )
return retval

end

def self.evaluate_group jcr, rule_atom, data, econs, behavior = nil
def self.evaluate_group jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil

rules, annotations = get_rules_and_annotations( jcr )

retval = nil

rules.each do |rule|
if rule[:choice_combiner] && retval && retval.success
return evaluate_not( annotations, retval, econs ) # short circuit
return evaluate_not( annotations, retval, econs, target_annotations ) # short circuit
elsif rule[:sequence_combiner] && retval && !retval.success
return evaluate_not( annotations, retval, econs ) # short circuit
return evaluate_not( annotations, retval, econs, target_annotations ) # short circuit
end
retval = evaluate_rule( rule, rule_atom, data, econs, behavior )
end

return evaluate_not( annotations, retval, econs )
return evaluate_not( annotations, retval, econs, target_annotations )
end

def self.group_to_s( jcr, shallow=true)
13 changes: 7 additions & 6 deletions lib/jcr/evaluate_member_rules.rb
Original file line number Diff line number Diff line change
@@ -25,19 +25,19 @@

module JCR

def self.evaluate_member_rule jcr, rule_atom, data, econs
def self.evaluate_member_rule jcr, rule_atom, data, econs, behavior, target_annotations

push_trace_stack( econs, jcr )
trace( econs, "Evaluating member rule for key '#{data[0]}' starting at #{slice_to_s(jcr)} against ", data[1])
trace_def( econs, "member", jcr, data )
retval = evaluate_member( jcr, rule_atom, data, econs )
retval = evaluate_member( jcr, rule_atom, data, econs, behavior, target_annotations )
trace_eval( econs, "Member", retval, jcr, data, "member" )
pop_trace_stack( econs )
return retval

end

def self.evaluate_member jcr, rule_atom, data, econs
def self.evaluate_member jcr, rule_atom, data, econs, behavior, target_annotations

# unlike the other evaluate functions, here data is not just the json data.
# it is an array, the first element being the member name or regex and the
@@ -68,13 +68,14 @@ def self.evaluate_member jcr, rule_atom, data, econs
end

if member_match
e = evaluate_rule( rule, rule_atom, data[ 1 ], econs )
e = evaluate_rule( rule, rule_atom, data[ 1 ], econs, nil, target_annotations )
e.member_found = true
return evaluate_not( annotations, e, econs )
return evaluate_not( annotations, e, econs, target_annotations )
end

return evaluate_not( annotations,
Evaluation.new( false, "#{match_spec} does not match #{data[0]} for #{raised_rule( jcr, rule_atom)}" ), econs )
Evaluation.new( false, "#{match_spec} does not match #{data[0]} for #{raised_rule( jcr, rule_atom)}" ),
econs, target_annotations )

end

Loading

0 comments on commit 910d183

Please sign in to comment.