sumodev 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/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ gem 'thor'
4
+ gem 'activesupport'
data/Gemfile.lock ADDED
@@ -0,0 +1,16 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.2.6)
5
+ i18n (~> 0.6)
6
+ multi_json (~> 1.0)
7
+ i18n (0.6.0)
8
+ multi_json (1.3.6)
9
+ thor (0.15.4)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ activesupport
16
+ thor
data/bin/sumo ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/sumodev')
3
+
4
+ Sumodev.start
@@ -0,0 +1,44 @@
1
+ module Sumodev::Actions
2
+ def copy_dest_file original, destination
3
+ from = File.expand_path(original, self.destination_root)
4
+ to = File.expand_path(destination, self.destination_root)
5
+
6
+ say_status :copy, relative_to_original_destination_root(to), true
7
+
8
+ unless options[:pretend]
9
+ FileUtils.cp from, to
10
+ end
11
+ end
12
+
13
+ def replace_in_file file, replacements
14
+ return unless behavior == :invoke
15
+
16
+ path = File.expand_path(file, self.destination_root)
17
+ say_status :replace, relative_to_original_destination_root(path), true
18
+
19
+ unless options[:pretend]
20
+ content = File.binread(path)
21
+ replacements.each do |match, replace|
22
+ content.gsub!(match, replace.to_s)
23
+ File.open(path, 'wb') {|f| f.write(content)}
24
+ end
25
+ end
26
+ end
27
+
28
+ def git(commands = {})
29
+ if commands.respond_to?(:each)
30
+ commands.each do |cmd, options|
31
+ run "git #{cmd} #{options}"
32
+ end
33
+ else
34
+ run "git #{commands}"
35
+ end
36
+ end
37
+
38
+ def git_track(message, &block)
39
+ block.call if block
40
+
41
+ git :add => '.'
42
+ git :commit => "-m \"#{message}\""
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ require 'active_support/core_ext/class'
2
+ require 'sumodev/command'
3
+
4
+ class Sumodev::Application < Sumodev::Command
5
+ include Thor::Actions
6
+
7
+ class << self
8
+ attr_accessor :login, :host, :log_file
9
+ end
10
+
11
+ desc 'log', "Opens the relevant log file"
12
+ def log
13
+ remote "less #{self.class.log_file}"
14
+ end
15
+
16
+ desc 'console', "Starts the Rails console"
17
+ def console
18
+ remote "\"cd production && rails console production\""
19
+ end
20
+
21
+ private
22
+ def remote command
23
+ run "ssh -t #{self.class.login}@#{self.class.host} #{command}" # -t forces TTY allocation
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ class Sumodev::Command < Thor
2
+ def self.banner(task, namespace = true, subcommand = false)
3
+ "#{basename} #{task.formatted_usage(self, true, subcommand)}"
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'sumodev/application'
2
+
3
+ class Sumodev::Commands::Factr < Sumodev::Application
4
+ self.login = "factr"
5
+ self.host = "app01.factr.be"
6
+ self.log_file = "/home/factr/production/log/production.log"
7
+ end
@@ -0,0 +1,31 @@
1
+ require 'sumodev/command'
2
+ require 'sumodev/generators/fork/v3'
3
+
4
+ class Sumodev::Commands::Fork < Sumodev::Command
5
+ namespace :fork
6
+
7
+ desc 'create CLIENT NAME', "Create a new Fork app"
8
+ method_option :multilanguage,
9
+ :type => :boolean,
10
+ :default => false,
11
+ :aliases => '-m'
12
+
13
+ method_option :fork_repository,
14
+ :type => :string,
15
+ :default => "git://github.com/sumocoders/forkcms.git"
16
+
17
+ method_option :branch,
18
+ :type => :string,
19
+ :default => 'master',
20
+ :aliases => '-b'
21
+
22
+ method_option :default_language,
23
+ :type => :string,
24
+ :default => 'nl',
25
+ :aliases => '-l'
26
+
27
+ def create(client, name)
28
+ fork_generator = Sumodev::Generators::Fork::V3.new([client, name], options)
29
+ fork_generator.invoke_all
30
+ end
31
+ end
@@ -0,0 +1,119 @@
1
+ require 'sumodev/command'
2
+ require 'net/ssh'
3
+ require 'net/scp'
4
+ require 'thor/group'
5
+
6
+ class Sumodev::Commands::Push < Thor::Group
7
+ argument :server, :type => :string
8
+
9
+ class_option :group
10
+
11
+ Jan = {:groups => ['admin', 'developer', 'root'], :identity => 'jan@defv.be', :key => 'AAAAB3NzaC1kc3MAAACBAP41tq+1L7MqdI2+ugo0wq5IDcLSWpXXxSqHwrDVpqLcNNjpCd1QMnJ0ZY0HaRZ9YVwMFzu/cyDTgD9JZnQjcBjNhKfgOC2GX3IEsJAEM/O2kzmikTK4xFqIeeTdIqqICxlMimrFNqKoXJARueNlPITwGkkEiLy9EydTs9Cj3cHHAAAAFQD7RLZI6BtwVjgRe4XPXZdJBFN2CwAAAIEAvWaJjP0vueLwDYB2FDIm8VT8Wm4glne9Ilo0hylM2HVffdCICqjIIjn+bcYTg3SfzQcXcUYsk1sKQ0nKdICOl+dfiQs3/rTW3NNner1UewDeas9nSJN3e2Y4hyXS1tI9UOXzN76XHJM8GBz7RAhFB4ZHKVeZHsyrG2yXKiQTO8IAAACAUcAms34UNsbBLGZ3MOPJ18A2aYr6ju+N2VPkSd74sC1K6E0DSda/xJPKffZUPCWqmEdjhkXDvnNq73R4YG9x8dvXGCSzdSXMJfr9lYQNt9PT8D18UVUuZHgC8PjKEL7Xw8xbd2yOp58SADMpEZdKSMmhHsgFuW1T2TKGM72Pmd4=', :protocol => 'dss'}
12
+ Tijs = {:groups => ['admin', 'developer', 'root'], :identity => 'tijs@sumocoders.be', :key => 'AAAAB3NzaC1yc2EAAAABIwAAAQEAueoZUITVE/YVZVZVi4cngE7FPK5+a3mt0mTtELdFM4JmXg7UmrQ1On2IwIdHw5Cq+VnrutRgWvQkfp+WaC2tzOrlzccpMeMv5lTRH7bRp6qR4FivJ/Aq7YeUrNJpzncUVTwyeHrveuhfCxEQoOIM2gN0Y54NGDTOp01D7GHApsYkObsw/3N7jgQVoL6xKPwRTMI52tFzUlkv+df78vx87X7bYK05dO4Ol8U0yFyKlV17+BEM4UuI/aTXhkNsBVNCqiksaqQwEGDK8IqyrNFYhbRwYDunTXc8zdd6imYfEJUSNdjOzTXUNUc15ssTMBsGpfvF/2bhYRlHuRWZP9BUqw==', :protocol => 'rsa'}
13
+ Jens = {:groups => ['admin', 'root'], :identity => 'jens@dehaese.eu', :key => 'AAAAB3NzaC1yc2EAAAABIwAAAIEAojZQXjhcy3fpHNEBqlj2C3EV+G+vOTSBvcn+U3Sq7eml+NrQqGoasAvC+c6bajJTRB0ZujynsUAWghDM43zY1SIbZl6PC0jdZg7qGjzlpOFdG96b84agiE6Dnz8Mjnb6846tJdslRV2Yyc9Y8iSgW3s3mszmW2hqHpZ4EqbVuPc=', :protocol => 'rsa'}
14
+ Sam = {:groups => ['developer'], :identity => 'sam@sumocoders.be', :key => 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDQ82XZ/2xJcaaba2fxZs41ciID9IT3bkoalfKmM7AJN/EHz70XuB0ghzA64gGTbKbyT9YyC0EgbbWr8dG52nJMy4g6tnPM8Ns/17WMkBDanhVCdzXy5RUlXcmqMsf+VQQUPtToXu0i4XqOCSVE0YJpfQmzqxyspoQqMO4D96S6uE2797kWGw/4ez7bkx5Ub8+gkgj718iyZy4qVD4VSF8bNxLhHHmUVp1gm0e5rki1uF4hPMQG8tuO+tJhg50LuCpmUe6tCX8CEYWTDTfaj5LCaSf9S7wUrZlk6VwtQSC8BOY+liCSDt9VonCaQudGSBpKtYN7vFk1/FGvyxdcxVSn', :protocol => 'rsa'}
15
+ Niels = {:groups => ['developer'], :identity => 'niels@sumocoders.be', :key => 'AAAAB3NzaC1yc2EAAAABIwAAAQEA3BAs8M+BuSsYwG0LsrrXmMveVgCd3mZOqvUFjJtWwO5ahzRPEDuT9eJGlwJwmVKk5aV76v/8ZFTJ/zgxYoGJF02j1tXaCyaDTnkRwS3AetFq3ZncIwLFgRDYgd+qI42E9VJg7WVXQKR+2jTusRFTZCfqp7/4sVZSWwKrbA8b5jEPWhgqSb8ZWGsbIQfFAXDQwr6gftkSOR9ABfXt3ULy2aGmtiZKQhxiIL7/0YT2xPtLshpWe7U9Y0nH5CFWOTDHILmzBYnVqvksEiFg+r4dtLPLAeGCwCo5ZSe8ae9JWyNtXELPNKi7Ds06ghtcSKuMrgYhdz9539JZYhy5IWtYNQ==', :protocol => 'rsa'}
16
+ Mathias = {:groups => ['developer'], :identity => 'mathias@sumocoders.be', :key => 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDLBHWXILbcsYfoDYCgJq4B3Q9ElcZq0hRWo8otXWsOP1pYU8Kq2thWbIB+RkuZr8Y7DMI2XtGXSquWJdx0Beddxt+yVoE/eReorVuB74bnEVXYtcZ8+mNHU6paC1T4XHMWjEXSgaEaSPdxnQxPPzQ+YDuJkYWaCrLxDyWe4sqio0R5SA5CasTkei5dHIfIzj8a16JvTj/FlbNrGHzdSwWY04QoSRdN3rpWJ4krzlHz5NIJmBhUxW15d9NgmKPGJYHmUyY1nfXdb82/zUS1vt6A/46hMJzRXhQuxzNVNBFN2q8d/bXhsNW6AZ467auWO3NLBUOfGHMX+Ga6FQiyCdhd', :protocol => 'rsa'}
17
+
18
+ def replace_keys
19
+ connect do |ssh|
20
+ say "Pushing SSH keys to #{login}@#{host}"
21
+ ssh.exec! 'mkdir -p ~/.ssh; touch ~/.ssh/authorized_keys'
22
+ keys_file = ssh.scp.download!('.ssh/authorized_keys')
23
+
24
+ authorized_keys = AuthorizedKeys.new(keys_file)
25
+
26
+ authorized_users.each do |user|
27
+ say "Adding #{user[:identity]}"
28
+ authorized_keys.add user
29
+ end
30
+
31
+ ssh.scp.upload(authorized_keys.to_io, '.ssh/authorized_keys')
32
+ end
33
+ end
34
+
35
+ private
36
+ def login
37
+ server[/(.*)@/, 1] || 'root'
38
+ end
39
+
40
+ def host
41
+ server[/^(?:.+@)?(.*)(?::.+)?/, 1]
42
+ end
43
+
44
+ def pass
45
+ @pass ||= ask_password("No valid SSH key found, please enter the password\nPass: ")
46
+ end
47
+
48
+ def port
49
+ server[/:(.+)/, 1] || 22
50
+ end
51
+
52
+ def users
53
+ [Jan, Tijs, Jens, Niels, Sam, Mathias]
54
+ end
55
+
56
+ def authorized_users
57
+ if group = options.fetch('group', nil)
58
+ users.select do |u|
59
+ u[:groups].member?(group)
60
+ end
61
+ else
62
+ users
63
+ end
64
+ end
65
+
66
+ def connect
67
+ options = {:port => port}
68
+ options[:password] = pass if @needs_pass
69
+
70
+ Net::SSH.start(host, login, options) do |ssh|
71
+ yield ssh
72
+ end
73
+ rescue Net::SSH::AuthenticationFailed
74
+ @needs_pass = true
75
+ @pass = nil
76
+ retry
77
+ end
78
+
79
+ def ask_password(message)
80
+ HighLine.new.ask(message) do |q|
81
+ q.echo = false
82
+ end
83
+ end
84
+
85
+ class AuthorizedKeys
86
+ attr_reader :contents
87
+
88
+ def initialize(file_contents)
89
+ @contents = parse!(file_contents)
90
+ end
91
+
92
+ def add key
93
+ @contents[key[:key]] = key
94
+ end
95
+
96
+ def remove key
97
+ @contents.delete(key[:key])
98
+ end
99
+
100
+ def to_s
101
+ @contents.collect do |identity, k|
102
+ "#{k[:optional]}ssh-#{k[:protocol]} #{k[:key]} #{k[:identity]}\n"
103
+ end.join
104
+ end
105
+
106
+ def to_io
107
+ StringIO.new(to_s)
108
+ end
109
+
110
+ private
111
+ def parse! (file)
112
+ file.split(/\n/).inject({}) do |hash, line|
113
+ #hash[$4] = {:optional => $1, :protocol => $2, :key => $3, :identity => $4} if line =~ /^([^#]*?)ssh-(dss|rsa) (.*) ((.*?)@(.*))/
114
+ hash[$3] = {:optional => $1, :protocol => $2, :key => $3, :identity => $4} if line =~ /^(\S*)ssh-(dss|rsa) (\S*)(?: (\S*))?$/
115
+ hash
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,45 @@
1
+ require 'sumodev/command'
2
+ require 'thor/group'
3
+
4
+ class Sumodev::Commands::Ssh < Thor::Group
5
+ argument :server, :type => :string
6
+
7
+ class_option :login
8
+
9
+ include Thor::Actions
10
+
11
+ Servers = {
12
+ :web1 => {:login => 'root', :host => 'web01.crsolutions.be'},
13
+ :web2 => {:login => 'root', :host => 'web02.crsolutions.be'},
14
+ :mysql => {:login => 'root', :host => 'mysql01.crsolutions.be'},
15
+ :mail => {:login => 'root', :host => 'mail.crsolutions.be'},
16
+ :mail2 => {:login => 'root', :host => 'mail02.crsolutions.be'},
17
+ :factr => {:login => 'factr', :host => 'app01.factr.be'},
18
+ :sumodev => {login: 'sites', :host => 'dev.sumocoders.eu'}
19
+ }
20
+
21
+ def check_ssh_key
22
+ if Dir.glob(ENV["HOME"] + "/.ssh/id_[rd]sa").none?
23
+ # No SSH key installed
24
+ if yes?("No SSH key installed! Do you want me to generate one for you?")
25
+ run "ssh-keygen -t rsa -f #{ENV["HOME"]}/.ssh/id_rsa"
26
+ end
27
+ end
28
+ end
29
+
30
+ def connect
31
+ server_details = Servers[server.to_sym]
32
+
33
+ if server_details
34
+ login = options.fetch(:login, server_details[:login])
35
+ host = server_details[:host]
36
+
37
+ run "ssh #{login}@#{host}"
38
+ else
39
+ say "No such server definition"
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ end
@@ -0,0 +1,6 @@
1
+ require 'sumodev/actions'
2
+
3
+ class Sumodev::Generator < Thor::Group
4
+ include Thor::Actions
5
+ include Sumodev::Actions
6
+ end
@@ -0,0 +1,77 @@
1
+ require 'sumodev/generator'
2
+ require 'sumodev/generators/general/capistrano'
3
+
4
+ module Sumodev::Generators
5
+ module Fork
6
+ class V3 < Sumodev::Generator
7
+ argument :client, :type => :string
8
+ argument :name, :type => :string
9
+
10
+ class_option :fork_repository, :default => "git://github.com/sumocoders/forkcms.git"
11
+ class_option :branch, :default => 'master'
12
+ class_option :multilanguage, :default => false, :type => :boolean
13
+ class_option :default_language, :default => 'nl'
14
+
15
+ attr_accessor :destination_dir
16
+
17
+ def create_root
18
+ self.destination_root = self.destination_dir = Pathname.pwd + client + name
19
+ FileUtils.mkdir_p destination_dir
20
+ FileUtils.chdir destination_dir
21
+ end
22
+
23
+ def checkout_fork
24
+ git :clone => "#{options[:fork_repository]} ."
25
+ git :checkout => "origin/#{options[:branch]}"
26
+ FileUtils.rm_rf '.git'
27
+ end
28
+
29
+ def initialize_repo
30
+ git :init
31
+ git_track 'Initial commit'
32
+ end
33
+
34
+ def setup_globals
35
+ git_track 'Setting up globals' do
36
+ copy_dest_file 'library/globals.base.php', 'library/globals.php'
37
+ copy_dest_file 'library/globals_frontend.base.php', 'library/globals_frontend.php'
38
+ copy_dest_file 'library/globals_backend.base.php', 'library/globals_backend.php'
39
+
40
+ replace_in_file 'library/globals.php', {
41
+ "'<debug-mode>'" => 'true',
42
+ "<spoon-debug-email>" => 'bugs@sumocoders.be',
43
+ "<database-name>" => name,
44
+ "<database-hostname>" => 'localhost',
45
+ "<database-port>" => '3306',
46
+ "<database-username>" => config[:db][:username],
47
+ "<database-password>" => config[:db][:password],
48
+ "<site-domain>" => "#{name}.#{client}.dev",
49
+ "'<site-multilanguage>'" => options[:multilanguage].to_s,
50
+ "<action-group-tag>" => '@actiongroup',
51
+ "<action-rights-level>" => 7,
52
+ "<path-www>" => destination_dir + "default_www",
53
+ "<path-library>" => destination_dir + "library"
54
+ }
55
+
56
+ replace_in_file 'library/globals_frontend.php', {
57
+ '<site-default-language>' => options[:default_language]
58
+ }
59
+ end
60
+ end
61
+
62
+ include Sumodev::Generators::General::Capistrano
63
+
64
+ private
65
+ # Config
66
+ def config
67
+ # TODO - Fetch this from ~/.sumorc OR ~/.my.cnf
68
+ {
69
+ :db => {
70
+ :username => 'root',
71
+ :password => 'root'
72
+ }
73
+ }
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,24 @@
1
+ require 'active_support/concern'
2
+ require 'sumodev/actions'
3
+
4
+ module Sumodev::Generators
5
+ module General
6
+ module Capistrano
7
+ include Thor::Actions
8
+ include Sumodev::Actions
9
+
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ create_task :generate_capfile
14
+ source_paths << File.dirname(__FILE__) + '/templates/'
15
+ end
16
+
17
+ def generate_capfile
18
+ git_track "Added Capfile" do
19
+ template 'Capfile', 'Capfile'
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ load 'deploy' if respond_to?(:namespace) # cap2 differentiator
2
+ # Capfile
3
+ set :client, "<%= client %>"
4
+ set :project, "<%= name %>"
5
+ set :repository, "git@git.sumocoders.be/<%= "#{client}_#{name}" %>.git"
6
+ set :branch, 'master'
7
+
8
+ require 'forkcms_3_deploy'
9
+ require 'forkcms_3_deploy/defaults'
10
+ require 'sumodev_deploy'
@@ -0,0 +1,3 @@
1
+ module Sumodev
2
+ VERSION = 0.1
3
+ end
data/lib/sumodev.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'thor'
2
+ require 'thor/group'
3
+
4
+ class Sumodev < Thor
5
+ module Commands
6
+ autoload :Fork, 'sumodev/commands/fork'
7
+ autoload :Ssh, 'sumodev/commands/ssh'
8
+ autoload :Factr, 'sumodev/commands/factr'
9
+ autoload :Push, 'sumodev/commands/push'
10
+ end
11
+
12
+ register Commands::Fork, 'fork', 'fork <command>', 'All commands concerning Fork applications'
13
+ register Commands::Ssh, 'ssh', 'ssh <server>', 'SSH access to servers'
14
+ register Commands::Factr, 'factr', 'factr <command>', 'All commands concerning Factr'
15
+ register Commands::Push, 'push', 'push <server>', 'Push the SSH keys to the server'
16
+ end
17
+
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sumodev
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jan De Poorter
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: thor
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: A command line tool to access all things you want
47
+ email: jan@sumocoders.be
48
+ executables:
49
+ - sumo
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - lib/sumodev/actions.rb
54
+ - lib/sumodev/application.rb
55
+ - lib/sumodev/command.rb
56
+ - lib/sumodev/commands/factr.rb
57
+ - lib/sumodev/commands/fork.rb
58
+ - lib/sumodev/commands/push.rb
59
+ - lib/sumodev/commands/ssh.rb
60
+ - lib/sumodev/generator.rb
61
+ - lib/sumodev/generators/fork/v3.rb
62
+ - lib/sumodev/generators/general/capistrano.rb
63
+ - lib/sumodev/generators/general/templates/Capfile
64
+ - lib/sumodev/version.rb
65
+ - lib/sumodev.rb
66
+ - bin/sumo
67
+ - Gemfile
68
+ - Gemfile.lock
69
+ homepage:
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.24
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: SumoCoders Developers gem
93
+ test_files: []
94
+ has_rdoc: