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