rundock 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.
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