spotify_cli 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/README.md +101 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/spotify +5 -0
- data/lib/dex/ui/README.md +20 -0
- data/lib/dex/ui/ansi.rb +48 -0
- data/lib/dex/ui/box.rb +15 -0
- data/lib/dex/ui/color.rb +57 -0
- data/lib/dex/ui/formatter.rb +155 -0
- data/lib/dex/ui/frame.rb +166 -0
- data/lib/dex/ui/glyph.rb +49 -0
- data/lib/dex/ui/progress.rb +19 -0
- data/lib/dex/ui/prompt.rb +121 -0
- data/lib/dex/ui/spinner.rb +168 -0
- data/lib/dex/ui/stdout_router.rb +186 -0
- data/lib/dex/ui/terminal.rb +18 -0
- data/lib/dex/ui.rb +83 -0
- data/lib/helpers/doc.rb +62 -0
- data/lib/spotify_cli/.DS_Store +0 -0
- data/lib/spotify_cli/api.rb +182 -0
- data/lib/spotify_cli/app.rb +101 -0
- data/lib/spotify_cli/version.rb +3 -0
- data/lib/spotify_cli.rb +38 -0
- data/spotify_cli.gemspec +36 -0
- metadata +118 -0
data/lib/dex/ui.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Dex
|
2
|
+
module UI
|
3
|
+
autoload :ANSI, 'dex/ui/ansi'
|
4
|
+
autoload :Glyph, 'dex/ui/glyph'
|
5
|
+
autoload :Color, 'dex/ui/color'
|
6
|
+
autoload :Box, 'dex/ui/box'
|
7
|
+
autoload :Frame, 'dex/ui/frame'
|
8
|
+
autoload :Progress, 'dex/ui/progress'
|
9
|
+
autoload :Prompt, 'dex/ui/prompt'
|
10
|
+
autoload :Terminal, 'dex/ui/terminal'
|
11
|
+
autoload :Formatter, 'dex/ui/formatter'
|
12
|
+
autoload :Spinner, 'dex/ui/spinner'
|
13
|
+
|
14
|
+
# TODO: this, better
|
15
|
+
SpinGroup = Spinner::SpinGroup
|
16
|
+
|
17
|
+
# TODO: test
|
18
|
+
def self.glyph(handle)
|
19
|
+
Dex::UI::Glyph.lookup(handle)
|
20
|
+
end
|
21
|
+
|
22
|
+
# TODO: test
|
23
|
+
def self.resolve_color(input)
|
24
|
+
case input
|
25
|
+
when Symbol
|
26
|
+
Dex::UI::Color.lookup(input)
|
27
|
+
else
|
28
|
+
input
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.confirm(question)
|
33
|
+
Dex::UI::Prompt.confirm(question)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.ask(question, **kwargs)
|
37
|
+
Dex::UI::Prompt.ask(question, **kwargs)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.resolve_text(input)
|
41
|
+
return input if input.nil?
|
42
|
+
Dex::UI::Formatter.new(input).format
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.fmt(input, enable_color: true)
|
46
|
+
Dex::UI::Formatter.new(input).format(enable_color: enable_color)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.frame(*args, &block)
|
50
|
+
Dex::UI::Frame.open(*args, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.spinner(*args, &block)
|
54
|
+
Dex::UI::Spinner.spin(*args, &block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.with_frame_color(color, &block)
|
58
|
+
Dex::UI::Frame.with_frame_color_override(color, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.log_output_to(path)
|
62
|
+
if Dex::UI::StdoutRouter.duplicate_output_to
|
63
|
+
raise "multiple logs not allowed"
|
64
|
+
end
|
65
|
+
Dex::UI::StdoutRouter.duplicate_output_to = File.open(path, 'w')
|
66
|
+
yield
|
67
|
+
ensure
|
68
|
+
f = Dex::UI::StdoutRouter.duplicate_output_to
|
69
|
+
f.close
|
70
|
+
Dex::UI::StdoutRouter.duplicate_output_to = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.raw
|
74
|
+
prev = Thread.current[:no_dexui_frame_inset]
|
75
|
+
Thread.current[:no_dexui_frame_inset] = true
|
76
|
+
yield
|
77
|
+
ensure
|
78
|
+
Thread.current[:no_dexui_frame_inset] = prev
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
require 'dex/ui/stdout_router'
|
data/lib/helpers/doc.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module MethodAddedHook
|
2
|
+
private
|
3
|
+
|
4
|
+
def method_added(meth)
|
5
|
+
method_added_hook(meth)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def singleton_method_added(meth)
|
10
|
+
method_added_hook(meth)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_added_hook(meth)
|
15
|
+
@@__last_defined_doc__ ||= nil
|
16
|
+
return if !defined?(@@__last_defined_doc__) || @@__last_defined_doc__.nil?
|
17
|
+
@@__class_docs__ ||= {}
|
18
|
+
@@__class_docs__[self.to_s] ||= {}
|
19
|
+
|
20
|
+
@@__class_docs__[self.to_s][meth] = @@__last_defined_doc__
|
21
|
+
@@__last_defined_doc__ = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Module
|
26
|
+
private
|
27
|
+
prepend MethodAddedHook
|
28
|
+
|
29
|
+
def doc(str, meth = nil)
|
30
|
+
return @@__class_docs__[self.to_s][meth] = str if meth
|
31
|
+
@@__last_defined_doc__ = str
|
32
|
+
end
|
33
|
+
|
34
|
+
def defdoc(str, meth, &block)
|
35
|
+
@@__class_docs__[self.to_s][meth] = str
|
36
|
+
define_method(meth, &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Kernel
|
41
|
+
def get_doc(klass, meth)
|
42
|
+
docs = klass.class_variable_get(:@@__class_docs__)
|
43
|
+
docs[self.to_s][meth.to_sym] if docs && docs[self.to_s]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
class String
|
49
|
+
# The following methods is taken from activesupport
|
50
|
+
#
|
51
|
+
# https://github.com/rails/rails/blob/d66e7835bea9505f7003e5038aa19b6ea95ceea1/activesupport/lib/active_support/core_ext/string/strip.rb
|
52
|
+
#
|
53
|
+
# All credit for this method goes to the original authors.
|
54
|
+
# The code is used under the MIT license.
|
55
|
+
#
|
56
|
+
# Strips indentation by removing the amount of leading whitespace in the least indented
|
57
|
+
# non-empty line in the whole string
|
58
|
+
#
|
59
|
+
def strip_heredoc
|
60
|
+
self.gsub(/^#{self.scan(/^[ \t]*(?=\S)/).min}/, "".freeze)
|
61
|
+
end
|
62
|
+
end
|
Binary file
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'spotify_cli/app'
|
2
|
+
require 'helpers/doc'
|
3
|
+
|
4
|
+
module SpotifyCli
|
5
|
+
class Api
|
6
|
+
PLAY = "▶"
|
7
|
+
STOP = "◼"
|
8
|
+
|
9
|
+
SPOTIFY_SEARCH_API = "https://api.spotify.com/v1/search"
|
10
|
+
|
11
|
+
class << self
|
12
|
+
doc <<-EOF
|
13
|
+
Changes to the next song
|
14
|
+
|
15
|
+
{{bold:Usage:}}
|
16
|
+
{{command:spotify next}}
|
17
|
+
EOF
|
18
|
+
def next
|
19
|
+
puts "Playing next song"
|
20
|
+
SpotifyCli::App.next!
|
21
|
+
end
|
22
|
+
|
23
|
+
doc <<-EOF
|
24
|
+
Changes to the previous song
|
25
|
+
|
26
|
+
{{bold:Usage:}}
|
27
|
+
{{command:spotify previous}}
|
28
|
+
EOF
|
29
|
+
def previous
|
30
|
+
puts "Playing previous song"
|
31
|
+
SpotifyCli::App.prev!
|
32
|
+
end
|
33
|
+
|
34
|
+
doc <<-EOF
|
35
|
+
Sets the position in the song
|
36
|
+
|
37
|
+
{{bold:Usage:}}
|
38
|
+
{{command:spotify set_pos 60}}
|
39
|
+
EOF
|
40
|
+
def set_pos
|
41
|
+
puts "Setting position to #{ARGV[1]}"
|
42
|
+
SpotifyCli::App.set_pos!(ARGV[1])
|
43
|
+
end
|
44
|
+
|
45
|
+
doc <<-EOF
|
46
|
+
Replays the current song
|
47
|
+
|
48
|
+
{{bold:Usage:}}
|
49
|
+
{{command:spotify replay}}
|
50
|
+
EOF
|
51
|
+
def replay
|
52
|
+
puts "Restarting song"
|
53
|
+
SpotifyCli::App.replay!
|
54
|
+
end
|
55
|
+
|
56
|
+
doc <<-EOF
|
57
|
+
Play/Pause the current song, or play a specified artist,
|
58
|
+
track, album, or uri
|
59
|
+
|
60
|
+
{{bold:Usage:}}
|
61
|
+
{{command:spotify play artist [name]}}
|
62
|
+
{{command:spotify play track [name]}}
|
63
|
+
{{command:spotify play album [name]}}
|
64
|
+
{{command:spotify play uri [spotify uri]}}
|
65
|
+
EOF
|
66
|
+
def play_pause
|
67
|
+
args = ARGV[1..-1]
|
68
|
+
|
69
|
+
if args.empty?
|
70
|
+
# no specifying paremeter, this is a standard play/pause
|
71
|
+
SpotifyCli::App.play_pause!
|
72
|
+
status
|
73
|
+
return
|
74
|
+
end
|
75
|
+
|
76
|
+
arg = args.shift
|
77
|
+
type = arg == 'song' ? 'track' : arg
|
78
|
+
|
79
|
+
Dex::UI.frame("Searching for #{type}", timing: false) do
|
80
|
+
play_uri = case type
|
81
|
+
when 'album', 'artist', 'track'
|
82
|
+
results = search_and_play(type: type, query: args.join(' '))
|
83
|
+
results.first
|
84
|
+
when 'uri'
|
85
|
+
args.first
|
86
|
+
end
|
87
|
+
puts "Results found, playing"
|
88
|
+
SpotifyCli::App.play_uri!(play_uri)
|
89
|
+
sleep 0.05 # Give time for the app to switch otherwise status may be stale
|
90
|
+
end
|
91
|
+
|
92
|
+
status
|
93
|
+
end
|
94
|
+
|
95
|
+
doc <<-EOF
|
96
|
+
Pause/stop the current song
|
97
|
+
|
98
|
+
{{bold:Usage:}}
|
99
|
+
{{command:spotify pause}}
|
100
|
+
{{command:spotify stop}}
|
101
|
+
EOF
|
102
|
+
def pause
|
103
|
+
SpotifyCli::App.pause!
|
104
|
+
status
|
105
|
+
end
|
106
|
+
|
107
|
+
doc <<-EOF
|
108
|
+
Show the current song
|
109
|
+
|
110
|
+
{{bold:Usage:}}
|
111
|
+
{{command:spotify status}}
|
112
|
+
EOF
|
113
|
+
def status
|
114
|
+
stat = SpotifyCli::App.status
|
115
|
+
|
116
|
+
time = "#{stat[:position]} / #{stat[:duration]}"
|
117
|
+
state_sym = case stat[:state]
|
118
|
+
when 'playing'
|
119
|
+
PLAY
|
120
|
+
else
|
121
|
+
STOP
|
122
|
+
end
|
123
|
+
# 3 for padding around time, and symbol, and space for the symbol, 2 for frame
|
124
|
+
width = Dex::UI::Terminal.width - time.size - 5
|
125
|
+
|
126
|
+
Dex::UI.frame(stat[:track], timing: false) do
|
127
|
+
puts Dex::UI.resolve_text([
|
128
|
+
"{{bold:Artist:}} #{stat[:artist]}",
|
129
|
+
"{{bold:Album:}} #{stat[:album]}",
|
130
|
+
].join("\n"))
|
131
|
+
puts [
|
132
|
+
Dex::UI::Progress.progress(stat[:percent_done], width),
|
133
|
+
state_sym,
|
134
|
+
time
|
135
|
+
].join(' ')
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
doc <<-EOF
|
140
|
+
Display Help
|
141
|
+
|
142
|
+
{{bold:Usage:}}
|
143
|
+
{{command:spotify}}
|
144
|
+
{{command:spotify help}}
|
145
|
+
EOF
|
146
|
+
def help(mappings)
|
147
|
+
Dex::UI.frame('Spotify CLI', timing: false) do
|
148
|
+
puts "CLI interface for Spotify"
|
149
|
+
end
|
150
|
+
|
151
|
+
mappings.group_by { |_,v| v }.each do |k, v|
|
152
|
+
v.reject! { |mapping| mapping.first == k.to_s }
|
153
|
+
doc = get_doc(self.class, k.to_s).strip_heredoc
|
154
|
+
|
155
|
+
Dex::UI.frame(k, timing: false) do
|
156
|
+
puts puts Dex::UI.resolve_text(doc)
|
157
|
+
next if v.empty?
|
158
|
+
puts Dex::UI.resolve_text("{{bold:Aliases:}}")
|
159
|
+
v.each { |mapping| puts Dex::UI.resolve_text(" - {{info:#{mapping.first}}}") }
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def search_and_play(args)
|
167
|
+
type = args[:type]
|
168
|
+
type2 = args[:type2] || type
|
169
|
+
query = args[:query]
|
170
|
+
limit = args[:limit] || 1
|
171
|
+
puts "Searching #{type}s for: #{query}";
|
172
|
+
|
173
|
+
curl_cmd = <<-EOF
|
174
|
+
curl -s -G #{SPOTIFY_SEARCH_API} --data-urlencode "q=#{query}" -d "type=#{type}&limit=#{limit}&offset=0" -H "Accept: application/json" \
|
175
|
+
| grep -E -o "spotify:#{type2}:[a-zA-Z0-9]+" -m #{limit}
|
176
|
+
EOF
|
177
|
+
|
178
|
+
`#{curl_cmd}`.strip.split("\n")
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module SpotifyCli
|
2
|
+
class App
|
3
|
+
def self.state
|
4
|
+
oascript('tell application "Spotify" to player state as string')
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.status
|
8
|
+
artist = oascript('tell application "Spotify" to artist of current track as string')
|
9
|
+
album = oascript('tell application "Spotify" to album of current track as string')
|
10
|
+
track = oascript('tell application "Spotify" to name of current track as string')
|
11
|
+
duration = oascript(<<-EOF)
|
12
|
+
tell application "Spotify"
|
13
|
+
set durSec to (duration of current track / 1000) as text
|
14
|
+
set tM to (round (durSec / 60) rounding down) as text
|
15
|
+
if length of ((durSec mod 60 div 1) as text) is greater than 1 then
|
16
|
+
set tS to (durSec mod 60 div 1) as text
|
17
|
+
else
|
18
|
+
set tS to ("0" & (durSec mod 60 div 1)) as text
|
19
|
+
end if
|
20
|
+
set myTime to tM as text & ":" & tS as text
|
21
|
+
end tell
|
22
|
+
return myTime
|
23
|
+
EOF
|
24
|
+
position = oascript(<<-EOF)
|
25
|
+
tell application "Spotify"
|
26
|
+
set pos to player position
|
27
|
+
set nM to (round (pos / 60) rounding down) as text
|
28
|
+
if length of ((round (pos mod 60) rounding down) as text) is greater than 1 then
|
29
|
+
set nS to (round (pos mod 60) rounding down) as text
|
30
|
+
else
|
31
|
+
set nS to ("0" & (round (pos mod 60) rounding down)) as text
|
32
|
+
end if
|
33
|
+
set nowAt to nM as text & ":" & nS as text
|
34
|
+
end tell
|
35
|
+
return nowAt
|
36
|
+
EOF
|
37
|
+
|
38
|
+
{
|
39
|
+
state: state,
|
40
|
+
artist: artist,
|
41
|
+
album: album,
|
42
|
+
track: track,
|
43
|
+
duration: duration,
|
44
|
+
position: position,
|
45
|
+
percent_done: percent_done(position, duration)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.play_pause!
|
50
|
+
oascript('tell application "Spotify" to playpause')
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.pause!
|
54
|
+
oascript('tell application "Spotify" to pause')
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.play_uri!(uri)
|
58
|
+
oascript("tell application \"Spotify\" to play track \"#{uri}\"")
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.next!
|
62
|
+
oascript('tell application "Spotify" to next track')
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.set_pos!(pos)
|
66
|
+
oascript("tell application \"Spotify\" to set player position to #{pos}")
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.previous!
|
70
|
+
oascript(<<-EOF)
|
71
|
+
tell application "Spotify"
|
72
|
+
set player position to 0
|
73
|
+
previous track
|
74
|
+
end tell
|
75
|
+
EOF
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.replay!
|
79
|
+
oascript('tell application "Spotify" to set player position to 0')
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.percent_done(position, duration)
|
83
|
+
seconds = ->(parts) do
|
84
|
+
acc = 0
|
85
|
+
multiplier = 1
|
86
|
+
while part = parts.shift
|
87
|
+
acc += part.to_f * multiplier
|
88
|
+
multiplier *= 60
|
89
|
+
end
|
90
|
+
acc
|
91
|
+
end
|
92
|
+
pos_parts = position.split(':').reverse
|
93
|
+
dur_parts = duration.split(':').reverse
|
94
|
+
seconds.call(pos_parts) / seconds.call(dur_parts)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.oascript(command)
|
98
|
+
`osascript -e '#{command}'`.strip
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/spotify_cli.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spotify_cli/version'
|
2
|
+
require 'dex/ui'
|
3
|
+
require 'spotify_cli/api'
|
4
|
+
|
5
|
+
module SpotifyCli
|
6
|
+
def self.call(args)
|
7
|
+
mappings = {
|
8
|
+
'next' => :next,
|
9
|
+
'n' => :next,
|
10
|
+
'previous' => :previous,
|
11
|
+
'pr' => :previous,
|
12
|
+
'set_pos' => :set_pos,
|
13
|
+
'pos' => :set_pos,
|
14
|
+
'replay' => :replay,
|
15
|
+
'rep' => :replay,
|
16
|
+
'restart' => :replay,
|
17
|
+
'pause' => :pause,
|
18
|
+
'stop' => :pause,
|
19
|
+
'play' => :play_pause,
|
20
|
+
'p' => :play_pause,
|
21
|
+
'play_pause' => :play_pause,
|
22
|
+
'status' => :status,
|
23
|
+
's' => :status,
|
24
|
+
'help' => :help
|
25
|
+
}
|
26
|
+
|
27
|
+
if args.empty?
|
28
|
+
SpotifyCli::Api.status
|
29
|
+
else
|
30
|
+
mapping = mappings[args.first]
|
31
|
+
if mapping.nil? || mapping == :help
|
32
|
+
SpotifyCli::Api.help(mappings)
|
33
|
+
else
|
34
|
+
SpotifyCli::Api.send(mapping)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spotify_cli.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'spotify_cli/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "spotify_cli"
|
8
|
+
spec.version = SpotifyCli::VERSION
|
9
|
+
spec.authors = ["Julian Nadeau"]
|
10
|
+
spec.email = ["julian@jnadeau.ca"]
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
spec.summary = "Spotify Application wrapper for control via command line"
|
14
|
+
spec.description = "Allow control of Spotify using a pretty UI interface. Intentionally simple."
|
15
|
+
spec.homepage = "https://github.com/jules2689/spotify_cli"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
"public gem pushes."
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = "bin"
|
30
|
+
spec.executables = ['spotify']
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
34
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
35
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spotify_cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julian Nadeau
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
description: Allow control of Spotify using a pretty UI interface. Intentionally simple.
|
56
|
+
email:
|
57
|
+
- julian@jnadeau.ca
|
58
|
+
executables:
|
59
|
+
- spotify
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".DS_Store"
|
64
|
+
- ".gitignore"
|
65
|
+
- ".travis.yml"
|
66
|
+
- CODE_OF_CONDUCT.md
|
67
|
+
- Gemfile
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- bin/spotify
|
73
|
+
- lib/dex/ui.rb
|
74
|
+
- lib/dex/ui/README.md
|
75
|
+
- lib/dex/ui/ansi.rb
|
76
|
+
- lib/dex/ui/box.rb
|
77
|
+
- lib/dex/ui/color.rb
|
78
|
+
- lib/dex/ui/formatter.rb
|
79
|
+
- lib/dex/ui/frame.rb
|
80
|
+
- lib/dex/ui/glyph.rb
|
81
|
+
- lib/dex/ui/progress.rb
|
82
|
+
- lib/dex/ui/prompt.rb
|
83
|
+
- lib/dex/ui/spinner.rb
|
84
|
+
- lib/dex/ui/stdout_router.rb
|
85
|
+
- lib/dex/ui/terminal.rb
|
86
|
+
- lib/helpers/doc.rb
|
87
|
+
- lib/spotify_cli.rb
|
88
|
+
- lib/spotify_cli/.DS_Store
|
89
|
+
- lib/spotify_cli/api.rb
|
90
|
+
- lib/spotify_cli/app.rb
|
91
|
+
- lib/spotify_cli/version.rb
|
92
|
+
- spotify_cli.gemspec
|
93
|
+
homepage: https://github.com/jules2689/spotify_cli
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
metadata:
|
97
|
+
allowed_push_host: https://rubygems.org
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.5.1
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: Spotify Application wrapper for control via command line
|
118
|
+
test_files: []
|