def function_table(parameters, config):
    qa.LogProcessStart(config, "function_table")
    link_map = config["link_map"]

    syntax_map = config["syntax_map"]
    lines = []
    functions = syntax_map["functions"]
    ordered_functions = sorted(functions)

    categoryDict = dict()
    returnTypeDict = dict()
    functionlist = list()

    for function in ordered_functions:
        link = function + "()"
        if not "`" + link + "`" in link_map:
            qa.LogMissingDocumentation(config, link, ["No documentation"], "")
        category = functions[function]["category"]
        addToDict(categoryDict, category, function)

        returnType = functions[function]["returnType"]
        if returnType == "context": returnType = "class"
        elif returnType in ["ilist", "slist", "rlist"]:
            returnType = "(i,r,s)list"
        elif returnType in ["irange", "rrange"]:
            returnType = "(i,r)range"
        addToDict(returnTypeDict, returnType, function)

    lines.append("### Functions by Category\n\n")
    lines += dictToTable(categoryDict)

    lines.append("### Functions by Return Type\n\n")
    lines += dictToTable(returnTypeDict)

    return lines
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_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 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 applyLinkMap(file_name, config):
	# print("applyLinkMap() filename = %s" % file_name)
	markdown_file = open(file_name,"r")
	markdown_lines = markdown_file.readlines()
	markdown_file.close()
	
	config["context_current_file"] = file_name
	config["context_current_line_number"] = 0
	link_map = config["link_map"]
	
	inside_anchor = re.compile("\\[(.*?)\\]\\[#(.+?)\\]")
	
	new_lines = []
	write_changes = False
	in_pre = False
	previous_empty = True
	current_section = ""
	current_title = ""
	for markdown_line in markdown_lines:
		config["context_current_line_number"] += 1
		# we ignore everything in code blocks
		if previous_empty or in_pre:
			if markdown_line.lstrip()[:3] == '```':
				in_pre = not in_pre
			if markdown_line[:4] == "    ":
				new_lines.append(markdown_line)
				continue
			
		# don't link to the current section
		if markdown_line.find("title:") == 0:
			current_title = markdown_line.split('title: ')[1]
			current_title = current_title.rstrip().rstrip('\"')
			current_title = current_title.lstrip().lstrip('\"')
			current_section = current_title
		elif markdown_line.find("#") == 0:
			current_section = markdown_line.rstrip().rstrip('#').lstrip('#').rstrip().lstrip()
					
		new_line = ""
		if not in_pre:
			match = inside_anchor.search(markdown_line)
			if match != None:
				markdown_line = inside_anchor.sub("[\\1][%s#\\2]" % current_title, markdown_line)
			while True:
				anchor = ""
				bracket_depth = 0
				index = -1
				candidate_start = -1
				i = 0
				# ignore existing links, ie everything in brackets
				while i < len(markdown_line):
					if markdown_line[i] == '[': bracket_depth += 1
					elif markdown_line[i] == ']': bracket_depth -= 1
					elif bracket_depth == 0:
						# single backtick creates link; triple backticks don't
						if markdown_line[i] == '`':
							if markdown_line[i:i+3] == '```':
								i += 2
							elif candidate_start == -1:
								candidate_start = i
							else:
								candidate = markdown_line[candidate_start:i+1]
								values = link_map.get(candidate)
								if not values == None and candidate != "`%s`" % current_section:
									anchor = values[0]
									if len(values) > 1:
										errors = ["Multiple link targets in section [%s#%s][%s#%s]" % (current_title, current_section, current_title, current_section)]
										for value in values:
											errors.append("Option: %s" % value)
										qa.LogMissingDocumentation(config, candidate, errors, file_name)
									index = candidate_start
									break
								candidate_start = -1
					i += 1
				if index != -1:
					# print("applyLinkMap() candidate = %s" % candidate)
					# print("applyLinkMap() markdownline = %s" % markdown_line)
					write_changes = True
					new_line += markdown_line[:index]
					new_line += "[" + candidate + "]" + anchor
					markdown_line = markdown_line[index + len(candidate):]
				else:
					break
		new_line += markdown_line
		new_lines.append(new_line)
		previous_empty = markdown_line.lstrip() == ""
		
	if write_changes:
		markdown_file = open(file_name + ".new", "w")
		for new_line in new_lines:
			markdown_file.write(new_line)
		markdown_file.close()
		os.rename(file_name + ".new", file_name)