listlace 0.0.3 → 0.0.4

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.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- listlace (0.0.2)
4
+ listlace (0.0.3)
5
5
  activerecord
6
6
  activesupport
7
7
  open4
data/README CHANGED
@@ -39,7 +39,7 @@ The only way to populate the database right
39
39
  now is to import your iTunes library.
40
40
  That goes a little something like this:
41
41
 
42
- >> import_from_itunes "/Users/jeremy/Music/iTunes/iTunes Music Library.xml"
42
+ >> import :itunes, "/Users/jeremy/Music/iTunes/iTunes Music Library.xml"
43
43
 
44
44
  Now you can play your music.
45
45
 
@@ -75,3 +75,12 @@ itself will start playing the queue.
75
75
  * queue
76
76
  - q: append some tracks to the queue, or get the queue
77
77
  - clear: clear the queue
78
+ - shuffle: shuffle the songs in the queue
79
+ - sort: sort the songs in the queue however you want
80
+ * library
81
+ - import: import another program's music library
82
+ - wipe_library: remove all tracks and playlists from the library
83
+
84
+ -- selectors
85
+
86
+ ...
data/bin/listlace CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "bundler/setup"
4
4
  require "pry"
5
- require "fileutils"
6
5
  require "listlace"
7
6
 
8
7
  unless File.exists? Listlace::DIR
@@ -2,41 +2,84 @@ require "plist"
2
2
  require "active_support/core_ext/string"
3
3
 
4
4
  module Listlace
5
- def import_from_itunes(path_to_xml)
6
- data = Plist::parse_xml(path_to_xml)
7
-
8
- whitelist = Track.new.attributes.keys
9
- data["Tracks"].each do |track_id, row|
10
- # row already contains a hash of attributes almost ready to be passed to
11
- # ActiveRecord. We just need to modify the keys, e.g. change "Play Count"
12
- # to "play_count".
13
- attributes = row.inject({}) do |acc, (key, value)|
14
- attribute = key.gsub(" ", "").underscore
15
- attribute = "original_id" if attribute == "track_id"
16
- acc[attribute] = value if whitelist.include? attribute
17
- acc
18
- end
5
+ # Imports the music library from another program. Currently only iTunes is
6
+ # supported.
7
+ def import(from, path)
8
+ if not File.exists?(path)
9
+ puts "File '%s' doesn't exist." % [path]
10
+ elsif from == :itunes
11
+ puts "Parsing XML..."
12
+ data = Plist::parse_xml(path)
19
13
 
20
- # change iTunes' URL-style locations into simple paths
21
- if attributes["location"] && attributes["location"] =~ /^file:\/\//
22
- attributes["location"] = CGI::unescape(attributes["location"].sub(/^file:\/\/localhost/, ""))
23
- end
14
+ puts "Importing #{data['Tracks'].length} tracks..."
15
+ num_tracks = 0
16
+ whitelist = Track.new.attributes.keys
17
+ data["Tracks"].each do |track_id, row|
18
+ # row already contains a hash of attributes almost ready to be passed to
19
+ # ActiveRecord. We just need to modify the keys, e.g. change "Play Count"
20
+ # to "play_count".
21
+ attributes = row.inject({}) do |acc, (key, value)|
22
+ attribute = key.gsub(" ", "").underscore
23
+ attribute = "original_id" if attribute == "track_id"
24
+ acc[attribute] = value if whitelist.include? attribute
25
+ acc
26
+ end
24
27
 
25
- track = Track.new(attributes)
26
- track.save!
27
- end
28
+ # change iTunes' URL-style locations into simple paths
29
+ if attributes["location"] && attributes["location"] =~ /^file:\/\//
30
+ attributes["location"].sub! /^file:\/\/localhost/, ""
31
+
32
+ # CGI::unescape changes plus signs to spaces. This is a work around to
33
+ # keep the plus signs.
34
+ attributes["location"].gsub! "+", "%2B"
35
+
36
+ attributes["location"] = CGI::unescape(attributes["location"])
37
+ end
28
38
 
29
- data["Playlists"].each do |playlist_data|
30
- playlist = Playlist.new(name: playlist_data["Name"])
31
- playlist.save!
39
+ track = Track.new(attributes)
32
40
 
