text: # -*- coding: utf-8 -*-
  
  # Copyright © 2012-2016 Roberto Alsina and others.
  
  # Permission is hereby granted, free of charge, to any
  # person obtaining a copy of this software and associated
  # documentation files (the "Software"), to deal in the
  # Software without restriction, including without limitation
  # the rights to use, copy, modify, merge, publish,
  # distribute, sublicense, and/or sell copies of the
  # Software, and to permit persons to whom the Software is
  # furnished to do so, subject to the following conditions:
  #
  # The above copyright notice and this permission notice
  # shall be included in all copies or substantial portions of
  # the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  # OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
  # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  """Nikola plugin categories."""
  
  from __future__ import absolute_import
  import sys
  import os
  import re
  
  from yapsy.IPlugin import IPlugin
  from doit.cmd_base import Command as DoitCommand
  
  from .utils import LOGGER, first_line
  
  __all__ = (
      'Command',
      'LateTask',
      'PageCompiler',
      'RestExtension',
      'MarkdownExtension',
      'Task',
      'TaskMultiplier',
      'TemplateSystem',
      'SignalHandler',
      'ConfigPlugin',
      'PostScanner',
  )
  
  
  class BasePlugin(IPlugin):
      """Base plugin class."""
  
      def set_site(self, site):
          """Set site, which is a Nikola instance."""
          self.site = site
          self.inject_templates()
  
      def inject_templates(self):
          """Inject 'templates/<engine>' (if exists) very early in the theme chain."""
          try:
              # Sorry, found no other way to get this
              mod_path = sys.modules[self.__class__.__module__].__file__
              mod_dir = os.path.dirname(mod_path)
              tmpl_dir = os.path.join(
                  mod_dir, 'templates', self.site.template_system.name
              )
              if os.path.isdir(tmpl_dir):
                  # Inject tmpl_dir low in the theme chain
                  self.site.template_system.inject_directory(tmpl_dir)
          except AttributeError:
              # In some cases, __builtin__ becomes the module of a plugin.
              # We couldn’t reproduce that, and really find the reason for this,
              # so let’s just ignore it and be done with it.
              pass
  
      def inject_dependency(self, target, dependency):
          """Add 'dependency' to the target task's task_deps."""
          self.site.injected_deps[target].append(dependency)
  
  
  class PostScanner(BasePlugin):
      """The scan method of these plugins is called by Nikola.scan_posts."""
  
      def scan(self):
          """Create a list of posts from some source. Returns a list of Post objects."""
          raise NotImplementedError()
  
  
  class Command(BasePlugin, DoitCommand):
      """Doit command implementation."""
  
      name = "dummy_command"
  
      doc_purpose = "A short explanation."
      doc_usage = ""
      doc_description = None  # None value will completely omit line from doc
      # see http://python-doit.sourceforge.net/cmd_run.html#parameters
      cmd_options = ()
      needs_config = True
  
      def __init__(self, *args, **kwargs):
          """Initialize a command."""
          BasePlugin.__init__(self, *args, **kwargs)
          DoitCommand.__init__(self)
  
      def __call__(self, config=None, **kwargs):
          """Reset doit arguments (workaround)."""
          self._doitargs = kwargs
          DoitCommand.__init__(self, config, **kwargs)
          return self
  
      def execute(self, options=None, args=None):
          """Check if the command can run in the current environment, fail if needed, or call _execute."""
          options = options or {}
          args = args or []
  
          if self.needs_config and not self.site.configured:
              LOGGER.error("This command needs to run inside an existing Nikola site.")
              return False
          return self._execute(options, args)
  
      def _execute(self, options, args):
          """Do whatever this command does.
  
          @param options (dict) with values from cmd_options
          @param args (list) list of positional arguments
          """
          raise NotImplementedError()
  
  
  def help(self):
      """Return help text for a command."""
      text = []
      text.append("Purpose: %s" % self.doc_purpose)
      text.append("Usage:   nikola %s %s" % (self.name, self.doc_usage))
      text.append('')
  
      text.append("Options:")
      for opt in self.cmdparser.options:
          text.extend(opt.help_doc())
  
      if self.doc_description is not None:
          text.append("")
          text.append("Description:")
          text.append(self.doc_description)
      return "\n".join(text)
  
  DoitCommand.help = help
  
  
  class BaseTask(BasePlugin):
      """Base for task generators."""
  
      name = "dummy_task"
  
      # default tasks are executed by default.
      # the others have to be specifie in the command line.
      is_default = True
  
      def gen_tasks(self):
          """Generate tasks."""
          raise NotImplementedError()
  
      def group_task(self):
          """Return dict for group task."""
          return {
              'basename': self.name,
              'name': None,
              'doc': first_line(self.__doc__),
          }
  
  
  class Task(BaseTask):
      """Task generator."""
  
      name = "dummy_task"
  
  
  class LateTask(BaseTask):
      """Late task generator (plugin executed after all Task plugins)."""
  
      name = "dummy_latetask"
  
  
  class TemplateSystem(BasePlugin):
      """Provide support for templating systems."""
  
      name = "dummy_templates"
  
      def set_directories(self, directories, cache_folder):
          """Set the list of folders where templates are located and cache."""
          raise NotImplementedError()
  
      def template_deps(self, template_name):
          """Return filenames which are dependencies for a template."""
          raise NotImplementedError()
  
      def render_template(self, template_name, output_name, context):
          """Render template to a file using context.
  
          This must save the data to output_name *and* return it
          so that the caller may do additional processing.
          """
          raise NotImplementedError()
  
      def render_template_to_string(self, template, context):
          """Render template to a string using context."""
          raise NotImplementedError()
  
      def inject_directory(self, directory):
          """Inject the directory with the lowest priority in the template search mechanism."""
          raise NotImplementedError()
  
  
  class TaskMultiplier(BasePlugin):
      """Take a task and return *more* tasks."""
  
      name = "dummy multiplier"
  
      def process(self, task):
          """Examine task and create more tasks. Returns extra tasks only."""
          return []
  
  
  class PageCompiler(BasePlugin):
      """Compile text files into HTML."""
  
      name = "dummy_compiler"
      friendly_name = ''
      demote_headers = False
      supports_onefile = True
      default_metadata = {
          'title': '',
          'slug': '',
          'date': '',
          'tags': '',
          'category': '',
          'link': '',
          'description': '',
          'type': 'text',
      }
      config_dependencies = []
  
      def register_extra_dependencies(self, post):
          """Add additional dependencies to the post object.
  
          Current main use is the ReST page compiler, which puts extra
          dependencies into a .dep file.
          """
          pass
  
      def compile_html(self, source, dest, is_two_file=False):
          """Compile the source, save it on dest."""
          raise NotImplementedError()
  
      def create_post(self, path, content=None, onefile=False, is_page=False, **kw):
          """Create post file with optional metadata."""
          raise NotImplementedError()
  
      def extension(self):
          """The preferred extension for the output of this compiler."""
          return ".html"
  
      def read_metadata(self, post, file_metadata_regexp=None, unslugify_titles=False, lang=None):
          """Read the metadata from a post, and return a metadata dict."""
          return {}
  
      def split_metadata(self, data):
          """Split data from metadata in the raw post content.
  
          This splits in the first empty line that is NOT at the beginning
          of the document.
          """
          split_result = re.split('(\n\n|\r\n\r\n)', data.lstrip(), maxsplit=1)
          if len(split_result) == 1:
              return '', split_result[0]
          # ['metadata', '\n\n', 'post content']
          return split_result[0], split_result[-1]
  
      def get_compiler_extensions(self):
          """Activate all the compiler extension plugins for a given compiler and return them."""
          plugins = []
          for plugin_info in self.site.compiler_extensions:
              if plugin_info.plugin_object.compiler_name == self.name:
                  plugins.append(plugin_info)
          return plugins
  
  
  class CompilerExtension(BasePlugin):
      """An extension for a Nikola compiler.
  
      If you intend to implement those in your own compiler, you can:
      (a) create a new plugin class for them; or
      (b) use this class and filter them yourself.
      If you choose (b), you should the compiler name to the .plugin
      file in the Nikola/Compiler section and filter all plugins of
      this category, getting the compiler name with:
          p.details.get('Nikola', 'Compiler')
      Note that not all compiler plugins have this option and you might
      need to catch configparser.NoOptionError exceptions.
      """
  
      name = "dummy_compiler_extension"
      compiler_name = "dummy_compiler"
  
  
  class RestExtension(CompilerExtension):
      """Extensions for reStructuredText."""
  
      name = "dummy_rest_extension"
      compiler_name = "rest"
  
  
  class MarkdownExtension(CompilerExtension):
      """Extensions for Markdown."""
  
      name = "dummy_markdown_extension"
      compiler_name = "markdown"
  
  
  class SignalHandler(BasePlugin):
      """Signal handlers."""
  
      name = "dummy_signal_handler"
  
  
  class ConfigPlugin(BasePlugin):
      """A plugin that can edit config (or modify the site) on-the-fly."""
  
      name = "dummy_config_plugin"
  
  
  class Importer(Command):
      """Basic structure for importing data into Nikola.
  
      The flow is:
  
      read_data
      preprocess_data
      parse_data
      generate_base_site
          populate_context
          create_config
      filter_data
      process_data
  
      process_data can branch into:
  
      import_story (may use import_file and save_post)
      import_post (may use import_file and save_post)
      import_attachment (may use import_file)
  
      Finally:
  
      write_urlmap
      """
  
      name = "dummy_importer"
  
      def _execute(self, options={}, args=[]):
          """Import the data into Nikola."""
          raise NotImplementedError()
  
      def generate_base_site(self, path):
          """Create the base site."""
          raise NotImplementedError()
  
      def populate_context(self):
          """Use data to fill context for configuration."""
          raise NotImplementedError()
  
      def create_config(self):
          """Use the context to create configuration."""
          raise NotImplementedError()
  
      def read_data(self, source):
          """Fetch data into self.data."""
          raise NotImplementedError()
  
      def preprocess_data(self):
          """Modify data if needed."""
          pass
  
      def parse_data(self):
          """Convert self.data into self.items."""
          raise NotImplementedError()
  
      def filter_data(self):
          """Remove data that's not to be imported."""
          pass
  
      def process_data(self):
          """Go through self.items and save them."""
  
      def import_story(self):
          """Create a story."""
          raise NotImplementedError()
  
      def import_post(self):
          """Create a post."""
          raise NotImplementedError()
  
      def import_attachment(self):
          """Create an attachment."""
          raise NotImplementedError()
  
      def import_file(self):
          """Import a file."""
          raise NotImplementedError()
  
      def save_post(self):
          """Save a post to disk."""
          raise NotImplementedError()
  