zeus 0.2.0.beta1 → 0.2.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in zeus.gemspec
4
4
  gemspec
5
+
6
+ gem 'pry'
data/README.md CHANGED
@@ -17,7 +17,6 @@ Soon? You can use Zeus now, but don't expect it to be perfect. I'm working hard
17
17
  ## Ugly bits
18
18
 
19
19
  * Not battle-tested yet
20
- * Creates a bunch of sockets
21
20
  * Uses an obscene number of file descriptors
22
21
 
23
22
  ## Installation
@@ -26,7 +25,17 @@ Install the gem.
26
25
 
27
26
  gem install zeus
28
27
 
29
- Copy `examples/rails.rb` to `{your app}/.zeus.rb`
28
+ Run the project initializer.
29
+
30
+ zeus init
31
+
32
+ ## Upgrading from initial release
33
+
34
+ Since zeus is super-brand-new, the config file format changed already.
35
+
36
+ gem install zeus
37
+ rm .zeus.rb
38
+ zeus init
30
39
 
31
40
  ## Usage
32
41
 
@@ -45,23 +54,25 @@ Run some commands:
45
54
 
46
55
  ## TODO (roughly prioritized)
47
56
 
48
- * Fix all the bugs I added when I switched to singlesocket...
49
- * Make the code less terrible
50
- * Figure out how to run full test suites without multiple env loads
51
- * Support other frameworks?
52
- * Use fsevent instead of kqueue to reduce the obscene number of file descriptors.
53
- * Support inotify on linux
57
+ * Make sure client connection requests are handled immediately
58
+ * Acceptors booting should not be dependent on passing all loaded features to the file monitor
59
+ * Route all logging output through Zeus.ui
54
60
  * Handle connections for not-yet-started sockets
61
+ * Refactor, refactor, refactor...
62
+ * Support other frameworks?
63
+ * Periodically re-attempt to add lost files to KQueue
64
+ * Figure out how to run full test suites without multiple env loads
55
65
  * Don't replace a socket with changed deps until the new one is ready
66
+
67
+ ## Ideas (not quite TODOs)
68
+
56
69
  * (maybe) Start the preloader as a daemon transparently when any command is run, then wait for it to finish
70
+ * Use fsevent instead of kqueue to reduce the obscene number of file descriptors.
71
+ * Support inotify on linux
57
72
 
58
73
  ## Contributing
59
74
 
60
- 1. Fork it
61
- 2. Create your feature branch (`git checkout -b my-new-feature`)
62
- 3. Commit your changes (`git commit -am 'Added some feature'`)
63
- 4. Push to the branch (`git push origin my-new-feature`)
64
- 5. Create new Pull Request
75
+ Fork, Branch, Pull Request.
65
76
 
66
77
  ## Thanks...
67
78
 
