predicated 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.markdown ADDED
@@ -0,0 +1,9 @@
1
+ Predicated is a simple predicate model for Ruby.
2
+
3
+ Tracker project:
4
+ [http://www.pivotaltracker.com/projects/95014](http://www.pivotaltracker.com/projects/95014)
5
+
6
+
7
+ Right now this project makes use of Wrong for assertions. Wrong uses this project. It's kind of neat in an eat-your-own-dogfood sense, but it's possible that this will be problematic over time (particularly when changes in this project cause assertions to behave differently - if even temporarily).
8
+
9
+ A middle ground is to make "from ruby string" and "from callable object" use minitest asserts, since these are the "interesting" parts of Predicated relied on by Wrong.
data/lib/predicated.rb ADDED
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+
3
+ require "predicated/gem_check"
4
+ require "predicated/predicate"
5
+ require "predicated/evaluate"
6
+ require "predicated/constrain"
@@ -0,0 +1,64 @@
1
+ module Predicated
2
+ class Constraints
3
+ def initialize
4
+ @constraints = []
5
+ end
6
+
7
+ def add(constraint)
8
+ @constraints << constraint
9
+ self
10
+ end
11
+
12
+ def check(whole_predicate)
13
+ result = ConstraintCheckResult.new
14
+
15
+ @constraints.collect do |constraint|
16
+ whole_predicate.select(*constraint.selectors).collect do |predicate, ancestors|
17
+ if ! constraint.check(predicate, ancestors)
18
+ result.violation(constraint, predicate)
19
+ end
20
+ end
21
+ end
22
+
23
+ result
24
+ end
25
+
26
+ def ==(other)
27
+ @constraints == other.instance_variable_get("@constraints".to_sym)
28
+ end
29
+ end
30
+
31
+ class Constraint
32
+ attr_reader :name, :selectors
33
+ def initialize(args)
34
+ @name = args[:name]
35
+ @selectors = args[:selectors] || [:all]
36
+ @check_that = args[:check_that]
37
+ end
38
+
39
+ def check(predicate, ancestors)
40
+ @check_that.call(predicate, ancestors)
41
+ end
42
+
43
+ def ==(other)
44
+ @name == other.name && @selectors == other.selectors
45
+ end
46
+ end
47
+
48
+ class ConstraintCheckResult
49
+ attr_reader :violations
50
+ def initialize
51
+ @violations = {}
52
+ end
53
+
54
+ def pass?
55
+ @violations.empty?
56
+ end
57
+
58
+ def violation(constraint, predicate)
59
+ @violations[constraint] ||= []
60
+ @violations[constraint] << predicate
61
+ self
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,75 @@
1
+ require "predicated/predicate"
2
+
3
+ module Predicated
4
+
5
+
6
+ class Operation
7
+ attr_reader :method_sym
8
+
9
+ def initialize(left, method_sym, right)
10
+ super(left, right)
11
+ @method_sym = method_sym
12
+ end
13
+
14
+ def evaluate
15
+ left.send(@method_sym, *right)
16
+ end
17
+
18
+ def ==(other)
19
+ super && method_sym==other.method_sym
20
+ end
21
+ end
22
+
23
+
24
+ class Equal < Operation; def initialize(left, right); super(left, :==, right); end end
25
+ class LessThan < Operation; def initialize(left, right); super(left, :<, right); end end
26
+ class GreaterThan < Operation; def initialize(left, right); super(left, :>, right); end end
27
+ class LessThanOrEqualTo < Operation; def initialize(left, right); super(left, :<=, right); end end
28
+ class GreaterThanOrEqualTo < Operation; def initialize(left, right); super(left, :>=, right); end end
29
+
30
+ class Call < Operation
31
+ def self.shorthand
32
+ :Call
33
+ end
34
+
35
+ def initialize(left, method_sym, right=[])
36
+ super
37
+ end
38
+
39
+ def inspect
40
+ "Call(#{self.send(:part_inspect,left)}.#{method_sym.to_s}(#{self.send(:part_inspect, right)}))"
41
+ end
42
+ end
43
+ Predicate.module_eval(%{
44
+ def Call(left_object, method_sym, right_args=[])
45
+ ::Predicated::Call.new(left_object, method_sym, right_args)
46
+ end
47
+ })
48
+
49
+ module Container
50
+ private
51
+ def boolean_or_evaluate(thing)
52
+ if thing.is_a?(FalseClass)
53
+ false
54
+ elsif thing.is_a?(TrueClass)
55
+ true
56
+ else
57
+ thing.evaluate
58
+ end
59
+ end
60
+ end
61
+
62
+ class And
63
+ include Container
64
+ def evaluate
65
+ boolean_or_evaluate(left) && boolean_or_evaluate(right)
66
+ end
67
+ end
68
+
69
+ class Or
70
+ include Container
71
+ def evaluate
72
+ boolean_or_evaluate(left) || boolean_or_evaluate(right)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,79 @@
1
+ require "predicated/predicate"
2
+ require "predicated/from/ruby_string"
3
+
4
+ #Procs and lambdas are "callable objects"
5
+
6
+ module Predicated
7
+
8
+ require_gem_version("ParseTree", "3.0.5", "parse_tree")
9
+
10
+ module Predicate
11
+
12
+ #hrm
13
+ def self.from_callable_object(context_or_callable_object=nil, context=nil, &block)
14
+ callable_object = nil
15
+
16
+ if context_or_callable_object.is_a?(Binding) || context_or_callable_object.nil?
17
+ context = context_or_callable_object
18
+ callable_object = block
19
+ else
20
+ callable_object = context_or_callable_object
21
+ end
22
+
23
+ context ||= callable_object.binding
24
+
25
+ from_ruby_string(TranslateToRubyString.convert(callable_object), context)
26
+ end
27
+
28
+ module TranslateToRubyString
29
+ #see http://stackoverflow.com/questions/199603/how-do-you-stringize-serialize-ruby-code
30
+ def self.convert(callable_object)
31
+ temp_class = Class.new
32
+ temp_class.class_eval do
33
+ define_method :serializable, &callable_object
34
+ end
35
+ ruby_string = Ruby2Ruby.translate(temp_class, :serializable)
36
+ ruby_string.sub(/^def serializable\n /, "").sub(/\nend$/, "")
37
+ end
38
+ end
39
+
40
+ #see http://gist.github.com/321038
41
+ # # Monkey-patch to have Ruby2Ruby#translate with r2r >= 1.2.3, from
42
+ # # http://seattlerb.rubyforge.org/svn/ruby2ruby/1.2.2/lib/ruby2ruby.rb
43
+ class ::Ruby2Ruby < ::SexpProcessor
44
+ def self.translate(klass_or_str, method = nil)
45
+ sexp = ParseTree.translate(klass_or_str, method)
46
+ unifier = Unifier.new
47
+ unifier.processors.each do |p|
48
+ p.unsupported.delete :cfunc # HACK
49
+ end
50
+ sexp = unifier.process(sexp)
51
+ self.new.process(sexp)
52
+ end
53
+
54
+ #sconover - 7/2010 - monkey-patch
55
+ #{1=>2}=={1=>2}
56
+ #The right side was having its braces cut off because of
57
+ #special handling of hashes within arglists within the seattlerb code.
58
+ #I tried to fork r2r and add a test, but a lot of other tests
59
+ #broke, and I just dont understand the test in ruby2ruby.
60
+ #So I'm emailing the author...
61
+ def process_hash(exp)
62
+ result = []
63
+ until exp.empty?
64
+ lhs = process(exp.shift)
65
+ rhs = exp.shift
66
+ t = rhs.first
67
+ rhs = process rhs
68
+ rhs = "(#{rhs})" unless [:lit, :str].include? t # TODO: verify better!
69
+
70
+ result << "#{lhs} => #{rhs}"
71
+ end
72
+
73
+ return "{ #{result.join(', ')} }"
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,65 @@
1
+ require "predicated/predicate"
2
+ require "predicated/evaluate"
3
+
4
+ module Predicated
5
+
6
+ require_gem_version("ruby_parser", "2.0.4")
7
+ require_gem_version("ruby2ruby", "1.2.4")
8
+
9
+ module Predicate
10
+ def self.from_ruby_string(ruby_predicate_string, context=binding())
11
+ sexp = RubyParser.new.process(ruby_predicate_string.strip)
12
+ SexpToPredicate.new(context).convert(sexp)
13
+ end
14
+
15
+ class SexpToPredicate
16
+ SIGN_TO_PREDICATE_CLASS = {
17
+ :== => Equal,
18
+ :> => GreaterThan,
19
+ :< => LessThan,
20
+ :>= => GreaterThanOrEqualTo,
21
+ :<= => LessThanOrEqualTo,
22
+ }
23
+
24
+ def initialize(context)
25
+ @context = context
26
+ end
27
+
28
+ def convert(sexp)
29
+ first_element = sexp.first
30
+ if first_element == :block
31
+ #eval all the top lines and then treat the last one as a predicate
32
+ body_sexps = sexp.sexp_body.to_a
33
+ body_sexps.slice(0..-2).each do |upper_sexp|
34
+ eval(Ruby2Ruby.new.process(upper_sexp), @context)
35
+ end
36
+ convert(body_sexps.last)
37
+ elsif first_element == :call
38
+ sym, left_sexp, method_sym, right_sexp = sexp
39
+ left = eval(Ruby2Ruby.new.process(left_sexp), @context)
40
+ right = eval(Ruby2Ruby.new.process(right_sexp), @context)
41
+
42
+ if operation_class=SIGN_TO_PREDICATE_CLASS[method_sym]
43
+ operation_class.new(left, right)
44
+ else
45
+ Call.new(left, method_sym, right)
46
+ end
47
+ elsif first_element == :and
48
+ sym, left, right = sexp
49
+ And.new(convert(left), convert(right))
50
+ elsif first_element == :or
51
+ sym, left, right = sexp
52
+ Or.new(convert(left), convert(right))
53
+ else
54
+ raise DontKnowWhatToDoWithThisSexpError.new(sexp)
55
+ end
56
+ end
57
+ end
58
+
59
+ class DontKnowWhatToDoWithThisSexpError < StandardError
60
+ def initialize(sexp)
61
+ super("don't know what to do with #{sexp.inspect}")
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,97 @@
1
+ require "predicated/predicate"
2
+
3
+ module Predicated
4
+
5
+ require_gem_version("treetop", "1.4.8")
6
+
7
+ module Predicate
8
+ def self.from_url_fragment(url_fragment_string)
9
+ TreetopUrlFragmentParser.new.parse(url_fragment_string).to_predicate
10
+ end
11
+ end
12
+
13
+ module TreetopUrlFragment
14
+ Treetop.load_from_string(%{
15
+
16
+ grammar TreetopUrlFragment
17
+
18
+ include Predicated::TreetopUrlFragment
19
+
20
+ rule or
21
+ ( and "|" or <OrNode>) / and
22
+ end
23
+
24
+ rule and
25
+ ( leaf "&" and <AndNode> ) / leaf
26
+ end
27
+
28
+ rule operation
29
+ unquoted_string sign unquoted_string <OperationNode>
30
+ end
31
+
32
+ rule parens
33
+ "(" or ")" <ParensNode>
34
+ end
35
+
36
+
37
+ rule leaf
38
+ operation / parens
39
+ end
40
+
41
+ rule unquoted_string
42
+ [0-9a-zA-Z]*
43
+ end
44
+
45
+ rule sign
46
+ ('>=' / '<=' / '<' / '>' / '=' )
47
+ end
48
+ end
49
+
50
+ })
51
+
52
+ class OperationNode < Treetop::Runtime::SyntaxNode
53
+ def left_text; elements[0].text_value end
54
+ def sign_text; elements[1].text_value end
55
+ def right_text; elements[2].text_value end
56
+
57
+ SIGN_TO_PREDICATE_CLASS = {
58
+ "=" => Equal,
59
+ ">" => GreaterThan,
60
+ "<" => LessThan,
61
+ ">=" => GreaterThanOrEqualTo,
62
+ "<=" => LessThanOrEqualTo
63
+ }
64
+
65
+ def to_predicate
66
+ SIGN_TO_PREDICATE_CLASS[sign_text].new(left_text, right_text)
67
+ end
68
+ end
69
+
70
+ class AndNode < Treetop::Runtime::SyntaxNode
71
+ def left; elements[0] end
72
+ def right; elements[2] end
73
+
74
+ def to_predicate
75
+ And.new(left.to_predicate, right.to_predicate)
76
+ end
77
+ end
78
+
79
+ class OrNode < Treetop::Runtime::SyntaxNode
80
+ def left; elements[0] end
81
+ def right; elements[2] end
82
+
83
+ def to_predicate
84
+ Or.new(left.to_predicate, right.to_predicate)
85
+ end
86
+ end
87
+
88
+ class ParensNode < Treetop::Runtime::SyntaxNode
89
+ def inner; elements[1] end
90
+
91
+ def to_predicate
92
+ inner.to_predicate
93
+ end
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,34 @@
1
+ module Predicated
2
+ def self.require_gem_version(gem_name, minimum_version, require_name=gem_name)
3
+ unless Gem.available?(gem_name, Gem::Requirement.create(">= #{minimum_version}"))
4
+ raise %{
5
+ Gem: #{gem_name} >=#{minimum_version}
6
+ Does not appear to be installed. Please install it.
7
+
8
+ Predicated is built in a way that allows you to pick and
9
+ choose which features to use.
10
+
11
+ RubyGems has no way to specify optional dependencies,
12
+ therefore I've made the decision not to have Predicated
13
+ automatically depend into the various gems referenced
14
+ in from/to "extensions".
15
+
16
+ The cost here is that the gem install doesn't necessarily
17
+ "just work" for you out of the box. But in return you get
18
+ greater flexibility.
19
+
20
+ Notably, rails/arel unfortunately has a hard dependency
21
+ on Rails 3 activesupport, which requires ruby 1.8.7.
22
+ By making from/to dependencies optional, those with
23
+ no interest in arel can use Predicated in a wider
24
+ variety of environments.
25
+
26
+ For more discussion see:
27
+ http://stackoverflow.com/questions/2993335/rubygems-optional-dependencies
28
+ }
29
+ end
30
+
31
+ require require_name
32
+ end
33
+
34
+ end
@@ -0,0 +1,92 @@
1
+ require "predicated/gem_check"
2
+ require "predicated/selector"
3
+
4
+ module Predicated
5
+ def Predicate(&block)
6
+ result = nil
7
+ Module.new do
8
+ extend Predicate
9
+ result = instance_eval(&block)
10
+ end
11
+ result
12
+ end
13
+
14
+ class Binary
15
+ attr_accessor :left, :right
16
+
17
+ def initialize(left, right)
18
+ @left = left
19
+ @right = right
20
+ end
21
+
22
+ module FlipThroughMe
23
+ def each(ancestors=[], &block)
24
+ yield([self, ancestors])
25
+ ancestors_including_me = ancestors.dup + [self]
26
+ enumerate_side(@left, ancestors_including_me, &block)
27
+ enumerate_side(@right, ancestors_including_me, &block)
28
+ end
29
+
30
+ private
31
+ def enumerate_side(thing, ancestors)
32
+ thing.each(ancestors) { |item| yield(item) } if thing.is_a?(Enumerable)
33
+ end
34
+ end
35
+ include FlipThroughMe
36
+ include Enumerable
37
+
38
+ module ValueEquality
39
+ def ==(other)
40
+ self.class == other.class &&
41
+ self.left == other.left &&
42
+ self.right == other.right
43
+ end
44
+ end
45
+ include ValueEquality
46
+ end
47
+
48
+ class Operation < Binary; end
49
+
50
+ module Predicate
51
+ extend Predicated::Selector
52
+
53
+ CLASS_INFO = [
54
+ [:And, :And, Class.new(Binary)],
55
+ [:Or, :Or, Class.new(Binary)],
56
+ [:Equal, :Eq, Class.new(Operation)],
57
+ [:LessThan, :Lt, Class.new(Operation)],
58
+ [:GreaterThan, :Gt, Class.new(Operation)],
59
+ [:LessThanOrEqualTo, :Lte, Class.new(Operation)],
60
+ [:GreaterThanOrEqualTo, :Gte, Class.new(Operation)]
61
+ ]
62
+
63
+ #not great
64
+ base_selector_enumerable = SelectorEnumerable(
65
+ (CLASS_INFO.collect{|class_sym, sh, class_obj|class_obj} + [Binary, Operation]).
66
+ inject({:all => proc{|predicate, enumerable|true}}) do |h, class_obj|
67
+ h[class_obj] = proc{|predicate, enumerable|predicate.is_a?(class_obj)}
68
+ h
69
+ end
70
+ )
71
+
72
+ CLASS_INFO.each do |operation_class_name, shorthand, class_object|
73
+ Predicated.const_set(operation_class_name, class_object)
74
+ class_object.instance_variable_set("@shorthand".to_sym, shorthand)
75
+ class_object.class_eval do
76
+
77
+ def self.shorthand
78
+ @shorthand
79
+ end
80
+
81
+ include base_selector_enumerable
82
+ end
83
+ module_eval(%{
84
+ def #{shorthand}(left, right)
85
+ ::Predicated::#{operation_class_name}.new(left, right)
86
+ end
87
+ })
88
+ end
89
+ end
90
+ end
91
+
92
+ require "predicated/print"