masterplan 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "activesupport", '~>2.3.5'
4
+
5
+ group :development do
6
+ gem "rspec", "~> 2.3.0"
7
+ gem "bundler", "~> 1.0.0"
8
+ gem "jeweler", "~> 1.5.2"
9
+ gem "rcov", ">= 0"
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (2.3.10)
5
+ diff-lcs (1.1.2)
6
+ git (1.2.5)
7
+ jeweler (1.5.2)
8
+ bundler (~> 1.0.0)
9
+ git (>= 1.2.5)
10
+ rake
11
+ rake (0.8.7)
12
+ rcov (0.9.9)
13
+ rspec (2.3.0)
14
+ rspec-core (~> 2.3.0)
15
+ rspec-expectations (~> 2.3.0)
16
+ rspec-mocks (~> 2.3.0)
17
+ rspec-core (2.3.1)
18
+ rspec-expectations (2.3.0)
19
+ diff-lcs (~> 1.1.2)
20
+ rspec-mocks (2.3.0)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ activesupport (~> 2.3.5)
27
+ bundler (~> 1.0.0)
28
+ jeweler (~> 1.5.2)
29
+ rcov
30
+ rspec (~> 2.3.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Martin Tepper
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,181 @@
1
+ = Masterplan
2
+
3
+ Masterplan is a library for comparing Ruby data structures against predefined templates.
4
+
5
+ At Travel IQ, this is used to define a canonical definition of the structure and format of the requests and responses of our APIs.
6
+ In short, you get something that is similar to a XML Scheme - a template against which your data can be compared, to
7
+ ensure its correctness programmatically. Only without the XML part.
8
+
9
+ Examples make this easier to explain. Say you have a webservice or class that produces
10
+ a Ruby data structure like this:
11
+
12
+ {
13
+ :airports => [
14
+ {
15
+ :name => "Tegel Airport",
16
+ :code => "TXL",
17
+ :latitude => 2.35454,
18
+ :longitude => 54.67867
19
+ },
20
+ {
21
+ :name => "Schönefeld Airport",
22
+ :code => "SXF",
23
+ :latitude => 5.35454,
24
+ :longitude => 34.67867
25
+ }
26
+ ]
27
+ }
28
+
29
+ and so on and so forth. In your tests of this service, you want to make sure that this
30
+ structure follows certain rules:
31
+
32
+ * It's a Hash with one key, :airports
33
+ * The value is an Array, and it can be empty, but not null
34
+ * Each entry is a hash
35
+ * Each hash has the keys :name, :code, :latitude, :longitude
36
+ * No value of these keys is ever null
37
+ * Name and code are strings
38
+ * The strings aren't empty
39
+ * The code is three characters long and consists of uppercase letters
40
+ * Latitude and Longitude are floats
41
+
42
+ In a more XML-centric world, you would define an XML (or Relax-NG etc.) Scheme that defines all
43
+ these rules and then use that to validate your output. But we also want to deliver JSON...so we'll define
44
+ the rules as a Masterplan::Document, and validate the data while it's still Ruby, and say that as long
45
+ as the source data is correct, the representation in JSON or XML will also be correct:
46
+
47
+ include Masterplan::DefineRules
48
+
49
+ doc = Masterplan::Document.new(
50
+ :airports => [
51
+ {
52
+ :name => "Tegel Airport",
53
+ :code => rule("TXL", :matches => /[A-Z]{3}/),
54
+ :latitude => 2.35454,
55
+ :longitude => 54.67867
56
+ },
57
+ {
58
+ :name => "Schönefeld Airport",
59
+ :code => "SXF",
60
+ :latitude => 5.35454,
61
+ :longitude => 34.67867
62
+ }
63
+ ]
64
+ )
65
+
66
+ It doesn't look much different from the example, but there are a lot of rules built-in.
67
+ You can now use the doc object to check your data against the template:
68
+
69
+ Masterplan.compare(:scheme => doc, :to => [{:example => :data}])
70
+
71
+ And it will throw a Masterplan::FailedError exception, and print out debugging data:
72
+
73
+ >> Masterplan.compare(:scheme => doc, :to => {:example => :data})
74
+ Masterplan::FailedError: keys don't match in 'root':
75
+ expected: airports
76
+ received: example
77
+
78
+ Expected:
79
+ {"airports"=>
80
+ [{:latitude=>2.35454,
81
+ :longitude=>54.67867,
82
+ :code=>
83
+ #<Masterplan::Rule:0x6d0bd70
84
+ @example_value="TXL",
85
+ @options=
86
+ {"compare_each"=>false,
87
+ "allow_nil"=>false,
88
+ "included_in"=>false,
89
+ "matches"=>/[A-Z]{3}/}>,
90
+ :name=>"Tegel Airport"},
91
+ {:latitude=>5.35454,
92
+ :longitude=>34.67867,
93
+ :code=>"SXF",
94
+ :name=>"Schönefeld Airport"}]}
95
+
96
+
97
+ but was:
98
+ {"example"=>:data}
99
+
100
+ Another example:
101
+
102
+ >> Masterplan.compare(:scheme => doc, :to => {:airports => [{:name => "Bla", :latitude => 1.1, :longitude => 2.3, :code => "XXx"}]})
103
+ Masterplan::FailedError: value at 'root'=>'airports'=>'0'=>'code' "XXx" (String) does not match /[A-Z]{3}/ !
104
+
105
+ Expected:
106
+ {"name"=>"Tegel Airport",
107
+ "latitude"=>2.35454,
108
+ "code"=>
109
+ #<Masterplan::Rule:0x6d0bd70
110
+ @example_value="TXL",
111
+ @options=
112
+ {"compare_each"=>false,
113
+ "allow_nil"=>false,
114
+ "included_in"=>false,
115
+ "matches"=>/[A-Z]{3}/}>,
116
+ "longitude"=>54.67867}
117
+
118
+
119
+ but was:
120
+ {"name"=>"Bla", "latitude"=>1.1, "code"=>"XXx", "longitude"=>2.3}
121
+
122
+ The implicit rules are:
123
+
124
+ * Each object in the data needs to be of the same class as in the template
125
+ * hash keys must match up
126
+ * The first element of an Array in the template is used as the template for all elements in the data. That's why we didn't
127
+ have to restate the custom rule about the code in the above example, as only the "Tegel Airport" hash is used for all checks.
128
+
129
+ You can add extra rules with the rule method - see Masterplan::DefineRules#rule for details.
130
+
131
+ There is also an added assertion for unit tests or specs:
132
+
133
+ assert_masterplan(doc, [{:example => :data})
134
+
135
+ == Use schemes as examples in documentation
136
+
137
+ A problem with webservices is that you need to keep the documentation up to date - something
138
+ that is easily forgotten. If you have a masterplan document, you can use it not only as the template,
139
+ but also as an example in, say, online documentation:
140
+
141
+ <pre>
142
+ <%= JSON.dump(doc.to_hash) %>
143
+ </pre>
144
+
145
+ The to_hash method removes the Masterplan::Rule objects for clean output.
146
+
147
+ == Caveat
148
+
149
+ Note that for the moment, schemes, i.e. the outermost object, can only be hashes.
150
+
151
+ == Installation
152
+
153
+ Currently, you can only install from Github. Add this to your Gemfile:
154
+
155
+ gem 'masterplan', :git => 'git://github.com/traveliq/masterplan.git'
156
+
157
+ If you don't use bundler, you're on your own, sorry.
158
+
159
+ == Authors
160
+
161
+ Martin Tepper (monogreen.de), Holger Pillmann (holger.pillmann@gmail.com), Dr. Florian Odronitz (odo@mac.com)
162
+
163
+ == Contact
164
+
165
+ For questions, contact the authors or developer@traveliq.net
166
+
167
+ == Contributing to masterplan
168
+
169
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
170
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
171
+ * Fork the project
172
+ * Start a feature/bugfix branch
173
+ * Commit and push until you are happy with your contribution
174
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
175
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
176
+
177
+ == Copyright
178
+
179
+ Copyright (c) 2011 www.travel-iq.com. See LICENSE.txt for
180
+ further details.
181
+
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "masterplan"
16
+ gem.homepage = "http://github.com/traveliq/masterplan"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Masterplan is a library for comparing Ruby data structures against predefined templates.}
19
+ gem.description = %Q{Please see the README}
20
+ gem.email = "developer@traveliq.net"
21
+ gem.authors = ["Martin Tepper"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "masterplan #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
data/lib/masterplan.rb ADDED
@@ -0,0 +1,94 @@
1
+ require 'active_support'
2
+ require 'active_support/version'
3
+ if ActiveSupport::VERSION::STRING >= "3.0.0"
4
+ require 'active_support/core_ext'
5
+ end
6
+ require 'test/unit/assertions'
7
+ require 'masterplan'
8
+ require 'masterplan/rule'
9
+ require 'masterplan/document'
10
+ require 'masterplan/define_rules'
11
+ require 'unit_test_extensions'
12
+ module Masterplan
13
+
14
+ class FailedError < Test::Unit::AssertionFailedError
15
+ attr_accessor :printed
16
+ end
17
+
18
+ class << self
19
+
20
+ def compare(options = {:scheme => {}, :to => {}})
21
+ scheme = options[:scheme]
22
+ testee = options[:to]
23
+ raise ArgumentError, ":to needs to be a hash !" unless testee.is_a?(Hash)
24
+ raise ArgumentError, ":scheme needs to be a Masterplan::Document !" unless scheme.is_a?(Document)
25
+ compare_hash(scheme, testee)
26
+ true
27
+ end
28
+
29
+ private
30
+
31
+ def compare_value(template, value, path)
32
+ if template.is_a?(Rule)
33
+ template.masterplan_compare(value, path)
34
+ else
35
+ Rule.check_class_equality!(template, value, path)
36
+ end
37
+ end
38
+
39
+ def compare_hash(template, testee, trail = ["root"])
40
+ template.stringify_keys!
41
+ testee.stringify_keys!
42
+ raise FailedError, "keys don't match in #{format_path(trail)}:\nexpected:\t#{template.keys.sort.join(',')}\nreceived:\t#{testee.keys.sort.join(',')}" if template.keys.sort != testee.keys.sort
43
+ template.each do |t_key, t_value|
44
+ current_path = trail + [t_key]
45
+ value = testee[t_key]
46
+ compare_value(t_value, value, format_path(current_path))
47
+ if value && t_value.is_a?(Array)
48
+ # all array elements need to be of the same type as the first value in the template
49
+ elements_template = t_value.first
50
+ value.each_with_index do |elements_value, index|
51
+ array_path = current_path + [index]
52
+ compare_value(elements_template, elements_value, format_path(array_path))
53
+ if elements_value.is_a?(Hash)
54
+ compare_hash(elements_template, elements_value, array_path)
55
+ end
56
+ end
57
+ end
58
+ if value.is_a?(Array) && t_value.is_a?(Rule) && t_value.options['compare_each']
59
+ value.each_with_index do |elements_value, index|
60
+ elements_template = t_value.example_value[index]
61
+ array_path = current_path + [index]
62
+ compare_value(elements_template, elements_value, format_path(array_path))
63
+ if elements_value.is_a?(Hash)
64
+ compare_hash(elements_template, elements_value, array_path)
65
+ end
66
+ end
67
+ end
68
+ if value.is_a?(Hash)
69
+ if t_value.is_a?(Masterplan::Rule)
70
+ compare_value(t_value, value, current_path)
71
+ compare_hash(t_value.example_value, value, current_path)
72
+ else
73
+ compare_hash(t_value, value, current_path)
74
+ end
75
+ end
76
+ end
77
+
78
+ rescue Masterplan::FailedError => e
79
+ raise e if e.printed
80
+
81
+ error = Masterplan::FailedError.new
82
+ error.printed = true
83
+
84
+ expected = PP.pp(template, '')
85
+ outcome = PP.pp(testee, '')
86
+
87
+ raise error, "#{e.message}\n\nExpected:\n#{expected}\n\nbut was:\n#{outcome}", caller
88
+ end
89
+
90
+ def format_path(trail)
91
+ "'" + trail.join("'=>'") + "'"
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,26 @@
1
+ module Masterplan
2
+
3
+ # Include this module into whatever code generates Masterplan::Documents - you get
4
+ # methods that make it easier to generate Masterplan::Rule objects.
5
+ module DefineRules
6
+
7
+ # This turns the supplied +example_value+ (any object) into an object that carries rules about itself with it.
8
+ # The rules will be applied when a template is compared with assert_masterplan. Rules are:
9
+ # (default): This always applies - the value must be of the same class as the +example_value+
10
+ # 'allow_nil': This allows the value to be nil (breaking the first rule)
11
+ # 'included_in': Pass an array of values - the value must be one of these
12
+ # 'matches': Pass a regexp - the value must match it, and be a String
13
+ def rule(example_value, options = {})
14
+ Rule.new(example_value, options)
15
+ end
16
+
17
+ #for iterating over each example in an array intead of using only the first to compare the data array with
18
+ def iterating_rule(example_value, options = {})
19
+ if example_value
20
+ Rule.new(example_value, :compare_each => true)
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,37 @@
1
+ module Masterplan
2
+ class Document < Hash
3
+
4
+ def initialize(hash = {})
5
+ raise ArgumentError, "Can only work with a Hash, not a #{hash.class.name} !" unless hash.is_a?(Hash)
6
+ hash.each do |k, v|
7
+ self[k] = v
8
+ end
9
+ end
10
+
11
+ # Turns a Masterplan::Document into a plain Hash - this removes all special
12
+ # objects like Masterplan::Rule and replaces them with their example values, so
13
+ # the result can be used as documentation.
14
+ def to_hash
15
+ result = {}
16
+ each do |k, v|
17
+ result[k] = self.class.derulerize(v)
18
+ end
19
+ result
20
+ end
21
+
22
+ private
23
+
24
+ def self.derulerize(object)
25
+ case object
26
+ when Hash
27
+ new(object).to_hash
28
+ when Array
29
+ object.map { |e| derulerize(e) }
30
+ when Masterplan::Rule
31
+ derulerize(object.example_value)
32
+ else
33
+ object
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,99 @@
1
+ module Masterplan
2
+ class Rule
3
+
4
+ OPTIONS = ["allow_nil", "compare_each", "included_in", "matches"]
5
+
6
+ attr_accessor :options, :example_value
7
+
8
+ def initialize(example, options = {})
9
+ options.stringify_keys!
10
+ options['allow_nil'] ||= false
11
+ options['compare_each'] ||= false
12
+ options["included_in"] ||= false
13
+ options["matches"] ||= false
14
+ raise ArgumentError, "options can be #{OPTIONS.join(',')}, not #{options.keys.inspect}" unless options.keys.sort == OPTIONS.sort
15
+ self.example_value = example
16
+ self.options = options
17
+ self.masterplan_compare(example, "initialization of rule")
18
+ end
19
+
20
+ def masterplan_compare(value, path)
21
+ # puts "#{path} #{@masterplan_rule_options.inspect}"
22
+ # puts self.inspect
23
+ # puts value.inspect
24
+ # puts @masterplan_rule_options["included_in"].inspect
25
+ # puts !@masterplan_rule_options["included_in"].include?(value) if @masterplan_rule_options["included_in"]
26
+ return true if masterplan_check_allowed_nil!(value, path)
27
+ return true if masterplan_check_included_in!(value, path)
28
+ return true if masterplan_check_matches!(value, path)
29
+ return true if masterplan_check_class_equality!(value, path)
30
+ end
31
+
32
+ def self.check_class_equality!(template, value, path)
33
+
34
+ value_klass = case value
35
+ when Document
36
+ Hash
37
+ when Rule
38
+ value.example_value.class
39
+ else
40
+ value.class
41
+ end
42
+ template_klass = case template
43
+ when Document
44
+ Hash
45
+ when Rule
46
+ template.example_value.class
47
+ else
48
+ template.class
49
+ end
50
+ if template_klass != value_klass
51
+ raise FailedError, "value at #{path} (#{value_klass}) is not a #{template_klass} !"
52
+ else
53
+ true
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def masterplan_check_class_equality!(value, path)
60
+ self.class.check_class_equality!(self, value, path)
61
+ end
62
+
63
+ def masterplan_check_allowed_nil!(value, path)
64
+ if options['allow_nil']
65
+ if value.nil?
66
+ true
67
+ else
68
+ false
69
+ end
70
+ else
71
+ false
72
+ end
73
+ end
74
+
75
+ def masterplan_check_included_in!(value, path)
76
+ if options["included_in"]
77
+ unless options["included_in"].include?(value)
78
+ raise Masterplan::FailedError, "value at #{path} #{value.inspect} (#{value.class}) is not one of #{options["included_in"].inspect} !"
79
+ else
80
+ true
81
+ end
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ def masterplan_check_matches!(value, path)
88
+ if options["matches"]
89
+ if value !~ options["matches"]
90
+ raise Masterplan::FailedError, "value at #{path} #{value.inspect} (#{value.class}) does not match #{options["matches"].inspect} !"
91
+ else
92
+ true
93
+ end
94
+ else
95
+ false
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,9 @@
1
+ module Test
2
+ module Unit
3
+ module Assertions
4
+ def assert_masterplan(scheme, compare_to)
5
+ Masterplan.compare(:scheme => scheme, :to => compare_to)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,72 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{masterplan}
8
+ s.version = "0.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Martin Tepper"]
12
+ s.date = %q{2011-02-02}
13
+ s.description = %q{Please see the README}
14
+ s.email = %q{developer@traveliq.net}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "lib/masterplan.rb",
29
+ "lib/masterplan/define_rules.rb",
30
+ "lib/masterplan/document.rb",
31
+ "lib/masterplan/rule.rb",
32
+ "lib/unit_test_extensions.rb",
33
+ "masterplan.gemspec",
34
+ "spec/masterplan_spec.rb",
35
+ "spec/spec_helper.rb"
36
+ ]
37
+ s.homepage = %q{http://github.com/traveliq/masterplan}
38
+ s.licenses = ["MIT"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.7}
41
+ s.summary = %q{Masterplan is a library for comparing Ruby data structures against predefined templates.}
42
+ s.test_files = [
43
+ "spec/masterplan_spec.rb",
44
+ "spec/spec_helper.rb"
45
+ ]
46
+
47
+ if s.respond_to? :specification_version then
48
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
49
+ s.specification_version = 3
50
+
51
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
+ s.add_runtime_dependency(%q<activesupport>, ["~> 2.3.5"])
53
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
54
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
55
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
56
+ s.add_development_dependency(%q<rcov>, [">= 0"])
57
+ else
58
+ s.add_dependency(%q<activesupport>, ["~> 2.3.5"])
59
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
60
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
61
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
62
+ s.add_dependency(%q<rcov>, [">= 0"])
63
+ end
64
+ else
65
+ s.add_dependency(%q<activesupport>, ["~> 2.3.5"])
66
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
67
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
68
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
69
+ s.add_dependency(%q<rcov>, [">= 0"])
70
+ end
71
+ end
72
+
@@ -0,0 +1,66 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ include Masterplan::DefineRules
4
+
5
+ describe "Masterplan" do
6
+ before(:each) do
7
+ @scheme = Masterplan::Document.new({
8
+ "ship" => {
9
+ "parts" => [
10
+ {
11
+ "name" => "Mast",
12
+ "length" => rule(12.3, :allow_nil => true),
13
+ "material" => rule("wood", :included_in => ['wood', 'steel', 'human'])
14
+ },
15
+ {
16
+ "name" => "Rudder",
17
+ "length" => nil,
18
+ "material" => "steel"
19
+ }
20
+ ]
21
+ }
22
+ })
23
+ end
24
+
25
+ describe "Testing with #compare" do
26
+
27
+ it "returns true for a valid document, treating symbols and strings alike" do
28
+ Masterplan.compare(
29
+ :scheme => @scheme,
30
+ :to => {
31
+ :ship => {
32
+ :parts => [
33
+ :name => "Thingy",
34
+ :length => 1.0,
35
+ :material => "human"
36
+ ]
37
+ }
38
+ }
39
+ ).should be_true
40
+ end
41
+ it "complains if a key is missing" do
42
+ lambda do
43
+ Masterplan.compare(
44
+ :scheme => @scheme,
45
+ :to => {
46
+ :tank => {}
47
+ }
48
+ )
49
+ end.should raise_error(Masterplan::FailedError, /expected: ship*\n*received: tank/)
50
+ end
51
+ it "complains if not given a Masterplan::Document"
52
+ it "complains if there are extra keys"
53
+ it "complains if a value is of the wrong class"
54
+ it "complains if a value is nil"
55
+ it "does not complain if a value is nil and the rule allows nil"
56
+ it "complains if a value does not match the regexp rule"
57
+ it "complains if a value is not included in the rule list"
58
+ it "checks all values of value arrays, but only against the first array value of the scheme"
59
+ it "checks all array values one-to-one if the compare_each rule is used"
60
+ end
61
+
62
+ it "convertsinto plain example hashes"
63
+ it "doesn't create a Document out of anything other than a Hash"
64
+ it "checks that the examples of rules obey the rules"
65
+ it "has a unit test extension method"
66
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'masterplan'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: masterplan
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
+ platform: ruby
12
+ authors:
13
+ - Martin Tepper
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-02 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ hash: 9
28
+ segments:
29
+ - 2
30
+ - 3
31
+ - 5
32
+ version: 2.3.5
33
+ requirement: *id001
34
+ prerelease: false
35
+ type: :runtime
36
+ name: activesupport
37
+ - !ruby/object:Gem::Dependency
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 2
46
+ - 3
47
+ - 0
48
+ version: 2.3.0
49
+ requirement: *id002
50
+ prerelease: false
51
+ type: :development
52
+ name: rspec
53
+ - !ruby/object:Gem::Dependency
54
+ version_requirements: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 23
60
+ segments:
61
+ - 1
62
+ - 0
63
+ - 0
64
+ version: 1.0.0
65
+ requirement: *id003
66
+ prerelease: false
67
+ type: :development
68
+ name: bundler
69
+ - !ruby/object:Gem::Dependency
70
+ version_requirements: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ hash: 7
76
+ segments:
77
+ - 1
78
+ - 5
79
+ - 2
80
+ version: 1.5.2
81
+ requirement: *id004
82
+ prerelease: false
83
+ type: :development
84
+ name: jeweler
85
+ - !ruby/object:Gem::Dependency
86
+ version_requirements: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ requirement: *id005
96
+ prerelease: false
97
+ type: :development
98
+ name: rcov
99
+ description: Please see the README
100
+ email: developer@traveliq.net
101
+ executables: []
102
+
103
+ extensions: []
104
+
105
+ extra_rdoc_files:
106
+ - LICENSE.txt
107
+ - README.rdoc
108
+ files:
109
+ - .document
110
+ - .rspec
111
+ - Gemfile
112
+ - Gemfile.lock
113
+ - LICENSE.txt
114
+ - README.rdoc
115
+ - Rakefile
116
+ - VERSION
117
+ - lib/masterplan.rb
118
+ - lib/masterplan/define_rules.rb
119
+ - lib/masterplan/document.rb
120
+ - lib/masterplan/rule.rb
121
+ - lib/unit_test_extensions.rb
122
+ - masterplan.gemspec
123
+ - spec/masterplan_spec.rb
124
+ - spec/spec_helper.rb
125
+ has_rdoc: true
126
+ homepage: http://github.com/traveliq/masterplan
127
+ licenses:
128
+ - MIT
129
+ post_install_message:
130
+ rdoc_options: []
131
+
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ hash: 3
140
+ segments:
141
+ - 0
142
+ version: "0"
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ hash: 3
149
+ segments:
150
+ - 0
151
+ version: "0"
152
+ requirements: []
153
+
154
+ rubyforge_project:
155
+ rubygems_version: 1.3.7
156
+ signing_key:
157
+ specification_version: 3
158
+ summary: Masterplan is a library for comparing Ruby data structures against predefined templates.
159
+ test_files:
160
+ - spec/masterplan_spec.rb
161
+ - spec/spec_helper.rb