qcmd 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -4,6 +4,9 @@
4
4
  QLab 3's new OSC interface. `qcmd` should be useable from any machine on
5
5
  the same local network as the QLab workspace you intend to work with.
6
6
 
7
+ This project is OS X only and has been tested against Ruby 1.8.7-p358 (OS X
8
+ 10.8 default), Ruby 1.8.7 REE 2012.02, and Ruby 1.9.3-p327.
9
+
7
10
  **This project should be considered experimental. DO NOT RUN SHOWS WITH
8
11
  IT.**
9
12
 
@@ -16,6 +19,7 @@ Install this gem to your machine by running the following command:
16
19
 
17
20
  That should do ya.
18
21
 
22
+
19
23
  ## Starting the `qcmd` console.
20
24
 
21
25
  Run the following command in a terminal window:
@@ -122,6 +126,7 @@ An example session might look like this:
122
126
  > exit
123
127
  exiting...
124
128
 
129
+
125
130
  ## Contributing
126
131
 
127
132
  1. Fork it
data/lib/qcmd.rb CHANGED
@@ -1,11 +1,17 @@
1
+ require 'socket'
2
+ require 'osc-ruby'
3
+
1
4
  require 'qcmd/version'
5
+
6
+ require 'qcmd/plaintext'
7
+ require 'qcmd/commands'
2
8
  require 'qcmd/input_completer'
3
9
 
4
10
  require 'qcmd/core_ext/array'
5
11
  require 'qcmd/core_ext/osc/message'
12
+ require 'qcmd/core_ext/osc/stopping_server'
6
13
 
7
14
  module Qcmd
8
- # Your code goes here...
9
15
  autoload :Handler, 'qcmd/handler'
10
16
  autoload :Server, 'qcmd/server'
11
17
  autoload :Context, 'qcmd/context'
@@ -14,8 +20,6 @@ module Qcmd
14
20
  autoload :Machine, 'qcmd/machine'
15
21
  autoload :Network, 'qcmd/network'
16
22
  autoload :QLab, 'qcmd/qlab'
17
- autoload :Plaintext, 'qcmd/plaintext'
18
- autoload :Commands, 'qcmd/commands'
19
23
  autoload :VERSION, 'qcmd/version'
20
24
 
21
25
  class << self
@@ -33,13 +37,21 @@ module Qcmd
33
37
  self.log_level = :warning
34
38
  end
35
39
 
40
+ def silent!
41
+ self.log_level = :none
42
+ end
43
+
44
+ def silent?
45
+ self.log_level == :none
46
+ end
47
+
36
48
  def quiet?
37
49
  self.log_level == :warning
38
50
  end
39
51
 
40
52
  def while_quiet
41
53
  previous_level = self.log_level
42
- self.log_level = :warning unless previous_level == :debug
54
+ self.log_level = :warning
43
55
  yield
44
56
  self.log_level = previous_level
45
57
  end
data/lib/qcmd/cli.rb CHANGED
@@ -3,7 +3,6 @@ require 'qcmd/server'
3
3
  require 'readline'
4
4
 
5
5
  require 'osc-ruby'
6
- require 'osc-ruby/em_server'
7
6
 
8
7
  module Qcmd
9
8
  class CLI
@@ -36,7 +35,7 @@ module Qcmd
36
35
 
37
36
  if options[:command_given]
38
37
  handle_message options[:command]
39
- puts %[sent command "#{ options[:command] }"]
38
+ print %[sent command "#{ options[:command] }"]
40
39
  exit 0
41
40
  end
42
41
  end
@@ -146,7 +145,22 @@ module Qcmd
146
145
 
147
146
  Qcmd.debug "(using workspace: #{ workspace_name.inspect })"
148
147
 
149
- connect_to_workspace_by_name workspace_name, passcode
148
+ if workspace_name
149
+ connect_to_workspace_by_name workspace_name, passcode
150
+ else
151
+ print "No workspace name given. The following workspaces are available:"
152
+ Qcmd.context.print_workspace_list
153
+ end
154
+
155
+ when 'help'
156
+ help_command = args.shift
157
+
158
+ if help_command.nil?
159
+ Qcmd::Commands::Help.print_all_commands
160
+ # print help according to current context
161
+ else
162
+ # print command specific help
163
+ end
150
164
 
151
165
  when 'cues'
152
166
  if !Qcmd.context.workspace_connected?
@@ -179,13 +193,21 @@ module Qcmd
179
193
  print
180
194
  print " > cue NUMBER COMMAND ARGUMENTS"
181
195
  print
182
- print_wrapped("available cue commands are: #{Qcmd::InputCompleter::ReservedCueWords.join(', ')}")
196
+ print_wrapped("available cue commands are: #{Qcmd::Commands::ALL_CUE_COMMANDS.join(', ')}")
183
197
  elsif cue_action.nil?
