wavesync 1.0.0.alpha1 → 1.0.0.alpha2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ba5ed4b274c7287903407029c902eeee5baeb3138f7f4fe19127a2ceca5329f
4
- data.tar.gz: c2653fe2efc113dafd67814a7c653bc2f7a9c068369800775028397ecc963793
3
+ metadata.gz: 166c4643be69e020ac6ac73611146502faa4891ad35f60bd30f3143702113c14
4
+ data.tar.gz: f41b3be18c7f4014f892a19e27612a363502d1f70effe098e74d239b19d9d873
5
5
  SHA512:
6
- metadata.gz: 846c29ec104723435dbb14bb4eb7902e17ed3276c322de9d82664ede303eb3878394bff44ee0769b934e3d39066ddc9d437656565f71c7ccf4ab8a956b42b2d0
7
- data.tar.gz: a3b5135a634bf8d088cc8886e4244d62ddfddd7428f974781d47037432ca80476e92bb736d5fa0f0467e5d64c9d07b8fd73c331c78b031d395c25ce432095483
6
+ metadata.gz: d1df257786183fa8253036ca6e59b7b559bab4d82fba2da101aa86c58e91f56af272215fd406785e05c8eec6cb473c22ea8d90ff9fd4367f6894ae9916aef54a
7
+ data.tar.gz: b29f51c0a796aedddee42d219e042e02b150b89ec23775b40ca7c8d7e93b2b7c1dae438a240aa3e549e2e3f53318f3960d6305965d29a6199b29f62d99c1fa10
data/README.md CHANGED
@@ -63,10 +63,17 @@ devices:
63
63
 
64
64
  Wavesync will exit with an error if a device model in the config is not supported.
65
65
 
66
+ ## Help
67
+
68
+ ```bash
69
+ wavesync help
70
+ ```
71
+
66
72
  ## Usage
67
73
 
68
74
  ```bash
69
- # Sync library to all devices (uses default config at ~/wavesync.yml)
75
+ # Sync library (uses default config at ~/wavesync.yml)
76
+ # If multiple devices are configured, you will be prompted to select one
70
77
  wavesync sync
71
78
 
72
79
  # Use a config at a specific path
@@ -133,3 +140,17 @@ Example: If a 96kHz file is synced to an Octatrack (which only supports 44.1kHz)
133
140
  ```bash
134
141
  rake test
