ampt 0.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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