lyricli 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Gemfile +3 -0
  2. data/Gemfile.lock +63 -0
  3. data/README.md +69 -0
  4. data/bin/lrc +52 -0
  5. data/config/defaults.json +5 -0
  6. data/lib/lyricli/configuration.rb +90 -0
  7. data/lib/lyricli/exceptions/disable_source_error.rb +8 -0
  8. data/lib/lyricli/exceptions/enable_source_error.rb +8 -0
  9. data/lib/lyricli/exceptions/invalid_lyrics_error.rb +8 -0
  10. data/lib/lyricli/exceptions/lyrics_not_found_error.rb +8 -0
  11. data/lib/lyricli/exceptions/reset_source_error.rb +8 -0
  12. data/lib/lyricli/exceptions/source_configuration_error.rb +9 -0
  13. data/lib/lyricli/exceptions/start_source_error.rb +7 -0
  14. data/lib/lyricli/exceptions/unknown_source_error.rb +8 -0
  15. data/lib/lyricli/exceptions.rb +7 -0
  16. data/lib/lyricli/lyricli.rb +55 -0
  17. data/lib/lyricli/lyrics_engine.rb +41 -0
  18. data/lib/lyricli/source_manager.rb +136 -0
  19. data/lib/lyricli/sources/arguments.rb +39 -0
  20. data/lib/lyricli/sources/current_song.scpt +0 -0
  21. data/lib/lyricli/sources/itunes.rb +41 -0
  22. data/lib/lyricli/sources/rdio.rb +73 -0
  23. data/lib/lyricli/sources.rb +6 -0
  24. data/lib/lyricli/util.rb +36 -0
  25. data/lib/lyricli.rb +96 -0
  26. data/spec/bin/lrc_spec.rb +0 -0
  27. data/spec/lib/lyricli/configuration_spec.rb +0 -0
  28. data/spec/lib/lyricli/exceptions_spec.rb +0 -0
  29. data/spec/lib/lyricli/lyricli_spec.rb +0 -0
  30. data/spec/lib/lyricli/lyrics_engine_spec.rb +0 -0
  31. data/spec/lib/lyricli/source_manager_spec.rb +25 -0
  32. data/spec/lib/lyricli/sources/arguments.rb +0 -0
  33. data/spec/lib/lyricli/sources/itunes.rb +0 -0
  34. data/spec/lib/lyricli/sources/rdio.rb +0 -0
  35. data/spec/lib/lyricli/sources_spec.rb +0 -0
  36. data/spec/lib/lyricli/util_spec.rb +42 -0
  37. data/spec/lib/lyricli_spec.rb +0 -0
  38. metadata +212 -0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lyricli (0.0.1)
