foreman 0.29.0 → 0.37.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.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Foreman
2
+
3
+ Manage Procfile-based applications
4
+
5
+ <table>
6
+ <tr>
7
+ <th>If you have...</th>
8
+ <th>Install with...</th>
9
+ </tr>
10
+ <tr>
11
+ <td>Ruby (MRI, JRuby, Windows)</td>
12
+ <td><pre>$ gem install foreman</pre></td>
13
+ </tr>
14
+ <tr>
15
+ <td>Mac OS X</td>
16
+ <td><a href="http://assets.foreman.io/foreman/foreman.pkg">foreman.pkg</a></td>
17
+ </tr>
18
+ </table>
19
+
20
+ ## Getting Started
21
+
22
+ * http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
23
+
24
+ ## Documentation
25
+
26
+ * [man page](http://ddollar.github.com/foreman)
27
+ * [wiki](http://github.com/ddollar/foreman/wiki)
28
+
29
+ ## Authors
30
+
31
+ #### Created and maintained by
32
+ David Dollar
33
+
34
+ #### Patches contributed by
35
+ Adam Wiggins, Chris Continanza, Chris Lowder, Craig R Webster, Dan Farina, Dan Peterson, David Dollar, Fletcher Nichol, Gabriel Burt, Gamaliel Toro, Greg Reinacker, Hugues Le Gendre, Hunter Nield, Iain Hecker, Jay Zeschin, Keith Rarick, Khaja Minhajuddin, Lincoln Stoll, Marcos Muino Garcia, Mark McGranaghan, Matt Griffin, Matt Haynes, Matthijs Langenberg, Michael Dwan, Michael van Rooijen, Mike Javorski, Nathan Broadbent, Nathan L Smith, Nick Zadrozny, Phil Hagelberg, Ricardo Chimal, Jr, Thom May, Tom Ward, brainopia, clifff, jc00ke
36
+
37
+ ## License
38
+
39
+ MIT
data/bin/runner CHANGED
@@ -1,2 +1,36 @@
1
1
  #!/bin/sh
2
- exec $1 2>&1
2
+ #
3
+ #/ Usage: runner [-d <dir>] <command>
4
+ #/
5
+ #/ Run a command with exec, optionally changing directory first
6
+
7
+ set -e
8
+
9
+ error() {
10
+ echo $@ >&2
11
+ exit 1
12
+ }
13
+
14
+ usage() {
15
+ cat $0 | grep '^#/' | cut -c4-
16
+ exit
17
+ }
18
+
19
+ while getopts ":hd:" OPT; do
20
+ case $OPT in
21
+ d) cd $OPTARG ;;
22
+ h) usage ;;
23
+ \?) error "invalid option: -$OPTARG" ;;
24
+ :) error "option -$OPTARG requires an argument" ;;
25
+ esac
26
+ done
27
+
28
+ shift $((OPTIND-1))
29
+
30
+ command=$1
31
+
32
+ if [ -z "$1" ]; then
33
+ usage
34
+ fi
35
+
36
+ exec $1
@@ -5,7 +5,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
5
5
 
6
6
  <% engine.procfile.entries.each do |process| %>
7
7
  <% 1.upto(concurrency[process.name]) do |num| %>
8
- <% port = engine.port_for(process, num, options[:port]) %>
8
+ <% port = engine.port_for(process, num, self.port) %>
9
9
  app.process("<%= process.name %>-<%=num%>") do |process|
10
10
  process.start_command = "<%= process.command.gsub("$PORT", port.to_s) %>"
11
11
 
data/lib/foreman/cli.rb CHANGED
@@ -1,19 +1,32 @@
1
1
  require "foreman"
2
+ require "foreman/helpers"
2
3
  require "foreman/engine"
3
4
  require "foreman/export"
4
5
  require "thor"
5
6
  require "yaml"
6
7
 
7
8
  class Foreman::CLI < Thor
9
+ include Foreman::Helpers
8
10
 
9
11
  class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
