lyricli 0.0.1

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 (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: