ampt 0.2.0.pre1
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/.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
|