def include_snippet(parameters, config):
    filename = find_include_file(parameters[0], config["include_directories"])
    if filename == None:
        qa.Log(
            config, "File '%s' not found in search-path %s" %
            (parameters[0], config["include_directories"]))
        return ""
    lines = load_include_file(filename)
    if lines == None:
        return ""

    brush = ""
    if filename[-3:] == '.cf':
        brush = "cf3"

    begin = re.compile(parameters[1])
    if len(parameters) < 3:
        end = re.compile("a^")  # never matches anything
    else:
        end = re.compile(parameters[2])

    markdown_lines = []
    skip_block = True
    in_documentation = False
    for line in lines:
        if skip_block == False:
            if line.find("#[%-%]") == 0:
                skip_block = True
                continue
            # #@ interrupts code block, interpret documentation in example code
            if line.find("#@ ") == 0:
                line = line[3:]
                if not in_documentation:
                    # terminate open code block, if any
                    if len(markdown_lines):
                        markdown_lines.append("```\n\n")
                    in_documentation = True
            elif in_documentation:
                markdown_lines.append("\n```%s\n" % brush)
                in_documentation = False
            # ignore other comments, otherwise append
            if line[0] != '#':
                markdown_lines.append(line)
            if end.match(line) != None:
                break
        elif (begin.match(line) != None) or (line.find("#[%+%]") == 0):
            skip_block = False
            if line[0] != '#':
                markdown_lines.append(line)

    if not len(markdown_lines):
        raise Exception("Snippet not found: %s" % begin.pattern)
        return list()

    return prune_include_lines(markdown_lines, filename)
