poro_validator 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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +13 -0
  3. data/Gemfile +11 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +382 -0
  6. data/Rakefile +139 -0
  7. data/lib/poro_validator.rb +54 -0
  8. data/lib/poro_validator/configuration.rb +63 -0
  9. data/lib/poro_validator/error_store.rb +52 -0
  10. data/lib/poro_validator/errors.rb +56 -0
  11. data/lib/poro_validator/exceptions.rb +14 -0
  12. data/lib/poro_validator/validator.rb +82 -0
  13. data/lib/poro_validator/validator/base_class.rb +55 -0
  14. data/lib/poro_validator/validator/conditions.rb +71 -0
  15. data/lib/poro_validator/validator/context.rb +19 -0
  16. data/lib/poro_validator/validator/factory.rb +32 -0
  17. data/lib/poro_validator/validator/validation.rb +50 -0
  18. data/lib/poro_validator/validator/validations.rb +55 -0
  19. data/lib/poro_validator/validators/base_class.rb +53 -0
  20. data/lib/poro_validator/validators/exclusion_validator.rb +26 -0
  21. data/lib/poro_validator/validators/float_validator.rb +17 -0
  22. data/lib/poro_validator/validators/format_validator.rb +16 -0
  23. data/lib/poro_validator/validators/inclusion_validator.rb +26 -0
  24. data/lib/poro_validator/validators/integer_validator.rb +17 -0
  25. data/lib/poro_validator/validators/length_validator.rb +41 -0
  26. data/lib/poro_validator/validators/numeric_validator.rb +48 -0
  27. data/lib/poro_validator/validators/presence_validator.rb +23 -0
  28. data/lib/poro_validator/validators/range_array_validator.rb +19 -0
  29. data/lib/poro_validator/validators/with_validator.rb +21 -0
  30. data/lib/poro_validator/version.rb +3 -0
  31. data/poro_validator.gemspec +97 -0
  32. data/spec/features/composable_validations_spec.rb +26 -0
  33. data/spec/features/inheritable_spec.rb +29 -0
  34. data/spec/features/nested_validations_spec.rb +136 -0
  35. data/spec/lib/poro_validator/configuration_spec.rb +37 -0
  36. data/spec/lib/poro_validator/error_store_spec.rb +125 -0
  37. data/spec/lib/poro_validator/errors_spec.rb +79 -0
  38. data/spec/lib/poro_validator/validator/base_class_spec.rb +62 -0
  39. data/spec/lib/poro_validator/validator/conditions_spec.rb +112 -0
  40. data/spec/lib/poro_validator/validator/factory_spec.rb +23 -0
  41. data/spec/lib/poro_validator/validator/validation_spec.rb +69 -0
  42. data/spec/lib/poro_validator/validator/validations_spec.rb +34 -0
  43. data/spec/lib/poro_validator/validator_spec.rb +55 -0
  44. data/spec/lib/poro_validator/validators/base_class_spec.rb +11 -0
  45. data/spec/lib/poro_validator/validators/exclusion_validator_spec.rb +81 -0
  46. data/spec/lib/poro_validator/validators/float_validator_spec.rb +43 -0
  47. data/spec/lib/poro_validator/validators/format_validator_spec.rb +48 -0
  48. data/spec/lib/poro_validator/validators/inclusion_validator_spec.rb +81 -0
  49. data/spec/lib/poro_validator/validators/integer_validator_spec.rb +43 -0
  50. data/spec/lib/poro_validator/validators/length_validator_spec.rb +64 -0
  51. data/spec/lib/poro_validator/validators/numeric_validator_spec.rb +68 -0
  52. data/spec/lib/poro_validator/validators/presence_validator_spec.rb +52 -0
  53. data/spec/lib/poro_validator/validators/with_validator_spec.rb +90 -0
  54. data/spec/poro_validator_spec.rb +25 -0
  55. data/spec/spec_helper.rb +34 -0
  56. data/spec/support/spec_helpers/concerns.rb +46 -0
  57. data/spec/support/spec_helpers/validator_test_macros.rb +99 -0
  58. metadata +199 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 072a8e275c97616be8b4b507107f78f01738b5bb
4
+ data.tar.gz: 278dc6e7841a98f5e713e0257096ab30aa5a7658
5
+ SHA512:
6
+ metadata.gz: 982f54f710bf94743d207f6be12375d3a4b7a9f287c37a4962f981d6c49b172c61d436c69ede5db32df11d8c4536328d9d1039c957169a31b7f3fb4d4c1bd313
7
+ data.tar.gz: 868f211a49e50d7be95c3c2d259db8d9e6f52b6c80c72a6680db16bd59808561311b1b7fcf4c648b05ca54be5a85fe4c38e1677ba61dba9b08aa159ef9902e7a
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :tools do
6
+ gem 'pry'
7
+ gem 'pry-byebug'
8
+ gem 'pry-rescue', github: 'ConradIrwin/pry-rescue'
9
+ gem 'pry-stack_explorer'
10
+ gem 'ruby-graphviz'
11
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Kareem Gan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,382 @@
1
+ # PORO Validator #
2
+
3
+ [![Gem Version][GV img]][Gem Version]
4
+ [![Build Status][BS img]][Build Status]
5
+ [![Dependency Status][DS img]][Dependency Status]
6
+ [![Code Climate][CC img]][Code Climate]
7
+ [![Coverage Status][CS img]][Coverage Status]
8
+
9
+ PoroValidator is a lightweight validation library for your
10
+ **P**lain **O**ld **R**uby **O**bjects (hence PoroValidator).
11
+
12
+ I always believed that validation is a seperate concern and should not be defined in the object
13
+ that you are going to be validating. This validator library aims to seperate the validation to a
14
+ seperate concern giving it great flexibility and scalability.
15
+
16
+ It is framework agnostic and can be used on any plain old ruby object/entities.
17
+
18
+ #### ActiveRecord/ActiveModel::Validations
19
+ While [ActiveModel::Validations] is great if you've got simple validation logic,
20
+ it doesn't cut it for complex validations needs. When you have different validation
21
+ for the same object at each point in it's life cycle, you need something more flexible.
22
+
23
+ The problem with [ActiveModel::Validations] is that it hooks pretty deep into [ActiveRecord].
24
+ The main use case for [ActiveModel::Validations] is to prevent bad data hitting your
25
+ database - which isn't always the case (sometimes we want to allow bad data to go through).
26
+ PoroValidator decouples your validation logic from your object structure. With
27
+ PoroValidator you can define different validation rules for different contexts.
28
+ So instead of *objective* validation where the validation is defined in the object
29
+ you want to validate we define it in a seperate class making it *subjective*.
30
+
31
+ ## Features ##
32
+ - **Familiar, simple and consistent API.**
33
+ - **Framework agnostic, all you need is a PORO**.
34
+ - **Validation logic is decoupled from business logic by creating seperate *validator* classes
35
+ which allows easy testing for the validator class**.
36
+ - **No magic, caller is in control.**
37
+ - **Invalid validations does not necessitate inability to persist to database**.
38
+ - [**Conditional Validations via `:if` and `:unless`.**](#conditional-validations)
39
+ - [**Nested validations for nested object structures - nicely handling nested errors.**](#nested-validations)
40
+ - [**Composable validations by reusing existing validators.**](#composable-validations)
41
+ - [**Create custom validators.**](#custom-validators)
42
+ - **Overrideable messages either via the ```:message``` option or through the configuration.**
43
+ - **Easily test validator classes.**
44
+
45
+ ## Installation ##
46
+
47
+ Add this line to your application's Gemfile:
48
+
49
+ ```ruby
50
+ gem 'poro_validator'
51
+ ```
52
+
53
+ And then execute:
54
+
55
+ ```ruby
56
+ $ bundle install
57
+ ```
58
+
59
+ Or install it yourself as:
60
+
61
+ ```ruby
62
+ $ gem install poro_validator
63
+ ```
64
+
65
+ ## Usage ##
66
+
67
+ ### Creating and using the validator ###
68
+ ```ruby
69
+ # Create a validator
70
+ class CustomerValidator
71
+ include PoroValidator.validator
72
+
73
+ validates :last_name, presence: true
74
+ validates :first_name, presence: true
75
+ validates :age, numeric: { min: 18 }
76
+ end
77
+
78
+ customer = CustomerDetail.new
79
+
80
+ # Validate entity
81
+
82
+ validator = CustomerValidator.new
83
+ validator.valid?(customer) # => false
84
+ validator.errors.full_messages # => ["last name is not present", "..."]
85
+ ```
86
+
87
+ ## Validators ##
88
+
89
+ ### Default Validators ###
90
+ - [exclusion_validator](#exclusion-validator)
91
+ - [float_validator](#float-validator)
92
+ - [format_validator](#format-validator)
93
+ - [inclusion_validator](#inclusion-validator)
94
+ - [integer_validator](#integer-validator)
95
+ - [length_validator](#length-validator)
96
+ - [numeric_validator](#numeric-validator)
97
+ - [presence_validator](#presence-validator)
98
+ - [with_validator](#with-validator)
99
+
100
+ #### Exclusion Validator ####
101
+ ##### Option:
102
+ - `in:` responds to an Array, Range or Set
103
+
104
+ ```
105
+ validates :foo, exclusion: 5..10
106
+ validates :boo, exclusion: [1,2,3,4,5]
107
+ validates :zoo, exclusion: { in: [1,2,3,4,5] }
108
+ validates :moo, exclusion: 5..10, if: proc { false }
109
+ ```
110
+
111
+ #### Float Validator ####
112
+ ```
113
+ validates :foo, float: true
114
+ validates :boo, float: true, if: proc { false }
115
+ ```
116
+
117
+ #### Format Validator ####
118
+ ##### Option
119
+ - `with` pass in regex or string
120
+
121
+ ```
122
+ validates :foo, format: /[a-z]/
123
+ validates :boo, format: { with: /[a-z]/ }
124
+ ```
125
+
126
+ #### Inclusion Validator ####
127
+ ##### Option
128
+ ```
129
+ validates :foo, inclusion: 1..10
130
+ validates :boo, inclusion: { in: [1,2,3,4,5] }
131
+ validates :zoo, inclusion: { in: 1..10 }
132
+ ```
133
+
134
+ #### Integer Validator ####
135
+ ```
136
+ validates :foo, integer: true
137
+ validates :boo, integer: true, if: proc { false }
138
+ ```
139
+
140
+ #### Length Validator ####
141
+ ##### Value must be either a string or can be casted as one
142
+ ##### Options:
143
+ - `extremum:` min and max
144
+ - `min:` minimum
145
+ - `max:` maximum
146
+
147
+ ```
148
+ validates :foo, length: 1..10
149
+ validates :boo, length: { extremum: 1 }
150
+ validates :zoo, length: { min: 10, max: 20 }
151
+ validates :moo, length: { min: 10 }
152
+ validates :goo, length: { max: 10 }
153
+ validates :loo, length: 1..10, if: proc { false }
154
+ ```
155
+
156
+ #### Numeric Validator ####
157
+ ##### Value must be either an integer or can be casted as one
158
+ ##### Options:
159
+ - `extremum:` min and max
160
+ - `min:` minimum
161
+ - `max:` maximum
162
+
163
+ ```
164
+ validates :foo, numeric: 1..10
165
+ validates :boo, numeric: { extremum: 5 }
166
+ validates :zoo, numeric: { min: 10, max: 20 }
167
+ validates :moo, numeric: { min: 10 }
168
+ validates :goo, numeric: { max: 20 }
169
+ validates :loo, numeric: 1..10, if: proc { false }
170
+ ```
171
+
172
+ #### Presence Validator ####
173
+ ```
174
+ validates :foo, presence: true
175
+ validates :boo, presence: true, if: proc { false }
176
+ ```
177
+
178
+ #### With Validator ####
179
+ Options:
180
+ - `:with` requires an existing validator class to be passed
181
+
182
+ ```
183
+ validates :foo, with: ExistingValidator
184
+ ```
185
+
186
+ ### Custom Validators ###
187
+ Creating validators is easy! Just follow the example below!
188
+
189
+ ```ruby
190
+ module PoroValidator
191
+ module Validators
192
+ class CustomValidator < BaseClass
193
+
194
+ def validate(attribute, value, options)
195
+ message = options[:message] || "custom validator message"
196
+ # your validation logic code goes here..
197
+
198
+ # add error message if validation fails
199
+ errors.add(attribute, message, options)
200
+ end
201
+ end
202
+ end
203
+ end
204
+ ```
205
+
206
+ #### Note when creating custom validators
207
+ You can either define the error message in your validator like shown above or
208
+ define it via the [PoroValidator.configuration](#error-messages)
209
+
210
+ ### Error Messages ###
211
+
212
+ #### #configure
213
+ The ```message``` configuration object, allows you to change the default error message produced by each validator. The message must be in the form of a lambda or Proc, and may or may not receive an argument. Use the example below for reference when customizing messages.
214
+
215
+ ```ruby
216
+ PoroValidator.configure do |config|
217
+ config.message.set(:numeric, lambda { "is not a number" })
218
+ config.message.set(:presence, lambda { "is not present" })
219
+ config.message.set(:inclusion, lambda { |set| "not in the set: #{set.inspect}")
220
+ ...
221
+ end
222
+ ```
223
+
224
+ #### #on method
225
+ The `on` method is used to acccess the error messages related to a key or attribute/method.
226
+
227
+ ##### unnested validations
228
+ Pass in either a symbol or a string
229
+
230
+ ```ruby
231
+ validator.errors.on(:attribute) || validator.errors.on("attribute")
232
+ ```
233
+
234
+ ##### nested validations
235
+ Pass in a nested hash structure reflective of the object that was validated
236
+
237
+ ```ruby
238
+ validator.errors.on({address: :line1})
239
+ validator.errors.on({address: {city: :locality}})
240
+ validator.errors.on({address: {country: {coordinates: {planent: :name}}}})
241
+ ```
242
+
243
+ ## Conditional Validations ##
244
+ You can pass in conditional
245
+
246
+ ```ruby
247
+ class CustomerValidator
248
+ include PoroValidator.validator
249
+
250
+ validates :last_name, presence: { unless: :foo_condition }
251
+ validates :first_name, presence: { if: lambda { true } }
252
+ validates :dob, presence: { if: :method_in_the_validator_class }
253
+ validates :age, presence: { if: :entity_method }
254
+ validates :address do
255
+ validates :line1, presence: { if: 'entity.nested_entity.method' }
256
+ end
257
+ ```
258
+
259
+ ## Nested Validations ##
260
+ ```ruby
261
+ # validator
262
+ class CustomerDetailValidator
263
+ include PoroValidator.validator
264
+
265
+ validates :customer_id, presence: true
266
+
267
+ validates :customer do
268
+ validates :first_name, presence: true
269
+ validates :last_name, presence: true
270
+ end
271
+
272
+ validates :address do
273
+ validates :line1, presence: true
274
+ validates :line2, presence: true
275
+ validates :city, presence: true
276
+ validates :country do
277
+ validates :iso_code, presence: true
278
+ validates :short_name, presence: true
279
+ validates :coordinates do
280
+ validates :longtitude, presence: true
281
+ validates :latitude, presence:true
282
+ validates :planet do
283
+ validates :name, presence: true
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ entity = CustomerDetailEntity.new
291
+ validator = CustomerDetailValidator.new
292
+ validator.valid?(entity)
293
+ validator.errors.full_messages # => [
294
+ "customer_id is not present",
295
+ "{:customer=>:first_name} is not present",
296
+ "{:customer=>:last_name} is not present",
297
+ "{:address=>{:country=>{:coordinates=>:longtitude}}} is not present",
298
+ "{:address=>{:country=>{:coordinates=>:latitude}}} is not present",
299
+ "{:address=>{:country=>{:coordinates=>{:planet=>:name}}}} is not present"
300
+ ]
301
+ ```
302
+
303
+ ## Composable Validations ##
304
+
305
+ ```ruby
306
+ # Create a validators
307
+ class CustomerValidator
308
+ include PoroValidator.validator
309
+
310
+ validates :last_name, presence: true
311
+ validates :first_name, presence: true
312
+ validates :age, numeric: { min: 18 }
313
+ end
314
+
315
+ class AddressValidator
316
+ include PoroValidator.validator
317
+
318
+ validates :line1, presence: true
319
+ validates :lin2, presence: true
320
+ validates :city, presence: true
321
+ validates :state, presence: true
322
+ validates :zip_code, format: /[0-9]/
323
+ end
324
+
325
+ # Create another validator and use the existing validator class as an option
326
+
327
+ class CustomerValidator
328
+ include PoroValidator.validator
329
+
330
+ validates :customer, with: CustomerValidator
331
+ validates :address, with: AddressValidator
332
+ end
333
+
334
+ # Create an entities
335
+ class CustomerDetailEntity
336
+ attr_accessor :customer
337
+ attr_accessor :address
338
+ end
339
+
340
+ customer_detail = CustomerDetailEntity.new
341
+
342
+ # Validate entity
343
+
344
+ validator = CustomerValidator.new
345
+ validator.valid?(customer_detail) # => false
346
+ validator.errors.full_messages # => [
347
+ "customer" => "last name is not present", ".."
348
+ "address" => "line1 is not present", ".."
349
+ ]
350
+ ```
351
+
352
+ ## Maintainers
353
+
354
+ - [magicalbanana](https://github.com/magicalbanana)
355
+
356
+ ## Contributing
357
+
358
+ Bug reports and pull requests are welcome on GitHub at https://github.com/magicalbanana/poro_validator. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
359
+
360
+ ## License
361
+
362
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
363
+
364
+ ## Copyright
365
+
366
+ Copyright (c) 2015 Kareem Gan
367
+
368
+ [Gem Version]: https://badge.fury.io/rb/poro_validator
369
+ [Build Status]: https://travis-ci.org/magicalbanana/poro_validator
370
+ [travis pull requests]: https://travis-ci.org/magicalbanana/poro_validator/pull_requests
371
+ [Dependency Status]: https://gemnasium.com/magicalbanana/poro_validator
372
+ [Code Climate]: https://codeclimate.com/github/magicalbanana/poro_validator
373
+ [Coverage Status]: https://coveralls.io/r/magicalbanana/poro_validator
374
+
375
+ [GV img]: https://badge.fury.io/rb/poro_validator.svg
376
+ [BS img]: https://travis-ci.org/magicalbanana/poro_validator.svg
377
+ [DS img]: https://gemnasium.com/magicalbanana/poro_validator.svg
378
+ [CC img]: https://codeclimate.com/github/magicalbanana/poro_validator.svg
379
+ [CS img]: https://coveralls.io/repos/magicalbanana/poro_validator/badge.svg?branch=master&service=github
380
+
381
+ [ActiveModel::Validations]: http://api.rubyonrails.org/classes/ActiveModel/Validations.html
382
+ [ActiveRecord]: http://guides.rubyonrails.org/active_record_validations.html
data/Rakefile ADDED
@@ -0,0 +1,139 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}/version.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ require 'rspec/core/rake_task'
47
+
48
+ RSpec::Core::RakeTask.new(:spec) do |t|
49
+ ENV["COVERAGE"] = "true"
50
+ t.pattern = Dir.glob('spec/**/*_spec.rb')
51
+ t.rspec_opts = '--format documentation'
52
+ end
53
+
54
+ task default: :test
55
+ task test: :spec
56
+
57
+ desc "Open an irb session preloaded with this library"
58
+ task :console do
59
+ sh "pry -I ./lib/ -rubygems -r ./lib/#{name}.rb"
60
+ end
61
+
62
+ #############################################################################
63
+ #
64
+ # Custom tasks (add your own tasks here)
65
+ #
66
+ #############################################################################
67
+
68
+ begin
69
+ require 'yard'
70
+ YARD::Rake::YardocTask.new
71
+ rescue LoadError
72
+ task :yardoc do
73
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
74
+ end
75
+ end
76
+
77
+ #############################################################################
78
+ #
79
+ # Packaging tasks
80
+ #
81
+ #############################################################################
82
+
83
+ task release: :build do
84
+ unless `git branch` =~ /^\* master$/
85
+ puts "You must be on the master branch to release!"
86
+ exit!
87
+ end
88
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
89
+ sh "git tag v#{version}"
90
+ sh "git push origin master"
91
+ sh "git push --tags"
92
+ sh "gem push pkg/#{name}-#{version}.gem"
93
+ end
94
+
95
+ task build: :gemspec do
96
+ sh "mkdir -p pkg"
97
+ sh "gem build #{gemspec_file}"
98
+ sh "mv #{gem_file} pkg"
99
+ end
100
+
101
+ task gemspec: :validate do
102
+ # read spec file and split out manifest section
103
+ spec = File.read(gemspec_file)
104
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
105
+
106
+ # replace name version and date
107
+ replace_header(head, :name)
108
+ replace_header(head, :version)
109
+ replace_header(head, :date)
110
+ #comment this out if your rubyforge_project has a different name
111
+ replace_header(head, :rubyforge_project)
112
+
113
+ # determine file list from git ls-files
114
+ files = `git ls-files`.
115
+ split("\n").
116
+ sort.
117
+ reject { |file| file =~ /^\./ }.
118
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
119
+ map { |file| " #{file}" }.
120
+ join("\n")
121
+
122
+ # piece file back together and write
123
+ manifest = " spec.files = %w[\n#{files}\n ]\n"
124
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
125
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
126
+ puts "Updated #{gemspec_file}"
127
+ end
128
+
129
+ task :validate do
130
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
131
+ unless libfiles.empty?
132
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
133
+ exit!
134
+ end
135
+ unless Dir['VERSION*'].empty?
136
+ puts "A `VERSION` file at root level violates Gem best practices."
137
+ exit!
138
+ end
139
+ end