~m-buck/+junk/gtk-desktop-info

« back to all changes in this revision

Viewing changes to plugin_forecast.py

  • Committer: Mark Buck (Kaivalagi)
  • Date: 2009-06-19 17:13:00 UTC
  • Revision ID: m_buck@hotmail.com-20090619171300-5cbhr90xwg62z27y
Added --backgroundblend and --backgroundcolour options for visual seperation of output from wallpaper if required, Fixed song length output in the rhythmbox plugin when songs are an hour long or more, Added copy option to right click, enabling the copying of html content to the clipboard for testing, Moved common functions into a plugin_common module

Show diffs side-by-side

added added

removed removed

Lines of Context:
7
7
#  Author: Kaivalagi
8
8
# Created: 23/11/2008
9
9
from datetime import datetime, timedelta
10
 
from htmlentitydefs import name2codepoint, codepoint2name
11
10
from optparse import OptionParser
 
11
from plugin_common import getHTMLText, getSpaces, getTypedValue, getHoursMinutesStringFromSeconds, isNumeric
12
12
from xml.dom import minidom
13
 
import sys
14
 
import os
15
 
import time
16
 
import socket
17
 
import urllib2
 
13
import codecs
 
14
import fileinput
18
15
import gettext
19
16
import locale
 
17
import logging
 
18
import os
20
19
import re
21
 
import codecs
22
20
import shutil
 
21
import socket
 
22
import sys
 
23
import time
23
24
import traceback
24
 
import logging
25
 
import fileinput
 
25
import urllib2
26
26
import uuid
27
27
 
28
28
try:
138
138
        "20": u"0", #Fog
139
139
        "21": u"9", #Haze
140
140
        "22": u"4", #Smoke
141
 
        "23": u"6", #Blustery 
 
141
        "23": u"6", #Blustery
142
142
        "24": u"6", #Windy
143
143
        "25": u"-", #N/A
144
144
        "26": u"f", #Cloudy
166
166
        "na": u"-", #N/A
167
167
        "-": u"-" #N/A
168
168
    }
169
 
    
 
169
 