5
+ launchy (~> 2.1.2)
6
+ multi_json (~> 1.3.6)
7
+ nokogiri (~> 1.5.5)
8
+ rdio (~> 0.1.0)
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ addressable (2.3.2)
14
+ archive-tar-minitar (0.5.2)
15
+ columnize (0.3.6)
16
+ daemons (1.1.9)
17
+ diff-lcs (1.1.3)
18
+ eventmachine (1.0.0)
19
+ json (1.7.5)
20
+ launchy (2.1.2)
21
+ addressable (~> 2.3)
22
+ linecache19 (0.5.12)
23
+ ruby_core_source (>= 0.1.4)
24
+ multi_json (1.3.6)
25
+ nokogiri (1.5.5)
26
+ oauth (0.4.7)
27
+ rack (1.4.1)
28
+ rdio (0.1.0)
29
+ json
30
+ oauth (>= 0.3.0)
31
+ rspec (2.11.0)
32
+ rspec-core (~> 2.11.0)
33
+ rspec-expectations (~> 2.11.0)
34
+ rspec-mocks (~> 2.11.0)
35
+ rspec-core (2.11.1)
36
+ rspec-expectations (2.11.3)
37
+ diff-lcs (~> 1.1.3)
38
+ rspec-mocks (2.11.3)
39
+ ruby-debug-base19 (0.11.25)
40
+ columnize (>= 0.3.1)
41
+ linecache19 (>= 0.5.11)
42
+ ruby_core_source (>= 0.1.4)
43
+ ruby-debug19 (0.11.6)
44
+ columnize (>= 0.3.1)
45
+ linecache19 (>= 0.5.11)
46
+ ruby-debug-base19 (>= 0.11.19)
47
+ ruby_core_source (0.1.5)
48
+ archive-tar-minitar (>= 0.5.2)
49
+ thin (1.5.0)
50
+ daemons (>= 1.0.9)
51
+ eventmachine (>= 0.12.6)
52
+ rack (>= 1.0.0)
53
+ yard (0.8.2.1)
54
+
55
+ PLATFORMS
56
+ ruby
57
+
58
+ DEPENDENCIES
59
+ lyricli!
60
+ rspec (~> 2.11.0)
61
+ ruby-debug19 (~> 0.11.6)
62
+ thin (~> 1.5.0)
63
+ yard (~> 0.8.2.1)
data/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # Lyricli #
2
+ ## The command line client for lyrics ##
3
+
4
+ This is a quick introduction for Lyricli. Right now it's in really early
5
+ stages of development, so it's lacking in a lot of stuff (mainly tests
6
+ and documentation) ... But it generally works and here's a tutorial to
7
+ see how to get it working.
8
+
9
+ ### Installing ###
10
+
11
+ 1. Clone this Repo
12
+ 2. `gem build lyricli.gemspec`
13
+ 3. `gem install lyricli-0.0.1.gem`
14
+ 4. Voila!
15
+
16
+ ### Usage ###
17
+
18
+ Lyricli can be invoked with the command `lrc` and there are three basic
19
+ ways of using it:
20
+
21
+ `lrc`
22
+
23
+ When you run it without arguments, it will look in the available sources
24
+ to try to find a playing song and extract the lyrics.
25
+
26
+ `lrc artist song`
27
+
28
+ When you run it with arguments, it will use them to search for the
29
+ lyrics. This won't work if you manually disable the arguments source in
30
+ your configuration file.
31
+
32
+ #### Commands ####
33
+
34
+ The third way to use it is by passing it one of the following special
35
+ commands:
36
+
37
+ * `lrc -l` or `lrc --list-sources` lists the available sources.
38
+ * `lrc -e` or `lrc --enable SOURCE` enable a source from the list.
39
+ * `lrc -d` or `lrc --disable SOURCE` disable a source from the list.
40
+ * `lrc -r` or `lrc --reset SOURCE` reset all configuration for a source.
41
+ * `lrc -v` or `lrc --version` show the installed version of lyricli.
42
+ * `lrc -h` or `lrc --help` display some help
43
+
44
+ ### Roadmap ###
45
+
46
+ There is not much defined right now as a roadmap, but this needs to be
47
+ done:
48
+
49
+ * Specs for all the components
50
+ * YARD documentation for all the components
51
+
52
+ And the first thing I want to work on after that is done is separating
53
+ the Lyrics Engines so we can add/remove lyrics engines in a similar way to how
54
+ we currently add/remove sources.
55
+
56
+ Also, I want to add the last song to the configuration, so you can check
57
+ that. This would let us "watch" lyricli without hammering the lyrics
58
+ wiki api
59
+
60
+ Also, during the enable phase, sources like iTunes should check for
61
+ proper OS and stop if they're not in their home turf.
62
+
63
+ ### Leave Feedback Please! ###
64
+
65
+ If you decide to use or hack away at Lyricly, please don't forget to
66
+ post any issues you find.
67
+
68
+ ### License ###
69
+ Licensed under 3-clause-BSD.
data/bin/lrc ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'optparse'
4
+ require 'lyricli'
5
+
6
+
7
+ options = {}
8
+ OptionParser.new do |opts|
9
+ opts.banner = %{Usage:
10
+ lrc [options]
11
+ lrc artist song
12
+ lrc You must enable other sources for this
13
+
14
+ Options:
15
+ }
16
+
17
+ opts.on("-e", "--enable SOURCE", "Enable SOURCE") do |source|
18
+ Lyricli.enable(source)
19
+ puts "#{source} has been enabled"
20
+ exit
21
+ end
22
+
23
+ opts.on("-l", "--list-sources", "List all available Sources") do
24
+ puts Lyricli.sources
25
+ exit
26
+ end
27
+
28
+ opts.on("-d", "--disable SOURCE", "Disable SOURCE") do |source|
29
+ Lyricli.disable(source)
30
+ puts "#{source} has been disabled"
31
+ exit
32
+ end
33
+
34
+ opts.on("-r", "--reset SOURCE", "Reset the configuration of SOURCE") do |source|
35
+ Lyricli.reset(source)
36
+ puts "#{source} has been disabled and all its configuration reset"
37
+ exit
38
+ end
39
+
40
+ opts.on("-h", "--help", "Shows this message") do
41
+ puts opts
42
+ exit
43
+ end
44
+
45
+ opts.on("-v", "--version", "Show version") do
46
+ puts Lyricli.version
47
+ exit
48
+ end
49
+ end.parse!
50
+
51
+
52
+ puts Lyricli.lyrics
@@ -0,0 +1,5 @@
1
+ {
2
+ "rdio_key": "sddac5t8akqrzh5b6kg53jfm",
3
+ "rdio_secret": "PRcB8TggFr",
4
+ "enabled_sources": ["arguments"]
5
+ }
@@ -0,0 +1,90 @@
1
+ module Lyricli
2
+
3
+ # This class handles the configuration of Lyricli
4
+ class Configuration
5
+
6
+ # Defines the paths to the default and user configuration files
7
+ def initialize
8
+ @config_path = "~/.lyricli.conf"
9
+ @defaults_path = "defaults.json"
10
+ @config = nil
11
+ end
12
+
13
+ @@instance = Configuration.new
14
+
15
+ # Ensure this is only called once. Only use the instance class variable
16
+ # to access this method, as its constructor is private.
17
+ def self.instance
18
+ @@instance
19
+ end
20
+
21
+ # Access configuration properties, loads config if needed beforehand.
22
+ #
23
+ # @param [String] key the configuration key to access
24
+ # @return [String, Hash, Array] the value of the configuration key.
25
+ def [](key)
26
+ load_config unless @config
27
+ @config[key]
28
+ end
29
+
30
+ # Assigns a new value to a configuration key, loads config if needed and
31
+ # saves it after updating.
32
+ #
33
+ # @param [String] key the configuration key to set
34
+ # @param [Object] value the value for the configuration key, can be any
35
+ # object as long as it can be converted to JSON
36
+ def []=(key, value)
37
+ load_config unless @config
38
+ @config[key] = value
39
+ save_config
40
+ end
41
+
42
+ # Deletes a key from the configuration, loads config if needed and saves
43
+ # it after deleting.
44
+ #
45
+ # @param [String] key the key to delete
46
+ def delete(key)
47
+ load_config unless @config
48
+ @config.delete(key)
49
+ save_config
50
+ end
51
+
52
+ private_class_method :new
53
+
54
+ # Loads the configuration from the user file, attempts to create it from
55
+ # defaults if it's not present. sets the `@config` instance variable.
56
+ def load_config
57
+ path = File.expand_path(@config_path)
58
+
59
+ if File.exists?(path)
60
+ file = File.new(path, "r")
61
+ @config = MultiJson.decode(file.read)
62
+ else
63
+ load_default_config
64
+ end
65
+ end
66
+
67
+ # Serializes the `@config` Hash to JSON and saves it to a file.
68
+ def save_config
69
+ path = File.expand_path(@config_path)
70
+ file = File.new(path, "w")
71
+ file.print(MultiJson.encode(@config))
72
+ file.close
73
+ end
74
+
75
+ private
76
+
77
+ # Loads the default configuration from a JSON file
78
+ def load_default_config
79
+ # Load the default
80
+ path = File.join(::Lyricli.root, "config", @defaults_path)
81
+
82
+ if File.exists?(path)
83
+ file = File.new(path, "r")
84
+ @config = MultiJson.decode(file.read)
85
+ else
86
+ @config = {}
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,8 @@
1
+ module Lyricli
2
+ module Exceptions
3
+ # There was an error when disabling a source
4
+ class DisableSourceError < StandardError
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,8 @@
1
+ module Lyricli
2
+ module Exceptions
3
+ # There was an error when enabling the source
4
+ class EnableSourceError < StandardError
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,8 @@
1
+ module Lyricli
2
+ module Exceptions
3
+ # No artist/song was found.
4
+ class InvalidLyricsError < StandardError
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,8 @@
1
+ module Lyricli
2
+ module Exceptions
3
+ # No lyrics could be found for this artist/song pair
4
+ class LyricsNotFoundError < StandardError
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,8 @@
1
+ module Lyricli
2
+ module Exceptions
3
+ # There was an error while resetting a source
4
+ class ResetSourceError < StandardError
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,9 @@
1
+ module Lyricli
2
+ module Exceptions
3
+ # There is an error with the source's configuration and it can't
4
+ # find its current track.
5
+ class SourceConfigurationError < StandardError
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,7 @@
1
+ module Lyricli
2
+ module Exceptions
3
+ # There was an error while starting a source
4
+ class StartSourceError < StandardError
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Lyricli
2
+ module Exceptions
3
+ # An unknown source was tried to enable/disable/reset
4
+ class UnknownSourceError < StandardError
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,7 @@
1
+ module Lyricli
2
+ # The namespace for all exceptions in Lyricli. Has no functionality by
3
+ # itself
4
+ module Exceptions
5
+ end
6
+ end
7
+
@@ -0,0 +1,55 @@
1
+ module Lyricli
2
+
3
+ # This class has the basic logic for extracting the lyrics and controlling the
4
+ # application
5
+ class Lyricli
6
+
7
+ # Constructor, initializes `@source_manager`
8
+ def initialize
9
+ @source_manager = SourceManager.new
10
+ end
11
+
12
+ # Raises an InvalidLyricsError which means we did not get any valid
13
+ # artist/song from any of the sources
14
+ #
15
+ # @raise [Lyricli::Exceptions::InvalidLyricsError] because we found nothing
16
+ def exit_with_error
17
+ raise Exceptions::InvalidLyricsError
18
+ end
19
+
20
+ # Extracts the current track, validates it and requests the lyrics from our
21
+ # LyricsEngine
22
+ #
23
+ # @return [String] the found lyrics, or a string indicating none were found
24
+ def get_lyrics
25
+
26
+ begin
27
+ set_current_track
28
+ check_params
29
+ rescue Exceptions::InvalidLyricsError
30
+ return "No Artist/Song could be found :("
31
+ end
32
+
33
+ engine = LyricsEngine.new(@current_track[:artist], @current_track[:song])
34
+
35
+ begin
36
+ return engine.get_lyrics
37
+ rescue Exceptions::LyricsNotFoundError
38
+ return "Lyrics not found :("
39
+ end
40
+ end
41
+
42
+ # Set the `@current_track` instance variable by asking the SourceManager for
43
+ # its current track
44
+ def set_current_track
45
+ @current_track = @source_manager.current_track
46
+ end
47
+
48
+ # Exits with error when there is an empty field from the current track.
49
+ def check_params
50
+ self.exit_with_error unless @current_track
51
+ self.exit_with_error if @current_track[:artist].nil? or @current_track[:artist].empty?
52
+ self.exit_with_error if @current_track[:song].nil? or @current_track[:song].empty?
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,41 @@
1
+ module Lyricli
2
+
3
+ # This class gets the lyrics according to a given artist and song name.
4
+ class LyricsEngine
5
+
6
+ include Util
7
+
8
+ # Starts a new instance of LyricsEngine
9
+ #
10
+ # @param [String] artist the artist
11
+ # @param [String] song the song to look for
12
+ def initialize(artist, song)
13
+ @provider = URI("http://lyrics.wikia.com/api.php?artist=#{sanitize_param artist}&song=#{sanitize_param song}&fmt=realjson")
14
+ end
15
+
16
+ # Asks Lyrics Wiki for the lyrics, also cleans up the output a little.
17
+ #
18
+ # @return [String] the lyrics
19
+ def get_lyrics
20
+ begin
21
+ response = Net::HTTP.get(@provider)
22
+ response = MultiJson.decode(response)
23
+
24
+ doc = Nokogiri::HTML(open(response['url']))
25
+ node = doc.search(".lyricbox").first
26
+ rescue
27
+ raise Exceptions::LyricsNotFoundError
28
+ end
29
+
30
+ node.search(".rtMatcher").each do |n|
31
+ n.remove
32
+ end
33
+
34
+ node.search("br").each do |br|
35
+ br.replace "\n"
36
+ end
37
+
38
+ node.inner_text
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,136 @@
1
+ module Lyricli
2
+
3
+ # Manages the different sources. SourceManager is in charge of enabling and
4
+ # disabling them, as well as getting the current track.
5
+ class SourceManager
6
+
7
+ include Util
8
+
9
+ # Creates a new instance of SourceManager
10
+ def initialize
11
+ @enabled_sources = []
12
+ @config = Configuration.instance
13
+ @config["enabled_sources"].each do |source|
14
+ if klass = parse_class(camelize(source))
15
+ current_source = klass.new
16
+ @enabled_sources << current_source
17
+ else
18
+ raise Exceptions::StartSourceError
19
+ end
20
+ end
21
+ end
22
+
23
+ # Enables a source. This runs the source's enable method and adds it to the
24
+ # `enabled_sources` configuration key. It will only enable sources that
25
+ # are "available" (see #available_sources)
26
+ #
27
+ # @param [String] source_name the name of the source to enable
28
+ def enable(source_name)
29
+ if available_sources.include?(source_name)
30
+ if klass = parse_class(camelize(source_name))
31
+ klass.enable
32
+ @config["enabled_sources"] << klass.name
33
+ @config["enabled_sources"].uniq!
34
+ @config.save_config
35
+ else
36
+ raise Exceptions::EnableSourceError
37
+ end
38
+ else
39
+ raise Exceptions::UnknownSourceError
40
+ end
41
+ end
42
+
43
+ # Disables a source. This only removes the source from the `enabled_sources`
44
+ # configuration key.
45
+ #
46
+ # @param [String] source_name the name of the source to disable
47
+ def disable(source_name)
48
+ if available_sources.include?(source_name)
49
+ if klass = parse_class(camelize(source_name))
50
+ @config["enabled_sources"].delete(klass.name)
51
+ @config.save_config
52
+ else
53
+ raise Exceptions::DisableSourceError
54
+ end
55
+ else
56
+ raise Exceptions::UnknownSourceError
57
+ end
58
+ end
59
+
60
+ # Resets a source. This runs the source's reset method. It will also disable
61
+ # them.
62
+ #
63
+ # @param [String] source_name the name of the source to reset.
64
+ def reset(source_name)
65
+ if available_sources.include?(source_name)
66
+ if klass = parse_class(camelize(source_name))
67
+ klass.reset
68
+ disable(source_name)
69
+ else
70
+ raise Exceptions::ResetSourceError
71
+ end
72
+ else
73
+ raise Exceptions::UnknownSourceError
74
+ end
75
+ end
76
+
77
+ # Iterates over every source to attempt to retrieve the current song.
78
+ #
79
+ # @return [Hash] the current track, has an `:artist` and `:song` key.
80
+ def current_track
81
+ track = nil
82
+ lock = false
83
+ @enabled_sources.each do |source|
84
+ begin
85
+ current_track = source.current_track
86
+
87
+ # This is a special thing for arguments. The thing is, they need to
88
+ # be inputted manually. So, if they are present they won't allow
89
+ # anyone else to give results. Makes sense, yet a bit hacky.
90
+ unless current_track[:artist].nil? || current_track[:artist].empty? || current_track[:song].nil? || current_track[:song].empty?
91
+ track = current_track unless lock
92
+ lock = true if source.class.name == "arguments"
93
+ end
94
+ rescue
95
+ raise Exceptions::SourceConfigurationError
96
+ end
97
+ end
98
+ track
99
+ end
100
+
101
+ # Returns an array with the available sources. Optionally formats the result
102
+ # so active sources are identified by an appended *
103
+ #
104
+ # @param [Boolean] format whether or not to render the stars for active
105
+ # sources.
106
+ # @return [Array] the names of the currently available sources.
107
+ def available_sources(format = false)
108
+ path_root = File.expand_path(File.dirname(__FILE__))
109
+ sources = Dir[path_root+"/sources/*.rb"].map{ |s|
110
+ name = s.split("/").last.gsub(/\.rb/, "")
111
+ name
112
+ }
113
+
114
+ # Remove arguments (Hack?) We don't want anybody to touch tihs one.
115
+ sources.delete("arguments")
116
+ if format
117
+ # Add a star to denote enabled sources
118
+ format_sources(sources)
119
+ else
120
+ sources
121
+ end
122
+ end
123
+
124
+ # Adds a star to all members of the array that correspond to an active
125
+ # source
126
+ #
127
+ # @param [Array] sources the array of sources to format
128
+ # @return [Array] the formatted array
129
+ def format_sources(sources)
130
+ sources.map{ |s|
131
+ s << "*" if @config["enabled_sources"].include?(s)
132
+ s
133
+ }
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,39 @@
1
+ module Lyricli
2
+ module Sources
3
+ # The arguments source. This one is special since it expects two
4
+ # arguments. It is treated specially by the SourceManager.
5
+ class Arguments
6
+
7
+ class << self
8
+ attr_accessor :name
9
+ end
10
+
11
+ @name = "arguments"
12
+
13
+ # The enable method should run all of the tasks needed to validate
14
+ # the source. In the case of Rdio it has to authenticate with OAuth.
15
+ def self.enable
16
+ # Nothing to do.
17
+ end
18
+
19
+ # Instantiates everything it needs to run.
20
+ def initialize
21
+ # Nothing to do.
22
+ end
23
+
24
+ # The current_track method should return the name of the current
25
+ # artist and song.
26
+ # @return [Hash] A hash containing the current `:song` and `:artist`.
27
+ def current_track
28
+ artist = ARGV[0]
29
+ song = ARGV[1]
30
+ {:artist => artist, :song => song}
31
+ end
32
+
33
+ # The reset method resets any configurations it may have
34
+ def self.reset
35
+ # Reset Code
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ module Lyricli
2
+ module Sources
3
+ # The source for iTunes
4
+ class Itunes
5
+
6
+ class << self
7
+ attr_accessor :name
8
+ end
9
+
10
+ @name = "itunes"
11
+
12
+ # The enable method should run all of the tasks needed to validate
13
+ # the source. In the case of Rdio it has to authenticate with OAuth.
14
+ def self.enable
15
+ # Nothing to do
16
+ end
17
+
18
+ # Instantiates everything it needs to run.
19
+ def initialize
20
+ @config = Configuration.instance
21
+ @script = "current_song.scpt"
22
+ end
23
+
24
+ # The current_track method should return the name of the current
25
+ # artist and song.
26
+ # @return [Hash] A hash containing the current `:song` and `:artist`.
27
+ def current_track
28
+ path_root = File.expand_path(File.dirname(__FILE__))
29
+ path = File.join(path_root, @script)
30
+ current = `osascript #{path}`
31
+ current = current.split("<-SEP->")
32
+ {:artist => current[0], :song => current[1]}
33
+ end
34
+
35
+ # The reset method resets any configurations it may have
36
+ def self.reset
37
+ # Nothing to do
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ module Lyricli
2
+ module Sources
3
+
4
+ # This is the Source for rdio
5
+ class Rdio
6
+
7
+ class << self
8
+ attr_accessor :name
9
+ end
10
+
11
+ @name = "rdio"
12
+
13
+ # The enable method should run all of the tasks needed to validate
14
+ # the source. In the case of Rdio it has to authenticate with OAuth.
15
+ def self.enable
16
+ # Validation Code
17
+ @config = Configuration.instance
18
+ unless @config["rdio_auth_token"] && !@config["rdio_auth_token"].empty?
19
+ create_auth_token
20
+ end
21
+
22
+ puts "***"
23
+ puts "Hello, rdio tends to be a bit aggressive and tends to have trouble with other sources. If you're having trouble, you can disable it temporarily. You will not have to reauthenticate."
24
+ puts "***"
25
+
26
+ end
27
+
28
+ # Instantiates everything it needs to run.
29
+ def initialize
30
+ @name = 'rdio'
31
+ @config = Configuration.instance
32
+ @rdio = ::Rdio::SimpleRdio.new([@config["rdio_key"], @config["rdio_secret"]], @config["rdio_auth_token"])
33
+ end
34
+
35
+ # The current_track method should return the name of the current
36
+ # artist and song.
37
+ #
38
+ # @return [Hash] A hash containing the current `:song` and `:artist`.
39
+ def current_track
40
+ response = @rdio.call('currentUser', {'extras' => 'lastSongPlayed'})
41
+ artist = response["result"]["lastSongPlayed"]["artist"]
42
+ song = response["result"]["lastSongPlayed"]["name"]
43
+ {:artist => artist, :song => song}
44
+ end
45
+
46
+ # The reset method resets any configurations it may have
47
+ def self.reset
48
+ @config = Configuration.instance
49
+ @config.delete("rdio_auth_token")
50
+ end
51
+
52
+ # Signs in to rdio with our credentials and requests access for a new auth
53
+ # token.
54
+ def self.create_auth_token
55
+ rdio = ::Rdio::SimpleRdio.new([@config["rdio_key"], @config["rdio_secret"]], @config["rdio_auth_token"])
56
+
57
+ # Request Authorization
58
+ puts "Follow this URL to authorize lyricli:"
59
+ auth_url = rdio.begin_authentication('oob')
60
+ puts auth_url
61
+ ::Launchy.open(auth_url)
62
+
63
+ # Request Code, Obtain Token
64
+ print "Please type the authorization code: "
65
+ auth_code = gets.chomp
66
+ token = rdio.complete_authentication(auth_code)
67
+
68
+ @config["rdio_auth_token"] = token
69
+ token
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,6 @@
1
+ module Lyricli
2
+ # The namespace for all sources in Lyricli. Has no functionality by
3
+ # itself
4
+ module Sources
5
+ end
6
+ end
@@ -0,0 +1,36 @@
1
+ module Lyricli
2
+ # This module contains several utility functions.
3
+ module Util
4
+
5
+ # Transforms a string from snake_case to UpperCamelCase
6
+ #
7
+ # @param [String] str the string that will be Camelized
8
+ # @return [String] the Camelized string.
9
+ def camelize(str)
10
+ str.split('_').map {|w| w.capitalize}.join
11
+ end
12
+
13
+ # Takes a class name in snake_case and attempts to find the corresponding
14
+ # class from the sources.
15
+ #
16
+ # @param [String] class_name the snake_case name of the class to search for.
17
+ # @return [Class,nil] the found class or nil
18
+ def parse_class(class_name)
19
+ begin
20
+ path = "Sources::#{class_name}"
21
+ return eval(path)
22
+ rescue NameError
23
+ return nil
24
+ end
25
+ end
26
+
27
+ # Simply escapes a param and substitutes spaces and escaped plus signs for
28
+ # plus signs.
29
+ #
30
+ # @param [String] p the parameter to be sanitized
31
+ # @return [String] the sanitized parameter
32
+ def sanitize_param(p)
33
+ CGI.escape(p.gsub(/ /, "+")).gsub("%2B", "+")
34
+ end
35
+ end
36
+ end
data/lib/lyricli.rb ADDED
@@ -0,0 +1,96 @@
1
+ require 'uri'
2
+ require 'cgi'
3
+ require 'net/http'
4
+ require 'multi_json'
5
+ require 'nokogiri'
6
+ require 'open-uri'
7
+ require 'launchy'
8
+
9
+ # This shit causes a lot of warnings. Quick Hack.
10
+ original_verbosity = $VERBOSE
11
+ $VERBOSE = nil
12
+ require 'rdio'
13
+ $VERBOSE = original_verbosity
14
+
15
+ # Local Dependencies
16
+ require "lyricli/util"
17
+ require "lyricli/configuration"
18
+ require "lyricli/lyricli"
19
+ require "lyricli/lyrics_engine"
20
+ require "lyricli/source_manager"
21
+ require "lyricli/sources"
22
+ require "lyricli/sources/arguments"
23
+ require "lyricli/sources/rdio"
24
+ require "lyricli/sources/itunes"
25
+ require "lyricli/exceptions"
26
+ require "lyricli/exceptions/disable_source_error"
27
+ require "lyricli/exceptions/enable_source_error"
28
+ require "lyricli/exceptions/invalid_lyrics_error"
29
+ require "lyricli/exceptions/lyrics_not_found_error"
30
+ require "lyricli/exceptions/reset_source_error"
31
+ require "lyricli/exceptions/source_configuration_error"
32
+ require "lyricli/exceptions/start_source_error"
33
+ require "lyricli/exceptions/unknown_source_error"
34
+
35
+ # The Lyricli module allows you to easily search for lyrics by looking for
36
+ # song and artist data from diverse sources.
37
+ module Lyricli
38
+ # Creates a new Lyricli instance and returns lyrics by going through the
39
+ # sources.
40
+ # @return [String] the fetched lyrics
41
+ def self.lyrics
42
+ @lyricli = Lyricli.new
43
+ @lyricli.get_lyrics
44
+ end
45
+
46
+ # Returns the version of the library
47
+ # @return [String] the version
48
+ def self.version
49
+ Gem.loaded_specs["lyricli"].version
50
+ end
51
+
52
+ # Returns a list of the available sources to enable or disable
53
+ # @return [String] the list of available sources. Enabled sources have
54
+ # a star appended.
55
+ def self.sources
56
+ source_manager = SourceManager.new
57
+ source_manager.available_sources(true).join(", ")
58
+ end
59
+
60
+ # Enables a source via the Source Manager
61
+ def self.enable(source_name)
62
+ source_manager = SourceManager.new
63
+ begin
64
+ source_manager.enable(source_name)
65
+ rescue Exceptions::UnknownSourceError
66
+ "There is no such Source"
67
+ end
68
+ end
69
+
70
+ # Disables a source via the Source Manager
71
+ def self.disable(source_name)
72
+ source_manager = SourceManager.new
73
+ begin
74
+ source_manager.disable(source_name)
75
+ rescue Exceptions::UnknownSourceError
76
+ "There is no such Source"
77
+ end
78
+ end
79
+
80
+ # Resets all configuration for a source via the Source Manager
81
+ def self.reset(source_name)
82
+ source_manager = SourceManager.new
83
+ begin
84
+ source_manager.reset(source_name)
85
+ rescue Exceptions::UnknownSourceError
86
+ "There is no such Source"
87
+ end
88
+ end
89
+
90
+ # Returns the root of the Gem.
91
+ #
92
+ # @return [String] the root path for this gem
93
+ def self.root
94
+ File.expand_path('../..',__FILE__)
95
+ end
96
+ end
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,25 @@
1
+ require "rspec"
2
+ require "lyricli"
3
+
4
+ describe Lyricli::SourceManager do
5
+ before :each do
6
+ # Stub the configuration
7
+ @example_configuration = {"enabled_sources" => "test_class"}
8
+ Configuration.stub(:'[]').and_return(@example_configuration)
9
+ Configuration.stub(:delete)
10
+
11
+ # Stub the test class.
12
+ module Lyricli
13
+ module Sources
14
+ module TestClass
15
+ end
16
+ end
17
+ end
18
+
19
+ @example_artist = {:artist => "The Shins", :song => "Know Your Onion"}
20
+
21
+ Lyricli::Sources::TestClass.stub(:enable)
22
+ Lyricli::Sources::TestClass.stub(:reset)
23
+ Lyricli::Sources::TestClass.stub(:current_track).and_return(@example_artist)
24
+ end
25
+ end
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,42 @@
1
+ require "rspec"
2
+ require "lyricli"
3
+
4
+ describe Lyricli::Util do
5
+ before :each do
6
+ class TestClass
7
+ include Lyricli::Util
8
+ end
9
+
10
+ module Lyricli
11
+ module Sources
12
+ class ParsedClass
13
+ end
14
+ end
15
+ end
16
+
17
+ @c = TestClass.new
18
+ end
19
+
20
+ describe "#camelize" do
21
+ it "should convert snake_case to CamelCase" do
22
+ expect(@c.camelize("test_string")).to eq("TestString")
23
+ end
24
+ end
25
+
26
+ describe "#parse_class" do
27
+ it "should parse classes under the Source namespace" do
28
+ expect(@c.parse_class("ParsedClass")).to eq(Lyricli::Sources::ParsedClass)
29
+ end
30
+
31
+ it "should return nil for nonexistent classes" do
32
+ expect(@c.parse_class("non_existent_class")).to eq(nil)
33
+ end
34
+ end
35
+
36
+ describe "#sanitize_param" do
37
+ it "should escape weird characters, but conserve the +" do
38
+ str = "one two+three /?&"
39
+ expect(@c.sanitize_param(str)).to eq("one+two+three+%2F%3F%26")
40
+ end
41
+ end
42
+ end
File without changes
metadata ADDED
@@ -0,0 +1,212 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lyricli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ben Beltran
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.5
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.5.5
30
+ - !ruby/object:Gem::Dependency
31
+ name: multi_json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.3.6
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.3.6
46
+ - !ruby/object:Gem::Dependency
47
+ name: rdio
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.1.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.1.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: launchy
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.1.2
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 2.1.2
78
+ - !ruby/object:Gem::Dependency
79
+ name: ruby-debug19
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.11.6
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.11.6
94
+ - !ruby/object:Gem::Dependency
95
+ name: yard
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 0.8.2.1
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 0.8.2.1
110
+ - !ruby/object:Gem::Dependency
111
+ name: thin
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 1.5.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 1.5.0
126
+ - !ruby/object:Gem::Dependency
127
+ name: rspec
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: 2.11.0
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: 2.11.0
142
+ description: Lyricli is an awesome CLI tool to read the lyrics of your currently playing
143
+ song right in the command line
144
+ email: ben@nsovocal.com
145
+ executables:
146
+ - lrc
147
+ extensions: []
148
+ extra_rdoc_files: []
149
+ files:
150
+ - lib/lyricli/configuration.rb
151
+ - lib/lyricli/exceptions/disable_source_error.rb
152
+ - lib/lyricli/exceptions/enable_source_error.rb
153
+ - lib/lyricli/exceptions/invalid_lyrics_error.rb
154
+ - lib/lyricli/exceptions/lyrics_not_found_error.rb
155
+ - lib/lyricli/exceptions/reset_source_error.rb
156
+ - lib/lyricli/exceptions/source_configuration_error.rb
157
+ - lib/lyricli/exceptions/start_source_error.rb
158
+ - lib/lyricli/exceptions/unknown_source_error.rb
159
+ - lib/lyricli/exceptions.rb
160
+ - lib/lyricli/lyricli.rb
161
+ - lib/lyricli/lyrics_engine.rb
162
+ - lib/lyricli/source_manager.rb
163
+ - lib/lyricli/sources/arguments.rb
164
+ - lib/lyricli/sources/itunes.rb
165
+ - lib/lyricli/sources/rdio.rb
166
+ - lib/lyricli/sources.rb
167
+ - lib/lyricli/util.rb
168
+ - lib/lyricli.rb
169
+ - bin/lrc
170
+ - Gemfile
171
+ - Gemfile.lock
172
+ - README.md
173
+ - spec/bin/lrc_spec.rb
174
+ - spec/lib/lyricli/configuration_spec.rb
175
+ - spec/lib/lyricli/exceptions_spec.rb
176
+ - spec/lib/lyricli/lyricli_spec.rb
177
+ - spec/lib/lyricli/lyrics_engine_spec.rb
178
+ - spec/lib/lyricli/source_manager_spec.rb
179
+ - spec/lib/lyricli/sources/arguments.rb
180
+ - spec/lib/lyricli/sources/itunes.rb
181
+ - spec/lib/lyricli/sources/rdio.rb
182
+ - spec/lib/lyricli/sources_spec.rb
183
+ - spec/lib/lyricli/util_spec.rb
184
+ - spec/lib/lyricli_spec.rb
185
+ - config/defaults.json
186
+ - lib/lyricli/sources/current_song.scpt
187
+ homepage: http://nsovocal.com/lyricli
188
+ licenses: []
189
+ post_install_message:
190
+ rdoc_options: []
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ none: false
195
+ requirements:
196
+ - - ! '>='
197
+ - !ruby/object:Gem::Version
198
+ version: '0'
199
+ required_rubygems_version: !ruby/object:Gem::Requirement
200
+ none: false
201
+ requirements:
202
+ - - ! '>='
203
+ - !ruby/object:Gem::Version
204
+ version: '0'
205
+ requirements: []
206
+ rubyforge_project:
207
+ rubygems_version: 1.8.24
208
+ signing_key:
209
+ specification_version: 3
210
+ summary: Lyricli is an awesome lyric client for your Command Line
211
+ test_files: []
212
+ has_rdoc: