rundock 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +39 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +49 -0
  5. data/.travis.yml +3 -0
  6. data/CHANGELOG.md +3 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE +22 -0
  9. data/README.md +74 -0
  10. data/Rakefile +137 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +7 -0
  13. data/circle.yml +11 -0
  14. data/default_ssh.yml +8 -0
  15. data/exe/rundock +4 -0
  16. data/lib/rundock/backend.rb +130 -0
  17. data/lib/rundock/cli.rb +50 -0
  18. data/lib/rundock/ext/hash.rb +23 -0
  19. data/lib/rundock/ext/object/blank.rb +45 -0
  20. data/lib/rundock/logger.rb +106 -0
  21. data/lib/rundock/node.rb +27 -0
  22. data/lib/rundock/operation/base.rb +20 -0
  23. data/lib/rundock/operation/command.rb +11 -0
  24. data/lib/rundock/operation/task.rb +16 -0
  25. data/lib/rundock/operation_factory.rb +30 -0
  26. data/lib/rundock/runner.rb +182 -0
  27. data/lib/rundock/scenario.rb +7 -0
  28. data/lib/rundock/version.rb +3 -0
  29. data/lib/rundock.rb +17 -0
  30. data/rundock.gemspec +30 -0
  31. data/scenario_sample.yml +53 -0
  32. data/spec/integration/platforms/centos6/Dockerfile +16 -0
  33. data/spec/integration/platforms/centos6/setup.sh +78 -0
  34. data/spec/integration/platforms/localhost/scenarios/simple_echo_scenario.yml +9 -0
  35. data/spec/integration/platforms/localhost/scenarios/use_default_ssh_scenario.yml +4 -0
  36. data/spec/integration/recipes/simple_echo_scenario_spec.rb +6 -0
  37. data/spec/integration/recipes/simple_echo_spec.rb +6 -0
  38. data/spec/integration/scenarios/simple_echo_scenario.yml +33 -0
  39. data/spec/integration/scenarios/use_default_ssh_scenario.yml +4 -0
  40. data/spec/integration/spec_helper.rb +19 -0
  41. metadata +202 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9bae0415498290a96be0d1867e1b8390995a818c
4
+ data.tar.gz: 72ecb870b1108accdd2c9ea8c38dd5291843379c
5
+ SHA512:
6
+ metadata.gz: cf8bde9f38284afd6b37c109dd58cdf22258d7bd2bc178ae4c4dae7e1ccb13329453228e85f1068548ce30a5a2440bb38cffd051b9f6edfefad78cfee1f45bbc
7
+ data.tar.gz: ae60ee17c14eff7a50b2bd0f0557d4e484c4f9d1d67c6076b8d78f6f7471945359fd3d4ca3f551e805fac9b4b1829dda9055c3fae6a3112e836d4df3b0b03aac
data/.gitignore ADDED
@@ -0,0 +1,39 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+ Gemfile.lock
12
+ .vagrant
13
+ Gemfile.local
14
+ authorized_keys
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+
21
+ ## Documentation cache and generated files:
22
+ /.yardoc/
23
+ /_yardoc/
24
+ /doc/
25
+ /rdoc/
26
+
27
+ ## Environment normalisation:
28
+ /.bundle/
29
+ /vendor/bundle
30
+ /lib/bundler/man/
31
+
32
+ # for a library or gem, you might want to ignore these files since the code is
33
+ # intended to run in multiple environments; otherwise, check them in:
34
+ # Gemfile.lock
35
+ # .ruby-version
36
+ # .ruby-gemset
37
+
38
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
39
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.rubocop.yml ADDED
@@ -0,0 +1,49 @@
1
+ # see https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand
2
+
3
+ AllCops:
4
+ Exclude:
5
+ - "vendor/**/*" # rubocop config/default.yml
6
+ - "db/schema.rb"
7
+ DisplayCopNames: true
8
+
9
+ Metrics/LineLength:
10
+ Max: 500
11
+
12
+ Metrics/ClassLength:
13
+ CountComments: false # count full line comments?
14
+ Max: 320
15
+
16
+ Metrics/MethodLength:
17
+ CountComments: false # count full line comments?
18
+ Max: 50
19
+
20
+ Metrics/AbcSize:
21
+ Enabled: true
22
+ Max: 50
23
+
24
+ Metrics/CyclomaticComplexity:
25
+ Max: 20
26
+
27
+ Metrics/PerceivedComplexity:
28
+ Max: 20
29
+
30
+ Style/Documentation:
31
+ Enabled: false
32
+
33
+ Style/SpecialGlobalVars:
34
+ Enabled: false
35
+
36
+ Style/RedundantSelf:
37
+ Enabled: false
38
+
39
+ Style/SignalException:
40
+ EnforcedStyle: only_raise
41
+
42
+ Style/HashSyntax:
43
+ Enabled: false
44
+
45
+ Style/FormatString:
46
+ EnforcedStyle: percent
47
+
48
+ Lint/UnusedMethodArgument:
49
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v0.1.0
2
+
3
+ - The first public version.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rundock.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 hiracy
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 all
13
+ 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 THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # Rundock [![Gem Version](https://badge.fury.io/rb/rundock.svg)](http://badge.fury.io/rb/rundock) [![Circle CI](https://circleci.com/gh/hiracy/rundock.png?style=shield&circle-token=0d8a3836c5e285b7ecb6d076f2d51c5deca52d8b)](https://circleci.com/gh/hiracy/rundock)
2
+
3
+ Simple and extensible server operation framework based on [specinfra](https://github.com/serverspec/specinfra).
4
+
5
+ - [CHANGELOG](https://github.com/hiracy/rundock/blob/master/CHANGELOG.md)
6
+
7
+ ## Installation
8
+
9
+ ```
10
+ $ gem install rundock
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Edit your operation scenario to "[scenario.yml](https://github.com/hiracy/rundock/blob/master/scenario_sample.yml)" like this sample.
16
+
17
+ ```
18
+ # scenario section
19
+ - node: 192.168.1.11
20
+ command:
21
+ - "sudo hostname new-host-01"
22
+ - "sudo sed -i -e 's/HOSTNAME=old-host-01/HOSTNAME=new-host-01/g' /etc/sysconfig/network"
23
+ - node: host-alias-01
24
+ command:
25
+ - "sudo yum -y install ruby"
26
+ task:
27
+ - update_gem
28
+ - install_bundler
29
+ ---
30
+ # host information section
31
+ host-alias-01:
32
+ host: 192.168.1.12
33
+ ssh_opts:
34
+ port: 2222
35
+ user: anyuser
36
+ keys: ["~/.ssh/id_rsa_anyuser"]
37
+ ---
38
+ # task information section
39
+ update_gem:
40
+ - "sudo gem update --system"
41
+ - "sudo gem update"
42
+ install_bundler:
43
+ - "sudo gem install bundler --no-ri --no-rdoc"
44
+ ```
45
+
46
+ and do rundock.
47
+
48
+ $ rundock do -s /path/to/your-dir/scenario.yml
49
+
50
+ You can also specify [ssh_options.yml](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html)(Net::SSH options) file contents that you specified "-d" option to the default ssh options.
51
+
52
+ $ rundock do -s /path/to/your-dir/scenario.yml -d /path/to/your-dir/ssh_options.yml
53
+
54
+ For more detail. You can see from `rundock -h` command.
55
+
56
+ ## Documentations
57
+
58
+ Now on editing...
59
+
60
+ ## Run tests
61
+
62
+ Requirements: Docker environments
63
+
64
+ ```
65
+ $ bundle exec rake spec
66
+ ```
67
+
68
+ ## Contributing
69
+
70
+ 1. Fork it ( https://github.com/[my-github-username]/rundock/fork )
71
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
72
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
73
+ 4. Push to the branch (`git push origin my-new-feature`)
74
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,137 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ run_commands = [
5
+ 'rm -f /var/tmp/hello_rundock;' \
6
+ 'echo \'Hello Rundock.\' > /var/tmp/hello_rundock'
7
+ ]
8
+
9
+ run_scenarios = %w(
10
+ use_default_ssh_scenario
11
+ simple_echo_scenario
12
+ )
13
+
14
+ def execute(command)
15
+ puts "[EXECUTE:] #{command}"
16
+ system command
17
+ end
18
+
19
+ def setup_docker(platform, timeout, interval)
20
+ Bundler.with_clean_env do
21
+ execute "./spec/integration/platforms/#{platform}/setup.sh &"
22
+ found = false
23
+ (timeout / interval).times do
24
+ execute 'sudo docker ps | grep rundock'
25
+ if $?.to_i == 0
26
+ found = true
27
+ break
28
+ end
29
+ sleep interval
30
+ end
31
+ raise 'Docker Error.' unless found
32
+ end
33
+ end
34
+
35
+ def do_rundock_ssh(commands, platform)
36
+ if platform == 'localhost'
37
+ commands.each do |cmd|
38
+ execute "bundle exec exe/rundock ssh -c \"#{cmd}\" -h localhost -l debug"
39
+ end
40
+ else
41
+ commands.each do |cmd|
42
+ execute 'bundle exec exe/rundock' \
43
+ " ssh -c \"#{cmd}\" -h 127.0.0.1 -p 22222 -u tester" \
44
+ " -i #{ENV['HOME']}/.ssh/id_rsa_rundock_spec_#{platform}_tmp -l debug"
45
+ end
46
+ end
47
+ end
48
+
49
+ def do_rundock_scenarios(scenarios, platform)
50
+ if platform == 'localhost'
51
+ base_dir = './spec/integration/platforms/localhost'
52
+ else
53
+ base_dir = "#{ENV['HOME']}/.rundock/#{platform}"
54
+ end
55
+
56
+ scenarios.each do |scenario|
57
+ default_ssh_opt = ''
58
+ if scenario =~ /use_default_ssh/ && platform != 'localhost'
59
+ default_ssh_opt = " -d #{base_dir}/integration_default_ssh.yml"
60
+ end
61
+
62
+ execute 'bundle exec exe/rundock' \
63
+ " do -s #{base_dir}/scenarios/#{scenario}.yml#{default_ssh_opt} -l debug"
64
+ end
65
+ end
66
+
67
+ desc 'Cleaning environments'
68
+
69
+ task :clean do
70
+ Bundler.with_clean_env do
71
+ Dir.glob('./spec/integration/platforms/*').each do |platform|
72
+ execute "#{platform}/setup.sh --clean"
73
+ end
74
+ end
75
+ end
76
+
77
+ desc 'execute rubocop'
78
+ task :rubocop do
79
+ Bundler.with_clean_env do
80
+ execute 'rubocop'
81
+ end
82
+ end
83
+
84
+ desc 'Run all tests.'
85
+ task :spec => ['rubocop', 'spec:integration:all']
86
+
87
+ namespace :spec do
88
+ desc 'Run all tests for localhost.'
89
+ task :local => 'integration:localhost:all'
90
+
91
+ namespace :integration do
92
+ targets = ['localhost']
93
+ Dir.glob('./spec/integration/platforms/*').each do |result|
94
+ targets << File.basename(result)
95
+ end
96
+
97
+ desc 'Run all tests for all platforms.'
98
+ task :all => targets.map { |t| "spec:integration:#{t}:all" }
99
+
100
+ targets.each do |target|
101
+ namespace target.to_sym do
102
+ desc "Run rundock and serverspec tests for #{target}"
103
+
104
+ if target != 'localhost'
105
+ task :all => [:docker, :rundock, :serverspec]
106
+ else
107
+ task :all => [:rundock, :serverspec]
108
+ end
109
+
110
+ unless target == 'localhost'
111
+ desc "Setup Docker for #{target}"
112
+ task :docker do
113
+ # timeout 3 minutes and wait interval 10 seconds
114
+ setup_docker(target, 180, 10)
115
+ end
116
+ end
117
+
118
+ desc "Run rundock for #{target}"
119
+
120
+ task :rundock do
121
+ Bundler.with_clean_env do
122
+ do_rundock_ssh(run_commands, target)
123
+ do_rundock_scenarios(run_scenarios, target)
124
+ end
125
+ end
126
+
127
+ desc "Run serverspec tests for #{target}"
128
+
129
+ RSpec::Core::RakeTask.new(:serverspec) do |t|
130
+ ENV['TARGET_HOST'] = target
131
+ t.ruby_opts = '-I ./spec/integration'
132
+ t.pattern = './spec/integration/recipes/*_spec.rb'
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'rundock'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/circle.yml ADDED
@@ -0,0 +1,11 @@
1
+ machine:
2
+ services:
3
+ - docker
4
+
5
+ dependencies:
6
+ cache_directories:
7
+ - "~/docker"
8
+
9
+ test:
10
+ override:
11
+ - bundle exec rake spec
data/default_ssh.yml ADDED
@@ -0,0 +1,8 @@
1
+ #
2
+ # Default ssh options are here.
3
+ #
4
+ :port: 22
5
+ :paranoid: false
6
+ #:user: "youre_name"
7
+ #:keys: ["/path/to/private_key"]
8
+ #:passphrase: "yourpassphrase"
data/exe/rundock ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rundock/cli'
4
+ Rundock::CLI.start
@@ -0,0 +1,130 @@
1
+ require 'rundock'
2
+ require 'singleton'
3
+ require 'specinfra/core'
4
+ require 'io/console'
5
+ require 'net/ssh'
6
+
7
+ Specinfra::Configuration.error_on_missing_backend_type = true
8
+
9
+ module Rundock
10
+ module Backend
11
+ CommandResultStatusError = Class.new(StandardError)
12
+
13
+ class << self
14
+ def create(type, options = {})
15
+ self.const_get(type.capitalize).new(options)
16
+ end
17
+ end
18
+
19
+ class Base
20
+ attr_reader :options
21
+ attr_reader :backend
22
+
23
+ def initialize(options)
24
+ @options = parse(options)
25
+ @backend = create_specinfra_backend
26
+ end
27
+
28
+ def run_commands(cmd, options = {})
29
+ Array(cmd).each do |c|
30
+ run_command(c)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def run_command(cmd, options = {})
37
+ command = cmd.strip
38
+ command = "cd #{Shellwords.escape(options[:cwd])} && #{command}" if options[:cwd]
39
+ command = "sudo -H -u #{Shellwords.escape(user)} -- /bin/sh -c #{command}" if options[:user]
40
+
41
+ Logger.debug(%(Start executing: "#{command}"))
42
+
43
+ result = @backend.run_command(command)
44
+ exit_status = result.exit_status
45
+
46
+ Logger.formatter.indent do
47
+ Logger.error("#{result.stderr}") unless result.stderr.blank?
48
+ Logger.info("#{result.stdout.strip}") unless result.stdout.strip.blank?
49
+ Logger.debug("exit status: #{exit_status}")
50
+ end
51
+
52
+ if options[:no_continue_if_error] && exit_status != 0
53
+ raise CommandResultStatucError
54
+ end
55
+
56
+ result
57
+ end
58
+
59
+ def parse(options)
60
+ raise NotImplementedError
61
+ end
62
+
63
+ def create_specinfra_backend
64
+ raise NotImplementedError
65
+ end
66
+ end
67
+
68
+ class Local < Base
69
+ private
70
+
71
+ def parse(options)
72
+ options
73
+ end
74
+
75
+ def create_specinfra_backend
76
+ Specinfra::Backend::Exec.new
77
+ end
78
+ end
79
+
80
+ class Ssh < Base
81
+ private
82
+
83
+ def parse(options)
84
+ if options['ssh_config'] && FileTest.exists?(options['ssh_config'])
85
+ ssh_opts = Net::SSH::Config.for(options['host'], [options['ssh_config']])
86
+ else
87
+ ssh_opts = Net::SSH::Config.for(options['host'])
88
+ end
89
+
90
+ ssh_opts[:host_name] = options['host']
91
+ ssh_opts[:user] = options['user']
92
+ ssh_opts[:keys] = options['keys']
93
+ ssh_opts[:keys] = Array(options['key']) if !ssh_opts[:keys] && options['key']
94
+ ssh_opts[:port] = options['port']
95
+ ssh_opts[:password] = parse_password_from_stdin if options['ask_password']
96
+
97
+ ssh_opts.merge!(filter_net_ssh_options(options))
98
+
99
+ Logger.debug(%(Net::SSH Options: "#{ssh_opts}"))
100
+
101
+ ssh_opts
102
+ end
103
+
104
+ def parse_password_from_stdin
105
+ print 'password: '
106
+ passwd = STDIN.noecho(&:gets).strip
107
+ print "\n"
108
+ passwd
109
+ end
110
+
111
+ def filter_net_ssh_options(options)
112
+ opts = {}
113
+ options.each do |k, v|
114
+ opts[k.to_sym] = v if Net::SSH::VALID_OPTIONS.include?(k.to_sym)
115
+ end
116
+
117
+ opts
118
+ end
119
+
120
+ def create_specinfra_backend
121
+ Specinfra::Backend::Ssh.new(
122
+ request_pty: true,
123
+ host: @options[:host_name],
124
+ disable_sudo: !@options[:sudo],
125
+ ssh_options: @options
126
+ )
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,50 @@
1
+ require 'rundock'
2
+ require 'thor'
3
+
4
+ module Rundock
5
+ class CLI < Thor
6
+ DEFAULT_SCENARIO_FILE_PATH = './scenario.yml'
7
+ DEFAULT_SSH_OPTIONS_DEFAULT_FILE_PATH = './default_ssh.yml'
8
+
9
+ class_option :log_level, type: :string, aliases: ['-l'], default: 'info'
10
+ class_option :color, type: :boolean, default: true
11
+
12
+ def initialize(args, opts, config)
13
+ super(args, opts, config)
14
+
15
+ Rundock::Logger.level = ::Logger.const_get(options[:log_level].upcase)
16
+ Rundock::Logger.formatter.colored = options[:color]
17
+ end
18
+
19
+ desc 'version', 'Print version'
20
+ def version
21
+ puts "#{Rundock::VERSION}"
22
+ end
23
+
24
+ desc 'do [SCENARIO] [options]', 'Run rundock from scenario file'
25
+ option :sudo, type: :boolean, default: false
26
+ option :scenario_yaml, type: :string, aliases: ['-s'], default: DEFAULT_SCENARIO_FILE_PATH
27
+ option :default_ssh_opts_yaml, type: :string, aliases: ['-d'], default: DEFAULT_SSH_OPTIONS_DEFAULT_FILE_PATH
28
+ def do(*scenario_file_path)
29
+ scenario_file_path = [DEFAULT_SCENARIO_FILE_PATH] if scenario_file_path.empty?
30
+ opts = { :scenario_yaml => scenario_file_path[0] }
31
+
32
+ Runner.run(opts.merge(options))
33
+ end
34
+
35
+ desc 'ssh [options]', 'Run rundock ssh with various options'
36
+ option :command, type: :string, aliases: ['-c'], banner: "NOTICE: Scenario's task is ignored."
37
+ option :default_ssh_opts_yaml, type: :string, aliases: ['-d'], default: DEFAULT_SSH_OPTIONS_DEFAULT_FILE_PATH
38
+ option :host, type: :string, aliases: ['-h']
39
+ option :user, type: :string, aliases: ['-u']
40
+ option :key, type: :string, aliases: ['-i']
41
+ option :port, type: :numeric, aliases: ['-p']
42
+ option :ssh_config, type: :string, aliases: ['-F']
43
+ option :ask_password, type: :boolean, default: false
44
+ option :sudo, type: :boolean, default: false
45
+ def ssh
46
+ opts = {}
47
+ Runner.run(opts.merge(options))
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ class Hash
2
+ def depth
3
+ 1 + (values.map { |v| v.is_a?(Hash) ? v.depth : 1 }.max)
4
+ end
5
+
6
+ def deep_symbolize_keys
7
+ self.each_with_object({}) do |(k, v), h|
8
+ if v.is_a?(Array)
9
+ v = v.map do |vv|
10
+ if vv.is_a?(Hash)
11
+ vv.deep_symbolize_keys
12
+ else
13
+ vv
14
+ end
15
+ end
16
+ elsif v.is_a?(Hash)
17
+ v = v.deep_symbolize_keys
18
+ end
19
+
20
+ h[k.to_s.to_sym] = v
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ class Object
2
+ def blank?
3
+ respond_to?(:empty?) ? empty? : !self
4
+ end
5
+ end
6
+
7
+ class NilClass
8
+ def blank?
9
+ true
10
+ end
11
+ end
12
+
13
+ class FalseClass
14
+ def blank?
15
+ true
16
+ end
17
+ end
18
+
19
+ class TrueClass
20
+ def blank?
21
+ false
22
+ end
23
+ end
24
+
25
+ class Array
26
+ alias_method :blank?, :empty?
27
+ end
28
+
29
+ class Hash
30
+ alias_method :blank?, :empty?
31
+ end
32
+
33
+ class String
34
+ BLANK_RE = /\A[[:space:]]*\z/
35
+
36
+ def blank?
37
+ self =~ BLANK_RE
38
+ end
39
+ end
40
+
41
+ class Numeric
42
+ def blank?
43
+ false
44
+ end
45
+ end