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)