zeus 0.1.0 → 0.2.0.beta1
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 +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