def library_include(parameters, config):
    markdown_lines = []
    policy_filename = parameters[0] + ".json"
    policy_json = config.get(policy_filename)
    html_name = config.get("context_current_html")

    qa.LogProcessStart(config, "library_include: %s" % policy_filename)

    if policy_json == None:
        policy_path = config[
            "project_directory"] + "/_generated/" + policy_filename
        if not os.path.exists(policy_path):
            qa.Log(config, "File does not exist: " + policy_path)
            return markdown_lines

        policy_json = json.load(open(policy_path, 'r'))
        config[policy_filename] = policy_json

    # for all bundles and bodies...
    for key in policy_json.keys():
        element_list = policy_json[key]
        current_type = None
        for element in element_list:
            errorString = []
            ignore = False
            namespace = element["namespace"]
            if namespace == "default":
                namespace = None
            name = element["name"]
            element_type = element.get("bundleType")
            if element_type == None:
                element_type = element.get("bodyType")
            if element_type == None:
                qa.Log(config, "element without type: " + name)
                continue

            # start a new block for changed types
            # Assumes that bundles and bodies are grouped by type in library.cf
            print_type = False
            if element_type != current_type:
                current_type = element_type
                print_type = True

            prototype = name
            if namespace:
                prototype = namespace + ":" + prototype

            title = prototype
            link_target = prototype + "()"
            if not namespace:
                link_target = parameters[0] + ":" + link_target
            linkresolver.addLinkToMap(prototype + "()", link_target,
                                      html_name + "#" + prototype, config)

            code_lines = []
            documentation_lines = []
            documentation_dict = dict()
            sourceLines = []
            sourceLine = -1

            try:
                source_path = element["sourcePath"]
                source_path = os.path.normpath(source_path)
                source_file = open(source_path, 'r')
                sourceLine = element["line"] - 1  # zero-based indexing
                sourceLines = source_file.readlines()[sourceLine:]
            except:
                qa.Log(
                    config,
                    "could not include code for element %s - check %s" %
                    (name, source_path))

            if len(sourceLines):
                headerLines = list()
                code_lines.append("\n```cf3\n")
                code_lines.append(sourceLines[0])
                del sourceLines[0]
                in_code = False

                for line in sourceLines:
                    if not in_code:
                        line = line.lstrip()
                        if line.find("{") == 0:
                            in_code = True
                        else:
                            headerLines.append(line)
                    if in_code:
                        code_lines.append(line)
                        # super-naive parser...
                        if line.find("}\n") == 0:
                            break
                code_lines.append("\n```\n")

                # scan comments for doxygen-style documentation
                if len(headerLines):
                    current_tag = None
                    current_param = None
                    current_line = ""
                    for headerLine in headerLines:
                        if headerLine.find("#") != 0:
                            continue

                        headerLine = headerLine[1:].rstrip()
                        # strip single whitespace, but maintain indentation
                        if headerLine.find(" ") == 0:
                            headerLine = headerLine[1:]
                        if headerLine.lstrip().find("@") == 0:
                            current_param = None
                            headerLine = headerLine.lstrip()[1:]
                            current_tag = headerLine
                            tag_end = headerLine.find(" ")
                            if tag_end != -1:
                                current_tag = current_tag[:tag_end]
                                headerLine = headerLine[tag_end + 1:]
                            documentation_dict[current_tag] = ""

                        if current_tag == None:
                            continue
                        if current_tag == "ignore":
                            ignore = True
                            break

                        if current_tag == "param":
                            if current_param == None:
                                current_param = headerLine[:headerLine.find(" "
                                                                            )]
                                headerLine = headerLine[len(current_param) +
                                                        1:]
                                documentation_dict[
                                    "param_" +
                                    current_param] = headerLine + "\n"
                            else:
                                documentation_dict[
                                    "param_" +
                                    current_param] += headerLine + "\n"
                        else:
                            documentation_dict[
                                current_tag] += headerLine + "\n"

                    brief = documentation_dict.get("brief", None)
                    if brief:
                        documentation_lines.append("**Description:** ")
                        documentation_lines.append(brief)
                        documentation_lines.append("\n")
                    else:
                        errorString.append("Missing description")
                    return_doc = documentation_dict.get("return", None)
                    if return_doc:
                        documentation_lines.append("**Return value:** ")
                        documentation_lines.append(return_doc)
                        documentation_lines.append("\n")
                else:  # no header lines
                    errorString.append("No documentation")
            else:  # no source lines
                errorString.append(
                    "No source code or unable to read source code")

            if ignore:
                continue
            arguments = element["arguments"]
            argument_idx = 0
            argument_lines = []
            while argument_idx < len(arguments):
                if argument_idx == 0:
                    prototype += "("
                    argument_lines.append("**Arguments:**\n\n")
                argument = arguments[argument_idx]
                prototype += argument
                argument_line = "* ```" + argument + "```"

                # if we have already found documentation for this, use it
                param_line = documentation_dict.get("param_" + argument)
                if param_line == None:
                    errorString.append("No documentation for parameter %s" %
                                       argument)
                if param_line != None:
                    argument_line += ": " + param_line
                # find out where the argument is being used
                elif key == "bundles":
                    for promise_type in element["promiseTypes"]:
                        promise_type_link = "`" + promise_type["name"] + "`"
                        for context in promise_type["contexts"]:
                            for promise in context["promises"]:
                                promiser = promise["promiser"]
                                if promiser.find("(" + argument + ")") != -1:
                                    argument_line += ", used as promiser of type " + promise_type_link
                                    argument_line += "\n"
                                else:
                                    argument_line += resolveAttribute(
                                        promise["attributes"], argument)
                                    if len(argument_line):
                                        argument_line += " of " + promise_type_link + " promiser *" + promiser + "*"
                    argument_line += "\n"
                elif key == "bodies":
                    for context in element["contexts"]:
                        argument_line += resolveAttribute(
                            context["attributes"], argument)
                    argument_line += "\n"

                argument_lines.append(argument_line)
                argument_idx += 1
                if argument_idx == len(arguments):
                    prototype += ")"
                else:
                    prototype += ", "

            if print_type:
                link_map = config["link_map"]
                if ("`body %s`" % current_type) in link_map:
                    printable_type = "[%s]%s bodies" % (
                        current_type, link_map["`body %s`" % current_type][0])
                elif ("`bundle %s`" % current_type) in link_map:
                    printable_type = "[%s]%s bundles" % (
                        current_type,
                        link_map["`bundle %s`" % current_type][0])
                elif ("`%s`" % current_type) in link_map:
                    printable_type = "[%s]%s %s" % (
                        current_type, link_map["`%s`" % current_type][0], key)
                elif key == "bundles":
                    printable_type = "%s [bundles][bundles]" % current_type
                else:
                    printable_type = current_type
                markdown_lines.append("### %s\n" % printable_type)
                markdown_lines.append("\n")

            markdown_lines.append("#### " + title + " ####\n")
            markdown_lines.append("\n")
            markdown_lines.append("**Prototype:** `" + prototype + "`\n")
            markdown_lines.append("\n")
            if len(documentation_lines):
                markdown_lines.append(documentation_lines)
                markdown_lines.append("\n")
            if len(argument_lines):
                markdown_lines.append(argument_lines)
                markdown_lines.append("\n")
            if len(code_lines):
                markdown_lines.append("**Implementation:**\n")
                markdown_lines.append("\n")
                markdown_lines.append(code_lines)
                markdown_lines.append("\n")
            markdown_lines.append("\n")
            if len(errorString):
                locationString = "`in library `" + os.path.relpath(
                    source_path) + "` (%d)" % sourceLine
                qa.LogMissingDocumentation(config, prototype, errorString,
                                           locationString)
                errorString = []

    if len(markdown_lines) == 0:
        qa.Log(config, "Failure to include " + parameters[0])

    return markdown_lines
