ampt 0.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +23 -0
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +31 -0
- data/README.rdoc +75 -0
- data/Rakefile +57 -0
- data/ampt.gemspec +59 -0
- data/bin/ampt +9 -0
- data/lib/ampt.rb +353 -0
- data/lib/ampt_api/acoustics.rb +155 -0
- data/lib/ampt_cfg/default.rb +423 -0
- data/test/helper.rb +9 -0
- data/test/test_ampt.rb +7 -0
- metadata +73 -0
data/.autotest
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
# Autotest.add_hook :initialize do |at|
|
6
|
+
# at.extra_files << "../some/external/dependency.rb"
|
7
|
+
#
|
8
|
+
# at.libs << ":../some/external"
|
9
|
+
#
|
10
|
+
# at.add_exception 'vendor'
|
11
|
+
#
|
12
|
+
# at.add_mapping(/dependency.rb/) do |f, _|
|
13
|
+
# at.files_matching(/test_.*rb$/)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# %w(TestA TestB).each do |klass|
|
17
|
+
# at.extra_class_map[klass] = "test/test_misc.rb"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
21
|
+
# Autotest.add_hook :run_command do |at|
|
22
|
+
# system "rake build"
|
23
|
+
# end
|
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Copyright (c) 2009 The Board of Trustees of the University of Illinois.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Developed by: ACM@UIUC
|
5
|
+
University of Illinois at Urbana-Champaign
|
6
|
+
www.acm.uiuc.edu
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
of this software and associated documentation files (the "Software"), to
|
10
|
+
deal with the Software without restriction, including without limitation the
|
11
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
12
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
13
|
+
furnished to do so, subject to the following conditions:
|
14
|
+
1. Redistributions of source code must retain the above copyright notice,
|
15
|
+
this list of conditions and the following disclaimers.
|
16
|
+
2. Redistributions in binary form must reproduce the above copyright
|
17
|
+
notice, this list of conditions and the following disclaimers in the
|
18
|
+
documentation and/or other materials provided with the distribution.
|
19
|
+
3. Neither the names of ACM@UIUC, University of Illinois at Urbana-
|
20
|
+
Champaign, nor the names of its contributors may be used to endorse
|
21
|
+
or promote products derived from this Software without specific prior
|
22
|
+
written permission.
|
23
|
+
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
27
|
+
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
29
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
30
|
+
WITH THE SOFTWARE.
|
31
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
= ampt
|
2
|
+
|
3
|
+
http://www.interhacktive.com/ampt
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
This is a command line client for the Acoustics Media Player (amp).
|
8
|
+
Amp is available here: http://www.github.com/avuserow/amp
|
9
|
+
|
10
|
+
This is a work in progress.
|
11
|
+
|
12
|
+
== FEATURES:
|
13
|
+
|
14
|
+
* Control things from the command line!
|
15
|
+
* Extremely scriptable!
|
16
|
+
|
17
|
+
== SYNOPSIS:
|
18
|
+
|
19
|
+
$ ampt --help
|
20
|
+
Usage ampt [command] [options] [args]
|
21
|
+
|
22
|
+
Commands include:
|
23
|
+
new Get [num] newest songs. Defaults to 50.
|
24
|
+
list List all songs with the specified parameter. This
|
25
|
+
is less fuzzy than search. One of the flags must
|
26
|
+
be specified.
|
27
|
+
add Vote for a song. Provide a song id, or one of the
|
28
|
+
flags.
|
29
|
+
history Get [num] last-played songs. Defaults to 25.
|
30
|
+
stop Stop Acoustics.
|
31
|
+
volume Set the current volume level. If run without an
|
32
|
+
arg, returns the current volume.
|
33
|
+
start Start Acoustics.
|
34
|
+
remove Remove your votes from songs in the playlist.
|
35
|
+
Provide a song id or one of the
|
36
|
+
flags.
|
37
|
+
random Get [num] random songs. Defaults to 20.
|
38
|
+
show Show more info about a song. Takes a song id as
|
39
|
+
the single parameter.
|
40
|
+
reset Clear all of your votes.
|
41
|
+
skip Skip the currently playing song.
|
42
|
+
status Prints the currently playing song and playlist.
|
43
|
+
search Search the database for songs. Defaults to
|
44
|
+
searching by any parameter.
|
45
|
+
|
46
|
+
--version, -v: Print version and exit
|
47
|
+
--help, -h: Show this message
|
48
|
+
$ ampt status
|
49
|
+
Now Playing:
|
50
|
+
[ 14871] Let There Be Light by Justice
|
51
|
+
Voted up by rotap2
|
52
|
+
|
53
|
+
Playlist:
|
54
|
+
[ 14881] Newjack by Justice
|
55
|
+
Voted up by rotap2
|
56
|
+
...
|
57
|
+
25 songs queued for a total time of 2:13:51
|
58
|
+
You have 20 songs queued for a total time of 1:49:20
|
59
|
+
|
60
|
+
== REQUIREMENTS:
|
61
|
+
|
62
|
+
* libopenssl bindings for ruby
|
63
|
+
* libcurl-dev
|
64
|
+
|
65
|
+
== INSTALL:
|
66
|
+
|
67
|
+
$ sudo gem install ampt
|
68
|
+
|
69
|
+
== TODO:
|
70
|
+
* Add an option to change default flags of a command
|
71
|
+
* Make aliases more powerful (eg, be able to handle options tacked on)
|
72
|
+
|
73
|
+
== LICENSE:
|
74
|
+
|
75
|
+
See LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require File.dirname(__FILE__) + "/lib/ampt.rb"
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "ampt"
|
9
|
+
gem.version = Ampt::VERSION
|
10
|
+
gem.summary = %Q{Ampt is a command line client for the Acoustics Media Player (amp).}
|
11
|
+
gem.description = %Q{This is a command line client for the Acoustics Media Player (amp).
|
12
|
+
Amp is available here: http://www.github.com/avuserow/amp
|
13
|
+
|
14
|
+
This is a work in progress.}
|
15
|
+
gem.email = "rich@interhacktive.com"
|
16
|
+
gem.homepage = "http://github.com/capslock/ampt"
|
17
|
+
gem.authors = ["Rich"]
|
18
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
19
|
+
end
|
20
|
+
Jeweler::GemcutterTasks.new
|
21
|
+
rescue LoadError
|
22
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'rake/testtask'
|
26
|
+
Rake::TestTask.new(:test) do |test|
|
27
|
+
test.libs << 'lib' << 'test'
|
28
|
+
test.pattern = 'test/**/test_*.rb'
|
29
|
+
test.verbose = true
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
require 'rcov/rcovtask'
|
34
|
+
Rcov::RcovTask.new do |test|
|
35
|
+
test.libs << 'test'
|
36
|
+
test.pattern = 'test/**/test_*.rb'
|
37
|
+
test.verbose = true
|
38
|
+
end
|
39
|
+
rescue LoadError
|
40
|
+
task :rcov do
|
41
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
task :test => :check_dependencies
|
46
|
+
|
47
|
+
task :default => :test
|
48
|
+
|
49
|
+
require 'rake/rdoctask'
|
50
|
+
Rake::RDocTask.new do |rdoc|
|
51
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
52
|
+
|
53
|
+
rdoc.rdoc_dir = 'rdoc'
|
54
|
+
rdoc.title = "ampt #{version}"
|
55
|
+
rdoc.rdoc_files.include('README*')
|
56
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
57
|
+
end
|
data/ampt.gemspec
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{ampt}
|
8
|
+
s.version = "0.2.0.pre1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Rich"]
|
12
|
+
s.date = %q{2010-01-08}
|
13
|
+
s.default_executable = %q{ampt}
|
14
|
+
s.description = %q{This is a command line client for the Acoustics Media Player (amp).
|
15
|
+
Amp is available here: http://www.github.com/avuserow/amp
|
16
|
+
|
17
|
+
This is a work in progress.}
|
18
|
+
s.email = %q{rich@interhacktive.com}
|
19
|
+
s.executables = ["ampt"]
|
20
|
+
s.extra_rdoc_files = [
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc"
|
23
|
+
]
|
24
|
+
s.files = [
|
25
|
+
".autotest",
|
26
|
+
".document",
|
27
|
+
".gitignore",
|
28
|
+
"LICENSE",
|
29
|
+
"README.rdoc",
|
30
|
+
"Rakefile",
|
31
|
+
"ampt.gemspec",
|
32
|
+
"bin/ampt",
|
33
|
+
"lib/ampt.rb",
|
34
|
+
"lib/ampt_api/acoustics.rb",
|
35
|
+
"lib/ampt_cfg/default.rb",
|
36
|
+
"test/helper.rb",
|
37
|
+
"test/test_ampt.rb"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/capslock/ampt}
|
40
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = %q{1.3.5}
|
43
|
+
s.summary = %q{Ampt is a command line client for the Acoustics Media Player (amp).}
|
44
|
+
s.test_files = [
|
45
|
+
"test/helper.rb",
|
46
|
+
"test/test_ampt.rb"
|
47
|
+
]
|
48
|
+
|
49
|
+
if s.respond_to? :specification_version then
|
50
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
51
|
+
s.specification_version = 3
|
52
|
+
|
53
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
54
|
+
else
|
55
|
+
end
|
56
|
+
else
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
data/bin/ampt
ADDED
data/lib/ampt.rb
ADDED
@@ -0,0 +1,353 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'trollop'
|
3
|
+
|
4
|
+
module Ampt
|
5
|
+
VERSION = '0.2.0.pre1'
|
6
|
+
# This is the module that holds the API for the current music player. Defined in
|
7
|
+
# ampt_api/acoustics.rb
|
8
|
+
module API
|
9
|
+
end
|
10
|
+
|
11
|
+
# The typical entry point for an Ampt app. Configures a new instance of ampt
|
12
|
+
# using the specified block, and runs that configuration.
|
13
|
+
def self.init args = ARGV, *a, &b
|
14
|
+
@c = Config.new(*a, &b)
|
15
|
+
@c.run(ARGV)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Class containing all of the configuration details for Ampt.
|
19
|
+
class Config
|
20
|
+
# Create a new configuration. Optionally takes a parameter, and
|
21
|
+
# a block with which to set up configuration options.
|
22
|
+
def initialize(*a, &b)
|
23
|
+
@commands = {}
|
24
|
+
@alias = {}
|
25
|
+
@default = nil
|
26
|
+
@api = nil
|
27
|
+
cloaker(&b).bind(self).call(*a) if b
|
28
|
+
end
|
29
|
+
|
30
|
+
def extras s, *a, &b
|
31
|
+
@extras.send(s, *a, &b)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Specify a file path from which to load the config from. If a relative path
|
35
|
+
# is provided, the path is taken as relative to the Ampt library directory.
|
36
|
+
def load_config file
|
37
|
+
# If we got a relative path, take it as relative to the lib path.
|
38
|
+
# Otherwise, just use the absolute path.
|
39
|
+
name = if file[0...1] != '/'
|
40
|
+
File.dirname(__FILE__) + '/' + file
|
41
|
+
else
|
42
|
+
file
|
43
|
+
end
|
44
|
+
# Try to open the file. It may fail, as by default, we try to read
|
45
|
+
# from the user's .amptrc, which may not exist.
|
46
|
+
begin
|
47
|
+
File.open(name, 'r') do |f|
|
48
|
+
# There used to be more code here, and it used to be more of a hack.
|
49
|
+
# Now, it's robust to multiple instances of Ampt, and doesn't require
|
50
|
+
# that ugly Ampt::Config::Extra nonsense; neither here, nor in the
|
51
|
+
# config file. Yes, I am an hero.
|
52
|
+
# This is the heart of the operation. It reads the config file into
|
53
|
+
# an anonymous object of anonymous class (what kind of trickery...).
|
54
|
+
# From there, it saves that object into this configuration object
|
55
|
+
# that we're in, also passing a reference to itself into the object,
|
56
|
+
# so that we can continue our trickery from there.
|
57
|
+
unless @extras
|
58
|
+
@extras = Class.new.new
|
59
|
+
@extras.instance_variable_set '@__p__', self
|
60
|
+
end
|
61
|
+
# Now that we have an object, we can use it to store any user-defined
|
62
|
+
# functions in the config, and have it forward to us anything that it
|
63
|
+
# isn't aware of (i.e., all of those pesky command definitions).
|
64
|
+
@extras.class.send(:define_method, :method_missing) do |s, *a, &b|
|
65
|
+
begin
|
66
|
+
@__p__.send(s, *a, &b)
|
67
|
+
rescue e
|
68
|
+
super
|
69
|
+
end
|
70
|
+
end
|
71
|
+
# Finally, we can read in the configuration file.
|
72
|
+
@extras.instance_eval(f.read)
|
73
|
+
end
|
74
|
+
rescue Errno::ENOENT => e
|
75
|
+
end
|
76
|
+
# If the user has configured an API, try to require it in.
|
77
|
+
# Otherwise, use the default acoustics API.
|
78
|
+
if @api
|
79
|
+
begin
|
80
|
+
require @api
|
81
|
+
rescue
|
82
|
+
puts "Couldn't load API #{@api}"
|
83
|
+
exit
|
84
|
+
end
|
85
|
+
else
|
86
|
+
require 'ampt_api/acoustics'
|
87
|
+
end
|
88
|
+
# Include that API in the command class.
|
89
|
+
Command.class_eval do
|
90
|
+
include Ampt::API
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Select an API to use. Defaults to the acoustics API. The path should
|
95
|
+
# be an absolute path.
|
96
|
+
def api path
|
97
|
+
@api = path
|
98
|
+
end
|
99
|
+
|
100
|
+
# Alias a command. Takes a hash of 'new command' => 'previous command'.
|
101
|
+
def alias_cmd cmd
|
102
|
+
@alias.merge! cmd
|
103
|
+
end
|
104
|
+
|
105
|
+
# Set as the default command to run if Ampt is run with no arguments.
|
106
|
+
def default cmd
|
107
|
+
@default = cmd
|
108
|
+
end
|
109
|
+
|
110
|
+
# Define a new Ampt command. Must specify a name, and a block containing
|
111
|
+
# the configuration for the command.
|
112
|
+
# The configuration should contain one or more desc and opt's, plus an on_run
|
113
|
+
# block. See Ampt::Command for the on_run block.
|
114
|
+
def command name, &b
|
115
|
+
# If the command already exists, reconfigure it. Else, create a new
|
116
|
+
# instance of Ampt::Command.
|
117
|
+
if @commands[name]
|
118
|
+
@commands[name].reconfig &b
|
119
|
+
else
|
120
|
+
@commands[name] = Ampt::Command.new self, name, &b
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Run the config. Usually not called manually.
|
125
|
+
def run args
|
126
|
+
Trollop::options args, [@commands, @alias] do |cmds, *args|
|
127
|
+
commands, aliases = cmds
|
128
|
+
banner "Usage ampt [command] [options] [args]"
|
129
|
+
banner "\nCommands include:\n"
|
130
|
+
# Print out a nice listing of each command
|
131
|
+
commands.each do |name, command|
|
132
|
+
banner sprintf(" %-16.16s %s", name, command.desc)
|
133
|
+
end
|
134
|
+
banner ""
|
135
|
+
version "ampt version #{Ampt::VERSION}"
|
136
|
+
# Stop parsing if we reach a command
|
137
|
+
stop_on(commands.keys + aliases.keys)
|
138
|
+
end
|
139
|
+
cmd = ARGV.shift
|
140
|
+
# Try to figure out if it's an alias or a command or nothing or invalid
|
141
|
+
if @commands[cmd]
|
142
|
+
@commands[cmd].run(ARGV)
|
143
|
+
elsif @alias[cmd]
|
144
|
+
until @alias[cmd].nil?
|
145
|
+
cmd = @alias[cmd]
|
146
|
+
end
|
147
|
+
@commands[cmd].run(ARGV)
|
148
|
+
elsif cmd.nil? and not @default.nil?
|
149
|
+
@commands[@default].run(ARGV)
|
150
|
+
else
|
151
|
+
Trollop::die "#{cmd} is not an ampt command"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
# Magic
|
157
|
+
def cloaker &b
|
158
|
+
(class << self; self; end).class_eval do
|
159
|
+
define_method :cloaker_, &b
|
160
|
+
meth = instance_method :cloaker_
|
161
|
+
remove_method :cloaker_
|
162
|
+
meth
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Class that is created to hold each Ampt command.
|
168
|
+
class Command
|
169
|
+
include Ampt::API
|
170
|
+
|
171
|
+
# Create a new command. This is usually not called directly; it is invoked by
|
172
|
+
# Ampt::Config::on_run.
|
173
|
+
def initialize config, name, &b
|
174
|
+
@config = config
|
175
|
+
@name = name
|
176
|
+
@desc = []
|
177
|
+
@arg = ['']
|
178
|
+
@opts = []
|
179
|
+
@conflicts = []
|
180
|
+
@depends = []
|
181
|
+
@on_run = lambda {}
|
182
|
+
cloaker(&b).bind(self).call
|
183
|
+
end
|
184
|
+
|
185
|
+
def method_missing s, *a, &b
|
186
|
+
begin
|
187
|
+
@config.extras(s, *a, &b)
|
188
|
+
rescue
|
189
|
+
super
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Specify that a command takes an argument without flags. This is only used
|
194
|
+
# for nicely formatting the help command.
|
195
|
+
def arg str
|
196
|
+
@arg << '[' + str + ']'
|
197
|
+
end
|
198
|
+
|
199
|
+
# Called if you define a command twice in a configuration file. It will
|
200
|
+
# reconfigure the command. This is usually not called manually.
|
201
|
+
def reconfig &b
|
202
|
+
cloaker(&b).bind(self).call
|
203
|
+
end
|
204
|
+
|
205
|
+
# Specify that a command takes an opt with the given name and description,
|
206
|
+
# and possibly some options. See Trollop::Opt.
|
207
|
+
def opt name, desc, options = {}
|
208
|
+
@opts << [name, desc, options]
|
209
|
+
end
|
210
|
+
|
211
|
+
# Specify that one or more options conflict with each other.
|
212
|
+
def conflicts *opts
|
213
|
+
@conflicts << opts
|
214
|
+
end
|
215
|
+
|
216
|
+
# Specify that one or more options depend on each other.
|
217
|
+
def depends *opts
|
218
|
+
@depends << opts
|
219
|
+
end
|
220
|
+
|
221
|
+
# Specify the behavior of the command when it is executed. Takes a block.
|
222
|
+
def on_run &b
|
223
|
+
@on_run = b
|
224
|
+
end
|
225
|
+
|
226
|
+
# If for any reason you need Ampt to quit (error, invalid args), call this.
|
227
|
+
def die message
|
228
|
+
Trollop::die "ampt #{@name}: #{message}"
|
229
|
+
end
|
230
|
+
|
231
|
+
# Executed when a command is run. Usually not called manually.
|
232
|
+
def run args
|
233
|
+
opts = Trollop::options args,
|
234
|
+
[@name, @desc, @arg, @opts, @conflicts, @depends] do |params, *args|
|
235
|
+
name, description, arg, opts, conflicts, depends = params
|
236
|
+
banner "ampt #{name}#{arg.join(' ')}: #{description.join " "}"
|
237
|
+
banner ""
|
238
|
+
# Parse each of the specified opts
|
239
|
+
opts.each do |o|
|
240
|
+
name, desc, options = o
|
241
|
+
opt name, desc, options
|
242
|
+
end
|
243
|
+
# Parse each of the conflicts
|
244
|
+
conflicts.each do |c|
|
245
|
+
conflicts(*c)
|
246
|
+
end
|
247
|
+
# Parse each of the depends'
|
248
|
+
depends.each do |d|
|
249
|
+
depends(*d)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
# Call the on_run block with the options and arguments.
|
253
|
+
@on_run.call(opts, ARGV)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Provide a description for the command. If called with no arguments,
|
257
|
+
# returns the current description.
|
258
|
+
# Can be called multiple times, and will append the the previous call.
|
259
|
+
# Specify :overwrite => true if you want to overwrite all of the previous
|
260
|
+
# text.
|
261
|
+
def desc description = nil, options = {}
|
262
|
+
if description
|
263
|
+
if options[:overwrite]
|
264
|
+
@desc = [description]
|
265
|
+
else
|
266
|
+
@desc << description
|
267
|
+
end
|
268
|
+
else
|
269
|
+
@desc
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
# Magic
|
275
|
+
def cloaker &b
|
276
|
+
(class << self; self; end).class_eval do
|
277
|
+
define_method :cloaker_, &b
|
278
|
+
meth = instance_method :cloaker_
|
279
|
+
remove_method :cloaker_
|
280
|
+
meth
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
class Song
|
285
|
+
# Create from json
|
286
|
+
def self.json_create o
|
287
|
+
new o['data'] if o['data']
|
288
|
+
end
|
289
|
+
# Serialize to json
|
290
|
+
def to_json(*a)
|
291
|
+
{
|
292
|
+
'json_class' => self.class.name,
|
293
|
+
'data' => [self.instance_variables.reduce({}) do |d,v|
|
294
|
+
d[v[1..-1]] = self.instance_variable_get v
|
295
|
+
d
|
296
|
+
end]
|
297
|
+
}.to_json(*a)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Takes a hash to set up the object
|
301
|
+
def initialize h
|
302
|
+
h.each do |k,v|
|
303
|
+
(class << self; self; end).instance_eval do
|
304
|
+
attr_reader k
|
305
|
+
end
|
306
|
+
instance_variable_set '@'+k, v
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
class Status
|
312
|
+
attr_reader :playlist, :now_playing, :who
|
313
|
+
def self.json_create(o)
|
314
|
+
o = o['data']
|
315
|
+
playlist = if o['playlist']
|
316
|
+
playlist = o['playlist'].collect do |s|
|
317
|
+
Song.json_create('data' => s)
|
318
|
+
end
|
319
|
+
else
|
320
|
+
[]
|
321
|
+
end
|
322
|
+
now_playing= Song.json_create('data' => o['now_playing'])
|
323
|
+
new o['player'], o['can_skip'], o['who'], now_playing, playlist
|
324
|
+
end
|
325
|
+
|
326
|
+
def initialize player, can_skip, whoami, now_playing, playlist
|
327
|
+
if player
|
328
|
+
player.each do |k,v|
|
329
|
+
meta_eval do
|
330
|
+
attr_reader k
|
331
|
+
end
|
332
|
+
instance_variable_set '@'+k, v
|
333
|
+
end
|
334
|
+
end
|
335
|
+
@can_skip = can_skip
|
336
|
+
@who = whoami
|
337
|
+
@playlist = playlist
|
338
|
+
@now_playing = now_playing
|
339
|
+
end
|
340
|
+
|
341
|
+
def can_skip?
|
342
|
+
@can_skip
|
343
|
+
end
|
344
|
+
|
345
|
+
private
|
346
|
+
def metaclass
|
347
|
+
(class << self; self; end)
|
348
|
+
end
|
349
|
+
def meta_eval &b
|
350
|
+
metaclass.instance_eval &b
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'curb'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Ampt
|
6
|
+
# Provides the API for Acoustics
|
7
|
+
module API
|
8
|
+
# The base url which all of our requests will be relative to.
|
9
|
+
BASEURL = "https://www-s.acm.uiuc.edu/acoustics"
|
10
|
+
|
11
|
+
# Request the player's status. gets the session first so we can tell
|
12
|
+
# whether or not we can skip the current song, which songs are ours, etc.
|
13
|
+
def status
|
14
|
+
auth do
|
15
|
+
player_status req
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Unvotes a song. Takes a song_id (string)
|
20
|
+
def unvote song_id
|
21
|
+
auth do
|
22
|
+
player_status req 'unvote', 'song_id' => song_id
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Votes up a song. Takes a song_id (string)
|
27
|
+
def vote song_id
|
28
|
+
auth do
|
29
|
+
player_status req 'vote', 'song_id' => song_id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Searches the database for the specified value in the given field.
|
34
|
+
def search value, field = 'any'
|
35
|
+
song_list req 'search', {'field' => field, 'value' => value}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Clears all of your votes.
|
39
|
+
def reset
|
40
|
+
player_status unvote '0'
|
41
|
+
end
|
42
|
+
|
43
|
+
# Gets the most recently added songs. Takes a string count.
|
44
|
+
def recent cnt = '50'
|
45
|
+
song_list req 'recent', 'amount' => cnt || '50'
|
46
|
+
end
|
47
|
+
|
48
|
+
# Gets the most recently played songs. Takes a string count.
|
49
|
+
def history cnt = '25'
|
50
|
+
song_list req 'history', 'amount' => cnt || '25'
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sets the volume to the specified level. level should be a string.
|
54
|
+
def volume level
|
55
|
+
auth do
|
56
|
+
player_status req 'volume', 'value' => level
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Start the player.
|
61
|
+
def start
|
62
|
+
auth do
|
63
|
+
player_status req 'start'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Skip the current song. Will probably fail horribly if you aren't allowed to skip.
|
68
|
+
def skip
|
69
|
+
auth do
|
70
|
+
player_status req 'skip'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Stop the player.
|
75
|
+
def stop
|
76
|
+
auth do
|
77
|
+
player_status req 'stop'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# takes field => value pair of what to list. Only one of the given pairs is used.
|
82
|
+
def list options
|
83
|
+
field = options.keys[0]
|
84
|
+
value = options[field]
|
85
|
+
song_list req 'select', {'field' => field, 'value' => value}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns amt random songs.
|
89
|
+
def random amt='20'
|
90
|
+
song_list req 'random', 'amount' => amt || '20'
|
91
|
+
end
|
92
|
+
|
93
|
+
# Performs a request with given mode and params.
|
94
|
+
def req mode=nil, params = nil
|
95
|
+
c = Curl::Easy.new
|
96
|
+
c.cookies = @sess
|
97
|
+
if params
|
98
|
+
param_str = params.collect do |key, value|
|
99
|
+
if value.instance_of? Array
|
100
|
+
value.collect {|v| "#{URI::escape key}=#{URI::escape v}"}.join(';')
|
101
|
+
else
|
102
|
+
"#{URI::escape key}=#{URI::escape value}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
c.url = "#{BASEURL}/json.pl?mode=#{mode};" + param_str.join(';')
|
106
|
+
elsif mode
|
107
|
+
c.url = "#{BASEURL}/json.pl?mode=#{mode};"
|
108
|
+
else
|
109
|
+
c.url = "#{BASEURL}/json.pl"
|
110
|
+
end
|
111
|
+
c.perform
|
112
|
+
c.body_str
|
113
|
+
end
|
114
|
+
|
115
|
+
def song_list json
|
116
|
+
begin
|
117
|
+
JSON.parse(json).collect do |s|
|
118
|
+
Ampt::Song.json_create('data' => s)
|
119
|
+
end
|
120
|
+
rescue JSON::ParserError => e
|
121
|
+
puts "Got invalid JSON!"
|
122
|
+
p json
|
123
|
+
abort
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def player_status json
|
128
|
+
begin
|
129
|
+
JSON.parse "{\"json_class\": \"Ampt::Status\", \"data\": " + json + "}"
|
130
|
+
rescue JSON::ParserError => e
|
131
|
+
puts "Got invalid JSON!"
|
132
|
+
p json
|
133
|
+
abort
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Perform an authenticated request
|
138
|
+
def auth &b
|
139
|
+
unless @sess
|
140
|
+
c = Curl::Easy.new
|
141
|
+
c.http_auth_types = Curl::CURLAUTH_GSSNEGOTIATE
|
142
|
+
c.userpwd = ":"
|
143
|
+
c.url = "#{BASEURL}/www-data/auth/"
|
144
|
+
c.perform
|
145
|
+
sessid = c.header_str[/CGISESSID=(.+);/]
|
146
|
+
if sessid.nil?
|
147
|
+
puts "Could not authenticate -- maybe you should kinit?"
|
148
|
+
abort
|
149
|
+
end
|
150
|
+
@sess = sessid.chop
|
151
|
+
end
|
152
|
+
(yield) if b
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,423 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
def colorize(text, color_code)
|
4
|
+
if @no_colorize
|
5
|
+
text
|
6
|
+
else
|
7
|
+
"#{color_code}#{text}\e[0m"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def red(text); colorize(text, "\e[31m"); end
|
12
|
+
def green(text); colorize(text, "\e[32m"); end
|
13
|
+
def bold(text); colorize(text, "\e[1m"); end
|
14
|
+
def under(text); colorize(text, "\e[4m"); end
|
15
|
+
def bggreen(text); colorize(text, "\e[42m"); end
|
16
|
+
|
17
|
+
def pp_song song, options={:title => true, :artist => true, :album => false, :track => false}
|
18
|
+
song_id = song.song_id
|
19
|
+
if song_id.nil?
|
20
|
+
puts " Nothing."
|
21
|
+
raise "Not playing."
|
22
|
+
end
|
23
|
+
artist = song.artist
|
24
|
+
path = song.path
|
25
|
+
title = song.title
|
26
|
+
title = path.split('/')[-1].rpartition('.')[0] if title.empty?
|
27
|
+
track = song.track
|
28
|
+
album = song.album
|
29
|
+
who = if song.respond_to? :who
|
30
|
+
song.who
|
31
|
+
else
|
32
|
+
[]
|
33
|
+
end
|
34
|
+
str = ["[#{green(song_id.rjust(7))}]"]
|
35
|
+
if options[:track]
|
36
|
+
str <<= "#{track.rjust(3)}."
|
37
|
+
end
|
38
|
+
if options[:title]
|
39
|
+
str <<= "#{under(title[0...40].strip)}"
|
40
|
+
end
|
41
|
+
if options[:artist] and not artist.empty?
|
42
|
+
str <<= "by #{(artist[0..20].strip)}"
|
43
|
+
end
|
44
|
+
if options[:album] and not album.empty?
|
45
|
+
str <<= "on #{(album[0..20].strip)}"
|
46
|
+
end
|
47
|
+
unless who.empty?
|
48
|
+
str <<= "\nVoted up by"
|
49
|
+
if who.size > 1 and (not options[:show_all_voters])
|
50
|
+
str <<= "#{who[0]} (and #{who.length - 1} other#{'s' if who.length > 2})"
|
51
|
+
elsif who.size > 1 and options[:show_all_voters]
|
52
|
+
str <<= who.join(', ')
|
53
|
+
else
|
54
|
+
str <<= who[0]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
str.join(' ')
|
58
|
+
end
|
59
|
+
|
60
|
+
def pp_songs songs
|
61
|
+
songs.each do |song|
|
62
|
+
pp_song(song).lines.each {|l| puts " " + l}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def songs_to_file songs, file
|
67
|
+
songs.uniq!
|
68
|
+
s = [sprintf("#\t%7s %3s %20s %20s %20s", 'song_id', '#', 'title', 'artist',
|
69
|
+
'album')]
|
70
|
+
s += songs.collect do |song|
|
71
|
+
sprintf("#\t%7s %3s %20.20s %20.20s %20.20s", song.song_id, song.track,
|
72
|
+
song.title, song.artist, song.album)
|
73
|
+
end
|
74
|
+
file.write(s.join("\n"))
|
75
|
+
end
|
76
|
+
|
77
|
+
def pp_songs_full songs, options = {}
|
78
|
+
opts = {:title => true, :artist => true, :album => true, :track => true}.merge(options)
|
79
|
+
songs.each do |song|
|
80
|
+
pp_song(song, opts).lines.each {|l| puts " " + l}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def pp_time len
|
85
|
+
len = len.to_i
|
86
|
+
str = if len > 3600
|
87
|
+
format("%d:%02d", len/3600, (len/60)%60)
|
88
|
+
else
|
89
|
+
(len / 60).to_s
|
90
|
+
end
|
91
|
+
str << format(":%02d", len % 60)
|
92
|
+
end
|
93
|
+
|
94
|
+
alias_cmd 'st' => 'status'
|
95
|
+
default 'status'
|
96
|
+
|
97
|
+
command 'status' do
|
98
|
+
desc "Prints the currently playing song and playlist."
|
99
|
+
opt :colorize, "Colorize the output", :short => '-c', :default => true
|
100
|
+
opt :no_colorize, "Disable color"
|
101
|
+
on_run do |opts, args|
|
102
|
+
if opts[:no_colorize]
|
103
|
+
@no_colorize = true
|
104
|
+
end
|
105
|
+
st = status
|
106
|
+
puts bold("Now Playing:") + "\n"
|
107
|
+
begin
|
108
|
+
pp_song(st.now_playing).lines.each {|l| puts " " + l}
|
109
|
+
rescue => e
|
110
|
+
puts " Ampt isn't currently playing anything."
|
111
|
+
end
|
112
|
+
unless st.playlist.empty?
|
113
|
+
puts bold("\nPlaylist:")
|
114
|
+
pp_songs st.playlist
|
115
|
+
totaltime = st.playlist.reduce(0) do |t, s|
|
116
|
+
t + s.length.to_i
|
117
|
+
end
|
118
|
+
print "\n #{st.playlist.size} songs queued for a total time of "
|
119
|
+
puts pp_time totaltime
|
120
|
+
me = st.who
|
121
|
+
mine = st.playlist.select do |s|
|
122
|
+
s.who.include? me
|
123
|
+
end
|
124
|
+
mytime = mine.reduce(0) do |t, s|
|
125
|
+
t + s.length.to_i
|
126
|
+
end
|
127
|
+
print " You have #{mine.size} songs queued for a total time of "
|
128
|
+
puts pp_time mytime
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
alias_cmd 'rm' => 'remove', 'unvote' => 'remove'
|
134
|
+
command 'remove' do
|
135
|
+
desc 'Remove your votes from songs in the playlist. Provide a song id ' +
|
136
|
+
'or one of the flags.'
|
137
|
+
arg 'song_id'
|
138
|
+
opt :all, "Remove all of your queued songs (identical to ampt clear)", :short => '-a'
|
139
|
+
opt :interactive, "Interactively remove songs", :short => '-i'
|
140
|
+
conflicts :all, :interactive
|
141
|
+
on_run do |opts, args|
|
142
|
+
if (opts[:all] || opts[:interactive]) && (not args.empty?)
|
143
|
+
die "Unknown argument #{args.join(' ')}"
|
144
|
+
end
|
145
|
+
if opts[:all]
|
146
|
+
reset
|
147
|
+
elsif opts[:interactive]
|
148
|
+
st = status
|
149
|
+
me = st.who
|
150
|
+
list = st.playlist.select do |s|
|
151
|
+
s.who.include? me
|
152
|
+
end
|
153
|
+
s = []
|
154
|
+
Tempfile.open("ampt") do |tempfile|
|
155
|
+
songs_to_file(list, tempfile)
|
156
|
+
tempfile.fsync
|
157
|
+
tempfile.flush
|
158
|
+
path = tempfile.path
|
159
|
+
tempfile.close
|
160
|
+
system(ENV['EDITOR'], path)
|
161
|
+
File.open(path) do |f|
|
162
|
+
s = f.readlines
|
163
|
+
end
|
164
|
+
end
|
165
|
+
arr = s.inject([]) do |a,line|
|
166
|
+
a + ((line.scan(/\s(\d+)\s/)[0] unless(line =~ /^\#/)) || [])
|
167
|
+
end
|
168
|
+
unless arr.empty?
|
169
|
+
arr.each_slice(40) do |a|
|
170
|
+
unvote a
|
171
|
+
end
|
172
|
+
end
|
173
|
+
else
|
174
|
+
unvote args
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
alias_cmd 'up' => 'add'
|
180
|
+
alias_cmd 'upvote' => 'add'
|
181
|
+
command 'add' do
|
182
|
+
desc "Vote for a song. Provide a song id, or one of the flags."
|
183
|
+
arg 'song_id'
|
184
|
+
opt :artist, "Interactively add songs by artist", :type => :string, :multi => true
|
185
|
+
opt :album, "Interactively add songs by album", :type => :string, :multi => true
|
186
|
+
opt :title, "Interactively add songs by title", :type => :string, :multi => true
|
187
|
+
opt :search, "Interactively add songs, searching across all fields", :type => :string, :multi => true
|
188
|
+
opt :randomize, "Randomize order of newly queued songs", :short => '-r'
|
189
|
+
conflicts :search, :artist, :album, :title
|
190
|
+
on_run do |opts, args|
|
191
|
+
if(opts[:artist_given]||opts[:album_given]||opts[:title_given]||opts[:search_given])&&(not args.empty?)
|
192
|
+
die "Unknown argument #{args.join(' ')}"
|
193
|
+
end
|
194
|
+
if opts[:artist_given] || opts[:album_given] || opts[:title_given] || opts[:search_given]
|
195
|
+
randomize = opts.delete(:randomize)
|
196
|
+
list_of = {}
|
197
|
+
list_of[:artist] = opts[:artist]
|
198
|
+
list_of[:album] = opts[:album]
|
199
|
+
list_of[:title] = opts[:title]
|
200
|
+
list_of[:search] = opts[:search]
|
201
|
+
list = if opts[:search_given]
|
202
|
+
list_of.collect do |k,v|
|
203
|
+
v.collect do |v|
|
204
|
+
search(v)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
else
|
208
|
+
list_of.collect do |k,v|
|
209
|
+
v.collect do |v|
|
210
|
+
list k.to_s => v
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
list = list.flatten
|
215
|
+
s = []
|
216
|
+
Tempfile.open("ampt") do |tempfile|
|
217
|
+
songs_to_file(list, tempfile)
|
218
|
+
tempfile.fsync
|
219
|
+
tempfile.flush
|
220
|
+
path = tempfile.path
|
221
|
+
tempfile.close
|
222
|
+
system(ENV['EDITOR'], path)
|
223
|
+
File.open(path) do |f|
|
224
|
+
s = f.readlines
|
225
|
+
end
|
226
|
+
end
|
227
|
+
arr = s.inject([]) do |a,line|
|
228
|
+
a + ((line.scan(/\s(\d+)\s/)[0] unless(line =~ /^\#/)) || [])
|
229
|
+
end
|
230
|
+
|
231
|
+
unless arr.empty?
|
232
|
+
if randomize
|
233
|
+
arr = arr.shuffle
|
234
|
+
end
|
235
|
+
arr.each_slice(40) do |a|
|
236
|
+
vote a
|
237
|
+
end
|
238
|
+
end
|
239
|
+
else
|
240
|
+
vote args
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
command 'search' do
|
246
|
+
desc 'Search the database for songs. Defaults to searching by any parameter.'
|
247
|
+
arg 'query'
|
248
|
+
opt :artist, "Search by artist", :type => :string
|
249
|
+
opt :album, "Search by album", :type => :string
|
250
|
+
opt :title, "Search by title", :type => :string
|
251
|
+
opt :path, "Search by path", :type => :string
|
252
|
+
opt :any, "Search by any parameter. This is the default", :type => :string
|
253
|
+
opt :colorize, "Colorize the output", :short => '-c', :default => true
|
254
|
+
opt :no_colorize, "Disable color"
|
255
|
+
conflicts :artist, :album, :title, :path, :any
|
256
|
+
on_run do |opts, args|
|
257
|
+
if opts[:no_colorize]
|
258
|
+
@no_colorize = true
|
259
|
+
end
|
260
|
+
if opts[:artist_given]||opts[:album_given]||opts[:title_given]
|
261
|
+
list_of = {}
|
262
|
+
list_of[:artist] = opts[:artist]
|
263
|
+
list_of[:album] = opts[:album]
|
264
|
+
list_of[:title] = opts[:title]
|
265
|
+
list_of[:path] = opts[:path]
|
266
|
+
list_of[:any] = opts[:any]
|
267
|
+
list_of.each do |k,v|
|
268
|
+
next if v.nil?
|
269
|
+
puts "Results for #{k.to_s} #{bold(v)}:"
|
270
|
+
pp_songs_full search(v,k.to_s)
|
271
|
+
end
|
272
|
+
else
|
273
|
+
puts "Results for any #{bold(args.join(' '))}:"
|
274
|
+
pp_songs_full search(args)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
alias_cmd 'clear' => 'reset'
|
280
|
+
command 'reset' do
|
281
|
+
desc "Clear all of your votes."
|
282
|
+
on_run do |opts, args|
|
283
|
+
reset
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
command 'start' do
|
288
|
+
desc "Start Acoustics."
|
289
|
+
on_run do |opts, args|
|
290
|
+
start
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
command 'stop' do
|
295
|
+
desc "Stop Acoustics."
|
296
|
+
on_run do |opts, args|
|
297
|
+
stop
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
command 'skip' do
|
302
|
+
desc "Skip the currently playing song."
|
303
|
+
on_run do |opts, args|
|
304
|
+
st = status
|
305
|
+
if st.can_skip?
|
306
|
+
skip
|
307
|
+
else
|
308
|
+
puts "Not allowed to skip!"
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
alias_cmd 'vol' => 'volume'
|
314
|
+
command 'volume' do
|
315
|
+
desc 'Set the current volume level. If run without an arg, returns ' +
|
316
|
+
"the current volume."
|
317
|
+
arg 'num'
|
318
|
+
on_run do |opts,args|
|
319
|
+
if args[0]
|
320
|
+
volume args[0]
|
321
|
+
else
|
322
|
+
st = status
|
323
|
+
puts "Volume is currently set to #{(st.volume)}."
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
alias_cmd 'ls' => 'list'
|
329
|
+
command 'list' do
|
330
|
+
desc 'List all songs with the specified parameter. This is ' +
|
331
|
+
'less fuzzy than search. One of the flags must be specified.'
|
332
|
+
opt :artist, "List by artist", :type => :string
|
333
|
+
opt :album, "List by album", :type => :string
|
334
|
+
opt :title, "List by title", :type => :string
|
335
|
+
opt :colorize, "Colorize the output", :short => '-c', :default => true
|
336
|
+
opt :no_colorize, "Disable color"
|
337
|
+
conflicts :artist, :album, :title
|
338
|
+
on_run do |opts, args|
|
339
|
+
if opts[:no_colorize]
|
340
|
+
@no_colorize = true
|
341
|
+
end
|
342
|
+
if not (opts[:artist_given]||opts[:album_given]||opts[:title_given])
|
343
|
+
die "You must specify one of the options"
|
344
|
+
else
|
345
|
+
list_of = {}
|
346
|
+
list_of[:artist] = opts[:artist]
|
347
|
+
list_of[:album] = opts[:album]
|
348
|
+
list_of[:title] = opts[:title]
|
349
|
+
list_of.each do |k,v|
|
350
|
+
next if v.nil?
|
351
|
+
puts "Results for #{k.to_s} #{bold(v)}:"
|
352
|
+
pp_songs_full list(k.to_s => v)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
command 'show' do
|
359
|
+
desc 'Show more info about a song. Takes a song id as the single ' +
|
360
|
+
'parameter.'
|
361
|
+
opt :colorize, "Colorize the output", :short => '-c', :default => true
|
362
|
+
opt :no_colorize, "Disable color"
|
363
|
+
arg 'song_id'
|
364
|
+
on_run do |opts,args|
|
365
|
+
if opts[:no_colorize]
|
366
|
+
@no_colorize = true
|
367
|
+
end
|
368
|
+
(die "Not enough arguments") unless args[0]
|
369
|
+
song_id = args[0]
|
370
|
+
song = (list 'song_id' => song_id)[0]
|
371
|
+
puts <<-SONGINFO
|
372
|
+
#{under(song.title)} by #{song.artist}
|
373
|
+
Track #{song.track} on album #{song.album}
|
374
|
+
#{song.path}
|
375
|
+
SONGINFO
|
376
|
+
len = song.length.to_i
|
377
|
+
print " "
|
378
|
+
puts pp_time len
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
alias_cmd 'rand' => 'random'
|
383
|
+
command 'random' do
|
384
|
+
desc 'Get [num] random songs. Defaults to 20.'
|
385
|
+
opt :colorize, "Colorize the output", :short => '-c', :default => true
|
386
|
+
opt :no_colorize, "Disable color"
|
387
|
+
arg 'num'
|
388
|
+
on_run do |opts,args|
|
389
|
+
if opts[:no_colorize]
|
390
|
+
@no_colorize = true
|
391
|
+
end
|
392
|
+
puts "#{args[0]||20} random songs:"
|
393
|
+
pp_songs_full random args[0]
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
command 'new' do
|
398
|
+
desc 'Get [num] newest songs. Defaults to 50.'
|
399
|
+
opt :colorize, "Colorize the output", :short => '-c', :default => true
|
400
|
+
opt :no_colorize, "Disable color"
|
401
|
+
arg 'num'
|
402
|
+
on_run do |opts,args|
|
403
|
+
if opts[:no_colorize]
|
404
|
+
@no_colorize = true
|
405
|
+
end
|
406
|
+
puts "#{args[0]||'50'} newest songs:"
|
407
|
+
pp_songs_full recent args[0]
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
command 'history' do
|
412
|
+
desc 'Get [num] last-played songs. Defaults to 25.'
|
413
|
+
opt :colorize, "Colorize the output", :short => '-c', :default => true
|
414
|
+
opt :no_colorize, "Disable color"
|
415
|
+
arg 'num'
|
416
|
+
on_run do |opts,args|
|
417
|
+
if opts[:no_colorize]
|
418
|
+
@no_colorize = true
|
419
|
+
end
|
420
|
+
puts "#{args[0]||'25'} last-played:"
|
421
|
+
pp_songs_full history(args[0]), :show_all_voters => true
|
422
|
+
end
|
423
|
+
end
|
data/test/helper.rb
ADDED
data/test/test_ampt.rb
ADDED
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ampt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0.pre1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rich
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-08 00:00:00 -06:00
|
13
|
+
default_executable: ampt
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: |-
|
17
|
+
This is a command line client for the Acoustics Media Player (amp).
|
18
|
+
Amp is available here: http://www.github.com/avuserow/amp
|
19
|
+
|
20
|
+
This is a work in progress.
|
21
|
+
email: rich@interhacktive.com
|
22
|
+
executables:
|
23
|
+
- ampt
|
24
|
+
extensions: []
|
25
|
+
|
26
|
+
extra_rdoc_files:
|
27
|
+
- LICENSE
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- .autotest
|
31
|
+
- .document
|
32
|
+
- .gitignore
|
33
|
+
- LICENSE
|
34
|
+
- README.rdoc
|
35
|
+
- Rakefile
|
36
|
+
- ampt.gemspec
|
37
|
+
- bin/ampt
|
38
|
+
- lib/ampt.rb
|
39
|
+
- lib/ampt_api/acoustics.rb
|
40
|
+
- lib/ampt_cfg/default.rb
|
41
|
+
- test/helper.rb
|
42
|
+
- test/test_ampt.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/capslock/ampt
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --charset=UTF-8
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 1.3.1
|
63
|
+
version:
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.3.5
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Ampt is a command line client for the Acoustics Media Player (amp).
|
71
|
+
test_files:
|
72
|
+
- test/helper.rb
|
73
|
+
- test/test_ampt.rb
|