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 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