xmltv 0.8.1

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.
@@ -0,0 +1,737 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ require 'rubygems'
4
+ require 'hpricot'
5
+ require 'rexml/document'
6
+ require 'yaml'
7
+ require 'date'
8
+ require 'fileutils'
9
+ require 'timeout'
10
+ require 'open-uri'
11
+ require 'pp'
12
+ require 'iconv'
13
+ require 'set'
14
+ require 'optparse'
15
+ require 'optparse/time'
16
+ require 'ostruct'
17
+
18
+ class Time
19
+ public :to_date
20
+ end
21
+ module MailSender
22
+ def linuxmail(to, subj, body)
23
+ from = `/usr/bin/id --name --user`.chomp
24
+ header = ["To: #{to}", "From: #{from}", "Subject: #{subj}"].join("\n")
25
+ mess = [header,"", body].join("\n")
26
+ puts mess
27
+ IO.popen("/usr/sbin/sendmail -t", 'w') do |h|
28
+ h.puts mess
29
+ end
30
+ end
31
+ def sendmail(*args)
32
+ if test(?x, '/usr/sbin/sendmail')
33
+ linuxmail(*args)
34
+ else
35
+ raise "FIXME: Don't know how to send mail"
36
+ end
37
+ end
38
+ module_function :sendmail, :linuxmail
39
+ end
40
+ class String
41
+ UTF = 'utf-8'
42
+ ISO = 'iso-8859-15'
43
+ TO_UTF = Iconv.new(UTF, ISO)
44
+ CK_UTF = Iconv.new(UTF, UTF)
45
+ def to_utf
46
+ TO_UTF.iconv(self)
47
+ end
48
+ def ck_utf
49
+ CK_UTF.iconv(self)
50
+ end
51
+ end
52
+
53
+ class Date
54
+ Maanden = [nil] + %w{ januari februari maart april mei juni juli augustus september oktober november december }
55
+ Dagen = %w{ zondag maandag dinsdag woensdag donderdag vrijdag zaterdag }
56
+ def self.dutch(instring)
57
+ nu = today
58
+ defjaar, defmaand, defdag, weekdag = nu.year, nu.month, nu.day, nu.wday
59
+ jaar = maand = dag = nil
60
+ string = instring.gsub(/[[:cntrl:][:punct:]]/, '').downcase
61
+ string.split.each do |elem|
62
+ case elem
63
+ when /[[:alpha:]]+/
64
+ if Maanden.include?(elem)
65
+ maand = Maanden.index(elem)
66
+ if maand < nu.month
67
+ defjaar += 1
68
+ end
69
+ elsif Dagen.include?(elem)
70
+ wd = Dagen.index(elem) - weekdag
71
+ wd += 7 if wd < 0
72
+ nu += wd
73
+ defjaar, defmaand, defdag, weekdag = nu.year, nu.month, nu.day, nu.wday
74
+ end
75
+ when /[[:digit:]]+/
76
+ ent = elem.to_i
77
+ if ent <= 31
78
+ dag = ent
79
+ elsif ent > 1900
80
+ jaar = ent
81
+ end
82
+ end
83
+ end
84
+ new(jaar || defjaar, maand || defmaand,dag || defdag)
85
+ end
86
+ end
87
+
88
+
89
+ module XMLTV
90
+ class ValidateError < Exception; end
91
+ class BadChannelError < Exception; end
92
+ class BadSiteError < Exception; end
93
+ class Progdata < Hash
94
+ def self.new
95
+ super {|h,v| h[v] = Hash.new }
96
+ end
97
+ end
98
+ VERSION = '0.8.1'
99
+ Progdir = ($LOAD_PATH.find {|x| test(?f, "#{x}/xmltv/xmltv.rb")} || 'lib') + '/xmltv/sites'
100
+ Sites = Dir["#{Progdir}/*.rb"].map{|x| x[0..-4]}.map{|x| File.basename(x)}
101
+ class XmltvOptparser
102
+
103
+ #
104
+ # Return a structure describing the options.
105
+ #
106
+ def self.parse(args)
107
+ options = OpenStruct.new
108
+ options.basedir = "#{ENV['HOME']}/.xmltv"
109
+ options.validate = true
110
+
111
+ opts = OptionParser.new do |opts|
112
+ opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
113
+
114
+ opts.separator ""
115
+ opts.separator "Specific options:"
116
+
117
+ opts.on("-a", "--list-all",
118
+ "List all available channels") do |av|
119
+ options.available = av
120
+ options.action = true
121
+ end
122
+
123
+ opts.on("--add x,y,z", Array, "Add channels to config") do |list|
124
+ options.add = list
125
+ options.action = true
126
+ end
127
+ opts.on("--delete x,y,z", Array, "Delete channels from config") do |list|
128
+ options.del = list
129
+ options.action = true
130
+ end
131
+ opts.on("-l", "--list", "List configured channels") do |list|
132
+ options.list = list
133
+ options.action = true
134
+ end
135
+ opts.on("-c", "--config-file FILENAME", "Configuration file") do |list|
136
+ options.config = list
137
+ end
138
+ opts.on( "--basedir DIRNAME", "Basedir (#{options.basedir})") do |list|
139
+ options.basedir = list
140
+ end
141
+ opts.on( "--no-validate", "Do not validate") do |v|
142
+ options.validate = false
143
+ end
144
+ opts.on( "--mailto USER", "Mail exception") do |user|
145
+ options.mailto = user
146
+ end
147
+
148
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
149
+ options.verbose = v
150
+ end
151
+
152
+ opts.separator ""
153
+ opts.separator "Common options:"
154
+
155
+ opts.on_tail("-h", "--help", "Show this message") do
156
+ STDERR.puts opts
157
+ exit
158
+ end
159
+
160
+ opts.on_tail("--version", "Show version") do
161
+ options.version = true
162
+ end
163
+ end
164
+
165
+ opts.parse!(args)
166
+ options
167
+ end
168
+ end
169
+ XmltvOptions = XmltvOptparser.parse(ARGV)
170
+
171
+
172
+ class XmlWriter
173
+ With_lang = [ 'title','sub-title','desc','category','language','orig-language','country','premiere','last-chance']
174
+ Programme_dtd = [
175
+ ['title' ],
176
+ [ 'sub-title' ],
177
+ [ 'desc' ],
178
+ [ 'credits', nil, [
179
+ 'director',
180
+ 'actor',
181
+ 'writer',
182
+ 'adapter',
183
+ 'producer',
184
+ 'presenter',
185
+ 'commentator',
186
+ 'guest'
187
+ ]
188
+ ],
189
+ ['date'],
190
+ ['category' ] ,
191
+ ['language' ] ,
192
+ ['orig-language' ] ,
193
+ [ 'length', [ 'units' ] ],
194
+ [ 'icon', [ 'src', 'width', 'height' ] ],
195
+ [ 'url'],
196
+ [ 'country' ] ,
197
+ [ 'episode-num', [ 'system' ] ],
198
+ [ 'video', [], [
199
+ 'present',
200
+ 'colour',
201
+ 'aspect',
202
+ 'quality'
203
+ ]
204
+ ],
205
+ [ 'audio' , [], [
206
+ 'present',
207
+ 'stereo'
208
+ ]
209
+ ],
210
+ ['previously-shown', ['start', 'channel' ]],
211
+ ['premiere' ],
212
+ [ 'last-chance' ],
213
+ [ 'new' ],
214
+ [ 'subtitles', [ 'type' ], [ 'language' ] ],
215
+ [ 'rating', [ 'system' ] , [ 'value', 'icon' ] ],
216
+ [ 'star-rating', [], [ 'value', 'icon' ] ]
217
+ ]
218
+ def initialize(grabber)
219
+ @grabber = grabber
220
+ @doc = doc = REXML::Document.new
221
+ doc << REXML::XMLDecl.new("1.0", "UTF-8")
222
+ dtd = grabber.config['dtd'] || 'file:///usr/share/xmltv/xmltv.dtd'
223
+ doc << REXML::DocType.new('tv', %Q{SYSTEM "#{dtd}"})
224
+ @el_tv = doc.add_element( 'tv' )
225
+ @el_tv.attributes['generator-info-name'] = 'xmltv.rb'
226
+ end
227
+ def add_dtd(progdata)
228
+ raise ArgumentError.new("add_dtd must have a Progdata") unless progdata.is_a?(Progdata)
229
+ p = @el_tv.add_element('programme')
230
+ %w{ start stop }.each do |word|
231
+ p.attributes[word] = progdata[word].strftime( '%Y%m%d%H%M%S %Z')
232
+ end
233
+ p.attributes['channel'] = progdata['channel']
234
+ Programme_dtd.each do |el|
235
+ name , attrs , elements = el
236
+ # STDERR.puts name
237
+ next if (cur = progdata[name]).empty?
238
+ el = p.add_element(name)
239
+ el.text = cur if cur.is_a? String
240
+ if With_lang.include? name
241
+ el.attributes['lang'] = @grabber.lang
242
+ end
243
+ if attrs
244
+ attrs.each do |a|
245
+ el.attributes[a] = cur[a] if cur[a]
246
+ end
247
+ end
248
+ if elements
249
+ elements.each do |e|
250
+ case cur[e]
251
+ when String
252
+ el.add_element(e).text = cur[e]
253
+ when Array
254
+ cur[e].each do |r|
255
+ el.add_element(e).text = r
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
262
+ def write_file(pda, chan_id)
263
+ pda.each do |progdata|
264
+ begin
265
+ add_dtd(progdata)
266
+ rescue StandardError => exc
267
+ STDERR.puts exc, exc.message, exc.backtrace, '===='
268
+ PP.pp(progdata, STDERR)
269
+ raise
270
+ end
271
+ end
272
+ file = ">#{@grabber.outputfile(chan_id)}"
273
+ errorfile = '/var/tmp/xmllint-errors'
274
+ if XmltvOptions.validate
275
+ IO.popen(" ( xmllint --valid - | tv_sort #{file} ) 2>#{errorfile}", 'w') do |h|
276
+ write_xml h
277
+ end
278
+ if test(?s, errorfile)
279
+ raise ValidateError.new("zie #{errorfile}")
280
+ end
281
+ else
282
+ File.open("#{@grabber.outputfile(chan_id)}", 'w') do |h|
283
+ write_xml h
284
+ end
285
+ end
286
+ end
287
+ def write_xml(h, indent = -1)
288
+ @doc.write(h, indent)
289
+ h.puts
290
+ end
291
+
292
+ end
293
+ class Grabber
294
+ Dag = 24 * 60 * 60
295
+ Vandaag = Date.today
296
+ attr_accessor :myname, :chnbasedir, :spooldir, :channel_list, :lang, :generator, :base_url, :config_file_name
297
+ attr_accessor :config, :all_channels, :reject_file_name
298
+ mtv =
299
+ begin
300
+ YAML.load_file("#{XmltvOptions.basedir}/mythtv_chns.yaml")
301
+ rescue Errno::ENOENT
302
+ Hash.new
303
+ end
304
+ MythTV = Hash.new
305
+ mtv.each do |myth, info|
306
+ info[0].each do |file|
307
+ MythTV[file] = myth
308
+ end
309
+ end
310
+
311
+ def grab_channel(chan_id)
312
+ STDERR.puts "Grabber must implement grab_channel(chan_id)"
313
+ exit
314
+ end
315
+ def transform(chan_id)
316
+ STDERR.puts "Grabber must implement transform(chan_id)"
317
+ exit
318
+ end
319
+
320
+ def initialize
321
+ @hits = 0
322
+ # @myname = File.basename($PROGRAM_NAME).gsub(/\..*/, '')
323
+ @myname = self.class.to_s[7..-8].downcase
324
+ @chnbasedir = "#{XmltvOptions.basedir}/#{myname}"
325
+ @config_file_name = XmltvOptions.config || "#{chnbasedir}/config"
326
+ @reject_file_name = "#{chnbasedir}/rejects"
327
+ @config = load_config_file
328
+ @lang = 'nl'
329
+ @generator = "#{myname}.#{lang}"
330
+ @base_url = "http://www.#{generator}"
331
+ @spooldir = config['spooldir'] || "#{chnbasedir}/spool"
332
+ [ chnbasedir, spooldir ].each do |dir|
333
+ FileUtils.mkdir_p(dir) unless test(?d, dir)
334
+ end
335
+ @channel_list = "#{chnbasedir}/channel_list"
336
+ @all_channels = load_channel_file(channel_list)
337
+ end
338
+ def version
339
+ "XMLTV::Grabber version 0.8"
340
+ end
341
+
342
+ def outputfile(chan_id)
343
+ "#{spooldir}/#{channel_name(chan_id)}.xml"
344
+ end
345
+ def channel_name(chan_id)
346
+ "#{chan_id}.#{myname}.#{lang}"
347
+ end
348
+ def channel_display(chan_id)
349
+ all_channels[chan_id]
350
+ end
351
+ def cachefile(chan_id)
352
+ "#{chnbasedir}/#{channel_name(chan_id)}.yaml"
353
+ end
354
+ def load_cachefile(chan_id)
355
+ begin
356
+ YAML.load_file(cachefile(chan_id))
357
+ rescue Errno::ENOENT
358
+ Hash.new
359
+ end
360
+ end
361
+ def check_argv(args)
362
+ case args[0]
363
+ when 'all'
364
+ all_channels.keys
365
+ when 'new'
366
+ XmltvOptions.only_new = true
367
+ all_channels.keys
368
+ else
369
+ args
370
+ end
371
+ end
372
+
373
+ def get_channels
374
+ clean_cache_dir
375
+ if ! ARGV.empty?
376
+ ARGV.replace(check_argv(ARGV))
377
+ else
378
+ config_channels
379
+ end
380
+ end
381
+
382
+ def load_config_file
383
+ begin
384
+ YAML.load_file(config_file_name)
385
+ rescue
386
+ h = Hash.new
387
+ h['channels'] = []
388
+ h
389
+ end
390
+ end
391
+
392
+ def load_channel_file(fn)
393
+ if (fn)
394
+ begin
395
+ YAML.load_file(fn)
396
+ rescue Errno::ENOENT
397
+ fetch_all_channels
398
+ end
399
+ end
400
+ end
401
+ def save_config
402
+ loop do
403
+ if test(?f, config_file_name)
404
+ File.open(config_file_name, 'w') { |h| h.puts config.to_yaml }
405
+ return
406
+ end
407
+ dir = File.dirname(config_file_name)
408
+ if test(?d, dir)
409
+ FileUtils.touch config_file_name
410
+ elsif test(?e, dir)
411
+ raise "#{dir} exists, but isn't a directory"
412
+ else
413
+ FileUtils.mkdir_p dir
414
+ end
415
+ end
416
+ end
417
+
418
+
419
+ def config_channels
420
+ rsl = config['channels']
421
+ if rsl.empty?
422
+ all_channels.keys
423
+ else
424
+ rsl
425
+ end
426
+ end
427
+ def clean_spool_dir
428
+ xmlfiles = config_channels.map{|x| outputfile(x) }
429
+ Dir["#{spooldir}/*.xml"].each do |fn|
430
+ unless xmlfiles.include? fn
431
+ STDERR.puts("removed #{fn} -- not configured")
432
+ File.unlink fn
433
+ end
434
+ end
435
+ end
436
+
437
+
438
+ def clean_cache_dir
439
+ clean_spool_dir
440
+ last_time = config['cleaned']
441
+ if last_time.is_a?(Date) && last_time >= Vandaag
442
+ return
443
+ end
444
+ count = 0
445
+ channels = config_channels.map{|x| cachefile(x) }
446
+ Dir["#{chnbasedir}/*.yaml"].each do |fn|
447
+ unless channels.include? fn
448
+ STDERR.puts("removed #{fn} -- not configured")
449
+ File.unlink fn
450
+ next
451
+ end
452
+ cache = YAML.load_file(fn)
453
+ dels = clean_cache(cache)
454
+ File.open(fn, 'w') {|h| h.puts cache.to_yaml} if dels > 0
455
+ count += dels
456
+ config['cleaned'] = Vandaag
457
+ end
458
+ STDERR.puts("#{myname}: Removed #{count} entries") if count > 0
459
+
460
+ end
461
+
462
+
463
+
464
+
465
+ def proghash(entry, chan_id)
466
+ progdata = Progdata.new
467
+ entry.keys.each do |k|
468
+ dtd = XmlWriter::Programme_dtd.assoc(k)
469
+ if dtd && dtd.size == 1
470
+ progdata[k] = entry[k]
471
+ end
472
+ end
473
+ cnm = channel_name(chan_id)
474
+ progdata['channel'] = MythTV[cnm] || cnm
475
+ progdata
476
+ end
477
+
478
+ def reject(*args)
479
+ File.open(reject_file_name, 'a') do |h|
480
+ h.puts Time.now
481
+ args.each do |arg|
482
+ case arg
483
+ when String, Time, Integer
484
+ h.puts arg
485
+ else
486
+ PP.pp(arg, h)
487
+ end
488
+ end
489
+ h.puts '===='
490
+ end
491
+ end
492
+
493
+
494
+
495
+
496
+ def fix_times(pda)
497
+ errors = 0
498
+ startwith = pda.size
499
+ pda.delete_if do |x|
500
+ rsl = x.has_key?('stop') && x['stop'] <= x['start']
501
+ reject('invalid', x) if rsl
502
+ rsl
503
+ end
504
+ starttimes = pda.sort do |x, y|
505
+ if (a = x['start']) != (b = y['start'])
506
+ a <=> b
507
+ else
508
+ a = x.has_key?('stop') ? x['stop'] : x['start']
509
+ b = y.has_key?('stop') ? y['stop'] : y['start']
510
+ b <=> a
511
+ end
512
+ end
513
+ starttimes.each_with_index do |entry, idx|
514
+ # dump(entry, '++++++++++++++') if entry.has_key?('title') && entry['title'] == 'NOS Studio Voetbal'
515
+ nxt = starttimes[idx + 1]
516
+ next unless nxt
517
+ unless entry.has_key?('stop') && entry['stop'] <= nxt['start']
518
+ errors += 1
519
+ entry['stop'] = nxt['start'].dup
520
+ end
521
+ end
522
+ unless starttimes[-1].has_key?('stop')
523
+ starttimes[-1]['start'] = nil
524
+ end
525
+ pda.delete_if do |x|
526
+ rsl = x['start'].nil? || x['stop'] <= x['start']
527
+ reject('removed', x) if rsl
528
+ rsl
529
+ end
530
+ @rejects = startwith - pda.size
531
+ errors
532
+ end
533
+ def fetch(url)
534
+ @hits += 1
535
+ tries = 0
536
+ begin
537
+ open(url) { |h| Hpricot(h) }
538
+ rescue Errno::ECONNREFUSED, ::Timeout::Error
539
+ tries += 1
540
+ sleep 2
541
+ retry if tries <= 3
542
+ raise BadSiteError.new
543
+ end
544
+ end
545
+ def save_object(obj, filename)
546
+ File.open(filename, 'w') do |h|
547
+ h.puts obj.to_yaml
548
+ end
549
+ end
550
+ def save(*args)
551
+ file = "/tmp/xmltv_exception.#{$$}"
552
+ args.each_with_index do |el, i|
553
+ File.open("#{file}-#{i}", 'w') do |h|
554
+ h.puts el
555
+ end
556
+ end
557
+ STDERR.puts "See #{file}-*"
558
+ end
559
+
560
+ def dump(*args)
561
+ args.each do |arg|
562
+ PP.pp(arg,STDERR)
563
+ end
564
+ end
565
+ def date_stats(chan, time)
566
+ @days_av[chan].add(time.to_date)
567
+ end
568
+ def check_channel(chan_id)
569
+ all_channels.has_key?(chan_id)
570
+ end
571
+
572
+ def add_channels_to_config
573
+ XmltvOptions.add.each do |item|
574
+ if (f = all_channels.has_key?(item)) && ! config['channels'].include?(item)
575
+ config['channels'] << item
576
+ STDERR.puts("added #{item} #{channel_display(item)}") if XmltvOptions.verbose
577
+ else
578
+ reason = f ? "already included" : "not in channel list"
579
+ STDERR.puts "#{item} #{reason}"
580
+ end
581
+ end
582
+ save_config
583
+ end
584
+
585
+ def delete_channels_from_config
586
+ XmltvOptions.del.each do |item|
587
+ if config['channels'].include?(item)
588
+ config['channels'].delete(item)
589
+ STDERR.puts("deleted #{item} #{channel_display(item)}") if XmltvOptions.verbose
590
+ else
591
+ STDERR.puts "#{item} not in config list"
592
+ end
593
+ end
594
+ save_config
595
+ end
596
+ def printline(it)
597
+ printf("%-5s %s\n", it, channel_display(it))
598
+ end
599
+ def do_list(array)
600
+ array.sort_by{|x| channel_display(x)}.each do |item|
601
+ printline item
602
+ end
603
+ end
604
+ def list_config
605
+ if config['channels'].empty?
606
+ explan = channel_list ? 'yet' : 'needed'
607
+ puts "No configuration #{explan} for #{myname}"
608
+ else
609
+ puts "Configured channels for #{myname}"
610
+ do_list(config['channels'])
611
+ end
612
+ end
613
+ def list_all
614
+ puts "Available channels for #{myname}"
615
+ do_list(all_channels.keys)
616
+ end
617
+ def do_options
618
+ if XmltvOptions.list
619
+ list_config
620
+ elsif XmltvOptions.available
621
+ list_all
622
+ else
623
+ if XmltvOptions.add
624
+ add_channels_to_config
625
+ end
626
+ if XmltvOptions.del
627
+ delete_channels_from_config
628
+ end
629
+ list_config
630
+ end
631
+ exit
632
+ end
633
+
634
+ def report(channel, nprogs, errors)
635
+ STDERR.printf(" %s %-6.6s %-20.20s %5d %5d %5d %5d %8d %5d\n",
636
+ Time.now.strftime("%H:%M:%S"),
637
+ channel,
638
+ channel_display(channel),
639
+ nprogs,
640
+ @hits,
641
+ @days_av[channel].size,
642
+ errors,
643
+ test(?s, outputfile(channel)),
644
+ @rejects)
645
+
646
+ @hits = 0
647
+ end
648
+
649
+ def run
650
+ if XmltvOptions.version
651
+ puts version
652
+ exit
653
+ end
654
+ do_options if XmltvOptions.action
655
+ if channel_list && ! test(?f, config_file_name) # We don't want to grab _all_ channels, surely ?
656
+ list_all
657
+ puts "\n Do #{$PROGRAM_NAME} --add chn1,chn2,chn3... to create a configuration file"
658
+ exit
659
+ end
660
+ ARGV.delete_if do |arg|
661
+ begin
662
+ send(arg)
663
+ true
664
+ rescue NoMethodError
665
+ false
666
+ end
667
+ end
668
+ trap('INT') do
669
+ STDERR.puts "\nInterrupted\n"
670
+ raise ArgumentError.new('interrupt')
671
+ # exit!
672
+ end
673
+
674
+ begin
675
+ channels = get_channels
676
+ STDERR.printf("\n%-40s prgs hts days errs bytes rej\n", "#{Date.today}: #{myname}")
677
+ channels.each do |channel|
678
+ next if XmltvOptions.only_new && test(?f, outputfile(channel))
679
+ @days_av = Hash.new { |h,k| h[k] = Set.new }
680
+ @rejects = 0
681
+ writer = XmlWriter.new(self)
682
+ unless check_channel(channel)
683
+ STDERR.puts "No such channel #{channel}"
684
+ next
685
+ end
686
+ begin
687
+ nprogs = grab_channel(channel)
688
+ pda = transform(channel)
689
+ errors = fix_times(pda)
690
+ rescue BadChannelError
691
+ STDERR.puts "Zie #{reject_file_name}"
692
+ next
693
+ end
694
+ writer.write_file(pda, channel)
695
+ report(channel, nprogs, errors)
696
+ end
697
+ rescue SystemExit
698
+
699
+ rescue ::Exception => exc
700
+ STDERR.puts exc.class, exc.message, exc.backtrace
701
+ if XmltvOptions.mailto
702
+ MailSender.sendmail(XmltvOptions.mailto, "Xmltv exception", [exc.class, exc.message, exc.backtrace].join("\n"))
703
+ end
704
+ raise
705
+ ensure
706
+ end
707
+ save_config
708
+ end
709
+
710
+ end
711
+ def go
712
+ if XmltvOptions.add || XmltvOptions.del || XmltvOptions.config
713
+ STDERR.puts "You must specify a site to configure channels"
714
+ exit
715
+ end
716
+ configs = Dir["#{XmltvOptions.basedir}/*"].select{|x| File::Stat.new(x).directory? }.map{|x| File.basename(x)}
717
+ if configs.empty?
718
+ STDERR.puts "No configs found in #{XmltvOptions.basedir}"
719
+ end
720
+ todo = configs & Sites
721
+ exit if todo.empty?
722
+ todo.sort.each do |grabber|
723
+ # puts grabber
724
+ begin
725
+ require "xmltv/sites/#{grabber}"
726
+ rescue SystemExit => exc
727
+ raise unless exc.success?
728
+ end
729
+ end
730
+ end
731
+
732
+ module_function :go
733
+ end
734
+ if __FILE__ == $PROGRAM_NAME
735
+ XMLTV::go
736
+ end
737
+