zeus 0.1.0 → 0.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +24 -6
- data/bin/zeus +34 -4
- data/lib/tiny_thor.rb +96 -0
- data/lib/zeus.rb +16 -1
- data/lib/zeus/cli.rb +72 -0
- data/lib/zeus/client.rb +6 -17
- data/lib/zeus/dsl.rb +88 -0
- data/lib/zeus/init.rb +17 -0
- data/lib/zeus/server.rb +82 -244
- data/lib/zeus/server/acceptor.rb +81 -0
- data/lib/zeus/server/acceptor_registration_monitor.rb +44 -0
- data/lib/zeus/server/client_handler.rb +88 -0
- data/lib/zeus/server/file_monitor.rb +57 -0
- data/lib/zeus/server/process_tree_monitor.rb +98 -0
- data/lib/zeus/server/stage.rb +57 -0
- data/{examples → lib/zeus/templates}/rails.rb +11 -11
- data/lib/zeus/ui.rb +56 -0
- data/lib/zeus/version.rb +1 -1
- data/zeus.gemspec +1 -1
- metadata +20 -9
data/README.md
CHANGED
@@ -4,17 +4,19 @@
|
|
4
4
|
|
5
5
|
Zeus preloads your app so that your normal development tasks such as `console`, `server`, `generate`, and tests are faster.
|
6
6
|
|
7
|
+
[Mediocre screencast](http://burke.libbey.me/zeus.mov). Better one coming soon.
|
8
|
+
|
7
9
|
## Why?
|
8
10
|
|
9
11
|
Because waiting 25 seconds sucks, but waiting 0.4 seconds doesn't.
|
10
12
|
|
11
13
|
## When?
|
12
14
|
|
13
|
-
|
15
|
+
Soon? You can use Zeus now, but don't expect it to be perfect. I'm working hard on it.
|
14
16
|
|
15
17
|
## Ugly bits
|
16
18
|
|
17
|
-
*
|
19
|
+
* Not battle-tested yet
|
18
20
|
* Creates a bunch of sockets
|
19
21
|
* Uses an obscene number of file descriptors
|
20
22
|
|
@@ -43,13 +45,15 @@ Run some commands:
|
|
43
45
|
|
44
46
|
## TODO (roughly prioritized)
|
45
47
|
|
46
|
-
*
|
47
|
-
* Handle client/server without requiring a unix socket for each acceptor (1 shared socket)
|
48
|
+
* Fix all the bugs I added when I switched to singlesocket...
|
48
49
|
* Make the code less terrible
|
49
50
|
* Figure out how to run full test suites without multiple env loads
|
50
51
|
* Support other frameworks?
|
51
|
-
* Use
|
52
|
-
* Support
|
52
|
+
* Use fsevent instead of kqueue to reduce the obscene number of file descriptors.
|
53
|
+
* Support inotify on linux
|
54
|
+
* Handle connections for not-yet-started sockets
|
55
|
+
* Don't replace a socket with changed deps until the new one is ready
|
56
|
+
* (maybe) Start the preloader as a daemon transparently when any command is run, then wait for it to finish
|
53
57
|
|
54
58
|
## Contributing
|
55
59
|
|
@@ -58,3 +62,17 @@ Run some commands:
|
|
58
62
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
59
63
|
4. Push to the branch (`git push origin my-new-feature`)
|
60
64
|
5. Create new Pull Request
|
65
|
+
|
66
|
+
## Thanks...
|
67
|
+
|
68
|
+
* To [Jesse Storimer](http://github.com/jstorimer) for spin, part of the inspiration for this project
|
69
|
+
* To [Samuel Kadolph](http://github.com/samuelkadolph) for doing most of the cross-process pseudoterminal legwork.
|
70
|
+
* To [Shopify](http://github.com/Shopify) for letting me spend (some of) my days working on this.
|
71
|
+
|
72
|
+
## Doesn't work for you?
|
73
|
+
|
74
|
+
Try these libraries instead:
|
75
|
+
|
76
|
+
* [spin](https://github.com/jstorimer/spin)
|
77
|
+
* [spork](https://github.com/sporkrb/spork)
|
78
|
+
* [guard](https://github.com/guard/guard)
|
data/bin/zeus
CHANGED
@@ -1,12 +1,42 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
zeus
|
4
|
-
|
3
|
+
require 'zeus'
|
4
|
+
require 'zeus/cli'
|
5
|
+
Zeus::CLI.start
|
5
6
|
|
6
|
-
|
7
|
+
begin
|
8
|
+
rescue Zeus::ZeusError => e
|
9
|
+
Zeus.ui.error e.message
|
10
|
+
Zeus.ui.debug e.backtrace.join("\n")
|
11
|
+
exit e.status_code
|
12
|
+
rescue Interrupt => e
|
13
|
+
Zeus.ui.error e.message
|
14
|
+
Zeus.ui.debug e.backtrace.join("\n")
|
15
|
+
exit 1
|
16
|
+
rescue SystemExit => e
|
17
|
+
exit e.status
|
18
|
+
rescue Exception => e
|
19
|
+
Zeus.ui.error("A fatal error has occurred. Please see the github issues at:\n" \
|
20
|
+
"http://github.com/burke/zeus/issues")
|
21
|
+
end
|
22
|
+
exit 0
|
23
|
+
|
24
|
+
|
25
|
+
case ARGV[0]
|
26
|
+
when "start"
|
7
27
|
require 'zeus/server'
|
8
|
-
|
28
|
+
begin
|
29
|
+
require './.zeus.rb'
|
30
|
+
rescue LoadError
|
31
|
+
puts "\x1b[31mYour project is missing a config file (.zeus.rb), or you are not in the project root."
|
32
|
+
puts "You can run `zeus init` to generate a config file.\x1b[0m"
|
33
|
+
exit 1
|
34
|
+
end
|
9
35
|
Zeus::Server.run
|
36
|
+
when "init"
|
37
|
+
require 'zeus/init'
|
38
|
+
ARGV.shift # get "init" off the list
|
39
|
+
Zeus::Init.run(ARGV)
|
10
40
|
else
|
11
41
|
require 'zeus/client'
|
12
42
|
Zeus::Client.run
|
data/lib/tiny_thor.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
class TinyThor
|
2
|
+
|
3
|
+
class Task < Struct.new(:method_name, :desc, :long_desc, :method_options)
|
4
|
+
def arity(obj)
|
5
|
+
obj.method(method_name).arity
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.desc(name, a)
|
10
|
+
@desc = a
|
11
|
+
end
|
12
|
+
def self.long_desc(a)
|
13
|
+
@long_desc = a
|
14
|
+
end
|
15
|
+
def self.method_option(*a)
|
16
|
+
@method_options ||= []
|
17
|
+
@method_options << a
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.method_added(m)
|
21
|
+
desc, long_desc, method_options = @desc, @long_desc, (@method_options||[])
|
22
|
+
@desc, @long_desc, @method_options = nil, nil, nil
|
23
|
+
|
24
|
+
@tasks ||= {}
|
25
|
+
@tasks[m.to_s] = Task.new(m, desc, long_desc, method_options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.map(a)
|
29
|
+
a.each do |aliases, target|
|
30
|
+
aliases = [aliases] unless aliases.kind_of?(Array)
|
31
|
+
aliases.each do |name|
|
32
|
+
@tasks[name.to_s] = @tasks[target.to_s]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.task_for_name(name)
|
38
|
+
@tasks[name.to_s]
|
39
|
+
end
|
40
|
+
|
41
|
+
def task_for_name(name)
|
42
|
+
self.class.task_for_name(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def help(taskname = nil)
|
46
|
+
if taskname && task = task_for_name(taskname)
|
47
|
+
arity = task.arity(self)
|
48
|
+
puts <<-BANNER
|
49
|
+
Usage:
|
50
|
+
zeus #{taskname} #{arity == -1 ? "[ARGS]" : ""}
|
51
|
+
|
52
|
+
Description:
|
53
|
+
#{task.long_desc || task.desc}
|
54
|
+
BANNER
|
55
|
+
else
|
56
|
+
# this is super non-generic. problem for future-burke.
|
57
|
+
project_tasks = self.class.instance_variable_get("@tasks").
|
58
|
+
reject{|k,_|['init', 'start', 'help'].include?(k)}.values.uniq
|
59
|
+
|
60
|
+
tasks = project_tasks.map { |task|
|
61
|
+
" zeus %-14s # %s" % [task.method_name, task.desc]
|
62
|
+
}
|
63
|
+
|
64
|
+
puts <<-BANNER
|
65
|
+
Global Commands:
|
66
|
+
zeus help # show this help menu
|
67
|
+
zeus help [COMMAND] # show help for a specific command
|
68
|
+
zeus init # #{task_for_name(:init).desc}
|
69
|
+
zeus start # #{task_for_name(:start).desc}
|
70
|
+
|
71
|
+
Project-local Commands:
|
72
|
+
#{tasks.join("\n")}
|
73
|
+
BANNER
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.start
|
78
|
+
taskname = ARGV.shift
|
79
|
+
arguments = ARGV
|
80
|
+
|
81
|
+
taskname == "" and taskname = "help"
|
82
|
+
|
83
|
+
unless task = @tasks[taskname.to_s]
|
84
|
+
Zeus.ui.error "Could not find task \"#{taskname}\""
|
85
|
+
exit 1
|
86
|
+
end
|
87
|
+
|
88
|
+
instance = new
|
89
|
+
if instance.method(task.method_name).arity == 0 && arguments.any?
|
90
|
+
Zeus.ui.error "\"#{task.method_name}\" was called incorrectly. Call as \"zeus #{task.method_name}\"."
|
91
|
+
exit 1
|
92
|
+
end
|
93
|
+
instance.send(task.method_name, *arguments)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/lib/zeus.rb
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
require "zeus/version"
|
2
|
+
require 'zeus/server'
|
3
|
+
require "zeus/ui"
|
2
4
|
|
3
5
|
module Zeus
|
4
|
-
|
6
|
+
class ZeusError < StandardError
|
7
|
+
def self.status_code(code)
|
8
|
+
define_method(:status_code) { code }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.ui
|
13
|
+
@ui ||= UI.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.ui=(ui)
|
17
|
+
@ui = ui
|
18
|
+
end
|
19
|
+
|
5
20
|
end
|
data/lib/zeus/cli.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'zeus'
|
2
|
+
require 'tiny_thor'
|
3
|
+
|
4
|
+
module Zeus
|
5
|
+
class CLI < TinyThor
|
6
|
+
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
Zeus.ui = Zeus::UI.new
|
10
|
+
Zeus.ui.debug! #if options['verbose']
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "init", "Generates a zeus config file in the current working directory"
|
14
|
+
long_desc <<-D
|
15
|
+
Init tries to determine what kind of project is in the current working directory,
|
16
|
+
and generates a relevant config file. Currently the only supported template is
|
17
|
+
rails.
|
18
|
+
D
|
19
|
+
# method_option "rails", type: :string, banner: "Use the rails template instead of auto-detecting based on project contents"
|
20
|
+
def init
|
21
|
+
if File.exist?(".zeus.rb")
|
22
|
+
Zeus.ui.error ".zeus.rb already exists at #{Dir.pwd}/.zeus.rb"
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
Zeus.ui.info "Writing new .zeus.rb to #{Dir.pwd}/.zeus.rb"
|
27
|
+
FileUtils.cp(File.expand_path("../templates/rails.rb", __FILE__), '.zeus.rb')
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "start", "Start a zeus server for the project in the current working directory"
|
31
|
+
long_desc <<-D
|
32
|
+
starts a server that boots your application using the config file in
|
33
|
+
.zeus.rb. The server will take several seconds to start, after which you may
|
34
|
+
use the zeus runner commands (see `zeus help` for a list of available commands).
|
35
|
+
D
|
36
|
+
def start
|
37
|
+
require 'zeus/server'
|
38
|
+
begin
|
39
|
+
require './.zeus.rb'
|
40
|
+
rescue LoadError
|
41
|
+
Zeus.ui.error("Your project is missing a config file (.zeus.rb), or you are not\n"\
|
42
|
+
"in the project root. You can run `zeus init` to generate a config file.")
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
Zeus::Server.new.run
|
46
|
+
end
|
47
|
+
|
48
|
+
def help(*)
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "version", "Print zeus's version information and exit"
|
53
|
+
def version
|
54
|
+
Zeus.ui.info "Zeus version #{Zeus::VERSION}"
|
55
|
+
end
|
56
|
+
map %w(-v --version) => :version
|
57
|
+
|
58
|
+
begin
|
59
|
+
require './.zeus.rb'
|
60
|
+
Zeus::Server.acceptors.each do |acc|
|
61
|
+
desc acc.name, (acc.description || "#{acc.name} task defined in .zeus.rb")
|
62
|
+
define_method(acc.name) { |*args|
|
63
|
+
require 'zeus/client'
|
64
|
+
Zeus::Client.run(acc.name, args)
|
65
|
+
}
|
66
|
+
map acc.aliases => acc.name
|
67
|
+
end
|
68
|
+
rescue LoadError
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
data/lib/zeus/client.rb
CHANGED
@@ -20,32 +20,19 @@ module Zeus
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def self.run
|
23
|
+
def self.run(command, args)
|
24
24
|
maybe_raw do
|
25
25
|
PTY.open do |master, slave|
|
26
26
|
$stdout.tty? and master.winsize = $stdout.winsize
|
27
27
|
winch, winch_ = IO.pipe
|
28
28
|
trap("WINCH") { winch_ << "\0" }
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
socket = UNIXSocket.new(".zeus.test_testrb.sock")
|
33
|
-
when 'console', 'c'
|
34
|
-
socket = UNIXSocket.new(".zeus.dev_console.sock")
|
35
|
-
when 'server', 's'
|
36
|
-
socket = UNIXSocket.new(".zeus.dev_server.sock")
|
37
|
-
when 'rake'
|
38
|
-
socket = UNIXSocket.new(".zeus.dev_rake.sock")
|
39
|
-
when 'runner', 'r'
|
40
|
-
socket = UNIXSocket.new(".zeus.dev_runner.sock")
|
41
|
-
when 'generate', 'g'
|
42
|
-
socket = UNIXSocket.new(".zeus.dev_generate.sock")
|
43
|
-
end
|
30
|
+
socket = UNIXSocket.new(".zeus.sock")
|
31
|
+
socket << {command: command, arguments: args}.to_json << "\n"
|
44
32
|
socket.send_io(slave)
|
45
|
-
socket << ARGV.to_json << "\n"
|
46
33
|
slave.close
|
47
34
|
|
48
|
-
pid = socket.
|
35
|
+
pid = socket.readline.chomp.to_i
|
49
36
|
|
50
37
|
begin
|
51
38
|
buffer = ""
|
@@ -76,3 +63,5 @@ module Zeus
|
|
76
63
|
end
|
77
64
|
end
|
78
65
|
end
|
66
|
+
|
67
|
+
__FILE__ == $0 and Zeus::Client.run
|
data/lib/zeus/dsl.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'zeus/server/stage'
|
2
|
+
require 'zeus/server/acceptor'
|
3
|
+
|
4
|
+
module Zeus
|
5
|
+
module DSL
|
6
|
+
|
7
|
+
class Evaluator
|
8
|
+
def stage(name, &b)
|
9
|
+
stage = DSL::Stage.new(name)
|
10
|
+
stage.instance_eval(&b)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Acceptor
|
15
|
+
|
16
|
+
attr_reader :name, :aliases, :description, :action
|
17
|
+
def initialize(name, aliases, description, &b)
|
18
|
+
@name = name
|
19
|
+
@description = description
|
20
|
+
@aliases = aliases
|
21
|
+
@action = b
|
22
|
+
end
|
23
|
+
|
24
|
+
# ^ configuration
|
25
|
+
# V later use
|
26
|
+
|
27
|
+
def acceptors
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_domain_object(server)
|
32
|
+
Zeus::Server::Acceptor.new(server).tap do |stage|
|
33
|
+
stage.name = @name
|
34
|
+
stage.aliases = @aliases
|
35
|
+
stage.action = @action
|
36
|
+
stage.description = @description
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class Stage
|
43
|
+
|
44
|
+
attr_reader :pid, :stages, :actions
|
45
|
+
def initialize(name)
|
46
|
+
@name = name
|
47
|
+
@stages, @actions = [], []
|
48
|
+
end
|
49
|
+
|
50
|
+
def action(&b)
|
51
|
+
@actions << b
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def desc(desc)
|
56
|
+
@desc = desc
|
57
|
+
end
|
58
|
+
|
59
|
+
def stage(name, &b)
|
60
|
+
@stages << DSL::Stage.new(name).tap { |s| s.instance_eval(&b) }
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def command(name, *aliases, &b)
|
65
|
+
@stages << DSL::Acceptor.new(name, aliases, @desc, &b)
|
66
|
+
@desc = nil
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# ^ configuration
|
71
|
+
# V later use
|
72
|
+
|
73
|
+
def acceptors
|
74
|
+
stages.map(&:acceptors).flatten
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_domain_object(server)
|
78
|
+
Zeus::Server::Stage.new(server).tap do |stage|
|
79
|
+
stage.name = @name
|
80
|
+
stage.stages = @stages.map { |stage| stage.to_domain_object(server) }
|
81
|
+
stage.actions = @actions
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
data/lib/zeus/init.rb
ADDED