33
- playlist_data["Playlist Items"].map(&:values).flatten.each.with_index do |track_id, i|
34
- playlist_item = PlaylistItem.new(position: i)
35
- playlist_item.playlist = playlist
36
- if playlist_item.track = Track.where(original_id: track_id).first
37
- playlist_item.save!
41
+ if track.kind =~ /audio/
42
+ if track.save
43
+ num_tracks += 1
44
+ end
45
+ else
46
+ puts "[skipping non-audio file]"
38
47
  end
39
48
  end
49
+ puts "Imported #{num_tracks} tracks successfully."
50
+
51
+ puts "Importing #{data['Playlists'].length} playlists..."
52
+ num_playlists = 0
53
+ data["Playlists"].each do |playlist_data|
54
+ playlist = Playlist.new(name: playlist_data["Name"])
55
+
56
+ if ["Library", "Music", "Movies", "TV Shows", "iTunes DJ"].include? playlist.name
57
+ puts "[skipping \"#{playlist.name}\" playlist]"
58
+ else
59
+ if playlist.save
60
+ playlist_data["Playlist Items"].map(&:values).flatten.each.with_index do |track_id, i|
61
+ playlist_item = PlaylistItem.new(position: i)
62
+ playlist_item.playlist = playlist
63
+ if playlist_item.track = Track.where(original_id: track_id).first
64
+ playlist_item.save!
65
+ end
66
+ end
67
+ num_playlists += 1
68
+ end
69
+ end
70
+ end
71
+ puts "Imported #{num_playlists} playlists successfully."
72
+ end
73
+ end
74
+
75
+ # Wipes the database. With no arguments, it just asks "Are you sure?" without
76
+ # doing anything. To actually wipe the database, pass :yes_im_sure.
77
+ def wipe_library(are_you_sure = :nope)
78
+ if are_you_sure == :yes_im_sure
79
+ Database.wipe
80
+ puts "Library wiped."
81
+ else
82
+ puts "Are you sure? If you are, then type: wipe_library :yes_im_sure"
40
83
  end
41
84
  end
42
85
  end
@@ -48,8 +48,8 @@ module Listlace
48
48
  end
49
49
 
50
50
  # Go back one song in the queue.
51
- def back
52
- if $player.back
51
+ def back(n = 1)
52
+ if $player.back(n)
53
53
  status
54
54
  else
55
55
  puts "End of queue."
@@ -57,8 +57,8 @@ module Listlace
57
57
  end
58
58
 
59
59
  # Go directly to the next song in the queue.
60
- def skip
61
- if $player.skip
60
+ def skip(n = 1)
61
+ if $player.skip(n)
62
62
  status
63
63
  else
64
64
  puts "End of queue."
@@ -114,9 +114,9 @@ module Listlace
114
114
  num_tracks = q.length
115
115
  repeat_one = $player.repeat_mode == :one ? REPEAT_SYMBOL : ""
116
116
  repeat_all = $player.repeat_mode == :all ? REPEAT_SYMBOL : ""
117
- puts "Playlist: queue (%d%s / %d%s)" % [track_number, repeat_one, num_tracks, repeat_all]
117
+ puts "Playlist: %s (%d%s / %d%s)" % [q.name, track_number, repeat_one, num_tracks, repeat_all]
118
118
  else
119
- puts "Playlist: queue (%d songs)" % [q.length]
119
+ puts "Playlist: %s" % [q]
120
120
  end
121
121
  when :playing
122
122
  if $player.started?
@@ -1,21 +1,10 @@
1
1
  module Listlace
2
- # The queue command. Simply appends tracks to the queue. Tracks can be
3
- # specified by a single Track, a Playlist, an ActiveRecord::Relation, or an
4
- # Array containing any of the above. With or without arguments, it returns the
5
- # queue as an Array of Tracks, so this can be used as an accessor method.
6
- def q(*tracks)
7
- tracks.each do |playlist_or_track|
8
- case playlist_or_track
9
- when Track
10
- $player.queue playlist_or_track
11
- when Playlist
12
- q *playlist_or_track.tracks
13
- when Array
14
- q *playlist_or_track
15
- when ActiveRecord::Relation
16
- q *playlist_or_track.all
17
- end
18
- end
2
+ # The queue command. Simply appends tracks to the queue. It creates a playlist
3
+ # with the arguments you give it, so anything you can pass to the playlist()
4
+ # method you can pass to this. It returns the queue, so you can use this
5
+ # method as an accessor by not passing any arguments.
6
+ def q(*args)
7
+ $player.queue playlist(*args)
19
8
  $player.queue
20
9
  end
21
10
 
@@ -25,11 +14,38 @@ module Listlace
25
14
  puts "Queue cleared."
26
15
  end
27
16
 
17
+ # Shuffles the queue, keeping the current track at the top.
28
18
  def shuffle
29
-
19
+ $player.shuffle
20
+ puts "Shuffled."
30
21
  end
31
22
 
32
- def sort(by = :artist_asc_album_asc_track_number_asc)
33
-
23
+ # Sorts the queue by a list of fields and directions in the form of a symbol,
24
+ # or uses the proc given to it, which should take two Tracks and return -1, 0,
25
+ # or 1.
26
+ def sort(by = :artist_asc_album_asc_track_number_asc, &proc)
27
+ if proc
28
+ $player.sort(&proc)
29
+ else
30
+ $player.sort do |a, b|
31
+ result = 0
32
+ by.to_s.scan(/([a-z_]+?)_(asc|desc)(?:_|$)/).each do |column, direction|
33
+ a_value = a.send(column)
34
+ b_value = b.send(column)
35
+ a_value = a_value.downcase if a_value.respond_to? :downcase
36
+ b_value = b_value.downcase if b_value.respond_to? :downcase
37
+ dir = (direction == "desc") ? -1 : 1
38
+ if a_value != b_value
39
+ if a_value.nil? || b_value.nil?
40
+ result = dir
41
+ else
42
+ result = (a_value <=> b_value) * dir
43
+ end
44
+ break
45
+ end
46
+ end
47
+ result
48
+ end
49
+ end
34
50
  end
35
51
  end
