mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 20:27:11 +01:00 
			
		
		
		
	Rename to updateDocumentToC.py and adapt for use with Catch
adding missing GPL 3.0 license (thanks for noting @horenmar).
This commit is contained in:
		 Martin Moene
					Martin Moene
				
			
				
					committed by
					
						 Martin Hořeňovský
						Martin Hořeňovský
					
				
			
			
				
	
			
			
			 Martin Hořeňovský
						Martin Hořeňovský
					
				
			
						parent
						
							7e9b53e40c
						
					
				
				
					commit
					61280e6d0a
				
			| @@ -1,44 +1,51 @@ | ||||
| #!/usr/bin/env python | ||||
| 
 | ||||
| # | ||||
| # Sebastian Raschka 2014-2015 | ||||
| # updateDocumentToC.py | ||||
| # | ||||
| # Python script that inserts a table of contents | ||||
| # into markdown documents and creates the required | ||||
| # internal links. | ||||
| # Insert table of contents at top of Catch markdown documents. | ||||
| # | ||||
| # For more information about how internal links | ||||
| # in HTML and Markdown documents work, please see | ||||
| # This script is distributed under the GNU General Public License v3.0 | ||||
| # | ||||
| # Creating a table of contents with internal links in | ||||
| # IPython Notebooks and Markdown documents: | ||||
| # http://sebastianraschka.com/Articles/2014_ipython_internal_links.html | ||||
| # | ||||
| # Updates for this script will be available at | ||||
| # It is based on markdown-toclify version 1.7.1 by Sebastian Raschka, | ||||
| # https://github.com/rasbt/markdown-toclify | ||||
| # | ||||
| # for more information about the usage: | ||||
| # markdown-toclify.py --help | ||||
| # | ||||
| 
 | ||||
| from  __future__  import print_function | ||||
| from scriptCommon import catchPath | ||||
| 
 | ||||
| import argparse | ||||
| import glob | ||||
| import os | ||||
| import re | ||||
| import sys | ||||
| 
 | ||||
| # Configuration: | ||||
| 
 | ||||
| __version__ = '1.7.1' | ||||
| minTocEntries = 4 | ||||
| 
 | ||||
| headingExcludeDefault = [1,3,4,5]  # use level 2 headers for at default | ||||
| headingExcludeRelease = [2,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 read_lines(in_file): | ||||
| 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 remove_lines(lines, remove=('[[back to top]', '<a class="mk-toclify"')): | ||||
| def removeLines(lines, remove=('[[back to top]', '<a class="mk-toclify"')): | ||||
|     """Removes existing [back to top] links and <a id> tags.""" | ||||
| 
 | ||||
|     if not remove: | ||||
| @@ -51,8 +58,22 @@ def remove_lines(lines, remove=('[[back to top]', '<a class="mk-toclify"')): | ||||
|         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[:] | ||||
| 
 | ||||
| def dashify_headline(line): | ||||
|     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 | ||||
| @@ -60,7 +81,7 @@ def dashify_headline(line): | ||||
|         a string version for <a id=''></a> anchor tags, | ||||
|         and the level of the headline as integer. | ||||
|     E.g., | ||||
|     >>> dashify_headline('### some header lvl3') | ||||
|     >>> dashifyHeadline('### some header lvl3') | ||||
|     ('Some header lvl3', 'some-header-lvl3', 3) | ||||
| 
 | ||||
|     """ | ||||
| @@ -84,8 +105,7 @@ def dashify_headline(line): | ||||
| 
 | ||||
|     return [stripped_wspace, dashified, level] | ||||
| 
 | ||||
| 
 | ||||
| def tag_and_collect(lines, id_tag=True, back_links=False, exclude_h=None): | ||||
| def tagAndCollect(lines, id_tag=True, back_links=False, exclude_h=None): | ||||
|     """ | ||||
|     Gets headlines from the markdown document and creates anchor tags. | ||||
| 
 | ||||
| @@ -118,9 +138,9 @@ def tag_and_collect(lines, id_tag=True, back_links=False, exclude_h=None): | ||||
|         saw_headline = False | ||||
| 
 | ||||
|         orig_len = len(l) | ||||
|         l = l.lstrip() | ||||
|         l_stripped = l.lstrip() | ||||
| 
 | ||||
|         if l.startswith(('# ', '## ', '### ', '#### ', '##### ', '###### ')): | ||||
|         if l_stripped.startswith(('# ', '## ', '### ', '#### ', '##### ', '###### ')): | ||||
| 
 | ||||
|             # comply with new markdown standards | ||||
| 
 | ||||
| @@ -131,7 +151,7 @@ def tag_and_collect(lines, id_tag=True, back_links=False, exclude_h=None): | ||||
|             if len(l) - len(l.lstrip('#')) > 6: | ||||
|                 continue | ||||
|             # headers can be indented by at most 3 spaces: | ||||
|             if orig_len - len(l) > 3: | ||||
|             if orig_len - len(l_stripped) > 3: | ||||
|                 continue | ||||
| 
 | ||||
|             # ignore empty headers | ||||
| @@ -139,7 +159,7 @@ def tag_and_collect(lines, id_tag=True, back_links=False, exclude_h=None): | ||||
|                 continue | ||||
| 
 | ||||
|             saw_headline = True | ||||
|             dashified = dashify_headline(l) | ||||
|             dashified = dashifyHeadline(l) | ||||
| 
 | ||||
|             if not exclude_h or not dashified[-1] in exclude_h: | ||||
|                 if id_tag: | ||||
| @@ -153,8 +173,7 @@ def tag_and_collect(lines, id_tag=True, back_links=False, exclude_h=None): | ||||
|             out_contents.append('[[back to top](#table-of-contents)]') | ||||
|     return out_contents, headlines | ||||
| 
 | ||||
| 
 | ||||
| def positioning_headlines(headlines): | ||||
| def positioningHeadlines(headlines): | ||||
|     """ | ||||
|     Strips unnecessary whitespaces/tabs if first header is not left-aligned | ||||
|     """ | ||||
| @@ -168,11 +187,10 @@ def positioning_headlines(headlines): | ||||
|             row[-1] -= 1 | ||||
|     return headlines | ||||
| 
 | ||||
| 
 | ||||
| def create_toc(headlines, hyperlink=True, top_link=False, no_toc_header=False): | ||||
| 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 tag_and_collect function. | ||||
|     that was returned by the tagAndCollect function. | ||||
| 
 | ||||
|     Keyword Arguments: | ||||
|         headlines: list of lists | ||||
| @@ -192,29 +210,28 @@ def create_toc(headlines, hyperlink=True, top_link=False, no_toc_header=False): | ||||
|     if not no_toc_header: | ||||
|         if top_link: | ||||
|             processed.append('<a class="mk-toclify" id="table-of-contents"></a>\n') | ||||
|         processed.append('# Table of Contents') | ||||
|         processed.append(contentTitle) | ||||
| 
 | ||||
|     for line in headlines: | ||||
|         if hyperlink: | ||||
|             item = '%s- [%s](#%s)' % ((line[2]-1)*'    ', line[0], line[1]) | ||||
|             item = '[%s](#%s)  ' % (line[0], line[1]) | ||||
|         else: | ||||
|             item = '%s- %s' % ((line[2]-1)*'    ', line[0]) | ||||
|         processed.append(item) | ||||
|     processed.append('\n') | ||||
|     return processed | ||||
| 
 | ||||
| 
 | ||||
| def build_markdown(toc_headlines, body, spacer=0, placeholder=None): | ||||
| 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 create_toc function. | ||||
|             as created by the createToc function. | ||||
|         body: contents of the Markdown file including | ||||
|             ID-anchor tags as returned by the | ||||
|             tag_and_collect function. | ||||
|             tagAndCollect function. | ||||
|         spacer: Adds vertical space after the table | ||||
|             of contents. Height in pixels. | ||||
|         placeholder: If a placeholder string is provided, the placeholder | ||||
| @@ -228,17 +245,17 @@ def build_markdown(toc_headlines, body, spacer=0, placeholder=None): | ||||
|     else: | ||||
|         toc_markdown = "\n".join(toc_headlines) | ||||
| 
 | ||||
|     body_markdown = "\n".join(body).strip() | ||||
| 
 | ||||
|     if placeholder: | ||||
|         body_markdown = "\n".join(body) | ||||
|         markdown = body_markdown.replace(placeholder, toc_markdown) | ||||
|     else: | ||||
|         markdown = toc_markdown + body_markdown | ||||
|         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 output_markdown(markdown_cont, output_file): | ||||
| def outputMarkdown(markdown_cont, output_file): | ||||
|     """ | ||||
|     Writes to an output file if `outfile` is a valid path. | ||||
| 
 | ||||
| @@ -247,11 +264,17 @@ def output_markdown(markdown_cont, output_file): | ||||
|         with open(output_file, 'w') as out: | ||||
|             out.write(markdown_cont) | ||||
| 
 | ||||
| 
 | ||||
| def markdown_toclify(input_file, output_file=None, github=False, | ||||
|                      back_to_top=False, nolink=False, | ||||
|                      no_toc_header=False, spacer=0, placeholder=None, | ||||
|                      exclude_h=None): | ||||
| 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 | ||||
| @@ -262,6 +285,9 @@ def markdown_toclify(input_file, output_file=None, github=False, | ||||
|       output_file: str (defaul: 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. | ||||
| 
 | ||||
| @@ -287,114 +313,134 @@ def markdown_toclify(input_file, output_file=None, github=False, | ||||
| 
 | ||||
|     Returns | ||||
|     ----------- | ||||
|     cont: str | ||||
|       Markdown contents including the TOC. | ||||
|     changed: Boolean | ||||
|       True if the file has been updated, False otherwise. | ||||
| 
 | ||||
|     """ | ||||
|     raw_contents = read_lines(input_file) | ||||
|     cleaned_contents = remove_lines(raw_contents, remove=('[[back to top]', '<a class="mk-toclify"')) | ||||
|     processed_contents, raw_headlines = tag_and_collect( | ||||
|                                             cleaned_contents, | ||||
|                                             id_tag=not github, | ||||
|                                             back_links=back_to_top, | ||||
|                                             exclude_h=exclude_h, | ||||
|                                             ) | ||||
|     cleaned_contents = removeLines( | ||||
|         removeToC(readLines(input_file)), | ||||
|         remove=('[[back to top]', '<a class="mk-toclify"')) | ||||
| 
 | ||||
|     leftjustified_headlines = positioning_headlines(raw_headlines) | ||||
|     processed_headlines = create_toc(leftjustified_headlines, | ||||
|                                      hyperlink=not nolink, | ||||
|                                      top_link=not nolink and not github, | ||||
|                                      no_toc_header=no_toc_header) | ||||
|     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 = build_markdown(toc_headlines=processed_headlines, | ||||
|                           body=processed_contents, | ||||
|                           spacer=spacer, | ||||
|                           placeholder=placeholder) | ||||
|     cont = buildMarkdown( | ||||
|         toc_headlines=processed_headlines, | ||||
|         body=processed_contents, | ||||
|         spacer=spacer, | ||||
|         placeholder=placeholder) | ||||
| 
 | ||||
|     if output_file: | ||||
|         output_markdown(cont, output_file) | ||||
|     return cont | ||||
|         outputMarkdown(cont, output_file) | ||||
| 
 | ||||
| def isReleaseNotes(f): | ||||
|     return os.path.basename(f) == releaseNotesName | ||||
| 
 | ||||
| def commandline(): | ||||
| 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='Python script that inserts a table of contents\n'\ | ||||
|                     'into markdown documents and creates the required internal links.', | ||||
|             epilog="""    Example: | ||||
|     markdown-toclify.py ~/Desktop/input.md -o ~/Desktop/output.md | ||||
|         description='Add or update table of contents in markdown documents.', | ||||
|         epilog="""""", | ||||
|         formatter_class=argparse.RawTextHelpFormatter) | ||||
| 
 | ||||
|     For more information about how internal links in | ||||
|     HTML and Markdown documents work | ||||
|     please see: | ||||
|     "Creating a table of contents with internal | ||||
|      links in IPython Notebooks and Markdown documents" | ||||
|     (http://sebastianraschka.com/Articles/2014_ipython_internal_links.html) | ||||
|     parser.add_argument( | ||||
|         'Input', | ||||
|         metavar='file', | ||||
|         type=str, | ||||
|         nargs=argparse.REMAINDER, | ||||
|         help='files to process, at default: docs/*.md') | ||||
| 
 | ||||
|     Updates for this script will be available at | ||||
|     https://github.com/rasbt/markdown-toclify | ||||
|     parser.add_argument( | ||||
|         '-v', '--verbose', | ||||
|         action='store_true', | ||||
|         help='report the name of the file being processed') | ||||
| 
 | ||||
|     """, | ||||
|             formatter_class=argparse.RawTextHelpFormatter | ||||
|     ) | ||||
|     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 [{deflt}]'.format(deflt=minTocEntries)) | ||||
| 
 | ||||
|     parser.add_argument('InputFile', | ||||
|                         metavar='input.md', | ||||
|                         help='path to the Markdown input file') | ||||
|     parser.add_argument('-o', '--output', | ||||
|                         metavar='output.md', | ||||
|                         default=None, | ||||
|                         help='path to the Markdown output file') | ||||
|     parser.add_argument('-b', '--back_to_top', | ||||
|                         action='store_true', | ||||
|                         help='add [back to top] links.') | ||||
|     parser.add_argument('-g', '--github', | ||||
|                         action='store_true', | ||||
|                         help='omits id-anchor tags (recommended for GitHub)') | ||||
|     parser.add_argument('-s', '--spacer', | ||||
|                         default=0, | ||||
|                         type=int, | ||||
|                         metavar='pixels', | ||||
|                         help='add horizontal space (in pixels) after the table of contents') | ||||
|     parser.add_argument('-n', '--nolink', | ||||
|                         action='store_true', | ||||
|                         help='create the table of contents without internal links') | ||||
|     parser.add_argument('-e', '--exclude_h', | ||||
|                         type=str, | ||||
|                         default='', | ||||
|                         help='exclude eading levels, e.g., "2,3" to exclude all level 2 and 3 headings') | ||||
|     parser.add_argument('--placeholder', | ||||
|                         type=str, | ||||
|                         help='inserts TOC at the placeholder string instead of inserting it on top of the document') | ||||
|     parser.add_argument('--no_toc_header', | ||||
|                         action='store_true', | ||||
|                         help='suppresses the Table of Contents header') | ||||
|     parser.add_argument('-v', '--version', | ||||
|                         action='version', | ||||
|                         version='%s' % __version__) | ||||
|     parser.add_argument( | ||||
|         '--remove-toc', | ||||
|         action='store_const', | ||||
|         dest='minTocEntries', | ||||
|         const=99, | ||||
|         help='remove all tables of contents') | ||||
| 
 | ||||
|     args = parser.parse_args() | ||||
| 
 | ||||
|     if args.exclude_h: | ||||
|         exclude_h = [int(i) for i in args.exclude_h.split(',')] | ||||
|     paths = args.Input if len(args.Input) > 0 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: | ||||
|         exclude_h = None | ||||
| 
 | ||||
|     cont = markdown_toclify(input_file=args.InputFile, | ||||
|                             output_file=args.output, | ||||
|                             github=args.github, | ||||
|                             back_to_top=args.back_to_top, | ||||
|                             nolink=args.nolink, | ||||
|                             no_toc_header=args.no_toc_header, | ||||
|                             spacer=args.spacer, | ||||
|                             placeholder=args.placeholder, | ||||
|                             exclude_h=exclude_h) | ||||
| 
 | ||||
|     if not args.output: | ||||
|         print(cont) | ||||
|         print( "No table of contents added or updated" ) | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     commandline() | ||||
|     updateDocumentToCMain() | ||||
| 
 | ||||
| # end of file | ||||
		Reference in New Issue
	
	Block a user