184
198
  server.send_workspace_command("cue/#{ cue_number }")
185
199
  else
186
200
  server.send_cue_command(cue_number, cue_action, *args)
187
201
  end
188
202
 
203
+ when 'workspaces'
204
+ if !Qcmd.context.machine_connected?
205
+ print 'cannot load workspaces until you are connected to a machine'
206
+ return
207
+ end
208
+
209
+ server.load_workspaces
210
+
189
211
  when 'workspace'
190
212
  workspace_command = args.shift
191
213
 
data/lib/qcmd/commands.rb CHANGED
@@ -1,10 +1,34 @@
1
+ require 'qcmd/plaintext'
2
+
1
3
  module Qcmd
2
4
  module Commands
3
- # Commands that expect reponses
5
+ # All Commands
6
+ #
7
+ # *_RESPONSE lists are commands that expect responses
8
+ #
9
+
10
+ NO_MACHINE_RESPONSE = %w(connect)
11
+
4
12
  MACHINE_RESPONSE = %w(workspaces)
5
13
 
6
14
  WORKSPACE_RESPONSE = %w(
7
- cueLists selectedCues runningCues runningOrPausedCues connect thump
15
+ cueLists selectedCues runningCues runningOrPausedCues thump
16
+ )
17
+
18
+ WORKSPACE_NO_RESPONSE = %w(
19
+ go stop pause resume reset panic
20
+ )
21
+
22
+ ALL_WORKSPACE_COMMANDS = [WORKSPACE_RESPONSE + WORKSPACE_NO_RESPONSE]
23
+
24
+ # commands that take no args and do not respond
25
+ CUE_NO_RESPONSE = %w(
26
+ start stop pause resume load preview reset panic
27
+ )
28
+
29
+ # commands that take args but do not respond
30
+ CUE_ARG_NO_RESPONSE = %w(
31
+ loadAt
8
32
  )
9
33
 
10
34
  # commands that always expect a response
@@ -16,14 +40,29 @@ module Qcmd
16
40
  basics children
17
41
  )
18
42
 
19
- # commands that expect a response if given without args
43
+ # commands that take args but expect a response if given without args
20
44
  NO_ARG_CUE_RESPONSE = %w(
21
45
  number name notes cueTargetNumber cueTargetId preWait duration
22
46
  postWait continueMode flagged armed colorName
23
47
  sliderLevel
24
48
  )
25
49
 
50
+ # all cue commands that take arguments
51
+ CUE_ARG = CUE_ARG_NO_RESPONSE + NO_ARG_CUE_RESPONSE
52
+
53
+ ALL_CUE_COMMANDS = CUE_NO_RESPONSE +
54
+ CUE_ARG_NO_RESPONSE +
55
+ CUE_RESPONSE +
56
+ NO_ARG_CUE_RESPONSE
57
+
26
58
  class << self
