invoker 0.0.3 → 0.1.1.pre

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -2,5 +2,6 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
+
5
6
  script: bundle exec rake spec
6
7
 
data/invoker.gemspec CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
29
29
  s.add_dependency("slop")
30
30
  s.add_dependency("iniparse")
31
31
  s.add_dependency("colored")
32
+ s.add_dependency("formatador")
32
33
  s.add_development_dependency("bacon")
33
34
  s.add_development_dependency("mocha")
34
35
  s.add_development_dependency("mocha-on-bacon")
@@ -7,26 +7,37 @@ module Invoker
7
7
  end
8
8
 
9
9
  def read_and_execute
10
- command_info = client_socket.read()
10
+ command_info = client_socket.gets()
11
+ command_info && command_info.strip!
12
+
11
13
  if command_info && !command_info.empty?
12
14
  worker_command, command_label, rest_args = command_info.strip.split(" ")
13
- if worker_command && command_label
15
+ worker_command.strip!
16
+ if worker_command
14
17
  run_command(worker_command, command_label, rest_args)
15
18
  end
16
19
  end
17
- client_socket.close()
18
20
  end
19
21
 
20
22
  def run_command(worker_command, command_label, rest_args = nil)
21
23
  case worker_command
22
24
  when 'add'
23
- Invoker::COMMANDER.add_command_by_label(command_label)
25
+ Invoker::COMMANDER.on_next_tick(command_label) { |b_command_label|
26
+ add_command_by_label(b_command_label)
27
+ }
28
+ when 'list'
29
+ json = Invoker::COMMANDER.list_commands()
30
+ client_socket.puts(json)
24
31
  when 'remove'
25
- Invoker::COMMANDER.remove_command(command_label, rest_args)
32
+ Invoker::COMMANDER.on_next_tick(command_label, rest_args) { |b_command_label,b_rest_args|
33
+ remove_command(b_command_label, b_rest_args)
34
+ }
26
35
  when 'reload'
27
- Invoker::COMMANDER.reload_command(command_label)
36
+ Invoker::COMMANDER.on_next_tick(command_label, rest_args) { |b_command_label, b_rest_args|
37
+ reload_command(b_command_label, b_rest_args)
38
+ }
28
39
  else
29
- $stdout.puts("\n Invalid command".red)
40
+ Invoker::Logger.puts("\n Invalid command".red)
30
41
  end
31
42
  end
32
43
  end
@@ -6,13 +6,13 @@ module Invoker
6
6
  SOCKET_PATH = "/tmp/invoker"
7
7
  def initialize
8
8
  @open_clients = []
9
- clean_old_socket()
10
- UNIXServer.open(SOCKET_PATH) do |client|
11
- loop do
12
- client_socket = client.accept
13
- process_client(client_socket)
9
+ Socket.unix_server_loop(SOCKET_PATH) {|sock, client_addrinfo|
10
+ begin
11
+ process_client(sock)
12
+ ensure
13
+ sock.close
14
14
  end
15
- end
15
+ }
16
16
  end
17
17
 
18
18
  def clean_old_socket
@@ -19,12 +19,16 @@ module Invoker
19
19
  end
20
20
 
21
21
  def unbind
22
- # $stdout.print(".")
22
+ Invoker::Logger.print(".")
23
23
  end
24
24
 
25
25
  # Print the lines received over the network
26
26
  def receive_line(line)
27
- $stdout.puts "#{@command_label.send(color)} : #{line}"
27
+ Invoker::Logger.puts "#{@command_label.send(color)} : #{line}"
28
+ end
29
+
30
+ def to_h
31
+ {:command_label => command_label, :pid => pid.to_s}
28
32
  end
29
33
  end
30
34
  end
@@ -1,11 +1,13 @@
1
1
  require "io/console"
2
2
  require 'pty'
3
+ require "json"
3
4
 
4
5
  module Invoker
5
6
  class Commander
6
7
  MAX_PROCESS_COUNT = 10
7
8
  LABEL_COLORS = ['green', 'yellow', 'blue', 'magenta', 'cyan']
8
9
  attr_accessor :reactor, :workers, :thread_group, :open_pipes
10
+ attr_accessor :event_manager, :runnables
9
11
 
10
12
  def initialize
11
13
  # mapping between open pipes and worker classes
@@ -16,10 +18,16 @@ module Invoker
16
18
 
17
19
  @thread_group = ThreadGroup.new()
18
20
  @worker_mutex = Mutex.new()
21
+
22
+ @event_manager = Invoker::Event::Manager.new()
23
+ @runnables = []
24
+
19
25
  @reactor = Invoker::Reactor.new
20
26
  Thread.abort_on_exception = true
21
27
  end
22
28
 
29
+ # Start the invoker process supervisor. This method starts a unix server
30
+ # in separate thread that listens for incoming commands.
23
31
  def start_manager
24
32
  if !Invoker::CONFIG.processes || Invoker::CONFIG.processes.empty?
25
33
  raise Invoker::Errors::InvalidConfig.new("No processes configured in config file")
@@ -28,9 +36,12 @@ module Invoker
28
36
  unix_server_thread = Thread.new { Invoker::CommandListener::Server.new() }
29
37
  thread_group.add(unix_server_thread)
30
38
  Invoker::CONFIG.processes.each { |process_info| add_command(process_info) }
31
- reactor.start
39
+ start_event_loop()
32
40
  end
33
41
 
42
+ # Start given command and start a background thread to wait on the process
43
+ #
44
+ # @param process_info [OpenStruct(command, directory)]
34
45
  def add_command(process_info)
35
46
  m, s = PTY.open
36
47
  s.raw! # disable newline conversion.
@@ -39,14 +50,20 @@ module Invoker
39
50
 
40
51
  s.close()
41
52
 
42
- selected_color = LABEL_COLORS.shift()
43
- LABEL_COLORS.push(selected_color)
44
- worker = Invoker::CommandWorker.new(process_info.label, m, pid, selected_color)
53
+ worker = Invoker::CommandWorker.new(process_info.label, m, pid, select_color())
45
54
 
46
55
  add_worker(worker)