@@ -0,0 +1,186 @@
1
+ module Listlace
2
+ STRING_SELECTORS = %w(name artist composer album album_artist genre comments location)
3
+ INTEGER_SELECTORS = %w(disc_number disc_count track_number track_count year bit_rate sample_rate play_count skip_count rating)
4
+
5
+ STRING_SELECTORS.each do |column|
6
+ define_method(column) do |*args|
7
+ options = args.last.is_a?(Hash) ? args.pop : {}
8
+
9
+ playlists = args.map { |query| string_selector(column, query, options) }
10
+ playlist *playlists
11
+ end
12
+ end
13
+
14
+ INTEGER_SELECTORS.each do |column|
15
+ define_method(column) do |*args|
16
+ playlists = args.map { |arg| integer_selector(column, arg) }
17
+ playlist *playlists
18
+ end
19
+ end
20
+
21
+ # rename the "name" selector to "song"
22
+ alias_method :song, :name
23
+ remove_method :name
24
+
25
+ # The length selector is an integer selector for the length of a track. A
26
+ # plain integer given to it represents the number of seconds. It can also take
27
+ # a String in the format "1:23", to represent 83 seconds, for example. These
28
+ # can be part of a Range, as usual: "1:23".."2:05", for example.
29
+ def length(*args)
30
+ normalize = lambda do |value|
31
+ case value
32
+ when String
33
+ Track.parse_time(value)
34
+ when Integer
35
+ value * 1000
36
+ when Range
37
+ (normalize.(value.begin))..(normalize.(value.end))
38
+ end
39
+ end
40
+
41
+ playlists = args.map do |arg|
42
+ if arg.is_a? Hash
43
+ key = arg.keys.first
44
+ arg[key] = normalize.(arg[key])
45
+ else
46
+ arg = normalize.(arg)
47
+ end
48
+
49
+ # If they want tracks of length "0:05", for example, we need to look for
50
+ # tracks that are from 5000 to 5999 milliseconds long.
51
+ if arg.is_a? Integer
52
+ arg = (arg)..(arg + 999)
53
+ end
54
+
55
+ integer_selector(:total_time, arg)
56
+ end
57
+
58
+ playlist *playlists
59
+ end
60
+
61
+ # Makes a playlist out of tracks that match the string query on the given
62
+ # column. It's SQL underneath, so you can use % and _ as wildcards in the
63
+ # query. By default, % wildcards are inserted on the left and right of your
64
+ # query. Use the :match option to change this:
65
+ #
66
+ # :match => :middle "%query%" (default)
67
+ # :match => :left "query%"
68
+ # :match => :right "%query"
69
+ # :match => :exact "query"
70
+ #
71
+ # This method shouldn't have to be used directly. Many convenient methods are
72
+ # generated for you, one for each string field you may want to select on.
73
+ # These are: artist, composer, album, album_artist, genre, comments, location.
74
+ # For example:
75
+ #
76
+ # artist :muse, match: :exact #=> playlist (108 tracks)
77
+ # composer :rachmanino #=> playlist (33 tracks)
78
+ #
79
+ # To match the name of a track, use song:
80
+ #
81
+ # song "frontier psychiatrist" #=> playlist (1 track)
82
+ #
83
+ def string_selector(column, query, options = {})
84
+ options[:match] ||= :middle
85
+
86
+ query = {
87
+ exact: "#{query}",
88
+ left: "#{query}%",
89
+ right: "%#{query}",
90
+ middle: "%#{query}%"
91
+ }[options[:match]]
92
+
93
+ tracks = Track.arel_table
94
+ PlaylistArray.new(Track.where(tracks[column].matches(query)).all)
95
+ end
96
+
97
+ # Makes a playlist out of tracks that satisfy certain conditions on the given
98
+ # integer column. You can pass an exact value to check for equality, a range,
99
+ # or a hash that specifies greater-than and less-than options like this:
100
+ #
101
+ # integer_selector :year, greater_than: 2000 #=> playlist (3555 tracks)
102
+ #
103
+ # The possible operators, with their shortcuts, are:
104
+ #
105
+ # :greater_than / :gt
106
+ # :less_than / :lt
107
+ # :greater_than_or_equal / :gte
108
+ # :less_than_or_equal / :lte
109
+ # :not_equal / :ne
110
+ #
111
+ # Note: You can only use one of these operators at a time. If you want a
112
+ # range, use a Range.
113
+ #
114
+ # This method shouldn't have to be used directly. Many convenient methods are
115
+ # generated for you, one for each integer field you may want to select on.
116
+ # These are: disc_number, disc_count, track_number, track_count, year,
117
+ # bit_rate, sample_rate, play_count, skip_count, rating, length. Length is
118
+ # special, it can take any of the time formats that the seek command can. For
119
+ # example:
120
+ #
121
+ # year 2010..2012 #=> playlist (1060 tracks)
122
+ # length gt: "4:00" #=> playlist (2543 tracks)
123
+ #
124
+ def integer_selector(column, value_or_options)
125
+ if value_or_options.is_a? Hash
126
+ operator = {
127
+ greater_than: ">",
128
+ gt: ">",
129
+ less_than: "<",
130
+ lt: "<",
131
+ greater_than_or_equal: ">=",
132
+ gte: ">=",
133
+ less_than_or_equal: "<=",
134
+ lte: "<=",
135
+ not_equal: "<>",
136
+ ne: "<>"
137
+ }[value_or_options.keys.first]
138
+ tracks = Track.where("tracks.#{column} #{operator} ?", value_or_options.values.first)
139
+ PlaylistArray.new(tracks.all)
140
+ else
141
+ tracks = Track.arel_table
142
+ PlaylistArray.new(Track.where(column => value_or_options).all)
143
+ end
144
+ end
145
+
146
+ # Creates or looks up a playlist. You can pass any number of multiple types of
147
+ # objects to make a playlist:
148
+ #
149
+ # Track: Makes a playlist with one track
150
+ # ActiveRecord::Relation: Makes a playlist out of the resulting Track or Playlist records
151
+ # Symbol or String: Tries to retrieve a saved playlist with the given name.
152
+ # If it can't find one, it creates a new one.
153
+ #
154
+ # You can also pass in the results of a selector. In this way you can create a
155
+ # playlist out of many smaller pieces.
156
+ #
157
+ # If given no arguments, it returns a blank playlist.
158
+ #
159
+ def playlist(*args)
160
+ args.map do |object|
161
+ case object
162
+ when Track
163
+ PlaylistArray.new([object])
164
+ when Playlist
165
+ PlaylistArray.new([object.tracks.all], object.name)
166
+ when PlaylistArray
167
+ object
168
+ when Array
169
+ playlist *object
170
+ when ActiveRecord::Relation
171
+ if object.table.name == "tracks"
172
+ PlaylistArray.new([object.all])
173
+ elsif object.table.name == "playlists"
174
+ playlist *object.all
175
+ end
176
+ when Symbol, String
177
+ playlists = Playlist.arel_table
178
+ if playlist = Playlist.where(playlists[:name].matches(object.to_s)).first
179
+ PlaylistArray.new(playlist)
180
+ else
181
+ PlaylistArray.new([], object)
182
+ end
183
+ end
184
+ end.inject(:+)
185
+ end
186
+ end
@@ -13,6 +13,16 @@ module Listlace
13
13
  File.exists? PATH