135
142
  ```
143
+
144
+ ### Running RuboCop
145
+
146
+ ```bash
147
+ bundle exec rubocop
148
+ ```
149
+
150
+ ### Releasing
151
+
152
+ ```bash
153
+ rake release:publish
154
+ ```
155
+
156
+ This tags the current version, pushes the tag to origin, builds the gem, and publishes it to RubyGems.
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Wavesync
4
4
  class Analyzer
5
+ CONFIRM_MESSAGE = 'wavesync analyze will add bpm meta data to files in library. Continue? [y/N] '
6
+
5
7
  def initialize(library_path)
6
8
  @library_path = File.expand_path(library_path)
7
9
  @audio_files = find_audio_files
@@ -14,6 +16,8 @@ module Wavesync
14
16
  exit 1
15
17
  end
16
18
 
19
+ return unless @ui.confirm(CONFIRM_MESSAGE)
20
+
17
21
  @audio_files.each_with_index do |file, index|
18
22
  audio = Audio.new(file)
19
23
 
data/lib/wavesync/cli.rb CHANGED
@@ -1,159 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'optparse'
3
+ require_relative 'commands'
4
4
 
5
5
  module Wavesync
6
6
  class CLI
7
7
  def self.start
8
- command = ARGV.first && !ARGV.first.start_with?('-') ? ARGV.shift : 'sync'
8
+ command_name = ARGV.first && !ARGV.first.start_with?('-') ? ARGV.shift : 'sync'
9
+ command_class = Commands::ALL.find { |cmd| command_name == cmd.name }
9
10
 
10
- case command
11
- when 'sync'
12
- start_sync
13
- when 'analyze'
14
- start_analyze
15
- when 'set'
16
- start_set
11
+ if command_class
12
+ command_class.new.run
17
13
  else
18
- puts "Unknown command: #{command}"
19
- puts 'Available commands: sync, analyze, set'
14
+ puts "Unknown command: #{command_name}"
15
+ puts "Available commands: #{Commands::ALL.map(&:name).join(', ')}"
20
16
  exit 1
21
17
  end
22
18
  end
23
-
24
- def self.start_sync
25
- options = {}
26
- parser = OptionParser.new do |opts|
27
- opts.banner = 'Usage: wavesync sync [options]'
28
-
29
- opts.on('-d', '--device NAME', 'Name of device to sync (as defined in config)') do |value|
30
- options[:device] = value
31
- end
32
-
33
- opts.on('-c', '--config PATH', 'Path to wavesync config YAML file') do |value|
34
- options[:config] = value
35
- end
36
-
37
- opts.on('-p', '--pad', 'Pad tracks with silence so total length is a multiple of 64 bars (Octatrack only)') do
38
- options[:pad] = true
39
- end
40
- end
41
-
42
- parser.parse!
43
-
44
- config_path = options[:config] || Wavesync::Config::DEFAULT_PATH
45
- config = load_config(config_path)
46
-
47
- device_configs = config.device_configs
48
- if options[:device]
49
- device_configs = device_configs.select { |device_config| device_config[:name] == options[:device] }
50
- if device_configs.empty?
51
- known = config.device_configs.map { |device_config| device_config[:name] }.join(', ')
52
- puts "Unknown device \"#{options[:device]}\". Devices in config: #{known}"
53
- exit 1
54
- end
55
- end
56
-
57
- device_configs.each do |device_config|
58
- next if Wavesync::Device.find_by(name: device_config[:model])
59
-
60
- supported = Wavesync::Device.all.map(&:name).join(', ')
61
- puts "Unknown device model \"#{device_config[:model]}\" in config. Supported models: #{supported}"
62
- exit 1
63
- end
64
-
65
- scanner = Wavesync::Scanner.new(config.library)
66
-
67
- device_configs.each do |device_config|
68
- scanner.sync(device_config[:path], Wavesync::Device.find_by(name: device_config[:model]),
69
- pad: options[:pad] || false)
70
- end
71
- end
72
-
73
- def self.start_set
74
- subcommand = ARGV.shift
75
-
76
- options = {}
77
- parser = OptionParser.new do |opts|
78
- opts.banner = 'Usage: wavesync set <subcommand> [options]'
79
-
80
- opts.on('-c', '--config PATH', 'Path to wavesync config YAML file') do |value|
81
- options[:config] = value
82
- end
83
- end
84
-
85
- parser.parse!
86
-
87
- config_path = options[:config] || Wavesync::Config::DEFAULT_PATH
88
- config = load_config(config_path)
89
-
90
- case subcommand
91
- when 'create'
92
- name = require_set_name('create')
93
- if Wavesync::Set.exists?(config.library, name)
94
- puts "Set '#{name}' already exists. Use 'wavesync set edit #{name}' to edit it."
95
- exit 1
96
- end
97
- set = Wavesync::Set.new(config.library, name)
98
- Wavesync::SetEditor.new(set, config.library).run
99
- when 'edit'
100
- name = require_set_name('edit')
101
- unless Wavesync::Set.exists?(config.library, name)
102
- puts "Set '#{name}' not found. Use 'wavesync set create #{name}' to create it."
103
- exit 1
104
- end
105
- set = Wavesync::Set.load(config.library, name)
106
- Wavesync::SetEditor.new(set, config.library).run
107
- when 'list'
108
- sets = Wavesync::Set.all(config.library)
109
- if sets.empty?
110
- puts 'No sets found.'
111
- else
112
- sets.each { |s| puts "#{s.name} (#{s.tracks.size} tracks)" }
113
- end
114
- else
115
- puts "Unknown subcommand: #{subcommand || '(none)'}"
116
- puts 'Available subcommands: create, edit, list'
117
- exit 1
118
- end
119
- end
120
-
121
- def self.load_config(path)
122
- Wavesync::Config.load(path)
123
- rescue Wavesync::ConfigError => e
124
- puts "Configuration error: #{e.message}"
125
- exit 1
126
- end
127
-
128
- def self.require_set_name(subcommand)
129
- name = ARGV.shift
130
- unless name
131
- puts "Usage: wavesync set #{subcommand} <name>"
132
- exit 1
133
- end
134
- name
135
- end
136
-
137
- def self.start_analyze
138
- options = {}
139
- parser = OptionParser.new do |opts|
140
- opts.banner = 'Usage: wavesync analyze [options]'
141
-
142
- opts.on('-c', '--config PATH', 'Path to wavesync config YAML file') do |value|
143
- options[:config] = value
144
- end
145
-
146
- opts.on('-f', '--force', 'Overwrite existing BPM values') do
147
- options[:overwrite] = true
148
- end
149
- end
150
-
151
- parser.parse!
152
-
153
- config_path = options[:config] || Wavesync::Config::DEFAULT_PATH
154
- config = load_config(config_path)
155
-
156
- Wavesync::Analyzer.new(config.library).analyze(overwrite: options[:overwrite] || false)
157
- end
158
19
  end
159
20
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module Wavesync
6
+ module Commands
7
+ class Analyze < Command
8
+ FORCE_OPTION = Option.new(short: '-f', long: '--force', description: 'Overwrite existing BPM values')
9
+
10
+ self.name = 'analyze'
11
+ self.description = 'Detect and write BPM metadata to library tracks'
12
+ self.options = [FORCE_OPTION].freeze
13
+
14
+ def run
15
+ options, config = parse_options(banner: 'Usage: wavesync analyze [options]') do |opts, opts_hash|
16
+ opts.on(*FORCE_OPTION.to_a) { opts_hash[:overwrite] = true }
17
+ end
18
+
19
+ Wavesync::Analyzer.new(config.library).analyze(overwrite: options[:overwrite] || false)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module Wavesync
6
+ module Commands
7
+ class Command
8
+ class << self
9
+ attr_accessor :name, :description
10
+ attr_writer :options, :subcommands
11
+
12
+ def options = @options || []
13
+ def subcommands = @subcommands || []
14
+ end
15
+
16
+ def parse_options(banner:)
17
+ options = {}
18
+ OptionParser.new do |opts|
19
+ opts.banner = banner
20
+ opts.on(*CONFIG_OPTION.to_a) { |value| options[:config] = value }
21
+ yield opts, options if block_given?
22
+ end.parse!
23
+ config_path = options[:config] || Wavesync::Config::DEFAULT_PATH
24
+ config = Commands.load_config(config_path)
25
+ [options, config]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'rainbow'
5
+
6
+ module Wavesync
7
+ module Commands
8
+ class Help < Command
9
+ self.name = 'help'
10
+ self.description = 'Show this help message'
11
+
12
+ DESCRIPTION_COLUMN = 23
13
+
14
+ def run
15
+ subcommand_name = ARGV.shift
16
+
17
+ if subcommand_name
18
+ command = ALL.find { |cmd| subcommand_name == cmd.name }
19
+ if command
20
+ show_command_help(command)
21
+ else
22
+ puts "Unknown command: #{subcommand_name}"
23
+ puts "Available commands: #{ALL.map(&:name).reject { |cmd_name| cmd_name == self.class.name }.join(', ')}"
24
+ exit 1
25
+ end
26
+ else
27
+ show_general_help
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def show_general_help
34
+ puts 'Usage: wavesync [command] [options]'
35
+ puts ''
36
+ puts 'Commands:'
37
+ ALL.each do |command|
38
+ if command.subcommands.any?
39
+ command.subcommands.each { |subcommand| puts format_command_line(subcommand.usage, subcommand.description) }
40
+ else
41
+ puts format_command_line(command.name, command.description)
42
+ command.options.each { |option| puts format_option_line(option, indent: 4) }
43
+ end
44
+ puts ''
45
+ end
46
+ puts 'Options:'
47
+ GLOBAL_OPTIONS.each { |option| puts format_option_line(option, indent: 2) }
48
+ end
49
+
50
+ def show_command_help(command)
51
+ if command.subcommands.any?
52
+ puts "Usage: wavesync #{command.name} <subcommand> [options]"
53
+ puts ''
54
+ puts 'Subcommands:'
55
+ command.subcommands.each do |subcommand|
56
+ subcommand_key = subcommand.usage.delete_prefix("#{command.name} ")
57
+ puts " #{subcommand_key.ljust(DESCRIPTION_COLUMN - 2)}#{subcommand.description}"
58
+ end
59
+ puts ''
60
+ puts 'Options:'
61
+ GLOBAL_OPTIONS.each { |option| puts " #{option.short}, #{option.long} #{option.description}" }
62
+ else
63
+ OptionParser.new do |opts|
64
+ opts.banner = "Usage: wavesync #{command.name} [options]"
65
+ (command.options + GLOBAL_OPTIONS).each { |option| opts.on(*option.to_a) }
66
+ puts opts
67
+ end
68
+ end
69
+ end
70
+
71
+ def format_command_line(name, description)
72
+ " #{name.ljust(DESCRIPTION_COLUMN - 2)}#{description}"
73
+ end
74
+
75
+ def format_option_line(option, indent:)
76
+ key = "#{option.short}, #{option.long}"
77
+ Rainbow("#{' ' * indent}#{key.ljust(DESCRIPTION_COLUMN - indent)}#{option.description}").darkgray
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module Wavesync
6
+ module Commands
7
+ class Set < Command
8
+ self.name = 'set'
9
+ self.subcommands = [
10
+ Subcommand.new(usage: 'set create NAME', description: 'Create a new track set'),
11
+ Subcommand.new(usage: 'set edit NAME', description: 'Edit an existing track set'),
12
+ Subcommand.new(usage: 'set list', description: 'List all track sets')
13
+ ].freeze
14
+
15
+ def run
16
+ subcommand = ARGV.shift
17
+
18
+ _options, config = parse_options(banner: 'Usage: wavesync set <subcommand> [options]')
19
+
20
+ case subcommand
21
+ when 'create'
22
+ name = require_name('create')
23
+ if Wavesync::Set.exists?(config.library, name)
24
+ puts "Set '#{name}' already exists. Use 'wavesync set edit #{name}' to edit it."
25
+ exit 1
26
+ end
27
+ set = Wavesync::Set.new(config.library, name)
28
+ Wavesync::SetEditor.new(set, config.library).run
29
+ when 'edit'
30
+ name = require_name('edit')
31
+ unless Wavesync::Set.exists?(config.library, name)
32
+ puts "Set '#{name}' not found. Use 'wavesync set create #{name}' to create it."
33
+ exit 1
34
+ end
35
+ set = Wavesync::Set.load(config.library, name)
36
+ Wavesync::SetEditor.new(set, config.library).run
37
+ when 'list'
38
+ sets = Wavesync::Set.all(config.library)
39
+ if sets.empty?
40
+ puts 'No sets found.'
41
+ else
42
+ sets.each { |set| puts "#{set.name} (#{set.tracks.size} tracks)" }
43
+ end
44
+ else
45
+ puts "Unknown subcommand: #{subcommand || '(none)'}"
46
+ puts 'Available subcommands: create, edit, list'
47
+ exit 1
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def require_name(subcommand)
54
+ name = ARGV.shift
55
+ unless name
56
+ puts "Usage: wavesync set #{subcommand} <name>"
57
+ exit 1
58
+ end
59
+ name
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module Wavesync
6
+ module Commands
7
+ class Sync < Command
8
+ DEVICE_OPTION = Option.new(short: '-d', long: '--device NAME', description: 'Name of device to sync (as defined in config)')
9
+ PAD_OPTION = Option.new(short: '-p', long: '--pad', description: 'Pad tracks with silence so total length is a multiple of 64 bars (Octatrack only)')
10
+
11
+ self.name = 'sync'
12
+ self.description = 'Sync music library to a device'
13
+ self.options = [DEVICE_OPTION, PAD_OPTION].freeze
14
+
15
+ def run
16
+ options, config = parse_options(banner: 'Usage: wavesync sync [options]') do |opts, opts_hash|
17
+ opts.on(*DEVICE_OPTION.to_a) { |value| opts_hash[:device] = value }
18
+ opts.on(*PAD_OPTION.to_a) { opts_hash[:pad] = true }
19
+ end
20
+
21
+ device_configs = config.device_configs
22
+ if options[:device]
23
+ device_configs = device_configs.select { |device_config| device_config[:name] == options[:device] }
24
+ if device_configs.empty?
25
+ known = config.device_configs.map { |device_config| device_config[:name] }.join(', ')
26
+ puts "Unknown device \"#{options[:device]}\". Devices in config: #{known}"
27
+ exit 1
28
+ end
29
+ elsif device_configs.size > 1
30
+ device_names = device_configs.map { |device_config| device_config[:name] }
31
+ selected_name = Wavesync::UI.new.select('Select device', device_names)
32
+ device_configs = device_configs.select { |device_config| device_config[:name] == selected_name }
33
+ end
34
+
35
+ device_pairs = device_configs.map do |device_config|
36
+ device = Wavesync::Device.find_by(name: device_config[:model])
37
+ unless device
38
+ supported = Wavesync::Device.all.map(&:name).join(', ')
39
+ puts "Unknown device model \"#{device_config[:model]}\" in config. Supported models: #{supported}"
40
+ exit 1
41
+ end
42
+ [device_config, device]
43
+ end
44
+
45
+ scanner = Wavesync::Scanner.new(config.library)
46
+
47
+ device_pairs.each do |device_config, device|
48
+ scanner.sync(device_config[:path], device, pad: options[:pad] || false)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wavesync
4
+ module Commands
5
+ Option = Struct.new(:short, :long, :description, keyword_init: true)
6
+ Subcommand = Struct.new(:usage, :description, keyword_init: true)
7
+
8
+ CONFIG_OPTION = Option.new(short: '-c', long: '--config PATH', description: 'Path to wavesync config YAML file')
9
+ GLOBAL_OPTIONS = [CONFIG_OPTION].freeze
10
+
11
+ def self.load_config(path)
12
+ Wavesync::Config.load(path)
13
+ rescue Wavesync::ConfigError => e
14
+ puts "Configuration error: #{e.message}"
15
+ exit 1
16
+ end
17
+ end
18
+ end
19
+
20
+ require_relative 'commands/command'
21
+ require_relative 'commands/sync'
22
+ require_relative 'commands/analyze'
23
+ require_relative 'commands/set'
24
+ require_relative 'commands/help'
25
+
26
+ module Wavesync
27
+ module Commands
28
+ ALL = [Sync, Analyze, Set, Help].freeze
29
+ end
30
+ end
data/lib/wavesync/ui.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'tty-cursor'
4
+ require 'tty-prompt'
4
5
  require 'rainbow'
5
6
 
6
7
  module Wavesync
@@ -17,6 +18,7 @@ module Wavesync
17
18
  def initialize
18
19
  @cursor = TTY::Cursor
19
20
  @sticky_lines = []
21
+ @prompt = TTY::Prompt.new(interrupt: :exit, active_color: :red)
20
22
  end
21
23
 
22
24
  def file_progress(filename)
@@ -86,6 +88,16 @@ module Wavesync
86
88
  set_analyze_file_stickies(file, label)
87
89
  end
88
90
 
91
+ def confirm(message)
92
+ print in_color(message, :secondary)
93
+ response = $stdin.gets.to_s.strip.downcase
94
+ response == 'y'
95
+ end
96
+
97
+ def select(label, options)
98
+ @prompt.select(label, options, cycle: true)
99
+ end
100
+
89
101
  def color(text, key)
90
102
  in_color(text, key)
91
103
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wavesync
4
- VERSION = '1.0.0.alpha1'
4
+ VERSION = '1.0.0.alpha2'
5
5
  end
data/lib/wavesync.rb CHANGED
@@ -18,4 +18,5 @@ require 'wavesync/bpm_detector'
18
18
  require 'wavesync/analyzer'
19
19
  require 'wavesync/set'
20
20
  require 'wavesync/set_editor'
21
+ require 'wavesync/commands'
21
22
  require 'wavesync/cli'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wavesync
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.alpha1
4
+ version: 1.0.0.alpha2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Zecher
@@ -109,6 +109,12 @@ files:
109
109
  - lib/wavesync/audio_format.rb
110
110
  - lib/wavesync/bpm_detector.rb
111
111
  - lib/wavesync/cli.rb
112
+ - lib/wavesync/commands.rb
113
+ - lib/wavesync/commands/analyze.rb
114
+ - lib/wavesync/commands/command.rb
115
+ - lib/wavesync/commands/help.rb
116
+ - lib/wavesync/commands/set.rb
117
+ - lib/wavesync/commands/sync.rb
112
118
  - lib/wavesync/config.rb
113
119
  - lib/wavesync/device.rb
114
120
  - lib/wavesync/file_converter.rb
@@ -122,7 +128,9 @@ files:
122
128
  homepage: https://github.com/pixelate/wavesync
123
129
  licenses:
124
130
  - MIT
125
- metadata: {}
131
+ metadata:
132
+ documentation_uri: https://github.com/pixelate/wavesync?tab=readme-ov-file#wavesync
133
+ rubygems_mfa_required: 'true'
126
134
  rdoc_options: []
127
135
  require_paths:
128
136
  - lib
@@ -130,7 +138,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
130
138
  requirements:
131
139
  - - ">="
132
140
  - !ruby/object:Gem::Version
133
- version: '3.0'
141
+ version: '3.3'
134
142
  required_rubygems_version: !ruby/object:Gem::Requirement
135
143
  requirements:
136
144
  - - ">="