qcmd 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +131 -0
- data/Rakefile +14 -0
- data/TODO.md +3 -0
- data/bin/qcmd +31 -0
- data/features/hello.feature +13 -0
- data/features/support/setup.rb +2 -0
- data/lib/qcmd.rb +48 -0
- data/lib/qcmd/cli.rb +167 -0
- data/lib/qcmd/commands.rb +103 -0
- data/lib/qcmd/context.rb +37 -0
- data/lib/qcmd/core_ext/array.rb +6 -0
- data/lib/qcmd/core_ext/osc/message.rb +7 -0
- data/lib/qcmd/handler.rb +80 -0
- data/lib/qcmd/input_completer.rb +67 -0
- data/lib/qcmd/machine.rb +27 -0
- data/lib/qcmd/network.rb +70 -0
- data/lib/qcmd/parser.rb +48 -0
- data/lib/qcmd/plaintext.rb +116 -0
- data/lib/qcmd/qlab.rb +3 -0
- data/lib/qcmd/qlab/cue.rb +70 -0
- data/lib/qcmd/qlab/reply.rb +25 -0
- data/lib/qcmd/qlab/workspace.rb +28 -0
- data/lib/qcmd/server.rb +178 -0
- data/lib/qcmd/version.rb +3 -0
- data/qcmd.gemspec +30 -0
- data/sample/dnssd.rb +36 -0
- data/spec/cli_spec.rb +14 -0
- data/spec/commands_spec.rb +38 -0
- data/spec/qcmd_spec.rb +19 -0
- data/spec/qlab_spec.rb +15 -0
- metadata +230 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Adam Bachman
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# Qcmd
|
2
|
+
|
3
|
+
`qcmd` is intended to be a simple command line client for QLab utilizing
|
4
|
+
QLab 3's new OSC interface. `qcmd` should be useable from any machine on
|
5
|
+
the same local network as the QLab workspace you intend to work with.
|
6
|
+
|
7
|
+
**This project should be considered experimental. DO NOT RUN SHOWS WITH
|
8
|
+
IT.**
|
9
|
+
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Install this gem to your machine by running the following command:
|
14
|
+
|
15
|
+
$ gem install qcmd
|
16
|
+
|
17
|
+
That should do ya.
|
18
|
+
|
19
|
+
## Starting the `qcmd` console.
|
20
|
+
|
21
|
+
Run the following command in a terminal window:
|
22
|
+
|
23
|
+
$ qcmd
|
24
|
+
|
25
|
+
From there, you can connect to a machine, connect to a workspace, and then
|
26
|
+
send commands to cues and the workspace.
|
27
|
+
|
28
|
+
`qcmd` supports tab completion for commands in case you get stuck or are
|
29
|
+
wondering what you can do from the console.
|
30
|
+
|
31
|
+
Run `qcmd` with the -v option to get full debugging output. Use the main
|
32
|
+
project repository (https://github.com/abachman/qcmd) to report any issues.
|
33
|
+
|
34
|
+
An example session might look like this:
|
35
|
+
|
36
|
+
$ qcmd
|
37
|
+
.:::: .:: .::
|
38
|
+
.:: .:: .:: .::
|
39
|
+
.:: .::.:: .:: .::
|
40
|
+
.:: .::.:: .:: .:: .:: .::
|
41
|
+
.:: .::.:: .:: .:: .:: .::
|
42
|
+
.:: .: .:: .:: .:: .:: .:: .::
|
43
|
+
.:: :: .:::::::: .:: .:::.:: .::
|
44
|
+
.:
|
45
|
+
|
46
|
+
qcmd 0.1.0 (c) 2012 Figure 53, Baltimore, MD.
|
47
|
+
|
48
|
+
Found 2 QLab machines
|
49
|
+
|
50
|
+
1. adam-retina
|
51
|
+
2. f53zwimac
|
52
|
+
|
53
|
+
type `connect MACHINE` to connect to a machine
|
54
|
+
|
55
|
+
> connect adam-retina
|
56
|
+
connecting to machine: adam-retina
|
57
|
+
-------------------------------- Workspaces --------------------------------
|
58
|
+
|
59
|
+
1. Untitled Workspace
|
60
|
+
|
61
|
+
Type `use "WORKSPACE_NAME" PASSCODE` to load a workspace. Only enter a
|
62
|
+
passcode if your workspace uses one
|
63
|
+
|
64
|
+
adam-retina> use "Untitled Workspace"
|
65
|
+
connecting to workspace: Untitled Workspace
|
66
|
+
connected to workspace
|
67
|
+
loaded 2 cues
|
68
|
+
adam-retina:Untitled Workspace> cues
|
69
|
+
|
70
|
+
----------------------------------- Cues -----------------------------------
|
71
|
+
|
72
|
+
Number Id Name Type
|
73
|
+
|
74
|
+
1 2 Nope Wait
|
75
|
+
2 3 Nipe Audio
|
76
|
+
|
77
|
+
|
78
|
+
adam-retina:Untitled Workspace> cue 2 start
|
79
|
+
adam-retina:Untitled Workspace> cue 2 isRunning
|
80
|
+
true
|
81
|
+
adam-retina:Untitled Workspace> workspace runningCues
|
82
|
+
|
83
|
+
------------------------------- Running Cues -------------------------------
|
84
|
+
|
85
|
+
Number Id Name Type
|
86
|
+
|
87
|
+
2 3 Nipe Audio
|
88
|
+
|
89
|
+
|
90
|
+
adam-retina:Untitled Workspace> cue 2
|
91
|
+
1 2 actionElapsed
|
92
|
+
allowsEditingDuration armed basics
|
93
|
+
children colorName connect
|
94
|
+
continueMode cue cueLists
|
95
|
+
cueTargetId cueTargetNumber disconnect
|
96
|
+
duration exit flagged
|
97
|
+
hasCueTargets hasFileTargets isBroken
|
98
|
+
isLoaded isPaused isRunning
|
99
|
+
load loadAt name
|
100
|
+
notes number panic
|
101
|
+
pause percentActionElapsed percentPostWaitElapsed
|
102
|
+
percentPreWaitElapsed postWait postWaitElapsed
|
103
|
+
preview preWait preWaitElapsed
|
104
|
+
reset resume runningCues
|
105
|
+
runningOrPausedCues selectedCues stop
|
106
|
+
thump type uniqueID
|
107
|
+
workspace workspaces
|
108
|
+
adam-retina:Untitled Workspace> cue 2 pause
|
109
|
+
adam-retina:Untitled Workspace> cue 2 isRunning
|
110
|
+
false
|
111
|
+
adam-retina:Untitled Workspace> cue 2 percentActionElapsed
|
112
|
+
0.109189204871655
|
113
|
+
adam-retina:Untitled Workspace> disconnect
|
114
|
+
|
115
|
+
Found 2 QLab machines
|
116
|
+
|
117
|
+
1. adam-retina
|
118
|
+
2. f53zwimac
|
119
|
+
|
120
|
+
type `connect MACHINE` to connect to a machine
|
121
|
+
|
122
|
+
> exit
|
123
|
+
exiting...
|
124
|
+
|
125
|
+
## Contributing
|
126
|
+
|
127
|
+
1. Fork it
|
128
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
129
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
130
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
131
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'cucumber'
|
4
|
+
require 'cucumber/rake/task'
|
5
|
+
|
6
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
7
|
+
t.cucumber_opts = "features --format pretty"
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'rspec/core/rake_task'
|
11
|
+
|
12
|
+
RSpec::Core::RakeTask.new(:spec)
|
13
|
+
|
14
|
+
task :default => [:spec, :features]
|
data/TODO.md
ADDED
data/bin/qcmd
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'qcmd'
|
4
|
+
require 'trollop'
|
5
|
+
|
6
|
+
VERSION_STRING = "qcmd #{ Qcmd::VERSION } (c) 2012 Figure 53, Baltimore, MD."
|
7
|
+
|
8
|
+
opts = Trollop::options do
|
9
|
+
version VERSION_STRING
|
10
|
+
opt :verbose, 'Use verbose mode', :default => false
|
11
|
+
opt :debug, "Show full debug output, don't make changes to workspaces", :default => false
|
12
|
+
end
|
13
|
+
|
14
|
+
if opts[:verbose]
|
15
|
+
Qcmd.log_level = :debug
|
16
|
+
end
|
17
|
+
|
18
|
+
if opts[:debug]
|
19
|
+
Qcmd.log_level = :debug
|
20
|
+
Qcmd.debug_mode = true
|
21
|
+
end
|
22
|
+
|
23
|
+
# browse local network and check for qlab + qlab workspaces
|
24
|
+
|
25
|
+
Qcmd.ascii_qlab
|
26
|
+
Qcmd.print
|
27
|
+
Qcmd.print Qcmd.centered_text(VERSION_STRING)
|
28
|
+
|
29
|
+
Qcmd::Network.browse_and_display
|
30
|
+
|
31
|
+
Qcmd::CLI.launch opts
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Feature: Hello
|
2
|
+
In order to render hello
|
3
|
+
As a CLI
|
4
|
+
I want to do the right thing
|
5
|
+
|
6
|
+
# Scenario: Phrase is default
|
7
|
+
# When I run `qcmd`
|
8
|
+
# Then the output should contain "hello world"
|
9
|
+
|
10
|
+
# Scenario: Phrase is given
|
11
|
+
# When I run `qcmd Tomato`
|
12
|
+
# Then the output should contain "Tomato"
|
13
|
+
|
data/lib/qcmd.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'qcmd/version'
|
2
|
+
require 'qcmd/input_completer'
|
3
|
+
|
4
|
+
require 'qcmd/core_ext/array'
|
5
|
+
require 'qcmd/core_ext/osc/message'
|
6
|
+
|
7
|
+
module Qcmd
|
8
|
+
# Your code goes here...
|
9
|
+
autoload :Handler, 'qcmd/handler'
|
10
|
+
autoload :Server, 'qcmd/server'
|
11
|
+
autoload :Context, 'qcmd/context'
|
12
|
+
autoload :Parser, 'qcmd/parser'
|
13
|
+
autoload :CLI, 'qcmd/cli'
|
14
|
+
autoload :Machine, 'qcmd/machine'
|
15
|
+
autoload :Network, 'qcmd/network'
|
16
|
+
autoload :QLab, 'qcmd/qlab'
|
17
|
+
autoload :Plaintext, 'qcmd/plaintext'
|
18
|
+
autoload :Commands, 'qcmd/commands'
|
19
|
+
autoload :VERSION, 'qcmd/version'
|
20
|
+
|
21
|
+
class << self
|
22
|
+
include Qcmd::Plaintext
|
23
|
+
|
24
|
+
attr_accessor :log_level
|
25
|
+
attr_accessor :debug_mode
|
26
|
+
attr_accessor :context
|
27
|
+
|
28
|
+
def verbose!
|
29
|
+
self.log_level = :debug
|
30
|
+
end
|
31
|
+
|
32
|
+
def quiet!
|
33
|
+
self.log_level = :warning
|
34
|
+
end
|
35
|
+
|
36
|
+
def debug?
|
37
|
+
!!debug_mode
|
38
|
+
end
|
39
|
+
|
40
|
+
def debug message
|
41
|
+
log(message) if log_level == :debug
|
42
|
+
end
|
43
|
+
|
44
|
+
def connected?
|
45
|
+
!!context && !!context.machine && !context.machine.nil?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/qcmd/cli.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'qcmd/server'
|
2
|
+
|
3
|
+
require 'readline'
|
4
|
+
|
5
|
+
require 'osc-ruby'
|
6
|
+
require 'osc-ruby/em_server'
|
7
|
+
|
8
|
+
module Qcmd
|
9
|
+
class CLI
|
10
|
+
include Qcmd::Plaintext
|
11
|
+
|
12
|
+
attr_accessor :server, :prompt
|
13
|
+
|
14
|
+
def self.launch options={}
|
15
|
+
new options
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize options={}
|
19
|
+
# start local listening port
|
20
|
+
Qcmd.context = Qcmd::Context.new
|
21
|
+
|
22
|
+
self.prompt = '> '
|
23
|
+
|
24
|
+
start
|
25
|
+
|
26
|
+
# if local machines have already been detected and only one is available,
|
27
|
+
# use it.
|
28
|
+
if Qcmd::Network.machines
|
29
|
+
if Qcmd::Network.machines.size == 1 && !Qcmd::Network.machines.first.passcode?
|
30
|
+
puts "AUTOCONNECT"
|
31
|
+
connect Qcmd::Network.machines.first, nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def connect machine, passcode
|
37
|
+
if machine.nil?
|
38
|
+
print "A valid machine is needed to connect!"
|
39
|
+
return
|
40
|
+
end
|
41
|
+
|
42
|
+
Qcmd.context.machine = machine
|
43
|
+
Qcmd.context.workspace = nil
|
44
|
+
|
45
|
+
if server.nil?
|
46
|
+
# set client connection and start listening port
|
47
|
+
self.server = Qcmd::Server.new :receive => 53001
|
48
|
+
else
|
49
|
+
# change client connection
|
50
|
+
server.connect_to_client
|
51
|
+
end
|
52
|
+
server.run
|
53
|
+
|
54
|
+
server.load_workspaces
|
55
|
+
|
56
|
+
self.prompt = "#{ machine.name }> "
|
57
|
+
end
|
58
|
+
|
59
|
+
def use_workspace workspace
|
60
|
+
Qcmd.debug %[(connecting to workspace: "#{workspace.name}")]
|
61
|
+
# set workspace in context. Will unset later if there's a problem.
|
62
|
+
Qcmd.context.workspace = workspace
|
63
|
+
self.prompt = "#{ Qcmd.context.machine.name }:#{ workspace.name }> "
|
64
|
+
|
65
|
+
server.connect_to_workspace workspace
|
66
|
+
end
|
67
|
+
|
68
|
+
def reset
|
69
|
+
Qcmd.context.reset
|
70
|
+
server.stop
|
71
|
+
self.prompt = "> "
|
72
|
+
end
|
73
|
+
|
74
|
+
def start
|
75
|
+
loop do
|
76
|
+
# blocks the whole Ruby VM
|
77
|
+
message = Readline.readline(prompt, true)
|
78
|
+
|
79
|
+
if message.nil? || message.size == 0
|
80
|
+
Qcmd.debug "(got: #{ message.inspect })"
|
81
|
+
next
|
82
|
+
end
|
83
|
+
|
84
|
+
handle_message(message)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def handle_message message
|
89
|
+
args = Qcmd::Parser.parse(message)
|
90
|
+
command = args.shift
|
91
|
+
|
92
|
+
case command
|
93
|
+
when 'exit'
|
94
|
+
print 'exiting...'
|
95
|
+
exit 0
|
96
|
+
when 'connect'
|
97
|
+
Qcmd.debug "(connect command received args: #{ args.inspect })"
|
98
|
+
|
99
|
+
machine_name = args.shift
|
100
|
+
passcode = args.shift
|
101
|
+
|
102
|
+
if machine = Qcmd::Network.find(machine_name)
|
103
|
+
print "connecting to machine: #{machine_name}"
|
104
|
+
connect machine, passcode
|
105
|
+
else
|
106
|
+
print 'sorry, that machine could not be found'
|
107
|
+
end
|
108
|
+
when 'disconnect'
|
109
|
+
reset
|
110
|
+
Qcmd::Network.browse_and_display
|
111
|
+
when 'use'
|
112
|
+
Qcmd.debug "(use command received args: #{ args.inspect })"
|
113
|
+
|
114
|
+
workspace_name = args.shift.gsub(/['"]/, '')
|
115
|
+
passcode = args.shift
|
116
|
+
|
117
|
+
Qcmd.debug "(using workspace: #{ workspace_name.inspect })"
|
118
|
+
|
119
|
+
if workspace = Qcmd.context.machine.find_workspace(workspace_name)
|
120
|
+
workspace.passcode = passcode
|
121
|
+
print "connecting to workspace: #{workspace_name}"
|
122
|
+
use_workspace workspace
|
123
|
+
end
|
124
|
+
when 'cues'
|
125
|
+
if !Qcmd.context.workspace_connected?
|
126
|
+
print "You must be connected to a workspace before you can view a cue list."
|
127
|
+
elsif Qcmd.context.workspace.cues
|
128
|
+
print
|
129
|
+
print centered_text(" Cues ", '-')
|
130
|
+
table ['Number', 'Id', 'Name', 'Type'], Qcmd.context.workspace.cues.map {|cue|
|
131
|
+
[cue.number, cue.id, cue.name, cue.type]
|
132
|
+
}
|
133
|
+
print
|
134
|
+
end
|
135
|
+
when 'cue'
|
136
|
+
# pull off cue number
|
137
|
+
cue_number = args.shift
|
138
|
+
cue_action = args.shift
|
139
|
+
args = args.map {|a| a.gsub(/^"/, '').gsub(/"$/, '')}
|
140
|
+
|
141
|
+
if cue_number.nil?
|
142
|
+
print "no cue command given. cue commands should be in the form:"
|
143
|
+
print
|
144
|
+
print " > cue NUMBER COMMAND ARGUMENTS"
|
145
|
+
print
|
146
|
+
print wrapped_text("available cue commands are: #{Qcmd::InputCompleter::ReservedCueWords.inspect}")
|
147
|
+
elsif cue_action.nil?
|
148
|
+
server.send_workspace_command(cue_number)
|
149
|
+
else
|
150
|
+
server.send_cue_command(cue_number, cue_action, *args)
|
151
|
+
end
|
152
|
+
when 'workspace'
|
153
|
+
workspace_command = args.shift
|
154
|
+
|
155
|
+
if workspace_command.nil?
|
156
|
+
print wrapped_text("no workspace command given. available workspace commands are: #{Qcmd::InputCompleter::ReservedWorkspaceWords.inspect}")
|
157
|
+
else
|
158
|
+
server.send_workspace_command(workspace_command, *args)
|
159
|
+
end
|
160
|
+
|
161
|
+
else
|
162
|
+
server.send_command(command, *args)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|