spreen-tracks 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7b2ac35193a300ae47b6855f8ffa96b23a757bf5615e4b1b121afc603e7e27f8
4
+ data.tar.gz: 33485326298b1398a225b3cedc8093086ef5f7ec91a7c6674f722be48b481e4b
5
+ SHA512:
6
+ metadata.gz: 4d942f9e86af2045a0ae213cc7ad472d3de11cf5c220dbf2ba4fe167084ac86e56761789203609d88a8f4b17784ec10aaac65fa09922b6572a927cad9dfaabc6
7
+ data.tar.gz: 68ddcb70d10c0ddeade905bcaf3393a5d4a9c4d346e117c51477e4e14b4f8b0b2f235f68b030e81d87c58dacebeeb44855bf78ab3a7a22b508cfc6a0d446f841
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 - present @hayat01sh1da
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ ## 1. Environment
2
+
3
+ - Ruby 4.0.6
4
+ - Gemfile 4.0.16
5
+ - Bundler 4.0.16
6
+
7
+ ## 2. Installation
8
+
9
+ ```command
10
+ $ gem install spreen-tracks
11
+ ```
12
+
13
+ For development, install the dependencies via Gemfile and Bundler:
14
+
15
+ ```command
16
+ $ bundle install
17
+ $ bundle lock --add-checksums
18
+ ```
19
+
20
+ ## 3. Execution
21
+
22
+ Run the command inside your music directory — with no options it dry-runs, printing every rename it would perform without touching a file:
23
+
24
+ ```command
25
+ $ cd ~/Music
26
+ $ track-delimiter
27
+ Target extension is `.m4a`
28
+ ========== [DRY RUN] Total File Count to Clean: 4 ==========
29
+ ========== [DRY RUN] The delimiters of those files will be replaced with `_` ==========
30
+ ========== [DRY RUN] Start! ==========
31
+ ========== [DRY RUN] Replacing the delimiter: `./Artist/Album1/1-01 Title.m4a` => `./Artist/Album1/Disc1/01_Title.m4a` ==========
32
+ ========== [DRY RUN] Replacing the delimiter: `./Artist/Album1/2-01 Title.m4a` => `./Artist/Album1/Disc2/01_Title.m4a` ==========
33
+ ========== [DRY RUN] Replacing the delimiter: `./Artist/Album2/01 Title.m4a` => `./Artist/Album2/01_Title.m4a` ==========
34
+ ========== [DRY RUN] Replacing the delimiter: `./Artist/Album2/02 Title.m4a` => `./Artist/Album2/02_Title.m4a` ==========
35
+ ========== [DRY RUN] Done! ==========
36
+ ========== [DRY RUN] Total Target File Count: 4 ==========
37
+ ```
38
+
39
+ When the plan looks right, execute it with `--mode e`; `--extension` and `--delimiter` override the defaults:
40
+
41
+ ```command
42
+ $ track-delimiter --mode e
43
+ $ track-delimiter --extension .mp3 --delimiter - --mode e
44
+ ```
45
+
46
+ As a library:
47
+
48
+ ```ruby
49
+ require 'spreen_tracks' # or require 'spreen-tracks'
50
+
51
+ SpreenTracks::Application.run # dry run over ./**/*.m4a
52
+ SpreenTracks::Application.run(delimiter: '-', mode: 'e') # execute the renames
53
+ ```
54
+
55
+ ## 4. Unit Test
56
+
57
+ ```command
58
+ $ rake
59
+ Run options: --seed 21935
60
+
61
+ # Running:
62
+
63
+ ..........
64
+
65
+ Finished in 2.362223s, 4.2333 runs/s, 10.1599 assertions/s.
66
+
67
+ 10 runs, 24 assertions, 0 failures, 0 errors, 0 skips
68
+ ```
69
+
70
+ ## 5. Static Code Analysis
71
+
72
+ ```command
73
+ $ bundle exec rubocop
74
+ Inspecting 12 files
75
+ ............
76
+
77
+ 12 files inspected, no offenses detected
78
+ ```
79
+
80
+ ## 6. Type Checks
81
+
82
+ ```command
83
+ $ bundle exec rbs-inline --output sig/generated/ .
84
+ 🎉 Generated 7 RBS files under sig/generated
85
+ $ bundle exec steep check
86
+ # Type checking files:
87
+
88
+ ..............
89
+
90
+ No type error detected. 🫖
91
+ ```
92
+
93
+ ## 7. Build
94
+
95
+ ```command
96
+ $ gem build spreen-tracks.gemspec
97
+ $ gem install ./spreen-tracks-0.1.0.gem
98
+ ```
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'spreen_tracks'
5
+
6
+ exit SpreenTracks::CLI.start
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # Convenience shim so that both `require 'spreen-tracks'` (the gem name) and
5
+ # `require 'spreen_tracks'` (the conventional path) work.
6
+ require_relative 'spreen_tracks'
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require 'fileutils'
5
+
6
+ module SpreenTracks
7
+ # Walks the current directory looking for media files with the given extension
8
+ # and rewrites their filenames so the space delimiter becomes a configurable
9
+ # character (and `N-` disc prefixes become `DiscN/` subdirectories).
10
+ class Application
11
+ class InvalidModeError < StandardError; end
12
+
13
+ # @rbs extension: String
14
+ # @rbs delimiter: String
15
+ # @rbs mode: String
16
+ # @rbs return: void
17
+ def self.run(extension: '.m4a', delimiter: '_', mode: 'd')
18
+ instance = new(extension:, delimiter:, mode:)
19
+ instance.validate_mode!
20
+ instance.replace
21
+ end
22
+
23
+ # @rbs extension: String
24
+ # @rbs delimiter: String
25
+ # @rbs mode: String
26
+ # @rbs return: void
27
+ def initialize(extension: '.m4a', delimiter: '_', mode: 'd')
28
+ @paths = Dir[File.join('.', '**', "*#{extension}")]
29
+ @extension = extension
30
+ @delimiter = delimiter
31
+ @mode = mode
32
+ end
33
+
34
+ # @rbs return: void
35
+ def replace
36
+ puts "Target extension is `#{extension}`"
37
+ return announce_empty if paths.empty?
38
+
39
+ announce_start
40
+ apply_renames
41
+ announce_finish
42
+ end
43
+
44
+ # @rbs return: void
45
+ def validate_mode!
46
+ case mode
47
+ when 'd', 'e'
48
+ nil
49
+ else
50
+ raise InvalidModeError, "#{mode} is invalid mode. Provide either `d`(default) or `e`."
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ attr_reader :paths, :extension, :delimiter, :mode
57
+
58
+ # @rbs return: void
59
+ def announce_empty
60
+ puts "========== [#{exec_mode}] No #{extension} Remains =========="
61
+ end
62
+
63
+ # @rbs return: void
64
+ def announce_start
65
+ puts "========== [#{exec_mode}] Total File Count to Clean: #{paths.length} =========="
66
+ puts "========== [#{exec_mode}] The delimiters of those files will be replaced with `#{delimiter}` =========="
67
+ puts "========== [#{exec_mode}] Start! =========="
68
+ end
69
+
70
+ # @rbs return: void
71
+ def announce_finish
72
+ puts "========== [#{exec_mode}] Done! =========="
73
+ puts "========== [#{exec_mode}] Total Target File Count: #{paths.length} =========="
74
+ end
75
+
76
+ # @rbs return: void
77
+ def apply_renames
78
+ file_conversion_map.each do |before, after|
79
+ puts "========== [#{exec_mode}] Replacing the delimiter: `#{before}` => `#{after}` =========="
80
+ rename(before, after) if mode == 'e'
81
+ end
82
+ end
83
+
84
+ # @rbs before: String
85
+ # @rbs after: String
86
+ # @rbs return: void
87
+ def rename(before, after)
88
+ FileUtils.mkdir_p(File.dirname(after)) if after.match?(%r{Disc\d{1}/})
89
+ FileUtils.mv(before, after) if before != after
90
+ end
91
+
92
+ # @rbs return: Hash[String, String]
93
+ def file_conversion_map
94
+ @file_conversion_map ||= paths.to_h { |path| [path, after(path)] }
95
+ end
96
+
97
+ # @rbs path: String
98
+ # @rbs return: String
99
+ def after(path)
100
+ elements = path.split('/')
101
+ elements[-1] = rewrite_filename(elements.last)
102
+ elements.join('/')
103
+ end
104
+
105
+ # @rbs filename: String
106
+ # @rbs return: String
107
+ def rewrite_filename(filename)
108
+ unless filename.match?(/^\d{1}-/)
109
+ return filename.gsub(/(?<track_number>\d{2})\s/, "\\k<track_number>#{delimiter}")
110
+ end
111
+
112
+ filename
113
+ .gsub(/^(?<disc_number>\d{1})-/, 'Disc\k<disc_number>/')
114
+ .gsub(%r{(?<disc_number>Disc\d{1})/(?<track_number>\d{2})\s}, "\\k<disc_number>/\\k<track_number>#{delimiter}")
115
+ end
116
+
117
+ # @rbs return: String
118
+ def exec_mode
119
+ @exec_mode ||= mode == 'e' ? 'EXECUTION' : 'DRY RUN'
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require 'optparse'
5
+ require_relative 'version'
6
+ require_relative 'application'
7
+
8
+ module SpreenTracks
9
+ # Command line interface behind the `track-delimiter` executable:
10
+ # `track-delimiter [--extension EXT] [--delimiter CHAR] [--mode d|e]`.
11
+ class CLI
12
+ MODES = %w[d e].freeze #: Array[String]
13
+
14
+ # @rbs argv: Array[String]
15
+ # @rbs return: Integer
16
+ def self.start(argv = ARGV)
17
+ new(argv).run
18
+ end
19
+
20
+ # @rbs argv: Array[String]
21
+ # @rbs return: void
22
+ def initialize(argv)
23
+ @argv = argv.dup
24
+ @extension = '.m4a'
25
+ @delimiter = '_'
26
+ @mode = 'd'
27
+ @action = :replace_delimiters
28
+ end
29
+
30
+ # @rbs return: Integer
31
+ def run
32
+ parser.parse!(argv)
33
+ __send__(action)
34
+ rescue Application::InvalidModeError, OptionParser::ParseError => e
35
+ warn e.message
36
+ 1
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :argv #: Array[String]
42
+ attr_reader :extension #: String
43
+ attr_reader :delimiter #: String
44
+ attr_reader :mode #: String
45
+ attr_reader :action #: Symbol
46
+
47
+ # @rbs return: Integer
48
+ def replace_delimiters
49
+ Application.run(extension:, delimiter:, mode:)
50
+ 0
51
+ end
52
+
53
+ # @rbs return: Integer
54
+ def print_version
55
+ puts VERSION
56
+ 0
57
+ end
58
+
59
+ # @rbs return: Integer
60
+ def print_help
61
+ puts parser
62
+ 0
63
+ end
64
+
65
+ # @rbs return: OptionParser
66
+ def parser
67
+ @parser ||= OptionParser.new('Usage: track-delimiter [options]') do |opt|
68
+ replacement_options(opt)
69
+ opt.on('--version', 'Print the version') { @action = :print_version }
70
+ opt.on('-h', '--help', 'Print this help') { @action = :print_help }
71
+ end
72
+ end
73
+
74
+ # @rbs opt: OptionParser
75
+ # @rbs return: void
76
+ def replacement_options(opt)
77
+ opt.on('--extension EXT', 'Target extension of the files to rename (default: .m4a)') do |value|
78
+ @extension = value
79
+ end
80
+ opt.on('--delimiter CHAR', 'Delimiter that replaces the space after the track number (default: _)') do |value|
81
+ @delimiter = value
82
+ end
83
+ opt.on('--mode MODE', MODES,
84
+ "Execution mode (#{MODES.join(' or ')}, default: d for dry run; e executes)") do |value|
85
+ @mode = value
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module SpreenTracks
5
+ VERSION = '0.1.0'
6
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative 'spreen_tracks/version'
5
+ require_relative 'spreen_tracks/application'
6
+ require_relative 'spreen_tracks/cli'
7
+
8
+ # Spreens a music library — the falcon's stoop, then the preen: walks the
9
+ # current directory for media files with a given extension and rewrites their
10
+ # filenames so the space after the track number becomes a configurable
11
+ # delimiter and `N-` disc prefixes become `DiscN/` subdirectories.
12
+ module SpreenTracks
13
+ end
@@ -0,0 +1,2 @@
1
+ # Generated from lib/spreen-tracks.rb with RBS::Inline
2
+
@@ -0,0 +1,70 @@
1
+ # Generated from lib/spreen_tracks/application.rb with RBS::Inline
2
+
3
+ module SpreenTracks
4
+ # Walks the current directory looking for media files with the given extension
5
+ # and rewrites their filenames so the space delimiter becomes a configurable
6
+ # character (and `N-` disc prefixes become `DiscN/` subdirectories).
7
+ class Application
8
+ class InvalidModeError < StandardError
9
+ end
10
+
11
+ # @rbs extension: String
12
+ # @rbs delimiter: String
13
+ # @rbs mode: String
14
+ # @rbs return: void
15
+ def self.run: (?extension: String, ?delimiter: String, ?mode: String) -> void
16
+
17
+ # @rbs extension: String
18
+ # @rbs delimiter: String
19
+ # @rbs mode: String
20
+ # @rbs return: void
21
+ def initialize: (?extension: String, ?delimiter: String, ?mode: String) -> void
22
+
23
+ # @rbs return: void
24
+ def replace: () -> void
25
+
26
+ # @rbs return: void
27
+ def validate_mode!: () -> void
28
+
29
+ private
30
+
31
+ attr_reader paths: untyped
32
+
33
+ attr_reader extension: untyped
34
+
35
+ attr_reader delimiter: untyped
36
+
37
+ attr_reader mode: untyped
38
+
39
+ # @rbs return: void
40
+ def announce_empty: () -> void
41
+
42
+ # @rbs return: void
43
+ def announce_start: () -> void
44
+
45
+ # @rbs return: void
46
+ def announce_finish: () -> void
47
+
48
+ # @rbs return: void
49
+ def apply_renames: () -> void
50
+
51
+ # @rbs before: String
52
+ # @rbs after: String
53
+ # @rbs return: void
54
+ def rename: (String before, String after) -> void
55
+
56
+ # @rbs return: Hash[String, String]
57
+ def file_conversion_map: () -> Hash[String, String]
58
+
59
+ # @rbs path: String
60
+ # @rbs return: String
61
+ def after: (String path) -> String
62
+
63
+ # @rbs filename: String
64
+ # @rbs return: String
65
+ def rewrite_filename: (String filename) -> String
66
+
67
+ # @rbs return: String
68
+ def exec_mode: () -> String
69
+ end
70
+ end
@@ -0,0 +1,48 @@
1
+ # Generated from lib/spreen_tracks/cli.rb with RBS::Inline
2
+
3
+ module SpreenTracks
4
+ # Command line interface behind the `track-delimiter` executable:
5
+ # `track-delimiter [--extension EXT] [--delimiter CHAR] [--mode d|e]`.
6
+ class CLI
7
+ MODES: Array[String]
8
+
9
+ # @rbs argv: Array[String]
10
+ # @rbs return: Integer
11
+ def self.start: (?Array[String] argv) -> Integer
12
+
13
+ # @rbs argv: Array[String]
14
+ # @rbs return: void
15
+ def initialize: (Array[String] argv) -> void
16
+
17
+ # @rbs return: Integer
18
+ def run: () -> Integer
19
+
20
+ private
21
+
22
+ attr_reader argv: Array[String]
23
+
24
+ attr_reader extension: String
25
+
26
+ attr_reader delimiter: String
27
+
28
+ attr_reader mode: String
29
+
30
+ attr_reader action: Symbol
31
+
32
+ # @rbs return: Integer
33
+ def replace_delimiters: () -> Integer
34
+
35
+ # @rbs return: Integer
36
+ def print_version: () -> Integer
37
+
38
+ # @rbs return: Integer
39
+ def print_help: () -> Integer
40
+
41
+ # @rbs return: OptionParser
42
+ def parser: () -> OptionParser
43
+
44
+ # @rbs opt: OptionParser
45
+ # @rbs return: void
46
+ def replacement_options: (OptionParser opt) -> void
47
+ end
48
+ end
@@ -0,0 +1,5 @@
1
+ # Generated from lib/spreen_tracks/version.rb with RBS::Inline
2
+
3
+ module SpreenTracks
4
+ VERSION: ::String
5
+ end
@@ -0,0 +1,8 @@
1
+ # Generated from lib/spreen_tracks.rb with RBS::Inline
2
+
3
+ # Spreens a music library — the falcon's stoop, then the preen: walks the
4
+ # current directory for media files with a given extension and rewrites their
5
+ # filenames so the space after the track number becomes a configurable
6
+ # delimiter and `N-` disc prefixes become `DiscN/` subdirectories.
7
+ module SpreenTracks
8
+ end
@@ -0,0 +1,22 @@
1
+ # Generated from test/application_test.rb with RBS::Inline
2
+
3
+ class ApplicationTest < Minitest::Test
4
+ def setup: () -> untyped
5
+
6
+ def teardown: () -> untyped
7
+
8
+ def test_invalid_mode: () -> untyped
9
+
10
+ def test_replace_space_with_underscore_in_dry_run_mode: () -> untyped
11
+
12
+ def test_replace_space_with_underscore_in_execution_mode: () -> untyped
13
+
14
+ def test_replace_space_with_specific_delimiter_in_execution_mode: () -> untyped
15
+
16
+ private
17
+
18
+ attr_reader base_dir: untyped
19
+
20
+ # @rbs return: Array[String]
21
+ def fixture_paths: () -> Array[String]
22
+ end
@@ -0,0 +1,26 @@
1
+ # Generated from test/cli_test.rb with RBS::Inline
2
+
3
+ class CLITest < Minitest::Test
4
+ def setup: () -> untyped
5
+
6
+ def teardown: () -> untyped
7
+
8
+ def test_dry_run_by_default: () -> untyped
9
+
10
+ def test_execution_mode_with_specific_delimiter: () -> untyped
11
+
12
+ def test_extension_option: () -> untyped
13
+
14
+ def test_invalid_mode: () -> untyped
15
+
16
+ def test_version: () -> untyped
17
+
18
+ def test_help: () -> untyped
19
+
20
+ private
21
+
22
+ attr_reader base_dir: untyped
23
+
24
+ # @rbs return: Array[String]
25
+ def fixture_paths: () -> Array[String]
26
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spreen-tracks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - hayat01sh1da
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Spreen — the falcon's stoop, then the preen. Walks the current directory
13
+ for media files with a given extension and rewrites their filenames so the space
14
+ after the track number becomes a configurable delimiter and `N-` disc prefixes become
15
+ `DiscN/` subdirectories, dry run by default, via the track-delimiter CLI.
16
+ executables:
17
+ - track-delimiter
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE.txt
22
+ - README.md
23
+ - exe/track-delimiter
24
+ - lib/spreen-tracks.rb
25
+ - lib/spreen_tracks.rb
26
+ - lib/spreen_tracks/application.rb
27
+ - lib/spreen_tracks/cli.rb
28
+ - lib/spreen_tracks/version.rb
29
+ - sig/generated/spreen-tracks.rbs
30
+ - sig/generated/spreen_tracks.rbs
31
+ - sig/generated/spreen_tracks/application.rbs
32
+ - sig/generated/spreen_tracks/cli.rbs
33
+ - sig/generated/spreen_tracks/version.rbs
34
+ - sig/generated/test/application_test.rbs
35
+ - sig/generated/test/cli_test.rbs
36
+ homepage: https://github.com/hayat01sh1da/spreen-tracks
37
+ licenses:
38
+ - MIT
39
+ metadata:
40
+ homepage_uri: https://github.com/hayat01sh1da/spreen-tracks
41
+ source_code_uri: https://github.com/hayat01sh1da/spreen-tracks
42
+ changelog_uri: https://github.com/hayat01sh1da/spreen-tracks/blob/master/CHANGELOG.md
43
+ bug_tracker_uri: https://github.com/hayat01sh1da/spreen-tracks/issues
44
+ rubygems_mfa_required: 'true'
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '3.4'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 4.0.16
60
+ specification_version: 4
61
+ summary: 'Spreen your tracks: normalise iTunes-imported filenames with a configurable
62
+ delimiter.'
63
+ test_files: []