listlace 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5e2d5c543763b463ddeb7d210dd9462eaca05944
4
- data.tar.gz: a91502ded9981eade141ba10f7ee90b4fdf0e9ee
3
+ metadata.gz: 81dd49a642dc3679a690ddc7b4107d614d2356c6
4
+ data.tar.gz: 49cfdc7c09b8e1835346c3d334796e2a25a81287
5
5
  SHA512:
6
- metadata.gz: da90361e229cf18c7f273ea58fa1bae79f2ae6e7f8b3fbfbf9d7e7163798c3653299562602863030a36937c0b98fd9a57b785ce119e707992a9a34fd0057142b
7
- data.tar.gz: 116b0aa40716b9cde245cb3042fcf0efd37b89a119f17fcbe36ad8697de08c30fa7f72966d73e1378d78e5d9f60337c3f7fbdd0519c42a438e11de7d77189823
6
+ metadata.gz: 1894c03f8bcc99d20c212bbc8c2b80ae37cf4ebe67bd44e6053cd64eb22a19223a7f2fbbf44fd22a40bebdf2a82cbe51e985d629c2811401dd277ad1fabbc2a9
7
+ data.tar.gz: fe454c5393bdedf4acbc145393ea94c83726670eeaf7b243e6b7df7f62fdf856ccec7a377427082117bc22d15eebde2de6f80559df5900b8924ad1d0964fbd68
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- listlace (0.1.0)
4
+ listlace (0.2.0)
5
5
  bundler
6
6
  pry
7
7
  ruby-mpd
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. To do an exact, case-sensitive search, use `artist_exact`:
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
- Here's a list of all tag selectors: `title`, `title_exact`, `artist`, `artist_exact`, `album`, `album_exact`, `genre`, and `genre_exact`.
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
- In addition to tag selectors, here are some special selectors:
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
@@ -1,9 +1,10 @@
1
1
  require "ruby-mpd"
2
2
 
3
- require "listlace/core_ext/array"
4
-
5
3
  require "listlace/commands"
6
4
  require "listlace/selectors"
5
+ require "listlace/time_helpers"
6
+
7
+ require "listlace/core_ext/array"
7
8
 
8
9
  class Listlace
9
10
  attr_reader :mpd
@@ -39,7 +39,8 @@ class Listlace
39
39
 
40
40
  def list(playlist = nil)
41
41
  (playlist || all).each do |song|
42
- puts "#{song.artist} - #{song.album} - #{song.title}"
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?
@@ -1,43 +1,89 @@
1
1
  class Listlace
2
2
  module Selectors
3
- TAG_SELECTORS = %w(title artist album genre)
4
-
5
- TAG_SELECTORS.each do |tag|
6
- define_method(tag) do |what|
7
- case what
8
- when Regexp
9
- what = Regexp.new(what.source, Regexp::IGNORECASE) # case-insensitize
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
- define_method("#{tag}_exact") do |what|
19
- mpd.where({tag => what}, {strict: true})
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 search(what)
32
- mpd.where any: what
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 where(params)
36
- mpd.where(params)
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 where_exact(params)
40
- mpd.where(params, {strict: true})
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.0"
4
- s.date = "2014-03-22"
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.0
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-22 00:00:00.000000000 Z
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: