cultome_player 2.0.0

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.
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