drum 0.1.12 → 0.2.0

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
  SHA256:
3
- metadata.gz: ccee94d125b95501fcfc0411415495ebd89e1fb628e354300cfe61a2c849d174
4
- data.tar.gz: 223c183c3fcff3aac3e892da9ffb1b29c79036054d236470a3f98ea575994138
3
+ metadata.gz: 744d7de8c05fe7f7dc8f843b04b2ccd1a77a2688228369706fa4fca79ddf6c07
4
+ data.tar.gz: c72fc4a18d59cb12017692e1a6fad3cb73cbec24ea73a64adf68279331c3eafb
5
5
  SHA512:
6
- metadata.gz: 20f2bb96ed9ffc1e72a9b336c7f0a211565325e12bd93d13f5b61efafbabe0beee8dd552a524190ba6f9609031eb27dceea2a47de712a6d9eada9e24bc9ca7a4
7
- data.tar.gz: a10fb471ba36bde100efbd9d6001344223efce95d1da5cf0a21e2646f54562dce7e1f97cf1634259ccef2ba8de9b62140e1a0efc279ea89b1ffab4be941fa3e9
6
+ metadata.gz: 9cd77d6cf666ef8cd90709b357b59994da0eb20f2a7afaea68f5a60c3aae909d4acf49b407426dd21af4e56caf542e2906dabae73e9270e8221272d543a02e5a
7
+ data.tar.gz: 488848553a20609b7f72bbd11431c46ba03e8d20db4ad428cca5d34df434a061a1ae55a668ee3a304836f42eb84ba5af84bf84e3a5ab0168271f1b86fd3e8f71
@@ -26,7 +26,7 @@ jobs:
26
26
  touch ~/.gem/credentials
27
27
  chmod 600 ~/.gem/credentials
28
28
  echo ":github: Bearer ${{ secrets.GITHUB_TOKEN }}" >> ~/.gem/credentials
29
- echo ":rubygems: Bearer ${{ secrets.RUBYGEMS_TOKEN }}" >> ~/.gem/credentials
29
+ echo ":rubygems: ${{ secrets.RUBYGEMS_TOKEN }}" >> ~/.gem/credentials
30
30
  - name: Push gem to GitHub Packages
31
31
  run: bundle exec gem push --key github --host "https://rubygems.pkg.github.com/fwcd" pkg/drum-*.gem
32
32
  - name: Push gem to RubyGems.org
@@ -4,13 +4,18 @@ on: [push]
4
4
 
5
5
  jobs:
6
6
  test:
7
- runs-on: ubuntu-latest
7
+ strategy:
8
+ matrix:
9
+ os: ['ubuntu-latest', 'macos-latest']
10
+ ruby: ['2.7.0', '3.2.0']
11
+
12
+ runs-on: '${{ matrix.os }}'
8
13
  steps:
9
14
  - uses: actions/checkout@v2
10
15
  - name: Set up Ruby
11
16
  uses: ruby/setup-ruby@v1
12
17
  with:
13
- ruby-version: 2.7.0
18
+ ruby-version: '${{ matrix.ruby }}'
14
19
  - name: Install dependencies
15
20
  run: |
16
21
  gem install bundler -v 2.2.0
data/Gemfile CHANGED
@@ -3,10 +3,21 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in drum.gemspec
4
4
  gemspec
5
5
 
6
+ # Install AppleScript bridge on macOS
7
+ #
8
+ # Note: End-users installing drum via 'gem install'
9
+ # will need to install rb-scpt manually to use the integration.
10
+ #
11
+ # TODO: Investigate whether this could be done automatically
12
+ # See https://stackoverflow.com/a/10249133 for more info.
13
+ # Unfortunately checking the OS in the gemspec is insufficient,
14
+ # since the file is evaluated at packaging time.
15
+ if RUBY_PLATFORM =~ /\bdarwin/
16
+ gem 'rb-scpt', '~> 1.0'
17
+ end
18
+
6
19
  group :development, :test do
7
20
  gem 'rake', '~> 13.0'
8
21
  gem 'yard', '~> 0.9'
9
22
  gem 'rspec', '~> 3.10'
10
- gem 'ruby-debug-ide', '~> 0.7'
11
- gem 'debase', '~> 0.2' # seems to be required by ruby-debug-ide
12
23
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- drum (0.1.12)
4
+ drum (0.2.0)
5
5
  highline (~> 2.0)
6
6
  jwt (~> 2.2)
7
7
  launchy (~> 2.4)
@@ -9,74 +9,55 @@ PATH
9
9
  rest-client (~> 2.0)
10
10
  rspotify (~> 2.10)
11
11
  ruby-limiter (~> 2.1)
12
- thor (~> 0.20)
12
+ thor (~> 1.2)
13
13
  webrick (~> 1.7)
14
14
 
15
15
  GEM
16
16
  remote: https://rubygems.org/
17
17
  specs:
18
- addressable (2.5.2)
19
- public_suffix (>= 2.0.2, < 4.0)
20
- debase (0.2.4.1)
21
- debase-ruby_core_source (>= 0.10.2)
22
- debase-ruby_core_source (0.10.12)
23
- diff-lcs (1.4.4)
18
+ addressable (2.8.4)
19
+ public_suffix (>= 2.0.2, < 6.0)
20
+ diff-lcs (1.5.0)
24
21
  domain_name (0.5.20190701)
25
22
  unf (>= 0.0.5, < 1.0.0)
26
- faraday (1.8.0)
27
- faraday-em_http (~> 1.0)
28
- faraday-em_synchrony (~> 1.0)
29
- faraday-excon (~> 1.1)
30
- faraday-httpclient (~> 1.0.1)
31
- faraday-net_http (~> 1.0)
32
- faraday-net_http_persistent (~> 1.1)
33
- faraday-patron (~> 1.0)
34
- faraday-rack (~> 1.0)
35
- multipart-post (>= 1.2, < 3)
23
+ faraday (2.7.4)
24
+ faraday-net_http (>= 2.0, < 3.1)
36
25
  ruby2_keywords (>= 0.0.4)
37
- faraday-em_http (1.0.0)
38
- faraday-em_synchrony (1.0.0)
39
- faraday-excon (1.1.0)
40
- faraday-httpclient (1.0.1)
41
- faraday-net_http (1.0.1)
42
- faraday-net_http_persistent (1.2.0)
43
- faraday-patron (1.0.0)
44
- faraday-rack (1.0.0)
45
- ffi (1.15.4-x64-mingw32)
46
- hashie (4.1.0)
47
- highline (2.0.3)
48
- http-cookie (1.0.4)
26
+ faraday-net_http (3.0.2)
27
+ ffi (1.15.5)
28
+ hashie (5.0.0)
29
+ highline (2.1.0)
30
+ http-cookie (1.0.5)
49
31
  domain_name (~> 0.5)
50
- jwt (2.3.0)
51
- launchy (2.4.3)
52
- addressable (~> 2.3)
53
- mime-types (3.3.1)
32
+ jwt (2.7.0)
33
+ launchy (2.5.2)
34
+ addressable (~> 2.8)
35
+ mime-types (3.4.1)
54
36
  mime-types-data (~> 3.2015)
55
- mime-types-data (3.2021.0901)
56
- multi_json (1.15.0)
37
+ mime-types-data (3.2023.0218.1)
57
38
  multi_xml (0.6.0)
58
- multipart-post (2.1.1)
59
39
  netrc (0.11.0)
60
- oauth2 (1.4.7)
61
- faraday (>= 0.8, < 2.0)
40
+ oauth2 (2.0.9)
41
+ faraday (>= 0.17.3, < 3.0)
62
42
  jwt (>= 1.0, < 3.0)
63
- multi_json (~> 1.3)
64
43
  multi_xml (~> 0.5)
65
- rack (>= 1.2, < 3)
66
- omniauth (2.0.4)
44
+ rack (>= 1.2, < 4)
45
+ snaky_hash (~> 2.0)
46
+ version_gem (~> 1.1)
47
+ omniauth (2.1.1)
67
48
  hashie (>= 3.4.6)
68
- rack (>= 1.6.2, < 3)
49
+ rack (>= 2.2.3)
69
50
  rack-protection
