tv_renamer 4.0.2
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 +7 -0
- data/LICENSE +340 -0
- data/README.md +2 -0
- data/bin/tv_renamer +6 -0
- data/lib/tv_renamer/ini.rb +267 -0
- data/lib/tv_renamer/renamer.rb +646 -0
- data/lib/tv_renamer.rb +2 -0
- data/test/testrenamer.rb +170 -0
- metadata +65 -0
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
# renamer.rb
|
|
2
|
+
# Version 4.0.0
|
|
3
|
+
# Copyright 2011 Kevin Adler
|
|
4
|
+
# License: GPL v2
|
|
5
|
+
|
|
6
|
+
require 'rubygems'
|
|
7
|
+
require 'net/http'
|
|
8
|
+
require 'date'
|
|
9
|
+
require 'cgi'
|
|
10
|
+
require 'fileutils'
|
|
11
|
+
require 'nokogiri'
|
|
12
|
+
|
|
13
|
+
class VideoFile
|
|
14
|
+
SPLITS = [ ' ', '.' ]
|
|
15
|
+
|
|
16
|
+
attr_accessor :orig_show, :season, :episode_number, :episode_name, :extension, :filename, :date, :production_code, :format, :rename_by_date
|
|
17
|
+
|
|
18
|
+
def show
|
|
19
|
+
@orig_show ||= show_name_from_tokens
|
|
20
|
+
@show ? @show : @orig_show
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def show=(show)
|
|
24
|
+
@show = show
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_s
|
|
28
|
+
[show, @season, @episode_number, @date, @production_code, @episode_name].join(' : ')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize(filename)
|
|
32
|
+
@filename = filename
|
|
33
|
+
@extension ||= @filename.split('.').pop
|
|
34
|
+
@show_pieces = Array.new
|
|
35
|
+
|
|
36
|
+
cleaned_filename = @filename.delete("[]").gsub(" - ", " ").gsub(/\([-\w]+\)/, '')
|
|
37
|
+
|
|
38
|
+
if date_match = cleaned_filename.match(/\d\d\.\d\d\.\d{4}/)
|
|
39
|
+
@date = Date.parse(date_match[0].gsub('.', '/')).strftime("%d %b %y")
|
|
40
|
+
|
|
41
|
+
# remove leading zeros
|
|
42
|
+
@date = @date[1..-1] if @date[0..0] == '0'
|
|
43
|
+
|
|
44
|
+
# remove date from filename to prevent matching parts of date as season or episode number
|
|
45
|
+
cleaned_filename = [date_match.pre_match, date_match.post_match].join('[date]')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
SPLITS.each do |char|
|
|
49
|
+
clear_variables
|
|
50
|
+
|
|
51
|
+
pieces = remove_extension(cleaned_filename).split(char)
|
|
52
|
+
|
|
53
|
+
parse_showname(pieces) if pieces.length > 1
|
|
54
|
+
|
|
55
|
+
break if parsed_ok?
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def clear_variables
|
|
60
|
+
@orig_show = @show = @season = @episode_name = @episode_number = @production_code = @date = nil
|
|
61
|
+
@show_pieces = Array.new
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def parsed_ok?
|
|
65
|
+
show && ((@episode_number && @season) || (@rename_by_date && @date))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def parse_showname(pieces)
|
|
69
|
+
date = false
|
|
70
|
+
pieces.each do |piece|
|
|
71
|
+
if match = piece.match(/^[sS]([0-9]{1,2})[eE]([0-9]{1,3})$/)
|
|
72
|
+
@season = match[1]
|
|
73
|
+
@episode_number = match[2]
|
|
74
|
+
if(@season[0].chr == '0') then @season.delete!("0") end
|
|
75
|
+
if(@episode_number[0].chr == '0') then @episode_number.delete!("0") end
|
|
76
|
+
break
|
|
77
|
+
elsif match = piece.match(/^[sS]([0-9]{1,2})$/)
|
|
78
|
+
@season = match[1]
|
|
79
|
+
if(@season[0].chr == '0') then @season.delete!("0") end
|
|
80
|
+
if(@episode_number) then break end
|
|
81
|
+
elsif match = piece.match(/^[eE]([0-9]{1,3})$/)
|
|
82
|
+
@episode_number = match[1]
|
|
83
|
+
if(@episode_number[0].chr == '0') then @episode_number.delete!("0") end
|
|
84
|
+
if(@season) then break end
|
|
85
|
+
elsif match = piece.match(/^([0-9]{1,2})[xX]([0-9]{1,3})$/)
|
|
86
|
+
@season = match[1]
|
|
87
|
+
@episode_number = match[2]
|
|
88
|
+
if(@season[0].chr == '0') then @season.delete!("0") end
|
|
89
|
+
if(@episode_number[0].chr == '0') then @episode_number.delete!("0") end
|
|
90
|
+
break
|
|
91
|
+
elsif
|
|
92
|
+
(
|
|
93
|
+
(match = piece.match(/^[0-9]{3,4}$/)) and
|
|
94
|
+
!(
|
|
95
|
+
show_name_from_tokens.downcase == "the" || # Work around for "The 4400"
|
|
96
|
+
show_name_from_tokens.downcase == "sealab" || # Work around for "Sealab 2021"
|
|
97
|
+
(show_name_from_tokens.downcase == "knight rider" and match.to_s == "2008") || # Work around for "Knight Rider 2008"
|
|
98
|
+
(show_name_from_tokens.downcase == "90210" and match.to_s == "90210") # Work around for 90210
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
piece = match.to_s
|
|
102
|
+
if piece.length == 3
|
|
103
|
+
@season = piece[0].chr
|
|
104
|
+
@episode_number = piece[1..2]
|
|
105
|
+
if(@episode_number[0].chr == '0') then @episode_number.delete!("0") end
|
|
106
|
+
else
|
|
107
|
+
@season = piece[0..1]
|
|
108
|
+
@episode_number = piece[2..3]
|
|
109
|
+
if(@season[0].chr == '0') then @season.delete!("0") end
|
|
110
|
+
if(@episode_number[0].chr == '0') then @episode_number.delete!("0") end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
break
|
|
114
|
+
elsif piece == "[date]"
|
|
115
|
+
date = true
|
|
116
|
+
else
|
|
117
|
+
if !date
|
|
118
|
+
if !@orig_show
|
|
119
|
+
@show_pieces.push camelize(piece)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def camelize(string)
|
|
130
|
+
string[0] = string[0].chr.upcase
|
|
131
|
+
string
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def remove_extension(file)
|
|
135
|
+
file.gsub(".#{@extension}", '')
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def show_name_from_tokens
|
|
139
|
+
@show_pieces.join ' '
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class Renamer
|
|
144
|
+
|
|
145
|
+
def initialize(args)
|
|
146
|
+
@output_dir = '.'
|
|
147
|
+
@rename = true
|
|
148
|
+
i=0
|
|
149
|
+
while i < args.size
|
|
150
|
+
case args[i]
|
|
151
|
+
when "-i"
|
|
152
|
+
@ini_file = args[i+1]
|
|
153
|
+
if @ini_file.nil?
|
|
154
|
+
puts "You must enter the path to the shows.ini file"
|
|
155
|
+
exit
|
|
156
|
+
end
|
|
157
|
+
i += 1
|
|
158
|
+
when "--output-dir", "-d"
|
|
159
|
+
@output_dir = args[i+1]
|
|
160
|
+
if @output_dir.nil?
|
|
161
|
+
puts "You must enter a directory to renamer files to!"
|
|
162
|
+
exit 1
|
|
163
|
+
end
|
|
164
|
+
i += 1
|
|
165
|
+
when "--debug"
|
|
166
|
+
@debug = true
|
|
167
|
+
when "--no-rename", "-n"
|
|
168
|
+
@rename = false
|
|
169
|
+
when "--overwrite", "-o"
|
|
170
|
+
@overwrite = true
|
|
171
|
+
when "--verbose", "-v"
|
|
172
|
+
@verbose = true
|
|
173
|
+
end
|
|
174
|
+
i += 1
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
if(!@ini_file)
|
|
178
|
+
#check if windows or linux
|
|
179
|
+
if RUBY_PLATFORM['linux']
|
|
180
|
+
if ENV['XDG_CONFIG_HOME']
|
|
181
|
+
basedir = ENV['XDG_CONFIG_HOME']
|
|
182
|
+
else
|
|
183
|
+
if ENV['HOME']
|
|
184
|
+
basedir = File.join(ENV['HOME'], '.config')
|
|
185
|
+
else
|
|
186
|
+
puts '$XDG_CONFIG_HOME and $HOME unset, falling back to current directory'
|
|
187
|
+
basedir = '.'
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
else
|
|
191
|
+
basedir = ENV['HOMEDRIVE'] + ENV['HOMEPATH']
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
@ini_file = File.join(basedir, 'shows.ini')
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
begin
|
|
198
|
+
@ini = Ini.new(@ini_file, true)
|
|
199
|
+
rescue
|
|
200
|
+
puts "#{@ini_file} does not exist, no custom renaming available"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def run
|
|
205
|
+
files = Dir['*.{avi,wmv,divx,mpg,mpeg,xvid,mp4,mkv}'].sort
|
|
206
|
+
|
|
207
|
+
files.each do |file|
|
|
208
|
+
video = VideoFile.new(file)
|
|
209
|
+
|
|
210
|
+
video.rename_by_date = true if video.show && attribute('renamebydate', video.orig_show)
|
|
211
|
+
|
|
212
|
+
# parse the show into @show, @season, @episode_number, etc...
|
|
213
|
+
if !video.parsed_ok?
|
|
214
|
+
puts "I could not match #{file} to a naming pattern I can interpret"
|
|
215
|
+
next
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
rename(video)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# delete the cached results from epguides
|
|
222
|
+
Dir['*.renamer'].each do |filename|
|
|
223
|
+
File::delete(filename)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
if @one_rename_failed
|
|
227
|
+
# if some renames succeeded, return 2
|
|
228
|
+
if @one_rename_succeeded
|
|
229
|
+
exit 2
|
|
230
|
+
# if no renames succeeded, return 1
|
|
231
|
+
else
|
|
232
|
+
exit 1
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def set_attributes_from_epguides(video)
|
|
238
|
+
line = epguide_line(video)
|
|
239
|
+
return false if line.nil?
|
|
240
|
+
|
|
241
|
+
info = parse_line(line, video.format)
|
|
242
|
+
return false if info.nil?
|
|
243
|
+
|
|
244
|
+
season_episode = info[1]
|
|
245
|
+
video.production_code = info[2]
|
|
246
|
+
video.date = info[3]
|
|
247
|
+
episode_name = info[4]
|
|
248
|
+
|
|
249
|
+
if line.match("<li>")
|
|
250
|
+
video.episode_name = episode_name
|
|
251
|
+
else
|
|
252
|
+
doc = Nokogiri::HTML("<pre>#{line}</pre>")
|
|
253
|
+
links = doc.css('pre a')
|
|
254
|
+
if links.empty?
|
|
255
|
+
puts "Could not find episode name for #{video}"
|
|
256
|
+
else
|
|
257
|
+
video.episode_name = links[0].content
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
seasonmatch = season_episode.match(/(\d\d?)- ?(\d+)/)
|
|
262
|
+
video.season ||= seasonmatch[1]
|
|
263
|
+
video.episode_number ||= seasonmatch[2]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def rename(video)
|
|
267
|
+
return false unless set_attributes_from_epguides(video)
|
|
268
|
+
|
|
269
|
+
# if the ini exists, we set the showname to the custome name if it exists
|
|
270
|
+
if customname = attribute('customname', video.orig_show)
|
|
271
|
+
video.show = customname
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# pad the episode with 0 if length is less than 0, we need to handle
|
|
275
|
+
# this better for 3+ digit episodes seasons
|
|
276
|
+
video.episode_number = sprintf("%02i", video.episode_number.to_i)
|
|
277
|
+
|
|
278
|
+
return false unless new_filename = generate_filename(video)
|
|
279
|
+
|
|
280
|
+
new_filename = [@output_dir, new_filename].join(File::Separator)
|
|
281
|
+
|
|
282
|
+
if !@rename or @verbose
|
|
283
|
+
puts "rename #{video.filename} to #{new_filename}"
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
if @rename
|
|
287
|
+
# if the file doesn't exist (or we allow overwrites), we rename it
|
|
288
|
+
if !File::exist?(new_filename) or @overwrite
|
|
289
|
+
begin
|
|
290
|
+
FileUtils.mv(video.filename, new_filename)
|
|
291
|
+
@one_rename_succeeded = true
|
|
292
|
+
puts "rename succeeded"
|
|
293
|
+
rescue Exception
|
|
294
|
+
@one_rename_failed = true
|
|
295
|
+
puts "rename failed"
|
|
296
|
+
end
|
|
297
|
+
# otherwise we don't overwrite it and just display a message
|
|
298
|
+
else
|
|
299
|
+
puts "Can't rename #{video.filename} to #{new_filename}!"
|
|
300
|
+
puts "#{video.filename} already exists!"
|
|
301
|
+
@one_rename_failed = true
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def generate_filename(video)
|
|
307
|
+
# if the ini file exists, and they set a custom renaming mask,
|
|
308
|
+
# or a global one exists we need to do more custom renaming
|
|
309
|
+
if mask = attribute('mask', video.orig_show)
|
|
310
|
+
# set the filename to the mask as a base
|
|
311
|
+
|
|
312
|
+
filename = mask.dup
|
|
313
|
+
|
|
314
|
+
# substitute the patterns with the data we found
|
|
315
|
+
filename.gsub!("%show%", video.show)
|
|
316
|
+
filename.gsub!("%episode%", video.episode_name)
|
|
317
|
+
filename.gsub!("%season%", video.season)
|
|
318
|
+
filename.gsub!("%epnumber%", video.episode_number)
|
|
319
|
+
|
|
320
|
+
# if there is a custom date format, use that
|
|
321
|
+
# otherwise date is however epguides displays it
|
|
322
|
+
if !video.date.nil? && date_format = attribute('dateformat', video.orig_show)
|
|
323
|
+
video.date.insert(-3, "20")
|
|
324
|
+
video.date = Date.parse(video.date).strftime(date_format)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# TODO: we need to handle this better if date, code, etc.. don't exists
|
|
328
|
+
# right now they just end up as spaces
|
|
329
|
+
filename.gsub!("%date%", video.date)
|
|
330
|
+
filename.gsub!("%code%", video.production_code)
|
|
331
|
+
# if we don't have a shows.ini, or nothing specific is set, use the default pattern
|
|
332
|
+
else
|
|
333
|
+
filename = [video.show, video.season, video.episode_number, video.episode_name].join(' - ')
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# add on the extension
|
|
337
|
+
filename = "#{filename}.#{video.extension}"
|
|
338
|
+
|
|
339
|
+
# replace html encoded characters
|
|
340
|
+
filename = CGI.unescapeHTML(filename)
|
|
341
|
+
|
|
342
|
+
# replace these illegal win32 characters with '-'
|
|
343
|
+
filename.gsub!(":", "-")
|
|
344
|
+
filename.gsub!("/", "-")
|
|
345
|
+
|
|
346
|
+
# just delete theses illegal win32 characters
|
|
347
|
+
filename.delete!("?\\/<>\"")
|
|
348
|
+
|
|
349
|
+
filename
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def epguide_data(video, url)
|
|
353
|
+
if !File::exist?(url + ".renamer")
|
|
354
|
+
page = Net::HTTP.new('www.epguides.com')
|
|
355
|
+
|
|
356
|
+
response = page.get("/#{url}/")
|
|
357
|
+
|
|
358
|
+
case response
|
|
359
|
+
when Net::HTTPSuccess
|
|
360
|
+
data = response.body
|
|
361
|
+
|
|
362
|
+
File.open(url + ".renamer", "w") do |file|
|
|
363
|
+
file << data
|
|
364
|
+
end
|
|
365
|
+
else
|
|
366
|
+
if !@ini.nil?
|
|
367
|
+
puts "#{@ini_file} does not exist, please create this file and add an alias for #{video.show}"
|
|
368
|
+
return nil
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
unless @ini[video.show.downcase]
|
|
372
|
+
puts "Please add an alias of the epguide.com show url for \"#{video.show}\" to #{@ini_file}"
|
|
373
|
+
return nil
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
if @ini[video.show.downcase]["url"]
|
|
377
|
+
puts "The entry for \"#{video.show}\" has an invalid URL"
|
|
378
|
+
else
|
|
379
|
+
puts "The entry for \"#{video.show}\" needs a URL"
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
data = nil
|
|
383
|
+
end
|
|
384
|
+
else
|
|
385
|
+
File.open(url + ".renamer", "r") do |file|
|
|
386
|
+
data = file.read
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
return data
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def attribute(attribute, show)
|
|
394
|
+
if show
|
|
395
|
+
attr = @ini[show.downcase][attribute] unless @ini.nil? or @ini[show.downcase].nil?
|
|
396
|
+
if attr
|
|
397
|
+
return attr
|
|
398
|
+
else
|
|
399
|
+
return attribute(attribute, nil)
|
|
400
|
+
end
|
|
401
|
+
else
|
|
402
|
+
return @ini[attribute] unless @ini.nil?
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def matchstring(video, tvrage)
|
|
407
|
+
if video.rename_by_date && video.date
|
|
408
|
+
if !tvrage
|
|
409
|
+
video.date
|
|
410
|
+
else
|
|
411
|
+
video.date.gsub(' ', '/')
|
|
412
|
+
end
|
|
413
|
+
else
|
|
414
|
+
if !tvrage
|
|
415
|
+
matchstring = "#{video.season}-#{sprintf('%2s', video.episode_number)}"
|
|
416
|
+
else
|
|
417
|
+
matchstring = "#{video.season}-#{sprintf('%02i', video.episode_number.to_i)}"
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# returns the line of html from epguides.com that contains the information for this episode
|
|
423
|
+
def epguide_line(video)
|
|
424
|
+
url = show_url(video)
|
|
425
|
+
data = epguide_data(video, url)
|
|
426
|
+
|
|
427
|
+
return nil if data.nil?
|
|
428
|
+
|
|
429
|
+
# default to TV.com pages
|
|
430
|
+
pattern = matchstring(video, false)
|
|
431
|
+
|
|
432
|
+
# get each line
|
|
433
|
+
lines = data.split(/\n|\r/)
|
|
434
|
+
|
|
435
|
+
# go through each until we find the show data
|
|
436
|
+
lines.each do |line|
|
|
437
|
+
if line =~ /((_+) )+/
|
|
438
|
+
video.format = line
|
|
439
|
+
elsif line.match(pattern)
|
|
440
|
+
return line
|
|
441
|
+
elsif line.match("this TVRage editor")
|
|
442
|
+
pattern = matchstring(video, true)
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
puts "Epguides does not have #{video.show} season: #{video.season} episode: #{video.episode_number} in its guides."
|
|
447
|
+
nil
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def parse_line(line, format)
|
|
451
|
+
if format.nil?
|
|
452
|
+
if line.match("<li>")
|
|
453
|
+
format = "____ _______ ________ ___________ ______________________________________________"
|
|
454
|
+
else
|
|
455
|
+
format = "_____ ______ ___________ ___________ ___________________________________________"
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
puts "Couldn't find data format string from epguides page, assuming format like:"
|
|
459
|
+
puts format
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# ensure format ends in exactly one space so loop gets all tokens
|
|
463
|
+
format = format.split(' ').join(' ') + ' '
|
|
464
|
+
|
|
465
|
+
tokens = []
|
|
466
|
+
prev_offset = -1
|
|
467
|
+
while offset = format.index(' ', prev_offset + 1) do
|
|
468
|
+
range = (prev_offset+1)..(offset - 1)
|
|
469
|
+
tokens.push line.slice(range).strip
|
|
470
|
+
prev_offset = offset
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
return tokens
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def show_url(video)
|
|
477
|
+
# use the url specified in the ini, if set
|
|
478
|
+
url = attribute('url', video.show)
|
|
479
|
+
|
|
480
|
+
if url.nil?
|
|
481
|
+
url = video.show.split(' ').join
|
|
482
|
+
|
|
483
|
+
url = url[3, url.length] if url[0,3] == "The"
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
url
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
end # class Renamer
|
|
490
|
+
|
|
491
|
+
# Ini class - read and write ini files
|
|
492
|
+
# Copyright (C) 2007 Jeena Paradies
|
|
493
|
+
# License: GPL
|
|
494
|
+
# Author: Jeena Paradies (info@jeenaparadies.net)
|
|
495
|
+
|
|
496
|
+
class Ini
|
|
497
|
+
|
|
498
|
+
# :inihash is a hash which holds all ini data
|
|
499
|
+
# :comment is a string which holds the comments on the top of the file
|
|
500
|
+
attr_accessor :inihash, :comment
|
|
501
|
+
|
|
502
|
+
# Creating a new Ini object
|
|
503
|
+
# +path+ is a path to the ini file
|
|
504
|
+
# +load+ if nil restores the data if possible
|
|
505
|
+
# if true restores the data, if not possible raises an error
|
|
506
|
+
# if false does not resotre the data
|
|
507
|
+
def initialize(path, load=nil)
|
|
508
|
+
@path = path
|
|
509
|
+
@inihash = {}
|
|
510
|
+
|
|
511
|
+
if load or ( load.nil? and FileTest.readable_real? @path )
|
|
512
|
+
restore()
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Retrive the ini data for the key +key+
|
|
517
|
+
def [](key)
|
|
518
|
+
@inihash[key]
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# Set the ini data for the key +key+
|
|
522
|
+
def []=(key, value)
|
|
523
|
+
raise TypeError, "String expected" unless key.is_a? String
|
|
524
|
+
raise TypeError, "String or Hash expected" unless value.is_a? String or value.is_a? Hash
|
|
525
|
+
|
|
526
|
+
@inihash[key] = value
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# Restores the data from file into the object
|
|
530
|
+
def restore()
|
|
531
|
+
@inihash = Ini.read_from_file(@path)
|
|
532
|
+
@comment = Ini.read_comment_from_file(@path)
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# Store data from the object in the file
|
|
536
|
+
def update()
|
|
537
|
+
Ini.write_to_file(@path, @inihash, @comment)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# Reading data from file
|
|
541
|
+
# +path+ is a path to the ini file
|
|
542
|
+
# returns a hash which represents the data from the file
|
|
543
|
+
def Ini.read_from_file(path)
|
|
544
|
+
inihash = {}
|
|
545
|
+
headline = nil
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
IO.foreach(path) do |line|
|
|
549
|
+
|
|
550
|
+
line = line.strip.split(/#/)[0]
|
|
551
|
+
|
|
552
|
+
# if line is nil, just go to the next one
|
|
553
|
+
next if line.nil?
|
|
554
|
+
|
|
555
|
+
# read it only if the line doesn't begin with a "=" and is long enough
|
|
556
|
+
unless line.length < 2 and line[0,1] == "="
|
|
557
|
+
|
|
558
|
+
# it's a headline if the line begins with a "[" and ends with a "]"
|
|
559
|
+
if line[0,1] == "[" and line[line.length - 1, line.length] == "]"
|
|
560
|
+
|
|
561
|
+
# get rid of the [] and unnecessary spaces
|
|
562
|
+
headline = line[1, line.length - 2 ].strip
|
|
563
|
+
inihash[headline] = {}
|
|
564
|
+
else
|
|
565
|
+
|
|
566
|
+
key, value = line.split(/=/, 2)
|
|
567
|
+
|
|
568
|
+
key = key.strip unless key.nil?
|
|
569
|
+
value = value.strip unless value.nil?
|
|
570
|
+
|
|
571
|
+
unless headline.nil?
|
|
572
|
+
inihash[headline][key] = value
|
|
573
|
+
else
|
|
574
|
+
inihash[key] = value unless key.nil?
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
inihash
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
# Reading comments from file
|
|
584
|
+
# +path+ is a path to the ini file
|
|
585
|
+
# Returns a string with comments from the beginning of the
|
|
586
|
+
# ini file.
|
|
587
|
+
def Ini.read_comment_from_file(path)
|
|
588
|
+
comment = ""
|
|
589
|
+
|
|
590
|
+
IO.foreach(path) do |line|
|
|
591
|
+
line.strip!
|
|
592
|
+
next if line.nil?
|
|
593
|
+
|
|
594
|
+
next unless line[0,1] == "#"
|
|
595
|
+
|
|
596
|
+
comment << "#{line[1, line.length ].strip}\n"
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
comment
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# Writing a ini hash into a file
|
|
603
|
+
# +path+ is a path to the ini file
|
|
604
|
+
# +inihash+ is a hash representing the ini File. Default is a empty hash.
|
|
605
|
+
# +comment+ is a string with comments which appear on the
|
|
606
|
+
# top of the file. Each line will get a "#" before.
|
|
607
|
+
# Default is no comment.
|
|
608
|
+
def Ini.write_to_file(path, inihash={}, comment=nil)
|
|
609
|
+
raise TypeError, "String expected" unless comment.is_a? String or comment.nil?
|
|
610
|
+
|
|
611
|
+
raise TypeError, "Hash expected" unless inihash.is_a? Hash
|
|
612
|
+
File.open(path, "w") { |file|
|
|
613
|
+
|
|
614
|
+
unless comment.nil?
|
|
615
|
+
comment.each do |line|
|
|
616
|
+
file << "# #{line}"
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
file << Ini.to_s(inihash)
|
|
621
|
+
}
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
# Turn a hash (up to 2 levels deepness) into a ini string
|
|
625
|
+
# +inihash+ is a hash representing the ini File. Default is a empty hash.
|
|
626
|
+
# Returns a string in the ini file format.
|
|
627
|
+
def Ini.to_s(inihash={})
|
|
628
|
+
str = ""
|
|
629
|
+
|
|
630
|
+
inihash.each do |key, value|
|
|
631
|
+
if value.is_a? Hash
|
|
632
|
+
str << "[#{key.to_s}]\n"
|
|
633
|
+
|
|
634
|
+
value.each do |under_key, under_value|
|
|
635
|
+
str << "#{under_key.to_s}=#{under_value.to_s unless under_value.nil?}\n"
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
else
|
|
639
|
+
str << "#{key.to_s}=#{value.to_s unless value2.nil?}\n"
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
str
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
end # end Ini
|
data/lib/tv_renamer.rb
ADDED