data/bin/zeus CHANGED
@@ -1,43 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'zeus'
4
- require 'zeus/cli'
5
- Zeus::CLI.start
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"
27
- require 'zeus/server'
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
35
- Zeus::Server.run
36
- when "init"
37
- require 'zeus/init'
38
- ARGV.shift # get "init" off the list
39
- Zeus::Init.run(ARGV)
40
- else
41
- require 'zeus/client'
42
- Zeus::Client.run
43
- end
4
+ Zeus::CLI.start
Binary file
@@ -0,0 +1,133 @@
1
+ //
2
+ // main.m
3
+ // fsevents-wrapper
4
+ //
5
+ // Created by Burke Libbey on 2012-07-30.
6
+ // Copyright (c) 2012 Burke Libbey. All rights reserved.
7
+ //
8
+
9
+ #import <Foundation/Foundation.h>
10
+ #include <CoreServices/CoreServices.h>
11
+ #include <sys/stat.h>
12
+
13
+
14
+ static CFMutableArrayRef _watchedFiles;
15
+ static FSEventStreamRef _activeStream;
16
+ static NSMutableDictionary *_fileIsWatched;
17
+
18
+ static int flagsWorthReloadingFor = \
19
+ kFSEventStreamEventFlagItemRemoved | \
20
+ kFSEventStreamEventFlagItemRenamed | \
21
+ kFSEventStreamEventFlagItemModified;
22
+
23
+ void myCallbackFunction(
24
+ ConstFSEventStreamRef streamRef,
25
+ void *clientCallBackInfo,
26
+ size_t numEvents,
27
+ void *eventPaths,
28
+ const FSEventStreamEventFlags eventFlags[],
29
+ const FSEventStreamEventId eventIds[])
30
+ {
31
+ int i, flags;
32
+ char **paths = eventPaths;
33
+
34
+ for (i = 0; i < numEvents; i++) {
35
+ flags = eventFlags[i];
36
+
37
+ if (flags & (kFSEventStreamEventFlagItemIsFile | flagsWorthReloadingFor)) {
38
+ printf("%s\n", paths[i]);
39
+ fflush(stdout);
40
+ }
41
+ }
42
+ }
43
+
44
+ void configureStream()
45
+ {
46
+ if (CFArrayGetCount(_watchedFiles) == 0) return;
47
+
48
+ if (_activeStream) {
49
+ FSEventStreamStop(_activeStream);
50
+ FSEventStreamUnscheduleFromRunLoop(_activeStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
51
+ //CFRelease(_activeStream);
52
+ }
53
+
54
+ _activeStream = FSEventStreamCreate(NULL,
55
+ &myCallbackFunction,
56
+ NULL,
57
+ _watchedFiles,
58
+ kFSEventStreamEventIdSinceNow,
59
+ 1.0, // latency
60
+ kFSEventStreamCreateFlagFileEvents);
61
+
62
+ FSEventStreamScheduleWithRunLoop(_activeStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
63
+
64
+ FSEventStreamStart(_activeStream);
65
+
66
+ }
67
+
68
+ int maybeAddFileToWatchList(char *line)
69
+ {
70
+ CFStringRef file = CFStringCreateWithCString(NULL, line, kCFStringEncodingASCII);
71
+ struct stat buf;
72
+
73
+ if ([_fileIsWatched valueForKey:(__bridge NSString *)file]) {
74
+ return 0;
75
+ } else if (stat(line, &buf) == 0) {
76
+ [_fileIsWatched setValue:@"yes" forKey:(__bridge NSString *)file];
77
+ CFArrayAppendValue(_watchedFiles, file);
78
+ return 1;
79
+ } else {
80
+ return 0;
81
+ }
82
+ }
83
+
84
+ void handleInputFiles()
85
+ {
86
+ int anyChanges = 0;
87
+
88
+ char line[2048];
89
+
90
+ fd_set fdset;
91
+
92
+ FD_ZERO(&fdset);
93
+ FD_SET(STDIN_FILENO, &fdset);
94
+ struct timeval timeout;
95
+ timeout.tv_sec = 0;
96
+ timeout.tv_usec = 0;
97
+
98
+ select(STDIN_FILENO+1, &fdset, NULL, NULL, &timeout);
99
+ while (FD_ISSET(STDIN_FILENO, &fdset) && fgets(line, 2048, stdin) != NULL) {
100
+ line[strlen(line)-1] = 0;
101
+ anyChanges |= maybeAddFileToWatchList(line);
102
+ select(STDIN_FILENO+1, &fdset, NULL, NULL, &timeout);
103
+ }
104
+
105
+ if (anyChanges) {
106
+ configureStream();
107
+ }
108
+ }
109
+
110
+ void configureTimerAndRun()
111
+ {
112
+ CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL,
113
+ 0,
114
+ 0.5,
115
+ 0,
116
+ 0,
117
+ &handleInputFiles,
118
+ NULL);
119
+
120
+ CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
121
+ CFRunLoopRun();
122
+ }
123
+
124
+ int main(int argc, const char * argv[])
125
+ {
126
+ @autoreleasepool {
127
+ _watchedFiles = CFArrayCreateMutable(NULL, 0, NULL);
128
+ _fileIsWatched = [[NSMutableDictionary alloc] initWithCapacity:500];
129
+
130
+ configureTimerAndRun();
131
+ }
132
+ return 0;
133
+ }
@@ -1,4 +1,4 @@
1
- class TinyThor
1
+ class Thrud
2
2
 
3
3
  class Task < Struct.new(:method_name, :desc, :long_desc, :method_options)
4
4
  def arity(obj)
@@ -78,7 +78,7 @@ BANNER
78
78
  taskname = ARGV.shift
79
79
  arguments = ARGV
80
80
 
81
- taskname == "" and taskname = "help"
81
+ taskname ||= "help"
82
82
 
83
83
  unless task = @tasks[taskname.to_s]
84
84
  Zeus.ui.error "Could not find task \"#{taskname}\""
@@ -1,8 +1,13 @@
1
- require "zeus/version"
2
- require 'zeus/server'
3
- require "zeus/ui"
4
-
5
1
  module Zeus
2
+ SOCKET_NAME = '.zeus.sock'
3
+
4
+ autoload :UI, 'zeus/ui'
5
+ autoload :CLI, 'zeus/cli'
6
+ autoload :DSL, 'zeus/dsl'
7
+ autoload :Server, 'zeus/server'
8
+ autoload :Client, 'zeus/client'
9
+ autoload :Version, 'zeus/version'
10
+
6
11
  class ZeusError < StandardError
7
12
  def self.status_code(code)
8
13
  define_method(:status_code) { code }
@@ -1,8 +1,7 @@
1
- require 'zeus'
2
- require 'tiny_thor'
1
+ require 'thrud'
3
2
 
4
3
  module Zeus
5
- class CLI < TinyThor
4
+ class CLI < Thrud
6
5
 
7
6
  def initialize(*)
8
7
  super
@@ -34,7 +33,6 @@ module Zeus
34
33
  use the zeus runner commands (see `zeus help` for a list of available commands).
35
34
  D
36
35
  def start
37
- require 'zeus/server'
38
36
  begin
39
37
  require './.zeus.rb'
40
38
  rescue LoadError
@@ -60,7 +58,6 @@ module Zeus
60
58
  Zeus::Server.acceptors.each do |acc|
61
59
  desc acc.name, (acc.description || "#{acc.name} task defined in .zeus.rb")
62
60
  define_method(acc.name) { |*args|
63
- require 'zeus/client'
64
61
  Zeus::Client.run(acc.name, args)
65
62
  }
66
63
  map acc.aliases => acc.name
@@ -6,61 +6,90 @@ require "socket"
6
6
  module Zeus
7
7
  class Client
8
8
 
9
+ attr_accessor :pid
10
+
9
11
  SIGNALS = {
10
12
  "\x03" => "TERM",
11
13
  "\x1C" => "QUIT"
12
14
  }
13
15
  SIGNAL_REGEX = Regexp.union(SIGNALS.keys)
14
16
 
15
- def self.maybe_raw(&b)
16
- if $stdout.tty?
17
- $stdout.raw(&b)
18
- else
19
- b.call
20
- end
17
+ def self.run(command, args)
18
+ new.run(command, args)
21
19
  end
22
20
 
23
- def self.run(command, args)
21
+ def run(command, args)
24
22
  maybe_raw do
25
23
  PTY.open do |master, slave|
26
- $stdout.tty? and master.winsize = $stdout.winsize
27
- winch, winch_ = IO.pipe
28
- trap("WINCH") { winch_ << "\0" }
24
+ @master = master
25
+ set_winsize
29
26
 
30
- socket = UNIXSocket.new(".zeus.sock")
31
- socket << {command: command, arguments: args}.to_json << "\n"
32
- socket.send_io(slave)
33
- slave.close
34
-
35
- pid = socket.readline.chomp.to_i
27
+ @winch = make_winch_channel
28
+ pid = connect_to_server(command, args, slave)
36
29
 
30
+ buffer = ""
37
31
  begin
38
- buffer = ""
39
-
40
- while ready = select([winch, master, $stdin])[0]
41
- if ready.include?(winch)
42
- winch.read(1)
43
- $stdout.tty? and master.winsize = $stdout.winsize
44
- Process.kill("WINCH", pid)
45
- end
46
-
47
- if ready.include?($stdin)
48
- input = $stdin.readpartial(4096, buffer)
49
- input.scan(SIGNAL_REGEX).each { |signal|
50
- Process.kill(SIGNALS[signal], pid)
51
- }
52
- master << input
53
- end
54
-
55
- if ready.include?(master)
56
- $stdout << master.readpartial(4096, buffer)
57
- end
32
+ while ready = select([@winch, @master, $stdin])[0]
33
+ handle_winch if ready.include?(@winch)
34
+ handle_stdin(buffer) if ready.include?($stdin)
35
+ handle_master(buffer) if ready.include?(@master)
58
36
  end
59
37
  rescue EOFError
60
38
  end
61
39
  end
62
40
  end
63
41
  end
42
+
43
+ private
44
+
45
+ def connect_to_server(command, arguments, slave, socket_path = Zeus::SOCKET_NAME)
46
+ socket = UNIXSocket.new(socket_path)
47
+ socket << {command: command, arguments: arguments}.to_json << "\n"
48
+ socket.send_io(slave)
49
+ slave.close
50
+
51
+ pid = socket.readline.chomp.to_i
52
+ rescue Errno::ENOENT, Errno::ECONNREFUSED, Errno::ECONNRESET
53
+ Zeus.ui.error "Zeus doesn't seem to be running, try 'zeus start`"
54
+ abort
55
+ end
56
+
57
+ def make_winch_channel
58
+ winch, winch_ = IO.pipe
59
+ trap("WINCH") { winch_ << "\0" }
60
+ winch
61
+ end
62
+
63
+ def handle_winch
64
+ @winch.read(1)
65
+ set_winsize
66
+ Process.kill("WINCH", pid) if pid
67
+ end
68
+
69
+ def handle_stdin(buffer)
70
+ input = $stdin.readpartial(4096, buffer)
71
+ input.scan(SIGNAL_REGEX).each { |signal|
72
+ Process.kill(SIGNALS[signal], pid)
73
+ }
74
+ @master << input
75
+ end
76
+
77
+ def handle_master(buffer)
78
+ $stdout << @master.readpartial(4096, buffer)
79
+ end
80
+
81
+ def set_winsize
82
+ $stdout.tty? and @master.winsize = $stdout.winsize
83
+ end
84
+
85
+ def maybe_raw(&b)
86
+ if $stdout.tty?
87
+ $stdout.raw(&b)
88
+ else
89
+ b.call
90
+ end
91
+ end
92
+
64
93
  end
65
94
  end
66
95
 
@@ -1,6 +1,3 @@
1
- require 'zeus/server/stage'
2
- require 'zeus/server/acceptor'
3
-
4
1
  module Zeus
5
2
  module DSL
6
3
 
@@ -2,18 +2,18 @@ require 'json'
2
2
  require 'socket'
3
3
 
4
4
  require 'rb-kqueue'
5
-
6
5
  require 'zeus/process'
7
- require 'zeus/dsl'
8
- require 'zeus/server/file_monitor'
9
- require 'zeus/server/client_handler'
10
- require 'zeus/server/process_tree_monitor'
11
- require 'zeus/server/acceptor_registration_monitor'
12
- require 'zeus/server/acceptor'
13
6
 
14
7
  module Zeus
15
8
  class Server
16
9
 
10
+ autoload :Stage, 'zeus/server/stage'
11
+ autoload :Acceptor, 'zeus/server/acceptor'
12
+ autoload :FileMonitor, 'zeus/server/file_monitor'
13
+ autoload :ClientHandler, 'zeus/server/client_handler'
14
+ autoload :ProcessTreeMonitor, 'zeus/server/process_tree_monitor'
15
+ autoload :AcceptorRegistrationMonitor, 'zeus/server/acceptor_registration_monitor'
16
+
17
17
  def self.define!(&b)
18
18
  @@definition = Zeus::DSL::Evaluator.new.instance_eval(&b)
19
19
  end
@@ -24,7 +24,7 @@ module Zeus
24
24
 
25
25
  attr_reader :client_handler, :acceptor_registration_monitor
26
26
  def initialize
27
- @file_monitor = FileMonitor.new(&method(:dependency_did_change))
27
+ @file_monitor = FileMonitor::FSEvent.new(&method(:dependency_did_change))
28
28
  @acceptor_registration_monitor = AcceptorRegistrationMonitor.new
29
29
  @process_tree_monitor = ProcessTreeMonitor.new
30
30
  @client_handler = ClientHandler.new(acceptor_registration_monitor)
@@ -39,22 +39,18 @@ module Zeus
39
39
 
40
40
  PID_TYPE = "P"
41
41
  def w_pid line
42
- begin
43
- @w_msg.send(PID_TYPE + line, 0)
44
- rescue Errno::ENOBUFS
45
- sleep 0.2
46
- retry
47
- end
42
+ @w_msg.send(PID_TYPE + line, 0)
43
+ rescue Errno::ENOBUFS
44
+ sleep 0.2
45
+ retry
48
46
  end
49
47
 
50
48
  FEATURE_TYPE = "F"
51
49
  def w_feature line
52
- begin
53
- @w_msg.send(FEATURE_TYPE + line, 0)
54
- rescue Errno::ENOBUFS
55
- sleep 0.2
56
- retry
57
- end
50
+ @w_msg.send(FEATURE_TYPE + line, 0)
51
+ rescue Errno::ENOBUFS
52
+ sleep 0.2
53
+ retry
58
54
  end
59
55
 
60
56
  def run
@@ -69,48 +65,38 @@ module Zeus
69
65
  @w_msg.close
70
66
 
71
67
  loop do
72
- @file_monitor.process_events
73
-
74
- datasources = [@r_msg,
75
- @acceptor_registration_monitor.datasource, @client_handler.datasource]
76
-
77
- # TODO: It would be really nice if we could put the queue poller in the select somehow.
78
- # --investigate kqueue. Is this possible?
79
- begin
80
- rs, _, _ = IO.select(datasources, [], [], 1)
81
- rescue Errno::EBADF
82
- puts "EBADF" unless defined?($asdf)
83
- sleep 1
84
- $asdf = true
68
+ monitors = [@file_monitor, @acceptor_registration_monitor, @client_handler]
69
+ # TODO: Make @r_msg a Monitor instead. All that logic should be its own thing.
70
+ datasources = [@r_msg, *monitors.map(&:datasource)]
71
+
72
+ ready, _, _ = IO.select(datasources, [], [], 1)
73
+ next unless ready
74
+ monitors.each do |m|
75
+ m.on_datasource_event if ready.include?(m.datasource)
85
76
  end
86
- rs.each do |r|
87
- case r
88
- when @acceptor_registration_monitor.datasource
89
- @acceptor_registration_monitor.on_datasource_event
90
- when @r_msg ; handle_messages
91
- when @client_handler.datasource
92
- @client_handler.on_datasource_event
93
- end
94
- end if rs
77
+ handle_messages if ready.include?(@r_msg)
95
78
  end
96
79
 
80
+ ensure
81
+ File.unlink(Zeus::SOCKET_NAME)
97
82
  end
98
83
 
99
84
  def handle_messages
100
85
  loop do
101
- begin
102
- data = @r_msg.recv_nonblock(1024)
103
- case data[0]
104
- when FEATURE_TYPE
105
- handle_feature_message(data[1..-1])
106
- when PID_TYPE
107
- handle_pid_message(data[1..-1])
108
- else
109
- raise "Unrecognized message"
110
- end
111
- rescue Errno::EAGAIN
112
- break
113
- end
86
+ handle_message
87
+ end
88
+ rescue Errno::EAGAIN
89
+ end
90
+
91
+ def handle_message
92
+ data = @r_msg.recv_nonblock(1024)
93
+ case data[0]
94
+ when FEATURE_TYPE
95
+ handle_feature_message(data[1..-1])
96
+ when PID_TYPE
97
+ handle_pid_message(data[1..-1])
98
+ else
99
+ raise "Unrecognized message"
114
100
  end
115
101
  end
116
102
 
@@ -36,9 +36,9 @@ module Zeus
36
36
 
37
37
  @server.w_pid "#{pid}:#{Process.ppid}"
38
38
 
39
- puts "\x1b[35m[zeus] starting acceptor `#{@name}`\x1b[0m"
39
+ Zeus.ui.as_zeus "starting acceptor `#{@name}`"
40
40
  trap("INT") {
41
- puts "\x1b[35m[zeus] killing acceptor `#{@name}`\x1b[0m"
41
+ Zeus.ui.as_zeus "killing acceptor `#{@name}`"
42
42
  exit 0
43
43
  }
44
44
 
@@ -1,8 +1,6 @@
1
1
  require 'socket'
2
2
  require 'json'
3
3
 
4
- require 'zeus/server/acceptor'
5
-
6
4
  module Zeus
7
5
  class Server
8
6
  # The model here is kind of convoluted, so here's an explanation of what's
@@ -26,23 +24,16 @@ module Zeus
26
24
  # 4. ClientHandler forwards the pid to the client over S_CLI.
27
25
  #
28
26
  class ClientHandler
29
- SERVER_SOCK = ".zeus.sock"
30
-
31
27
  def datasource ; @server ; end
32
28
  def on_datasource_event ; handle_server_connection ; end
33
29
 
34
30
  def initialize(registration_monitor)
35
31
  @reg_monitor = registration_monitor
36
- begin
37
- @server = UNIXServer.new(SERVER_SOCK)
38
- @server.listen(10)
39
- rescue Errno::EADDRINUSE
40
- Zeus.ui.error "Zeus appears to be already running in this project. If not, remove .zeus.sock and try again."
41
- exit 1
42
- # ensure
43
- # @server.close rescue nil
44
- # File.unlink(SERVER_SOCK)
45
- end
32
+ @server = UNIXServer.new(Zeus::SOCKET_NAME)
33
+ @server.listen(10)
34
+ rescue Errno::EADDRINUSE
35
+ Zeus.ui.error "Zeus appears to be already running in this project. If not, remove .zeus.sock and try again."
36
+ exit 1
46
37
  end
47
38
 
48
39
  def handle_server_connection
@@ -71,7 +62,7 @@ module Zeus
71
62
  # TODO handle nothing found
72
63
  usock.send_io(client_terminal)
73
64
 
74
- puts "accepting connection for #{command}"
65
+ Zeus.ui.info "accepting connection for #{command}"
75
66
 
76
67
  # 4
77
68
  acceptor.socket.puts arguments.to_json
@@ -2,56 +2,8 @@ require 'rb-kqueue'
2
2
 
3
3
  module Zeus
4
4
  class Server
5
- class FileMonitor
6
-
7
- TARGET_FD_LIMIT = 8192
8
-
9
- def initialize(&change_callback)
10
- configure_file_descriptor_resource_limit
11
- @queue = KQueue::Queue.new
12
- @watched_files = {}
13
- @deleted_files = []
14
- @change_callback = change_callback
15
- end
16
-
17
- def process_events
18
- @queue.poll
19
- end
20
-
21
- def watch(file)
22
- return if @watched_files[file]
23
- @watched_files[file] = true
24
- @queue.watch_file(file, :write, :extend, :rename, :delete, &method(:file_did_change))
25
- rescue Errno::ENOENT
26
- Zeus.ui.debug("No file found at #{file}")
27
- end
28
-
29
- private
30
-
31
- def file_did_change(event)
32
- Zeus.ui.info("Dependency change at #{event.watcher.path}")
33
- resubscribe_deleted_file(event) if event.flags.include?(:delete)
34
- @change_callback.call(event.watcher.path)
35
- end
36
-
37
- def configure_file_descriptor_resource_limit
38
- limit = Process.getrlimit(Process::RLIMIT_NOFILE)
39
- if limit[0] < TARGET_FD_LIMIT && limit[1] >= TARGET_FD_LIMIT
40
- Process.setrlimit(Process::RLIMIT_NOFILE, TARGET_FD_LIMIT)
41
- else
42
- puts "\x1b[33m[zeus] Warning: increase the max number of file descriptors. If you have a large project, this max cause a crash in about 10 seconds.\x1b[0m"
43
- end
44
- end
45
-
46
- def resubscribe_deleted_file(event)
47
- event.watcher.disable!
48
- begin
49
- @queue.watch_file(event.watcher.path, :write, :extend, :rename, :delete, &method(:file_did_change))
50
- rescue Errno::ENOENT
51
- @deleted_files << event.watcher.path
52
- end
53
- end
54
-
5
+ module FileMonitor
6
+ autoload :FSEvent, 'zeus/server/file_monitor/fsevent'
55
7
  end
56
8
  end
57
9
  end
@@ -0,0 +1,63 @@
1
+ require 'open3'
2
+
3
+ module Zeus
4
+ class Server
5
+ module FileMonitor
6
+ class FSEvent
7
+ WRAPPER_PATH = File.expand_path("../../../../../ext/fsevents-wrapper/fsevents-wrapper", __FILE__)
8
+
9
+ def datasource ; @io_out ; end
10
+ def on_datasource_event ; handle_changed_files ; end
11
+
12
+ def initialize(&change_callback)
13
+ @change_callback = change_callback
14
+ @io_in, @io_out, _ = Open3.popen2e(WRAPPER_PATH)
15
+ @watched_files = {}
16
+ @buffer = ""
17
+ end
18
+
19
+ def handle_changed_files
20
+ loop do
21
+ begin
22
+ read_and_notify_files
23
+ rescue Errno::EAGAIN
24
+ break
25
+ end
26
+ end
27
+ end
28
+
29
+ def read_and_notify_files
30
+ lines = @io_out.read_nonblock(30000)
31
+ files = lines.split("\n")
32
+ files[0] = "#{@buffer}#{files[0]}" unless @buffer == ""
33
+ unless lines[-1] == "\n"
34
+ @buffer = files.pop
35
+ end
36
+
37
+ files.each do |file|
38
+ file_did_change(file)
39
+ end
40
+ end
41
+
42
+ def watch(file)
43
+ return false if @watched_files[file]
44
+ @watched_files[file] = true
45
+ File.open('a.log', 'a') { |f| f.puts file }
46
+ @io_in.puts file
47
+ true
48
+ end
49
+
50
+ private
51
+
52
+ def file_did_change(file)
53
+ Zeus.ui.info("Dependency change at #{file}")
54
+ @change_callback.call(file)
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+
63
+
@@ -76,7 +76,7 @@ module Zeus
76
76
  def kill_nodes_with_feature(file, base = @root)
77
77
  if base.has_feature?(file)
78
78
  if base == @root.children[0] || base == @root
79
- puts "\x1b[31mOne of zeus's dependencies changed. Not killing zeus. You may have to restart the server.\x1b[0m"
79
+ Zeus.ui.error "One of zeus's dependencies changed. Not killing zeus. You may have to restart the server."
80
80
  return false
81
81
  end
82
82
  kill_node(base)
@@ -1,6 +1,7 @@
1
1
  module Zeus
2
2
  class Server
3
3
  class Stage
4
+ HasNoChildren = Class.new(Exception)
4
5
 
5
6
  attr_accessor :name, :stages, :actions
6
7
  attr_reader :pid
@@ -17,9 +18,10 @@ module Zeus
17
18
  $0 = "zeus spawner: #{@name}"
18
19
  pid = Process.pid
19
20
  @server.w_pid "#{pid}:#{Process.ppid}"
20
- puts "\x1b[35m[zeus] starting spawner `#{@name}`\x1b[0m"
21
+
22
+ Zeus.ui.as_zeus("starting spawner `#{@name}`")
21
23
  trap("INT") {
22
- puts "\x1b[35m[zeus] killing spawner `#{@name}`\x1b[0m"
24
+ Zeus.ui.as_zeus("killing spawner `#{@name}`")
23
25
  exit 0
24
26
  }
25
27
 
@@ -38,7 +40,7 @@ module Zeus
38
40
  begin
39
41
  pid = Process.wait
40
42
  rescue Errno::ECHILD
41
- raise "Stage `#{@name}` has no children. All terminal nodes must be acceptors"
43
+ raise HasNoChildren.new("Stage `#{@name}` - All terminal nodes must be acceptors")
42
44
  end
43
45
  if (status = $?.exitstatus) > 0
44
46
  exit status
@@ -19,7 +19,7 @@ Zeus::Server.define! do
19
19
  stage :dev do
20
20
  action do
21
21
  Bundler.require(:development)
22
- ENV['RAILS_ENV'] = "development"
22
+ Rails.env = ENV['RAILS_ENV'] = "development"
23
23
  require APP_PATH
24
24
  Rails.application.require_environment!
25
25
  end
@@ -57,22 +57,23 @@ Zeus::Server.define! do
57
57
  end
58
58
  end
59
59
 
60
- stage :test do
61
- action do
62
- ENV['RAILS_ENV'] = "test"
63
- Bundler.require(:test)
64
- require APP_PATH
65
- Rails.application.require_environment!
66
- end
60
+ # stage :test do
61
+ # action do
62
+ # Rails.env = ENV['RAILS_ENV'] = "test"
63
+ # Bundler.require(:test)
64
+ # require APP_PATH
65
+ # Rails.application.require_environment!
66
+ # end
67
67
 
68
- command :testrb do
69
- (r = Test::Unit::AutoRunner.new(true)).process_args(ARGV) or
70
- abort r.options.banner + " tests..."
71
- exit r.run
72
- end
68
+ # command :testrb do
69
+ # (r = Test::Unit::AutoRunner.new(true)).process_args(ARGV) or
70
+ # abort r.options.banner + " tests..."
71
+ # exit r.run
72
+ # end
73
73
 
74
- end
74
+ # end
75
75
 
76
76
  end
77
77
  end
78
78
  end
79
+
@@ -6,6 +6,10 @@ module Zeus
6
6
  @debug = ENV['DEBUG']
7
7
  end
8
8
 
9
+ def as_zeus(msg)
10
+ tell_me("[zeus] #{msg}",:purple)
11
+ end
12
+
9
13
  def info(msg)
10
14
  tell_me(msg, nil) if !@quiet
11
15
  end
@@ -45,6 +49,7 @@ module Zeus
45
49
  when :red ; "\x1b[31m#{msg}\x1b[0m"
46
50
  when :green ; "\x1b[32m#{msg}\x1b[0m"
47
51
  when :yellow ; "\x1b[33m#{msg}\x1b[0m"
52
+ when :purple ; "\x1b[35m#{msg}\x1b[0m"
48
53
  else ; msg
49
54
  end
50
55
  puts msg
@@ -1,3 +1,3 @@
1
1
  module Zeus
2
- VERSION = "0.2.0.beta1"
2
+ VERSION = "0.2.0.beta2"
3
3
  end
@@ -8,12 +8,10 @@ Gem::Specification.new do |gem|
8
8
  gem.summary = %q{Zeus is an alpha-quality application preloader with terrible documentation.}
9
9
  gem.homepage = "http://github.com/burke/zeus"
10
10
 
11
- gem.files = `git ls-files`.split($\)
11
+ gem.files = `git ls-files`.split("\n").reject{ |f| f =~ /xcodeproj/ }
12
12
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
13
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
14
  gem.name = "zeus"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Zeus::VERSION
17
-
18
- gem.add_dependency "rb-kqueue-burke", "~> 0.1.0"
19
17
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zeus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.beta1
4
+ version: 0.2.0.beta2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,19 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-28 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: rb-kqueue-burke
16
- requirement: &70116037263980 !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ~>
20
- - !ruby/object:Gem::Version
21
- version: 0.1.0
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: *70116037263980
12
+ date: 2012-07-31 00:00:00.000000000 Z
13
+ dependencies: []
25
14
  description: Zeus preloads pretty much everything you'll ever want to use in development.
26
15
  email:
27
16
  - burke@libbey.me
@@ -36,18 +25,21 @@ files:
36
25
  - README.md
37
26
  - Rakefile
38
27
  - bin/zeus
39
- - lib/tiny_thor.rb
28
+ - ext/.DS_Store
29
+ - ext/fsevents-wrapper/fsevents-wrapper
30
+ - ext/fsevents-wrapper/main.m
31
+ - lib/thrud.rb
40
32
  - lib/zeus.rb
41
33
  - lib/zeus/cli.rb
42
34
  - lib/zeus/client.rb
43
35
  - lib/zeus/dsl.rb
44
- - lib/zeus/init.rb
45
36
  - lib/zeus/process.rb
46
37
  - lib/zeus/server.rb
47
38
  - lib/zeus/server/acceptor.rb
48
39
  - lib/zeus/server/acceptor_registration_monitor.rb
49
40
  - lib/zeus/server/client_handler.rb
50
41
  - lib/zeus/server/file_monitor.rb
42
+ - lib/zeus/server/file_monitor/fsevent.rb
51
43
  - lib/zeus/server/process_tree_monitor.rb
52
44
  - lib/zeus/server/stage.rb
53
45
  - lib/zeus/templates/rails.rb
@@ -1,17 +0,0 @@
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