import os
import os.path
import re
import time
import jinja2
import robot.utils

import cumulusci
from cumulusci.core.exceptions import TaskOptionsError
from cumulusci.core.tasks import BaseTask
from cumulusci.core.utils import process_list_arg
from cumulusci.robotframework import PageObjects

from robot.libdocpkg import DocumentationBuilder
from robot.libraries.BuiltIn import RobotNotRunningError
from robot.libdocpkg.robotbuilder import LibraryDocBuilder
from robot.utils import Importer


class RobotLibDoc(BaseTask):
    task_options = {
        "path": {
            "description": "The path to the robot library to be documented.  Can be single a python file or a .robot file, or a comma separated list of those files. The order of the files will be preserved in the generated documentation.",
            "required": True,
        },
        "output": {
            "description": "The output file where the documentation will be written",
            "required": True,
        },
        "title": {
            "description": "A string to use as the title of the generated output",
            "required": False,
        },
    }

    def _validate_options(self):
        super(RobotLibDoc, self)._validate_options()

        self.options["path"] = process_list_arg(self.options["path"])

        bad_files = []
        for input_file in self.options["path"]:
            if not os.path.exists(input_file):
                bad_files.append(input_file)

        if bad_files:
            if len(bad_files) == 1:
                error_message = "Unable to find the input file '{}'".format(
                    bad_files[0]
                )
            else:
                files = ", ".join(["'{}'".format(filename) for filename in bad_files])
                error_message = "Unable to find the following input files: {}".format(
                    files
                )
            raise TaskOptionsError(error_message)

    def is_pageobject_library(self, path):
        """Return True if the file looks like a page object library"""
        if path.endswith(".py"):
            with open(path, "r") as f:
                data = f.read()
                if re.search(r"@pageobject\(", data):
                    return True
        return False

    def _run_task(self):
        kwfiles = []
        processed_files = []
        for input_file in self.options["path"]:
            kwfile = KeywordFile(input_file)
            try:
                if self.is_pageobject_library(input_file):
                    PageObjects._reset()
                    Importer().import_class_or_module_by_path(
                        os.path.abspath(input_file)
                    )

                    for pobj_name, pobj in sorted(PageObjects.registry.items()):
                        pobj = PageObjects.registry[pobj_name]
                        libname = "{}.{}".format(pobj.__module__, pobj.__name__)
                        libdoc = LibraryDocBuilder().build(libname)
                        libdoc.src = os.path.basename(input_file)
                        libdoc.pobj = libname
                        kwfile.add_keywords(libdoc, pobj_name)

                else:
                    libdoc = DocumentationBuilder(input_file).build(input_file)
                    kwfile.add_keywords(libdoc)

                # if we get here, we were able to process the file correctly
                kwfiles.append(kwfile)
                processed_files.append(input_file)

            except RobotNotRunningError as e:
                # oddly, robot's exception has a traceback embedded in the message, so we'll
                # only print out the first line to hide most of the noise
                self.logger.warn("unexpected error: {}".format(str(e).split("\n")[0]))

        with open(self.options["output"], "w") as f:
            html = self._render_html(kwfiles)
            f.write(html)
            self.logger.info("created {}".format(f.name))

        return {"files": processed_files, "html": html}

    def _render_html(self, libraries):
        """Generate the html. `libraries` is a list of LibraryDocumentation objects"""

        title = self.options.get("title", "Keyword Documentation")
        date = time.strftime("%A %B %d, %I:%M %p")
        cci_version = cumulusci.__version__

        stylesheet_path = os.path.join(os.path.dirname(__file__), "stylesheet.css")
        with open(stylesheet_path) as f:
            stylesheet = f.read()

        jinjaenv = jinja2.Environment(
            loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), autoescape=False
        )
        jinjaenv.filters["robot_html"] = robot.utils.html_format
        template = jinjaenv.get_template("template.html")
        return template.render(
            libraries=libraries,
            title=title,
            cci_version=cci_version,
            stylesheet=stylesheet,
            date=date,
        )


class KeywordFile:
    """Helper class which represents a file and its keywords

    A file may have just a bunch of keywords, or groups of
    keywords organized as page objects. Each group of keywords
    is stored in self.keywords, with the page object metadata
    as a key.

    For normal libraries, the key is an empty tuple.
    """

    def __init__(self, path):
        self.filename = os.path.basename(path)
        self.path = path
        self.keywords = {}

    def add_keywords(self, libdoc, page_object=tuple()):
        self.keywords[page_object] = libdoc

@@ -1324,33 +1324,35 @@
         for 
-input_fil
+library_nam
 e in self.op
@@ -1378,18 +1378,114 @@
         
-if
+name, extension = os.path.splitext(library_name)%0A            if extension in (%22.py%22, %22.robot%22) and
  not os.
@@ -1496,25 +1496,27 @@
 .exists(
-input_fil
+library_nam
 e):%0A    
@@ -1540,33 +1540,35 @@
 iles.append(
-input_fil
+library_nam
 e)%0A%0A        
@@ -2445,25 +2445,27 @@
     for 
-input_fil
+library_nam
 e in sel
@@ -2516,25 +2516,27 @@
 ordFile(
-input_fil
+library_nam
 e)%0A     
@@ -2593,25 +2593,27 @@
 library(
-input_fil
+library_nam
 e):%0A    
@@ -2752,25 +2752,27 @@
 abspath(
-input_fil
+library_nam
 e)%0A     
@@ -3136,25 +3136,27 @@
 asename(
-input_fil
+library_nam
 e)%0A     
@@ -3336,35 +3336,39 @@
 der(
-input_file).build(input_fil
+library_name).build(library_nam
 e)%0A 
@@ -3574,17 +3574,19 @@
 end(
-input_fil
+library_nam
 e)%0A%0A
@@ -5398,32 +5398,69 @@
 __(self, path):%0A
+        if os.path.exists(path):%0A    
         self.fil
@@ -5490,16 +5490,132 @@
 e(path)%0A
+        else:%0A            # if it's not a file, it must be a module%0A            self.filename = path.split(%22.%22)%5B-1%5D%0A
         

Fix to allow documenting a library by its module path rather than filename