1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
import argparse
import fnmatch
import os
from collections import defaultdict
arguments = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="Parses files for content tags and creates and a doxygen suitable listing from the parse results.",
epilog="%(prog)s Parses FILES in the MATCH directory for TAGs."
"The comment lines may be in either # or /// form in a file,"
" but only one form throughout a file. The results of the parse are"
' formatted for a doxygen ".dox" file.',
)
arguments.add_argument("--match", default="./", help="Root directory to search for files to parse.")
arguments.add_argument(
"-f", "--files", metavar="MASK", nargs="*", default=["*.*"], help="One or more file masks of files to parse."
)
arguments.add_argument(
"-x",
"--exclude",
metavar="MASK",
nargs="*",
default=[],
help="One or more file masks to exclude from results matching source_mask(s).",
)
arguments.add_argument(
"--tag",
nargs=2,
default=["@content_tag{", "}"],
help="Set of opening and closing" " strings which surround a tag name and precede the tag documentation.",
)
arguments.add_argument("-o", "--output", default="output.txt", help="Full path to an output file.")
arguments.add_argument(
"--link_source",
metavar="CONTEXT_DIR BASE_URL LINE_PREFIX]",
nargs="*",
default=[],
help="Display linked source before each tag documentation. Requires arguments in order:"
" CONTEXT_DIR BASE_URL LINE_PREFIX. File sources will link to"
" {BASE_URL}{FILE}{LINE_PREFIX}{LINE_NUM}, where FILE is the relative path of CONTEXT_DIR."
" e.g. Given: FILE=a/b/c/xyz.txt LINE_NUM=4 CONTEXT_DIR=a/b BASE_URL=http://a.it LINE_PREFIX=?"
" Link result will be: http://a.it/c/xyz.txt?4",
)
arguments.add_argument(
"--dry_run", action="store_true", help="Does not write to OUTPUT, instead printing info" " to stdout."
)
args = vars(arguments.parse_args())
tag_open = args.get("tag")[0]
tag_close = args.get("tag")[1]
def add_doc_source(_file_name, _line_number, _content, _tags):
fn = _file_name.lstrip("./")
# index documentation by tag name storing the source file name, line number, and documentation string
_tags[_content[0]].add((fn, _line_number, _content[1].strip()))
def parse_file(_parse_file, _tags): # noqa: C901
with open(_parse_file, encoding="utf-8") as f:
match_line = 0
content = []
special_comments_this_file = None
# Documentation may span multiple lines for a content tag.
# Matched lines are only stored in _tags once an end condition is met.
# End conditions are: a line not starting a comment, a new content tag, end of file
for raw_line in enumerate(f):
if raw_line[1].lstrip().startswith("#") and special_comments_this_file is not True:
if special_comments_this_file is None:
special_comments_this_file = False
if tag_open in raw_line[1]:
if match_line:
# in event focs scripts decide to stack one documentation on top of another
add_doc_source(_parse_file, match_line, content, _tags)
# store content and line for later addition
content = "".join(raw_line[1].split(tag_open, 1)[1]).split(tag_close, 1)
content[1] = content[1].strip()
match_line = raw_line[0] + 1
elif match_line:
# not a new content tag, append to previous line description
content[1] += " " + raw_line[1].lstrip("# ")
elif raw_line[1].lstrip().startswith("/// ") and special_comments_this_file is not False:
if special_comments_this_file is None:
special_comments_this_file = True
content = ["# "]
if tag_open in raw_line[1]:
if match_line:
# in event focs scripts decide to stack one documentation on top of another
add_doc_source(_parse_file, match_line, content, _tags)
# store content and line for later addition
content = "".join(raw_line[1].split(tag_open, 1)[1]).split(tag_close, 1)
content[1] = content[1].strip()
match_line = raw_line[0] + 1
elif match_line:
# not a new content tag, append to previous line description
content[1] += " " + raw_line[1].lstrip("/// ")
elif match_line:
# end of description, add a node for this source
add_doc_source(_parse_file, match_line, content, _tags)
match_line = 0
content = []
# EOF
if match_line:
# in event a matched comment is the last line in a file
add_doc_source(_parse_file, match_line, content, _tags)
def get_link(_file, _line_number):
link_args = args.get("link_source")
if len(link_args) != 3:
if link_args:
print("Invalid link_source arguments: ", link_args)
return ""
base_path = os.path.relpath(_file, link_args[0].lstrip("./"))
link = os.path.join(link_args[1], base_path)
link += str.format("{0}{1}", link_args[2], _line_number)
return str.format("[{0}]({1})", base_path, link)
all_tags = defaultdict(set)
# parse files for tags
for _file_path, _, _files in os.walk(args.get("match")):
for file_name in enumerate(_files):
file_match = False
# if this file should be checked
for mask in args.get("files"):
if fnmatch.fnmatch(file_name[1], mask):
file_match = True
if file_match:
# check for exclusions
for mask in args.get("exclude"):
if fnmatch.fnmatch(file_name[1], mask):
file_match = False
if file_match:
parse_file(os.path.join(_file_path, file_name[1]), all_tags)
output_buff = []
# create tags formatted for doxygen
output_buff.append("/**\n")
output_buff.append("@page content_tag_listing Content Definition Tag Listing\n")
output_buff.append(
"This page is required to load parsed data into the documentation, the actual listing can be found @ref content_tags\n"
)
for tag in sorted(all_tags):
output_buff.append("@content_tag{" + tag + "} ")
for source in all_tags[tag]:
description = str.format("{0} ", source[2])
# prepend source and link if requested
link_str = get_link(source[0], source[1])
if link_str:
link_str += " - "
output_buff.append(link_str + description)
output_buff.append("\n")
output_buff.append("*/\n")
if args.get("dry_run"):
source_count = 0
for tag in all_tags:
for value in all_tags[tag]:
source_count += 1
print("arguments:")
for arg in args:
print("\t", arg, ":", args[arg])
print(f"\nFound {len(all_tags.keys())} tags with {source_count} total sources")
else:
abs_dir = os.path.dirname(os.path.abspath(args.get("output")))
if not os.path.exists(abs_dir):
os.mkdir(abs_dir)
with open(args.get("output"), "w") as output_file:
output_file.writelines(output_buff)
|