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 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
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require 'ampt'
3
+
4
+ # You CAN specify configs over here... or you can just load them from your .amptrc.
5
+ Ampt.init do
6
+ load_config "#{ENV['HOME']}/.amptrc"
7
+ load_config 'ampt_cfg/default.rb'
8
+ end
9
+
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
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'ampt'
7
+
8
+ class Test::Unit::TestCase
9
+ end
data/test/test_ampt.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestAmpt < Test::Unit::TestCase
4
+ def test_something_for_real
5
+ #flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
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