listlace 0.2.0 → 0.2.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +71 -25
- data/lib/listlace.rb +3 -2
- data/lib/listlace/commands.rb +10 -1
- data/lib/listlace/core_ext/array.rb +25 -1
- data/lib/listlace/selectors.rb +69 -23
- data/lib/listlace/time_helpers.rb +34 -0
- data/listlace.gemspec +2 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81dd49a642dc3679a690ddc7b4107d614d2356c6
|
4
|
+
data.tar.gz: 49cfdc7c09b8e1835346c3d334796e2a25a81287
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1894c03f8bcc99d20c212bbc8c2b80ae37cf4ebe67bd44e6053cd64eb22a19223a7f2fbbf44fd22a40bebdf2a82cbe51e985d629c2811401dd277ad1fabbc2a9
|
7
|
+
data.tar.gz: fe454c5393bdedf4acbc145393ea94c83726670eeaf7b243e6b7df7f62fdf856ccec7a377427082117bc22d15eebde2de6f80559df5900b8924ad1d0964fbd68
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -26,7 +26,25 @@ Query your music library and build playlists using selectors. There are selector
|
|
26
26
|
♫> artist "thirsty cups"
|
27
27
|
=> [8 songs]
|
28
28
|
|
29
|
-
It returned an Array of 8 Songs.
|
29
|
+
It returned an Array of 8 Songs.
|
30
|
+
|
31
|
+
You can pass multiple queries to a selector, and it will select all songs that match **any** of those queries:
|
32
|
+
|
33
|
+
♫> _why = artist(:the_thirsty_cups, :moonboots, :the_child_who_was_a_keyhole)
|
34
|
+
=> [33 songs]
|
35
|
+
|
36
|
+
You can chain selectors together to narrow down a playlist:
|
37
|
+
|
38
|
+
♫> _why.album(:elfin_princess).title(/^the/)
|
39
|
+
=> [1 song]
|
40
|
+
|
41
|
+
### String selectors
|
42
|
+
|
43
|
+
The string selectors are: `title`, `artist`, `album`, and `genre`.
|
44
|
+
|
45
|
+
String selectors have "exact" counterparts: `title_exact`, `artist_exact`, `album_exact`, and `genre_exact`.
|
46
|
+
|
47
|
+
Use these "exact" selectors to do an exact, case-sensitive search:
|
30
48
|
|
31
49
|
♫> artist_exact "thirsty cups"
|
32
50
|
=> []
|
@@ -35,7 +53,7 @@ It returned an Array of 8 Songs. To do an exact, case-sensitive search, use `art
|
|
35
53
|
|
36
54
|
The first one didn't match anything, the second one matched the same 8 songs.
|
37
55
|
|
38
|
-
You can also pass in a `Regexp` or a `Symbol
|
56
|
+
You can also pass in a `Regexp` or a `Symbol` to a string selector:
|
39
57
|
|
40
58
|
♫> artist :the_thirsty_cups
|
41
59
|
=> [8 songs]
|
@@ -44,9 +62,39 @@ You can also pass in a `Regexp` or a `Symbol`:
|
|
44
62
|
|
45
63
|
Underscores in symbols are interpreted as spaces.
|
46
64
|
|
47
|
-
|
65
|
+
### Numeric selectors
|
66
|
+
|
67
|
+
The numeric selectors are: `track`, `disc`, and `year`.
|
68
|
+
|
69
|
+
In addition to `Integers`, you can pass a `Range` to match against or a `Hash` that specifies one or more comparison operators:
|
70
|
+
|
71
|
+
♫> year 2000, 2004
|
72
|
+
=> [362 songs]
|
73
|
+
♫> year 1990..1999
|
74
|
+
=> [521 songs]
|
75
|
+
♫> year lt: 1970
|
76
|
+
=> [76 songs]
|
77
|
+
|
78
|
+
The comparison operators are `:eq`, `:ne`, `:gt`, `:ge`, `:lt`, and `:le`. You can also just use `:==`, `:>`, `:>=`, `:<`, and `:<=`.
|
79
|
+
|
80
|
+
### Time selectors
|
81
|
+
|
82
|
+
The time selector is just `time`, which matches the length of songs in seconds.
|
48
83
|
|
49
|
-
|
84
|
+
It's basically a numeric selector, but you can pass a String containing a formatted time in place of a number:
|
85
|
+
|
86
|
+
♫> time "1:23"
|
87
|
+
=> [2 songs]
|
88
|
+
♫> time "8:00"..."9:00"
|
89
|
+
=> [79 songs]
|
90
|
+
♫> time gt: "20:00"
|
91
|
+
=> [5 songs]
|
92
|
+
♫> time lt: 20
|
93
|
+
=> [27 songs]
|
94
|
+
|
95
|
+
"1:23" means 1 minute and 23 seconds, and "1:23:45" means 1 hour, 23 minutes, 45 seconds. If you just pass an integer, it is the number of seconds.
|
96
|
+
|
97
|
+
### Special selectors
|
50
98
|
|
51
99
|
### all
|
52
100
|
|
@@ -62,20 +110,6 @@ In addition to tag selectors, here are some special selectors:
|
|
62
110
|
♫> none
|
63
111
|
=> []
|
64
112
|
|
65
|
-
### search
|
66
|
-
|
67
|
-
`search` will match **any** tag that contains your query.
|
68
|
-
|
69
|
-
♫> search "thirsty"
|
70
|
-
=> [8 songs]
|
71
|
-
|
72
|
-
### where, where\_exact
|
73
|
-
|
74
|
-
`where` and `where_exact` let you specify multiple queries for different tags:
|
75
|
-
|
76
|
-
♫> where(artist: "thirsty", title: "belljar")
|
77
|
-
=> [1 song]
|
78
|
-
|
79
113
|
## Commands
|
80
114
|
|
81
115
|
### p
|
@@ -105,15 +139,15 @@ In addition to tag selectors, here are some special selectors:
|
|
105
139
|
`list` with no arguments lists all the songs in your music library. If you pass it a playlist, it will list all the songs in that playlist.
|
106
140
|
|
107
141
|
♫> list
|
108
|
-
Air - 10 000 Hz Legend - Electronic Performers
|
109
|
-
Air - 10 000 Hz Legend - How Does It Make You Feel?
|
110
|
-
Air - 10 000 Hz Legend - Radio #1
|
142
|
+
Air - 10 000 Hz Legend - Electronic Performers (5:36)
|
143
|
+
Air - 10 000 Hz Legend - How Does It Make You Feel? (4:38)
|
144
|
+
Air - 10 000 Hz Legend - Radio #1 (4:23)
|
111
145
|
...
|
112
146
|
♫> list title :fish
|
113
|
-
Eisley - Currents - Blue Fish
|
114
|
-
Moonboots - Elfin Princess - The Fish Said Hello
|
115
|
-
Radiohead - In Rainbows - Weird Fishes/Arpeggi
|
116
|
-
Thee More Shallows - A History of Sport Fishing - A History of Sport Fishing
|
147
|
+
Eisley - Currents - Blue Fish (4:02)
|
148
|
+
Moonboots - Elfin Princess - The Fish Said Hello (2:44)
|
149
|
+
Radiohead - In Rainbows - Weird Fishes/Arpeggi (5:18)
|
150
|
+
Thee More Shallows - A History of Sport Fishing - A History of Sport Fishing (7:00)
|
117
151
|
|
118
152
|
### artists
|
119
153
|
|
@@ -158,6 +192,18 @@ In addition to tag selectors, here are some special selectors:
|
|
158
192
|
♫> genres title(:constantinople)
|
159
193
|
TMBG (1 song)
|
160
194
|
|
195
|
+
### years
|
196
|
+
|
197
|
+
`years` with no arguments lists all the years in your music library. If you pass it a playlist, it will list all the years in that playlist.
|
198
|
+
|
199
|
+
♫> years
|
200
|
+
2007 (205 songs)
|
201
|
+
2010 (220 songs)
|
202
|
+
2012 (148 songs)
|
203
|
+
...
|
204
|
+
♫> years artist :the_avalanches
|
205
|
+
2000 (18 songs)
|
206
|
+
|
161
207
|
### mpd
|
162
208
|
|
163
209
|
`mpd` gives you an instance of the `MPDClient` object, which you can use to send any `mpd` command that isn't available in Listlace yet. This'll probably be removed once I actually implement all the commands.
|
data/lib/listlace.rb
CHANGED
data/lib/listlace/commands.rb
CHANGED
@@ -39,7 +39,8 @@ class Listlace
|
|
39
39
|
|
40
40
|
def list(playlist = nil)
|
41
41
|
(playlist || all).each do |song|
|
42
|
-
|
42
|
+
time = TimeHelpers.format_time(song.time)
|
43
|
+
puts "#{song.artist} - #{song.album} - #{song.title} (#{time})"
|
43
44
|
end
|
44
45
|
nil
|
45
46
|
end
|
@@ -67,6 +68,14 @@ class Listlace
|
|
67
68
|
end
|
68
69
|
nil
|
69
70
|
end
|
71
|
+
|
72
|
+
def years(playlist = nil)
|
73
|
+
(playlist || all).group_by(&:date).each do |year, songs|
|
74
|
+
plural = (songs.length == 1) ? "" : "s"
|
75
|
+
puts "#{year} (#{songs.length} song#{plural})"
|
76
|
+
end
|
77
|
+
nil
|
78
|
+
end
|
70
79
|
end
|
71
80
|
end
|
72
81
|
|
@@ -2,7 +2,31 @@ class Array
|
|
2
2
|
def playlist?
|
3
3
|
@is_playlist ||= all? { |x| x.is_a? MPD::Song }
|
4
4
|
end
|
5
|
-
|
5
|
+
|
6
|
+
Listlace::Selectors::STRING_SELECTORS.each do |tag|
|
7
|
+
define_method(tag) do |*queries|
|
8
|
+
Listlace::Selectors.string_selector(tag, self, false, *queries)
|
9
|
+
end
|
10
|
+
|
11
|
+
define_method("#{tag}_exact") do |*queries|
|
12
|
+
Listlace::Selectors.string_selector(tag, self, true, *queries)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Listlace::Selectors::NUMERIC_SELECTORS.each do |tag|
|
17
|
+
define_method(tag) do |*queries|
|
18
|
+
Listlace::Selectors.numeric_selector(tag, self, *queries)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Listlace::Selectors::TIME_SELECTORS.each do |tag|
|
23
|
+
define_method(tag) do |*queries|
|
24
|
+
Listlace::Selectors.time_selector(tag, self, *queries)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
alias year date
|
29
|
+
|
6
30
|
alias _original_inspect inspect
|
7
31
|
def inspect
|
8
32
|
if playlist?
|
data/lib/listlace/selectors.rb
CHANGED
@@ -1,43 +1,89 @@
|
|
1
1
|
class Listlace
|
2
2
|
module Selectors
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
all.select { |song| song.send(tag).to_s =~ what }
|
11
|
-
when Symbol
|
12
|
-
mpd.where(tag => what.to_s.tr("_", " "))
|
13
|
-
when String
|
14
|
-
mpd.where(tag => what)
|
15
|
-
end
|
3
|
+
STRING_SELECTORS = %w(title artist album genre)
|
4
|
+
NUMERIC_SELECTORS = %w(track date disc)
|
5
|
+
TIME_SELECTORS = %w(time)
|
6
|
+
|
7
|
+
(STRING_SELECTORS | NUMERIC_SELECTORS | TIME_SELECTORS).each do |tag|
|
8
|
+
define_method(tag) do |*queries|
|
9
|
+
all.send(tag, *queries)
|
16
10
|
end
|
17
11
|
|
18
|
-
|
19
|
-
|
12
|
+
if STRING_SELECTORS.include? tag
|
13
|
+
define_method("#{tag}_exact") do |*queries|
|
14
|
+
all.send("#{tag}_exact", *queries)
|
15
|
+
end
|
20
16
|
end
|
21
17
|
end
|
22
18
|
|
19
|
+
alias year date
|
20
|
+
|
23
21
|
def all
|
24
|
-
mpd.songs
|
22
|
+
@all ||= mpd.songs
|
25
23
|
end
|
26
24
|
|
27
25
|
def none
|
28
26
|
[]
|
29
27
|
end
|
30
28
|
|
31
|
-
def
|
32
|
-
|
29
|
+
def self.string_selector(tag, playlist, exact, *queries)
|
30
|
+
if exact
|
31
|
+
playlist.select { |song| song.send(tag).to_s == query.to_s }
|
32
|
+
else
|
33
|
+
queries.map do |query|
|
34
|
+
case query
|
35
|
+
when Regexp
|
36
|
+
query = Regexp.new(query.source, Regexp::IGNORECASE) # case-insensitize
|
37
|
+
playlist.select { |song| song.send(tag).to_s =~ query }
|
38
|
+
when Symbol
|
39
|
+
playlist.select { |song| song.send(tag).to_s.downcase[query.to_s.downcase.tr("_", " ")] }
|
40
|
+
when String
|
41
|
+
playlist.select { |song| song.send(tag).to_s.downcase[query.downcase] }
|
42
|
+
end
|
43
|
+
end.inject(:|)
|
44
|
+
end
|
33
45
|
end
|
34
|
-
|
35
|
-
def
|
36
|
-
|
46
|
+
|
47
|
+
def self.numeric_selector(tag, playlist, *queries)
|
48
|
+
queries.map do |query|
|
49
|
+
case query
|
50
|
+
when Numeric
|
51
|
+
playlist.select { |song| song.send(tag) == query }
|
52
|
+
when Hash
|
53
|
+
query.map do |op, value|
|
54
|
+
op = { eq: "==", ne: "!=", lt: "<", le: "<=", gt: ">", ge: ">=" }[op] || op
|
55
|
+
playlist.select { |song| song.send(tag).to_i.send(op, value) }
|
56
|
+
end.inject(:&)
|
57
|
+
when Range
|
58
|
+
playlist.select { |song| query === song.send(tag) }
|
59
|
+
end
|
60
|
+
end.inject(:|)
|
37
61
|
end
|
38
62
|
|
39
|
-
def
|
40
|
-
|
63
|
+
def self.time_selector(tag, playlist, *queries)
|
64
|
+
queries.map! do |query|
|
65
|
+
case query
|
66
|
+
when String
|
67
|
+
TimeHelpers.parse_time(query)
|
68
|
+
when Range
|
69
|
+
Range.new(
|
70
|
+
TimeHelpers.parse_time(query.begin.to_s),
|
71
|
+
TimeHelpers.parse_time(query.end.to_s),
|
72
|
+
query.exclude_end?
|
73
|
+
)
|
74
|
+
when Hash
|
75
|
+
query.each do |op, value|
|
76
|
+
if value.is_a? String
|
77
|
+
query[op] = TimeHelpers.parse_time(value)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
query
|
81
|
+
else
|
82
|
+
query
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
numeric_selector(tag, playlist, *queries)
|
41
87
|
end
|
42
88
|
end
|
43
89
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Listlace
|
2
|
+
module TimeHelpers
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# Helper method to format a number of seconds as a string like "1:03:56".
|
6
|
+
def format_time(seconds, options = {})
|
7
|
+
raise ArgumentError, "can't format negative time" if seconds < 0
|
8
|
+
|
9
|
+
hours = seconds / 3600
|
10
|
+
minutes = (seconds / 60) % 60
|
11
|
+
seconds = seconds % 60
|
12
|
+
|
13
|
+
if hours > 0
|
14
|
+
"%d:%02d:%02d" % [hours, minutes, seconds]
|
15
|
+
else
|
16
|
+
"%d:%02d" % [minutes, seconds]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Helper method to parse a string like "1:03:56" and return the number of
|
21
|
+
# seconds that time length represents.
|
22
|
+
def parse_time(string)
|
23
|
+
parts = string.split(":", -1).map(&:to_i)
|
24
|
+
|
25
|
+
raise ArgumentError, "too many parts" if parts.length > 3
|
26
|
+
raise ArgumentError, "can't parse negative numbers" if parts.any? { |x| x < 0 }
|
27
|
+
|
28
|
+
parts.unshift(0) until parts.length == 3
|
29
|
+
hours, minutes, seconds = parts
|
30
|
+
hours * 3600 + minutes * 60 + seconds
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
data/listlace.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "listlace"
|
3
|
-
s.version = "0.2.
|
4
|
-
s.date = "2014-03-
|
3
|
+
s.version = "0.2.1"
|
4
|
+
s.date = "2014-03-24"
|
5
5
|
s.summary = "An mpd (music player daemon) client with a Ruby shell as the interface."
|
6
6
|
s.description = "Listlace is an mpd (music player daemon) client with a Ruby shell as the interface."
|
7
7
|
s.author = "Jeremy Ruten"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: listlace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Ruten
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -83,6 +83,7 @@ files:
|
|
83
83
|
- lib/listlace/commands.rb
|
84
84
|
- lib/listlace/core_ext/array.rb
|
85
85
|
- lib/listlace/selectors.rb
|
86
|
+
- lib/listlace/time_helpers.rb
|
86
87
|
- listlace.gemspec
|
87
88
|
homepage: http://github.com/yjerem/listlace
|
88
89
|
licenses:
|