cultome_player 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +24 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +325 -0
  9. data/Rakefile +8 -0
  10. data/bin/cultome_player +39 -0
  11. data/config/environment.yml +28 -0
  12. data/cultome_player.gemspec +35 -0
  13. data/db/001_create_schema.rb +58 -0
  14. data/lib/cultome_player.rb +107 -0
  15. data/lib/cultome_player/command.rb +11 -0
  16. data/lib/cultome_player/command/language.rb +61 -0
  17. data/lib/cultome_player/command/processor.rb +165 -0
  18. data/lib/cultome_player/command/reader.rb +86 -0
  19. data/lib/cultome_player/environment.rb +130 -0
  20. data/lib/cultome_player/events.rb +29 -0
  21. data/lib/cultome_player/media.rb +47 -0
  22. data/lib/cultome_player/objects.rb +15 -0
  23. data/lib/cultome_player/objects/album.rb +21 -0
  24. data/lib/cultome_player/objects/artist.rb +18 -0
  25. data/lib/cultome_player/objects/command.rb +37 -0
  26. data/lib/cultome_player/objects/drive.rb +26 -0
  27. data/lib/cultome_player/objects/genre.rb +16 -0
  28. data/lib/cultome_player/objects/parameter.rb +37 -0
  29. data/lib/cultome_player/objects/response.rb +42 -0
  30. data/lib/cultome_player/objects/song.rb +38 -0
  31. data/lib/cultome_player/player.rb +13 -0
  32. data/lib/cultome_player/player/adapter.rb +14 -0
  33. data/lib/cultome_player/player/adapter/mpg123.rb +143 -0
  34. data/lib/cultome_player/player/interactive.rb +56 -0
  35. data/lib/cultome_player/player/interface.rb +13 -0
  36. data/lib/cultome_player/player/interface/basic.rb +96 -0
  37. data/lib/cultome_player/player/interface/builtin_help.rb +368 -0
  38. data/lib/cultome_player/player/interface/extended.rb +199 -0
  39. data/lib/cultome_player/player/interface/helper.rb +300 -0
  40. data/lib/cultome_player/player/playlist.rb +280 -0
  41. data/lib/cultome_player/plugins.rb +23 -0
  42. data/lib/cultome_player/plugins/help.rb +58 -0
  43. data/lib/cultome_player/state_checker.rb +74 -0
  44. data/lib/cultome_player/utils.rb +95 -0
  45. data/lib/cultome_player/version.rb +3 -0
  46. data/spec/config.yml +0 -0
  47. data/spec/cultome_player/command/processor_spec.rb +168 -0
  48. data/spec/cultome_player/command/reader_spec.rb +45 -0
  49. data/spec/cultome_player/cultome_player_spec.rb +17 -0
  50. data/spec/cultome_player/environment_spec.rb +65 -0
  51. data/spec/cultome_player/events_spec.rb +22 -0
  52. data/spec/cultome_player/media_spec.rb +41 -0
  53. data/spec/cultome_player/player/adapter/mpg123_spec.rb +82 -0
  54. data/spec/cultome_player/player/interface/basic_spec.rb +168 -0
  55. data/spec/cultome_player/player/interface/extended/connect_spec.rb +117 -0
  56. data/spec/cultome_player/player/interface/extended/search_spec.rb +90 -0
  57. data/spec/cultome_player/player/interface/extended/show_spec.rb +36 -0
  58. data/spec/cultome_player/player/interface/extended/shuffle_spec.rb +26 -0
  59. data/spec/cultome_player/player/interface/extended_spec.rb +136 -0
  60. data/spec/cultome_player/player/interface/helper_spec.rb +63 -0
  61. data/spec/cultome_player/player/interface_spec.rb +17 -0
  62. data/spec/cultome_player/player/playlist_spec.rb +301 -0
  63. data/spec/cultome_player/plugins/help_spec.rb +21 -0
  64. data/spec/cultome_player/plugins_spec.rb +19 -0
  65. data/spec/cultome_player/utils_spec.rb +15 -0
  66. data/spec/spec_helper.rb +108 -0
  67. data/spec/test/uno/dos/dos.mp3 +0 -0
  68. data/spec/test/uno/dos/tres/tres.mp3 +0 -0
  69. data/spec/test/uno/uno.mp3 +0 -0
  70. data/tasks/console.rake +19 -0
  71. data/tasks/db.rake +19 -0
  72. data/tasks/run.rake +7 -0
  73. metadata +322 -0
@@ -0,0 +1,280 @@
1
+ require 'cultome_player/utils'
2
+
3
+ module CultomePlayer::Player::Playlist
4
+
5
+ # Lazy getter for playlists.
6
+ #
7
+ # @return [Playlists] The playlists handled by the system.
8
+ def playlists
9
+ @playlists ||= Playlists.new
10
+ end
11
+
12
+ # (see Playlists#registered?)
13
+ def playlist?(name)
14
+ playlists.registered?(name)
15
+ end
16
+
17
+ class Playlists
18
+ include Enumerable
19
+ include CultomePlayer::Utils
20
+
21
+ # Initialize a playlist with optional information to fill.
22
+ #
23
+ # @param data [#each] A collection of items to add to playlist
24
+ def initialize(data=nil)
25
+ @data = {}
26
+ data.each{|arr| register(*arr) } unless data.nil?
27
+ end
28
+
29
+ # Register a playlist.
30
+ #
31
+ # @param name [Symbol] The name of the new playlist.
32
+ # @param value [List<Object>] Optional data to initialize the playlist.
33
+ def register(name, value=nil)
34
+ raise 'invalid registry:playlist already registered' unless @data[name].nil?
35
+ @data[name] = value.nil? ? {list: [], idx: -1, repeat: true, shuffled: false} : value
36
+ end
37
+
38
+ # Check if a playlist is registered.
39
+ #
40
+ # @param name [Symbol] The name of the new playlist.
41
+ # @return [Boolean] True if previously registered, False otherwise.
42
+ def registered?(name)
43
+ @data.has_key?(name)
44
+ end
45
+
46
+ # Creates an interator for all the songs in all the playlists
47
+ #
48
+ # @return [Iterator] Iterator over all the songs.
49
+ def each_song
50
+ idx = 0
51
+ @data.values.each{|info| info[:list].each{|song| yield song, idx += 1 } }
52
+ end
53
+
54
+ # Creates an interator for the playlists
55
+ #
56
+ # @return [Iterator] Iterator over the playlists.
57
+ def each
58
+ @data.values.each{|info| yield info[:list] }
59
+ end
60
+
61
+ # Check if there is playlists registered.
62
+ #
63
+ # @return [Boolean] True if there is any playlist registered. False otherwise.
64
+ def empty?
65
+ return @data.values.first[:list].empty? if @data.size == 1
66
+ @data.empty?
67
+ end
68
+
69
+ # Creates a new playlist object that contains the playlists named in parameters.
70
+ #
71
+ # @return [Playlists] Playlists with selected playlists inside.
72
+ def [](*names)
73
+ validate names
74
+ selected = @data.select{|name,info| names.include?(name) }
75
+ return Playlists.new(selected)
76
+ end
77
+
78
+ # Replace the content of the playlist with the content of parameter.
79
+ #
80
+ # @param value [List<Object>] The new contents of the playlist.
81
+ def <=(value)
82
+ @data.keys.each{|name| replace(name, value) }
83
+ end
84
+
85
+ # Append the content of the playlist with the content of parameter.
86
+ #
87
+ # @param value [List<Object>] The appended of the playlist.
88
+ def <<(value)
89
+ if value.respond_to?(:each)
90
+ @data.values.each{|info| value.each{|v| info[:list] << v } }
91
+ else
92
+ @data.values.each{|info| info[:list] << value }
93
+ end
94
+ end
95
+
96
+ # Removes the last element in the playlists.
97
+ #
98
+ # @return [List<Object>, Object] The las elements in the playlists.
99
+ def pop
100
+ last_ones = collect{|list| list.pop }
101
+ return last_ones.first if last_ones.size == 1
102
+ return last_ones
103
+ end
104
+
105
+ # Shuffle the playlists and reset the indexes.
106
+ def shuffle
107
+ @data.values.each do |info|
108
+ info[:list].shuffle!
109
+ info[:shuffled] = true
110
+ info[:idx] = -1
111
+ end
112
+ end
113
+
114
+ # Order the playlists and reset the indexes.
115
+ def order
116
+ @data.values.each do |info|
117
+ info[:list].sort!
118
+ info[:idx] = -1
119
+ info[:shuffled] = false
120
+ end
121
+ end
122
+
123
+ # Returns the next song in playlist, which means the new current song.
124
+ #
125
+ # @return [List<Object>,Object] The next element(s) in playlist(s).
126
+ def next
127
+ each_next do |info, nxt_idx|
128
+ info[:idx] = nxt_idx
129
+ info[:list].at nxt_idx
130
+ end
131
+ end
132
+
133
+ # Returns the previous song in playlist.
134
+ #
135
+ # @return [List<Object>,Object] The previous element(s) in playlist(s).
136
+ def rewind_by(idx)
137
+ each_next do |info, nxt_idx|
138
+ info[:idx] -= idx
139
+ info[:list].at info[:idx]
140
+ end
141
+ end
142
+
143
+ # Remove the next element in playlist.
144
+ #
145
+ # @return [List<Object>,Object] The next element(s) in playlist(s).
146
+ def remove_next
147
+ each_next do |info, nxt_idx|
148
+ info[:list].delete_at nxt_idx
149
+ end
150
+ end
151
+
152
+ # Return the play index in the playlists.
153
+ #
154
+ # @return [List<Integer>, Integer] Indexes of the playlists.
155
+ def play_index
156
+ return first_or_map :idx
157
+ end
158
+
159
+ # Return the repeat status in the playlists.
160
+ #
161
+ # @return [List<Boolean>, Boolean] Indexes of the playlists.
162
+ def repeat?
163
+ return first_or_map :repeat
164
+ end
165
+
166
+ # Change the repeat status in the playlists.
167
+ def repeat(value)
168
+ @data.values.each{|info| info[:repeat] = is_true_value?(value) }
169
+ end
170
+
171
+ # Returns the current element in playlists.
172
+ #
173
+ # @return [List<Object>, Object] The current element(s) in playlist(s).
174
+ def current
175
+ currents = @data.values
176
+ .select{|info| info[:idx] >= 0}
177
+ .map do |info|
178
+ info[:list].at info[:idx]
179
+ end
180
+
181
+ return nil if currents.empty?
182
+ raise 'no current:no current song in one of the playlists' if @data.size != currents.size
183
+ return currents.first if currents.size == 1
184
+ return currents
185
+ end
186
+
187
+ # The number of registered playlists.
188
+ #
189
+ # @return [Integer] The size of registered playlist.
190
+ def size
191
+ @data.size
192
+ end
193
+
194
+ # Return a list with all the songs in all the playlists.
195
+ #
196
+ # @return [List<Object>] A list with all the songs in all the playlists.
197
+ def to_a
198
+ @data.values.reduce([]){|acc,info| acc + info[:list]}
199
+ end
200
+
201
+ alias :songs :to_a
202
+
203
+ # Returns the elements in the playlist.
204
+ #
205
+ # @param idx [Integer] The positional index of the element required.
206
+ # @return [List<Object>, Object] The positional elements in the playlists.
207
+ def at(idx)
208
+ return @data.values.first[:list].at(idx) if @data.size == 1
209
+ return @data.values.collect{|info| info[:list].at(idx) }
210
+ end
211
+
212
+ # Returns a string representation of the playlists.
213
+ #
214
+ # @return [String] A representation of the playlists.
215
+ def as_list
216
+ list = ""
217
+ each_song{|s,i| list << "#{i}. #{s.to_s}\n" }
218
+ return list
219
+ end
220
+
221
+ # Check if there is another element in playlists.
222
+ #
223
+ # @return [List<Boolean>, Boolean] True if the the playlist has more elements, False otherwise.
224
+ def next?
225
+ nexts = each_next_with_index{|info, nxt_idx| nxt_idx }
226
+ has_nexts = nexts.map{|nxt_idx| !nxt_idx.nil? }
227
+ return has_nexts.first if has_nexts.size == 1
228
+ return has_nexts
229
+ end
230
+
231
+ # Check the status of shuffling in playlists.
232
+ #
233
+ # @return [List<Boolean>, Boolean] True if playlist is shuffling. False otherwise.
234
+ def shuffling?
235
+ return first_or_map :shuffled
236
+ end
237
+
238
+ private
239
+
240
+ def first_or_map(attr)
241
+ return @data.values.first[attr] if @data.size == 1
242
+ return @data.values.map{|info| info[attr] }
243
+ end
244
+
245
+ # Returns the non-empty playlists yielded by the block
246
+ def each_next_with_index
247
+ @data.values
248
+ .select{|info| !info[:list].empty? }
249
+ .map do |info|
250
+ nxt_idx = next_idx(info[:idx], info[:list].size, info[:repeat])
251
+ nxt_idx.nil? ? nil : yield(info, nxt_idx)
252
+ end
253
+ end
254
+
255
+ # Returns an array with songs, if multiple playlists and only one if single playlist is available
256
+ def each_next(&block)
257
+ nexts = each_next_with_index(&block).compact
258
+ raise "playlist empty:no songs in playlists" if nexts.empty?
259
+ raise "playlist empty:no songs in one of the playlists" if nexts.size != @data.size
260
+ return nexts.first if nexts.size == 1
261
+ return nexts
262
+ end
263
+
264
+ def next_idx(actual_idx, size, repeat)
265
+ next_idx = actual_idx + 1
266
+ next_idx = next_idx % size if repeat
267
+ return nil if next_idx >= size
268
+ return next_idx
269
+ end
270
+
271
+ def replace(name, value)
272
+ @data[name][:list].replace value
273
+ @data[name][:idx] = -1
274
+ end
275
+
276
+ def validate(names)
277
+ raise 'unknown playlist:playlist is not registered' if names.any?{|n| !@data.keys.include?(n) }
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,23 @@
1
+ require 'cultome_player/plugins/help'
2
+
3
+ module CultomePlayer
4
+ module Plugins
5
+ include Help
6
+
7
+ # Check if a plugin implements the given command.
8
+ #
9
+ # @param cmd_name [String] The command name.
10
+ # @return [Boolean] True is the given command is implemented by a plugin.
11
+ def plugins_respond_to?(cmd_name)
12
+ return respond_to?("command_#{cmd_name}".to_sym)
13
+ end
14
+
15
+ # Get a command format for a command implemented by a plugin
16
+ #
17
+ # @param cmd_name [String] The command name.
18
+ # @return [Regex] The regex to validate a command format that is implemented by a plugin.
19
+ def plugin_command_sintaxis(cmd_name)
20
+ return send("sintaxis_#{cmd_name}".to_sym)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,58 @@
1
+ module CultomePlayer
2
+ module Plugins
3
+ module Help
4
+
5
+ # Command implementation for action "help".
6
+ # Shows usage information for the actions of the player if called with an action as parameter and shows a player usage information if called without parameters.
7
+ #
8
+ # @contract Plugin
9
+ # @param cmd [Command] Command information parsed from user input
10
+ # @return [Response] Contains a message to be displayed with the help required.
11
+ def command_help(cmd)
12
+ if cmd.params.empty?
13
+ success(message: usage_cultome_player)
14
+ else
15
+ help = send("usage_#{cmd.params.first.value}")
16
+ if help.nil?
17
+ failure("No help is available for '#{cmd.first.value}'.")
18
+ else
19
+ success(message: help)
20
+ end
21
+ end
22
+ end
23
+
24
+ def sintaxis_help
25
+ return /^literal (literal)$/
26
+ end
27
+
28
+ # Description of the action help.
29
+ #
30
+ # @contract Help Plugin.
31
+ # @return [String] The description of the action.
32
+ def description_help
33
+ "Provides information for player features."
34
+ end
35
+
36
+ # Usage information of the action help.
37
+ #
38
+ # @contract Help Plugin.
39
+ # @return [String] The usage information of the action.
40
+ def usage_help
41
+ return <<-USAGE
42
+ usage: help [command]
43
+
44
+ Provides usage information for player commands. If called without parameters, shows the player usage.
45
+
46
+ Examples:
47
+
48
+ To see all the commands availables in the player:
49
+ help
50
+
51
+ To see the usage for play command:
52
+ help play
53
+
54
+ USAGE
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,74 @@
1
+ module CultomePlayer
2
+ module StateChecker
3
+
4
+ # Check the status of pause.
5
+ #
6
+ # @return [Boolean] True if paused, False otherwise
7
+ def paused?
8
+ @paused ||= false
9
+ end
10
+
11
+ # Check the status of stop.
12
+ #
13
+ # @return [Boolean] True if stopped, False otherwise
14
+ def stopped?
15
+ @stopped ||= true
16
+ end
17
+
18
+ # Check the status of play.
19
+ #
20
+ # @return [Boolean] True if playing, False otherwise
21
+ def playing?
22
+ @playing ||= false
23
+ end
24
+
25
+ # Check the status of shuffle.
26
+ #
27
+ # @return [Boolean] True if shuffling, False otherwise
28
+ def shuffling?
29
+ playlists[:current].shuffling?
30
+ end
31
+
32
+ # Returns the current song.
33
+ #
34
+ # @return [Song] The current song or nil if any.
35
+ def current_song
36
+ @current_song
37
+ end
38
+
39
+ # Returns the current artist.
40
+ #
41
+ # @return [Artist] The current artist or nil if any.
42
+ def current_artist
43
+ current_song.artist
44
+ end
45
+
46
+ # Returns the current album.
47
+ #
48
+ # @return [Album] The current album or nil if any.
49
+ def current_album
50
+ current_song.album
51
+ end
52
+
53
+ # Returns the current playlist.
54
+ #
55
+ # @return [Playlist] The current playlist or nil if any.
56
+ def current_playlist
57
+ playlists[:current]
58
+ end
59
+
60
+ # Returns the current playback position.
61
+ #
62
+ # @return [Integer] The current playback position in seconds.
63
+ def playback_position
64
+ @playback_time_position ||= 0
65
+ end
66
+
67
+ # Returns the current playback length.
68
+ #
69
+ # @return [Integer] The current playback length in seconds.
70
+ def playback_length
71
+ @playback_time_length ||= 0
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,95 @@
1
+ require 'active_record'
2
+
3
+ module CultomePlayer
4
+ module Utils
5
+
6
+ # Check if a string value can be a positive boolean value.
7
+ #
8
+ # @param value [String] String to check if can be a positive boolean value.
9
+ # @return [Boolean] True if value is a positive boolean value. False otherwise.
10
+ def is_true_value?(value)
11
+ /true|yes|on|y|n|s|si|cierto/ === value
12
+ end
13
+
14
+ # Print a string into stdout (not STDOUT) and finish with a newline character.
15
+ #
16
+ # @param msg [String] The value to be printed.
17
+ # @return [String] The value printed.
18
+ def display(msg)
19
+ stdout.puts msg
20
+ return msg
21
+ end
22
+
23
+ # Print a string into stdout (not STDOUT) but before insert a carriage return and dont append a newline character at the end.
24
+ #
25
+ # @param msg [String] The value to be printed.
26
+ # @return [String] The value printed.
27
+ def display_over(msg)
28
+ stdout.print "\r#{msg}"
29
+ return msg
30
+ end
31
+
32
+ # Define the 15 colors allowed in my console scheme
33
+ (1..15).each do |idx|
34
+ define_method :"c#{idx}" do |str|
35
+ str
36
+ end
37
+ end
38
+
39
+ # Arrange an array of string into single string arranged by columns separed by an inner border.
40
+ #
41
+ # @param cols [List<String>] The strings to be arranged.
42
+ # @param widths [List<Integer>] The width of the columns.
43
+ # @param border [Integer] The width of the inner borders.
44
+ # @return [String] The string representation of columns.
45
+ def arrange_in_columns(cols, widths, border)
46
+ row = ""
47
+ idxs = cols.collect{|c| 0 }
48
+
49
+ while cols.zip(idxs).any?{|col| col[0].length > col[1] }
50
+ cols.each.with_index do |col, idx|
51
+ slice_width = widths[idx]
52
+
53
+ slice = col.slice(idxs[idx], slice_width) || "" # sacamos el pedazo de la columna
54
+ row << slice.ljust(slice_width) # concatenamos a la fila
55
+ idxs[idx] += slice_width # recorremos el indice
56
+ row << " " * border # agregamos el border de la derecha
57
+ end
58
+
59
+ row = row.strip << "\n" # quitamos el ultimo border
60
+ end
61
+
62
+ return row.strip # quitamos el ultimo salto de linea
63
+ end
64
+
65
+ # Capture and dispose the standard output sended inside the block provided.
66
+ #
67
+ # @return [String] The swallowed data.
68
+ def swallow_stdout
69
+ s = StringIO.new
70
+ oldstd = $stdout
71
+ $stdout = s
72
+ yield
73
+ return s.string
74
+ ensure
75
+ $stdout = oldstd
76
+ end
77
+
78
+ # Provides a wrapper for database connection.
79
+ #
80
+ # @param db_block [Block] The block to be executed inside a database connection.
81
+ def with_connection(&db_logic)
82
+ begin
83
+ ActiveRecord::Base.connection_pool
84
+ rescue Exception => e
85
+ ActiveRecord::Base.establish_connection(
86
+ adapter: db_adapter,
87
+ database: db_file
88
+ )
89
+ ActiveRecord::Base.logger = Logger.new(File.open(db_log_file, 'a'))
90
+ end
91
+
92
+ ActiveRecord::Base.connection_pool.with_connection(&db_logic)
93
+ end
94
+ end
95
+ end