70
- omniauth-oauth2 (1.7.1)
71
- oauth2 (~> 1.4)
72
- omniauth (>= 1.9, < 3)
51
+ omniauth-oauth2 (1.8.0)
52
+ oauth2 (>= 1.4, < 3)
53
+ omniauth (~> 2.0)
73
54
  options (2.3.2)
74
55
  progress_bar (1.3.3)
75
56
  highline (>= 1.6, < 3)
76
57
  options (~> 2.3.0)
77
- public_suffix (3.1.1)
78
- rack (2.2.3)
79
- rack-protection (2.1.0)
58
+ public_suffix (5.0.1)
59
+ rack (3.0.7)
60
+ rack-protection (3.0.6)
80
61
  rack
81
62
  rake (13.0.6)
82
63
  rest-client (2.0.2)
@@ -88,34 +69,35 @@ GEM
88
69
  http-cookie (>= 1.0.2, < 2.0)
89
70
  mime-types (>= 1.16, < 4.0)
90
71
  netrc (~> 0.8)
91
- rspec (3.10.0)
92
- rspec-core (~> 3.10.0)
93
- rspec-expectations (~> 3.10.0)
94
- rspec-mocks (~> 3.10.0)
95
- rspec-core (3.10.1)
96
- rspec-support (~> 3.10.0)
97
- rspec-expectations (3.10.1)
72
+ rspec (3.12.0)
73
+ rspec-core (~> 3.12.0)
74
+ rspec-expectations (~> 3.12.0)
75
+ rspec-mocks (~> 3.12.0)
76
+ rspec-core (3.12.2)
77
+ rspec-support (~> 3.12.0)
78
+ rspec-expectations (3.12.3)
98
79
  diff-lcs (>= 1.2.0, < 2.0)
99
- rspec-support (~> 3.10.0)
100
- rspec-mocks (3.10.2)
80
+ rspec-support (~> 3.12.0)
81
+ rspec-mocks (3.12.5)
101
82
  diff-lcs (>= 1.2.0, < 2.0)
102
- rspec-support (~> 3.10.0)
103
- rspec-support (3.10.2)
104
- rspotify (2.10.3)
105
- addressable (~> 2.5.2)
83
+ rspec-support (~> 3.12.0)
84
+ rspec-support (3.12.0)
85
+ rspotify (2.11.1)
86
+ addressable (~> 2.8.0)
106
87
  omniauth-oauth2 (>= 1.6)
107
88
  rest-client (~> 2.0.2)
108
- ruby-debug-ide (0.7.3)
109
- rake (>= 0.8.1)
110
- ruby-limiter (2.1.0)
89
+ ruby-limiter (2.2.2)
111
90
  ruby2_keywords (0.0.5)
112
- thor (0.20.3)
91
+ snaky_hash (2.0.1)
92
+ hashie
93
+ version_gem (~> 1.1, >= 1.1.1)
94
+ thor (1.2.1)
113
95
  unf (0.1.4)
114
96
  unf_ext
115
- unf_ext (0.0.8)
116
- unf_ext (0.0.8-x64-mingw32)
117
- webrick (1.7.0)
118
- yard (0.9.26)
97
+ unf_ext (0.0.8.2)
98
+ version_gem (1.1.2)
99
+ webrick (1.8.1)
100
+ yard (0.9.34)
119
101
 
120
102
  PLATFORMS
121
103
  ruby
@@ -123,12 +105,10 @@ PLATFORMS
123
105
  x86_64-linux
124
106
 
125
107
  DEPENDENCIES
126
- debase (~> 0.2)
127
108
  drum!
128
109
  rake (~> 13.0)
129
110
  rspec (~> 3.10)
130
- ruby-debug-ide (~> 0.7)
131
111
  yard (~> 0.9)
132
112
 
133
113
  BUNDLED WITH
134
- 2.2.29
114
+ 2.4.12
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Drum
2
2
 
