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 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
- Not yet. Zeus is nowhere near production-ready yet. Use only if you really like broken things.
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
- * Probably crashes a lot
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
- * Kill process when files are detected to have changed
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 fsevents instead of kqueue to reduce the obscene number of file descriptors.
52
- * Support epoll on linux
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 = File.expand_path("../../lib/", __FILE__)
4
- $:.unshift(zeus) unless $:.include?(zeus)
3
+ require 'zeus'
4
+ require 'zeus/cli'
5
+ Zeus::CLI.start
5
6
 
6
- if ARGV[0] == "start"
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
- require './.zeus.rb'
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
- # Your code goes here...
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
- case ARGV.shift
31
- when 'testrb', 't'
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.gets.strip.to_i
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
@@ -0,0 +1,17 @@
1
+ module Zeus
2
+ module Init
3
+
4
+ def self.run
5
+ if looks_like_rails?
6
+ copy_rails_template!
7
+ else
8
+ puts
9
+ end
10
+ end
11
+
12
+ def self.looks_like_rails?
13
+ File.exist?('Gemfile') && File.read('Gemfile').include?('rails')
14
+ end
15
+
16
+ end
17
+ end