170
170
    conditions_moon_icon = {
171
171
        "0": u"24",
172
172
        "1": u"01",
227
227
 
228
228
    # New translatable strings
229
229
    # foppeh >> These arrays are here so they get translated even if they
230
 
    #           are not used in the code. 
231
 
    
 
230
    #           are not used in the code.
 
231
 
232
232
    # first all about moon phases
233
 
    moon_phase = {    
 
233
    moon_phase = {
234
234
    "New": _(u"New"),
235
235
    "First Quarter": _(u"First Quarter"),
236
236
    "Full": _(u"Full"),
240
240
    "Waxing Crescent": _(u"Waxing Crescent"),
241
241
    "Waxing Gibbous": _(u"Waxing Gibbous")
242
242
    }
243
 
    
 
243
 
244
244
    # foppeh >> Something is going on with these string. I don't know if they are valid.
245
245
    #           The weird thing is they are in lower case and the XML seems to spit
246
246
    #           text with too many Uppercase. So I don't know if strings presented here
247
247
    #           will ever be a valid output from weather.com. Yet I stored them here for
248
248
    #           future reference (and because of the French translation).
249
 
    
 
249
 
250
250
    # all about weather conditions
251
251
    # 'blowing dust'                 => 'tempᅢᆰte de sable',
252
252
    # 'blowing snow'                 => 'tempᅢᆰte de neige',
314
314
    # 'widespread dust'              => 'vents de poussiᅢᄄre',
315
315
    # 'wintry mix'                   => 'conditions hivernales variables',
316
316
 
317
 
    
 
317
 
318
318
    # some general things...
319
319
    general = {
320
320
     "n/a": _(u"n/a"),
325
325
    'day': _(u"day"),
326
326
    'night': _(u"night")
327
327
    }
328
 
    
 
328
 
329
329
    # UV index ...
330
330
    UV_index = {
331
331
    "Extreme": _(u"Extreme"),
334
334
    "Moderate": (u"Moderate"),
335
335
    "Low": _(u"Low")
336
336
    }
337
 
    
 
337
 
338
338
    # tendencies used for barometric pressure
339
339
    bar_pressure = {
340
340
    "Very Low": _(u"Very Low"),
366
366
    "West Northwest": _(u"West Northwest"),
367
367
    "West Southwest": _(u"West Southwest")
368
368
    }
369
 
    
 
369
 
370
370
    wind_directions_short = {
371
371
    "E": _(u"E"),
372
372
    "ENE": _(u"ENE"),
433
433
        self.sunrise = sunrise
434
434
        self.sunset = sunset
435
435
        self.moon_phase = moon_phase
436
 
        self.moon_icon = moon_icon        
 
436
        self.moon_icon = moon_icon
437
437
        self.bar_read = bar_read
438
438
        self.bar_desc = bar_desc
439
439
        self.uv_index = uv_index
447
447
 
448
448
class ForecastLocation:
449
449
    timestamp = None
450
 
    
 
450
 
451
451
    def __init__(self, current, day, night, timestamp):
452
452
        self.current = current
453
453
        self.day = day
454
454
        self.night = night
455
455
        self.timestamp = timestamp #datetime.today()
456
 
        
 
456
 
457
457
    def outdated(self, mins):
458
458
        if datetime.today() > self.timestamp + timedelta(minutes=mins):
459
459
            return True
461
461
            return False
462
462
 
463
463
class Output:
464
 
    
 
464
 
465
465
    # design time variables
466
466
    options = None
467
467
    config = None
472
472
    loaded_locations = []
473
473
    error = ""
474
474
    errorfound = False
475
 
    
 
475
 
476
476
    # design time settings
477
477
    #CONFIG_FILENAME = ".plugin_forecast.config"
478
478
    CACHE_FILENAME_TEMPLATE = ".plugin_forecast-LOCATION.cache"
479
 
    
 
479
 
480
480
    template_options = """
481
481
        --location : location code for weather data [default: %default],Use the following url to determine your location code by city name: http://xoap.weather.com/search/search?where=Norwich")
482
482
        --datatype : The data type options are: DW (Day of Week), WF (Weather Font output), LT (Forecast:Low Temp,Current:Feels Like Temp), HT (Forecast:High Temp,Current:Current Temp), CC (Current Conditions), CT (Conditions Text), PC (Precipitation Chance), HM (Humidity), VI (Visibility), WD (Wind Direction), WA (Wind Angle - in degrees), WS (Wind Speed), WG (Wind Gusts), BF (Bearing Font), BS (Bearing font with Speed), CN (City Name), CO (Country), OB (Observatory), SR (SunRise), SS (SunSet), DL (DayLight), MP (Moon Phase), MF (Moon Font), BR (Barometer Reading), BD (Barometer Description), UI (UV Index), UT (UV Text), DP (Dew Point), LU (Last Update at weather.com), LF (Last Fetch from weather.com), WM (weather map). Not applicable at command line when using templates.")
493
493
        --hidedegreesymbol : Hide the degree symbol used with temperature output, this is only valid if used in conjunction with --hideunits.")
494
494
        --minuteshide : Works only with LU and LF. If present, hides the date part of the LU or LF timestamp if the day of the timestamp is today. The time part is also hidden, if the timestamp is older than minutes specified in this argument. If set to 0, the time part is always shown. If set to -1, the value EXPIRY_MINUTES from the config file is used.")
495
495
        """
496
 
        
 
496
 
497
497
    def __init__(self, options):
498
498
        self.options = options
499
499
        self.logger = logging.getLogger(app_name+"."+module_name)
500
500
        self.loadConfigData()
501
501
        self.loadTranslation()
502
 
        
 
502
 
503
503
        # setup timeout for connections
504
504
        # TODO: seems like this doesn't work in all cases..
505
505
        socket.setdefaulttimeout(self.config.CONNECTION_TIMEOUT)
506
 
        
507
 
        
508
 
        
 
506
 
 
507
 
 
508
 
509
509
    def loadTranslation(self):
510
 
        try:    
 
510
        try:
511
511
            # set the locale
512
512
            if self.config.LOCALE == None:
513
513
                self.config.LOCALE = locale.getdefaultlocale()[0][0:2]
514
 
    
 
514
 
515
515
            self.logger.info("Locale set to " + self.config.LOCALE)
516
 
            
517
 
            # if not the default "en" locale, configure the i18n language translation    
 
516
 
 
517
            # if not the default "en" locale, configure the i18n language translation
518
518
            if self.config.LOCALE != "en":
519
 
    
 
519
 
520
520
                self.logger.info("Looking for translation file for '%s' under %s" % (self.config.LOCALE, localedirectory))
521
 
                
 
521
 
522
522
                if gettext.find(domain, localedirectory, languages=[self.config.LOCALE]) != None:
523
523
                    self.logger.info("Translation file found for '%s'" % self.config.LOCALE)
524
 
                    
 
524
 
525
525
                    try:
526
526
                        trans = gettext.translation(domain, localedirectory, languages=[self.config.LOCALE])
527
527
                        trans.install(unicode=True)
528
528
                        self.logger.info("Translation installed for '%s'" % self.config.LOCALE)
529
 
                        
 
529
 
530
530
                    except Exception, e:
531
531
                        self.logger.error("Unable to load translation for '%s' %s" % (self.config.LOCALE, e.__str__()))
532
532
                else:
533
533
                    self.logger.info("Translation file not found for '%s', defaulting to 'en'" % self.config.LOCALE)
534
534
                    self.config.LOCALE = "en"
535
 
                    
 
535
 
536
536
        except Exception, e:
537
537
            self.logger.error(e.__str__()+"\n"+traceback.format_exc())
538
 
            
 
538
 
539
539
    def loadConfigData(self):
540
540
        try:
541
 
            
 
541
 
542
542
            self.config = ForecastConfig()
543
 
            
 
543
 
544
544
            if self.options.config != None:
545
545
                # load the config based on options passed in from the main app
546
546
                configfilepath = self.options.config
547
547
            else:
548
548
                # load plugin config from home directory of the user
549
549
                configfilepath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/"+module_name+".config")
550
 
                            
 
550
 
551
551
            if os.path.exists(configfilepath):
552
 
                
 
552
 
553
553
                self.logger.info("Loading config settings from \"%s\""%configfilepath)
554
 
                
 
554
 
555
555
                for line in fileinput.input(os.path.expanduser(configfilepath)):
556
556
                    line = line.strip()
557
557
                    if len(line) > 0 and line[0:1] != "#": # ignore commented lines or empty ones
558
 
    
 
558
 
559
559
                        name = line.split("=")[0].strip().upper() # config setting name on the left of =
560
560
                        value = line.split("=")[1].split("#")[0].strip() # config value on the right of = (minus any trailing comments)
561
 
                        
 
561
 
562
562
                        if len(value) > 0:
563
563
                            if name == "HEADERTEMPLATE":
564
 
                                self.config.HEADERTEMPLATE = self.getTypedValue(value, "string")
 
564
                                self.config.HEADERTEMPLATE = getTypedValue(value, "string")
565
565
                            elif name == "TEMPLATE":
566
 
                                self.config.TEMPLATE = self.getTypedValue(value, "string")
 
566
                                self.config.TEMPLATE = getTypedValue(value, "string")
567
567
                            elif name == "LOCATION":
568
 
                                self.config.LOCATION = self.getTypedValue(value, "string")                            
 
568
                                self.config.LOCATION = getTypedValue(value, "string")
569
569
                            elif name == "CACHE_FOLDERPATH":
570
 
                                self.config.CACHE_FOLDERPATH = self.getTypedValue(value, "string")
 
570
                                self.config.CACHE_FOLDERPATH = getTypedValue(value, "string")
571
571
                            elif name == "CONNECTION_TIMEOUT":
572
 
                                self.config.CONNECTION_TIMEOUT = self.getTypedValue(value, "integer")
 
572
                                self.config.CONNECTION_TIMEOUT = getTypedValue(value, "integer")
573
573
                            elif name == "EXPIRY_MINUTES":
574
 
                                self.config.EXPIRY_MINUTES = self.getTypedValue(value, "integer")
 
574
                                self.config.EXPIRY_MINUTES = getTypedValue(value, "integer")
575
575
                            elif name == "TIMEFORMAT":
576
 
                                self.config.TIMEFORMAT = self.getTypedValue(value, "string")
 
576
                                self.config.TIMEFORMAT = getTypedValue(value, "string")
577
577
                            elif name == "DATEFORMAT":
578
 
                                self.config.DATEFORMAT = self.getTypedValue(value, "string")                                    
 
578
                                self.config.DATEFORMAT = getTypedValue(value, "string")
579
579
                            elif name == "LOCALE":
580
 
                                self.config.LOCALE = self.getTypedValue(value, "string")
 
580
                                self.config.LOCALE = getTypedValue(value, "string")
581
581
                            elif name == "XOAP_PARTNER_ID":
582
 
                                self.config.XOAP_PARTNER_ID = self.getTypedValue(value, "integer")
 
582
                                self.config.XOAP_PARTNER_ID = getTypedValue(value, "integer")
583
583
                            elif name == "XOAP_LICENCE_KEY":
584
 
                                self.config.XOAP_LICENCE_KEY = self.getTypedValue(value, "string")
 
584
                                self.config.XOAP_LICENCE_KEY = getTypedValue(value, "string")
585
585
                            elif name == "MAXIMUM_DAYS_FORECAST":
586
 
                                self.config.MAXIMUM_DAYS_FORECAST = self.getTypedValue(value, "integer")                                
 
586
                                self.config.MAXIMUM_DAYS_FORECAST = getTypedValue(value, "integer")
587
587
                            elif name == "IMAGESIZE":
588
 
                                self.config.IMAGESIZE = self.getTypedValue(value, "integer")
 
588
                                self.config.IMAGESIZE = getTypedValue(value, "integer")
589
589
                            elif name == "REFETCH":
590
 
                                self.config.REFETCH = self.getTypedValue(value, "boolean")                                
 
590
                                self.config.REFETCH = getTypedValue(value, "boolean")
591
591
                            else:
592
592
                                self.logger.error("Unknown option in config file: " + name)
593
593
            else:
594
594
                self.logger.info("Config data file %s not found, using defaults and setting up config file for next time" % configfilepath)
595
 
                
 
595
 
596
596
                userconfigpath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/")
597
597
                configsource = os.path.join(app_path, "config/"+module_name+".config")
598
 
                
 
598
 
599
599
                if os.path.exists(userconfigpath) == False:
600
600
                    os.makedirs(userconfigpath)
601
601
 
602
602
                shutil.copy(configsource, configfilepath)
603
 
                
 
603
 
604
604
        except Exception, e:
605
605
            self.logger.error(e.__str__()+"\n"+traceback.format_exc())
606
606
 
607
 
    def getTypedValue(self, value, expectedtype):
608
 
        
609
 
        try:
610
 
            if len(value.strip(" ")) == 0:
611
 
                return None
612
 
            
613
 
            elif value.lower() == "true":
614
 
                if expectedtype == "boolean":
615
 
                    return True
616
 
                else:
617
 
                    self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
618
 
                    
619
 
            elif value.lower() == "false":
620
 
                if expectedtype == "boolean":
621
 
                    return False
622
 
                else:
623
 
                    self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
624
 
                    
625
 
            elif self.isNumeric(value) == True:
626
 
                if expectedtype == "integer":
627
 
                    return int(value)
628
 
                else:
629
 
                    self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
630
 
                    
631
 
            else:
632
 
                return value
633
 
 
634
 
        except (TypeError, ValueError):
635
 
            self.logger.error("Cannot convert '%s' to expected type of '%s'"%(value,expectedtype))
636
 
            return value
637
 
 
638
607
    def checkAndLoad(self, location):
639
608
 
640
609
        # define CACHE_FILEPATH based on cache folder and location code
641
610
        cachefilepath = os.path.join(self.config.CACHE_FOLDERPATH, self.CACHE_FILENAME_TEMPLATE.replace("LOCATION", location))
642
 
            
 
611
 
643
612
        # if the location was not loaded before (or attempted to load)
644
613
        if not location in self.loaded_locations:
645
614
            # add it to the list so it doesn't get loaded again (or attempted to load)
651
620
                        self.logger.info("Loading cache file " + cachefilepath)
652
621
                        file = open(cachefilepath, 'rb')
653
622
                    except Exception, e:
654
 
                        self.logger.error("Unable to read the cache file %s: %s" % (cachefilepath, e.__str__()+"\n"+traceback.format_exc()))                        
 
623
                        self.logger.error("Unable to read the cache file %s: %s" % (cachefilepath, e.__str__()+"\n"+traceback.format_exc()))
655
624
                    else:
656
625
                        self.forecast_data[location] = pickle.load(file)
657
626
                    finally:
673
642
        if self.forecast_data.has_key(location):
674
643
            return True
675
644
        else:
676
 
            self.logger.error("Location %s is not in cache." % self.config.LOCATION) 
 
645
            self.logger.error("Location %s is not in cache." % self.config.LOCATION)
677
646
            return False
678
647
 
679
648
    def checkAndUpdate(self, location):
686
655
            try:
687
656
                #url = "http://xoap.weather.com/weather/local/" + location + "?cc=*&dayf=10&link=xoap&prod=xoap&par=" + str(self.config.XOAP_PARTNER_ID) + "&key=" + self.config.XOAP_LICENCE_KEY + "&unit=m"
688
657
                url = "http://xoap.weather.com/weather/local/" + location + "?cc=*&dayf=10&par=" + str(self.config.XOAP_PARTNER_ID) + "&key=" + self.config.XOAP_LICENCE_KEY + "&unit=m"
689
 
        
 
658
 
690
659
                self.logger.info("Fetching weather data from " + url)
691
660
 
692
661
                usock = urllib2.urlopen(url)
693
662
                xml = usock.read()
694
663
                usock.close()
695
 
                
 
664
 
696
665
            except Exception, e:
697
666
                self.logger.error("Server connection error: " + e.__str__()+"\n"+traceback.format_exc())
698
 
            
 
667
 
699
668
            else:
700
669
                # interrogate weather data
701
670
                try:
702
671
                    # parse the XML
703
672
                    self.weatherxmldoc = minidom.parseString(xml)
704
 
                                    
 
673
 
705
674
                    weather_n = self.weatherxmldoc.documentElement
706
 
                    
 
675
 
707
676
                    # check for an error and raise an exception
708
677
                    if len(weather_n.getElementsByTagName('err')) > 0:
709
678
                        raise Exception, self.getText(weather_n, 'err')
710
 
                    
 
679
 
711
680
                    #head_n = self.getChild(weather_n, 'head')
712
681
                    #visibility_unit = self.getText(head_n, 'ud')
713
 
                    
 
682
 
714
683
                    location_n = self.getChild(weather_n, 'loc')
715
684
                    city, country = self.getText(location_n, 'dnam').split(',')
716
685
                    country = country.strip()
717
 
                    
 
686
 
718
687
                    # current conditions data
719
688
                    day_of_week = _(u"Today")
720
689
                    precip = _(u"N/A")
730
699
                    current_code = self.getText(current_condition_n, 'icon')
731
700
                    current_temp = self.getText(current_condition_n, 'tmp')
732
701
                    current_temp_feels = self.getText(current_condition_n, 'flik')
733
 
                    
 
702
 
734
703
                    bar_n = self.getChild(current_condition_n, 'bar')
735
704
                    bar_read = self.getText(bar_n, 'r')
736
705
                    bar_desc = self.getText(bar_n, 'd')
737
 
                    
 
706
 
738
707
                    wind_n = self.getChild(current_condition_n, 'wind')
739
708
                    wind_speed = self.getText(wind_n, 's')
740
709
                    wind_gusts = self.getText(wind_n, 'gust')
741
710
                    wind_direction_numeric = self.getText(wind_n, 'd')
742
711
                    wind_direction = self.getText(wind_n, 't')
743
 
                    
 
712
 
744
713
                    humidity = self.getText(current_condition_n, 'hmid')
745
714
                    visibility = self.getText(current_condition_n, 'vis')
746
 
                    #if self.isNumeric(visibility):
 
715
                    #if isNumeric(visibility):
747
716
                    #    visibility = visibility + visibility_unit
748
 
                    
 
717
 
749
718
                    uv_n = self.getChild(current_condition_n, 'uv')
750
719
                    uv_index = self.getText(uv_n, 'i')
751
720
                    uv_text = self.getText(uv_n, 't')
752
 
                    
 
721
 
753
722
                    dew_point = self.getText(current_condition_n, 'dewp')
754
 
                    
 
723
 
755
724
                    moon_n = self.getChild(current_condition_n, 'moon')
756
725
                    moon_icon = self.getText(moon_n, 'icon')
757
726
                    moon_phase = self.getText(moon_n, 't')
758
 
                    
 
727
 
759
728
                    weathermap = self.getImageSrcForWeatherMap(location)
760
 
                    
 
729
 
761
730
                    current_forecast_data = ForecastDataset(last_update, day_of_week, current_temp_feels, current_temp, current_code, current_desc, precip, humidity, wind_direction, wind_direction_numeric, wind_speed, wind_gusts, sunrise, sunset, moon_phase, moon_icon, bar_read, bar_desc, uv_index, uv_text, dew_point, observatory, visibility, city, country, weathermap)
762
 
    
 
731
 
763
732
                    # collect forecast data
764
733
                    observatory = _(u"N/A")
765
734
                    bar_read = _(u"N/A")
770
739
                    dew_point = _(u"N/A")
771
740
                    moon_phase = _(u"N/A")
772
741
                    moon_icon = _(u"N/A")
773
 
                    
 
742
 
774
743
                    forecast_n = self.getChild(weather_n, 'dayf')
775
744
                    last_update = self.getText(forecast_n, 'lsup')
776
 
                    
 
745
 
777
746
                    day_nodes = forecast_n.getElementsByTagName('day')
778
 
                    
 
747
 
779
748
                    day_forecast_data_list = []
780
749
                    night_forecast_data_list = []
781
 
        
 
750
 
782
751
                    for day in day_nodes:
783
752
                        day_of_week = day.getAttribute('t')
784
753
 
786
755
                        low_temp = self.getText(day, 'low')
787
756
                        sunrise = self.getText(day, 'sunr')
788
757
                        sunset = self.getText(day, 'suns')
789
 
    
 
758
 
790
759
                        # day forecast specific data
791
760
                        daytime_n = self.getChild(day, 'part')
792
761
                        condition_code = self.getText(daytime_n, 'icon')
797
766
                        wind_gusts = self.getText(wind_n, 'gust')
798
767
                        wind_direction_numeric = self.getText(wind_n, 'd')
799
768
                        wind_direction = self.getText(wind_n, 't')
800
 
                        
 
769
 
801
770
                        precip = self.getText(daytime_n, 'ppcp')
802
771
                        humidity = self.getText(daytime_n, 'hmid')
803
 
                        
 
772
 
804
773
                        day_forecast_data = ForecastDataset(last_update, day_of_week, low_temp, high_temp, condition_code, condition, precip, humidity, wind_direction, wind_direction_numeric, wind_speed, wind_gusts, sunrise, sunset, moon_phase, moon_icon, bar_read, bar_desc, uv_index, uv_text, dew_point, observatory, visibility, city, country, weathermap)
805
774
                        day_forecast_data_list.append(day_forecast_data)
806
 
    
 
775
 
807
776
                        # night forecast specific data
808
777
                        daytime_n = self.getChild(day, 'part', 1)
809
778
                        condition_code = self.getText(daytime_n, 'icon')
814
783
                        wind_gusts = self.getText(wind_n, 'gust')
815
784
                        wind_direction_numeric = self.getText(wind_n, 'd')
816
785
                        wind_direction = self.getText(wind_n, 't')
817
 
                        
 
786
 
818
787
                        precip = self.getText(daytime_n, 'ppcp')
819
788
                        humidity = self.getText(daytime_n, 'hmid')
820
 
                        
 
789
 
821
790
                        night_forecast_data = ForecastDataset(last_update, day_of_week, low_temp, high_temp, condition_code, condition, precip, humidity, wind_direction, wind_direction_numeric, wind_speed, wind_gusts, sunrise, sunset, moon_phase, moon_icon, bar_read, bar_desc, uv_index, uv_text, dew_point, observatory, visibility, city, country, weathermap)
822
791
                        night_forecast_data_list.append(night_forecast_data)
823
 
                        
 
792
 
824
793
                    self.forecast_data[location] = ForecastLocation(current_forecast_data, day_forecast_data_list, night_forecast_data_list, datetime.today())
825
 
                    
 
794
 
826
795
                    return True
827
 
            
 
796
 
828
797
                except Exception, e:
829
798
                    self.logger.error("Error reading weather data: " + e.__str__()+"\n"+traceback.format_exc())
830
799
 
831
 
    def getTimestampOutput(self, timestamp, minuteshide):            
 
800
    def getTimestampOutput(self, timestamp, minuteshide):
832
801
        # minuteshide:
833
802
        # None = disabled
834
803
        # -1 = hide days and use config.EXPIRY_MINUTES
835
804
        # 0 = hide days and always show hours
836
 
        
 
805
 
837
806
        output = u""
838
 
        
 
807
 
839
808
        today = datetime.today()
840
809
        days = today.day - timestamp.day
841
810
        if days or minuteshide == None:
842
811
            output += timestamp.strftime(self.config.DATEFORMAT)
843
 
        
 
812
 
844
813
        if minuteshide == -1:
845
814
            minuteshide = self.config.EXPIRY_MINUTES
846
 
            
 
815
 
847
816
        delta = today - timestamp
848
817
        if days or minuteshide == None or minuteshide == 0 or delta.seconds > minuteshide * 60:
849
818
            if (len(output) > 0):
850
819
                output += " "
851
820
            output += timestamp.strftime(self.config.TIMEFORMAT)
852
 
        
 
821
 
853
822
        return output
854
823
 
855
824
 
870
839
                        except:
871
840
                            self.logger.error("Failed to extract update datetime from data using python 2.4 compliant code" + traceback.format_exc())
872
841
                            output = ""
873
 
                            
 
842
 
874
843
                    if timezone != None and timezone != "Local Time" and len(output) > 0:
875
844
                        output = output + " " + timezone
876
845
 
877
846
                if len(output) == 0:
878
847
                    self.logger.error("Unable to extract the Last update date from the dataset, using raw text without formatting")
879
848
                    output = set.last_update.strip()
880
 
                    
 
849
 
881
850
            elif datatype == "LF":
882
851
                output = self.getTimestampOutput(self.forecast_data[location].timestamp, minuteshide)
883
852
            elif datatype == "DW":
889
858
                #output = ForecastText.conditions_weather_font[set.condition_code]
890
859
                output = self.getImageSrcForConditionCode(set.condition_code)
891
860
            elif datatype == "LT":
892
 
                if self.isNumeric(set.low) == True:
 
861
                if isNumeric(set.low) == True:
893
862
                    if imperial == True:
894
863
                        string = self.convertCelsiusToFahrenheit(set.low)
895
864
                    else:
899
868
                    string = _(set.low)
900
869
                output = string
901
870
            elif datatype == "HT":
902
 
                if self.isNumeric(set.high) == True:
 
871
                if isNumeric(set.high) == True:
903
872
                    if imperial == True:
904
873
                        string = self.convertCelsiusToFahrenheit(set.high)
905
874
                    else:
914
883
                #output = _(set.condition_text)
915
884
                output = _(ForecastText.conditions_text[set.condition_code])
916
885
            elif datatype == "PC":
917
 
                if self.isNumeric(set.precip) == True:
 
886
                if isNumeric(set.precip) == True:
918
887
                    string = set.precip + u"%"
919
888
                else:
920
889
                    string = _(set.precip)
921
890
                output = string
922
891
            elif datatype == "HM":
923
 
                if self.isNumeric(set.humidity) == True:
 
892
                if isNumeric(set.humidity) == True:
924
893
                    string = set.humidity + u"%"
925
894
                else:
926
895
                    string = _(set.humidity)
940
909
            elif datatype == "BS":
941
910
                if set.wind_speed == "calm":
942
911
                    output = self.getImageSrcForBearing(ForecastText.bearing_icon["calm"])
943
 
                elif self.isNumeric(set.wind_speed) == True:
 
912
                elif isNumeric(set.wind_speed) == True:
944
913
                    if (set.wind_dir == "VAR"):
945
914
                        #TODO: need speed factored in once we have bearing icons with speed and direction...
946
915
                        #output = self.getImageSrcForBearing(ForecastText.bearing_icon[set.wind_dir])
947
916
                        output = self.getImageSrcForBearing(int(ForecastText.bearing_icon[set.wind_dir]) + self.getWindLevel(set.wind_speed))
948
 
                    else:                    
 
917
                    else:
949
918
                        try:
950
919
                            #TODO: need speed factored in once we have bearing icons with speed and direction...
951
920
                            #output = self.getImageSrcForBearing(ForecastText.bearing_icon[set.wind_dir])
963
932
            elif datatype == "WA":
964
933
                output = _(set.wind_dir_numeric)
965
934
            elif datatype == "WS":
966
 
                if self.isNumeric(set.wind_speed) == True:
 
935
                if isNumeric(set.wind_speed) == True:
967
936
                    if beaufort == True:
968
937
                        string = self.convertKPHtoBeaufort(set.wind_speed)
969
938
                    elif imperial == True:
975
944
                    string = _(set.wind_speed)
976
945
                output = string
977
946
            elif datatype == "WG":
978
 
                if self.isNumeric(set.wind_gusts) == True:
 
947
                if isNumeric(set.wind_gusts) == True:
979
948
                    if beaufort == True:
980
949
                        string = self.convertKPHtoBeaufort(set.wind_gusts)
981
950
                    elif imperial == True:
987
956
                    string = _(set.wind_gusts) # need to define translations
988
957
                output = string
989
958
            elif datatype == "SR":
990
 
                
 
959
 
991
960
                try:
992
961
                    srtime = datetime.strptime(set.sunrise, "%I:%M %p")
993
962
                    output = srtime.strftime(self.config.TIMEFORMAT)
1000
969
                    except:
1001
970
                        self.logger.error("Failed to extract sunrise datetime from data using python 2.4 compliant code" + traceback.format_exc())
1002
971
                        output = set.sunrise
1003
 
                                
 
972
 
1004
973
            elif datatype == "SS":
1005
974
                try:
1006
975
                    sstime = datetime.strptime(set.sunset, "%I:%M %p")
1014
983
                    except:
1015
984
                        self.logger.error("Failed to extract sunset datetime from data using python 2.4 compliant code" + traceback.format_exc())
1016
985
                        output = set.sunset
1017
 
                                        
 
986
 
1018
987
            elif datatype == "DL":
1019
 
                
 
988
 
1020
989
                srtime = None
1021
990
                sstime = None
1022
 
                
 
991
 
1023
992
                try:
1024
993
                    srtime = datetime.strptime(set.sunrise, "%I:%M %p")
1025
 
                    sstime = datetime.strptime(set.sunset, "%I:%M %p")                  
 
994
                    sstime = datetime.strptime(set.sunset, "%I:%M %p")
1026
995
                except:
1027
996
                    self.logger.error("Failed to extract sunrise/sunset from data using standard code" + traceback.format_exc())
1028
997
                    try:
1033
1002
                        self.logger.error("Failed to extract sunrise/sunset from data using python 2.4 compliant code" + traceback.format_exc())
1034
1003
 
1035
1004
                if srtime != None and sstime != None:
1036
 
                    delta = sstime - srtime              
1037
 
                    output = self.getHoursMinutesStringFromSeconds(delta.seconds)
 
1005
                    delta = sstime - srtime
 
1006
                    output = getHoursMinutesStringFromSeconds(delta.seconds)
1038
1007
                else:
1039
1008
                    output = "??:??"
1040
 
                    
 
1009
 
1041
1010
            elif datatype == "MP":
1042
1011
                output = _(set.moon_phase) # need to define translations
1043
1012
            elif datatype == "MF":
1044
1013
                output = self.getImageSrcForMoonCode(ForecastText.conditions_moon_icon[set.moon_icon])
1045
1014
            elif datatype == "BR":
1046
 
                if self.isNumeric(set.bar_read) == True:
 
1015
                if isNumeric(set.bar_read) == True:
1047
1016
                    if imperial == True:
1048
1017
                        string = self.convertMillibarsToInches(set.bar_read,2)
1049
1018
                    else:
1059
1028
            elif datatype == "UT":
1060
1029
                output = _(set.uv_text)
1061
1030
            elif datatype == "DP":
1062
 
                if self.isNumeric(set.dew_point) == True:
 
1031
                if isNumeric(set.dew_point) == True:
1063
1032
                    if imperial == True:
1064
1033
                        string = self.convertCelsiusToFahrenheit(set.dew_point)
1065
1034
                    else:
1071
1040
            elif datatype == "OB":
1072
1041
                output = set.observatory
1073
1042
            elif datatype == "VI":
1074
 
                if self.isNumeric(set.visibility) == True:
 
1043
                if isNumeric(set.visibility) == True:
1075
1044
                    if imperial == True:
1076
1045
                        string = self.convertKilometresToMiles(set.visibility,1)
1077
1046
                    else:
1079
1048
                    string = string + distanceunit
1080
1049
                else:
1081
1050
                    string = _(set.visibility)
1082
 
                output = string            
 
1051
                output = string
1083
1052
            elif datatype == "CN":
1084
1053
                output = set.city
1085
1054
            elif datatype == "CO":
1090
1059
                self.logger.error("Unknown datatype requested: " + datatype)
1091
1060
 
1092
1061
        except KeyError, e:
1093
 
            self.logger.error("Unknown value %s encountered for datatype '%s'! Please report this!" % (e.__str__()+"\n"+traceback.format_exc(), datatype)) 
1094
 
        
1095
 
        output = self.getHTMLText(output)   
 
1062
            self.logger.error("Unknown value %s encountered for datatype '%s'! Please report this!" % (e.__str__()+"\n"+traceback.format_exc(), datatype))
 
1063
 
 
1064
        output = getHTMLText(output)
1096
1065
        return output
1097
1066
 
1098
1067
    def getDatasetOutput(self, location, datatype, startday, endday, night, shortweekday, imperial, beaufort, hideunits, hidedegreesymbol, spaces, minuteshide):
1104
1073
        if not self.checkAndLoad(location):
1105
1074
            self.logger.error("Failed to load the location cache")
1106
1075
            return u""
1107
 
        
 
1076
 
1108
1077
        # define current units for output
1109
1078
        if hideunits == False:
1110
1079
            if imperial == False:
1117
1086
                speedunit = u"mph"
1118
1087
                distanceunit = u"m"
1119
1088
                pressureunit = u"in"
1120
 
                
 
1089
 
1121
1090
            # override speed units if beaufort selected
1122
1091
            if beaufort == True:
1123
1092
                speedunit = u""
1127
1096
                tempunit = u"°"
1128
1097
            else:
1129
1098
                tempunit = u""
1130
 
                
 
1099
 
1131
1100
            speedunit = u""
1132
1101
            distanceunit = u""
1133
1102
            pressureunit = u""
1137
1106
        else: # forecast data
1138
1107
 
1139
1108
            # ensure startday and enday are within the forecast limit
1140
 
            
 
1109
 
1141
1110
            if startday < 0:
1142
1111
                startday = 0
1143
1112
                self.logger.error("--startday set beyond forecast limit, reset to minimum of 0")
1144
1113
            elif startday > self.config.MAXIMUM_DAYS_FORECAST:
1145
1114
                startday = self.config.MAXIMUM_DAYS_FORECAST
1146
1115
                self.logger.error("--startday set beyond forecast limit, reset to maximum of " + str(self.config.MAXIMUM_DAYS_FORECAST))
1147
 
                
 
1116
 
1148
1117
            if endday == None: # if no endday was set use startday
1149
1118
                endday = startday
1150
1119
            elif endday < 0:
1153
1122
            elif endday > self.config.MAXIMUM_DAYS_FORECAST:
1154
1123
                endday = self.config.MAXIMUM_DAYS_FORECAST
1155
1124
                self.logger.error("--endday set beyond forecast limit, reset to maximum of " + str(self.config.MAXIMUM_DAYS_FORECAST))
1156
 
                
 
1125
 
1157
1126
            for daynumber in range(startday, endday + 1):
1158
 
                
 
1127
 
1159
1128
                if night == True:
1160
1129
                    output += self.getDatatypeFromSet(location, datatype, self.forecast_data[location].night[daynumber], shortweekday, imperial, beaufort, tempunit, speedunit, distanceunit, pressureunit, minuteshide)
1161
1130
                else:
1162
1131
                    output += self.getDatatypeFromSet(location, datatype, self.forecast_data[location].day[daynumber], shortweekday, imperial, beaufort, tempunit, speedunit, distanceunit, pressureunit, minuteshide)
1163
 
                    
 
1132
 
1164
1133
                if daynumber != endday:
1165
 
                    output += self.getSpaces(spaces)
 
1134
                    output += getSpaces(spaces)
1166
1135
 
1167
1136
        return output
1168
1137
 
1169
1138
    def getTemplateItemOutput(self, template_text):
1170
 
        
 
1139
 
1171
1140
        # keys to template data
1172
1141
        LOCATION_KEY = "location"
1173
1142
        DATATYPE_KEY = "datatype"
1181
1150
        HIDEDEGREESYMBOL_KEY = "hidedegreesymbol"
1182
1151
        SPACES_KEY = "spaces"
1183
1152
        MINUTESHIDE_KEY = "minuteshide"
1184
 
        
 
1153
 
1185
1154
        location = self.config.LOCATION
1186
1155
        datatype = None
1187
1156
        startday = None
1194
1163
        hidedegreesymbol = False
1195
1164
        spaces = None
1196
1165
        minuteshide = None
1197
 
        
 
1166
 
1198
1167
        for option in template_text.split('--'):
1199
1168
            if len(option) == 0 or option.isspace():
1200
1169
                continue
1201
 
            
 
1170
 
1202
1171
            # not using split here...it can't assign both key and value in one call, this should be faster
1203
1172
            x = option.find('=')
1204
1173
            if (x != -1):
1209
1178
            else:
1210
1179
                key = option.strip()
1211
1180
                value = None
1212
 
            
 
1181
 
1213
1182
            try:
1214
1183
                if key == LOCATION_KEY:
1215
 
                    location = self.getTypedValue(value, "string")
 
1184
                    location = getTypedValue(value, "string")
1216
1185
                elif key == DATATYPE_KEY:
1217
 
                    datatype = self.getTypedValue(value, "string")
 
1186
                    datatype = getTypedValue(value, "string")
1218
1187
                elif key == STARTDAY_KEY:
1219
 
                    startday = self.getTypedValue(value, "integer")
 
1188
                    startday = getTypedValue(value, "integer")
1220
1189
                elif key == ENDDAY_KEY:
1221
 
                    endday = self.getTypedValue(value, "integer")
 
1190
                    endday = getTypedValue(value, "integer")
1222
1191
                elif key == NIGHT_KEY:
1223
1192
                    night = True
1224
1193
                elif key == SHORTWEEKDAY_KEY:
1232
1201
                elif key == HIDEDEGREESYMBOL_KEY:
1233
1202
                    hidedegreesymbol = True
1234
1203
                elif key == SPACES_KEY:
1235
 
                    spaces = self.getTypedValue(value, "integer")
 
1204
                    spaces = getTypedValue(value, "integer")
1236
1205
                elif key == MINUTESHIDE_KEY:
1237
1206
                    if value != None:
1238
 
                        minuteshide = self.getTypedValue(value, "integer")
 
1207
                        minuteshide = getTypedValue(value, "integer")
1239
1208
                    else:
1240
1209
                        minuteshide = -1
1241
1210
                else:
1244
1213
            except (TypeError, ValueError):
1245
1214
                self.logger.error("Cannot convert option argument to number: " + option)
1246
1215
                return u""
1247
 
                            
 
1216
 
1248
1217
        if datatype != None:
1249
1218
            output = self.getDatasetOutput(location, datatype, startday, endday, night, shortweekday, imperial, beaufort, hideunits, hidedegreesymbol, spaces, minuteshide)
1250
1219
            return output
1256
1225
        output = u""
1257
1226
        end = False
1258
1227
        a = 0
1259
 
        
 
1228
 
1260
1229
        # a and b are indexes in the template string
1261
1230
        # moving from left to right the string is processed
1262
1231
        # b is index of the opening bracket and a of the closing bracket
1263
1232
        # everything between b and a is a template that needs to be parsed
1264
1233
        while not end:
1265
1234
            b = template.find('[', a)
1266
 
            
 
1235
 
1267
1236
            if b == -1:
1268
1237
                b = len(template)
1269
1238
                end = True
1270
 
            
 
1239
 
1271
1240
            # if there is something between a and b, append it straight to output
1272
1241
            if b > a:
1273
1242
                output += template[a : b]
1278
1247
                    # skip the bracket in the input string and continue from the beginning
1279
1248
                    a = b + 1
1280
1249
                    continue
1281
 
                    
 
1250
 
1282
1251
            if end:
1283
1252
                break
1284
 
            
 
1253
 
1285
1254
            a = template.find(']', b)
1286
 
            
 
1255
 
1287
1256
            if a == -1:
1288
1257
                self.logger.error("Missing terminal bracket (]) for a template item")
1289
1258
                return u""
1290
 
            
 
1259
 
1291
1260
            # if there is some template text...
1292
1261
            if a > b + 1:
1293
1262
                output += self.getTemplateItemOutput(template[b + 1 : a])
1294
 
            
 
1263
 
1295
1264
            a = a + 1
1296
1265
 
1297
1266
        return output
1298
1267
 
1299
1268
    def getOutput(self):
1300
 
        
 
1269
 
1301
1270
        if self.options.noheader == True:
1302
1271
            headertemplatefilepath = app_path+"/templates/nullheader.template"
1303
1272
            self.logger.info("Using custom header template file '%s'"%headertemplatefilepath)
1319
1288
            headertemplate = inputfile.read()
1320
1289
        finally:
1321
1290
            inputfile.close()
1322
 
                    
 
1291
 
1323
1292
        if self.options.template != None:
1324
1293
            templatefilepath = self.options.template
1325
1294
            self.logger.info("Using custom template file '%s'"%templatefilepath)
1329
1298
        else:
1330
1299
            templatefilepath = app_path+"/templates/forecast.template"
1331
1300
            self.logger.info("Using default template")
1332
 
             
 
1301
 
1333
1302
        # load the file
1334
1303
        try:
1335
1304
            inputfile = codecs.open(os.path.expanduser(templatefilepath), encoding='utf-8')
1342
1311
 
1343
1312
        output = headertemplate
1344
1313
        output = output + self.getOutputFromTemplate(template)
1345
 
        
 
1314
 
1346
1315
        return output.encode("utf-8")
1347
 
    
 
1316
 
1348
1317
    def getText(self, parentNode, name):
1349
1318
        try:
1350
1319
            node = parentNode.getElementsByTagName(name)[0]
1356
1325
            if child.nodeType == child.TEXT_NODE:
1357
1326
                rc = rc + child.data
1358
1327
        return rc
1359
 
    
 
1328
 
1360
1329
    def getChild(self, parentNode, name, index = 0):
1361
1330
        try:
1362
1331
            return parentNode.getElementsByTagName(name)[index]
1363
1332
        except IndexError:
1364
1333
            raise Exception, "Data element <%s> is not present under <%s> (index %i)" % (name, parentNode.tagName, index)
1365
1334
 
1366
 
    def getSpaces(self, spaces=1):
1367
 
        string = u""
1368
 
        for dummy in range(0, spaces):
1369
 
            string = string + u" "
1370
 
        return string
1371
 
 
1372
 
    def isNumeric(self, string):
1373
 
        try:
1374
 
            dummy = float(string)
1375
 
            return True
1376
 
        except:
1377
 
            return False
1378
 
 
1379
1335
    def convertCelsiusToFahrenheit(self, temp, dp=0):
1380
1336
        value = ((float(temp) * 9.0) / 5.0) + 32
1381
1337
        if dp == 0:
1396
1352
            return str(int(round(value,dp))) # lose the dp
1397
1353
        else:
1398
1354
            return str(round(value,dp))
1399
 
        
 
1355
 
1400
1356
    def convertMillibarsToInches(self,mb,dp=0):
1401
1357
        value = float(mb)/33.8582
1402
1358
        if dp == 0:
1403
1359
            return str(int(round(value,dp))) # lose the dp
1404
1360
        else:
1405
1361
            return str(round(value,dp))
1406
 
    
 
1362
 
1407
1363
    def getWindLevel(self, speed):
1408
1364
        beaufort = int(self.convertKPHtoBeaufort(speed))
1409
1365
        if beaufort < 4:
1415
1371
        else:
1416
1372
            return 3
1417
1373
 
1418
 
    def getHoursMinutesStringFromSeconds(self,seconds):
1419
 
        time = int(seconds)
1420
 
        hours, time = divmod(time, 60*60)
1421
 
        minutes, seconds = divmod(time, 60)
1422
 
        output = str(hours).rjust(2,"0") + ":" + str(minutes).rjust(2,"0")
1423
 
        return output
1424
 
    
1425
1374
    def getImageSrcForConditionCode(self, conditioncode):
1426
 
        if self.isNumeric(conditioncode) == False:
 
1375
        if isNumeric(conditioncode) == False:
1427
1376
            conditioncode = "25" # N/A image
1428
 
        
 
1377
 
1429
1378
        imagesrc = "file://%s/images/weathericons/%s.png"%(app_path, str(conditioncode).rjust(2,"0"))
1430
1379
        return imagesrc
1431
 
    
 
1380
 
1432
1381
    def getImageSrcForMoonCode(self, mooncode):
1433
1382
        imagesrc = "file://%s/images/moonicons/%s.png"%(app_path, str(mooncode).rjust(2,"0"))
1434
1383
        return imagesrc
1435
1384
 
1436
1385
    def getImageSrcForBearing(self, bearingcode):
1437
1386
        if int(bearingcode) > 0 and int(bearingcode) <= 4:
1438
 
            fileext = "gif" # use animated gif for VAR output 
 
1387
            fileext = "gif" # use animated gif for VAR output
1439
1388
        else:
1440
1389
            fileext = "png"
1441
1390
        imagesrc = "file://%s/images/bearingicons/%s.%s"%(app_path, str(bearingcode).rjust(2,"0"),fileext)
1445
1394
        imagetag = ""
1446
1395
        try:
1447
1396
            url = "http://www.weather.com/outlook/travel/businesstraveler/map/" + location
1448
 
            
 
1397
 
1449
1398
            self.logger.info("Fetching satellite image page from " + url)
1450
 
            
 
1399
 
1451
1400
            usock = urllib2.urlopen(url)
1452
 
            html = usock.read()         
 
1401
            html = usock.read()
1453
1402
        except Exception, e:
1454
1403
            self.logger.error("Error downloading the satellite image page: " + e.__str__()+"\n"+traceback.format_exc())
1455
1404
        else:
1456
1405
            regex = """<IMG NAME="mapImg" SRC="([^\"]+)" WIDTH=([0-9]+) HEIGHT=([0-9]+) BORDER"""
1457
1406
            result = re.findall(regex, html)
1458
1407
            if result and len(result) == 1:
1459
 
                imagesrc, width, height = result[0]                
 
1408
                imagesrc, width, height = result[0]
1460
1409
        finally:
1461
1410
            usock.close()
1462
 
        
 
1411
 
1463
1412
        try:
1464
 
            
 
1413
 
1465
1414
            self.logger.info("Fetching satellite image from " + imagesrc)
1466
 
            
 
1415
 
1467
1416
            usock = urllib2.urlopen(imagesrc)
1468
1417
            img = usock.read()
1469
1418
        except Exception, e:
1479
1428
            imgfile.close()
1480
1429
 
1481
1430
        return imgfilepath
1482
 
    
1483
 
    def getHTMLText(self,text):
1484
 
        try:
1485
 
            htmlentities = []               
1486
 
            for char in text: #html:
1487
 
                if ord(char) < 128:
1488
 
                    htmlentities.append(char)
1489
 
                else:
1490
 
                    htmlentities.append('&%s;' % codepoint2name[ord(char)])
1491
 
            html = "".join(htmlentities)
1492
 
            
1493
 
            html = html.replace("\n","<br>\n") # switch out new line for html breaks
1494
 
            return html            
1495
 
        except:
1496
 
            return text
1497
1431
 
1498
 
    def getCleanText(self,html):
1499
 
        try:
1500
 
            text = str(html)
1501
 
            text = text.replace("\n","") # remove new lines from html
1502
 
            text = text.replace("&apos;","'") # workaround for shitty xml codes not compliant with html
1503
 
            text = text.replace("<br>","\n") # switch out html breaks for new line
1504
 
            text = re.sub('<(.|\n)+?>','',text) # remove any html tags
1505
 
            text =  re.sub('&(%s);' % '|'.join(name2codepoint), lambda m: chr(name2codepoint[m.group(1)]), text)
1506
 
            return text            
1507
 
        except:
1508
 
            return html
1509
 
    
1510
1432
def getHTML(options):
1511
1433
    output = Output(options)
1512
1434
    html = output.getOutput()
1515
1437
 
1516
1438
# to enable testing in isolation
1517
1439
if __name__ == "__main__":
1518
 
    
 
1440
 
1519
1441
    parser = OptionParser()
1520
 
    parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")        
 
1442
    parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")
1521
1443
    parser.add_option("--headertemplate", dest="headertemplate", type="string", metavar="FILE", help=u"Override the header template for the plugin, default or config based template ignored.")
1522
1444
    parser.add_option("--template", dest="template", type="string", metavar="FILE", help=u"Override the template for the plugin, default or config based template ignored.")
1523
1445
    parser.add_option("--verbose", dest="verbose", default=False, action="store_true", help=u"Outputs verbose info to the terminal")
1524
1446
    parser.add_option("--version", dest="version", default=False, action="store_true", help=u"Displays the version of the script.")
1525
 
    parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")                
1526
 
    
 
1447
    parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
 
1448
 
1527
1449
    (options, args) = parser.parse_args()
1528
 
        
 
1450
 
1529
1451
    output = Output(options)
1530
1452
    html = output.getOutput()
1531
1453
    del output
1532
1454
    print html
1533
 
    
 
1455