ogg_album_tagger 0.1.0 → 0.1.2

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: cbafc73bba9ee1dd7d8b8e5ee7c041258f8a9461
4
- data.tar.gz: b44bd48acb92454f074fdb2c61c912d81a29e3ea
3
+ metadata.gz: 983a9f7496ee72da685f94ffa21ae742faf8a0f6
4
+ data.tar.gz: 5936938764084915a9d788c0515d7245291e999e
5
5
  SHA512:
6
- metadata.gz: eafe6bc6b531c0c6d66b83ac632d837ef7ce7b2834ed678e9537f6ab7d3cce977c044413906b6e0b70882b1f60a88b5ff2bbf95f44349f724dcfde7a36000e89
7
- data.tar.gz: 59dbd75622405bff1ebfc78eeb5163608f9bb8824c8778a304f261b5b03ec589e300c264d70af08409415bf01f29139cc0ddf616be298a2e7b7ef1cf1f99ee53
6
+ metadata.gz: b5f6d82ef7288f08eee1643d468ab672187718bb5e3337a0c6985736dd02ceab905a99f4f3e6a6c7db45a73001499b497d61c395adce07d5107756f0f144de0e
7
+ data.tar.gz: '092765d7af62d73484ea9867a173757322424d5a6855c3a89cb6928ecae24fc36b75e7197ae452ae80dba061042c37c87d5c428c10f60eb679d303baa602297a'
@@ -4,264 +4,100 @@ require 'rubygems'
4
4
 
5
5
  require 'ogg_album_tagger/version'
6
6
  require 'ogg_album_tagger/library'
7
- require 'ogg_album_tagger/picture'
8
- require 'ogg_album_tagger/tag_container'
9
- require 'ogg_album_tagger/exceptions'
7
+ require 'ogg_album_tagger/ogg_file'
8
+ require 'ogg_album_tagger/cli'
10
9
 
11
10
  require 'readline'
12
11
  require 'optparse'
13
- require 'shellwords'
12
+ require 'pathname'
14
13
 