10
12
 
11
13
  desc "start", "Start the application"
12
14
 
15
+ class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
16
+ class_option :app_root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
17
+
13
18
  method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
14
19
  method_option :port, :type => :numeric, :aliases => "-p"
15
20
  method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
16
21
 
22
+ class << self
23
+ # Hackery. Take the run method away from Thor so that we can redefine it.
24
+ def is_thor_reserved_word?(word, type)
25
+ return false if word == 'run'
26
+ super
27
+ end
28
+ end
29
+
17
30
  def start
18
31
  check_procfile!
19
32
  engine.start
@@ -27,22 +40,12 @@ class Foreman::CLI < Thor
27
40
  method_option :port, :type => :numeric, :aliases => "-p"
28
41
  method_option :user, :type => :string, :aliases => "-u"
29
42
  method_option :template, :type => :string, :aliases => "-t"
30
- method_option :concurrency, :type => :string, :aliases => "-c",
31
- :banner => '"alpha=5,bar=3"'
43
+ method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
32
44
 
33
45
  def export(format, location=nil)
34
46
  check_procfile!
35
-
36
- formatter = case format
37
- when "inittab" then Foreman::Export::Inittab
38
- when "upstart" then Foreman::Export::Upstart
39
- when "bluepill" then Foreman::Export::Bluepill
40
- when "runit" then Foreman::Export::Runit
41
- else error "Unknown export format: #{format}."
42
- end
43
-
44
- formatter.new(engine).export(location, options)
45
-
47
+ formatter = Foreman::Export.formatter(format)
48
+ formatter.new(location, engine, options).export
46
49
  rescue Foreman::Export::Exception => ex
47
50
  error ex.message
48
51
  end
@@ -51,7 +54,20 @@ class Foreman::CLI < Thor
51
54
 
52
55
  def check
53
56
  error "no processes defined" unless engine.procfile.entries.length > 0
54
- display "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
57
+ puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
58
+ end
59
+
60
+ desc "run COMMAND", "Run a command using your application's environment"
61
+
62
+ def run(*args)
63
+ engine.apply_environment!
64
+ begin
65
+ exec args.join(" ")
66
+ rescue Errno::EACCES
67
+ error "not executable: #{args.first}"
68
+ rescue Errno::ENOENT
69
+ error "command not found: #{args.first}"
70
+ end
55
71
  end
56
72
 
57
73
  private ######################################################################
@@ -68,24 +84,15 @@ private ######################################################################
68
84
  options[:procfile] || "Procfile"
69
85
  end
70
86
 
71
- def display(message)
72
- puts message
73
- end
74
-
75
87
  def error(message)
76
88
  puts "ERROR: #{message}"
77
89
  exit 1
78
90
  end
79
91
 
80
- def procfile_exists?(procfile)
81
- File.exist?(procfile)
82
- end
83
-
84
92
  def options
85
93
  original_options = super
86
94
  return original_options unless File.exists?(".foreman")
87
95
  defaults = YAML::load_file(".foreman") || {}
88
96
  Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
89
97
  end
90
-
91
98
  end
@@ -2,11 +2,11 @@ require "foreman"
2
2
  require "foreman/process"
3
3
  require "foreman/procfile"
4
4
  require "foreman/utils"
5
- require "pty"
6
5
  require "tempfile"
7
6
  require "timeout"
8
7
  require "term/ansicolor"
9
8
  require "fileutils"
9
+ require "thread"
10
10
 
11
11
  class Foreman::Engine
12
12
 
@@ -16,11 +16,13 @@ class Foreman::Engine
16
16
 
17
17
  extend Term::ANSIColor
18
18
 
19
- COLORS = [ cyan, yellow, green, magenta, red ]
19
+ COLORS = [ cyan, yellow, green, magenta, red, blue,
20
+ intense_cyan, intense_yellow, intense_green, intense_magenta,
21
+ intense_red, intense_blue ]
20
22
 
21
23
  def initialize(procfile, options={})
22
24
  @procfile = Foreman::Procfile.new(procfile)
23
- @directory = File.expand_path(File.dirname(procfile))
25
+ @directory = options[:app_root] || File.expand_path(File.dirname(procfile))
24
26
  @options = options
25
27
  @environment = read_environment_files(options[:env])
26
28
  @output_mutex = Mutex.new
@@ -57,7 +59,7 @@ private ######################################################################
57
59
 
58
60
  procfile.entries.each do |entry|
59
61
  reader, writer = IO.pipe
60
- entry.spawn(concurrency[entry.name], writer, @directory, @environment, base_port).each do |process|
62
+ entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process|
61
63
  running_processes[process.pid] = process
62
64
  readers[process] = reader
63
65
  end
@@ -70,14 +72,23 @@ private ######################################################################
70
72
 
71
73
  def kill_all(signal="SIGTERM")
72
74
  running_processes.each do |pid, process|
73
- Process.kill(signal, pid) rescue Errno::ESRCH
75
+ info "sending #{signal} to pid #{pid}"
76
+ process.kill signal
74
77
  end
75
78
  end
76
79
 
77
80
  def terminate_gracefully
81
+ return if @terminating
82
+ @terminating = true
78
83
  info "sending SIGTERM to all processes"
79
84
  kill_all "SIGTERM"
80
- Timeout.timeout(5) { Process.waitall }
85
+ Timeout.timeout(5) do
86
+ while running_processes.length > 0
87
+ pid, status = Process.wait2
88
+ process = running_processes.delete(pid)
89
+ info "process terminated", process.name
90
+ end
91
+ end
81
92
  rescue Timeout::Error
82
93
  info "sending SIGKILL to all processes"
83
94
  kill_all "SIGKILL"
@@ -85,11 +96,14 @@ private ######################################################################
85
96
 
86
97
  def watch_for_output
87
98
  Thread.new do
99
+ require "win32console" if Foreman.windows?
88
100
  begin
89
101
  loop do
90
102
  rs, ws = IO.select(readers.values, [], [], 1)
91
103
  (rs || []).each do |r|
92
- ps, message = r.gets.split(",", 2)
104
+ data = r.gets
105
+ next unless data
106
+ ps, message = data.split(",", 2)
93
107
  color = colors[ps.split(".").first]
94
108
  info message, ps, color
95
109
  end
@@ -106,16 +120,16 @@ private ######################################################################
106
120
  process = running_processes.delete(pid)
107
121
  info "process terminated", process.name
108
122
  terminate_gracefully
109
- kill_all
110
123
  rescue Errno::ECHILD
111
124
  end
112
125
 
113
126
  def info(message, name="system", color=Term::ANSIColor.white)
114
- print color
115
- print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
116
- print Term::ANSIColor.reset
117
- print message.chomp
118
- puts
127
+ output = ""
128
+ output += color
129
+ output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
130
+ output += Term::ANSIColor.reset
131
+ output += message.chomp
132
+ puts output
119
133
  end
120
134
 
121
135
  def print(message=nil)
@@ -130,11 +144,6 @@ private ######################################################################
130
144
  end
131
145
  end
132
146
 
133
- def error(message)
134
- puts "ERROR: #{message}"
135
- exit 1
136
- end
137
-
138
147
  def longest_process_name
139
148
  @longest_process_name ||= begin
140
149
  longest = procfile.process_names.map { |name| name.length }.sort.last
@@ -152,7 +161,7 @@ private ######################################################################
152
161
  end
153
162
 
154
163
  def termtitle(title)
155
- printf("\033]0;#{title}\007")
164
+ printf("\033]0;#{title}\007") unless Foreman.windows?
156
165
  end
157
166
 
158
167
  def running_processes
@@ -180,7 +189,8 @@ private ######################################################################
180
189
  def next_color
181
190
  @current_color ||= -1
182
191
  @current_color += 1