def document_type(type, type_definition, excludes, config):
    link_map = config["link_map"]

    lines = []

    type_status = type_definition.get("status", "normal")
    type_attributes = type_definition.get("attributes")

    attributes = sorted(type_attributes.keys())
    for attribute in attributes:
        if attribute in excludes:
            continue
        attribute_definition = type_attributes.get(attribute)
        if attribute_definition == None:
            qa.Log(
                config,
                "cfdoc_macros: syntax_map - no definition for attribute %s in type %s"
                % (attribute, type))
            continue

        attribute_status = attribute_definition.get("status", "normal")
        attribute_type = attribute_definition.get("type")
        attribute_range = attribute_definition.get("range")
        attribute_link = "`%s`" % attribute

        if attribute_status == "normal" and (not attribute_link in link_map):
            qa.LogMissingDocumentation(config, type + "/" + attribute,
                                       ["No documentation for attribute"], "")

        if attribute_type == "body":
            if ("`body %s`" % attribute) in link_map:
                attribute_type = "body [`%s`]%s" % (
                    attribute, link_map["`body %s`" % attribute][0])
            elif ("`%s`" % attribute) in link_map:
                attribute_type = "body `%s`" % attribute
            else:
                attribute_type = "body `%s`" % attribute
                qa.LogMissingDocumentation(config, type + "/" + attribute,
                                           ["No documentation for body type"],
                                           "")
        elif attribute_type == "option":
            if attribute_range == "true,false,yes,no,on,off":
                attribute_type = "`boolean`"
                attribute_range = None
            else:
                attribute_type = "one of `%s`" % attribute_range.replace(
                    ",", "`, `")
                attribute_range = None
        elif attribute_type == "context":
            attribute_type = "class expression"
        else:
            attribute_type = "`%s`" % attribute_type

        if attribute_status == "normal":
            attribute_status = ""
        else:
            attribute_status = "<sup>**%s**</sup>" % attribute_status

        if attribute_link in link_map:
            type_link = link_map.get("`%s`" % type)
            if not type_link:
                type_link = link_map.get("`body %s`" % type)
            if type_link:
                type_link = type_link[0]
            anchors = link_map[attribute_link]
            anchor = anchors[0]
            # pick the anchor that matches the currently documented type/attribute best
            if len(anchors) > 1:
                highscore = 0
                for a in anchors:
                    score = 0
                    if type in a:
                        score += 1
                    if attribute in a:
                        score += 1
                    if highscore < score:
                        highscore = score
                        anchor = a

            attribute_link = "[%s]%s" % (attribute_link, anchor)

        line = "* %s%s: %s" % (attribute_link, attribute_status,
                               attribute_type)
        if attribute_range:
            line += " in range `%s`" % attribute_range
        lines.append(line + "\n")
    lines.append("\n")

    return lines