15
- def autocomplete(input)
16
- context = Readline.line_buffer.slice(0, Readline.point - input.length)
17
- begin
18
- args = Shellwords.shellwords(context)
19
-
20
- # If there is no space after a quoted string, no autocompletion,
21
- # or we may end up with things like "something"somethingelse
22
- return [] if context.size > 0 and Readline.completer_quote_characters.include?(context[-1])
23
- rescue ArgumentError => ex
24
- # We are autocompleting a quoted string
25
- # The quote will be the last item in the context, so we dump it
26
- context.slice!(-1) if context.size > 0 and Readline.completer_quote_characters.include?(context[-1])
27
- begin
28
- args = Shellwords.shellwords(context)
29
- rescue ArgumentError => ex
30
- return []
31
- end
32
- end
33
-
34
- props = []
35
-
36
- if args.empty? then props = %w{ls select show set add rm auto check write help exit quit}
37
- elsif %w{ls select check help exit quit}.include?(args[0]) then props = []
38
- elsif args[0] == 'show'
39
- if args.size == 1
40
- props = %w{tag}
41
- elsif args[1] == 'tag' and args.size == 2
42
- props = $library.tags_used
43
- end
44
- elsif %w{add set}.include? args[0]
45
- if args.size == 1
46
- props = $library.tags_used
47
- elsif %w{METADATA_BLOCK_PICTURE PICTURE}.include? args[1].upcase
48
- if args.size == 2
49
- props = Readline::FILENAME_COMPLETION_PROC.call(input)
50
- end
51
- end
52
- elsif args[0] == 'rm'
53
- if args.size == 1
54
- props = $library.tags_used
55
- else
56
- tag = args[1].upcase
57
- if tag == 'METADATA_BLOCK_PICTURE'
58
- $stderr.puts
59
- $stderr.puts "Autocompletion is not supported for pictures"
60
- Readline.refresh_line
61
- else props = $library.tag_summary(tag).values.flatten.uniq
62
- end
63
- end
64
- elsif args[0] == 'auto'
65
- if args.length == 1
66
- props = %w{tracknumber rename}
67
- end
68
- end
69
-
70
- (props || []).grep(/^#{Regexp.escape(input)}/i).map do |v|
71
- v.include?(' ') ? "\"#{v}\"" : v
72
- end
73
- end
74
-
75
- def print_album_summary(summary)
76
- OggAlbumTagger::TagContainer.sorted_tags(summary.keys) do |tag|
77
- puts tag
78
-
79
- if (summary[tag].size == $library.selected_files.size) && (summary[tag].values.uniq.length == 1)
80
- # If all tracks have only one common value
81
- puts "\t" + OggAlbumTagger::TagContainer::pp_tag(summary[tag].first[1])
82
- else
83
- summary[tag].keys.sort.each do |i|
84
- values = summary[tag][i]
85
- puts sprintf("\t%4d: %s", i+1, OggAlbumTagger::TagContainer::pp_tag(values))
86
- end
87
- end
88
- end
89
- end
90
-
91
- def show_command(command)
92
- case command.length
93
- when 0
94
- print_album_summary($library.summary)
95
- else
96
- case command[0]
97
- when 'tag'
98
- if command.size == 1 then puts 'You need to specify a tag name'
99
- else print_album_summary $library.summary(command[1].upcase)
100
- end
101
- else
102
- puts "Unknown command 'show #{command[0]}'"
103
- end
104
- end
105
- end
106
-
107
- def ls_command
108
- $library.ls().each do |f|
109
- puts sprintf("%s %4d: %s", (f[:selected] ? '*' : ' '), f[:position], f[:file])
110
- end
111
- end
112
-
113
- def handle_picture_args args
114
- if %w{METADATA_BLOCK_PICTURE PICTURE}.include? args[0].upcase
115
- file = args[1]
116
- desc = args.length == 2 ? args[1] : ''
117
- args.clear
118
- args << 'METADATA_BLOCK_PICTURE'
119
- args << OggAlbumTagger::Picture::generate_metadata_block_picture(file, desc)
120
- end
121
- end
122
-
123
- def execute_command(command_line)
124
- begin
125
- command, *args = Shellwords.shellwords(command_line)
126
- rescue ::StandardError => ex
127
- puts 'Invalid command.'
128
- return
129
- end
130
-
131
- begin
132
- case command
133
- when 'ls' then ls_command()
134
- when 'select'
135
- if args.length < 1
136
- puts 'You need to specify the files you want to select. Either enter "all", a single number or a range (ex. "3-5").', 'Number and range based selections can be made cumulative by adding a plus or minus sign in front of the selector (ex. "-1-3").'
137
- else
138
- $library.select(args)
139
- ls_command()
140
- end
141
- when 'show' then show_command(args)
142
- when 'set'
143
- if args.length < 2
144
- puts 'You need to specify the tag to edit and at least one value.'
145
- else
146
- handle_picture_args(args)
147
- $library.set_tag(*args)
148
- show_command(['tag', args[0]])
149
- end
150
- when 'add'
151
- if args.length < 2
152
- puts 'You need to specify the tag to edit and at least one value.'
153
- else
154
- handle_picture_args(args)
155
- $library.add_tag(*args)
156
- show_command(['tag', args[0]])
157
- end
158
- when 'rm'
159
- if args.length < 1
160
- puts 'You need to specify the tag to edit and eventually one or several values.'
161
- else
162
- $library.rm_tag(*args)
163
- show_command(['tag', args[0]])
164
- end
165
- when 'auto'
166
- if args.length < 1
167
- puts 'You need to specify the auto command you want to execute.'
168
- else
169
- case args[0]
170
- when 'tracknumber' then $library.auto_tracknumber()
171
- when 'rename'
172
- $library.auto_rename
173
- ls_command()
174
- end
175
- end
176
- when 'check'
177
- $library.check
178
- puts "OK"
179
- when 'write'
180
- $library.write
181
- when 'help'
182
- # TODO
183
- else
184
- puts "Unknown command \"#{command}\""
185
- end
186
- rescue OggAlbumTagger::Error => err
187
- puts err
188
- end
189
- end
190
-
191
- def list_ogg_files dir, with_dir = false
192
- Dir.chdir(dir) do
193
- Dir.glob("**/*.ogg").map { |f| with_dir ? Pathname.new(dir).join(f) : Pathname.new(f) }
194
- end
14
+ def list_ogg_files dir
15
+ Dir.chdir(dir) do
16
+ return Dir.glob("**/*.ogg").sort.map { |f| Pathname.new(dir).join(f) }
17
+ end
195
18
  end
196
19
 
197
20
  options = {album: false}
198
21
  option_parser = OptionParser.new do |opts|
199
- executable_name = File.basename($PROGRAM_NAME)
200
- opts.banner = "Interactive edition of ogg tags with album/compilation support.\n" +
201
- "Usage: #{executable_name} [options] files|directories"
22
+ executable_name = File.basename($PROGRAM_NAME)
23
+ opts.banner = "Interactive edition of ogg tags with album/compilation support.\n" +
24
+ "Usage: #{executable_name} [options] files|directories"
202
25
 
203
- opts.separator "Options:"
26
+ opts.separator "Options:"
204
27
 
205
- opts.on("-a", "--album", "Album mode: treat a single directory as an album.") do
206
- options[:album] = true
207
- end
28
+ opts.on("-a", "--album", "Album mode: treat a single directory as an album.") do
29
+ options[:album] = true
30
+ end
208
31
 
209
- opts.on("-v", "--version", "Display version information and exit.") do
210
- puts "OggAlbumTagger #{OggAlbumTagger::VERSION}"
211
- exit
212
- end
32
+ opts.on("-v", "--version", "Display version information and exit.") do
33
+ puts "OggAlbumTagger #{OggAlbumTagger::VERSION}"
34
+ exit
35
+ end
213
36
 
214
- opts.on("-h", "--help", "Print this help and exit.") do
215
- puts opts
216
- exit
217
- end
37
+ opts.on("-h", "--help", "Print this help and exit.") do
38
+ puts opts
39
+ exit
40
+ end
218
41
 
219
- opts.separator ""
220
- opts.separator "OggAlbumTagger is released under the terms of the MIT License."
221
- opts.separator "For more informations, updates and bug reports, see https://github.com/Sigill/OggAlbumTagger."
222
- end.parse!
42
+ opts.separator ""
43
+ opts.separator "OggAlbumTagger is released under the terms of the MIT License."
44
+ opts.separator "For more informations, updates and bug reports, see https://github.com/Sigill/OggAlbumTagger."
45
+ end
46
+
47
+ option_parser.parse!
223
48
 
224
49
  if ARGV.size == 0
225
- puts options_parser
226
- exit
50
+ puts option_parser
51
+ exit
227
52
  else
228
- if options[:album]
229
- unless ARGV.size == 1 and File.directory?(ARGV[0])
230
- abort "Error: in album mode, you have to provide a single directory as argument."
231
- end
232
- end
233
-
234
- files = []
235
- ARGV.each do |arg|
236
- begin
237
- if File.directory?(arg)
238
- files.concat list_ogg_files(arg, !options[:album])
239
- elsif File.file?(arg)
240
- files << Pathname.new(arg)
241
- else
242
- raise Errno::ENOENT
243
- end
244
- rescue Errno::ENOENT
245
- abort "#{arg} does not exists."
246
- end
247
- end
248
-
249
- begin
250
- $library = OggAlbumTagger::Library.new(files, options[:album] ? Pathname.new(ARGV[0]) : nil)
251
- rescue OggAlbumTagger::Error => ex
252
- abort ex.message
253
- end
53
+ if options[:album]
54
+ unless ARGV.size == 1 and File.directory?(ARGV[0])
55
+ abort "Error: in album mode, you have to provide a single directory as argument."
56
+ end
57
+ end
58
+
59
+ files = []
60
+ ARGV.each do |arg|
61
+ begin
62
+ if File.directory?(arg)
63
+ files.concat list_ogg_files(arg)
64
+ elsif File.file?(arg)
65
+ files << Pathname.new(arg)
66
+ else
67
+ raise Errno::ENOENT
68
+ end
69
+ rescue Errno::ENOENT
70
+ abort "#{arg} does not exists."
71
+ end
72
+ end
73
+
74
+ begin
75
+ containers = files.map { |f| OggAlbumTagger::OggFile.new(f) }
76
+ $library = OggAlbumTagger::Library.new(options[:album] ? Pathname.new(ARGV[0]) : nil, containers)
77
+ $interpreter = OggAlbumTagger::CLI.new($library)
78
+ rescue OggAlbumTagger::Error => ex
79
+ abort ex.message
80
+ end
254
81
  end
255
82
 
256
- Readline.completion_append_character = " "
257
- Readline.completer_quote_characters = "\"'"
83
+ $interpreter.configure_readline
258
84
  Readline.completion_proc = ->(input) {
259
- autocomplete(input)
85
+ begin
86
+ $interpreter.autocomplete(Readline.line_buffer, Readline.point, input)
87
+ rescue OggAlbumTagger::Error => ex
88
+ # Warn the user of autocompletion issue
89
+ puts "", ex
90
+ Readline.refresh_line
91
+ end
260
92
  }
261
93
 
262
94
  loop do
263
- command = Readline.readline("> ", true)
264
- break if command.nil? or command.eql?('exit') or command.eql?('quit')
265
-
266
- execute_command(command.strip)
95
+ command = Readline.readline("> ", true)
96
+ break if command.nil? or command.eql?('exit') or command.eql?('quit')
97
+
98
+ begin
99
+ $interpreter.execute_command(command.strip)
100
+ rescue OggAlbumTagger::Error => ex
101
+ puts ex
102
+ end
267
103
  end
@@ -0,0 +1,278 @@
1
+ require 'readline'
2
+ require 'shellwords'
3
+
4
+ require 'ogg_album_tagger/exceptions'
5
+ require 'ogg_album_tagger/picture'
6
+
7
+ module OggAlbumTagger
8
+
9
+
10
+ class CLI
11
+ QUOTE_CHARACTERS = "\"'"
12
+
13
+ def initialize(library)
14
+ @library = library
15
+ end
16
+
17
+ def configure_readline
18
+ Readline.completer_quote_characters = QUOTE_CHARACTERS
19
+ Readline.completion_append_character = " "
20
+ end
21
+
22
+ SIMPLE_SELECTOR = /^[+-]?[1-9]\d*$/
23
+ RANGE_SELECTOR = /^[+-]?[1-9]\d*-[1-9]\d*$/
24
+
25
+ def selector?(arg)
26
+ return arg == 'all' || arg.match(SIMPLE_SELECTOR) || arg.match(RANGE_SELECTOR)
27
+ end
28
+
29
+ def autocomplete(buffer, point, input)
30
+ # Extract the context: everything before the word being completed.
31
+ context = buffer.slice(0, point - input.length)
32
+
33
+ begin
34
+ # Let's try to split the context into its various arguments.
35
+ context_args = Shellwords.shellwords(context)
36
+
37
+ # If the last argument is quoted and the quote is the last character of the context,
38
+ # no autocompletion, or we may end up with things like "something"somethingelse.
39
+ return [] if context.size > 0 and QUOTE_CHARACTERS.include?(context[-1])
40
+ rescue ::ArgumentError => ex
41
+ # The context couldn't be parsed because it ends with an opening quote.
42
+ # Let's remove it to allow the context to be parsed.
43
+ context.slice!(-1) if context.size > 0 and QUOTE_CHARACTERS.include?(context[-1])
44
+ begin
45
+ context_args = Shellwords.shellwords(context)
46
+ rescue ::ArgumentError => ex
47
+ return []
48
+ end
49
+ end
50
+
51
+ # Remove leading selectors
52
+ first_not_selector = context_args.find_index { |e| !selector?(e) }
53
+ context_args.slice!(0, first_not_selector) unless first_not_selector.nil? || first_not_selector == 0
54
+
55
+ # Keep only suggestions starting with the input.
56
+ sugg = suggestions(context_args, input).grep(/^#{Regexp.escape(input)}/i).map { |v|
57
+ # Quote them if they contain spaces, otherwise a multiple word value
58
+ # will appear unquoted on the command line.
59
+ v.include?(' ') ? "\"#{v}\"" : v
60
+ }
61
+ sugg
62
+ end
63
+
64
+ def suggestions(context, input)
65
+ if context.empty?
66
+ # No args, suggest the supported commands.
67
+ return %w{ls select move show set add rm auto check write help exit quit}
68
+ elsif %w{ls select move check help exit quit}.include?(context[0])
69
+ # These commands don't take any argument or don't need autocomplete.
70
+ elsif context[0] == 'show'
71
+ # The "show" command can be followed by a tag
72
+ return @library.tags_used if context.size == 1
73
+ elsif %w{add set}.include?(context[0])
74
+ case context.size
75
+ when 1
76
+ # The "add" and "set" commands must be followed by a tag.
77
+ # TODO Suggest usual ogg tags.
78
+ return @library.tags_used
79
+ when 2
80
+ if %w{METADATA_BLOCK_PICTURE PICTURE}.include? context[1].upcase
81
+ # When a picture tag is edited, the tag must be followed by the path of a file.
82
+ return Readline::FILENAME_COMPLETION_PROC.call(input) || []
83
+ else
84
+ # Otherwise, propose values already used by the specified tag.
85
+ return @library.tag_summary(context[1].upcase).values.flatten.uniq
86
+ end
87
+ end
88
+ elsif context[0] == 'rm'
89
+ if context.size == 1
90
+ return @library.tags_used
91
+ else
92
+ tag = context[1].upcase
93
+ raise ArgumentError, "Autocompletion is not supported for pictures" if tag == 'METADATA_BLOCK_PICTURE'
94
+
95
+ return @library.tag_summary(tag).values.flatten.uniq
96
+ end
97
+ elsif context[0] == 'auto'
98
+ if context.length == 1
99
+ return %w{tracknumber rename}
100
+ end
101
+ end
102
+
103
+ return []
104
+ end
105
+
106
+ def print_album_summary(summary)
107
+ OggAlbumTagger::OggFile.sorted_tags(summary.keys) do |tag|
108
+ puts tag
109
+
110
+ if (summary[tag].size == @library.selected_files.size) && (summary[tag].values.uniq.length == 1)
111
+ # If all tracks have only one common value
112
+ puts "\t" + OggAlbumTagger::TagContainer::pp_tag(summary[tag].first[1])
113
+ else
114
+ summary[tag].keys.sort.each do |i|
115
+ values = summary[tag][i]
116
+ puts sprintf("\t%4d: %s", i+1, OggAlbumTagger::TagContainer::pp_tag(values))
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ def show_command(args)
123
+ case args.length
124
+ when 0
125
+ print_album_summary(@library.summary)
126
+ else
127
+ print_album_summary @library.summary(args[0].upcase)
128
+ end
129
+ end
130
+
131
+ def select_command(args)
132
+ if args.length == 0
133
+ m = "Usage: select <selector>+\n\n" +
134
+ "A selector can either be \"all\", a single index or a range (e.g. 3-5).\n" +
135
+ "Number and range based selectors can be made cumulative by adding a plus or minus sign in front of the selector (ex. \"-1-3\")."
136
+ raise ArgumentError, m
137
+ end
138
+
139
+ @library.select(args)
140
+ end
141
+
142
+ def ls_command
143
+ @library.ls().each_with_index { |f, i|
144
+ puts sprintf("%s %4d: %s", (f[:selected] ? '*' : ' '), i + 1, f[:file])
145
+ }
146
+ end
147
+
148
+ def handle_picture_args args
149
+ if %w{METADATA_BLOCK_PICTURE PICTURE}.include? args[0].upcase
150
+ file = args[1]
151
+ desc = args.length == 3 ? args[2] : ''
152
+ args.clear
153
+ args << 'METADATA_BLOCK_PICTURE'
154
+ args << OggAlbumTagger::Picture::generate_metadata_block_picture(file, desc)
155
+ end
156
+ end
157
+
158
+ def move_command(args)
159
+ raise ArgumentError if args.length != 2
160
+
161
+ # Will raise ::ArgumentError if not integers
162
+ from = Integer(args[0]) - 1
163
+ to = Integer(args[1]) - 1
164
+
165
+ @library.move(from, to)
166
+ rescue ::StandardError
167
+ m = "Usage: move <from_index> <to_index>\n\n" +
168
+ "<from_index> must be in the [1; #{@library.size}] range.\n" +
169
+ "<to_index> must be in the [1; #{@library.size + 1}] range."
170
+ raise ArgumentError, m
171
+ end
172
+
173
+ def set_command(args)
174
+ if args.length < 2
175
+ m = "Usage: set <tag> <value>+"
176
+ raise ArgumentError, m
177
+ end
178
+
179
+ handle_picture_args(args)
180
+ @library.set_tag(*args)
181
+ end
182
+
183
+ def add_command(args)
184
+ if args.length < 2
185
+ m = "Usage: add <tag> [<value>+]"
186
+ raise ArgumentError, m
187
+ end
188
+
189
+ handle_picture_args(args)
190
+ @library.add_tag(*args)
191
+ end
192
+
193
+ def rm_command(args)
194
+ if args.length < 1
195
+ m = "Usage: rm <tag> [<value>+]"
196
+ raise ArgumentError, m
197
+ end
198
+
199
+ @library.rm_tag(*args)
200
+ end
201
+
202
+ def auto_command(args)
203
+ usage = "Usage: auto tracknumber|rename"
204
+ if args.length < 1
205
+ raise ArgumentError, usage
206
+ end
207
+
208
+ case args[0]
209
+ when 'tracknumber'
210
+ @library.auto_tracknumber()
211
+ when 'rename'
212
+ @library.auto_rename
213
+ ls_command()
214
+ else
215
+ raise ArgumentError, usage
216
+ end
217
+ end
218
+
219
+ def parse_command(command_line)
220
+ begin
221
+ arguments = Shellwords.shellwords(command_line)
222
+ rescue ::StandardError
223
+ raise OggAlbumTagger::ArgumentError, "Invalid command."
224
+ end
225
+
226
+ selectors = []
227
+
228
+ first_not_selector = arguments.find_index { |e| !selector?(e) }
229
+ unless first_not_selector.nil? || first_not_selector == 0
230
+ selectors = arguments.slice!(0, first_not_selector)
231
+ end
232
+
233
+ command, *args = arguments
234
+
235
+ return selectors, command, args
236
+ end
237
+
238
+ def execute_command(command_line)
239
+ selectors, command, args = parse_command(command_line)
240
+
241
+ return if command.nil?
242
+
243
+ @library.with_selection(selectors) {
244
+ case command
245
+ when 'ls' then ls_command()
246
+ when 'select'
247
+ select_command(args)
248
+ ls_command()
249
+ when 'move'
250
+ move_command(args)
251
+ ls_command()
252
+ when 'show' then show_command(args)
253
+ when 'set'
254
+ set_command(args)
255
+ show_command([args[0]])
256
+ when 'add'
257
+ add_command(args)
258
+ show_command([args[0]])
259
+ when 'rm'
260
+ rm_command(args)
261
+ show_command([args[0]])
262
+ when 'auto'
263
+ auto_command(args)
264
+ when 'check'
265
+ @library.check
266
+ puts "OK"
267
+ when 'write'
268
+ @library.write
269
+ when 'help'
270
+ # TODO
271
+ else
272
+ puts "Unknown command \"#{command}\""
273
+ end
274
+ }
275
+ end
276
+ end
277
+
278
+ end # module OggAlbumTagger