Esempio n. 1
0
 def __init__(self, workDir, profile):
     self.log = logging.getLogger("Pyggs")
     self.version = VersionInfo(__version__)
     self.workDir = workDir
     self.profile = profile
     self.config = ProfileConfig(
         os.path.join(workDir, "pyggs", "profiles", profile, "config.ini"))
     self.plugins = {}
     self.templateDirs = [
         os.path.join(self.workDir, "pyggs", "templates"),
         os.path.join(os.path.abspath(os.path.dirname(__file__)),
                      "templates")
     ]
     self.themeDirs = [
         os.path.join(self.workDir, "pyggs", "themes"),
         os.path.join(os.path.abspath(os.path.dirname(__file__)), "themes")
     ]
     # Set prefered language
     langs = globals()["langs"]
     lang = self.config.get("general", "language")
     if len(lang) > 0 and lang in langs:
         langs[lang].install()
Esempio n. 2
0
 def __init__(self, workDir, profile):
     self.log = logging.getLogger("Pyggs")
     self.version = VersionInfo(__version__)
     self.workDir = workDir
     self.profile = profile
     self.config = ProfileConfig(os.path.join(workDir, "pyggs", "profiles", profile, "config.ini"))
     self.plugins = {}
     self.templateDirs = [os.path.join(self.workDir, "pyggs", "templates"), os.path.join(os.path.abspath(os.path.dirname(__file__)), "templates")]
     self.themeDirs = [os.path.join(self.workDir, "pyggs", "themes"), os.path.join(os.path.abspath(os.path.dirname(__file__)), "themes")]
     # Set prefered language
     langs = globals()["langs"]
     lang = self.config.get("general", "language")
     if len(lang) > 0 and lang in langs:
         langs[lang].install()