59
+ def no_machine_response_matcher
60
+ @no_machine_response_matcher ||= %r[(#{NO_MACHINE_RESPONSE.join('|')})]
61
+ end
62
+ def no_machine_response_match command
63
+ !!(no_machine_response_matcher =~ command)
64
+ end
65
+
27
66
  def machine_response_matcher
28
67
  @machine_response_matcher ||= %r[(#{MACHINE_RESPONSE.join('|')})]
29
68
  end
@@ -63,7 +102,7 @@ module Qcmd
63
102
  when :none
64
103
  # shouldn't be dealing with OSC messages when unconnected to
65
104
  # machine or workspace
66
- response = false
105
+ response = no_machine_response_match(address)
67
106
  when :machine
68
107
  # could be workspace or machine command
69
108
  response = machine_response_match(address) ||
@@ -100,5 +139,85 @@ module Qcmd
100
139
  /workspace/ =~ address && !(%r[cue/] =~ address || %r[cue_id/] =~ address)
101
140
  end
102
141
  end
142
+
143
+ module Help
144
+ class << self
145
+
146
+ def print_all_commands
147
+ Qcmd.print %[
148
+ #{Qcmd.centered_text(' Available Commands ', '-')}
149
+
150
+ exit
151
+
152
+ close qcmd
153
+
154
+
155
+ connect MACHINE_NAME
156
+
157
+ connect to the machine with name MACHINE_NAME
158
+
159
+
160
+ disconnect
161
+
162
+ disconnect from the current machine and workspace
163
+
164
+
165
+ use WORKSPACE_NAME [PASSCODE]
166
+
167
+ connect to the workspace with name WORKSPACE_NAME using passcode PASSCODE. A
168
+ passcode is only required if the workspace has one enabled.
169
+
170
+
171
+ workspaces
172
+
173
+ show a list of the available workspaces for the currently connected machine.
174
+
175
+
176
+ workspace COMMAND [VALUE]
177
+
178
+ pass the given COMMAND to the connected workspace. The following commands will
179
+ act on a workspace but will not return a value:
180
+
181
+ #{Qcmd.wrapped_text(Qcmd::Commands::WORKSPACE_NO_RESPONSE.sort.join(', '), :indent => ' ').join("\n")}
182
+
183
+ And these commands will not act on a workspace, but will return a value
184
+ (usually a list of cues):
185
+
186
+ #{Qcmd.wrapped_text((Qcmd::Commands::WORKSPACE_RESPONSE - ['connect']).sort.join(', '), :indent => ' ').join("\n")}
187
+
188
+ * Pro Tip: once you are connected to a workspace, you can just use COMMAND
189
+ and leave off "workspace" to quickly send the given COMMAND to the connected
190
+ workspace.
191
+
192
+
193
+ cue NUMBER COMMAND [VALUE [ANOTHER_VALUE ...]]
194
+
195
+ send a command to the cue with the given NUMBER.
196
+
197
+ NUMBER can be a string or a number, depending on the command.
198
+
199
+ COMMAND can be one of:
200
+
201
+ #{Qcmd.wrapped_text(Qcmd::Commands::ALL_CUE_COMMANDS.sort.join(', '), :indent => ' ').join("\n")}
202
+
203
+ Of those commands, only some accept a VALUE. The following commands, if given
204
+ a value, will update the cue:
205
+
206
+ #{Qcmd.wrapped_text(Qcmd::Commands::CUE_ARG.sort.join(', '), :indent => ' ').join("\n")}
207
+
208
+ Some cues are Read-Only and will return information about a cue:
209
+
210
+ #{Qcmd.wrapped_text(Qcmd::Commands::CUE_RESPONSE.sort.join(', '), :indent => ' ').join("\n")}
211
+
212
+ Finally, some commands act on a cue, but don't take a VALUE and don't
213
+ respond:
214
+
215
+ #{Qcmd.wrapped_text(Qcmd::Commands::CUE_NO_RESPONSE.sort.join(', '), :indent => ' ').join("\n")}
216
+
217
+ ]
218
+ end
219
+ end
220
+
221
+ end
103
222
  end
104
223
  end
data/lib/qcmd/context.rb CHANGED
@@ -33,5 +33,18 @@ module Qcmd
33
33
  :workspace
34
34
  end
35
35
  end
36
+
37
+ def print_workspace_list
38
+ Qcmd.print Qcmd.centered_text(" Workspaces ", '-')
39
+ Qcmd.print
40
+
41
+ machine.workspaces.each_with_index do |ws, n|
42
+ Qcmd.print "#{ n + 1 }. #{ ws.name }#{ ws.passcode? ? ' [PROTECTED]' : ''}"
43
+ end
44
+
45
+ Qcmd.print
46
+ Qcmd.print_wrapped('Type `use "WORKSPACE_NAME" PASSCODE` to load a workspace. Passcode is optional.')
47
+ Qcmd.print
48
+ end
36
49
  end
37
50
  end
@@ -0,0 +1,84 @@
1
+ module OSC
2
+ class StoppingServer < Server
3
+ def initialize *args
4
+ @state = :initialized
5
+ @port = args.first
6
+ super(*args)
7
+ end
8
+
9
+ def run
10
+ @state = :starting
11
+ super
12
+ end
13
+
14
+ def stop
15
+ @state = :stopping
16
+ stop_detector
17
+ stop_dispatcher
18
+ end
19
+
20
+ def state
21
+ @state
22
+ end
23
+
24
+ private
25
+
26
+ def stop_detector
27
+ # send listening port a "CLOSE" signal on the open UDP port
28
+ _closer = UDPSocket.new
29
+ _closer.connect('', @port)
30
+ _closer.puts "CLOSE-#{@port}"
31
+ _closer.close unless _closer.closed? || !_closer.respond_to?(:close)
32
+ end
33
+
34
+ def stop_dispatcher
35
+ @queue << :stop
36
+ end
37
+
38
+ def dispatcher
39
+ loop do
40
+ mesg = @queue.pop
41
+ dispatch_message( mesg )
42
+ end
43
+ rescue StopException
44
+ @state = :stopped
45
+ end
46
+
47
+ def dispatch_message message
48
+ if message.is_a?(Symbol) && message.to_s == 'stop'
49
+ raise StopException.new
50
+ end
51
+
52
+ super(message)
53
+ end
54
+
55
+ def detector
56
+ @state = :listening
57
+
58
+ loop do
59
+ osc_data, network = @socket.recvfrom( 16384 )
60
+
61
+ # quit if socket receives the close signal
62
+ if osc_data == "CLOSE-#{@port}"
63
+ @socket.close if !@socket.closed? && @socket.respond_to?(:close)
64
+ break
65
+ end
66
+
67
+ unpack_socket_receipt osc_data, network
68
+ end
69
+ end
70
+
71
+ def unpack_socket_receipt osc_data, network
72
+ ip_info = Array.new
73
+ ip_info << network[1]
74
+ ip_info.concat(network[2].split('.'))
75
+ OSC::OSCPacket.messages_from_network( osc_data, ip_info ).each do |message|
76
+ @queue.push(message)
77
+ end
78
+ rescue EOFError
79
+ # pass
80
+ end
81
+ end
82
+
83
+ class StopException < Exception; end
84
+ end
data/lib/qcmd/handler.rb CHANGED
@@ -10,15 +10,7 @@ module Qcmd
10
10
  Qcmd.context.machine.workspaces = reply.data.map {|ws| Qcmd::QLab::Workspace.new(ws)}
11
11
 
12
12
  unless Qcmd.quiet?
13
- print centered_text(" Workspaces ", '-')
14
- print
15
- Qcmd.context.machine.workspaces.each_with_index do |ws, n|
16
- print "#{ n + 1 }. #{ ws.name }#{ ws.passcode? ? ' [PROTECTED]' : ''}"
17
- end
18
-
19
- print
20
- print_wrapped('Type `use "WORKSPACE_NAME" PASSCODE` to load a workspace. Passcode is optional.')
21
- print
13
+ Qcmd.context.print_workspace_list
22
14
  end
23
15
 
24
16
  when %r[/workspace/[^/]+/connect]
@@ -69,8 +61,24 @@ module Qcmd
69
61
  if result.is_a?(String) || result.is_a?(Numeric)
70
62
  print result
71
63
  else
72
- print result.inspect
64
+ case reply.address
65
+ when %r[/basics]
66
+ keys = result.keys.sort
67
+ table(['Field Name', 'Value'], keys.map {|key|
68
+ [key, result[key]]
69
+ })
70
+ else
71
+ begin
72
+ print JSON.pretty_generate(result)
73
+ rescue JSON::GeneratorError
74
+ print result.to_s
75
+ end
76
+ end
73
77
  end
78
+
79
+ when %r[/thump]
80
+ print reply.data
81
+
74
82
  else
75
83
  Qcmd.debug "(unrecognized message from QLab, cannot handle #{ reply.address })"
76
84
  end
@@ -4,23 +4,12 @@ module Qcmd
4
4
  module InputCompleter
5
5
  # the commands listed here should represent every possible legal command
6
6
  ReservedWords = %w[
7
- connect exit quit workspace workspaces disconnect
7
+ connect exit quit workspace workspaces disconnect help
8
8
  ]
9
9
 
10
- ReservedWorkspaceWords = %w[
11
- cueLists selectedCues runningCues runningOrPausedCues thump
12
- go stop pause resume reset panic disconnect
13
- ]
10
+ ReservedWorkspaceWords = Qcmd::Commands::ALL_WORKSPACE_COMMANDS
14
11
 
15
- ReservedCueWords = %w[
16
- cue stop pause resume load preview reset panic loadAt uniqueID
17
- hasFileTargets hasCueTargets allowsEditingDuration isLoaded isRunning
18
- isPaused isBroken preWaitElapsed actionElapsed postWaitElapsed
19
- percentPreWaitElapsed percentActionElapsed percentPostWaitElapsed
20
- type number name notes cueTargetNumber cueTargetId preWait duration
21
- postWait continueMode flagged armed colorName basics children
22
- sliderLevel sliderLevels
23
- ]
12
+ ReservedCueWords = Qcmd::Commands::ALL_CUE_COMMANDS
24
13
 
25
14
  CompletionProc = Proc.new {|input|
26
15
  # puts "input: #{ input }"
data/lib/qcmd/network.rb CHANGED
@@ -2,7 +2,7 @@ require 'dnssd'
2
2
 
3
3
  module Qcmd
4
4
  class Network
5
- BROWSE_TIMEOUT = 8
5
+ BROWSE_TIMEOUT = 2
6
6
 
7
7
  class << self
8
8
  attr_accessor :machines, :browse_thread
@@ -18,19 +18,7 @@ module Qcmd
18
18
  end
19
19
  end
20
20
 
21
- naps = 0
22
- changed = false
23
- previous = 0
24
-
25
- # sleep for BROWSE_TIMEOUT loops
26
- while naps < BROWSE_TIMEOUT
27
- sleep 0.2
28
- naps += 1
29
-
30
- if machines.size != previous
31
- previous = machines.size
32
- end
33
- end
21
+ sleep BROWSE_TIMEOUT
34
22
 
35
23
  Thread.kill(browse_thread) if browse_thread.alive?
36
24
  end
data/lib/qcmd/osc.rb ADDED
@@ -0,0 +1 @@
1
+ require 'qcmd/osc/server'
@@ -8,17 +8,21 @@ module Qcmd
8
8
  end
9
9
  end
10
10
 
11
- # always output
11
+ # display output unless absolutely silent
12
12
  def print message=nil
13
- log(message)
13
+ log(message) unless Qcmd.silent?
14
+ end
15
+
16
+ def set_columns value
17
+ @columns = value
14
18
  end
15
19
 
16
20
  def columns
17
- begin
18
- `stty size`.split.last.to_i
19
- rescue
20
- 80
21
- end
21
+ @columns || (begin
22
+ @columns = `stty size`.split.last.to_i
23
+ rescue
24
+ @columns = 80
25
+ end)
22
26
  end
23
27
 
24
28
  def pluralize n, word
@@ -27,12 +31,35 @@ module Qcmd
27
31
 
28
32
  def word_wrap(text, options={})
29
33
  options = {
30
- :line_width => columns
34
+ :line_width => columns,
35
+ :preserve_whitespace => false
31
36
  }.merge options
32
37
 
33
- text.split("\n").collect do |line|
34
- line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line
35
- end * "\n"
38
+ unless options[:preserve_whitespace]
39
+ text = text.gsub(/\s+/, ' ') # collapse whitespace
40
+ end
41
+
42
+ prefix = options[:indent] ? options[:indent] : ''
43
+
44
+ line_width = options[:line_width]
45
+ lines = ['']
46
+
47
+ space = ' '
48
+ space_size = 2
49
+ space_left = line_width
50
+ text.split.each do |word|
51
+ if (word.size + space.size) >= space_left
52
+ word = "%s%s" % [prefix, word]
53
+ space_left = line_width - (word.size + space_size)
54
+ lines << ""
55
+ else
56
+ space_left = space_left - (word.size + space_size)
57
+ end
58
+
59
+ lines.last << "%s%s" % [word, space]
60
+ end
61
+
62
+ lines
36
63
  end
37
64
 
38
65
  def ascii_qlab
@@ -53,9 +80,14 @@ module Qcmd
53
80
  end
54
81
 
55
82
  # turn line into lines of text of columns length
56
- def wrapped_text line
57
- line = line.gsub(/\s+/, ' ') # collapse whitespace
58
- word_wrap(line, :line_width => columns).split("\n")
83
+ def wrapped_text *args
84
+ options = {
85
+ :line_width => columns
86
+ }.merge args.extract_options!
87
+
88
+ line = args.shift
89
+
90
+ word_wrap(line, options)
59
91
  end
60
92
 
61
93
  def print_wrapped line
@@ -103,6 +135,14 @@ module Qcmd
103
135
  def table headers, rows
104
136
  print
105
137
  columns = headers.map(&:size)
138
+
139
+ # coerce row values to strings
140
+ rows.each do |row|
141
+ columns.each_with_index do |col, n|
142
+ row[n] = row[n].to_s
143
+ end
144
+ end
145
+
106
146
  rows.each do |row|
107
147
  columns.each_with_index do |col, n|
108
148
  columns[n] = [col, row[n].size].max + 1
data/lib/qcmd/server.rb CHANGED
@@ -1,9 +1,4 @@
1
1
  require 'osc-ruby'
2
- require 'osc-ruby/em_server'
3
- begin
4
- require 'ruby-debug'
5
- rescue LoadError
6
- end
7
2
 
8
3
  require 'json'
9
4
 
@@ -49,11 +44,10 @@ module Qcmd
49
44
  # initialize
50
45
  def listen
51
46
  if receive_channel
52
- Qcmd.debug "(stopping existing server)"
53
47
  stop
54
48
  end
55
49
 
56
- self.receive_channel = OSC::EMServer.new(self.receive_port)
50
+ self.receive_channel = OSC::StoppingServer.new(self.receive_port)
57
51
 
58
52
  Qcmd.debug "(opening receiving channel: #{ self.receive_channel.inspect })"
59
53
 
@@ -133,7 +127,7 @@ module Qcmd
133
127
  end
134
128
 
135
129
  def stop
136
- Thread.kill(receive_thread) if receive_thread.alive?
130
+ receive_channel.stop if receive_channel && receive_channel.state == :listening
137
131
  end
138
132
 
139
133
  def run
data/lib/qcmd/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qcmd
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.7"
3
3
  end
data/qcmd.gemspec CHANGED
@@ -18,13 +18,11 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ["lib"]
19
19
 
20
20
  gem.add_runtime_dependency 'dnssd'
21
- gem.add_runtime_dependency 'eventmachine'
22
21
  gem.add_runtime_dependency 'json'
23
22
  gem.add_runtime_dependency 'osc-ruby'
24
23
  gem.add_runtime_dependency 'trollop'
25
24
 
26
- gem.add_development_dependency "rspec"
25
+ gem.add_development_dependency "rspec", '~> 2.10.0'
27
26
  gem.add_development_dependency "cucumber"
28
27
  gem.add_development_dependency "aruba"
29
- gem.add_development_dependency 'ruby-debug'
30
28
  end
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # ruby builtin
4
+ require 'readline'
5
+
6
+ require 'rubygems'
7
+
8
+ # use Qcmd's parser for type conversions and double quote recognizing
9
+ require 'qcmd/parser'
10
+
11
+ # other gems
12
+ require 'osc-ruby'
13
+ require 'json'
14
+
15
+ # handle Ctrl-C quitting
16
+ trap("INT") { exit }
17
+
18
+ # if there are args, there must be two:
19
+ #
20
+ # send_address:send_port
21
+ #
22
+ # and
23
+ #
24
+ # receive_port
25
+
26
+ # default qlab send port 53000
27
+ send_address = 'localhost'
28
+ send_port = 53000
29
+
30
+ # default qlab receive port 53001
31
+ receive_port = 53001
32
+
33
+ if ARGV.size > 0
34
+ send_matcher = /([^:]+):(\d+)/
35
+ recv_matcher = /(\d+)/
36
+
37
+ if send_matcher =~ ARGV[0]
38
+ send_address, send_port = $1, $2
39
+ elsif recv_matcher =~ ARGV[0]
40
+ receive_port = $1
41
+ else
42
+ puts 'send address must be an address in the form SERVER_ADDRESS:PORT'
43
+ end
44
+
45
+ if ARGV[1]
46
+ if recv_matcher =~ ARGV[1]
47
+ receive_port = $1
48
+ else
49
+ puts 'send address must be a port number'
50
+ end
51
+ end
52
+ end
53
+
54
+ puts %[connecting to server #{send_address}:#{send_port} with receiver at port #{receive_port}]
55
+
56
+ # how long to wait for responses from QLab. If you notice responses coming in
57
+ # out of order, you may need to increase this value.
58
+ REPLY_TIMEOUT = 1
59
+
60
+ # open IO pipes to communicate between client / server process
61
+ response_receiver, writer = IO.pipe
62
+
63
+ # fork readline process to allow server to communicate because if we use
64
+ # Thread.new, readline locks the WHOLE Ruby VM and the server can't start
65
+ pid = fork do
66
+ # close the IO channel that server process will be using
67
+ writer.close
68
+
69
+ # native OSC connection, outbound
70
+ client = OSC::Client.new 'localhost', send_port
71
+
72
+ loop do
73
+ command_string = Readline.readline('> ', true)
74
+ next if command_string.nil? || command_string.strip.size == 0
75
+
76
+ # break command string up and properly typecast all given values
77
+ args = Qcmd::Parser.parse(command_string)
78
+ address = args.shift
79
+
80
+ # quit, q, and exit all quit
81
+ exit if /^(q(uit)?|exit)/i =~ address
82
+
83
+ # "sanitize" the given address
84
+ if %r[^/] != address
85
+ if address == '>'
86
+ # pasted previous command line entry
87
+ address = args.shift
88
+ else
89
+ # add lazy slash
90
+ address = "/#{ address }"
91
+ end
92
+ end
93
+
94
+ message = OSC::Message.new(address, *args)
95
+ client.send message
96
+
97
+ # wait for response until TIMEOUT seconds
98
+ select = IO.select([response_receiver], [], [], REPLY_TIMEOUT)
99
+ if !select.nil?
100
+ rs = select[0]
101
+
102
+ # get readable channel
103
+ if in_channel = rs[0]
104
+ # read everything until end of stream
105
+ while line = in_channel.gets
106
+ if line.strip != '<<EOS>>'
107
+ puts line
108
+ else
109
+ break
110
+ end
111
+ end
112
+ end
113
+ else
114
+ # select timed out, probably not going to get a response,
115
+ # go back to command line mode
116
+ end
117
+ end
118
+ end
119
+
120
+ puts "launched console with process id #{ pid }, use Ctrl-c or 'exit' to quit"
121
+
122
+ # close unused pipe
123
+ response_receiver.close
124
+
125
+ # native OSC connection, inbound
126
+ server = OSC::Server.new receive_port
127
+
128
+ # server listens and forwards responses to the forked process
129
+ server.add_method %r[/reply] do |osc_message|
130
+ data = JSON.parse(osc_message.to_a.first)['data']
131
+
132
+ begin
133
+ writer.puts JSON.pretty_generate(data)
134
+ rescue JSON::GeneratorError
135
+ writer.puts data.to_s
136
+ end
137
+
138
+ # end of signal
139
+ writer.puts '<<EOS>>'
140
+ end
141
+
142
+ # start blocking server
143
+ Thread.new do
144
+ server.run
145
+ end
146
+
147
+ # chill until the command line process quits
148
+ Process.wait pid
@@ -0,0 +1,2 @@
1
+ require 'osc-ruby'
2
+ require 'rspec'
File without changes
File without changes
@@ -0,0 +1,78 @@
1
+ require File.join( File.dirname(__FILE__) , '..', 'spec_helper' )
2
+ require 'qcmd'
3
+ require 'socket'
4
+
5
+ class PortFactory
6
+ @@counter = 12345
7
+
8
+ def self.new_port
9
+ @@counter += 1
10
+ @@counter
11
+ end
12
+ end
13
+
14
+ describe OSC::StoppingServer do
15
+ before :each do
16
+ @port = PortFactory.new_port
17
+ end
18
+
19
+ it "should bind to a socket when initialized" do
20
+ UDPSocket.any_instance.should_receive(:bind).with('', @port)
21
+ server = OSC::StoppingServer.new @port
22
+ end
23
+
24
+ it 'should start a listening thread when started' do
25
+ server = OSC::StoppingServer.new @port
26
+
27
+ test_thread = Thread.new do
28
+ Thread.should_receive :fork
29
+ server.run
30
+ end
31
+
32
+ server.stop
33
+ end
34
+
35
+ it 'should kill the listening thread and close socket when stopped' do
36
+ server = OSC::StoppingServer.new @port
37
+
38
+ test_thread = Thread.new do
39
+ server.run
40
+ end
41
+
42
+ sleep 0.1
43
+ server.stop
44
+ sleep 0.1
45
+
46
+ # server has stopped blocking
47
+ test_thread.alive?.should == false
48
+
49
+ # server claims it is closed
50
+ server.state.should == :stopped
51
+ end
52
+
53
+ it 'should create messages for legitimate OSC commands' do
54
+ server = OSC::StoppingServer.new @port
55
+
56
+ received = nil
57
+
58
+ server.add_method '/test' do |message|
59
+ received = message
60
+ end
61
+
62
+ test_thread = Thread.new do
63
+ server.run
64
+ end
65
+
66
+ received.should == nil
67
+
68
+ client = OSC::Client.new 'localhost', @port
69
+ client.send OSC::Message.new('/test', 'ansible')
70
+
71
+ sleep 0.1
72
+ server.stop
73
+
74
+ received.is_a?(OSC::Message).should == true
75
+ received.to_a.first.should == 'ansible'
76
+ end
77
+ end
78
+
File without changes
@@ -3,8 +3,7 @@ require 'qcmd'
3
3
  describe Qcmd do
4
4
  # tests go here
5
5
  it "should log debug messages when in verbose mode" do
6
- Qcmd.should_receive(:log, 'hello')
7
-
6
+ Qcmd.should_receive(:log).with('hello')
8
7
  Qcmd.verbose!
9
8
  Qcmd.log_level.should eql(:debug)
10
9
  Qcmd.debug 'hello'
@@ -16,4 +15,17 @@ describe Qcmd do
16
15
  Qcmd.log_level.should eql(:warning)
17
16
  Qcmd.debug 'hello'
18
17
  end
18
+
19
+ it 'should not log debug messages when in quiet block' do
20
+ Qcmd.verbose!
21
+ Qcmd.log_level.should eql(:debug)
22
+
23
+ Qcmd.while_quiet do
24
+ Qcmd.should_not_receive(:log)
25
+ Qcmd.log_level.should eql(:warning)
26
+ Qcmd.debug 'hello'
27
+ end
28
+
29
+ Qcmd.log_level.should eql(:debug)
30
+ end
19
31
  end
File without changes
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qcmd
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 6
10
- version: 0.1.6
9
+ - 7
10
+ version: 0.1.7
11
11
  platform: ruby
12
12
  authors:
13
13
  - Adam Bachman
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-11-21 00:00:00 -05:00
18
+ date: 2012-11-24 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -33,7 +33,7 @@ dependencies:
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
35
  - !ruby/object:Gem::Dependency
36
- name: eventmachine
36
+ name: json
37
37
  prerelease: false
38
38
  requirement: &id002 !ruby/object:Gem::Requirement
39
39
  none: false
@@ -47,7 +47,7 @@ dependencies:
47
47
  type: :runtime
48
48
  version_requirements: *id002
49
49
  - !ruby/object:Gem::Dependency
50
- name: json
50
+ name: osc-ruby
51
51
  prerelease: false
52
52
  requirement: &id003 !ruby/object:Gem::Requirement
53
53
  none: false
@@ -61,7 +61,7 @@ dependencies:
61
61
  type: :runtime
62
62
  version_requirements: *id003
63
63
  - !ruby/object:Gem::Dependency
64
- name: osc-ruby
64
+ name: trollop
65
65
  prerelease: false
66
66
  requirement: &id004 !ruby/object:Gem::Requirement
67
67
  none: false
@@ -75,21 +75,23 @@ dependencies:
75
75
  type: :runtime
76
76
  version_requirements: *id004
77
77
  - !ruby/object:Gem::Dependency
78
- name: trollop
78
+ name: rspec
79
79
  prerelease: false
80
80
  requirement: &id005 !ruby/object:Gem::Requirement
81
81
  none: false
82
82
  requirements:
83
- - - ">="
83
+ - - ~>
84
84
  - !ruby/object:Gem::Version
85
- hash: 3
85
+ hash: 39
86
86
  segments:
87
+ - 2
88
+ - 10
87
89
  - 0
88
- version: "0"
89
- type: :runtime
90
+ version: 2.10.0
91
+ type: :development
90
92
  version_requirements: *id005
91
93
  - !ruby/object:Gem::Dependency
92
- name: rspec
94
+ name: cucumber
93
95
  prerelease: false
94
96
  requirement: &id006 !ruby/object:Gem::Requirement
95
97
  none: false
@@ -103,7 +105,7 @@ dependencies:
103
105
  type: :development
104
106
  version_requirements: *id006
105
107
  - !ruby/object:Gem::Dependency
106
- name: cucumber
108
+ name: aruba
107
109
  prerelease: false
108
110
  requirement: &id007 !ruby/object:Gem::Requirement
109
111
  none: false
@@ -116,34 +118,6 @@ dependencies:
116
118
  version: "0"
117
119
  type: :development
118
120
  version_requirements: *id007
119
- - !ruby/object:Gem::Dependency
120
- name: aruba
121
- prerelease: false
122
- requirement: &id008 !ruby/object:Gem::Requirement
123
- none: false
124
- requirements:
125
- - - ">="
126
- - !ruby/object:Gem::Version
127
- hash: 3
128
- segments:
129
- - 0
130
- version: "0"
131
- type: :development
132
- version_requirements: *id008
133
- - !ruby/object:Gem::Dependency
134
- name: ruby-debug
135
- prerelease: false
136
- requirement: &id009 !ruby/object:Gem::Requirement
137
- none: false
138
- requirements:
139
- - - ">="
140
- - !ruby/object:Gem::Version
141
- hash: 3
142
- segments:
143
- - 0
144
- version: "0"
145
- type: :development
146
- version_requirements: *id009
147
121
  description: A simple interactive QLab 3 command line controller
148
122
  email:
149
123
  - adam.bachman@gmail.com
@@ -168,10 +142,12 @@ files:
168
142
  - lib/qcmd/context.rb
169
143
  - lib/qcmd/core_ext/array.rb
170
144
  - lib/qcmd/core_ext/osc/message.rb
145
+ - lib/qcmd/core_ext/osc/stopping_server.rb
171
146
  - lib/qcmd/handler.rb
172
147
  - lib/qcmd/input_completer.rb
173
148
  - lib/qcmd/machine.rb
174
149
  - lib/qcmd/network.rb
150
+ - lib/qcmd/osc.rb
175
151
  - lib/qcmd/parser.rb
176
152
  - lib/qcmd/plaintext.rb
177
153
  - lib/qcmd/qlab.rb
@@ -182,11 +158,14 @@ files:
182
158
  - lib/qcmd/version.rb
183
159
  - qcmd.gemspec
184
160
  - sample/dnssd.rb
185
- - spec/cli_spec.rb
186
- - spec/commands_spec.rb
187
- - spec/parser_spec.rb
188
- - spec/qcmd_spec.rb
189
- - spec/qlab_spec.rb
161
+ - sample/simple_console.rb
162
+ - spec/spec_helper.rb
163
+ - spec/unit/cli_spec.rb
164
+ - spec/unit/commands_spec.rb
165
+ - spec/unit/osc_server_spec.rb
166
+ - spec/unit/parser_spec.rb
167
+ - spec/unit/qcmd_spec.rb
168
+ - spec/unit/qlab_spec.rb
190
169
  has_rdoc: true
191
170
  homepage: https://github.com/abachman/qcmd
192
171
  licenses: []
@@ -223,8 +202,10 @@ specification_version: 3
223
202
  summary: QLab 3 console
224
203
  test_files:
225
204
  - features/support/setup.rb
226
- - spec/cli_spec.rb
227
- - spec/commands_spec.rb
228
- - spec/parser_spec.rb
229
- - spec/qcmd_spec.rb
230
- - spec/qlab_spec.rb
205
+ - spec/spec_helper.rb
206
+ - spec/unit/cli_spec.rb
207
+ - spec/unit/commands_spec.rb
208
+ - spec/unit/osc_server_spec.rb
209
+ - spec/unit/parser_spec.rb
210
+ - spec/unit/qcmd_spec.rb
211
+ - spec/unit/qlab_spec.rb