3
+ [![Test](https://github.com/fwcd/drum/actions/workflows/test.yml/badge.svg)](https://github.com/fwcd/drum/actions/workflows/test.yml)
4
+ [![Documentation](https://github.com/fwcd/drum/actions/workflows/documentation.yml/badge.svg)](https://fwcd.github.io/drum)
5
+
3
6
  A small tool for copying your playlists across music streaming services. Think `rsync`, but for playlists.
4
7
 
5
8
  ![Icon](artwork/icon128.png)
@@ -21,6 +24,7 @@ The basic usage pattern is always `drum cp [source] [destination]` where `source
21
24
  * `@spotify/playlists`
22
25
  * `@spotify/tracks`
23
26
  * `@applemusic/playlists`
27
+ * `@music/playlists` (referring to the local Apple Music (`Music.app`) library on macOS)
24
28
  * `@stdin`
25
29
  * `@stdout`
26
30
  * A dash `-`, synonymous with `@stdin` and `@stdout`, depending on usage
@@ -67,6 +71,8 @@ To run the application, run `bundle exec bin/drum`. You can also run `bin/consol
67
71
 
68
72
  To package the application into a gem, run `bundle exec rake build`. The built gem should then be located in `pkg`.
69
73
 
74
+ > Note: If you wish to install `drum` using `gem install`, you may need to install additional gems such as `rb-scpt` on macOS to use platform-specific integrations. See [the `Gemfile`](Gemfile) for more information.
75
+
70
76
  To install the gem, run `bundle exec rake install`.
71
77
 
72
78
  To generate the documentation, run `bundle exec rake yard`.
data/drum.gemspec CHANGED
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.bindir = 'bin'
24
24
  spec.executables = ['drum']
25
25
  spec.require_paths = ['lib']
26
-
27
- spec.add_dependency 'thor', '~> 0.20'
26
+
27
+ spec.add_dependency 'thor', '~> 1.2'
28
28
  spec.add_dependency 'rspotify', '~> 2.10'
29
29
  spec.add_dependency 'launchy', '~> 2.4'
30
30
  spec.add_dependency 'ruby-limiter', '~> 2.1'
@@ -2,6 +2,7 @@ require 'drum/model/playlist'
2
2
  require 'drum/model/ref'
3
3
  require 'drum/service/service'
4
4
  require 'drum/utils/log'
5
+ require 'drum/utils/yaml'
5
6
  require 'pathname'
6
7
  require 'uri'
7
8
  require 'yaml'
@@ -9,7 +10,7 @@ require 'yaml'
9
10
  module Drum
10
11
  # A service that reads/writes playlists to/from YAML files.
11
12
  class FileService < Service
12
- include Log
13
+ include Log, YAMLUtils
13
14
 
14
15
  def name
15
16
  'file'
@@ -45,12 +46,12 @@ module Drum
45
46
  if base_path.directory?
46
47
  Dir.glob("#{base_path}/**/*.{yaml,yml}").map do |p|
47
48
  path = Pathname.new(p)
48
- playlist = Playlist.deserialize(YAML.load(path.read))
49
+ playlist = Playlist.deserialize(from_yaml(path.read))
49
50
  playlist.path = path.relative_path_from(base_path).parent.each_filename.to_a
50
51
  playlist
51
52
  end
52
53
  else
53
- [Playlist.deserialize(YAML.load(base_path.read))]
54
+ [Playlist.deserialize(from_yaml(base_path.read))]
54
55
  end
55
56
  end
56
57
 
@@ -74,7 +75,7 @@ module Drum
74
75
  end
75
76
 
76
77
  length = 6
77
- while playlist_path[length].exist? && Playlist.deserialize(YAML.load(playlist_path[length].read)).id != playlist.id
78
+ while playlist_path[length].exist? && Playlist.deserialize(from_yaml(playlist_path[length].read)).id != playlist.id
78
79
  length += 1
79
80
  end
80
81
 
@@ -0,0 +1,117 @@
1
+ require 'drum/model/album'
2
+ require 'drum/model/artist'
3
+ require 'drum/model/ref'
4
+ require 'drum/model/playlist'
5
+ require 'drum/model/track'
6
+ require 'drum/service/service'
7
+ require 'drum/utils/log'
8
+
9
+ module Drum
10
+ # A service that uses AppleScript to interact with the local Apple Music (Music.app) library.
11
+ #
12
+ # Useful resources:
13
+ # - https://github.com/BrendanThompson/rb-scpt/tree/develop/sample
14
+ # - https://stackoverflow.com/questions/12964766/create-playlist-in-itunes-with-python-and-scripting-bridge
15
+ # - https://dougscripts.com/itunes/itinfo/info01.php
16
+ # - https://dougscripts.com/itunes/itinfo/info02.php
17
+ # - https://dougscripts.com/itunes/itinfo/info03.php
18
+ # - https://github.com/sorah/jockey/blob/master/add.rb
19
+ class MusicService < Service
20
+ include Log
21
+
22
+ def name
23
+ 'music'
24
+ end
25
+
26
+ def require_rb_scpt
27
+ begin
28
+ require 'rb-scpt'
29
+ rescue LoadError
30
+ raise "Using the local Apple Music importer requires the 'rb-scpt' gem (which requires macOS)"
31
+ end
32
+ end
33
+
34
+ def get_library_proxy
35
+ self.require_rb_scpt
36
+ music = Appscript.app('Music')
37
+ source = music.sources[1]
38
+ unless source.kind.get == :library
39
+ raise 'Could not get music library source'
40
+ end
41
+ return source
42
+ end
43
+
44
+ # Ref parsing
45
+
46
+ def parse_ref(raw_ref)
47
+ if raw_ref.is_token
48
+ location = case raw_ref.text
49
+ when "#{self.name}/playlists" then :playlists
50
+ else return nil
51
+ end
52
+ Ref.new(self.name, :special, location)
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ # Upload helpers
59
+
60
+ def to_track_proxy(library_proxy, playlist, track)
61
+ # Get track metadata
62
+ name = track.name
63
+ artists = track.artist_ids.map { |id| playlist.artists[id].name }
64
+
65
+ # Match the track with a track in the local library
66
+ # TODO: Don't require exact 100% matches
67
+ name_query = Appscript.its.name.eq(name)
68
+ artists_query = artists
69
+ .map { |a| Appscript.its.artist.contains(a) }
70
+ .reduce { |q1, q2| q1.or(q2) }
71
+ query = name_query.and(artists_query)
72
+
73
+ # Find a matching track in the track
74
+ # TODO: Instead of choosing the first, prefer iTunes-matched/higher bitrate ones etc.
75
+ track_proxy = library_proxy.tracks[query].get.first
76
+
77
+ if track_proxy.nil?
78
+ log.warn "No match found for '#{track.name}' by '#{artists.first}'"
79
+ else
80
+ log.info "Matched '#{track.name}' with '#{track_proxy.name.get}' by '#{track_proxy.artist.get}'"
81
+ end
82
+ track_proxy
83
+ end
84
+
85
+ def upload_playlist(library_proxy, playlist)
86
+ playlist_proxy = library_proxy.make(new: :playlist, with_properties: {
87
+ name: playlist.name,
88
+ description: playlist.description,
89
+ }.compact)
90
+
91
+ playlist.tracks.each do |track|
92
+ track_proxy = self.to_track_proxy(library_proxy, playlist, track)
93
+ unless track_proxy.nil?
94
+ track_proxy.duplicate(to: playlist_proxy)
95
+ end
96
+ end
97
+ end
98
+
99
+ # Service
100
+
101
+ def download(ref)
102
+ raise 'Downloading is not implemented yet'
103
+ end
104
+
105
+ def upload(ref, playlists)
106
+ library_proxy = self.get_library_proxy
107
+
108
+ unless ref.resource_type == :special && ref.resource_location == :playlists
109
+ raise "Cannot upload to anything other than @#{self.name}/playlists yet!"
110
+ end
111
+
112
+ playlists.each do |playlist|
113
+ self.upload_playlist(library_proxy, playlist)
114
+ end
115
+ end
116
+ end
117
+ end
@@ -555,8 +555,12 @@ module Drum
555
555
  log.info 'Fetching playlists...'
556
556
  Enumerator.new(sp_playlists.length) do |enum|
557
557
  sp_playlists.each do |sp_playlist|
558
- new_playlist = self.from_sp_playlist(sp_playlist)
559
- enum.yield new_playlist
558
+ begin
559
+ new_playlist = self.from_sp_playlist(sp_playlist)
560
+ enum.yield new_playlist
561
+ rescue StandardError => e
562
+ log.info "Could not download playlist '#{sp_playlist.name}': #{e}"
563
+ end
560
564
  end
561
565
  end
562
566
  when :tracks
@@ -2,12 +2,13 @@ require 'drum/model/playlist'
2
2
  require 'drum/model/ref'
3
3
  require 'drum/service/service'
4
4
  require 'drum/utils/log'
5
+ require 'drum/utils/yaml'
5
6
  require 'yaml'
6
7
 
7
8
  module Drum
8
9
  # A service that reads from stdin and writes to stdout.
9
10
  class StdioService < Service
10
- include Log
11
+ include Log, YAMLUtils
11
12
 
12
13
  def name
13
14
  'stdio'
@@ -31,7 +32,7 @@ module Drum
31
32
  def download(playlist_ref)
32
33
  if playlist_ref.resource_location.include?(:stdin)
33
34
  # TODO: Support multiple, --- delimited playlists?
34
- [Playlist.deserialize(YAML.load(STDIN.read))]
35
+ [Playlist.deserialize(from_yaml(STDIN.read))]
35
36
  else
36
37
  []
37
38
  end
@@ -1,50 +1,58 @@
1
+ require 'drum/utils/log'
2
+ require 'drum/utils/yaml'
1
3
  require 'yaml'
2
4
 
3
- # A wrapper around a hash that stores values persistently in a YAML.
4
- #
5
- # @!attribute value
6
- # @return [Hash] The wrapped hash
7
- class Drum::PersistentHash
8
- attr_reader :value
9
-
10
- # Creates a new persistent hash.
5
+ module Drum
6
+ # A wrapper around a hash that stores values persistently in a YAML.
11
7
  #
12
- # @param [String] file_path The path to the stored YAML file (may be non-existent).
13
- # @param [Hash] value The initial default value, if the file doesn't exist yet or is malformed
14
- def initialize(file_path, value={})
15
- @file_path = file_path
16
- begin
17
- self.load
18
- rescue StandardError => e
19
- @value = value
20
- self.store
8
+ # @!attribute value
9
+ # @return [Hash] The wrapped hash
10
+ class PersistentHash
11
+ include Log, YAMLUtils
12
+ attr_reader :value
13
+
14
+ # Creates a new persistent hash.
15
+ #
16
+ # @param [String] file_path The path to the stored YAML file (may be non-existent).
17
+ # @param [Hash] value The initial default value, if the file doesn't exist yet or is malformed
18
+ def initialize(file_path, value={})
19
+ @file_path = file_path
20
+ begin
21
+ self.load
22
+ rescue StandardError => e
23
+ unless e.is_a?(Errno::ENOENT)
24
+ log.warn "Could not load #{file_path}: #{e.inspect}... creating from scratch"
25
+ end
26
+ @value = value
27
+ self.store
28
+ end
21
29
  end
22
- end
23
30
 
24
- # Loads the hash from the file.
25
- def load
26
- @value = YAML.load(File.read(@file_path))
27
- end
31
+ # Loads the hash from the file.
32
+ def load
33
+ @value = from_yaml(File.read(@file_path))
34
+ end
28
35
 
29
- # Saves the hash to the file.
30
- def store
31
- File.write(@file_path, @value.to_yaml)
32
- end
36
+ # Saves the hash to the file.
37
+ def store
38
+ File.write(@file_path, @value.to_yaml)
39
+ end
33
40
 
34
- # Writes a mapping to the hash and stores it on disk.
35
- #
36
- # @param [Object] key The key to use.
37
- # @param [Object] value The value to map the key to.
38
- def []=(key, value)
39
- @value[key] = value
40
- store
41
- end
41
+ # Writes a mapping to the hash and stores it on disk.
42
+ #
43
+ # @param [Object] key The key to use.
44
+ # @param [Object] value The value to map the key to.
45
+ def []=(key, value)
46
+ @value[key] = value
47
+ store
48
+ end
42
49
 
43
- # Reads a mapping from the hash.
44
- #
45
- # @param [Object] key The key to use.
46
- # @return [Object] The value the key is mapped to.
47
- def [](key)
48
- @value[key]
50
+ # Reads a mapping from the hash.
51
+ #
52
+ # @param [Object] key The key to use.
53
+ # @return [Object] The value the key is mapped to.
54
+ def [](key)
55
+ @value[key]
56
+ end
49
57
  end
50
58
  end
@@ -0,0 +1,11 @@
1
+ require 'date'
2
+ require 'yaml'
3
+
4
+ module Drum
5
+ module YAMLUtils
6
+ # Deserializes YAML with a number of standard permitted classes.
7
+ def from_yaml(yaml)
8
+ YAML.load(yaml, permitted_classes: [Date, DateTime, Symbol, Time])
9
+ end
10
+ end
11
+ end
data/lib/drum/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Drum
2
- VERSION = '0.1.12'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/drum.rb CHANGED
@@ -2,6 +2,7 @@ require 'drum/model/raw_ref'
2
2
  require 'drum/service/applemusic'
3
3
  require 'drum/service/file'
4
4
  require 'drum/service/mock'
5
+ require 'drum/service/music'
5
6
  require 'drum/service/service'
6
7
  require 'drum/service/spotify'
7
8
  require 'drum/service/stdio'
@@ -25,8 +26,8 @@ module Drum
25
26
 
26
27
  @hl = HighLine.new
27
28
 
28
- # Set up .drum directory
29
- @dot_dir = Pathname.new(Dir.home) / '.drum'
29
+ # Set up directory for persisted state
30
+ @dot_dir = Pathname.new(Dir.home) / '.local' / 'state' / 'drum'
30
31
  @dot_dir.mkdir unless @dot_dir.directory?
31
32
 
32
33
  @cache_dir = @dot_dir / 'cache'
@@ -35,6 +36,7 @@ module Drum
35
36
  # Declare services in descending order of parse priority
36
37
  @services = [
37
38
  MockService.new,
39
+ MusicService.new,
38
40
  StdioService.new,
39
41
  AppleMusicService.new(@cache_dir),
40
42
  SpotifyService.new(@cache_dir),
@@ -125,13 +127,17 @@ module Drum
125
127
  method_option :group_by_author,
126
128
  type: :boolean,
127
129
  default: false,
128
- desc: "Prepend the author name to each playlist's path"
130
+ desc: "Prepend the author name to each playlist's path."
129
131
 
130
132
  method_option :recase_paths,
131
133
  type: :string,
132
134
  enum: ['kebabcase', 'startcase', 'camelcase', 'pascalcase'],
135
+ desc: "Convert each playlist's path segments to a specific case."
136
+
137
+ method_option :flatten,
138
+ type: :boolean,
133
139
  default: false,
134
- desc: "Convert each playlist's path segments to a specific case"
140
+ desc: 'Clear all playlist paths. Note that flags such as --group-by-author will still be applied.'
135
141
 
136
142
  method_option :note_date,
137
143
  type: :boolean,
@@ -176,6 +182,10 @@ module Drum
176
182
  playlists = playlists.map do |playlist|
177
183
  bar&.increment!
178
184
 
185
+ if options[:flatten]
186
+ playlist.path = []
187
+ end
188
+
179
189
  if options[:group_by_author]
180
190
  author_name = playlist.author_id.try { |id| playlist.users[id] }&.display_name || 'Other'
181
191
  playlist.path.unshift(author_name)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: drum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fwcd
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-15 00:00:00.000000000 Z
11
+ date: 2024-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.20'
19
+ version: '1.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.20'
26
+ version: '1.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspotify
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -171,12 +171,14 @@ files:
171
171
  - lib/drum/service/applemusic.rb
172
172
  - lib/drum/service/file.rb
173
173
  - lib/drum/service/mock.rb
174
+ - lib/drum/service/music.rb
174
175
  - lib/drum/service/service.rb
175
176
  - lib/drum/service/spotify.rb
176
177
  - lib/drum/service/stdio.rb
177
178
  - lib/drum/utils/ext.rb
178
179
  - lib/drum/utils/log.rb
179
180
  - lib/drum/utils/persist.rb
181
+ - lib/drum/utils/yaml.rb
180
182
  - lib/drum/version.rb
181
183
  - userdoc/playlist-format.md
182
184
  homepage: https://github.com/fwcd/drum.git
@@ -200,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
200
202
  - !ruby/object:Gem::Version
201
203
  version: '0'
202
204
  requirements: []
203
- rubygems_version: 3.2.22
205
+ rubygems_version: 3.1.2
204
206
  signing_key:
205
207
  specification_version: 4
206
208
  summary: Playlist manager