higher_expectations 0.0.1

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/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-08-14
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (C) 2008 Justin Tyler Wiley
2
+
3
+ This program is free software: you can redistribute it and/or modify
4
+ it under the terms of the GNU General Public License as published by
5
+ the Free Software Foundation, either version 3 of the License, or
6
+ (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU General Public License for more details.
12
+
13
+ You should have received a copy of the GNU General Public License
14
+ along with this program. If not, see <http://www.gnu.org/licenses/>
data/Manifest.txt ADDED
@@ -0,0 +1,30 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/higher_expectations.rb
9
+ lib/instance_methods.rb
10
+ lib/version.rb
11
+ script/console
12
+ script/console.cmd
13
+ script/destroy
14
+ script/destroy.cmd
15
+ script/generate
16
+ script/generate.cmd
17
+ script/txt2html
18
+ script/txt2html.cmd
19
+ setup.rb
20
+ tasks/deployment.rake
21
+ tasks/environment.rake
22
+ tasks/website.rake
23
+ spec/higher_expectations_spec.rb
24
+ spec/instance_methods_spec.rb
25
+ spec/spec_helper.rb
26
+ website/index.html
27
+ website/index.txt
28
+ website/javascripts/rounded_corners_lite.inc.js
29
+ website/stylesheets/screen.css
30
+ website/template.html.erb
data/README.txt ADDED
@@ -0,0 +1,155 @@
1
+ = higher_expectations
2
+
3
+ * http://higher_expectations.rubyforge
4
+
5
+ == DESCRIPTION:
6
+
7
+ Provides an easy and quick way to make sure method arguments are what you expect them to be.
8
+
9
+ You want to make sure that methods explode if they are given inappropriate inputs, but you don't want to deal with a complete design-by-contract implementation like RDBC. Please note that this is nothing like a formal design-by-contract in any number of important ways. It provides something -like- the "obligations" component of DBC.
10
+
11
+ Writing explicit exception checking is tiring, redundant and error prone:
12
+
13
+ def calc_sunrise(day, month, year, latitude, longitude, planet)
14
+ raise Exception.new("day should be numeric and in the range of 1-32) unless day.kind_of?(Numeric) && day > 0 && day < 32
15
+ ...etc. etc. ...
16
+ end
17
+
18
+ Higher expectations provides an easy and human readable alternative:
19
+
20
+ def calc_sunrise(day, month, year)
21
+ has_expectations(day, month)
22
+ day.must_not_be(Numeric).and_must_be_in_range(0..5)
23
+ month.must_be(Numeric)
24
+ ...do other critical work below...
25
+ end
26
+
27
+ == FEATURES/PROBLEMS:
28
+
29
+ * Please note that this is alpha software
30
+ * Provides a set of usefull methods for determining what an object is at runtime, and raising an exception
31
+ * Avoids creating these methods in Object directly, and instead extends the objects passed in (although it does add them directly to Numeric due to constraints in Ruby's Numeric implementation)
32
+ * Allows for method changing and provides a dose of syntactic sugar
33
+
34
+ == SYNOPSIS:
35
+
36
+ Imagine you have a method to calculate sunrise buried within a 1D planet simulator codebase. Throughout the codebase, validations are used to check data input, and there is a well thought-out and well written test suite.
37
+
38
+ def calc_sunrise(day, month)
39
+ sunrise = (day - 50000)/month # arbitrary calculation that assumes day is a number and not negative
40
+ end
41
+
42
+ However, Joey your coworker hacks and calls the following function:
43
+
44
+ PlanetEarth.sunrise = calc_sunrise(-5, 1)
45
+
46
+ Code executes, no exceptions are raised, but earths sunrise changes to a weird value. No amount of unit testing, specing, validating outside the model would have stopped Joey from making this hambone maneuver.
47
+
48
+ Now you could have said something like:
49
+
50
+ def calc_sunrise(day, month)
51
+ raise ArgumentError.new("day must be numeric") unless day.kind_of?(Numeric)
52
+ raise ArgumentError.new("day must be in range of 1-31") unless day > 1 && day < 31
53
+ raise ArgumentError.new("month must be numeric") unless month.kind_of?(Numeric)
54
+ raise ArgumentError.new("month must be in range of 1-31") unless month > 1 && day < 31 # note subtle bug
55
+ raise ArgumentError.new("month must not be nil") unless month > 1 && month < 31
56
+ ...sunrise calc...
57
+ end
58
+
59
+ Drudgery, duplication, error prone, etc. etc. Wouldn't you like to do this instead?
60
+
61
+ include HigherExpectations # somewhere in the class
62
+ def calc_sunrise(day, month)
63
+ has_expectations(day, month) # attach expectation methods to each object
64
+ day.must_be(Numeric) # day must be numeric or an exception will be raise
65
+ day.must_be_in_range(1..31) # day must be in range or exception
66
+ month.must_be(Numeric).and_must_be_in_range(1..31) # a neat combination of both
67
+ month.must_be_nil rescue nil # since it raises an exception, its trappable, allowing for more flexible handling
68
+ ...sunrise calc
69
+ end
70
+
71
+ ...or even cleaner.
72
+
73
+ def calc_sunrise(day,month)
74
+ has_expectations(day,month)
75
+ *args.map{|a| a.must_be(Numeric).and_must_be_in_range(1..31)}
76
+ month.must_be_nil
77
+ ...sunrise calc...
78
+ end
79
+
80
+ See spec below for details on methods possible.
81
+
82
+ Copyright (c) 2008 Justin Tyler Wiley (justintylerwiley.com), under GPL V3
83
+
84
+ == SPEC:
85
+
86
+ HigherExpectations
87
+ - should not raise an exception when included
88
+
89
+ HigherExpectations#has_expectations
90
+ - should loop through given objects, extending each with instance_expectations.rb methods
91
+
92
+ InstanceMethods
93
+
94
+ InstanceMethods#raise_ae
95
+ - should raise an ArgumentException with the given message
96
+
97
+ InstanceMethods#must_be_/must_not_be_ - true, false, and nil
98
+ - should raise exception if expected to be true false or nil, and they are not
99
+ - should not raise exception if expected to be true false or nil, and they are
100
+
101
+ InstanceMethods#must_be and must_not_be a particular value
102
+ - should raise exception if item must be something and isnt
103
+ - should not raise exception if item must be something and IS
104
+ - (#not) should raise exception if item must not be something and IS
105
+ - (#not) should not raise exception if item must not be something and is not
106
+
107
+ InstanceMethods#must_be_a and must_not_be_a particular class of object
108
+ - should raise exception if item must be a class and isnt
109
+ - should not raise exception if item must be a class and IS
110
+ - (#not) should raise exception if item must not be a class and is not
111
+ - (#not) should not raise exception if item must not be a class and IS
112
+
113
+ InstanceMethods#must_be_in_range and must_not_be_in_range of numbers
114
+ - should raise exception if item must be in a range and isnt
115
+ - should not raise exception if item must be in a range and is
116
+ - (#not) should raise exception if item in a range
117
+ - (#not) should not raise exception if item not in range
118
+ - should raise HigherExpectation exception if handed something besides and array or a range
119
+
120
+ InstanceMethods#must_match and must_not_match a given pattern
121
+ - should raise exception if item does not match
122
+ - should not raise exception if item matches
123
+ - (#not) should not raise exception if item does not match
124
+ - (#not) should raise exception if item matches
125
+
126
+ InstanceMethods#must_respond_to and must_not_respond_to a given method
127
+ - should raise exception if item does not respond
128
+ - should not raise exception if item respond
129
+ - (#not) should raise exception if item responds
130
+ - (#not) should not raise exception if does respond
131
+
132
+ == REQUIREMENTS:
133
+
134
+ * hoe
135
+
136
+ == INSTALL:
137
+
138
+ * sudo gem install higher-expectations
139
+
140
+ == LICENSE:
141
+
142
+ Copyright (C) 2008 Justin Tyler Wiley
143
+
144
+ This program is free software: you can redistribute it and/or modify
145
+ it under the terms of the GNU General Public License as published by
146
+ the Free Software Foundation, either version 3 of the License, or
147
+ (at your option) any later version.
148
+
149
+ This program is distributed in the hope that it will be useful,
150
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
151
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
152
+ GNU General Public License for more details.
153
+
154
+ You should have received a copy of the GNU General Public License
155
+ along with this program. If not, see <http://www.gnu.org/licenses/>
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/config/hoe.rb ADDED
@@ -0,0 +1,73 @@
1
+ require 'higher_expectations/version'
2
+
3
+ AUTHOR = 'FIXME full name' # can also be an array of Authors
4
+ EMAIL = "FIXME email"
5
+ DESCRIPTION = "description of gem"
6
+ GEM_NAME = 'higher_expectations' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'higher_expectations' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+ EXTRA_DEPENDENCIES = [
11
+ # ['activesupport', '>= 1.3.1']
12
+ ] # An array of rubygem dependencies [name, version]
13
+
14
+ @config_file = "~/.rubyforge/user-config.yml"
15
+ @config = nil
16
+ RUBYFORGE_USERNAME = "unknown"
17
+ def rubyforge_username
18
+ unless @config
19
+ begin
20
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
21
+ rescue
22
+ puts <<-EOS
23
+ ERROR: No rubyforge config file found: #{@config_file}
24
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
25
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
26
+ EOS
27
+ exit
28
+ end
29
+ end
30
+ RUBYFORGE_USERNAME.replace @config["username"]
31
+ end
32
+
33
+
34
+ REV = nil
35
+ # UNCOMMENT IF REQUIRED:
36
+ # REV = YAML.load(`svn info`)['Revision']
37
+ VERS = HigherExpectations::VERSION::STRING + (REV ? ".#{REV}" : "")
38
+ RDOC_OPTS = ['--quiet', '--title', 'higher_expectations documentation',
39
+ "--opname", "index.html",
40
+ "--line-numbers",
41
+ "--main", "README",
42
+ "--inline-source"]
43
+
44
+ class Hoe
45
+ def extra_deps
46
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
47
+ @extra_deps
48
+ end
49
+ end
50
+
51
+ # Generate all the Rake tasks
52
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
53
+ $hoe = Hoe.new(GEM_NAME, VERS) do |p|
54
+ p.developer(AUTHOR, EMAIL)
55
+ p.description = DESCRIPTION
56
+ p.summary = DESCRIPTION
57
+ p.url = HOMEPATH
58
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
59
+ p.test_globs = ["test/**/test_*.rb"]
60
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
61
+
62
+ # == Optional
63
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
64
+ #p.extra_deps = EXTRA_DEPENDENCIES
65
+
66
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
67
+ end
68
+
69
+ CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
70
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
71
+ $hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
72
+ $hoe.rsync_args = '-av --delete --ignore-errors'
73
+ $hoe.spec.post_install_message = File.open(File.dirname(__FILE__) + "/../PostInstall.txt").read rescue ""
@@ -0,0 +1,15 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
@@ -0,0 +1,37 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'rubygems'
3
+ require 'instance_methods'
4
+
5
+ ##
6
+ # Higher expectations module
7
+ # Provides method to load instance methods in a group of arguments
8
+ # Requires instance methods
9
+ #
10
+ # Usage:
11
+ #
12
+ # class Something
13
+ # include HigherExpectations
14
+ # end
15
+ # ..or optionally inject into Object (not recommended)
16
+ #
17
+ module HigherExpectations
18
+ VERSION = '0.1.0'
19
+
20
+ # Should be called at method entrance
21
+ # instead of spaming up entire Object class method space, extend each given object with expectation methods
22
+ def has_expectations(*objects)
23
+ objects.map do |obj|
24
+ begin
25
+ obj.extend(HigherExpectations::InstanceMethods)
26
+ # TypeErrors are generated when trying to extend Numeric objects, which are not and cannot be singeltons and hence cannot get new methods.
27
+ # Handled below via Numeric
28
+ rescue TypeError
29
+ next
30
+ end
31
+ end
32
+ end
33
+ alias :has_expectation :has_expectations
34
+ end
35
+
36
+ # Provides instance methods for Fixnum class, which due to limitiations mentioned above must be treated as a special case
37
+ class Fixnum; include HigherExpectations::InstanceMethods; end;
@@ -0,0 +1,103 @@
1
+ module HigherExpectations
2
+ module InstanceMethods
3
+ # must_X functions raise the traditional "ArgumentException", this exception is for programic errors when using HigherExpectations
4
+ class HigherExpectationException < Exception; end;
5
+
6
+ # defines must_be and must_not_be nil, true, false
7
+ [true, false].each do |value|
8
+ send :define_method, "must_be_#{value.to_s}".to_sym, lambda {
9
+ raise_ae(" to be #{value.to_s}") unless self == value; self;
10
+ }
11
+ send :define_method, "must_not_be_#{value.to_s}".to_sym, lambda {
12
+ raise_ae(" to be #{value.to_s}") unless self != value; self;
13
+ }
14
+ end
15
+
16
+ # value must be nil, or must not be nil respectively
17
+ def must_be_nil; raise_ae(" to be nil") unless self == nil; end;
18
+ alias :and_must_be_nil :must_be_nil
19
+ def must_not_be_nil; raise_ae(" to be nil") unless self != nil; end;
20
+ alias :and_must_not_be_nil :must_not_be_nil
21
+
22
+ # raise ArgumentError exception
23
+ def raise_ae(message)
24
+ raise ArgumentError.new("Method expects this argument" + message.to_s)
25
+ end
26
+
27
+ # argument must be the given value
28
+ def must_be(*values)
29
+ values.each {|value| raise_ae(" must equal #{value}") unless self == value; }
30
+ self # allow for method concatonation "foo.must_be(String).and_must_not_be_nil
31
+ end
32
+ alias :and_must_be :must_be
33
+
34
+ # argument must NOT be the given value
35
+ def must_not_be(*values)
36
+ values.each {|value| raise_ae(" must NOT equal #{value}") unless self != value; }
37
+ self
38
+ end
39
+ alias :and_must_not_be :must_not_be
40
+
41
+ # value(s) must be a specific class or in given set of classes
42
+ def must_be_a(*klasses)
43
+ klasses.each do |klass|
44
+ raise_ae(" should be #{klass.to_s}") unless self.kind_of?(klass)
45
+ end
46
+ self
47
+ end
48
+ alias :and_must_be_a :must_be_a
49
+
50
+ # value(s) must not be in a specific class or set of klasses
51
+ def must_not_be_a(*klasses)
52
+ klasses.each do |klass|
53
+ raise_ae(" should NOT be #{klass.to_s}") if self.kind_of?(klass)
54
+ end
55
+ self
56
+ end
57
+ alias :and_must_not_be_a :must_not_be_a
58
+
59
+ # value(s) must be in a specific numeric range
60
+ def must_be_in_range(range)
61
+ raise HigherExpectationException.new("Must pass in two values ('foo.must_be_in_range(0,5)')") unless range.kind_of?(Range) || range.kind_of?(Array)
62
+ raise_ae("'s value to be in the range of #{range.first} to #{range.last} (it was #{self.to_s})") unless self >= range.first && self <= range.last
63
+ self
64
+ end
65
+ alias :and_must_be_in_range :must_be_in_range
66
+
67
+ # value(s) must be in a specific numeric range
68
+ def must_not_be_in_range(range)
69
+ raise HigherExpectationException.new("Must pass in two values ('foo.must_be_in_range(0,5)')") unless range.kind_of?(Range) || range.kind_of?(Array)
70
+ raise_ae("'s value NOT to be in the range of #{range.first} to #{range.last} (it was #{self.to_s})") if self >= range.first && self <= range.last
71
+ self
72
+ end
73
+ alias :and_must_not_be_in_range :must_not_be_in_range
74
+
75
+ def must_match(pattern)
76
+ raise_ae(" to match pattern #{pattern.to_s}") unless self =~ pattern
77
+ self
78
+ end
79
+ alias :and_must_match :must_match
80
+
81
+ def must_not_match(pattern)
82
+ raise_ae(" to match pattern #{pattern.to_s}") if self =~ pattern
83
+ self
84
+ end
85
+ alias :and_must_not_match :must_not_match
86
+
87
+ def must_respond_to(*meths)
88
+ meths.each do |meth|
89
+ raise_ae(" to respond to #{meth}") unless self.respond_to?(meth.to_s)
90
+ end
91
+ self
92
+ end
93
+ alias :and_must_respond_to :must_respond_to
94
+
95
+ def must_not_respond_to(*meths)
96
+ meths.each do |meth|
97
+ raise_ae(" to respond to #{meth}") if self.respond_to?(meth.to_s)
98
+ end
99
+ self
100
+ end
101
+ alias :and_must_not_respond_to :must_not_respond_to
102
+ end
103
+ end