14
14
  end
15
15
 
16
+ def delete
17
+ FileUtils.rm PATH
18
+ end
19
+
20
+ def wipe
21
+ delete
22
+ connect
23
+ generate_schema
24
+ end
25
+
16
26
  def generate_schema
17
27
  ActiveRecord::Schema.define do
18
28
  create_table :tracks do |t|
@@ -7,11 +7,6 @@ module Listlace
7
7
  Track.format_time(total_time)
8
8
  end
9
9
 
10
- def play
11
- $player.queue = [self]
12
- Listlace.play
13
- end
14
-
15
10
  def self.format_time(milliseconds)
16
11
  total_seconds = milliseconds / 1000
17
12
 
@@ -25,5 +20,13 @@ module Listlace
25
20
  "%d:%02d" % [minutes, seconds]
26
21
  end
27
22
  end
23
+
24
+ def self.parse_time(string)
25
+ parts = string.split(":").map(&:to_i)
26
+ parts = [0] + parts if parts.length == 2
27
+ hours, minutes, seconds = parts
28
+ seconds = hours * 3600 + minutes * 60 + seconds
29
+ seconds * 1000
30
+ end
28
31
  end
29
32
  end
@@ -7,7 +7,7 @@ module Listlace
7
7
 
8
8
  def initialize
9
9
  @mplayer = nil
10
- @queue = []
10
+ @queue = PlaylistArray.new([], :queue)
11
11
  @current_track = nil
12
12
  @current_track_index = nil
13
13
  @paused = false
@@ -15,14 +15,14 @@ module Listlace
15
15
  @repeat_mode = false
16
16
  end
17
17
 
18
- def queue(track = nil)
19
- @queue << track if track.is_a? Track
18
+ def queue(playlist = nil)
19
+ @queue << playlist if playlist
20
20
  @queue.dup
21
21
  end
22
22
 
23
23
  def clear
24
24
  stop
25
- @queue = []
25
+ @queue.clear
26
26
  end
27
27
 
28
28
  def empty?
@@ -88,12 +88,12 @@ module Listlace
88
88
  change_track(0)
89
89
  end
90
90
 
91
- def back
92
- change_track(-1)
91
+ def back(n = 1)
92
+ change_track(-n)
93
93
  end
94
94
 
95
- def skip
96
- change_track(1)
95
+ def skip(n = 1)
96
+ change_track(n)
97
97
  end
98
98
 
99
99
  def seek(where)
@@ -103,10 +103,7 @@ module Listlace
103
103
  when Range
104
104
  @mplayer.command("seek %d 2" % [where.begin * 60 + where.end], expect_answer: true)
105
105
  when String
106
- parts = where.split(":").map(&:to_i)
107
- parts = [0] + parts if parts.length == 2
108
- hours, minutes, seconds = parts
109
- @mplayer.command("seek %d 2" % [hours * 3600 + minutes * 60 + seconds], expect_answer: true)
106
+ @mplayer.command("seek %d 2" % [Track.parse_time(where) / 1000], expect_answer: true)
110
107
  when Hash
111
108
  if where[:abs]
112
109
  if where[:abs].is_a? Integer
@@ -131,6 +128,23 @@ module Listlace
131
128
  @mplayer.command("speed_set %f" % [speed], expect_answer: true)
132
129
  end
133
130
 