183
- @current_color >= COLORS.length ? "" : COLORS[@current_color]
192
+ @current_color = 0 if COLORS.length < @current_color
193
+ COLORS[@current_color]
184
194
  end
185
195
 
186
196
  module Env
@@ -212,6 +222,11 @@ private ######################################################################
212
222
  def apply_environment!
213
223
  @environment.each { |k,v| ENV[k] = v }
214
224
  end
225
+
226
+ def error(message)
227
+ puts "ERROR: #{message}"
228
+ exit 1
229
+ end
215
230
  end
216
231
 
217
232
  include Env
@@ -3,10 +3,17 @@ require "foreman/utils"
3
3
 
4
4
  class Foreman::Export::Base
5
5
 
6
- attr_reader :engine
7
-
8
- def initialize(engine)
9
- @engine = engine
6
+ attr_reader :location, :engine, :app, :log, :port, :user, :template, :concurrency
7
+
8
+ def initialize(location, engine, options={})
9
+ @location = location
10
+ @engine = engine
11
+ @app = options[:app]
12
+ @log = options[:log]
13
+ @port = options[:port]
14
+ @user = options[:user]
15
+ @template = options[:template]
16
+ @concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
10
17
  end
11
18
 
12
19
  def export
@@ -26,7 +33,7 @@ private ######################################################################
26
33
  def export_template(exporter, file, template_root)
27
34
  if template_root && File.exist?(file_path = File.join(template_root, file))
28
35
  File.read(file_path)
29
- elsif File.exist?(file_path = File.join("~/.foreman/templates", file))
36
+ elsif File.exist?(file_path = File.expand_path(File.join("~/.foreman/templates", file)))
30
37
  File.read(file_path)
31
38
  else
32
39
  File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
@@ -3,23 +3,21 @@ require "foreman/export"
3
3
 
4
4
  class Foreman::Export::Bluepill < Foreman::Export::Base
5
5
 
6
- def export(location, options={})
6
+ def export
7
7
  error("Must specify a location") unless location
8
8
 
9
9
  FileUtils.mkdir_p location
10
10
 
11
- app = options[:app] || File.basename(engine.directory)
12
- user = options[:user] || app
13
- log_root = options[:log] || "/var/log/#{app}"
14
- template_root = options[:template]
11
+ app = self.app || File.basename(engine.directory)
12
+ user = self.user || app
13
+ log_root = self.log || "/var/log/#{app}"
14
+ template_root = self.template
15
15
 
16
16
  Dir["#{location}/#{app}.pill"].each do |file|
17
17
  say "cleaning up: #{file}"
18
18
  FileUtils.rm(file)
19
19
  end
20
20
 
21
- concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
22
-
23
21
  master_template = export_template("bluepill", "master.pill.erb", template_root)
24
22
  master_config = ERB.new(master_template).result(binding)
25
23
  write_file "#{location}/#{app}.pill", master_config
@@ -2,20 +2,18 @@ require "foreman/export"
2
2
 
3
3
  class Foreman::Export::Inittab < Foreman::Export::Base
4
4
 
5
- def export(fname=nil, options={})
6
- app = options[:app] || File.basename(engine.directory)
7
- user = options[:user] || app
8
- log_root = options[:log] || "/var/log/#{app}"
9
-
10
- concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
5
+ def export
6
+ app = self.app || File.basename(engine.directory)
7
+ user = self.user || app
8
+ log_root = self.log || "/var/log/#{app}"
11
9
 
12
10
  inittab = []
13
11
  inittab << "# ----- foreman #{app} processes -----"
14
12
 
15
13
  engine.procfile.entries.inject(1) do |index, process|
16
- 1.upto(concurrency[process.name]) do |num|
14
+ 1.upto(self.concurrency[process.name]) do |num|
17
15
  id = app.slice(0, 2).upcase + sprintf("%02d", index)
18
- port = engine.port_for(process, num, options[:port])
16
+ port = engine.port_for(process, num, self.port)
19
17
  inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'"
20
18
  index += 1
21
19
  end
@@ -26,12 +24,12 @@ class Foreman::Export::Inittab < Foreman::Export::Base
26
24
 
27
25
  inittab = inittab.join("\n") + "\n"
28
26
 
29
- if fname
27
+ if location == "-"
28
+ puts inittab
29
+ else
30
30
  FileUtils.mkdir_p(log_root) rescue error "could not create #{log_root}"
31
31
  FileUtils.chown(user, nil, log_root) rescue error "could not chown #{log_root} to #{user}"
32
- write_file(fname, inittab)
33
- else
34
- puts inittab
32
+ write_file(location, inittab)
35
33
  end
36
34
  end
37
35
 
@@ -4,21 +4,19 @@ require "foreman/export"
4
4
  class Foreman::Export::Runit < Foreman::Export::Base
5
5
  ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
6
6
 
7
- def export(location, options={})
7
+ def export
8
8
  error("Must specify a location") unless location
9
9
 
10
- app = options[:app] || File.basename(engine.directory)
11
- user = options[:user] || app
12
- log_root = options[:log] || "/var/log/#{app}"
13
- template_root = options[:template]
14
-
15
- concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
10
+ app = self.app || File.basename(engine.directory)
11
+ user = self.user || app
12
+ log_root = self.log || "/var/log/#{app}"
13
+ template_root = self.template
16
14
 
17
15
  run_template = export_template('runit', 'run.erb', template_root)
18
16
  log_run_template = export_template('runit', 'log_run.erb', template_root)
19
17
 
20
18
  engine.procfile.entries.each do |process|
21
- 1.upto(concurrency[process.name]) do |num|
19
+ 1.upto(self.concurrency[process.name]) do |num|
22
20
  process_directory = "#{location}/#{app}-#{process.name}-#{num}"
23
21
  process_env_directory = "#{process_directory}/env"
24
22
  process_log_directory = "#{process_directory}/log"
@@ -29,8 +27,9 @@ class Foreman::Export::Runit < Foreman::Export::Base
29
27
 
30
28
  run = ERB.new(run_template).result(binding)
31
29
  write_file "#{process_directory}/run", run
30
+ FileUtils.chmod 0755, "#{process_directory}/run"
32
31
 
33
- port = engine.port_for(process, num, options[:port])
32
+ port = engine.port_for(process, num, self.port)
34
33
  environment_variables = {'PORT' => port}.
35
34
  merge(engine.environment).
36
35
  merge(inline_variables(process.command))
@@ -41,7 +40,7 @@ class Foreman::Export::Runit < Foreman::Export::Base
41
40
 
42
41
  log_run = ERB.new(log_run_template).result(binding)
43
42
  write_file "#{process_log_directory}/run", log_run
44
-
43
+ FileUtils.chmod 0755, "#{process_log_directory}/run"
45
44
  end
46
45
  end
47
46
 
@@ -50,7 +49,7 @@ class Foreman::Export::Runit < Foreman::Export::Base
50
49
  private
51
50
  def create_directory(location)
52
51
  say "creating: #{location}"
53
- FileUtils.mkdir(location)
52
+ FileUtils.mkdir_p(location)
54
53
  end
55
54
 
56
55
  def inline_variables(command)
@@ -3,23 +3,21 @@ require "foreman/export"
3
3
 
4
4
  class Foreman::Export::Upstart < Foreman::Export::Base
5
5
 
6
- def export(location, options={})
6
+ def export
7
7
  error("Must specify a location") unless location
8
8
 
9
9
  FileUtils.mkdir_p location
10
10
 
11
- app = options[:app] || File.basename(engine.directory)
12
- user = options[:user] || app
13
- log_root = options[:log] || "/var/log/#{app}"
14
- template_root = options[:template]
11
+ app = self.app || File.basename(engine.directory)
12
+ user = self.user || app
13
+ log_root = self.log || "/var/log/#{app}"
14
+ template_root = self.template
15
15
 
16
16
  Dir["#{location}/#{app}*.conf"].each do |file|
17
17
  say "cleaning up: #{file}"
18
18
  FileUtils.rm(file)
19
19
  end
20
20
 
21
- concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
22
-
23
21
  master_template = export_template("upstart", "master.conf.erb", template_root)
24
22
  master_config = ERB.new(master_template).result(binding)
25
23
  write_file "#{location}/#{app}.conf", master_config
@@ -27,13 +25,13 @@ class Foreman::Export::Upstart < Foreman::Export::Base
27
25
  process_template = export_template("upstart", "process.conf.erb", template_root)
28
26
 
29
27
  engine.procfile.entries.each do |process|
30
- next if (conc = concurrency[process.name]) < 1
28
+ next if (conc = self.concurrency[process.name]) < 1
31
29
  process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
32
30
  process_master_config = ERB.new(process_master_template).result(binding)
33
31
  write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
34
32
 
35
- 1.upto(concurrency[process.name]) do |num|
36
- port = engine.port_for(process, num, options[:port])
33
+ 1.upto(self.concurrency[process.name]) do |num|
34
+ port = engine.port_for(process, num, self.port)
37
35
  process_config = ERB.new(process_template).result(binding)
38
36
  write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
39
37
  end
@@ -1,9 +1,30 @@
1
1
  require "foreman"
2
+ require "foreman/helpers"
2
3
 
3
4
  module Foreman::Export
5
+ extend Foreman::Helpers
6
+
4
7
  class Exception < ::Exception; end
8
+
9
+ def self.formatter(format)
10
+ begin
11
+ require "foreman/export/#{ format.tr('-', '_') }"
12
+ classy_format = classify(format)
13
+ formatter = constantize("Foreman::Export::#{ classy_format }")
14
+ rescue NameError => ex
15
+ error "Unknown export format: #{format} (no class Foreman::Export::#{ classy_format })."
16
+ rescue LoadError => ex
17
+ error "Unknown export format: #{format} (unable to load file 'foreman/export/#{ format.tr('-', '_') }')."
18
+ end
19
+ end
20
+
21
+ def self.error(message)
22
+ raise Foreman::Export::Exception.new(message)
23
+ end
24
+
5
25
  end
6
26
 
27
+
7
28
  require "foreman/export/base"
8
29
  require "foreman/export/inittab"
9
30
  require "foreman/export/upstart"
@@ -0,0 +1,45 @@
1
+ module Foreman::Helpers
2
+ # Copied whole sale from, https://github.com/defunkt/resque/
3
+
4
+ # Given a word with dashes, returns a camel cased version of it.
5
+ #
6
+ # classify('job-name') # => 'JobName'
7
+ def classify(dashed_word)
8
+ dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
9
+ end # Tries to find a constant with the name specified in the argument string:
10
+
11
+ #
12
+ # constantize("Module") # => Module
13
+ # constantize("Test::Unit") # => Test::Unit
14
+ #
15
+ # The name is assumed to be the one of a top-level constant, no matter
16
+ # whether it starts with "::" or not. No lexical context is taken into
17
+ # account:
18
+ #
19
+ # C = 'outside'
20
+ # module M
21
+ # C = 'inside'
22
+ # C # => 'inside'
23
+ # constantize("C") # => 'outside', same as ::C
24
+ # end
25
+ #
26
+ # NameError is raised when the constant is unknown.
27
+ def constantize(camel_cased_word)
28
+ camel_cased_word = camel_cased_word.to_s
29
+
30
+ names = camel_cased_word.split('::')
31
+ names.shift if names.empty? || names.first.empty?
32
+
33
+ constant = Object
34
+ names.each do |name|
35
+ args = Module.method(:const_get).arity != 1 ? [false] : []
36
+
37
+ if constant.const_defined?(name, *args)
38
+ constant = constant.const_get(name)
39
+ else
40
+ constant = constant.const_missing(name)
41
+ end
42
+ end
43
+ constant
44
+ end
45
+ end