47
56
  wait_on_pid(process_info.label,pid)
48
57
  end
49
58
 
59
+ # List currently running commands
60
+ def list_commands
61
+ Invoker::ProcessPrinter.to_json(workers)
62
+ end
63
+
64
+ # Start executing given command by their label name.
65
+ #
66
+ # @param command_label [String] Command label of process specified in config file.
50
67
  def add_command_by_label(command_label)
51
68
  process_info = Invoker::CONFIG.processes.detect {|pconfig|
52
69
  pconfig.label == command_label
@@ -56,30 +73,86 @@ module Invoker
56
73
  end
57
74
  end
58
75
 
59
- def reload_command(command_label)
60
- remove_command(command_label)
61
- add_command_by_label(command_label)
76
+ # Reload a process given by command label
77
+ #
78
+ # @params command_label [String] Command label of process specified in config file.
79
+ def reload_command(command_label, rest_args)
80
+ if remove_command(command_label, rest_args)
81
+ event_manager.schedule_event(command_label, :worker_removed) {
82
+ add_command_by_label(command_label)
83
+ }
84
+ else
85
+ add_command_by_label(command_label)
86
+ end
62
87
  end
63
88
 
89
+ # Remove a process from list of processes managed by invoker supervisor.It also
90
+ # kills the process before removing it from the list.
91
+ #
92
+ # @param command_label [String] Command label of process specified in config file
93
+ # @param rest_args [Array] Additional option arguments, such as signal that can be used.
94
+ # @return [Boolean] if process existed and was removed else false
64
95
  def remove_command(command_label, rest_args)
65
96
  worker = workers[command_label]
97
+ return false unless worker
66
98
  signal_to_use = rest_args ? Array(rest_args).first : 'INT'
67
99
 
68
- if worker
69
- $stdout.puts("Removing #{command_label} with signal #{signal_to_use}".red)
70
- process_kill(worker.pid, signal_to_use)
71
- end
100
+ Invoker::Logger.puts("Removing #{command_label} with signal #{signal_to_use}".red)
101
+ kill_or_remove_process(worker.pid, signal_to_use, command_label)
72
102
  end
73
103
 
104
+ # Given a file descriptor returns the worker object
105
+ #
106
+ # @param fd [IO] an IO object with valid file descriptor
107
+ # @return [Invoker::CommandWorker] The worker object which is associated with this fd
74
108
  def get_worker_from_fd(fd)
75
109
  open_pipes[fd.fileno]
76
110
  end
77
111
 
112
+ # Given a command label returns the associated worker object
113
+ #
114
+ # @param label [String] Command label of the command
115
+ # @return [Invoker::CommandWorker] The worker object which is associated with this command
78
116
  def get_worker_from_label(label)
79
117
  workers[label]
80
118
  end
119
+
120
+ def on_next_tick(*args, &block)
121
+ @worker_mutex.synchronize do
122
+ @runnables << OpenStruct.new(:args => args, :block => block)
123
+ end
124
+ end
125
+
126
+ def run_runnables
127
+ @runnables.each do |runnable|
128
+ instance_exec(*runnable.args, &runnable.block)
129
+ end
130
+ @runnables = []
131
+ end
81
132
 
82
133
  private
134
+ def start_event_loop
135
+ loop do
136
+ reactor.watch_on_pipe()
137
+ run_runnables()
138
+ run_scheduled_events()
139
+ end
140
+ end
141
+
142
+ def run_scheduled_events
143
+ event_manager.run_scheduled_events do |event|
144
+ event.block.call()
145
+ end
146
+ end
147
+
148
+ def kill_or_remove_process(pid, signal_to_use, command_label)
149
+ process_kill(pid, signal_to_use)
150
+ true
151
+ rescue Errno::ESRCH
152
+ remove_worker(command_label, false)
153
+ false
154
+ end
155
+
83
156
  def process_kill(pid, signal_to_use)
84
157
  if signal_to_use.to_i == 0
85
158
  Process.kill(signal_to_use, pid)
@@ -87,29 +160,37 @@ module Invoker
87
160
  Process.kill(signal_to_use.to_i, pid)
88
161
  end
89
162
  end
163
+
164
+ def select_color
165
+ selected_color = LABEL_COLORS.shift()
166
+ LABEL_COLORS.push(selected_color)
167
+ selected_color
168
+ end
90
169
 
91
170
  # Remove worker from all collections
92
- def remove_worker(command_label)
93
- @worker_mutex.synchronize do
94
- worker = @workers[command_label]
95
- if worker
96
- @open_pipes.delete(worker.pipe_end.fileno)
97
- @reactor.remove_from_monitoring(worker.pipe_end)
98
- @workers.delete(command_label)
99
- end
171
+ def remove_worker(command_label, trigger_event = true)
172
+ worker = @workers[command_label]
173
+ if worker
174
+ @open_pipes.delete(worker.pipe_end.fileno)
175
+ @workers.delete(command_label)
176
+ end
177
+ if trigger_event
178
+ event_manager.trigger(command_label, :worker_removed)
100
179
  end
101
180
  end
102
181
 
103
182
  # add worker to global collections
104
183
  def add_worker(worker)
105
- @worker_mutex.synchronize do
106
- @open_pipes[worker.pipe_end.fileno] = worker
107
- @workers[worker.command_label] = worker
108
- @reactor.add_to_monitor(worker.pipe_end)
109
- end
184
+ @open_pipes[worker.pipe_end.fileno] = worker
185
+ @workers[worker.command_label] = worker
186
+ @reactor.add_to_monitor(worker.pipe_end)
110
187
  end
111
188
 
112
189
  def run_command(process_info, write_pipe)
190
+ command_label = process_info.label
191
+
192
+ event_manager.schedule_event(command_label, :exit) { remove_worker(command_label) }
193
+
113
194
  if defined?(Bundler)
114
195
  Bundler.with_clean_env do
115
196
  spawn(process_info.cmd,
@@ -125,12 +206,13 @@ module Invoker
125
206
 
126
207
  def wait_on_pid(command_label,pid)
127
208
  raise Invoker::Errors::ToomanyOpenConnections if @thread_group.enclosed?
209
+
128
210
  thread = Thread.new do
129
211
  Process.wait(pid)
130
212
  message = "Process with command #{command_label} exited with status #{$?.exitstatus}"
131
- $stdout.puts("\n#{message}".red)
213
+ Invoker::Logger.puts("\n#{message}".red)
132
214
  notify_user(message)
133
- remove_worker(command_label)
215
+ event_manager.trigger(command_label, :exit)
134
216
  end
135
217
  @thread_group.add(thread)
136
218
  end
@@ -0,0 +1,79 @@
1
+ module Invoker
2
+ module Event
3
+ class Manager
4
+ attr_accessor :scheduled_events, :triggered_events
5
+
6
+ def initialize
7
+ @scheduled_events = Hash.new {|h,k| h[k] = [] }
8
+ @triggered_events = []
9
+ @trigger_mutex = Mutex.new()
10
+ end
11
+
12
+ # Trigger an event. The event is not triggered immediately, but is just scheduled to be
13
+ # triggered.
14
+ #
15
+ # @param command_label [String] Command for which event should be triggered
16
+ # @param event_name [Symbol, nil] The optional event name
17
+ def trigger(command_label, event_name = nil)
18
+ @trigger_mutex.synchronize do
19
+ triggered_events << OpenStruct.new(
20
+ :command_label => command_label,
21
+ :event_name => event_name)
22
+ end
23
+ end
24
+
25
+ # Schedule an Event. The event will only trigger when a scheduled event matches
26
+ # a triggered event.
27
+ #
28
+ # @param command_label [String] Command for which the event should be triggered
29
+ # @param event_name [String, nil] Optional event name
30
+ # @param block The block to execute when event actually triggers
31
+ def schedule_event(command_label, event_name = nil, &block)
32
+ @trigger_mutex.synchronize do
33
+ scheduled_events[command_label] << OpenStruct.new(:event_name => event_name, :block => block)
34
+ end
35
+ end
36
+
37
+ # On next iteration of event loop, this method is called and we try to match
38
+ # scheduled events with events that were triggered.
39
+ def run_scheduled_events
40
+ filtered_events_by_name_and_command = []
41
+
42
+ triggered_events.each_with_index do |triggered_event, index|
43
+ matched_events = scheduled_events[triggered_event.command_label]
44
+ if matched_events && !matched_events.empty?
45
+ filtered_events_by_name_and_command, unmatched_events =
46
+ filter_matched_events(matched_events, triggered_event)
47
+ triggered_events[index] = nil
48
+ remove_scheduled_event(unmatched_events, triggered_event.command_label)
49
+ end
50
+ end
51
+ triggered_events.compact!
52
+
53
+ filtered_events_by_name_and_command.each {|event| yield event }
54
+ end
55
+
56
+ private
57
+ def filter_matched_events(matched_events, event)
58
+ matched_filtered_events = []
59
+
60
+ matched_events.each_with_index do |matched_event, index|
61
+ if !event.event_name || event.event_name == matched_event.event_name
62
+ matched_filtered_events << matched_event
63
+ matched_events[index] = nil
64
+ end
65
+ end
66
+ [matched_filtered_events, matched_events.compact]
67
+ end
68
+
69
+ def remove_scheduled_event(matched_events, command_label)
70
+ if !matched_events || matched_events.empty?
71
+ scheduled_events.delete(command_label)
72
+ else
73
+ scheduled_events[command_label] = matched_events
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,13 @@
1
+ module Invoker
2
+ class Logger
3
+ def self.puts(message)
4
+ return if ENV["INVOKER_TESTS"]
5
+ $stdout.puts(message)
6
+ end
7
+
8
+ def self.print(message)
9
+ return if ENV["INVOKER_TESTS"]
10
+ $stdout.print(message)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ require 'iniparse'
2
+
3
+ module Invoker
4
+ module Parsers
5
+ class Config
6
+ attr_accessor :processes
7
+ def initialize(filename)
8
+ @ini_content = File.read(filename)
9
+ @processes = process_ini(@ini_content)
10
+ end
11
+
12
+ private
13
+ def process_ini(ini_content)
14
+ document = IniParse.parse(ini_content)
15
+ document.map do |section|
16
+ check_directory(section["directory"])
17
+ OpenStruct.new(label: section.key, dir: section["directory"], cmd: section["command"])
18
+ end
19
+ end
20
+
21
+ def check_directory(app_dir)
22
+ if app_dir && !app_dir.empty? && !File.directory?(app_dir)
23
+ raise Invoker::Errors::InvalidConfig.new("Invalid directory #{app_dir}")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,86 @@
1
+ require "slop"
2
+ require "ostruct"
3
+
4
+ module Invoker
5
+ module Parsers
6
+ class OptionParser
7
+ def self.parse(args)
8
+ selected_command = nil
9
+
10
+ opts = Slop.parse(args, help: true) do
11
+ on :v, "Print the version" do
12
+ Invoker::Logger.puts Invoker::VERSION
13
+ end
14
+
15
+ command 'start' do
16
+ banner "Usage : invoker start config.ini \n Start Invoker Process Manager"
17
+ run do |cmd_opts, cmd_args|
18
+ selected_command = OpenStruct.new(:command => 'start', :file => cmd_args.first)
19
+ end
20
+ end
21
+
22
+ command 'list' do
23
+ banner "Usage : invoker list\n List processes managed by invoker"
24
+ run do |cmd_opts, cmd_args|
25
+ selected_command = OpenStruct.new(:command => 'list')
26
+ end
27
+ end
28
+
29
+ command 'add' do
30
+ banner "Usage : invoker add process_label \n Start the process with given process_label"
31
+ run do |cmd_opts, cmd_args|
32
+ selected_command = OpenStruct.new(:command => 'add', :command_key => cmd_args.first)
33
+ end
34
+ end
35
+
36
+ command 'remove' do
37
+ banner "Usage : invoker remove process_label \n Stop the process with given label"
38
+ on :s, :signal=, "Signal to send for killing the process, default is SIGINT", as: String
39
+
40
+ run do |cmd_opts, cmd_args|
41
+ signal_to_use = cmd_opts.to_hash[:signal] || 'INT'
42
+ selected_command = OpenStruct.new(
43
+ :command => 'remove',
44
+ :command_key => cmd_args.first,
45
+ :signal => signal_to_use
46
+ )
47
+ end
48
+ end
49
+
50
+ command 'reload' do
51
+ banner "Usage : invoker reload process_label \n Restart the process with given label"
52
+ on :s, :signal=, "Signal to send for killing the process, default is SIGINT", as: String
53
+
54
+ run do |cmd_opts, cmd_args|
55
+ signal_to_use = cmd_opts.to_hash[:signal] || 'INT'
56
+ selected_command = OpenStruct.new(
57
+ :command => 'reload',
58
+ :command_key => cmd_args.first,
59
+ :signal => signal_to_use
60
+ )
61
+ end
62
+ end
63
+ end
64
+
65
+ selected_command || create_default_command(args, opts)
66
+ end
67
+
68
+
69
+ # If user specifies no command either show help message or start the invoker
70
+ # process supervisor.
71
+ #
72
+ # @param args [Array] command line arguments
73
+ # @param opts [Slop::Options] Processed slop options
74
+ # @return [OpenStruct, false] returns default command or nil
75
+ def self.create_default_command(args, opts)
76
+ if args.first && File.exists?(args.first) && File.file?(args.first)
77
+ OpenStruct.new(:command => "start", :file => args.first)
78
+ else
79
+ Invoker::Logger.puts opts.inspect
80
+ false
81
+ end
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,53 @@
1
+ module Invoker
2
+ class ProcessPrinter
3
+ MAX_COLUMN_WIDTH = 40
4
+ def self.to_json(workers)
5
+ final_json = []
6
+ Invoker::CONFIG.processes.each do |process|
7
+ if worker = workers[process.label]
8
+ final_json << {
9
+ :command => process.cmd, :command_label => process.label,
10
+ :dir => process.dir, :pid => worker.pid
11
+ }
12
+ else
13
+ final_json << {
14
+ :command => process.cmd, :command_label => process.label,
15
+ :dir => process.dir
16
+ }
17
+ end
18
+ end
19
+
20
+ final_json.to_json
21
+ end
22
+
23
+ def self.print_table(json_data)
24
+ final_json = JSON.parse(json_data)
25
+ if final_json && !final_json.empty?
26
+ json_for_printing = []
27
+ final_json.each do |json_row|
28
+ if json_row["pid"]
29
+ json_for_printing << colorize_hash(json_row, "green")
30
+ else
31
+ json_row["pid"] = "[light_black]not running[/]"
32
+ json_for_printing << colorize_hash(json_row, "light_black")
33
+ end
34
+ end
35
+ Formatador.display_compact_table(json_for_printing)
36
+ end
37
+ end
38
+
39
+ private
40
+ def self.colorize_hash(hash, color)
41
+ hash.inject({}) do |mem,(key,obj)|
42
+ if obj.to_s.length > MAX_COLUMN_WIDTH
43
+ short_command = "#{obj.to_s[0..MAX_COLUMN_WIDTH]}.."
44
+ mem[key] = "[#{color}]#{short_command}[/]"
45
+ else
46
+ mem[key] = "[#{color}]#{obj}[/]"
47
+ end
48
+ mem
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -13,12 +13,6 @@ module Invoker
13
13
  @monitored_fds.delete(fd)
14
14
  end
15
15
 
16
- def start
17
- loop do
18
- watch_on_pipe
19
- end
20
- end
21
-
22
16
  def watch_on_pipe
23
17
  ready_read_fds,ready_write_fds,read_error_fds = select(monitored_fds,[],[],0.05)
24
18
 
@@ -38,6 +32,7 @@ module Invoker
38
32
  data = read_data(ready_fd)
39
33
  command_worker.receive_data(data)
40
34
  rescue Invoker::Errors::ProcessTerminated
35
+ remove_from_monitoring(command_worker.pipe_end)
41
36
  command_worker.unbind()
42
37
  end
43
38
  end
@@ -5,45 +5,8 @@ require "socket"
5
5
  module Invoker
6
6
  class Runner
7
7
  def self.run(args)
8
-
9
- selected_command = nil
10
-
11
- opts = Slop.parse(args, help: true) do
12
- on :v, "Print the version" do
13
- $stdout.puts Invoker::VERSION
14
- end
15
-
16
- command 'start' do
17
- banner "Usage : invoker start config.ini \n Start Invoker Process Manager"
18
- run do |cmd_opts, cmd_args|
19
- selected_command = OpenStruct.new(:command => 'start', :file => cmd_args.first)
20
- end
21
- end
22
-
23
- command 'add' do
24
- banner "Usage : invoker add process_label \n Start the process with given process_label"
25
- run do |cmd_opts, cmd_args|
26
- selected_command = OpenStruct.new(:command => 'add', :command_key => cmd_args.first)
27
- end
28
- end
29
-
30
- command 'remove' do
31
- banner "Usage : invoker remove process_label \n Stop the process with given label"
32
- on :s, :signal=, "Signal to send for killing the process, default is SIGINT", as: String
33
-
34
- run do |cmd_opts, cmd_args|
35
- signal_to_use = cmd_opts.to_hash[:signal] || 'INT'
36
- selected_command = OpenStruct.new(
37
- :command => 'remove',
38
- :command_key => cmd_args.first,
39
- :signal => signal_to_use
40
- )
41
- end
42
- end
43
- end
44
- unless selected_command
45
- $stdout.puts opts.inspect
46
- else
8
+ selected_command = Invoker::Parsers::OptionParser.parse(args)
9
+ if selected_command
47
10
  run_command(selected_command)
48
11
  end
49
12
  end
@@ -55,15 +18,19 @@ module Invoker
55
18
  start_server(selected_command)
56
19
  when 'add'
57
20
  add_command(selected_command)
21
+ when 'reload'
22
+ refresh_command(selected_command)
23
+ when 'list'
24
+ list_commands(selected_command)
58
25
  when 'remove'
59
26
  remove_command(selected_command)
60
27
  else
61
- $stdout.puts "Invalid command"
28
+ Invoker::Logger.puts "Invalid command"
62
29
  end
63
30
  end
64
31
 
65
32
  def self.start_server(selected_command)
66
- config = Invoker::Config.new(selected_command.file)
33
+ config = Invoker::Parsers::Config.new(selected_command.file)
67
34
  Invoker.const_set(:CONFIG, config)
68
35
  warn_about_terminal_notifier()
69
36
  commander = Invoker::Commander.new()
@@ -72,31 +39,39 @@ module Invoker
72
39
  end
73
40
 
74
41
  def self.add_command(selected_command)
75
- socket = UNIXSocket.open(Invoker::CommandListener::Server::SOCKET_PATH)
76
- socket.puts("add #{selected_command.command_key}")
77
- socket.flush()
78
- socket.close()
42
+ Socket.unix(Invoker::CommandListener::Server::SOCKET_PATH) do |socket|
43
+ socket.puts("add #{selected_command.command_key}")
44
+ socket.flush()
45
+ end
79
46
  end
80
47
 
81
48
  def self.remove_command(selected_command)
82
- socket = UNIXSocket.open(Invoker::CommandListener::Server::SOCKET_PATH)
83
- socket.puts("remove #{selected_command.command_key} #{selected_command.signal}")
84
- socket.flush()
85
- socket.close()
49
+ Socket.unix(Invoker::CommandListener::Server::SOCKET_PATH) do |socket|
50
+ socket.puts("remove #{selected_command.command_key} #{selected_command.signal}")
51
+ socket.flush()
52
+ end
86
53
  end
87
54
 
88
55
  def self.refresh_command(selected_command)
89
- socket = UNIXSocket.open(Invoker::CommandListener::Server::SOCKET_PATH)
90
- socket.puts("reload #{selected_command.command_key}")
91
- socket.flush()
92
- socket.close()
56
+ Socket.unix(Invoker::CommandListener::Server::SOCKET_PATH) do |socket|
57
+ socket.puts("reload #{selected_command.command_key} #{selected_command.signal}")
58
+ socket.flush()
59
+ end
60
+ end
61
+
62
+ def self.list_commands(selected_command)
63
+ Socket.unix(Invoker::CommandListener::Server::SOCKET_PATH) {|sock|
64
+ sock.puts("list")
65
+ data = sock.gets()
66
+ Invoker::ProcessPrinter.print_table(data)
67
+ }
93
68
  end
94
69
 
95
70
  def self.warn_about_terminal_notifier
96
71
  if RUBY_PLATFORM.downcase.include?("darwin")
97
72
  command_path = `which terminal-notifier`
98
73
  if !command_path || command_path.empty?
99
- $stdout.puts("You can enable OSX notification for processes by installing terminal-notification gem".red)
74
+ Invoker::Logger.puts("You can enable OSX notification for processes by installing terminal-notifier gem".red)
100
75
  end
101
76
  end
102
77
  end
@@ -1,3 +1,3 @@
1
1
  module Invoker
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.1.pre"
3
3
  end
data/lib/invoker.rb CHANGED
@@ -1,16 +1,18 @@
1
1
  $: << File.dirname(__FILE__) unless $:.include?(File.expand_path(File.dirname(__FILE__)))
2
2
 
3
3
  require "colored"
4
- require_relative "invoker/version"
5
- require_relative "invoker/runner"
6
- require_relative "invoker/command_listener"
7
- require_relative "invoker/errors"
8
- require_relative "invoker/config"
9
- require_relative "invoker/commander"
10
- require_relative "invoker/command_worker"
11
- require_relative "invoker/reactor"
12
-
13
-
14
-
15
-
16
-
4
+ require "formatador"
5
+ require "ostruct"
6
+ require "invoker/version"
7
+ require "invoker/logger"
8
+ require "invoker/runner"
9
+ require "invoker/command_listener/server"
10
+ require "invoker/command_listener/client"
11
+ require "invoker/errors"
12
+ require "invoker/parsers/config"
13
+ require "invoker/parsers/option_parser"
14
+ require "invoker/commander"
15
+ require "invoker/command_worker"
16
+ require "invoker/reactor"
17
+ require "invoker/event/manager"
18
+ require "invoker/process_printer"
data/readme.md CHANGED
@@ -22,7 +22,6 @@ You need to start by creating a `ini` file which will define processes you want
22
22
  directory = /home/gnufied/god_particle
23
23
  command = zsh -c 'bundle exec ruby script/delayed_job'
24
24
 
25
-
26
25
  [events]
27
26
  directory = /home/gnufied/god_particle
28
27
  command = zsh -c 'bundle exec ruby script/event_server'
@@ -44,7 +43,16 @@ Now additionally you can control individual process by,
44
43
  # add and start running
45
44
  ~> invoker add dj
46
45
 
47
- You can also enable OSX notifications for crashed processes by installing `terminal-notification` gem. It is not a dependency, but can be useful if something crashed and you weren't paying attention.
46
+ # List currently running processes managed by invoker
47
+ ~> invoker list
48
+
49
+ # Restart process given by command Label
50
+ ~> invoker reload dj
51
+
52
+ # Restart process given by command label using specific signal for killing
53
+ ~> invoker reload dj -s 9
54
+
55
+ You can also enable OSX notifications for crashed processes by installing `terminal-notifier` gem. It is not a dependency, but can be useful if something crashed and you weren't paying attention.
48
56
 
49
57
  ## Using with rbenv or rvm ##
50
58
 
@@ -58,7 +66,7 @@ If you are running `invoker` with Ruby version x, but your application requires
58
66
  * Unless version of Ruby using which you are running `invoker` command and version of Ruby you are using in the application is same, you almost always will want to use
59
67
  `zsh -c` or `bash -c`. `RVM` in particular requires a login shell and hence sometimes you may have to use `bash -lc`. For example:
60
68
 
61
- command = bash -lc "rvm use 2.0.0-p0 && bundle exec rails s"
69
+ command = bash -lc "rvm 2.0.0-p0 do bundle exec rails s"
62
70
 
63
71
 
64
72
  ## Bug reports and Feature requests
@@ -8,9 +8,8 @@ describe Invoker::CommandListener::Client do
8
8
  end
9
9
 
10
10
  it "should run if read from socket" do
11
- invoker_commander.expects(:add_command_by_label).with("foo")
12
- @client_socket.expects(:read).returns("add foo\n")
13
- @client_socket.expects(:close)
11
+ invoker_commander.expects(:on_next_tick).with("foo")
12
+ @client_socket.expects(:gets).returns("add foo\n")
14
13
 
15
14
  @client.read_and_execute()
16
15
  end
@@ -23,17 +22,15 @@ describe Invoker::CommandListener::Client do
23
22
  end
24
23
 
25
24
  it "with specific signal" do
26
- invoker_commander.expects(:remove_command).with("foo", "9")
27
- @client_socket.expects(:read).returns("remove foo 9\n")
28
- @client_socket.expects(:close)
25
+ invoker_commander.expects(:on_next_tick).with("foo", "9")
26
+ @client_socket.expects(:gets).returns("remove foo 9\n")
29
27
 
30
28
  @client.read_and_execute()
31
29
  end
32
30
 
33
31
  it "with default signal" do
34
- invoker_commander.expects(:remove_command).with("foo",nil)
35
- @client_socket.expects(:read).returns("remove foo\n")
36
- @client_socket.expects(:close)
32
+ invoker_commander.expects(:on_next_tick).with("foo",nil)
33
+ @client_socket.expects(:gets).returns("remove foo\n")
37
34
 
38
35
  @client.read_and_execute()
39
36
  end
@@ -46,10 +43,8 @@ describe Invoker::CommandListener::Client do
46
43
  end
47
44
 
48
45
  it "should print error if read from socket" do
49
- invoker_commander.expects(:remove_command).never()
50
- invoker_commander.expects(:add_command_by_label).never()
51
- @client_socket.expects(:read).returns("eugh foo\n")
52
- @client_socket.expects(:close)
46
+ invoker_commander.expects(:on_next_tick).never()
47
+ @client_socket.expects(:gets).returns("eugh foo\n")
53
48
 
54
49
  @client.read_and_execute
55
50
  end
@@ -1,4 +1,15 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "Command Worker" do
4
+ describe "converting workers hash to json" do
5
+ before do
6
+ @workers = {}
7
+ @workers["foo"] = Invoker::CommandWorker.new("foo", 89, 1023, "red")
8
+ @workers["bar"] = Invoker::CommandWorker.new("bar", 99, 1024, "blue")
9
+ end
10
+
11
+ it "should print json" do
12
+ @workers.values.map {|worker| worker.to_h }.to_json.should.not.be.empty
13
+ end
14
+ end
4
15
  end
@@ -39,16 +39,27 @@ describe "Invoker::Commander" do
39
39
  describe "if a signal is specified" do
40
40
  it "should use that signal to kill the worker" do
41
41
  @commander.expects(:process_kill).with("bogus", "HUP").returns(true)
42
- @commander.remove_command("resque", "HUP")
42
+ @commander.remove_command("resque", "HUP").should.be.true
43
43
  end
44
44
  end
45
45
 
46
46
  describe "if no signal is specified" do
47
47
  it "should use INT signal" do
48
48
  @commander.expects(:process_kill).with("bogus", "INT").returns(true)
49
- @commander.remove_command("resque", nil)
49
+ @commander.remove_command("resque", nil).should.be.true
50
50
  end
51
51
  end
52
+
53
+ # describe "when a worker is not found" do
54
+ # before do
55
+ # @commander = Invoker::Commander.new()
56
+ # @commander.workers.expects(:[]).returns(OpenStruct.new(:pid => "bogus"))
57
+ # end
58
+
59
+ # it "should return false" do
60
+
61
+ # end
62
+ # end
52
63
  end
53
64
 
54
65
  describe "when no worker is found" do
@@ -69,10 +80,15 @@ describe "Invoker::Commander" do
69
80
  before do
70
81
  invoker_config.stubs(:processes).returns([OpenStruct.new(:label => "sleep", :cmd => "sleep 4", :dir => ENV['HOME'])])
71
82
  @commander = Invoker::Commander.new()
83
+ Invoker.const_set(:COMMANDER, @commander)
84
+ end
85
+
86
+ after do
87
+ Invoker.send(:remove_const,:COMMANDER)
72
88
  end
73
89
 
74
90
  it "should populate workers and open_pipes" do
75
- @commander.reactor.expects(:start).returns(true)
91
+ @commander.expects(:start_event_loop)
76
92
  @commander.start_manager()
77
93
  @commander.open_pipes.should.not.be.empty
78
94
  @commander.workers.should.not.be.empty
@@ -89,4 +105,30 @@ describe "Invoker::Commander" do
89
105
  end
90
106
  end
91
107
 
108
+ describe "#runnables" do
109
+ before do
110
+ @commander = Invoker::Commander.new()
111
+ end
112
+
113
+ it "should run runnables in reactor tick with one argument" do
114
+ @commander.on_next_tick("foo") { |cmd| add_command_by_label(cmd) }
115
+ @commander.expects(:add_command_by_label).returns(true)
116
+ @commander.run_runnables()
117
+ end
118
+
119
+ it "should run runnables with multiple args" do
120
+ @commander.on_next_tick("foo", "bar", "baz") { |t1,*rest|
121
+ remove_command(t1, rest)
122
+ }
123
+ @commander.expects(:remove_command).with("foo", ["bar", "baz"]).returns(true)
124
+ @commander.run_runnables()
125
+ end
126
+
127
+ it "should run runnable with no args" do
128
+ @commander.on_next_tick() { hello() }
129
+ @commander.expects(:hello).returns(true)
130
+ @commander.run_runnables()
131
+ end
132
+ end
133
+
92
134
  end
@@ -16,7 +16,7 @@ command = ruby try_sleep.rb
16
16
  file.write(config_data)
17
17
  file.close
18
18
  lambda {
19
- Invoker::Config.new(file.path)
19
+ Invoker::Parsers::Config.new(file.path)
20
20
  }.should.raise(Invoker::Errors::InvalidConfig)
21
21
  ensure
22
22
  file.unlink()
@@ -0,0 +1,72 @@
1
+ require "spec_helper"
2
+
3
+ describe Invoker::Event::Manager do
4
+ describe "Run scheduled events" do
5
+ before do
6
+ @event_manager = Invoker::Event::Manager.new()
7
+ end
8
+
9
+ it "should run matched events" do
10
+ @event_manager.schedule_event("foo", :exit) { 'exit foo' }
11
+ @event_manager.trigger("foo", :exit)
12
+
13
+ @event_manager.run_scheduled_events do |event|
14
+ event.block.call.should.equal("exit foo")
15
+ end
16
+
17
+ @event_manager.scheduled_events.should.be.empty
18
+ @event_manager.triggered_events.should.be.empty
19
+ end
20
+
21
+ it "should remove triggrered and scheduld events on run" do
22
+ @event_manager.schedule_event("foo", :exit) { 'exit foo' }
23
+ @event_manager.schedule_event("bar", :entry) { "entry bar"}
24
+ @event_manager.trigger("foo", :exit)
25
+ @event_manager.trigger("baz", :exit)
26
+
27
+ @event_manager.run_scheduled_events do |event|
28
+ event.block.call.should.equal("exit foo")
29
+ end
30
+
31
+ @event_manager.scheduled_events.should.not.be.empty
32
+ @event_manager.triggered_events.should.not.be.empty
33
+
34
+ baz_containing_event = lambda do |events|
35
+ events.detect {|event| event.command_label == "baz" }
36
+ end
37
+
38
+ bar_containing_scheduled_event = lambda do |events|
39
+ events.keys.detect {|event_key| event_key == "bar" }
40
+ end
41
+
42
+ @event_manager.triggered_events.should.be.a baz_containing_event
43
+ @event_manager.scheduled_events.should.be.a bar_containing_scheduled_event
44
+ end
45
+
46
+ it "should handle multiple events for same command" do
47
+ @event_manager.schedule_event("foo", :exit) { 'exit foo' }
48
+ @event_manager.schedule_event("foo", :entry) { "entry bar"}
49
+ @event_manager.trigger("foo", :exit)
50
+
51
+ @event_manager.run_scheduled_events { |event| }
52
+
53
+
54
+ @event_manager.schedule_event("foo", :exit) { 'exit foo' }
55
+ @event_manager.trigger("foo", :exit)
56
+
57
+ @event_manager.scheduled_events.should.not.be.empty
58
+ @event_manager.triggered_events.should.not.be.empty
59
+ end
60
+
61
+ it "should not run unmatched events" do
62
+ @event_manager.schedule_event("bar", :entry) { "entry bar"}
63
+ @event_manager.trigger("foo", :exit)
64
+
65
+ events_ran = false
66
+ @event_manager.run_scheduled_events do |event|
67
+ events_ran = true
68
+ end
69
+ events_ran.should.be.false
70
+ end
71
+ end
72
+ end
data/spec/spec_helper.rb CHANGED
@@ -7,6 +7,7 @@ $: << __LIB_PATH__
7
7
  require "pry"
8
8
  require "invoker"
9
9
 
10
+ ENV["INVOKER_TESTS"] = "true"
10
11
 
11
12
  def invoker_config
12
13
  if Invoker.const_defined?(:CONFIG)
metadata CHANGED
@@ -1,7 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: invoker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.1.pre
5
+ prerelease: 6
5
6
  platform: ruby
6
7
  authors:
7
8
  - Hemant Kumar
@@ -11,99 +12,129 @@ cert_chain: []
11
12
  date: 2013-05-04 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
- version_requirements: !ruby/object:Gem::Requirement
15
+ name: slop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
15
18
  requirements:
16
19
  - - ! '>='
17
20
  - !ruby/object:Gem::Version
18
21
  version: '0'
19
22
  type: :runtime
20
23
  prerelease: false
21
- name: slop
22
- requirement: !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
27
  - - ! '>='
25
28
  - !ruby/object:Gem::Version
26
29
  version: '0'
27
30
  - !ruby/object:Gem::Dependency
28
- version_requirements: !ruby/object:Gem::Requirement
31
+ name: iniparse
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
29
34
  requirements:
30
35
  - - ! '>='
31
36
  - !ruby/object:Gem::Version
32
37
  version: '0'
33
38
  type: :runtime
34
39
  prerelease: false
35
- name: iniparse
36
- requirement: !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
43
  - - ! '>='
39
44
  - !ruby/object:Gem::Version
40
45
  version: '0'
41
46
  - !ruby/object:Gem::Dependency
42
- version_requirements: !ruby/object:Gem::Requirement
47
+ name: colored
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
43
50
  requirements:
44
51
  - - ! '>='
45
52
  - !ruby/object:Gem::Version
46
53
  version: '0'
47
54
  type: :runtime
48
55
  prerelease: false
49
- name: colored
50
- requirement: !ruby/object:Gem::Requirement
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
59
  - - ! '>='
53
60
  - !ruby/object:Gem::Version
54
61
  version: '0'
55
62
  - !ruby/object:Gem::Dependency
56
- version_requirements: !ruby/object:Gem::Requirement
63
+ name: formatador
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
57
66
  requirements:
58
67
  - - ! '>='
59
68
  - !ruby/object:Gem::Version
60
69
  version: '0'
61
- type: :development
70
+ type: :runtime
62
71
  prerelease: false
63
- name: bacon
64
- requirement: !ruby/object:Gem::Requirement
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
65
74
  requirements:
66
75
  - - ! '>='
67
76
  - !ruby/object:Gem::Version
68
77
  version: '0'
69
78
  - !ruby/object:Gem::Dependency
70
- version_requirements: !ruby/object:Gem::Requirement
79
+ name: bacon
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
71
82
  requirements:
72
83
  - - ! '>='
73
84
  - !ruby/object:Gem::Version
74
85
  version: '0'
75
86
  type: :development
76
87
  prerelease: false
77
- name: mocha
78
- requirement: !ruby/object:Gem::Requirement
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
79
90
  requirements:
80
91
  - - ! '>='
81
92
  - !ruby/object:Gem::Version
82
93
  version: '0'
83
94
  - !ruby/object:Gem::Dependency
84
- version_requirements: !ruby/object:Gem::Requirement
95
+ name: mocha
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
85
98
  requirements:
86
99
  - - ! '>='
87
100
  - !ruby/object:Gem::Version
88
101
  version: '0'
89
102
  type: :development
90
103
  prerelease: false
91
- name: mocha-on-bacon
92
- requirement: !ruby/object:Gem::Requirement
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
93
106
  requirements:
94
107
  - - ! '>='
95
108
  - !ruby/object:Gem::Version
96
109
  version: '0'
97
110
  - !ruby/object:Gem::Dependency
98
- version_requirements: !ruby/object:Gem::Requirement
111
+ name: mocha-on-bacon
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
99
114
  requirements:
100
115
  - - ! '>='
101
116
  - !ruby/object:Gem::Version
102
117
  version: '0'
103
118
  type: :development
104
119
  prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
105
127
  name: rake
106
128
  requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
107
138
  requirements:
108
139
  - - ! '>='
109
140
  - !ruby/object:Gem::Version
@@ -123,13 +154,16 @@ files:
123
154
  - bin/invoker
124
155
  - invoker.gemspec
125
156
  - lib/invoker.rb
126
- - lib/invoker/command_listener.rb
127
157
  - lib/invoker/command_listener/client.rb
128
158
  - lib/invoker/command_listener/server.rb
129
159
  - lib/invoker/command_worker.rb
130
160
  - lib/invoker/commander.rb
131
- - lib/invoker/config.rb
132
161
  - lib/invoker/errors.rb
162
+ - lib/invoker/event/manager.rb
163
+ - lib/invoker/logger.rb
164
+ - lib/invoker/parsers/config.rb
165
+ - lib/invoker/parsers/option_parser.rb
166
+ - lib/invoker/process_printer.rb
133
167
  - lib/invoker/reactor.rb
134
168
  - lib/invoker/runner.rb
135
169
  - lib/invoker/version.rb
@@ -138,35 +172,37 @@ files:
138
172
  - spec/invoker/command_worker_spec.rb
139
173
  - spec/invoker/commander_spec.rb
140
174
  - spec/invoker/config_spec.rb
175
+ - spec/invoker/event/manager_spec.rb
141
176
  - spec/spec_helper.rb
142
177
  homepage: http://github.com/code-mancers/invoker
143
178
  licenses:
144
179
  - MIT
145
- metadata: {}
146
180
  post_install_message:
147
181
  rdoc_options: []
148
182
  require_paths:
149
183
  - lib
150
184
  required_ruby_version: !ruby/object:Gem::Requirement
185
+ none: false
151
186
  requirements:
152
187
  - - ! '>='
153
188
  - !ruby/object:Gem::Version
154
189
  version: '0'
155
190
  required_rubygems_version: !ruby/object:Gem::Requirement
191
+ none: false
156
192
  requirements:
157
193
  - - ! '>='
158
194
  - !ruby/object:Gem::Version
159
195
  version: '0'
160
196
  requirements: []
161
197
  rubyforge_project:
162
- rubygems_version: 2.0.3
198
+ rubygems_version: 1.8.23
163
199
  signing_key:
164
- specification_version: 4
200
+ specification_version: 3
165
201
  summary: Something small for Process management
166
202
  test_files:
167
203
  - spec/invoker/command_listener/client_spec.rb
168
204
  - spec/invoker/command_worker_spec.rb
169
205
  - spec/invoker/commander_spec.rb
170
206
  - spec/invoker/config_spec.rb
207
+ - spec/invoker/event/manager_spec.rb
171
208
  - spec/spec_helper.rb
172
- has_rdoc:
checksums.yaml DELETED
@@ -1,15 +0,0 @@
1
- ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MGY0NmQ0YjY1MzZiOWNiYjJkNzE3NjI1YTQxOTI3YTEwMGQxM2FhZQ==
5
- data.tar.gz: !binary |-
6
- NGNiZDNlMTgxZjliOWVmMTdlNjI0YmFkNjUyODcwNjViNGQ5MTJlMA==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- MGQxYzdhY2MxYWRhMjExM2NlNDdiYTM0OTc1YjAxZmIyYTFjMjM2YWJiMmUx
10
- NTE3OWFiMWNiZDEwOGE0NTA2ZmUyOGI1NGYwYTAxYzYwNjM2ZTQzMzU2ODkw
11
- Yjc5MGQxZDg4ZmQ2MTRlYWJjMTE3ZDkwODIzODYxOTkyYjM2ZmE=
12
- data.tar.gz: !binary |-
13
- NmIxY2Q3NzkzM2Y0OTYxMTk4ZmJmZTIxMmI2NGRjZTkyZjdkOGU2NGZiZjhh
14
- YmNlZDZmODdmYjE4OWIxMzY5ZDUyNDhjODU2NmIwYTA0ZjQ5YmQxNzQxZTM2
15
- MjUyMjI1MTAyYTZlZGVjZjRkYmEwMjRjOTgwNWE4ZjBmYjIwYTE=
@@ -1,3 +0,0 @@
1
- require_relative "command_listener/server"
2
- require_relative "command_listener/client"
3
-
@@ -1,28 +0,0 @@
1
- require "yaml"
2
- require 'iniparse'
3
-
4
- module Invoker
5
- class Config
6
- attr_accessor :processes
7
- def initialize(filename)
8
- @ini_content = File.read(filename)
9
- @processes = process_ini(@ini_content)
10
- end
11
-
12
- private
13
- def process_ini(ini_content)
14
- document = IniParse.parse(ini_content)
15
- document.map do |section|
16
- check_directory(section["directory"])
17
- OpenStruct.new(label: section.key, dir: section["directory"], cmd: section["command"])
18
- end
19
- end
20
-
21
- def check_directory(app_dir)
22
- if app_dir && !app_dir.empty? && !File.directory?(app_dir)
23
- raise Invoker::Errors::InvalidConfig.new("Invalid directory #{app_dir}")
24
- end
25
- end
26
-
27
- end
28
- end