Esempio n. 3
0
class Pyggs(object):
    def __init__(self, workDir, profile):
        self.log = logging.getLogger("Pyggs")
        self.version = VersionInfo(__version__)
        self.workDir = workDir
        self.profile = profile
        self.config = ProfileConfig(os.path.join(workDir, "pyggs", "profiles", profile, "config.ini"))
        self.plugins = {}
        self.templateDirs = [os.path.join(self.workDir, "pyggs", "templates"), os.path.join(os.path.abspath(os.path.dirname(__file__)), "templates")]
        self.themeDirs = [os.path.join(self.workDir, "pyggs", "themes"), os.path.join(os.path.abspath(os.path.dirname(__file__)), "themes")]
        # Set prefered language
        langs = globals()["langs"]
        lang = self.config.get("general", "language")
        if len(lang) > 0 and lang in langs:
            langs[lang].install()


    def interactiveSetup(self):
        """ Interactive setup script.
        """
        print()
        console.writeln(_("Entering setup menu for profile {0}.").format(self.profile), console.color("G", True, ""))
        self.setupWorkDir()

        choice = ""
        choices = []
        choices.append(_("Exit"))
        choices.append(_("General options"))
        choices.append("Geocaching.com")
        choices.append(_("Output"))
        choices.append(_("Enable/Disable plugins"))
        choices.append(_("Plugins settings"))
        while choice != choices[0]:
            print()
            console.writeln(_("Main menu"), console.color("RB", True, ""))
            choice = console.menu(_("Action:"), choices)
            if choice == choices[1]:
                self.setupGeneral()
            elif choice == choices[2]:
                self.setupGeocachingCom()
            elif choice == choices[3]:
                self.setupOutput()
            elif choice == choices[4]:
                self.setupPlugins()
            elif choice == choices[5]:
                self.setupPluginsSettings()
        self.setupEnd()


    def fullSetup(self):
        """ Full setup script.
        """
        print()
        console.writeln(_("Entering full setup for profile {0}.").format(self.profile), console.color("G", True, ""))
        self.setupWorkDir()
        self.setupGeneral()
        self.setupGeocachingCom()
        self.setupOutput()
        self.setupPlugins()
        self.setupEnd()


    def setupWorkDir(self):
        """ Setup structure of working directory.
        """
        self.log.debug("Setting up base working directory structure.")
        if not os.path.isdir(self.workDir):
            os.makedirs(self.workDir, 0o750)
        if not os.path.isdir(self.workDir):
            self.log.critical(_("Unable to create working directory {0}.").format(self.workDir))

        parserDir = os.path.join(self.workDir, "parser")
        if not os.path.isdir(parserDir):
            os.mkdir(parserDir, 0o750)
        pyggsDir = os.path.join(self.workDir, "pyggs")
        if not os.path.isdir(pyggsDir):
            os.mkdir(pyggsDir, 0o750)
        profilesDir = os.path.join(pyggsDir, "profiles")
        if not os.path.isdir(profilesDir):
            os.mkdir(profilesDir, 0o750)
        if not os.path.isdir(parserDir) or not os.path.isdir(pyggsDir) or not os.path.isdir(profilesDir):
            self.log.critical(_("Unable to set up base directory structure in working directory {0}.").format(self.workDir))

        self.log.info(_("Working directory is {0}.").format(self.workDir))

        profileDir = os.path.join(profilesDir, self.profile)
        if not os.path.isdir(profileDir):
            os.mkdir(profileDir)
        if not os.path.isdir(profileDir):
            self.log.critical(_("Unable to create profile directory {0}.").format(profileDir))


    def setupGeneral(self):
        """ Config: general section
        """
        config = self.config
        config.assertSection("general")
        print()
        console.writeln(_("General options"), console.color("G", True, ""))

        langs = globals()["langs"]
        config.update("general", "language", _("Please, select user interface language ({CHOICES})."), validate=list(langs.keys()))
        langs[config.get("general", "language")].install()

        print("    " + _("Enter your home coordinates in degrees as decimal number (N means positive value, S negative; E means positive value, W negative)."))
        config.update("general", "homelat", _("Latitude:"), validate=lambda val: None if re.search("^-?[0-9]+\.?[0-9]*$", val) is not None else _("Please, use decimal number."))
        config.update("general", "homelon", _("Longitude:"), validate=lambda val: None if re.search("^-?[0-9]+\.?[0-9]*$", val) is not None else _("Please, use decimal number."))
        config.save()


    def setupGeocachingCom(self):
        """ Config: geocaching.com section
        """
        config = self.config
        config.assertSection("geocaching.com")
        print()
        console.writeln("Geocaching.com", console.color("G", True, ""))

        config.update("geocaching.com", "username", _("Username:"******"geocaching.com", "password", _("Password:"******""" Config: output section
        """
        config = self.config
        config.assertSection("output")
        print()
        console.writeln(_("Output"), console.color("G", True, ""))

        templates = self.detectTemplates()
        print("    " + _("Templates are looked up in these directories (consecutively):") + "\n      * " + "\n      * ".join(self.templateDirs))
        config.update("output", "template", _("Template ({CHOICES}):"), validate=templates)

        themes = self.detectThemes()
        print("    " + _("Themes are looked up in these directories (consecutively):") + "\n      * " + "\n      * ".join(self.themeDirs))
        config.update("output", "theme", _("Theme ({CHOICES}):"), validate=themes)

        config.update("output", "directory", _("Output directory:"), validate=lambda val: None if os.path.isdir(os.path.expanduser(val)) else _("You have to input existing directory."))
        config.save()


    def setupPlugins(self):
        """ Config: plugins section
        """
        config = self.config
        config.assertSection("plugins")
        print()
        console.writeln(_("Plugins"), console.color("G", True, ""))

        # Remove not installed plugins
        installedPlugins = self.detectPlugins()
        for plugin in config.options("plugins"):
            if plugin not in installedPlugins:
                logging.debug("Removing not installed plugin {0}.".format(plugin))
                config.remove_option("plugins", plugin)
        # Setup new found plugins
        for plugin in installedPlugins:
            if plugin not in config.options("plugins"):
                self.loadPlugin(plugin)
                console.writeln("  " + _("Plugin") + " " + plugin + ": " + self.plugins[plugin].about, console.color("G", False, ""))
                config.update("plugins", plugin, _("Enable") + " " + plugin + " ({CHOICES})?", validate=["y", "n"])
                if config.get("plugins", plugin) == "y":
                    self.setupPluginsEnable(plugin)
                print()
        config.save()

        plugins = config.options("plugins")
        plugins.sort()
        while True:
            choices = []
            choices.append(_("Exit"))
            for plugin in plugins:
                self.loadPlugin(plugin)
                if config.get("plugins", plugin) == "y":
                    choices.append("[x] {0:25s} {1}".format(plugin, self.plugins[plugin].about))
                else:
                    choices.append("[ ] {0:25s} {1}".format(plugin, self.plugins[plugin].about))
            print()
            console.writeln("  " + _("Enable/Disable plugins menu"), console.color("RB", False, ""))
            choice = console.menu(_("Plugins:"), choices, padding=1)
            if choice == choices[0]:
                break
            plugin = plugins[choices.index(choice) - 1]
            if config.get("plugins", plugin) == "y":
                block = []
                for bl in plugins:
                    if plugin in self.plugins[bl].dependencies:
                        block.append(bl)
                if len(block) > 0:
                    self.log.error(_("Cannot disable plugin {0}, because {1} depend on it.").format(plugin, ", ".join(block)))
                else:
                    print("  " + _("Disabling plugin {0}.").format(plugin))
                    config.set("plugins", plugin, "n")
            else:
                print("  " + _("Enabling plugin {0}.").format(plugin))
                config.set("plugins", plugin, "y")
                self.setupPluginsEnable(plugin)
            config.save()


    def setupPluginsEnable(self, plugin):
        """ Enable and setup plugin with all dependencies
        """
        if hasattr(self.plugins[plugin], "setup"):
            console.write("  " + _("Configuration of") + " ", console.color("GB", False, ""))
            console.writeln(plugin, console.color("GB", True, ""))
            self.plugins[plugin].setup()

        loaded = []
        for plugin in self.config.options("plugins"):
            if self.config.get("plugins", plugin) == "y":
                loaded.append(plugin)
        deps = self.findDeps(loaded)
        for plugin in deps:
            self.log.warn(_("Plugin {0} pulled in as dependency.").format(plugin))
            self.config.set("plugins", plugin, "y")
        for plugin in deps:
            self.setupPluginsEnable(plugin)
        self.config.save()


    def setupPluginsSettings(self):
        """ Config of every enabled plugin
        """
        config = self.config
        choices = []
        choices.append(_("Exit"))
        plugins = config.options("plugins")
        plugins.sort()
        for plugin in list(plugins):
            self.loadPlugin(plugin)
            if hasattr(self.plugins[plugin], "setup"):
                choices.append("{0:25s} {1}".format(plugin, self.plugins[plugin].about))
            else:
                plugins.remove(plugin)
        while True:
            print()
            console.writeln("  " + _("Plugins settings menu"), console.color("RB", False, ""))
            choice = console.menu(_("Plugins:"), choices, padding=1)
            if choice == choices[0]:
                break
            plugin = plugins[choices.index(choice) - 1]
            console.write("  " + _("Configuration of") + " ", console.color("GB", False, ""))
            console.writeln(plugin, console.color("GB", True, ""))
            self.plugins[plugin].setup()
            config.save()


    def setupEnd(self):
        """ Save config, etc
        """
        self.config.save()
        with open(os.path.join(self.workDir, "pyggs", "version"), "w") as fp:
            fp.write(__version__)
        with open(os.path.join(self.workDir, "pyggs" , "profiles", self.profile, "version"), "w") as fp:
            fp.write(__version__)
        print()
        console.writeln(_("Note: You can always edit these setings by running pyggs with --setup (-s) switch."), console.color("G", True, ""))


    def run(self):
        """ Run pyggs
        """
        config = self.config
        # Init GCparser, and redefine again self.log
        gcparser.HTTPInterface.set_data_dir(os.path.join(self.workDir, "parser"))
        gcparser.HTTPInterface.set_credentials(gcparser.Credentials(config.get("geocaching.com", "username"), password=config.get("geocaching.com", "password")))

        self.parsers = {}
        self.parsers["cache"] = gcparser.CacheDetails().get
        self.parsers["myFinds"] = gcparser.MyGeocachingLogs().get_finds
        self.parsers["editProfile"] = gcparser.Profile().update

        self.globalStorage = Storage(os.path.join(self.workDir, "pyggs", "storage.sqlite"))
        self.profileStorage = Storage(os.path.join(self.workDir, "pyggs", "profiles", profile, "storage.sqlite"))

        self.handlers = {}
        self.pages = {}
        self.loadPlugins()
        self.makeDepTree()

        # Prepare plugins
        for plugin in self.plugins:
            if hasattr(self.plugins[plugin], "prepare"):
                self.log.info(_("Preparing plugin {0}...").format(plugin))
                self.plugins[plugin].prepare()

        # Run plugins
        for plugin in self.plugins:
            if hasattr(self.plugins[plugin], "run"):
                self.log.info(_("Running plugin {0}...").format(plugin))
                self.plugins[plugin].run()

        # Render output
        self.outDir = os.path.abspath(os.path.expanduser(self.config.get("output", "directory")))
        if not os.path.isdir(self.outDir):
            self.log.critical(_("Invalid ouput directory {0}.").format(self.outDir))
        templar = Templar(self.getTemplate(), self.getTheme(), self.outDir)
        templar.outputPages(self.pages)

        # Finish plugins
        for plugin in self.plugins:
            if hasattr(self.plugins[plugin], "finish"):
                self.log.info(_("Finishing plugin {0}...").format(plugin))
                self.plugins[plugin].finish()


    def registerPage(self, output, template, menutemplate, context, layout=True):
        """ Register page for rendering.
        """
        self.pages[output] = {"template":template, "menu":menutemplate, "context":context, "layout":layout}


    def registerHandler(self, parsername, handler):
        """ Register handler that gets Parser object, when parse() method is called.
        """
        try:
            self.handlers[parsername].append(handler)
        except KeyError:
            self.handlers[parsername] = []
            self.handlers[parsername].append(handler)


    def parse(self, name, *args, **kwargs):
        """ Create parser and return it to every registered handler.
        """
        handlers = self.handlers.get(name)
        if handlers is not None:
            result = self.parsers[name](*args, **kwargs)
            for handler in handlers:
                handler(result)


    def loadPlugin(self, name):
        """ Load a plugin - name is the file and class name.
        """
        if name not in globals()["plugins"].__dict__:
            self.log.info(_("Loading plugin {0}.").format(name))
            __import__("{0}.{1}".format(globals()["plugins"].__name__, name))
        if name not in self.plugins:
            self.plugins[name] = getattr(globals()["plugins"].__dict__[name], "Plugin")(self)


    def loadPlugins(self):
        """ Loads all plugins and their dependencies.
        """
        for plugin in self.config.options("plugins"):
            if self.config.get("plugins", plugin) == "y":
                self.loadPlugin(plugin)
        deps = self.findDeps(list(self.plugins.keys()))
        if len(deps) > 0:
            self.log.critical(_("Missing dependencies {0}.").format(", ".join(deps)))


    def findDeps(self, plugins):
        """ Finds all dependencies that are not loaded.
        """
        result = []
        for plugin in plugins:
            self.loadPlugin(plugin)
            result.extend(self.findPluginDeps(plugins, self.plugins[plugin].dependencies))
        return list(set(result))


    def findPluginDeps(self, loaded, deps):
        """ findDeps recursive callback
        """
        result = list(deps)
        for plugin in deps:
            if plugin in loaded:
                result.remove(plugin)
            else:
                self.loadPlugin(plugin)
                result.extend(self.findPluginDeps(loaded, self.plugins[plugin].dependencies))
        return list(set(result))


    def makeDepTree(self):
        """ Rearragne the order of self.plugins according to dependencies.
        """
        plugins = OrderedDict()
        fs = 0
        while len(self.plugins):
            fs = fs +1
            if fs > 100:
                self.log.warn(_("Cannot make plugin depedency tree for {0}. Possible circular dependencies.").format(",".join(list(self.plugins.keys()))))
                for plugin in list(self.plugins.keys()):
                    plugins[plugin] = self.plugins.pop(plugin)

            for plugin in list(self.plugins.keys()):
                if len(self.findPluginDeps(list(plugins.keys()), self.plugins[plugin].dependencies)) == 0:
                    plugins[plugin] = self.plugins.pop(plugin)

        self.plugins = plugins


    def detectTemplates(self):
        """ Search for available templates.
        """
        templates = []
        for dir in self.templateDirs:
            if os.path.isdir(dir):
                for template in os.listdir(dir):
                    if os.path.isdir(os.path.join(dir, template)):
                        templates.append(template)
        templates.sort()
        return templates


    def getTemplate(self):
        """ Find directory of selected template.
        """
        template = self.config.get("output", "template")
        for dir in self.templateDirs:
            if os.path.isdir(os.path.join(dir, template)):
                return os.path.join(dir, template)
        self.log.critical(_("Cannot find template {0}.").format(template))


    def detectThemes(self):
        """ Search for available themes.
        """
        themes = []
        for dir in self.themeDirs:
            if os.path.isdir(dir):
                for theme in os.listdir(dir):
                    if os.path.isfile(os.path.join(dir, theme)):
                        themes.append(theme.replace(".theme", ""))
        themes.sort()
        return themes


    def getTheme(self):
        """ Find file of selected theme.
        """
        theme = self.config.get("output", "theme")
        for dir in self.themeDirs:
            if os.path.isfile(os.path.join(dir, theme + ".theme")):
                return os.path.join(dir, theme + ".theme")
        self.log.critical(_("Cannot find theme {0}.").format(theme))


    def detectPlugins(self):
        """ Search for available plugins.
        """
        plugins = []
        for plugin in os.listdir(os.path.join(os.path.dirname(__file__), "plugins")):
            if plugin.endswith(".py") and not plugin.startswith("__init__") and not plugin.startswith("example") and plugin[:-3] != "base":
                plugins.append(plugin[:-3])
        plugins.sort()
        return plugins


    def fetch(self, url, data=None, timeout=20):
        self.log.debug("Downloading {0}".format(url))
        try:
            if data is not None:
                data = urllib.parse.urlencode(data).encode("utf-8")
            response = urllib.request.urlopen(url, data=data, timeout=timeout)
            responseData = response.read()
        except IOError:
            self.log.error(_("Could not fetch URL {0}.").format(url))
            return None
        if response.getcode() != 200:
            self.log.error(_("Got error code {0} while fetching {1}.").format(response.getcode(), url))
            return None
        return responseData
Esempio n. 4
0
class Pyggs(object):
    def __init__(self, workDir, profile):
        self.log = logging.getLogger("Pyggs")
        self.version = VersionInfo(__version__)
        self.workDir = workDir
        self.profile = profile
        self.config = ProfileConfig(
            os.path.join(workDir, "pyggs", "profiles", profile, "config.ini"))
        self.plugins = {}
        self.templateDirs = [
            os.path.join(self.workDir, "pyggs", "templates"),
            os.path.join(os.path.abspath(os.path.dirname(__file__)),
                         "templates")
        ]
        self.themeDirs = [
            os.path.join(self.workDir, "pyggs", "themes"),
            os.path.join(os.path.abspath(os.path.dirname(__file__)), "themes")
        ]
        # Set prefered language
        langs = globals()["langs"]
        lang = self.config.get("general", "language")
        if len(lang) > 0 and lang in langs:
            langs[lang].install()

    def interactiveSetup(self):
        """ Interactive setup script.
        """
        print()
        console.writeln(
            _("Entering setup menu for profile {0}.").format(self.profile),
            console.color("G", True, ""))
        self.setupWorkDir()

        choice = ""
        choices = []
        choices.append(_("Exit"))
        choices.append(_("General options"))
        choices.append("Geocaching.com")
        choices.append(_("Output"))
        choices.append(_("Enable/Disable plugins"))
        choices.append(_("Plugins settings"))
        while choice != choices[0]:
            print()
            console.writeln(_("Main menu"), console.color("RB", True, ""))
            choice = console.menu(_("Action:"), choices)
            if choice == choices[1]:
                self.setupGeneral()
            elif choice == choices[2]:
                self.setupGeocachingCom()
            elif choice == choices[3]:
                self.setupOutput()
            elif choice == choices[4]:
                self.setupPlugins()
            elif choice == choices[5]:
                self.setupPluginsSettings()
        self.setupEnd()

    def fullSetup(self):
        """ Full setup script.
        """
        print()
        console.writeln(
            _("Entering full setup for profile {0}.").format(self.profile),
            console.color("G", True, ""))
        self.setupWorkDir()
        self.setupGeneral()
        self.setupGeocachingCom()
        self.setupOutput()
        self.setupPlugins()
        self.setupEnd()

    def setupWorkDir(self):
        """ Setup structure of working directory.
        """
        self.log.debug("Setting up base working directory structure.")
        if not os.path.isdir(self.workDir):
            os.makedirs(self.workDir, 0o750)
        if not os.path.isdir(self.workDir):
            self.log.critical(
                _("Unable to create working directory {0}.").format(
                    self.workDir))

        parserDir = os.path.join(self.workDir, "parser")
        if not os.path.isdir(parserDir):
            os.mkdir(parserDir, 0o750)
        pyggsDir = os.path.join(self.workDir, "pyggs")
        if not os.path.isdir(pyggsDir):
            os.mkdir(pyggsDir, 0o750)
        profilesDir = os.path.join(pyggsDir, "profiles")
        if not os.path.isdir(profilesDir):
            os.mkdir(profilesDir, 0o750)
        if not os.path.isdir(parserDir) or not os.path.isdir(
                pyggsDir) or not os.path.isdir(profilesDir):
            self.log.critical(
                _("Unable to set up base directory structure in working directory {0}."
                  ).format(self.workDir))

        self.log.info(_("Working directory is {0}.").format(self.workDir))

        profileDir = os.path.join(profilesDir, self.profile)
        if not os.path.isdir(profileDir):
            os.mkdir(profileDir)
        if not os.path.isdir(profileDir):
            self.log.critical(
                _("Unable to create profile directory {0}.").format(
                    profileDir))

    def setupGeneral(self):
        """ Config: general section
        """
        config = self.config
        config.assertSection("general")
        print()
        console.writeln(_("General options"), console.color("G", True, ""))

        langs = globals()["langs"]
        config.update("general",
                      "language",
                      _("Please, select user interface language ({CHOICES})."),
                      validate=list(langs.keys()))
        langs[config.get("general", "language")].install()

        print("    " + _(
            "Enter your home coordinates in degrees as decimal number (N means positive value, S negative; E means positive value, W negative)."
        ))
        config.update("general",
                      "homelat",
                      _("Latitude:"),
                      validate=lambda val: None
                      if re.search("^-?[0-9]+\.?[0-9]*$", val) is not None else
                      _("Please, use decimal number."))
        config.update("general",
                      "homelon",
                      _("Longitude:"),
                      validate=lambda val: None
                      if re.search("^-?[0-9]+\.?[0-9]*$", val) is not None else
                      _("Please, use decimal number."))
        config.save()

    def setupGeocachingCom(self):
        """ Config: geocaching.com section
        """
        config = self.config
        config.assertSection("geocaching.com")
        print()
        console.writeln("Geocaching.com", console.color("G", True, ""))

        config.update("geocaching.com",
                      "username",
                      _("Username:"******"geocaching.com",
                      "password",
                      _("Password:"******""" Config: output section
        """
        config = self.config
        config.assertSection("output")
        print()
        console.writeln(_("Output"), console.color("G", True, ""))

        templates = self.detectTemplates()
        print("    " + _(
            "Templates are looked up in these directories (consecutively):") +
              "\n      * " + "\n      * ".join(self.templateDirs))
        config.update("output",
                      "template",
                      _("Template ({CHOICES}):"),
                      validate=templates)

        themes = self.detectThemes()
        print("    " +
              _("Themes are looked up in these directories (consecutively):") +
              "\n      * " + "\n      * ".join(self.themeDirs))
        config.update("output",
                      "theme",
                      _("Theme ({CHOICES}):"),
                      validate=themes)

        config.update("output",
                      "directory",
                      _("Output directory:"),
                      validate=lambda val: None
                      if os.path.isdir(os.path.expanduser(val)) else _(
                          "You have to input existing directory."))
        config.save()

    def setupPlugins(self):
        """ Config: plugins section
        """
        config = self.config
        config.assertSection("plugins")
        print()
        console.writeln(_("Plugins"), console.color("G", True, ""))

        # Remove not installed plugins
        installedPlugins = self.detectPlugins()
        for plugin in config.options("plugins"):
            if plugin not in installedPlugins:
                logging.debug(
                    "Removing not installed plugin {0}.".format(plugin))
                config.remove_option("plugins", plugin)
        # Setup new found plugins
        for plugin in installedPlugins:
            if plugin not in config.options("plugins"):
                self.loadPlugin(plugin)
                console.writeln(
                    "  " + _("Plugin") + " " + plugin + ": " +
                    self.plugins[plugin].about, console.color("G", False, ""))
                config.update("plugins",
                              plugin,
                              _("Enable") + " " + plugin + " ({CHOICES})?",
                              validate=["y", "n"])
                if config.get("plugins", plugin) == "y":
                    self.setupPluginsEnable(plugin)
                print()
        config.save()

        plugins = config.options("plugins")
        plugins.sort()
        while True:
            choices = []
            choices.append(_("Exit"))
            for plugin in plugins:
                self.loadPlugin(plugin)
                if config.get("plugins", plugin) == "y":
                    choices.append("[x] {0:25s} {1}".format(
                        plugin, self.plugins[plugin].about))
                else:
                    choices.append("[ ] {0:25s} {1}".format(
                        plugin, self.plugins[plugin].about))
            print()
            console.writeln("  " + _("Enable/Disable plugins menu"),
                            console.color("RB", False, ""))
            choice = console.menu(_("Plugins:"), choices, padding=1)
            if choice == choices[0]:
                break
            plugin = plugins[choices.index(choice) - 1]
            if config.get("plugins", plugin) == "y":
                block = []
                for bl in plugins:
                    if plugin in self.plugins[bl].dependencies:
                        block.append(bl)
                if len(block) > 0:
                    self.log.error(
                        _("Cannot disable plugin {0}, because {1} depend on it."
                          ).format(plugin, ", ".join(block)))
                else:
                    print("  " + _("Disabling plugin {0}.").format(plugin))
                    config.set("plugins", plugin, "n")
            else:
                print("  " + _("Enabling plugin {0}.").format(plugin))
                config.set("plugins", plugin, "y")
                self.setupPluginsEnable(plugin)
            config.save()

    def setupPluginsEnable(self, plugin):
        """ Enable and setup plugin with all dependencies
        """
        if hasattr(self.plugins[plugin], "setup"):
            console.write("  " + _("Configuration of") + " ",
                          console.color("GB", False, ""))
            console.writeln(plugin, console.color("GB", True, ""))
            self.plugins[plugin].setup()

        loaded = []
        for plugin in self.config.options("plugins"):
            if self.config.get("plugins", plugin) == "y":
                loaded.append(plugin)
        deps = self.findDeps(loaded)
        for plugin in deps:
            self.log.warn(
                _("Plugin {0} pulled in as dependency.").format(plugin))
            self.config.set("plugins", plugin, "y")
        for plugin in deps:
            self.setupPluginsEnable(plugin)
        self.config.save()

    def setupPluginsSettings(self):
        """ Config of every enabled plugin
        """
        config = self.config
        choices = []
        choices.append(_("Exit"))
        plugins = config.options("plugins")
        plugins.sort()
        for plugin in list(plugins):
            self.loadPlugin(plugin)
            if hasattr(self.plugins[plugin], "setup"):
                choices.append("{0:25s} {1}".format(
                    plugin, self.plugins[plugin].about))
            else:
                plugins.remove(plugin)
        while True:
            print()
            console.writeln("  " + _("Plugins settings menu"),
                            console.color("RB", False, ""))
            choice = console.menu(_("Plugins:"), choices, padding=1)
            if choice == choices[0]:
                break
            plugin = plugins[choices.index(choice) - 1]
            console.write("  " + _("Configuration of") + " ",
                          console.color("GB", False, ""))
            console.writeln(plugin, console.color("GB", True, ""))
            self.plugins[plugin].setup()
            config.save()

    def setupEnd(self):
        """ Save config, etc
        """
        self.config.save()
        with open(os.path.join(self.workDir, "pyggs", "version"), "w") as fp:
            fp.write(__version__)
        with open(
                os.path.join(self.workDir, "pyggs", "profiles", self.profile,
                             "version"), "w") as fp:
            fp.write(__version__)
        print()
        console.writeln(
            _("Note: You can always edit these setings by running pyggs with --setup (-s) switch."
              ), console.color("G", True, ""))

    def run(self):
        """ Run pyggs
        """
        config = self.config
        # Init GCparser, and redefine again self.log
        gcparser.HTTPInterface.set_data_dir(
            os.path.join(self.workDir, "parser"))
        gcparser.HTTPInterface.set_credentials(
            gcparser.Credentials(config.get("geocaching.com", "username"),
                                 password=config.get("geocaching.com",
                                                     "password")))

        self.parsers = {}
        self.parsers["cache"] = gcparser.CacheDetails().get
        self.parsers["myFinds"] = gcparser.MyGeocachingLogs().get_finds
        self.parsers["editProfile"] = gcparser.Profile().update

        self.globalStorage = Storage(
            os.path.join(self.workDir, "pyggs", "storage.sqlite"))
        self.profileStorage = Storage(
            os.path.join(self.workDir, "pyggs", "profiles", profile,
                         "storage.sqlite"))

        self.handlers = {}
        self.pages = {}
        self.loadPlugins()
        self.makeDepTree()

        # Prepare plugins
        for plugin in self.plugins:
            if hasattr(self.plugins[plugin], "prepare"):
                self.log.info(_("Preparing plugin {0}...").format(plugin))
                self.plugins[plugin].prepare()

        # Run plugins
        for plugin in self.plugins:
            if hasattr(self.plugins[plugin], "run"):
                self.log.info(_("Running plugin {0}...").format(plugin))
                self.plugins[plugin].run()

        # Render output
        self.outDir = os.path.abspath(
            os.path.expanduser(self.config.get("output", "directory")))
        if not os.path.isdir(self.outDir):
            self.log.critical(
                _("Invalid ouput directory {0}.").format(self.outDir))
        templar = Templar(self.getTemplate(), self.getTheme(), self.outDir)
        templar.outputPages(self.pages)

        # Finish plugins
        for plugin in self.plugins:
            if hasattr(self.plugins[plugin], "finish"):
                self.log.info(_("Finishing plugin {0}...").format(plugin))
                self.plugins[plugin].finish()

    def registerPage(self,
                     output,
                     template,
                     menutemplate,
                     context,
                     layout=True):
        """ Register page for rendering.
        """
        self.pages[output] = {
            "template": template,
            "menu": menutemplate,
            "context": context,
            "layout": layout
        }

    def registerHandler(self, parsername, handler):
        """ Register handler that gets Parser object, when parse() method is called.
        """
        try:
            self.handlers[parsername].append(handler)
        except KeyError:
            self.handlers[parsername] = []
            self.handlers[parsername].append(handler)

    def parse(self, name, *args, **kwargs):
        """ Create parser and return it to every registered handler.
        """
        handlers = self.handlers.get(name)
        if handlers is not None:
            result = self.parsers[name](*args, **kwargs)
            for handler in handlers:
                handler(result)

    def loadPlugin(self, name):
        """ Load a plugin - name is the file and class name.
        """
        if name not in globals()["plugins"].__dict__:
            self.log.info(_("Loading plugin {0}.").format(name))
            __import__("{0}.{1}".format(globals()["plugins"].__name__, name))
        if name not in self.plugins:
            self.plugins[name] = getattr(globals()["plugins"].__dict__[name],
                                         "Plugin")(self)

    def loadPlugins(self):
        """ Loads all plugins and their dependencies.
        """
        for plugin in self.config.options("plugins"):
            if self.config.get("plugins", plugin) == "y":
                self.loadPlugin(plugin)
        deps = self.findDeps(list(self.plugins.keys()))
        if len(deps) > 0:
            self.log.critical(
                _("Missing dependencies {0}.").format(", ".join(deps)))

    def findDeps(self, plugins):
        """ Finds all dependencies that are not loaded.
        """
        result = []
        for plugin in plugins:
            self.loadPlugin(plugin)
            result.extend(
                self.findPluginDeps(plugins,
                                    self.plugins[plugin].dependencies))
        return list(set(result))

    def findPluginDeps(self, loaded, deps):
        """ findDeps recursive callback
        """
        result = list(deps)
        for plugin in deps:
            if plugin in loaded:
                result.remove(plugin)
            else:
                self.loadPlugin(plugin)
                result.extend(
                    self.findPluginDeps(loaded,
                                        self.plugins[plugin].dependencies))
        return list(set(result))

    def makeDepTree(self):
        """ Rearragne the order of self.plugins according to dependencies.
        """
        plugins = OrderedDict()
        fs = 0
        while len(self.plugins):
            fs = fs + 1
            if fs > 100:
                self.log.warn(
                    _("Cannot make plugin depedency tree for {0}. Possible circular dependencies."
                      ).format(",".join(list(self.plugins.keys()))))
                for plugin in list(self.plugins.keys()):
                    plugins[plugin] = self.plugins.pop(plugin)

            for plugin in list(self.plugins.keys()):
                if len(
                        self.findPluginDeps(
                            list(plugins.keys()),
                            self.plugins[plugin].dependencies)) == 0:
                    plugins[plugin] = self.plugins.pop(plugin)

        self.plugins = plugins

    def detectTemplates(self):
        """ Search for available templates.
        """
        templates = []
        for dir in self.templateDirs:
            if os.path.isdir(dir):
                for template in os.listdir(dir):
                    if os.path.isdir(os.path.join(dir, template)):
                        templates.append(template)
        templates.sort()
        return templates

    def getTemplate(self):
        """ Find directory of selected template.
        """
        template = self.config.get("output", "template")
        for dir in self.templateDirs:
            if os.path.isdir(os.path.join(dir, template)):
                return os.path.join(dir, template)
        self.log.critical(_("Cannot find template {0}.").format(template))

    def detectThemes(self):
        """ Search for available themes.
        """
        themes = []
        for dir in self.themeDirs:
            if os.path.isdir(dir):
                for theme in os.listdir(dir):
                    if os.path.isfile(os.path.join(dir, theme)):
                        themes.append(theme.replace(".theme", ""))
        themes.sort()
        return themes

    def getTheme(self):
        """ Find file of selected theme.
        """
        theme = self.config.get("output", "theme")
        for dir in self.themeDirs:
            if os.path.isfile(os.path.join(dir, theme + ".theme")):
                return os.path.join(dir, theme + ".theme")
        self.log.critical(_("Cannot find theme {0}.").format(theme))

    def detectPlugins(self):
        """ Search for available plugins.
        """
        plugins = []
        for plugin in os.listdir(
                os.path.join(os.path.dirname(__file__), "plugins")):
            if plugin.endswith(".py") and not plugin.startswith(
                    "__init__") and not plugin.startswith(
                        "example") and plugin[:-3] != "base":
                plugins.append(plugin[:-3])
        plugins.sort()
        return plugins

    def fetch(self, url, data=None, timeout=20):
        self.log.debug("Downloading {0}".format(url))
        try:
            if data is not None:
                data = urllib.parse.urlencode(data).encode("utf-8")
            response = urllib.request.urlopen(url, data=data, timeout=timeout)
        except IOError:
            self.log.error(_("Could not fetch URL {0}.").format(url))
            return None
        if response.getcode() != 200:
            self.log.error(
                _("Got error code {0} while fetching {1}.").format(
                    response.getcode(), url))
            return None
        return response