131
+ def shuffle
132
+ if started?
133
+ @queue.shuffle_except! @current_track
134
+ @current_track_index = 0
135
+ else
136
+ @queue.shuffle!
137
+ end
138
+ end
139
+
140
+ def sort(&by)
141
+ @queue.sort! &by
142
+
143
+ if started?
144
+ @current_track_index = @queue.index(@current_track)
145
+ end
146
+ end
147
+
134
148
  def current_time
135
149
  answer = @mplayer.command "get_time_pos", expect_answer: true
136
150
  if answer =~ /^ANS_TIME_POSITION=([0-9.]+)$/
@@ -0,0 +1,82 @@
1
+ module Listlace
2
+ class PlaylistArray < Array
3
+ attr_accessor :name, :model
4
+
5
+ def initialize(tracks_or_playlist = [], name = :playlist)
6
+ if tracks_or_playlist.is_a? Playlist
7
+ replace tracks_or_playlist.tracks.to_a
8
+ @name = tracks_or_playlist.name
9
+ @model = tracks_or_playlist
10
+ else
11
+ replace tracks_or_playlist.to_a
12
+ @name = name.to_s
13
+ @model = nil
14
+ end
15
+ end
16
+
17
+ def save(name = nil)
18
+ if @model
19
+ @model.playlist_items.destroy_all
20
+ @model.name = @name = name if name
21
+ else
22
+ @name = name if name
23
+ @model = Playlist.new(name: @name)
24
+ end
25
+
26
+ if @model.save
27
+ each.with_index do |track, i|
28
+ item = PlaylistItem.new(position: i)
29
+ item.playlist = @model
30
+ item.track = track
31
+ item.save!
32
+ end
33
+ @model
34
+ else
35
+ false
36
+ end
37
+ end
38
+
39
+ def <<(other)
40
+ if other.is_a? Track
41
+ super
42
+ else
43
+ other.each do |track|
44
+ self << track
45
+ end
46
+ end
47
+ end
48
+
49
+ def +(other)
50
+ result = self.dup
51
+ result << Listlace.playlist(other)
52
+ result
53
+ end
54
+
55
+ def &(other)
56
+ replace super
57
+ end
58
+
59
+ def shuffle_except(track)
60
+ ary = dup
61
+ dup.shuffle_except! track
62
+ dup
63
+ end
64
+
65
+ def shuffle_except!(track)
66
+ replace([track] + (self - [track]).shuffle)
67
+ end
68
+
69
+ def to_s
70
+ "%s (%d track%s)" % [@name || "playlist", length, ("s" if length != 1)]
71
+ end
72
+
73
+ def inspect
74
+ to_s
75
+ end
76
+
77
+ # override pry
78
+ def pretty_inspect
79
+ inspect
80
+ end
81
+ end
82
+ end
data/lib/listlace.rb CHANGED
@@ -8,10 +8,12 @@ end
8
8
  require "open4"
9
9
  require "shellwords"
10
10
  require "active_record"
11
+ require "fileutils"
11
12
 
12
13
  require "listlace/database"
13
14
  require "listlace/player"
14
15
  require "listlace/mplayer"
16
+ require "listlace/playlist_array"
15
17
 
16
18
  require "listlace/models/track"
17
19
  require "listlace/models/playlist"
@@ -23,7 +25,6 @@ require "listlace/commands/selectors"
23
25
  require "listlace/commands/volume"
24
26
  require "listlace/commands/queue"
25
27
 
26
- # gotta ged rid of this global sometime
27
28
  $player = Listlace::Player.new
28
29
 
29
30
  at_exit do
data/listlace.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "listlace"
3
- s.version = "0.0.3"
4
- s.date = "2012-08-24"
3
+ s.version = "0.0.4"
4
+ s.date = "2012-08-25"
5
5
  s.summary = "A music player in a REPL."
6
6
  s.description = "Listlace is a music player which is interacted with through a Ruby REPL."
7
7
  s.author = "Jeremy Ruten"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: listlace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-24 00:00:00.000000000 Z
12
+ date: 2012-08-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pry
@@ -147,6 +147,7 @@ files:
147
147
  - lib/listlace/models/track.rb
148
148
  - lib/listlace/mplayer.rb
149
149
  - lib/listlace/player.rb
150
+ - lib/listlace/playlist_array.rb
150
151
  - lib/listlace.rb
151
152
  homepage: http://github.com/yjerem/listlace
152
153
  licenses: