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,28 @@
1
+ user:
2
+ db_adapter: sqlite3
3
+ db_file: ~/.cultome/db.dat
4
+ db_log_file: ~/.cultome/db.log
5
+ file_types: mp3
6
+ mplayer_pipe: ~/.cultome/mpctr
7
+ config_file: ~/.cultome/config.yml
8
+ memory:
9
+ db_adapter: sqlite3
10
+ db_file: /tmp/db.dat
11
+ db_log_file: /tmp/db.log
12
+ file_types: mp3
13
+ mplayer_pipe: /tmp/mpctr
14
+ config_file: /tmp/config.yml
15
+ rspec:
16
+ db_adapter: sqlite3
17
+ db_file: spec/db.dat
18
+ db_log_file: spec/db.log
19
+ file_types: mp3
20
+ mplayer_pipe: spec/mpctr
21
+ config_file: spec/config.yml
22
+ test:
23
+ db_adapter: sqlite3
24
+ db_file: ~/tmp/db.dat
25
+ db_log_file: ~/tmp/db.log
26
+ file_types: mp3
27
+ mplayer_pipe: ~/tmp/mpctr
28
+ config_file: ~/tmp/config.yml
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cultome_player/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "cultome_player"
8
+ gem.version = CultomePlayer::VERSION
9
+ gem.summary = "CulToMe Player"
10
+ gem.description = "A console music library explorer and player"
11
+ gem.authors = ["Carlos Soria"]
12
+ gem.email = "cultome@gmail.com"
13
+ gem.homepage = "https://github.com/cultome/cultome_player"
14
+ gem.license = "MIT"
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_runtime_dependency "activerecord"
22
+ gem.add_runtime_dependency "activesupport"
23
+ gem.add_runtime_dependency "taglib-ruby"
24
+ gem.add_runtime_dependency "rb-readline"
25
+ gem.add_runtime_dependency "sqlite3"
26
+ gem.add_runtime_dependency "colorize"
27
+
28
+ gem.add_development_dependency "rake"
29
+ gem.add_development_dependency "coveralls"
30
+ gem.add_development_dependency "rspec"
31
+ gem.add_development_dependency "bundler"
32
+ gem.add_development_dependency "database_cleaner"
33
+ gem.add_development_dependency "pry"
34
+ gem.add_development_dependency "yard"
35
+ end
@@ -0,0 +1,58 @@
1
+
2
+ class CreateSchema < ActiveRecord::Migration
3
+ def self.up
4
+ create_table :songs do |t|
5
+ t.string :name # If I Had A Gun
6
+ t.integer :artist_id, default: 0 # Noel Gallagher
7
+ t.integer :album_id, default: 0 # High Flying Birds
8
+ t.integer :year # 2011
9
+ t.integer :track # 3
10
+ t.integer :duration # 210 sec
11
+ t.integer :drive_id
12
+ t.string :relative_path
13
+ t.integer :points, default: 0
14
+ t.integer :plays, default: 0
15
+ t.datetime :last_played_at
16
+ t.timestamps
17
+ end
18
+
19
+ create_table :albums do |t|
20
+ t.string :name
21
+ t.integer :points
22
+ t.timestamps
23
+ end
24
+
25
+ create_table :artists do |t|
26
+ t.string :name
27
+ t.integer :points
28
+ t.timestamps
29
+ end
30
+
31
+ create_table :genres do |t|
32
+ t.integer :points
33
+ t.string :name
34
+ end
35
+
36
+ create_table :genres_songs, id: false do |t|
37
+ t.integer :song_id
38
+ t.integer :genre_id
39
+ end
40
+
41
+ create_table :drives do |t|
42
+ t.string :name
43
+ t.string :path
44
+ t.boolean :connected, default: true
45
+ t.timestamps
46
+ end
47
+ end
48
+
49
+ def self.down
50
+ drop_table :songs
51
+ drop_table :albums
52
+ drop_table :artists
53
+ drop_table :genres
54
+ drop_table :genres_songs
55
+ drop_table :drives
56
+ end
57
+ end
58
+
@@ -0,0 +1,107 @@
1
+ require "cultome_player/version"
2
+ require "cultome_player/environment"
3
+ require "cultome_player/objects"
4
+ require "cultome_player/command"
5
+ require "cultome_player/player"
6
+ require "cultome_player/media"
7
+ require "cultome_player/utils"
8
+ require "cultome_player/events"
9
+ require "cultome_player/plugins"
10
+ require "cultome_player/state_checker"
11
+
12
+ module CultomePlayer
13
+ include Environment
14
+ include Utils
15
+ include Objects
16
+ include Command
17
+ include Player
18
+ include Media
19
+ include Events
20
+ include Plugins
21
+ include StateChecker
22
+
23
+ # Interpret a user input string as it would be typed in the console.
24
+ #
25
+ # @param user_input [String] The user input.
26
+ # @return [Response] Response object with information about command execution.
27
+ def execute(user_input)
28
+ cmd = parse user_input
29
+ # revisamos si es un built in command o un plugin
30
+ action = cmd.action
31
+ plugin_action = "command_#{cmd.action}".to_sym
32
+ action = plugin_action if respond_to?(plugin_action)
33
+
34
+ raise 'invalid command:action unknown' unless respond_to?(action)
35
+ with_connection do
36
+ begin
37
+ send(action, cmd)
38
+ rescue Exception => e
39
+ s = e.message.split(":")
40
+ failure(message: s[0], details: s[1])
41
+ end
42
+ end
43
+ end
44
+
45
+ # Creates a generic response
46
+ #
47
+ # @param type [Symbol] The response type.
48
+ # @param data [Hash] The information that the response will contain.
49
+ # @return [Response] Response object with information in form of getter methods.
50
+ def create_response(type, data)
51
+ data[:response_type] = data.keys.first unless data.has_key?(:response_type)
52
+ return Response.new(type, data)
53
+ end
54
+
55
+ # Creates a success response. Handy method for #create_response
56
+ #
57
+ # @param response [Hash] The information that the response will contain.
58
+ # @return [Response] Response object with information in form of getter methods.
59
+ def success(response)
60
+ create_response(:success, get_response_params(response))
61
+ end
62
+
63
+ # Creates a failure response. Handy method for #create_response
64
+ #
65
+ # @param response [Hash] The information that the response will contain.
66
+ # @return [Response] Response object with information in form of getter methods.
67
+ def failure(response)
68
+ create_response(:failure, get_response_params(response))
69
+ end
70
+
71
+ class << self
72
+ class DefaultPlayer
73
+ include CultomePlayer
74
+
75
+ def initialize(env)
76
+ prepare_environment(env)
77
+ playlists.register(:current)
78
+ playlists.register(:history)
79
+ playlists.register(:queue)
80
+ playlists.register(:focus)
81
+ playlists.register(:search)
82
+
83
+ register_listener(:playback_finish, self)
84
+ end
85
+
86
+ def on_playback_finish
87
+ r = execute("next no_history")
88
+ display_over("#{r.message}\n#{PROMPT}")
89
+ end
90
+ end
91
+
92
+ # Get an instance of DefaultPlayer
93
+ #
94
+ # @param env [Symbol] The environment from which the configirations will be taken.
95
+ # @return [DefaultPlayer] A Cultome player ready to rock.
96
+ def get_player(env=:user)
97
+ DefaultPlayer.new(env)
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def get_response_params(response)
104
+ return {message: response} if response.instance_of?(String)
105
+ return response
106
+ end
107
+ end
@@ -0,0 +1,11 @@
1
+ require 'cultome_player/command/language'
2
+ require 'cultome_player/command/processor'
3
+ require 'cultome_player/command/reader'
4
+
5
+ module CultomePlayer
6
+ module Command
7
+ include Language
8
+ include Processor
9
+ include Reader
10
+ end
11
+ end
@@ -0,0 +1,61 @@
1
+ module CultomePlayer::Command
2
+ module Language
3
+
4
+ # Define the sintaxis of the player language.
5
+ #
6
+ # @return [Hash] With the keys :command, :parameters, :actions, :param
7
+ def sintaxis
8
+ # <command> : <action> | <action> <parameters>
9
+ # <action> : literal
10
+ # <parameters> : <param> | <param> <parameters>
11
+ # <param> : literal | criteria | number | object | path | bubble
12
+ {
13
+ command: ["action", "action parameters"],
14
+ parameters: ["param", "param parameters"],
15
+ action: [:literal],
16
+ param: [:literal, :criteria, :number, :object, :path, :boolean, :bubble],
17
+ }
18
+ end
19
+
20
+ # Returns the semantics of the builtin commands.
21
+ #
22
+ # @note The first literal in regex is the command itself.
23
+ # @return [Hash<String, Regex>] The key is the command name and the regex its format.
24
+ def semantics
25
+ {
26
+ "play" => /^literal(literal|number|criteria|object|[\s]+)*$/,
27
+ "show" => /^literal(number|object|[\s]+)*$/,
28
+ "search" => /^literal(literal|criteria|[\s]+)+$/,
29
+ "enqueue" => /^literal(literal|number|criteria|object|[\s]+)+$/,
30
+ "connect" => /^literal ((literal)|(path) bubble (literal))$/,
31
+ "disconnect" => /^literal (literal)$/,
32
+ "stop" => /^literal[\s]*$/,
33
+ "pause" => /^literal (boolean)$/,
34
+ "prev" => /^literal[\s]*$/,
35
+ "next" => /^literal[\s]*$/,
36
+ "quit" => /^literal[\s]*$/,
37
+ "ff" => /^literal(number|[\s]+)*$/,
38
+ "fb" => /^literal(number|[\s]+)*$/,
39
+ "shuffle" => /^literal[\s]+(boolean)$/,
40
+ "repeat" => /^literal[\s]*$/,
41
+ }
42
+ end
43
+
44
+ # Return the token identities.
45
+ #
46
+ # @return [List<Hash>] The has contains the type of the token and their format.
47
+ def token_identities
48
+ [
49
+ {type: :bubble, identity: /^(=>|->)$/},
50
+ {type: :number, identity: /^([\d]+)$/},
51
+ {type: :object, identity: /^@([\w\d]+)$/},
52
+ {type: :path, identity: /^(['"]?(?:\/|~\/)[\/\w\d\s.]+)["']?$/},
53
+ {type: :criteria, identity: /^([\w]+):([\d\w\s]+)$/, captures: 2, labels: [:criteria, :value]},
54
+ {type: :boolean, identity: /^(on|off|yes|false|true|si|no|y|n|s|ok)$/},
55
+ {type: :ip, identity: /^([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})$/},
56
+ {type: :literal, identity: /^([\w\d\s]+)$/},
57
+ ]
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,165 @@
1
+ module CultomePlayer::Command
2
+ module Processor
3
+
4
+ # Parse a user input into a command
5
+ #
6
+ # @param user_input [String] The user input to be parsed.
7
+ # @return [Command] The parsed command.
8
+ def parse(user_input)
9
+ tokens = identify_tokens(get_tokens(user_input))
10
+ validate_command(:command, tokens)
11
+ return CultomePlayer::Objects::Command.new(tokens.shift, tokens)
12
+ end
13
+
14
+ # Split the user input into tokens.
15
+ #
16
+ # @param user_input [String] The user input.
17
+ # @return [List<String>] The detected tokens.
18
+ def get_tokens(user_input)
19
+ tokens = []
20
+ token = ""
21
+ capturing_string = false
22
+
23
+ user_input.each_char do |char|
24
+ case char
25
+ when /[\d\w\/:@]/
26
+ token << char
27
+ when /["']/
28
+ capturing_string = !capturing_string
29
+ when /[\s]/
30
+ if capturing_string
31
+ token << char
32
+ else
33
+ tokens << token
34
+ token = ""
35
+ end
36
+ else
37
+ token << char
38
+ end # case
39
+ end # each
40
+
41
+ tokens << token unless token.empty?
42
+ raise "invalid command:unclosed string" if capturing_string
43
+
44
+ return tokens
45
+ end
46
+
47
+ # Identify detected tokens.
48
+ #
49
+ # @param tokens [List<String>] The detected tokens.
50
+ # @return [List<Hash>] The hash contains keys :type and :value.
51
+ def identify_tokens(tokens)
52
+ tokens.map do |token|
53
+ id = guess_token_id(token)
54
+ id.nil? ? {type: :unknown, value: token} : get_token_value(token, id)
55
+ end
56
+ end
57
+
58
+ # Check that a the tokens identifed correspond to a player command.
59
+ #
60
+ # @param type [Symbol] The language structure you try to match.
61
+ # @param tokens [List<Hash>] The list of tokens identified.
62
+ # @return [Boolean] True if the user command match with a player command format.
63
+ def validate_command(type, tokens)
64
+ current_format = get_command_format(type, tokens)
65
+ # extraemos el primer token, que debe ser el comando
66
+ cmd = tokens.first[:value]
67
+
68
+ valid_format = semantics[cmd]
69
+ if valid_format.nil?
70
+ if plugins_respond_to?(cmd)
71
+ valid_format = plugin_command_sintaxis(cmd)
72
+ else
73
+ raise 'invalid command:invalid action'
74
+ end
75
+ end
76
+ return current_format =~ valid_format
77
+ end
78
+
79
+ # Creates a string representation of the command prototype.
80
+ #
81
+ # @param type [Symbol] The Language structure you try to match.
82
+ # @param tokens [List<Hash>] The Language structure you try to match.
83
+ # @return [String] The string representation of the command prototype.
84
+ def get_command_format(type, tokens)
85
+ format = guess_command_format(type, tokens)
86
+
87
+ return format if format.class == Symbol
88
+
89
+ langs = format.split
90
+ # partimos el formato y validamos cada pedazo
91
+ tks = tokens.clone
92
+
93
+ cmd_format = ""
94
+ while !langs.empty? do
95
+ # extraemos el primer elemento del formato
96
+ lang = langs.shift
97
+
98
+ if langs.empty?
99
+ # volvemos a validar con el nuevo elemento del lenguaje
100
+ cmd_format << " " << get_command_format(lang.to_sym, tks).to_s
101
+ else
102
+ tk = tks.shift
103
+ cmd_format << " " << get_command_format(lang.to_sym, tk).to_s
104
+ end
105
+ end
106
+ # limpiamos el formato final
107
+ return cmd_format.strip.gsub(" ", " ")
108
+ end
109
+
110
+ private
111
+
112
+ def guess_token_id(token)
113
+ token_identities.find do |tok_id|
114
+ token =~ tok_id[:identity]
115
+ end
116
+ end
117
+
118
+ def get_token_value(token, id)
119
+ captures = id[:captures] || 1
120
+ labels = id[:labels] || [:value]
121
+
122
+ token_info = {type: id[:type]}
123
+
124
+ token =~ id[:identity]
125
+ (1..captures).to_a.zip(labels).each do |idx, label|
126
+ token_info[label] = eval("$#{idx}")
127
+ end
128
+
129
+ return token_info
130
+ end
131
+
132
+ def guess_command_format(type, tokens)
133
+ # buscamos el formato que tenga mas matches con los parametros
134
+ format = sintaxis[type].find do |stxs_elem| # ["action", "action parameters"]
135
+ if stxs_elem.is_a?(String)
136
+ # checamos si el numero de token en el comando corresponde
137
+ # con el numer de tokens en la sintaxis
138
+ stxs_elem.split.size >= tokens.size # ej. "play 1 2" === "action paramters"
139
+ elsif stxs_elem.is_a?(Symbol)
140
+ if tokens.is_a?(Hash)
141
+ tokens[:type] == stxs_elem
142
+ elsif tokens.is_a?(Array) && tokens.size == 1
143
+ tokens.first[:type] == stxs_elem
144
+ else
145
+ false
146
+ end
147
+ else
148
+ raise 'invalid command:invalid command format'
149
+ end
150
+ end
151
+
152
+ if format.nil?
153
+ max = sintaxis[type].max{|tk| tk.class == String ? tk.split.size: 0}
154
+ if max.respond_to?(:split) && tokens.size > max.split.size
155
+ format = max
156
+ else
157
+ raise 'invalid command:invalid command'
158
+ end
159
+ end
160
+
161
+ return format
162
+ end
163
+
164
+ end
165
+ end