Download build/_deps/catch2-src/scripts/updateDocumentToC.py from camenduru/thrust: direct link, hf CLI and curl.
- Browser
- Download file 13.8 kB
-
https://huggingface.co/camenduru/thrust/resolve/main/build/_deps/catch2-src/scripts/updateDocumentToC.py
- Command line
-
hf download hf://camenduru/thrust/build/_deps/catch2-src/scripts/updateDocumentToC.py
-
curl -L -o updateDocumentToC.py https://huggingface.co/camenduru/thrust/resolve/main/build/_deps/catch2-src/scripts/updateDocumentToC.py
13.8 kB
| #!/usr/bin/env python3 | |
| # | |
| # updateDocumentToC.py | |
| # | |
| # Insert table of contents at top of Catch markdown documents. | |
| # | |
| # This script is distributed under the GNU General Public License v3.0 | |
| # | |
| # It is based on markdown-toclify version 1.7.1 by Sebastian Raschka, | |
| # https://github.com/rasbt/markdown-toclify | |
| # | |
| from __future__ import print_function | |
| import argparse | |
| import glob | |
| import os | |
| import re | |
| import sys | |
| from scriptCommon import catchPath | |
| # Configuration: | |
| minTocEntries = 4 | |
| headingExcludeDefault = [1,3,4,5] # use level 2 headers for at default | |
| headingExcludeRelease = [1,3,4,5] # use level 1 headers for release-notes.md | |
| documentsDefault = os.path.join(os.path.relpath(catchPath), 'docs/*.md') | |
| releaseNotesName = 'release-notes.md' | |
| contentTitle = '**Contents**' | |
| contentLineNo = 4 | |
| contentLineNdx = contentLineNo - 1 | |
| # End configuration | |
| VALIDS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-&' | |
| def readLines(in_file): | |
| """Returns a list of lines from a input markdown file.""" | |
| with open(in_file, 'r') as inf: | |
| in_contents = inf.read().split('\n') | |
| return in_contents | |
| def removeLines(lines, remove=('[[back to top]', '<a class="mk-toclify"')): | |
| """Removes existing [back to top] links and <a id> tags.""" | |
| if not remove: | |
| return lines[:] | |
| out = [] | |
| for l in lines: | |
| if l.startswith(remove): | |
| continue | |
| out.append(l) | |
| return out | |
| def removeToC(lines): | |
| """Removes existing table of contents starting at index contentLineNdx.""" | |
| if not lines[contentLineNdx ].startswith(contentTitle): | |
| return lines[:] | |
| result_top = lines[:contentLineNdx] | |
| pos = contentLineNdx + 1 | |
| while lines[pos].startswith('['): | |
| pos = pos + 1 | |
| result_bottom = lines[pos + 1:] | |
| return result_top + result_bottom | |
| def dashifyHeadline(line): | |
| """ | |
| Takes a header line from a Markdown document and | |
| returns a tuple of the | |
| '#'-stripped version of the head line, | |
| a string version for <a id=''></a> anchor tags, | |
| and the level of the headline as integer. | |
| E.g., | |
| >>> dashifyHeadline('### some header lvl3') | |
| ('Some header lvl3', 'some-header-lvl3', 3) | |
| """ | |
| stripped_right = line.rstrip('#') | |
| stripped_both = stripped_right.lstrip('#') | |
| level = len(stripped_right) - len(stripped_both) | |
| stripped_wspace = stripped_both.strip() | |
| # GitHub's sluggification works in an interesting way | |
| # 1) '+', '/', '(', ')' and so on are just removed | |
| # 2) spaces are converted into '-' directly | |
| # 3) multiple -- are not collapsed | |
| dashified = '' | |
| for c in stripped_wspace: | |
| if c in VALIDS: | |
| dashified += c.lower() | |
| elif c.isspace(): | |
| dashified += '-' | |
| else: | |
| # Unknown symbols are just removed | |
| continue | |
| return [stripped_wspace, dashified, level] | |
| def tagAndCollect(lines, id_tag=True, back_links=False, exclude_h=None): | |
| """ | |
| Gets headlines from the markdown document and creates anchor tags. | |
| Keyword arguments: | |
| lines: a list of sublists where every sublist | |
| represents a line from a Markdown document. | |
| id_tag: if true, creates inserts a the <a id> tags (not req. by GitHub) | |
| back_links: if true, adds "back to top" links below each headline | |
| exclude_h: header levels to exclude. E.g., [2, 3] | |
| excludes level 2 and 3 headings. | |
| Returns a tuple of 2 lists: | |
| 1st list: | |
| A modified version of the input list where | |
| <a id="some-header"></a> anchor tags where inserted | |
| above the header lines (if github is False). | |
| 2nd list: | |
| A list of 3-value sublists, where the first value | |
| represents the heading, the second value the string | |
| that was inserted assigned to the IDs in the anchor tags, | |
| and the third value is an integer that represents the headline level. | |
| E.g., | |
| [['some header lvl3', 'some-header-lvl3', 3], ...] | |
| """ | |
| out_contents = [] | |
| headlines = [] | |
| for l in lines: | |
| saw_headline = False | |
| orig_len = len(l) | |
| l_stripped = l.lstrip() | |
| if l_stripped.startswith(('# ', '## ', '### ', '#### ', '##### ', '###### ')): | |
| # comply with new markdown standards | |
| # not a headline if '#' not followed by whitespace '##no-header': | |
| if not l.lstrip('#').startswith(' '): | |
| continue | |
| # not a headline if more than 6 '#': | |
| if len(l) - len(l.lstrip('#')) > 6: | |
| continue | |
| # headers can be indented by at most 3 spaces: | |
| if orig_len - len(l_stripped) > 3: | |
| continue | |
| # ignore empty headers | |
| if not set(l) - {'#', ' '}: | |
| continue | |
| saw_headline = True | |
| dashified = dashifyHeadline(l) | |
| if not exclude_h or not dashified[-1] in exclude_h: | |
| if id_tag: | |
| id_tag = '<a class="mk-toclify" id="%s"></a>'\ | |
| % (dashified[1]) | |
| out_contents.append(id_tag) | |
| headlines.append(dashified) | |
| out_contents.append(l) | |
| if back_links and saw_headline: | |
| out_contents.append('[[back to top](#table-of-contents)]') | |
| return out_contents, headlines | |
| def positioningHeadlines(headlines): | |
| """ | |
| Strips unnecessary whitespaces/tabs if first header is not left-aligned | |
| """ | |
| left_just = False | |
| for row in headlines: | |
| if row[-1] == 1: | |
| left_just = True | |
| break | |
| if not left_just: | |
| for row in headlines: | |
| row[-1] -= 1 | |
| return headlines | |
| def createToc(headlines, hyperlink=True, top_link=False, no_toc_header=False): | |
| """ | |
| Creates the table of contents from the headline list | |
| that was returned by the tagAndCollect function. | |
| Keyword Arguments: | |
| headlines: list of lists | |
| e.g., ['Some header lvl3', 'some-header-lvl3', 3] | |
| hyperlink: Creates hyperlinks in Markdown format if True, | |
| e.g., '- [Some header lvl1](#some-header-lvl1)' | |
| top_link: if True, add a id tag for linking the table | |
| of contents itself (for the back-to-top-links) | |
| no_toc_header: suppresses TOC header if True. | |
| Returns a list of headlines for a table of contents | |
| in Markdown format, | |
| e.g., [' - [Some header lvl3](#some-header-lvl3)', ...] | |
| """ | |
| processed = [] | |
| if not no_toc_header: | |
| if top_link: | |
| processed.append('<a class="mk-toclify" id="table-of-contents"></a>\n') | |
| processed.append(contentTitle + '<br>') | |
| for line in headlines: | |
| if hyperlink: | |
| item = '[%s](#%s)' % (line[0], line[1]) | |
| else: | |
| item = '%s- %s' % ((line[2]-1)*' ', line[0]) | |
| processed.append(item + '<br>') | |
| processed.append('\n') | |
| return processed | |
| def buildMarkdown(toc_headlines, body, spacer=0, placeholder=None): | |
| """ | |
| Returns a string with the Markdown output contents incl. | |
| the table of contents. | |
| Keyword arguments: | |
| toc_headlines: lines for the table of contents | |
| as created by the createToc function. | |
| body: contents of the Markdown file including | |
| ID-anchor tags as returned by the | |
| tagAndCollect function. | |
| spacer: Adds vertical space after the table | |
| of contents. Height in pixels. | |
| placeholder: If a placeholder string is provided, the placeholder | |
| will be replaced by the TOC instead of inserting the TOC at | |
| the top of the document | |
| """ | |
| if spacer: | |
| spacer_line = ['\n<div style="height:%spx;"></div>\n' % (spacer)] | |
| toc_markdown = "\n".join(toc_headlines + spacer_line) | |
| else: | |
| toc_markdown = "\n".join(toc_headlines) | |
| if placeholder: | |
| body_markdown = "\n".join(body) | |
| markdown = body_markdown.replace(placeholder, toc_markdown) | |
| else: | |
| body_markdown_p1 = "\n".join(body[:contentLineNdx ]) + '\n' | |
| body_markdown_p2 = "\n".join(body[ contentLineNdx:]) | |
| markdown = body_markdown_p1 + toc_markdown + body_markdown_p2 | |
| return markdown | |
| def outputMarkdown(markdown_cont, output_file): | |
| """ | |
| Writes to an output file if `outfile` is a valid path. | |
| """ | |
| if output_file: | |
| with open(output_file, 'w') as out: | |
| out.write(markdown_cont) | |
| def markdownToclify( | |
| input_file, | |
| output_file=None, | |
| min_toc_len=2, | |
| github=False, | |
| back_to_top=False, | |
| nolink=False, | |
| no_toc_header=False, | |
| spacer=0, | |
| placeholder=None, | |
| exclude_h=None): | |
| """ Function to add table of contents to markdown files. | |
| Parameters | |
| ----------- | |
| input_file: str | |
| Path to the markdown input file. | |
| output_file: str (default: None) | |
| Path to the markdown output file. | |
| min_toc_len: int (default: 2) | |
| Miniumum number of entries to create a table of contents for. | |
| github: bool (default: False) | |
| Uses GitHub TOC syntax if True. | |
| back_to_top: bool (default: False) | |
| Inserts back-to-top links below headings if True. | |
| nolink: bool (default: False) | |
| Creates the table of contents without internal links if True. | |
| no_toc_header: bool (default: False) | |
| Suppresses the Table of Contents header if True | |
| spacer: int (default: 0) | |
| Inserts horizontal space (in pixels) after the table of contents. | |
| placeholder: str (default: None) | |
| Inserts the TOC at the placeholder string instead | |
| of inserting the TOC at the top of the document. | |
| exclude_h: list (default None) | |
| Excludes header levels, e.g., if [2, 3], ignores header | |
| levels 2 and 3 in the TOC. | |
| Returns | |
| ----------- | |
| changed: Boolean | |
| True if the file has been updated, False otherwise. | |
| """ | |
| cleaned_contents = removeLines( | |
| removeToC(readLines(input_file)), | |
| remove=('[[back to top]', '<a class="mk-toclify"')) | |
| processed_contents, raw_headlines = tagAndCollect( | |
| cleaned_contents, | |
| id_tag=not github, | |
| back_links=back_to_top, | |
| exclude_h=exclude_h) | |
| # add table of contents? | |
| if len(raw_headlines) < min_toc_len: | |
| processed_headlines = [] | |
| else: | |
| leftjustified_headlines = positioningHeadlines(raw_headlines) | |
| processed_headlines = createToc( | |
| leftjustified_headlines, | |
| hyperlink=not nolink, | |
| top_link=not nolink and not github, | |
| no_toc_header=no_toc_header) | |
| if nolink: | |
| processed_contents = cleaned_contents | |
| cont = buildMarkdown( | |
| toc_headlines=processed_headlines, | |
| body=processed_contents, | |
| spacer=spacer, | |
| placeholder=placeholder) | |
| if output_file: | |
| outputMarkdown(cont, output_file) | |
| def isReleaseNotes(f): | |
| return os.path.basename(f) == releaseNotesName | |
| def excludeHeadingsFor(f): | |
| return headingExcludeRelease if isReleaseNotes(f) else headingExcludeDefault | |
| def updateSingleDocumentToC(input_file, min_toc_len, verbose=False): | |
| """Add or update table of contents in specified file. Return 1 if file changed, 0 otherwise.""" | |
| if verbose : | |
| print( 'file: {}'.format(input_file)) | |
| output_file = input_file + '.tmp' | |
| markdownToclify( | |
| input_file=input_file, | |
| output_file=output_file, | |
| min_toc_len=min_toc_len, | |
| github=True, | |
| back_to_top=False, | |
| nolink=False, | |
| no_toc_header=False, | |
| spacer=False, | |
| placeholder=False, | |
| exclude_h=excludeHeadingsFor(input_file)) | |
| # prevent race-condition (Python 3.3): | |
| if sys.version_info >= (3, 3): | |
| os.replace(output_file, input_file) | |
| else: | |
| os.remove(input_file) | |
| os.rename(output_file, input_file) | |
| return 1 | |
| def updateDocumentToC(paths, min_toc_len, verbose): | |
| """Add or update table of contents to specified paths. Return number of changed files""" | |
| n = 0 | |
| for g in paths: | |
| for f in glob.glob(g): | |
| if os.path.isfile(f): | |
| n = n + updateSingleDocumentToC(input_file=f, min_toc_len=min_toc_len, verbose=verbose) | |
| return n | |
| def updateDocumentToCMain(): | |
| """Add or update table of contents to specified paths.""" | |
| parser = argparse.ArgumentParser( | |
| description='Add or update table of contents in markdown documents.', | |
| epilog="""""", | |
| formatter_class=argparse.RawTextHelpFormatter) | |
| parser.add_argument( | |
| 'Input', | |
| metavar='file', | |
| type=str, | |
| nargs=argparse.REMAINDER, | |
| help='files to process, at default: docs/*.md') | |
| parser.add_argument( | |
| '-v', '--verbose', | |
| action='store_true', | |
| help='report the name of the file being processed') | |
| parser.add_argument( | |
| '--min-toc-entries', | |
| dest='minTocEntries', | |
| default=minTocEntries, | |
| type=int, | |
| metavar='N', | |
| help='the minimum number of entries to create a table of contents for [{default}]'.format(default=minTocEntries)) | |
| parser.add_argument( | |
| '--remove-toc', | |
| action='store_const', | |
| dest='minTocEntries', | |
| const=99, | |
| help='remove all tables of contents') | |
| args = parser.parse_args() | |
| paths = args.Input if args.Input else [documentsDefault] | |
| changedFiles = updateDocumentToC(paths=paths, min_toc_len=args.minTocEntries, verbose=args.verbose) | |
| if changedFiles > 0: | |
| print( "Processed table of contents in " + str(changedFiles) + " file(s)" ) | |
| else: | |
| print( "No table of contents added or updated" ) | |
| if __name__ == '__main__': | |
| updateDocumentToCMain() | |
| # end of file | |