def document_syntax_map(tree, branch, config):
    lines = []
    tree = tree[branch]

    # JSON structure in `tree` is:
    # * type -> dict
    #     * status: normal|deprecated
    #     * attributes -> dict
    #         * attr_name -> dict
    #             * attribute: attr_name
    #             * status: normal|deprecated
    #             * type: int, string, slist...
    #             * range: regex
    #             * visibility: (ignored)

    # first, collect everything that all types have and call it "common"
    types = sorted(tree.keys())
    common_attributes = dict()
    if not "common" in types:
        try:
            common_attributes = copy.deepcopy(
                tree.get("classes").get("attributes"))
        except:
            qa.Log(config,
                   "cfdoc_macros: syntax_map - no promise type classes?!")
        for common_attribute in common_attributes.keys():
            type_count = 0
            for type in types:
                if tree.get(type).get("attributes").get(common_attribute):
                    type_count += 1
            if type_count != len(types):
                del common_attributes[common_attribute]

        if len(common_attributes):
            common_definition = dict()
            common_definition["status"] = "normal"
            common_definition["attributes"] = common_attributes
            lines.append(
                "### [Common Attributes][Promise Types and Attributes#Common Attributes]\n\n"
            )
            lines.append(document_type("common", common_definition, [],
                                       config))

    excludes = common_attributes.keys()

    link_map = config["link_map"]
    for type in types:
        link = None
        if branch == "bodyTypes" and ("`body " + type + "`") in link_map:
            # hack for classes, common and file bodies - see _reference.md
            link = link_map.get("`body " + type + "`")
        else:
            link = link_map.get("`" + type + "`")

        if link:
            lines.append("### [%s]%s\n\n" % (type, link[0]))
        else:
            lines.append("### %s\n\n" % type)
            qa.LogMissingDocumentation(config, type,
                                       ["No documentation for type"], "")
        type_definition = tree.get(type)
        if type_definition == None:
            qa.Log(
                config,
                "cfdoc_macros: syntax_map - no definition for type %s" % type)
            continue
        lines.append(document_type(type, type_definition, excludes, config))

    return lines
def promise_attribute(parameters, config):
    lines = []

    if not "context_current_header" in config:
        sys.stdout.write(
            'ERROR: no "context_current_header" in config when reading promise_attribute with %r parameters, expect broken output'
            % (parameters))
        return lines
    header = config["context_current_header"]
    promise_types = config["syntax_map"]["promiseTypes"]
    body_types = config["syntax_map"]["bodyTypes"]

    if header[2].find("Attributes") != -1:
        # assumption for promise type definition
        # header[1] = promise type
        # header[2] = "Attributes"
        # header[3] = attribute name
        # header[4] = body attribute
        promise_type_def = promise_types[header[1]]
        attribute_def = promise_type_def["attributes"][header[3]]
        attribute_type = attribute_def["type"]
        if attribute_type == "body":
            if header[4]:
                body_type_def = body_types[header[3]]
                attribute_def = body_type_def["attributes"][header[4]]
                attribute_type = attribute_def["type"]
            else:
                lines.append("**Type:** `body %s`\n\n" % header[3])
                return lines
    elif header[2] == "Control Promises" or header[2] == "Common Control":
        # assume body control promise
        # header[1] = component name (cf-*)
        # header[2] = "Control Promises"
        # header[3] = body attribute
        # cut off the cf- prefix and 'd'; this will leave the executor
        # general exception for body common control
        component_name = header[1][3:]
        if header[2] == "Common Control":
            component_name = "common"
        if component_name[-1] == 'd':
            component_name = component_name[:-1]
        if component_name == "exec":  # ugl-hack
            component_name = "executor"
        body_type_def = body_types[component_name]
        attribute_def = body_type_def["attributes"][header[3]]
        attribute_type = attribute_def["type"]
    else:
        qa.Log(
            config,
            "Error: Document structure does not support promise_attribute macro!"
        )
        return lines

    attribute_range = attribute_def.get("range")
    if attribute_type == "option":
        if attribute_range == "true,false,yes,no,on,off":
            lines.append("**Type:** `boolean`\n\n")
        else:
            attribute_values = attribute_range.split(",")
            lines.append("**Type:** (menu option)\n\n")
            lines.append("**Allowed input range:**\n\n")
            for attribute_value in attribute_values:
                lines.append("* ```%s```\n" % attribute_value)
            lines.append("\n")
    elif attribute_type == "bundle":
        lines.append("**Type:** `%s`\n\n" % attribute_type)
    else:
        lines.append("**Type:** `%s`\n\n" % attribute_type)
        if attribute_range == "":
            lines.append("**Allowed input range:** (arbitrary string)\n\n")
        else:
            lines.append("**Allowed input range:** `%s`\n\n" % attribute_range)

    if parameters:
        lines.append("**Default value:** %s\n\n" % parameters[0])

    return lines