diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..62104894 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Report a parsing error, unexpected output and other bugs +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Attach a minimal markdown snippet that causes the bug to occur. This should be placed inside a fenced code block to escape GitHub's formatting. + +If your snippet contains fenced code blocks then you can escape them by adding more backticks to the enclosing block. See the [this GitHub article](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#fenced-code-blocks) for an example. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Debug info** +Version of library being used: + +Any extras being used: + +**Additional context** +Add any other context about the problem here. diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml new file mode 100644 index 00000000..40ce721a --- /dev/null +++ b/.github/workflows/python.yaml @@ -0,0 +1,32 @@ +name: PythonCI +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + os: + - ubuntu-latest + - macos-latest + - windows-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[all] + - name: Test + run: | + make testone + - name: Test ReDoS + run: | + make testredos diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 543f2ffb..00000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: python -python: - - "pypy" - - "3.5" - - "3.6" - - "3.7" - - "3.8" -# command to install dependencies -install: pip install Pygments>=2.5.2 -# command to run tests -script: make testone diff --git a/CHANGES.md b/CHANGES.md index 2cb9fb3a..718fb9ed 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,223 @@ # python-markdown2 Changelog -## python-markdown2 2.3.10 (not yet released) +## python-markdown2 2.5.6 (not yet released) + +- [pull #687] Fix AssertionError hashing HTML blocks spread over multiple lines (#686) +- [pull #692] Fix XSS from code spans in link titles (#691) +- [pull #695] Fix XSS issue from incomplete tags with no attributes (#694) +- [pull #700] Fix XSS from code spans in image alt text (#699) +- [pull #701] Allow boolean attribute syntax in `markdown-in-html` extra +- [pull #704] Fix XSS from smuggling spans into image attributes (#702, #703) + + +## python-markdown2 2.5.5 + +- [pull #639] Fix middle-word-em interfering with strongs (#637) +- [pull #640] Fix code friendly extra stopping other syntax being processed (#638) +- [pull #644] Fix a number of em/strong issues (#641, #642, #643) +- [pull #659] Fix a number of safemode issues (#647) +- [pull #665] Rewrite emphasis and strong processing to be more GFM compliant +- [pull #672] Fix nested footnote references (#664) +- [pull #681] Forbid square brackets in reference link IDs + + +## python-markdown2 2.5.4 + +- [pull #617] Add MarkdownFileLinks extra (#528) +- [pull #622] Add missing block tags to regex (#620) +- [pull #623] Don't escape plus signs in URLs (#621) +- [pull #626] Fix XSS when encoding incomplete tags (#625) +- [pull #628] Fix TypeError in MiddleWordEm extra when options was None (#627) +- [pull #630] Fix nbsp breaking tables (#629) +- [pull #634] Fix ReDoS in HTML tokenizer regex (#633) + + +## python-markdown2 2.5.3 + +- [pull #616] make tables without body gfm compatible + + +## python-markdown2 2.5.2 + +- [pull #605] Add support for Python 3.13, drop EOL 3.8 +- [pull #607] Fix `middle-word-em` extra preventing strongs from being recognized (#606) +- [pull #609] Add option to output to file in CLI (#608) +- [pull #612] Fix footnote labels appearing out-of-order (#536) +- [pull #613] Fix smarty pants extra not triggering when it should (#611) + + +## python-markdown2 2.5.1 + +- [pull #590] Fix underscores within bold text getting emphasized (#589) +- [pull #591] Add Alerts extra +- [pull #595] Fix img alt text being processed as markdown (#594) +- [pull #598] Add `link-shortrefs` extra (#597) +- [pull #600] Use urandom for SECRET_SALT +- [pull #602] Fix XSS issue in safe mode (#601) +- [pull #604] Fix XSS injection in image URLs (#603) + + +## python-markdown2 2.5.0 + +- [pull #519] Add support for custom extras +- [pull #519] Drop Python 3.5 support +- [pull #568] Add `prepend` arg to toc extra (#397) +- [pull #569] Process HTML comments as markdown in 'escape' safe mode +- [pull #570] Fix syntax warnings in test suite +- [pull #572] Process inline tags as HTML blocks when they span multiple lines (#571) +- [pull #573] Add new LaTeX Extra +- [pull #576] Fix `html`, `head` and `body` tags being wrapped in `

` tags (#575) +- [pull #578] Graceful handling of broken lists when cuddled-lists extra is enabled +- [pull #581] Add type hints (#562) +- [pull #581] Drop Python 3.6 and 3.7 support +- [pull #582] Fix fenced code blocks breaking lists (#580) +- [pull #586] Fix #583 by tweaking incomplete tag regex +- [pull #587] Fix AssertionError on malformed HTML (#584) + + +## python-markdown2 2.4.13 + +- [pull #559] Allow cuddled tables (#557) +- [pull #560] Fix `markdown-in-html` not always splitting HTML tags into separate lines (#558) +- [pull #564] Fix incomplete comments in safe mode not being escaped (#563) +- [pull #566] Fix crash in `markdown-in-html` extra (#565) + + +## python-markdown2 2.4.12 + +- [pull #547] Update `markdown-in-html` extra to handle markdown on same line as HTML (#546) +- [pull #550] Fix tables with trailing whitespace not being recognized (#549) +- [pull #545] Fix multiple instances of strong emphasis (`**`) in one line (#541) +- [pull #556] Fix incorrect parsing of links after square brackets (#552) + +## python-markdown2 2.4.11 + +- [pull #524] Fix angles being escaped in style blocks (issue #523) +- [pull #527] Fix base64 images being corrupted in safe mode (issue #526) +- [pull #529] Add `breaks` extra with ability to hard break on backslashes (issue #525) +- [pull #532] Fix #493 persisting when `code-friendly` extra enabled +- [pull #535] Update `_slugify` to use utf-8 encoding (issue #534) +- [pull #536] Maintain order of appearance in footnotes +- [pull #538] Include HTML headers in TOC +- [pull #540] Add mechanism to prevent header ID counter resetting (issue #530) + +## python-markdown2 2.4.10 + +- [pull #520] Allow more relative links in safe mode (issue #517) +- [pull #521] Always restore hashed HTML blocks (issue #185) +- [pull #522] Add `middle-word-em` extra + + +## python-markdown2 2.4.9 + +- [pull #500] Add `` tag to html-classes extra +- [pull #501] Fix link patterns extra matching against internal hashes +- [pull #502] Replace deprecated `optparse` with `argparse` +- [pull #506] Fix `_uniform_outdent` failing with empty strings (issue #505) +- [pull #509] Fix HTML elements not unhashing correctly (issue 508) +- [pull #511] Remove deprecated `imp` module (issue #510) +- [pull #512] Allow link patterns to be passed via extras dict +- [pull #513] Fix relative links not working in safe mode (issue #254) + + +## python-markdown2 2.4.8 + +- [pull #499] Fix images not being procesed correctly (#498) + + +## python-markdown2 2.4.7 + +- [pull #483] Fix hashing nested HTML blocks +- [pull #486] Fix backslash being unable to escape raw HTML tags +- [pull #482] Add support for telegram spoiler in extras +- [pull #485] mermaid support +- [pull #487] Fix escaping ampersands in hrefs +- [pull #490] Fix indented codeblocks inside fences (#489) +- [pull #490] Remove `code-color` extra + + +## python-markdown2 2.4.6 + +- [pull #477] Feature wavedrom support +- [pull #480] Fix mixing ordered and un-ordered lists combining into single list type + + +## python-markdown2 2.4.5 + +- [pull #466] Add optional dependencies to `setup.py` + + +## python-markdown2 2.4.4 + +- [pull #439] Fix TypeError if html-classes extra is None +- [pull #441] Remove Python2 support +- [pull #445] Replace `` with `` in strike extra +- [pull #446] Fix link patterns extra applying within links +- [pull #443] create proper entry point +- [pull #449] Codespans inside link text issue344 +- [pull #451] Underline and HTML comments +- [pull #453] Links with brackets +- [pull #454] Fix emacs local variable one-liners +- [pull #457] Example of the current mixed-paragraph mode behavior in lists +- [pull #455] Fix code block indentation in lists +- [pull #434] Fix filter bypass leading to XSS (#362) +- [pull #464] Fix html-classes extra not applying to code spans +- [pull #462] Fix pygments block matching +- [pull #462] Fix pyshell blocks in blockquotes +- [pull #463] Fix multilevel lists +- [pull #468] Remove `_uniform_outdent_limit` function +- [pull #470] Add support for ordered lists that don't start at 1. (#469) +- [pull #472] Fix `AssertionError` with lazy numbered lists (issue #471) +- [pull #475] Add `

" % indent()) - lines.append('%s
  • %s' % ( + lines.append('{}
  • {}'.format( indent(), id, name)) while len(h_stack) > 1: h_stack.pop() @@ -2336,18 +4382,18 @@ def indent(): return '\n'.join(lines) + '\n' -class UnicodeWithAttrs(unicode): +class UnicodeWithAttrs(str): """A subclass of unicode used for the return value of conversion to possibly attach some attributes. E.g. the "toc_html" attribute when the "toc" extra is used. """ - metadata = None - toc_html = None + metadata: Optional[dict[str, str]] = None + toc_html: Optional[str] = None ## {{{ http://code.activestate.com/recipes/577257/ (r1) _slugify_strip_re = re.compile(r'[^\w\s-]') _slugify_hyphenate_re = re.compile(r'[-\s]+') -def _slugify(value): +def _slugify(value: str) -> str: """ Normalizes string, converts to lowercase, removes non-alpha characters, and converts spaces to hyphens. @@ -2355,15 +4401,14 @@ def _slugify(value): From Django's "django/template/defaultfilters.py". """ import unicodedata - value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode() + value = unicodedata.normalize('NFKD', value).encode('utf-8', 'ignore').decode() value = _slugify_strip_re.sub('', value).strip().lower() return _slugify_hyphenate_re.sub('-', value) ## end of http://code.activestate.com/recipes/577257/ }}} # From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549 -def _curry(*args, **kwargs): - function, args = args[0], args[1:] +def _curry(function: Callable, *args, **kwargs) -> Callable: def result(*rest, **kwrest): combined = kwargs.copy() combined.update(kwrest) @@ -2372,7 +4417,7 @@ def result(*rest, **kwrest): # Recipe: regex_from_encoded_pattern (1.0) -def _regex_from_encoded_pattern(s): +def _regex_from_encoded_pattern(s: str) -> re.Pattern[str]: """'foo' -> re.compile(re.escape('foo')) '/foo/' -> re.compile('foo') '/foo/i' -> re.compile('foo', re.I) @@ -2402,7 +4447,7 @@ def _regex_from_encoded_pattern(s): # Recipe: dedent (0.1.2) -def _dedentlines(lines, tabsize=8, skip_first_line=False): +def _dedentlines(lines: list[str], tabsize: int = 8, skip_first_line: bool = False) -> list[str]: """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines "lines" is a list of lines to dedent. @@ -2420,7 +4465,8 @@ def _dedentlines(lines, tabsize=8, skip_first_line=False): % (tabsize, skip_first_line)) margin = None for i, line in enumerate(lines): - if i == 0 and skip_first_line: continue + if i == 0 and skip_first_line: + continue indent = 0 for ch in line: if ch == ' ': @@ -2433,16 +4479,19 @@ def _dedentlines(lines, tabsize=8, skip_first_line=False): break else: continue # skip all-whitespace lines - if DEBUG: print("dedent: indent=%d: %r" % (indent, line)) + if DEBUG: + print("dedent: indent=%d: %r" % (indent, line)) if margin is None: margin = indent else: margin = min(margin, indent) - if DEBUG: print("dedent: margin=%r" % margin) + if DEBUG: + print("dedent: margin=%r" % margin) if margin is not None and margin > 0: for i, line in enumerate(lines): - if i == 0 and skip_first_line: continue + if i == 0 and skip_first_line: + continue removed = 0 for j, ch in enumerate(line): if ch == ' ': @@ -2450,7 +4499,8 @@ def _dedentlines(lines, tabsize=8, skip_first_line=False): elif ch == '\t': removed += tabsize - (removed % tabsize) elif ch in '\r\n': - if DEBUG: print("dedent: %r: EOL -> strip up to EOL" % line) + if DEBUG: + print("dedent: %r: EOL -> strip up to EOL" % line) lines[i] = lines[i][j:] break else: @@ -2472,7 +4522,7 @@ def _dedentlines(lines, tabsize=8, skip_first_line=False): return lines -def _dedent(text, tabsize=8, skip_first_line=False): +def _dedent(text: str, tabsize: int = 8, skip_first_line: bool = False) -> str: """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text "text" is the text to dedent. @@ -2483,12 +4533,12 @@ def _dedent(text, tabsize=8, skip_first_line=False): textwrap.dedent(s), but don't expand tabs to spaces """ - lines = text.splitlines(1) + lines = text.splitlines(True) _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line) return ''.join(lines) -class _memoized(object): +class _memoized: """Decorator that caches a function's return value each time it is called. If called later with the same arguments, the cached value is returned, and not re-evaluated. @@ -2515,7 +4565,7 @@ def __repr__(self): return self.func.__doc__ -def _xml_oneliner_re_from_tab_width(tab_width): +def _xml_oneliner_re_from_tab_width(tab_width: int) -> re.Pattern[str]: """Standalone XML processing instruction regex.""" return re.compile(r""" (?: @@ -2537,7 +4587,7 @@ def _xml_oneliner_re_from_tab_width(tab_width): _xml_oneliner_re_from_tab_width = _memoized(_xml_oneliner_re_from_tab_width) -def _hr_tag_re_from_tab_width(tab_width): +def _hr_tag_re_from_tab_width(tab_width: int) -> re.Pattern[str]: return re.compile(r""" (?: (?<=\n\n) # Starting after a blank line @@ -2557,7 +4607,7 @@ def _hr_tag_re_from_tab_width(tab_width): _hr_tag_re_from_tab_width = _memoized(_hr_tag_re_from_tab_width) -def _xml_escape_attr(attr, skip_single_quote=True): +def _xml_escape_attr(attr: str, skip_single_quote: bool = True) -> str: """Escape the given string for use in an HTML/XML tag attribute. By default this doesn't bother with escaping `'` to `'`, presuming that @@ -2574,7 +4624,7 @@ def _xml_escape_attr(attr, skip_single_quote=True): return escaped -def _xml_encode_email_char_at_random(ch): +def _xml_encode_email_char_at_random(ch: str) -> str: r = random() # Roughly 10% raw, 45% hex, 45% dec. # '@' *must* be encoded. I [John Gruber] insist. @@ -2588,22 +4638,31 @@ def _xml_encode_email_char_at_random(ch): return '&#%s;' % ord(ch) -def _html_escape_url(attr, safe_mode=False): - """Replace special characters that are potentially malicious in url string.""" +def _html_escape_url( + attr: str, + safe_mode: Union[_safe_mode, bool, None] = False, + charset: Optional[str] = None +): + """ + Replace special characters that are potentially malicious in url string. + + Args: + charset: don't escape characters from this charset. Currently the only + exception is for '+' when charset=='base64' + """ escaped = (attr .replace('"', '"') .replace('<', '<') .replace('>', '>')) if safe_mode: - escaped = escaped.replace('+', ' ') escaped = escaped.replace("'", "'") return escaped # ---- mainline -class _NoReflowFormatter(optparse.IndentedHelpFormatter): - """An optparse formatter that does NOT reflow the description.""" +class _NoReflowFormatter(argparse.RawDescriptionHelpFormatter): + """An argparse formatter that does NOT reflow the description.""" def format_description(self, description): return description or "" @@ -2619,38 +4678,46 @@ def main(argv=None): if not logging.root.handlers: logging.basicConfig() - usage = "usage: %prog [PATHS...]" - version = "%prog "+__version__ - parser = optparse.OptionParser(prog="markdown2", usage=usage, - version=version, description=cmdln_desc, - formatter=_NoReflowFormatter()) - parser.add_option("-v", "--verbose", dest="log_level", + parser = argparse.ArgumentParser( + prog="markdown2", description=cmdln_desc, usage='%(prog)s [PATHS...]', + formatter_class=_NoReflowFormatter + ) + parser.add_argument('--version', action='version', + version=f'%(prog)s {__version__}') + parser.add_argument('paths', nargs='*', + help=( + 'optional list of files to convert.' + 'If none are given, stdin will be used' + )) + parser.add_argument("-v", "--verbose", dest="log_level", action="store_const", const=logging.DEBUG, help="more verbose output") - parser.add_option("--encoding", + parser.add_argument("--encoding", help="specify encoding of text content") - parser.add_option("--html4tags", action="store_true", default=False, + parser.add_argument("--html4tags", action="store_true", default=False, help="use HTML 4 style for empty element tags") - parser.add_option("-s", "--safe", metavar="MODE", dest="safe_mode", + parser.add_argument("-s", "--safe", metavar="MODE", dest="safe_mode", help="sanitize literal HTML: 'escape' escapes " "HTML meta chars, 'replace' replaces with an " "[HTML_REMOVED] note") - parser.add_option("-x", "--extras", action="append", + parser.add_argument("-x", "--extras", action="append", help="Turn on specific extra features (not part of " "the core Markdown spec). See above.") - parser.add_option("--use-file-vars", + parser.add_argument("--use-file-vars", help="Look for and use Emacs-style 'markdown-extras' " "file var to turn on extras. See " "") - parser.add_option("--link-patterns-file", + parser.add_argument("--link-patterns-file", help="path to a link pattern file") - parser.add_option("--self-test", action="store_true", + parser.add_argument("--self-test", action="store_true", help="run internal self-tests (some doctests)") - parser.add_option("--compare", action="store_true", + parser.add_argument("--compare", action="store_true", help="run against Markdown.pl as well (for testing)") + parser.add_argument('--output', type=str, help='output to a file instead of stdout') parser.set_defaults(log_level=logging.INFO, compare=False, encoding="utf-8", safe_mode=None, use_file_vars=False) - opts, paths = parser.parse_args() + opts = parser.parse_args() + paths = opts.paths log.setLevel(opts.log_level) if opts.self_test: @@ -2678,8 +4745,10 @@ def main(argv=None): f = open(opts.link_patterns_file) try: for i, line in enumerate(f.readlines()): - if not line.strip(): continue - if line.lstrip().startswith("#"): continue + if not line.strip(): + continue + if line.lstrip().startswith("#"): + continue try: pat, href = line.rstrip().rsplit(None, 1) except ValueError: @@ -2692,7 +4761,7 @@ def main(argv=None): else: link_patterns = None - from os.path import join, dirname, abspath, exists + from os.path import abspath, dirname, exists, join markdown_pl = join(dirname(dirname(abspath(__file__))), "test", "Markdown.pl") if not paths: @@ -2701,21 +4770,16 @@ def main(argv=None): if path == '-': text = sys.stdin.read() else: - fp = codecs.open(path, 'r', opts.encoding) - text = fp.read() - fp.close() + with open(path, 'r', encoding=opts.encoding) as f: + text = f.read() if opts.compare: - from subprocess import Popen, PIPE + from subprocess import PIPE, Popen print("==== Markdown.pl ====") p = Popen('perl %s' % markdown_pl, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True) p.stdin.write(text.encode('utf-8')) p.stdin.close() perl_html = p.stdout.read().decode('utf-8') - if py3: - sys.stdout.write(perl_html) - else: - sys.stdout.write(perl_html.encode( - sys.stdout.encoding or "utf-8", 'xmlcharrefreplace')) + sys.stdout.write(perl_html) print("==== markdown2.py ====") html = markdown(text, html4tags=opts.html4tags, @@ -2723,11 +4787,11 @@ def main(argv=None): extras=extras, link_patterns=link_patterns, use_file_vars=opts.use_file_vars, cli=True) - if py3: - sys.stdout.write(html) + if opts.output: + with open(opts.output, 'w') as f: + f.write(html) else: - sys.stdout.write(html.encode( - sys.stdout.encoding or "utf-8", 'xmlcharrefreplace')) + sys.stdout.write(html) if extras and "toc" in extras: log.debug("toc_html: " + str(html.toc_html.encode(sys.stdout.encoding or "utf-8", 'xmlcharrefreplace'))) diff --git a/perf/gen_perf_cases.py b/perf/gen_perf_cases.py index ecd4c54e..33336c86 100755 --- a/perf/gen_perf_cases.py +++ b/perf/gen_perf_cases.py @@ -4,11 +4,9 @@ from os.path import * import sys import re -import datetime from glob import glob import operator import shutil -import codecs TMP = "tmp-" @@ -16,17 +14,17 @@ def gen_aspn_cases(limit=0): base_dir = TMP+'aspn-cases' if exists(base_dir): - print "'%s' exists, skipping" % base_dir - return + print("'%s' exists, skipping" % base_dir) + return os.makedirs(base_dir) sys.stdout.write("generate %s" % base_dir); sys.stdout.flush() recipes_path = expanduser("~/as/code.as.com/db/aspn/recipes.pprint") - recipe_dicts = eval(open(recipes_path).read()) + with open(recipes_path) as f: + recipe_dicts = eval(f.read()) for i, r in enumerate(recipe_dicts): sys.stdout.write('.'); sys.stdout.flush() - f = codecs.open(join(base_dir, "r%04d.text" % i), "w", "utf-8") - f.write(r["desc"]) - f.close() + with open(join(base_dir, "r%04d.text" % i), "w", encoding="utf-8") as f: + f.write(r["desc"]) for j, c in enumerate(sorted(r["comments"], key=operator.itemgetter("pub_date"))): @@ -37,10 +35,8 @@ def gen_aspn_cases(limit=0): headline += '.' headline = _markdown_from_aspn_html(headline).strip() text = "**" + headline + "** " + text - f = codecs.open(join(base_dir, "r%04dc%02d.text" % (i, j)), - 'w', "utf-8") - f.write(text) - f.close() + with open(join(base_dir, "r%04dc%02d.text" % (i, j)), 'w', encoding="utf-8") as f: + f.write(text) if limit and i >= limit: break @@ -49,10 +45,10 @@ def gen_aspn_cases(limit=0): def gen_test_cases(): base_dir = TMP+"test-cases" if exists(base_dir): - print "'%s' exists, skipping" % base_dir - return + print("'%s' exists, skipping" % base_dir) + return os.makedirs(base_dir) - print "generate %s" % base_dir + print("generate %s" % base_dir) for test_cases_dir in glob(join("..", "test", "*-cases")): for text_file in glob(join(test_cases_dir, "*.text")): shutil.copy(text_file, join(base_dir, basename(text_file))) @@ -70,8 +66,6 @@ def gen_test_cases(): i_pat = re.compile(r"<(i)>(.*?)", re.I) def _markdown_from_aspn_html(html): - from cgi import escape - markdown = html markdown = br_eol_pat.sub('\n', markdown) #
    EOL @@ -107,12 +101,12 @@ def _markdown_from_aspn_html(html): title = None escaped_href = href.replace('(', '\\(').replace(')', '\\)') if title is None: - replacement = '[%s](%s)' % (content, escaped_href) + replacement = '[{}]({})'.format(content, escaped_href) else: - replacement = '[%s](%s "%s")' % (content, escaped_href, + replacement = '[{}]({} "{}")'.format(content, escaped_href, title.replace('"', "'")) markdown = markdown[:start] + replacement + markdown[end:] - + markdown = markdown.replace(" ", ' ') #
     part 1: Pull out 
    -blocks and put in placeholders
    @@ -137,7 +131,7 @@ def _markdown_from_aspn_html(html):
             try:
                 idx = markdown.index(marker)
             except ValueError:
    -            print "marker: %r" % marker
    +            print("marker: %r" % marker)
                 raise
             if not markdown[:idx].strip():
                 #TODO: Correct this false diagnosis. Problem is not limited
    @@ -182,20 +176,20 @@ def _markdown_from_aspn_html(html):
     # Recipe: dedent (0.1.2)
     def _dedentlines(lines, tabsize=8, skip_first_line=False):
         """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines
    -    
    +
             "lines" is a list of lines to dedent.
             "tabsize" is the tab width to use for indent width calculations.
             "skip_first_line" is a boolean indicating if the first line should
                 be skipped for calculating the indent width and for dedenting.
                 This is sometimes useful for docstrings and similar.
    -    
    +
         Same as dedent() except operates on a sequence of lines. Note: the
         lines list is modified **in-place**.
         """
         DEBUG = False
    -    if DEBUG: 
    -        print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
    -              % (tabsize, skip_first_line)
    +    if DEBUG:
    +        print("dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
    +              % (tabsize, skip_first_line))
         indents = []
         margin = None
         for i, line in enumerate(lines):
    @@ -212,12 +206,12 @@ def _dedentlines(lines, tabsize=8, skip_first_line=False):
                     break
             else:
                 continue # skip all-whitespace lines
    -        if DEBUG: print "dedent: indent=%d: %r" % (indent, line)
    +        if DEBUG: print("dedent: indent=%d: %r" % (indent, line))
             if margin is None:
                 margin = indent
             else:
                 margin = min(margin, indent)
    -    if DEBUG: print "dedent: margin=%r" % margin
    +    if DEBUG: print("dedent: margin=%r" % margin)
     
         if margin is not None and margin > 0:
             for i, line in enumerate(lines):
    @@ -229,7 +223,7 @@ def _dedentlines(lines, tabsize=8, skip_first_line=False):
                     elif ch == '\t':
                         removed += tabsize - (removed % tabsize)
                     elif ch in '\r\n':
    -                    if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line
    +                    if DEBUG: print("dedent: %r: EOL -> strip up to EOL" % line)
                         lines[i] = lines[i][j:]
                         break
                     else:
    @@ -237,8 +231,8 @@ def _dedentlines(lines, tabsize=8, skip_first_line=False):
                                          "line %r while removing %d-space margin"
                                          % (ch, line, margin))
                     if DEBUG:
    -                    print "dedent: %r: %r -> removed %d/%d"\
    -                          % (line, ch, removed, margin)
    +                    print("dedent: %r: %r -> removed %d/%d"\
    +                          % (line, ch, removed, margin))
                     if removed == margin:
                         lines[i] = lines[i][j+1:]
                         break
    @@ -258,7 +252,7 @@ def _dedent(text, tabsize=8, skip_first_line=False):
             "skip_first_line" is a boolean indicating if the first line should
                 be skipped for calculating the indent width and for dedenting.
                 This is sometimes useful for docstrings and similar.
    -    
    +
         textwrap.dedent(s), but don't expand tabs to spaces
         """
         lines = text.splitlines(1)
    diff --git a/perf/perf.py b/perf/perf.py
    index ea6f10ea..ed8bd864 100755
    --- a/perf/perf.py
    +++ b/perf/perf.py
    @@ -10,7 +10,6 @@
     
     import os
     import sys
    -import timeit
     import time
     from os.path import *
     from glob import glob
    @@ -35,7 +34,7 @@ def time_markdown_py(cases_dir, repeat):
         for i in range(repeat):
             start = clock()
             for path in glob(join(cases_dir, "*.text")):
    -            f = open(path, 'r')
    +            f = open(path)
                 content = f.read()
                 f.close()
                 try:
    @@ -45,7 +44,7 @@ def time_markdown_py(cases_dir, repeat):
                     pass
             end = clock()
             times.append(end - start)
    -    print "  markdown.py: best of %d: %.3fs" % (repeat, min(times))
    +    print("  markdown.py: best of %d: %.3fs" % (repeat, min(times)))
     
     @hotshotit
     def hotshot_markdown2_py(cases_dir, repeat):
    @@ -60,13 +59,13 @@ def time_markdown2_py(cases_dir, repeat):
         for i in range(repeat):
             start = clock()
             for path in glob(join(cases_dir, "*.text")):
    -            f = open(path, 'r')
    +            f = open(path)
                 content = f.read()
                 f.close()
                 markdowner.convert(content)
             end = clock()
             times.append(end - start)
    -    print "  markdown2.py: best of %d: %.3fs" % (repeat, min(times))
    +    print("  markdown2.py: best of %d: %.3fs" % (repeat, min(times)))
     
     def time_markdown_pl(cases_dir, repeat):
         times = []
    @@ -75,7 +74,7 @@ def time_markdown_pl(cases_dir, repeat):
             os.system('perl time_markdown_pl.pl "%s"' % cases_dir)
             end = clock()
             times.append(end - start)
    -    print "  Markdown.pl: best of %d: %.3fs" % (repeat, min(times))
    +    print("  Markdown.pl: best of %d: %.3fs" % (repeat, min(times)))
     
     def time_all(cases_dir, repeat):
         time_markdown_pl(cases_dir, repeat=repeat)
    @@ -131,10 +130,10 @@ def main(args=sys.argv):
             if timer_name not in d:
                 raise ValueError("no '%s' timer function" % timer_name)
             timer = d[timer_name]
    -        print "Profile conversion of %s (plat=%s):" \
    -              % (os.path.join(cases_dir, "*.text"), sys.platform)
    +        print("Profile conversion of %s (plat=%s):" \
    +              % (os.path.join(cases_dir, "*.text"), sys.platform))
             timer(cases_dir, repeat=opts.repeat)
    -        print
    +        print()
             os.system("python show_stats.py %s.prof" % timer_name)
     
         else:
    @@ -145,8 +144,8 @@ def main(args=sys.argv):
             if timer_name not in d:
                 raise ValueError("no '%s' timer function" % timer_name)
             timer = d[timer_name]
    -        print "Time conversion of %s (plat=%s):" \
    -              % (os.path.join(cases_dir, "*.text"), sys.platform)
    +        print("Time conversion of %s (plat=%s):" \
    +              % (os.path.join(cases_dir, "*.text"), sys.platform))
             timer(cases_dir, repeat=opts.repeat)
         
     if __name__ == "__main__":
    diff --git a/perf/strip_cookbook_data.py b/perf/strip_cookbook_data.py
    index 0f4abed0..94ba3048 100644
    --- a/perf/strip_cookbook_data.py
    +++ b/perf/strip_cookbook_data.py
    @@ -1,11 +1,10 @@
    -
     from os.path import *
    -from pprint import pprint, pformat
    -import datetime
    +from pprint import pformat
     
     def doit():
         recipes_path = expanduser("recipes.pprint")
    -    recipe_dicts = eval(open(recipes_path).read())
    +    with open(recipes_path) as f:
    +        recipe_dicts = eval(f.read())
         for r in recipe_dicts:
             for key in r.keys():
                 if key not in ('desc', 'comments'):
    @@ -14,11 +13,10 @@ def doit():
                 for key in c.keys():
                     if key not in ('comment', 'title'):
                         del c[key]
    -    
    +
         f = open("stripped.pprint", 'w')
         f.write(pformat(recipe_dicts))
         f.close()
     
     
     doit()
    -
    diff --git a/perf/util.py b/perf/util.py
    index 05a22a0c..7fcc862f 100644
    --- a/perf/util.py
    +++ b/perf/util.py
    @@ -3,15 +3,7 @@
     
     """Perf utility functions"""
     
    -import os
    -from os.path import basename
     import sys
    -import md5
    -import re
    -import stat
    -import textwrap
    -import types
    -from pprint import pprint, pformat
     
     
     # Global dict for holding specific hotshot profilers
    @@ -38,14 +30,14 @@ def wrapper(*args, **kw):
                 return func(*args, **kw)
             finally:
                 total_time = clock() - start_time
    -            print "%s took %.3fs" % (func.func_name, total_time)
    +            print("{} took {:.3f}s".format(func.__name__, total_time))
         return wrapper
     
     def hotshotit(func):
         def wrapper(*args, **kw):
             import hotshot
             global hotshotProfilers
    -        prof_name = func.func_name+".prof"
    +        prof_name = func.__name__+".prof"
             profiler = hotshotProfilers.get(prof_name)
             if profiler is None:
                 profiler = hotshot.Profile(prof_name)
    diff --git a/sandbox/wiki.py b/sandbox/wiki.py
    index ac27199a..7da4a8bc 100644
    --- a/sandbox/wiki.py
    +++ b/sandbox/wiki.py
    @@ -1,9 +1,8 @@
    -
     import sys
     import re
    -from os.path import *
    +from os.path import dirname, abspath
     
    -sys.path.insert(0, dirname(dirname(abspath(__file__))))
    +sys.path.insert(0, dirname(dirname(abspath(__file__))) + '/lib')
     import markdown2
     
     wiki_page = """
    @@ -18,4 +17,4 @@
     ]
     processor = markdown2.Markdown(extras=["link-patterns"],
                                    link_patterns=link_patterns)
    -print processor.convert(wiki_page)
    +print(processor.convert(wiki_page))
    diff --git a/setup.cfg b/setup.cfg
    deleted file mode 100644
    index 5e409001..00000000
    --- a/setup.cfg
    +++ /dev/null
    @@ -1,2 +0,0 @@
    -[wheel]
    -universal = 1
    diff --git a/setup.py b/setup.py
    old mode 100644
    new mode 100755
    index 1d374829..05dba326
    --- a/setup.py
    +++ b/setup.py
    @@ -15,17 +15,13 @@
     classifiers = """\
     Development Status :: 5 - Production/Stable
     Intended Audience :: Developers
    -License :: OSI Approved :: MIT License
     Programming Language :: Python
    -Programming Language :: Python :: 2
    -Programming Language :: Python :: 2.6
    -Programming Language :: Python :: 2.7
     Programming Language :: Python :: 3
    -Programming Language :: Python :: 3.3
    -Programming Language :: Python :: 3.4
    -Programming Language :: Python :: 3.5
    -Programming Language :: Python :: 3.6
    -Programming Language :: Python :: 3.7
    +Programming Language :: Python :: 3.9
    +Programming Language :: Python :: 3.10
    +Programming Language :: Python :: 3.11
    +Programming Language :: Python :: 3.12
    +Programming Language :: Python :: 3.13
     Operating System :: OS Independent
     Topic :: Software Development :: Libraries :: Python Modules
     Topic :: Software Development :: Documentation
    @@ -33,7 +29,13 @@
     Topic :: Text Processing :: Markup :: HTML
     """
     
    -script = (sys.platform == "win32" and "lib\\markdown2.py" or "bin/markdown2")
    +extras_require = {
    +    "code_syntax_highlighting": ["pygments>=2.7.3"],
    +    "wavedrom": ["wavedrom"],
    +    "latex": ['latex2mathml; python_version>="3.8.1"'],
    +}
    +# nested listcomp to combine all optional extras into convenient "all" option
    +extras_require["all"] = [i for v in tuple(extras_require.values()) for i in v]
     
     setup(
         name="markdown2",
    @@ -47,9 +49,15 @@
         platforms=["any"],
         py_modules=["markdown2"],
         package_dir={"": "lib"},
    -    scripts=[script],
    +    entry_points={
    +        "console_scripts": [
    +            "markdown2 = markdown2:main"
    +        ]
    +    },
         description="A fast and complete Python implementation of Markdown",
    -    classifiers=filter(None, classifiers.split("\n")),
    +    python_requires=">=3.9, <4",
    +    extras_require=extras_require,
    +    classifiers=classifiers.strip().split("\n"),
         long_description="""markdown2: A fast and complete Python implementation of Markdown.
     
     Markdown is a text-to-HTML filter; it translates an easy-to-read /
    diff --git a/test/markdown.py b/test/markdown.py
    index 07334a81..0e99f6d7 100644
    --- a/test/markdown.py
    +++ b/test/markdown.py
    @@ -29,16 +29,17 @@
     """
     
     
    -import re, sys, os, random, codecs
    +from pathlib import Path
    +import re, sys
     
     # Set debug level: 3 none, 2 critical, 1 informative, 0 all
    -(VERBOSE, INFO, CRITICAL, NONE) = range(4)
    +(VERBOSE, INFO, CRITICAL, NONE) = list(range(4))
     
     MESSAGE_THRESHOLD = CRITICAL
     
     def message(level, text) :
         if level >= MESSAGE_THRESHOLD :
    -        print text
    +        print(text)
     
     
     # --------------- CONSTANTS YOU MIGHT WANT TO MODIFY -----------------
    @@ -48,9 +49,9 @@ def message(level, text) :
     SMART_EMPHASIS = 1        # this_or_that does not become thisorthat
     HTML_REMOVED_TEXT = "[HTML_REMOVED]" # text used instead of HTML in safe mode
     
    -RTL_BIDI_RANGES = ( (u'\u0590', u'\u07FF'),
    +RTL_BIDI_RANGES = ( ('\u0590', '\u07FF'),
                         # from Hebrew to Nko (includes Arabic, Syriac and Thaana)
    -                    (u'\u2D30', u'\u2D7F'),
    +                    ('\u2D30', '\u2D7F'),
                         # Tifinagh
                         )
     
    @@ -62,9 +63,9 @@ def message(level, text) :
     # 0780-07BF - Thaana
     # 07C0-07FF - Nko
     
    -BOMS = { 'utf-8' : (unicode(codecs.BOM_UTF8, "utf-8"), ),
    -         'utf-16' : (unicode(codecs.BOM_UTF16_LE, "utf-16"),
    -                     unicode(codecs.BOM_UTF16_BE, "utf-16")),
    +BOMS = { 'utf-8' : (str(codecs.BOM_UTF8, "utf-8"), ),
    +         'utf-16' : (str(codecs.BOM_UTF16_LE, "utf-16"),
    +                     str(codecs.BOM_UTF16_BE, "utf-16")),
              #'utf-32' : (unicode(codecs.BOM_UTF32_LE, "utf-32"),
              #            unicode(codecs.BOM_UTF32_BE, "utf-32")),
              }
    @@ -81,7 +82,7 @@ def removeBOM(text, encoding):
     # and uses the actual name of the executable called.)
     
     EXECUTABLE_NAME_FOR_USAGE = "python markdown.py"
    -                    
    +
     
     # --------------- CONSTANTS YOU _SHOULD NOT_ HAVE TO CHANGE ----------
     
    @@ -116,7 +117,7 @@ def is_block_level (tag) :
                                          (re.compile(">"), ">"),
                                          (re.compile("\""), """)]
     
    -ENTITY_NORMALIZATION_EXPRESSIONS_SOFT = [ (re.compile("&(?!\#)"), "&"),
    +ENTITY_NORMALIZATION_EXPRESSIONS_SOFT = [ (re.compile(r"&(?!\#)"), "&"),
                                          (re.compile("<"), "<"),
                                          (re.compile(">"), ">"),
                                          (re.compile("\""), """)]
    @@ -128,7 +129,7 @@ def getBidiType(text) :
     
         ch = text[0]
     
    -    if not isinstance(ch, unicode) or not ch.isalpha():
    +    if not isinstance(ch, str) or not ch.isalpha():
             return None
     
         else :
    @@ -306,26 +307,26 @@ def toxml(self):
                 childBuffer += "/>"
     
     
    -            
    +
             buffer += "<" + self.nodeName
     
             if self.nodeName in ['p', 'li', 'ul', 'ol',
                                  'h1', 'h2', 'h3', 'h4', 'h5', 'h6'] :
     
    -            if not self.attribute_values.has_key("dir"):
    +            if "dir" not in self.attribute_values:
                     if self.bidi :
                         bidi = self.bidi
                     else :
                         bidi = self.doc.bidi
    -                    
    +
                     if bidi=="rtl" :
                         self.setAttribute("dir", "rtl")
    -        
    +
             for attr in self.attributes :
                 value = self.attribute_values[attr]
                 value = self.doc.normalizeEntities(value,
                                                    avoidDoubleNormalizing=True)
    -            buffer += ' %s="%s"' % (attr, value)
    +            buffer += ' {}="{}"'.format(attr, value)
     
     
             # Now let's actually append the children
    @@ -345,7 +346,7 @@ class TextNode :
         attrRegExp = re.compile(r'\{@([^\}]*)=([^\}]*)}') # {@id=123}
     
         def __init__ (self, text) :
    -        self.value = text        
    +        self.value = text
     
         def attributeCallback(self, match) :
     
    @@ -359,7 +360,7 @@ def toxml(self) :
             text = self.value
     
             self.parent.setBidi(getBidiType(text))
    -        
    +
             if not text.startswith(HTML_PLACEHOLDER_PREFIX):
                 if self.parent.nodeName == "p" :
                     text = text.replace("\n", "\n   ")
    @@ -479,7 +480,7 @@ def run (self, lines) :
     
     class HtmlBlockPreprocessor (Preprocessor):
         """Removes html blocks from self.lines"""
    -    
    +
         def _get_left_tag(self, block):
             return block[1:].replace(">", " ", 1).split()[0].lower()
     
    @@ -488,7 +489,7 @@ def _get_right_tag(self, left_tag, block):
             return block.rstrip()[-len(left_tag)-2:-1].lower()
     
         def _equal_tags(self, left_tag, right_tag):
    -        
    +
             if left_tag in ['?', '?php', 'div'] : # handle PHP, etc.
                 return True
             if ("/" + left_tag) == right_tag:
    @@ -504,18 +505,18 @@ def _equal_tags(self, left_tag, right_tag):
         def _is_oneliner(self, tag):
             return (tag in ['hr', 'hr/'])
     
    -    
    +
         def run (self, lines) :
     
             new_blocks = []
             text = "\n".join(lines)
             text = text.split("\n\n")
    -        
    +
             items = []
             left_tag = ''
             right_tag = ''
             in_tag = False # flag
    -        
    +
             for block in text:
                 if block.startswith("\n") :
                     block = block[1:]
    @@ -523,7 +524,7 @@ def run (self, lines) :
                 if not in_tag:
     
                     if block.startswith("<"):
    -                    
    +
                         left_tag = self._get_left_tag(block)
                         right_tag = self._get_right_tag(left_tag, block)
     
    @@ -535,13 +536,13 @@ def run (self, lines) :
                         if self._is_oneliner(left_tag):
                             new_blocks.append(block.strip())
                             continue
    -                        
    +
                         if block[1] == "!":
                             # is a comment block
                             left_tag = "--"
                             right_tag = self._get_right_tag(left_tag, block)
                             # keep checking conditions below and maybe just append
    -                        
    +
                         if block.rstrip().endswith(">") \
                             and self._equal_tags(left_tag, right_tag):
                             new_blocks.append(
    @@ -557,9 +558,9 @@ def run (self, lines) :
     
                 else:
                     items.append(block.strip())
    -                
    +
                     right_tag = self._get_right_tag(left_tag, block)
    -                
    +
                     if self._equal_tags(left_tag, right_tag):
                         # if find closing tag
                         in_tag = False
    @@ -570,7 +571,7 @@ def run (self, lines) :
             if items :
                 new_blocks.append(self.stash.store('\n\n'.join(items)))
                 new_blocks.append('\n')
    -            
    +
             return "\n\n".join(new_blocks).split("\n")
     
     HTML_BLOCK_PREPROCESSOR = HtmlBlockPreprocessor()
    @@ -672,7 +673,7 @@ def run (self, lines) :
     LINK_ANGLED_RE = BRK + r'\s*\(<([^\)]*)>\)'      # [text]()
     IMAGE_LINK_RE = r'\!' + BRK + r'\s*\(([^\)]*)\)' # ![alttxt](http://x.com/)
     REFERENCE_RE = BRK+ r'\s*\[([^\]]*)\]'           # [Google][3]
    -IMAGE_REFERENCE_RE = r'\!' + BRK + '\s*\[([^\]]*)\]' # ![alt text][2]
    +IMAGE_REFERENCE_RE = r'\!' + BRK + r'\s*\[([^\]]*)\]' # ![alt text][2]
     NOT_STRONG_RE = r'( \* )'                        # stand-alone * or _
     AUTOLINK_RE = r'<(http://[^>]*)>'                # 
     AUTOMAIL_RE = r'<([^> \!]*@[^> ]*)>'               # 
    @@ -788,7 +789,7 @@ def handleMatch(self, m, doc):
                 # we'll use "google" as the id
                 id = m.group(2).lower()
     
    -        if not self.references.has_key(id) : # ignore undefined refs
    +        if id not in self.references : # ignore undefined refs
                 return None
             href, title = self.references[id]
             text = m.group(2)
    @@ -1076,10 +1077,10 @@ def __init__(self, source=None,  # deprecated
                                      # inserted later
     
             self.textPostprocessors = [] # a footnote postprocessor will get
    -                                     # inserted later                                 
    +                                     # inserted later
     
             self.prePatterns = []
    -        
    +
     
             self.inlinePatterns = [ DOUBLE_BACKTICK_PATTERN,
                                     BACKTICK_PATTERN,
    @@ -1127,11 +1128,11 @@ def registerExtensions(self, extensions, configs) :
                             % (ext, extension_module_name) )
                 else :
     
    -                if configs.has_key(ext) :
    +                if ext in configs :
                         configs_for_ext = configs[ext]
                     else :
                         configs_for_ext = []
    -                extension = module.makeExtension(configs_for_ext)    
    +                extension = module.makeExtension(configs_for_ext)
                     extension.extendMarkdown(self, globals())
     
     
    @@ -1197,7 +1198,7 @@ def _transform(self):
                 else :
                     buffer.append(line)
             self._processSection(self.top_element, buffer)
    -        
    +
             #self._processSection(self.top_element, self.lines)
     
             # Not sure why I put this in but let's leave it for now.
    @@ -1362,7 +1363,7 @@ def _processList(self, parent_elem, lines, inList, listexpr, tag) :
     
                     # Check if the next non-blank line is still a part of the list
                     if ( RE.regExp['ul'].match(next) or
    -                     RE.regExp['ol'].match(next) or 
    +                     RE.regExp['ol'].match(next) or
                          RE.regExp['tabbed'].match(next) ):
                         # get rid of any white space in the line
                         items[item].append(line.strip())
    @@ -1486,10 +1487,10 @@ def _handleInlineWrapper (self, line) :
                 i = 0
     
                 while i < len(parts) :
    -                
    +
                     x = parts[i]
     
    -                if isinstance(x, (str, unicode)) :
    +                if isinstance(x, str) :
                         result = self._applyPattern(x, pattern)
     
                         if result :
    @@ -1502,11 +1503,11 @@ def _handleInlineWrapper (self, line) :
     
             for i in range(len(parts)) :
                 x = parts[i]
    -            if isinstance(x, (str, unicode)) :
    +            if isinstance(x, str) :
                     parts[i] = self.doc.createTextNode(x)
     
             return parts
    -        
    +
     
         def _handleInline(self,  line):
             """Transform a Markdown line with inline elements to an XHTML
    @@ -1532,7 +1533,7 @@ def _applyPattern(self, line, pattern) :
             """ Given a pattern name, this function checks if the line
             fits the pattern, creates the necessary elements, and returns
             back a list consisting of NanoDom elements and/or strings.
    -        
    +
             @param line: the text to be processed
             @param pattern: the pattern to be checked
     
    @@ -1560,30 +1561,30 @@ def _applyPattern(self, line, pattern) :
                 if not node.nodeName in ["code", "pre"] :
                     for child in node.childNodes :
                         if isinstance(child, TextNode):
    -                        
    +
                             result = self._handleInlineWrapper(child.value)
    -                        
    +
                             if result:
     
                                 if result == [child] :
                                     continue
    -                                
    +
                                 result.reverse()
                                 #to make insertion easier
     
                                 position = node.childNodes.index(child)
    -                            
    +
                                 node.removeChild(child)
     
                                 for item in result:
     
    -                                if isinstance(item, (str, unicode)):
    +                                if isinstance(item, str):
                                         if len(item) > 0:
                                             node.insertChild(position,
                                                  self.doc.createTextNode(item))
                                     else:
                                         node.insertChild(position, item)
    -                
    +
     
     
     
    @@ -1610,7 +1611,7 @@ def convert (self, source = None):
     
             self.source = removeBOM(self.source, self.encoding)
     
    -        
    +
             doc = self._transform()
             xml = doc.toxml()
     
    @@ -1623,7 +1624,7 @@ def convert (self, source = None):
                 html = self.htmlStash.rawHtmlBlocks[i]
                 if self.safeMode :
                     html = HTML_REMOVED_TEXT
    -                
    +
                 xml = xml.replace("

    %s\n

    " % (HTML_PLACEHOLDER % i), html + "\n") xml = xml.replace(HTML_PLACEHOLDER % i, @@ -1642,7 +1643,7 @@ def convert (self, source = None): __str__ = convert # deprecated - will be changed in 1.7 to report # information about the MD instance - + toString = __str__ # toString() method is deprecated @@ -1675,16 +1676,15 @@ def markdownFromFile(input = None, if not encoding : encoding = "utf-8" - input_file = codecs.open(input, mode="r", encoding=encoding) - text = input_file.read() - input_file.close() + assert isinstance(input, (str, Path)), 'input path required' + with open(input, mode="r", encoding=encoding) as f: + text = f.read() new_text = markdown(text, extensions, encoding, safe_mode = safe) if output : - output_file = codecs.open(output, "w", encoding=encoding) - output_file.write(new_text) - output_file.close() + with open(output, "w", encoding=encoding) as f: + f.write(new_text) else : sys.stdout.write(new_text.encode(encoding)) @@ -1693,14 +1693,14 @@ def markdown(text, extensions = [], encoding = None, safe_mode = False) : - + message(VERBOSE, "in markdown.markdown(), received text:\n%s" % text) extension_names = [] extension_configs = {} - + for ext in extensions : - pos = ext.find("(") + pos = ext.find("(") if pos == -1 : extension_names.append(ext) else : @@ -1715,7 +1715,7 @@ def markdown(text, safe_mode = safe_mode) return md.convert(text) - + class Extension : @@ -1723,7 +1723,7 @@ def __init__(self, configs = {}) : self.config = configs def getConfig(self, key) : - if self.config.has_key(key) : + if key in self.config : return self.config[key][0] else : return "" @@ -1740,7 +1740,7 @@ def setConfig(self, key, value) : For lower versions of Python use: %s INPUT_FILE > OUTPUT_FILE - + """ % EXECUTABLE_NAME_FOR_USAGE def parse_options() : @@ -1757,7 +1757,7 @@ def parse_options() : 'encoding' : None } else : - print OPTPARSE_WARNING + print(OPTPARSE_WARNING) return None parser = optparse.OptionParser(usage="%prog INPUTFILE [options]") @@ -1776,7 +1776,7 @@ def parse_options() : parser.add_option("-s", "--safe", action="store_const", const=True, dest="safe", help="same mode (strip user's HTML tag)") - + parser.add_option("--noisy", action="store_const", const=VERBOSE, dest="verbose", help="print debug messages") @@ -1810,7 +1810,7 @@ def parse_options() : if not options : sys.exit(0) - + markdownFromFile(**options) diff --git a/test/markdowntest-cases/Auto links.html b/test/markdowntest-cases/Auto links.html index f8df9852..f13817fd 100644 --- a/test/markdowntest-cases/Auto links.html +++ b/test/markdowntest-cases/Auto links.html @@ -1,6 +1,6 @@

    Link: http://example.com/.

    -

    With an ampersand: http://example.com/?foo=1&bar=2

    +

    With an ampersand: http://example.com/?foo=1&bar=2

    • In a list?
    • diff --git a/test/markdowntest-cases/Backslash escapes.html b/test/markdowntest-cases/Backslash escapes.html index 77823c3c..5658c795 100644 --- a/test/markdowntest-cases/Backslash escapes.html +++ b/test/markdowntest-cases/Backslash escapes.html @@ -20,7 +20,7 @@

      Right paren: )

      -

      Greater-than: >

      +

      Greater-than: >

      Hash: #

      diff --git a/test/markdowntest-cases/Strong and em together.html b/test/markdowntest-cases/Strong and em together.html index 71ec78c7..bab1b98f 100644 --- a/test/markdowntest-cases/Strong and em together.html +++ b/test/markdowntest-cases/Strong and em together.html @@ -1,7 +1,7 @@ -

      This is strong and em.

      +

      This is strong and em.

      -

      So is this word.

      +

      So is this word.

      -

      This is strong and em.

      +

      This is strong and em.

      -

      So is this word.

      +

      So is this word.

      diff --git a/test/php-markdown-cases/Links, inline style.html b/test/php-markdown-cases/Links, inline style.html index d3e4d111..98e7da52 100644 --- a/test/php-markdown-cases/Links, inline style.html +++ b/test/php-markdown-cases/Links, inline style.html @@ -1 +1 @@ -

      silly URL w/ angle brackets.

      +

      silly URL w/ angle brackets.

      diff --git a/test/php-markdown-extra-cases/Tables.html b/test/php-markdown-extra-cases/Tables.html index 95b9c2a9..92b6a5aa 100644 --- a/test/php-markdown-extra-cases/Tables.html +++ b/test/php-markdown-extra-cases/Tables.html @@ -308,3 +308,24 @@

      Missing tailing pipe

      + +

      With a space at the end of the underline row

      + + + + + + + + + + + + + + + + + + +
      Header 1Header 2
      Cell 1Cell 2
      Cell 3Cell 4
      diff --git a/test/php-markdown-extra-cases/Tables.text b/test/php-markdown-extra-cases/Tables.text index b761ad91..1c1d31cb 100644 --- a/test/php-markdown-extra-cases/Tables.text +++ b/test/php-markdown-extra-cases/Tables.text @@ -101,3 +101,10 @@ Header 1 | Header 2 | --------- | --------- | Cell | Cell | Cell | Cell + +# With a space at the end of the underline row + +Header 1 | Header 2 | +--------- | --------- | +Cell 1 | Cell 2 | +Cell 3 | Cell 4 | diff --git a/test/test.py b/test/test.py index c69df78f..995db47a 100755 --- a/test/test.py +++ b/test/test.py @@ -4,11 +4,11 @@ """The markdown2 test suite entry point.""" +import importlib import os -from os.path import exists, join, abspath, dirname, normpath +from os.path import join, abspath, dirname import sys import logging - import testlib log = logging.getLogger("test") @@ -24,25 +24,37 @@ def setup(): # Attempt to get 'pygments' on the import path. try: # If already have it, use that one. - import pygments + import pygments # noqa except ImportError: pygments_dir = join(top_dir, "deps", "pygments") - if sys.version_info[0] <= 2: - sys.path.insert(0, pygments_dir) - else: - sys.path.insert(0, pygments_dir + "3") + sys.path.insert(0, pygments_dir + "3") if __name__ == "__main__": logging.basicConfig() setup() default_tags = [] - try: - import pygments - except ImportError: - log.warning("skipping pygments tests ('pygments' module not found)") - default_tags.append("-pygments") + warnings = [] + for extra_lib in ('pygments', 'wavedrom', 'latex2mathml'): + try: + mod = importlib.import_module(extra_lib) + except ImportError: + warnings.append("skipping {} tests ('{}' module not found)".format(extra_lib, extra_lib)) + default_tags.append("-%s" % extra_lib) + else: + if extra_lib == 'pygments': + version = tuple(int(i) for i in mod.__version__.split('.')[:3]) + if version >= (2, 14, 0): + tag = "pygments<2.14" + else: + tag = "pygments>=2.14" + warnings.append("skipping {} tests (pygments {} found)".format(tag, mod.__version__)) + default_tags.append("-%s" % tag) retval = testlib.harness(testdir_from_ns=testdir_from_ns, default_tags=default_tags) + + for warning in warnings: + log.warning(warning) + sys.exit(retval) diff --git a/test/test_markdown2.py b/test/test_markdown2.py index 363934f8..fb433efc 100755 --- a/test/test_markdown2.py +++ b/test/test_markdown2.py @@ -9,14 +9,12 @@ from os.path import join, dirname, abspath, exists, splitext, basename import re from glob import glob -from pprint import pprint import unittest import codecs import difflib import doctest from json import loads as json_loads - -from testlib import TestError, TestSkipped, tag +import warnings sys.path.insert(0, join(dirname(dirname(abspath(__file__))))) try: @@ -24,26 +22,6 @@ finally: del sys.path[0] - - -#---- Python version compat - -# Use `bytes` for byte strings and `unicode` for unicode strings (str in Py3). -if sys.version_info[0] <= 2: - py3 = False - try: - bytes - except NameError: - bytes = str - base_string_type = basestring -elif sys.version_info[0] >= 3: - py3 = True - unicode = str - base_string_type = str - unichr = chr - - - #---- Test cases class _MarkdownTestCase(unittest.TestCase): @@ -73,16 +51,19 @@ def _assertMarkdownParity(self, text): def _assertMarkdownPath(self, text_path, encoding="utf-8", opts=None, toc_html_path=None, metadata_path=None): - text = codecs.open(text_path, 'r', encoding=encoding).read() + with open(text_path, 'r', encoding=encoding) as f: + text = f.read() html_path = splitext(text_path)[0] + ".html" - html = codecs.open(html_path, 'r', encoding=encoding).read() + with open(html_path, 'r', encoding=encoding) as f: + html = f.read() extra = {} if toc_html_path: - extra["toc_html"] = codecs.open(toc_html_path, 'r', encoding=encoding).read() + with open(toc_html_path, 'r', encoding=encoding) as f: + extra["toc_html"] = f.read() extra["toc_html_path"] = toc_html_path if metadata_path: - extra["metadata"] = json_loads( - codecs.open(metadata_path, 'r', encoding=encoding).read()) + with open(metadata_path, 'r', encoding=encoding) as f: + extra["metadata"] = json_loads(f.read()) extra["metadata_path"] = metadata_path self._assertMarkdown(text, html, text_path, html_path, opts=opts, **extra) @@ -126,12 +107,8 @@ def _assertMarkdown(self, text, html, text_path=None, html_path=None, def charreprreplace(exc): if not isinstance(exc, UnicodeEncodeError): raise TypeError("don't know how to handle %r" % exc) - if py3: - obj_repr = repr(exc.object[exc.start:exc.end])[1:-1] - else: - # repr -> remote "u'" and "'" - obj_repr = repr(exc.object[exc.start:exc.end])[2:-1] - return (unicode(obj_repr), exc.end) + obj_repr = repr(exc.object[exc.start:exc.end])[1:-1] + return (str(obj_repr), exc.end) codecs.register_error("charreprreplace", charreprreplace) self.assertEqual(python_norm_html, norm_html, errmsg) @@ -177,11 +154,15 @@ def generate_tests(cls): opts_path = splitext(text_path)[0] + ".opts" if exists(opts_path): try: - opts = eval(open(opts_path, 'r').read()) + with warnings.catch_warnings(record=True) as caught_warnings: + with open(opts_path) as f: + opts = eval(f.read()) + for warning in caught_warnings: + print("WARNING: loading %s generated warning: %s - lineno %d" % (opts_path, warning.message, warning.lineno), file=sys.stderr) except Exception: _, ex, _ = sys.exc_info() print("WARNING: couldn't load `%s' opts file: %s" \ - % (opts_path, ex)) + % (opts_path, ex), file=sys.stderr) toc_html_path = splitext(text_path)[0] + ".toc_html" if not exists(toc_html_path): @@ -198,10 +179,11 @@ def generate_tests(cls): tags_path = splitext(text_path)[0] + ".tags" if exists(tags_path): tags = [] - for line in open(tags_path): - if '#' in line: # allow comments in .tags files - line = line[:line.index('#')] - tags += line.split() + with open(tags_path) as f: + for line in f: + if '#' in line: # allow comments in .tags files + line = line[:line.index('#')] + tags += line.split() test_func.tags = tags name = splitext(basename(text_path))[0] @@ -285,7 +267,7 @@ def test_pre(self): '

      some starter text

      \n\n
      #!/usr/bin/python\nprint "hi"\n
      \n') def test_russian(self): - ko = '\u043b\u0449' # 'ko' on russian keyboard + ko = '\\u043b\\u0449' # 'ko' on russian keyboard self._assertMarkdown("## %s" % ko, '

      %s

      \n' % ko) test_russian.tags = ["unicode", "issue3"] @@ -342,13 +324,6 @@ def test_api(self): test = doctest.DocFileTest("api.doctests") test.runTest() - # Don't bother on Python 3 because (a) there aren't many inline doctests, - # and (b) they are more to be didactic than comprehensive test suites. - if not py3: - def test_internal(self): - doctest.testmod(markdown2) - - #---- internal support stuff @@ -356,16 +331,16 @@ def test_internal(self): def _xml_escape_sub(match): escape = match.group(1) if escape[0] == 'x': - return unichr(int('0'+escape, base=16)) + return chr(int('0'+escape, base=16)) else: - return unichr(int(escape)) + return chr(int(escape)) _markdown_email_link_re = re.compile(r'(.*?)', re.U) def _markdown_email_link_sub(match): href, text = match.groups() href = _xml_escape_re.sub(_xml_escape_sub, href) text = _xml_escape_re.sub(_xml_escape_sub, text) - return '%s' % (href, text) + return '{}'.format(href, text) def norm_html_from_html(html): """Normalize (somewhat) Markdown'd HTML. @@ -375,18 +350,17 @@ def norm_html_from_html(html): Also normalize EOLs. """ - if not isinstance(html, unicode): + if not isinstance(html, str): html = html.decode('utf-8') html = _markdown_email_link_re.sub( _markdown_email_link_sub, html) - if sys.platform == "win32": - html = html.replace('\r\n', '\n') + html = html.replace('\r\n', '\n') return html def _display(s): """Markup the given string for useful display.""" - if not isinstance(s, unicode): + if not isinstance(s, str): s = s.decode("utf-8") s = _indent(_escaped_text_from_text(s, "whitespace"), 4) if not s.endswith('\n'): @@ -533,7 +507,7 @@ def _escaped_text_from_text(text, escapes="eol"): # - Add _escaped_html_from_text() with a similar call sig. import re - if isinstance(escapes, base_string_type): + if isinstance(escapes, str): if escapes == "eol": escapes = {'\r\n': "\\r\\n\r\n", '\n': "\\n\n", '\r': "\\r\r"} elif escapes == "whitespace": diff --git a/test/test_redos.py b/test/test_redos.py new file mode 100644 index 00000000..3bea176f --- /dev/null +++ b/test/test_redos.py @@ -0,0 +1,96 @@ +import logging +import subprocess +import sys +import time +from pathlib import Path + +log = logging.getLogger("test") +LIB_DIR = Path(__file__).parent.parent / "lib" + + +def pull_387_example_1(): + # https://github.com/trentm/python-markdown2/pull/387 + return "[#a" + " " * 3456 + + +def pull_387_example_2(): + # https://github.com/trentm/python-markdown2/pull/387 + return "```" + "\n" * 3456 + + +def pull_387_example_3(): + # https://github.com/trentm/python-markdown2/pull/387 + return "-*-" + " " * 3456 + + +def pull_402(): + # https://github.com/trentm/python-markdown2/pull/402 + return " " * 100_000 + "$" + + +def issue493(): + # https://github.com/trentm/python-markdown2/issues/493 + return "**_" + "*_" * 38730 * 10 + "\x00" + + +def issue_633(): + # https://github.com/trentm/python-markdown2/issues/633 + return '

      max_time + tolerance: - raise DurationError(('Test was too long (%.2f s)' - % total_time)) + raise DurationError('Test was too long (%.2f s)' + % total_time) return wrapper return _timedtest @@ -144,7 +140,7 @@ def wrapper(*args, **kw): #---- module api -class Test(object): +class Test: def __init__(self, ns, testmod, testcase, testfn_name, testsuite_class=None): self.ns = ns @@ -238,13 +234,12 @@ def testmods_from_testdir(testdir): testmod_name = splitext(basename(testmod_path))[0] log.debug("import test module '%s'", testmod_path) try: - iinfo = imp.find_module(testmod_name, [dirname(testmod_path)]) testabsdir = abspath(testdir) sys.path.insert(0, testabsdir) old_dir = os.getcwd() os.chdir(testdir) try: - testmod = imp.load_module(testmod_name, *iinfo) + testmod = importlib.import_module(testmod_name) finally: os.chdir(old_dir) sys.path.remove(testabsdir) @@ -444,7 +439,7 @@ def list_tests(testdir_from_ns, tags): if testfile.endswith(".pyc"): testfile = testfile[:-1] print("%s:" % t.shortname()) - print(" from: %s#%s.%s" % (testfile, + print(" from: {}#{}.{}".format(testfile, t.testcase.__class__.__name__, t.testfn_name)) wrapped = textwrap.fill(' '.join(t.tags()), WIDTH-10) print(" tags: %s" % _indent(wrapped, 8, True)) @@ -475,7 +470,7 @@ def __init__(self, stream): def getDescription(self, test): if test._testlib_explicit_tags_: - return "%s [%s]" % (test._testlib_shortname_, + return "{} [{}]".format(test._testlib_shortname_, ', '.join(test._testlib_explicit_tags_)) else: return test._testlib_shortname_ @@ -519,7 +514,7 @@ def printErrorList(self, flavour, errors): self.stream.write("%s\n" % err) -class ConsoleTestRunner(object): +class ConsoleTestRunner: """A test runner class that displays results on the console. It prints out the names of tests as they are run, errors as they diff --git a/test/tm-cases/admonitions.html b/test/tm-cases/admonitions.html new file mode 100644 index 00000000..e8a7d05e --- /dev/null +++ b/test/tm-cases/admonitions.html @@ -0,0 +1,53 @@ +

      + + + +

      Otherwise the text is no longer part of the admonition.

      + + + +
      print('In case you wanted something like')
      +print('an indented code block right after')
      +
      + + diff --git a/test/tm-cases/admonitions.opts b/test/tm-cases/admonitions.opts new file mode 100644 index 00000000..44e237fc --- /dev/null +++ b/test/tm-cases/admonitions.opts @@ -0,0 +1 @@ +{"extras": ["admonitions"]} \ No newline at end of file diff --git a/test/tm-cases/admonitions.text b/test/tm-cases/admonitions.text new file mode 100644 index 00000000..b79ff8a1 --- /dev/null +++ b/test/tm-cases/admonitions.text @@ -0,0 +1,38 @@ +.. NOTE:: Admonitions + They contain 3 main parts, the admonition type, title and body. + + The admonition type is case insensitive, title is optional and the body + should be able to contain pretty much anything. For example: + + - Lists + - With multiple levels + - Of indentation + + And code blocks: + + print('indented code blocks') + +.. warning:: + The admonition's body must be indented by a tab or 3 or more spaces + from where the admonition was declared + Otherwise the text is no longer part of the admonition. + +.. IMPORTANT:: + You can also use 3 or more empty lines after an admonition + to end it + + + + print('In case you wanted something like') + print('an indented code block right after') + +.. admonition:: Generic admonitions + + These should be given a title but this is not enforced + + .. note:: Nested admonitions + Nested admonitions should also work + + - Even inside + .. tip:: + of a list \ No newline at end of file diff --git a/test/tm-cases/admonitions_with_fenced_code_blocks.html b/test/tm-cases/admonitions_with_fenced_code_blocks.html new file mode 100644 index 00000000..7428c571 --- /dev/null +++ b/test/tm-cases/admonitions_with_fenced_code_blocks.html @@ -0,0 +1,35 @@ + + + + +
      +
      # admonitions WITHIN fenced code blocks should NOT be rendered
      +.. attention:: title
      +   body
      +
      +
      diff --git a/test/tm-cases/admonitions_with_fenced_code_blocks.opts b/test/tm-cases/admonitions_with_fenced_code_blocks.opts new file mode 100644 index 00000000..145a5305 --- /dev/null +++ b/test/tm-cases/admonitions_with_fenced_code_blocks.opts @@ -0,0 +1 @@ +{"extras": ["admonitions", "fenced-code-blocks", "pygments"]} \ No newline at end of file diff --git a/test/tm-cases/admonitions_with_fenced_code_blocks.tags b/test/tm-cases/admonitions_with_fenced_code_blocks.tags new file mode 100644 index 00000000..f0d7f9c4 --- /dev/null +++ b/test/tm-cases/admonitions_with_fenced_code_blocks.tags @@ -0,0 +1 @@ +extra admonitions fenced-code-blocks pygments diff --git a/test/tm-cases/admonitions_with_fenced_code_blocks.text b/test/tm-cases/admonitions_with_fenced_code_blocks.text new file mode 100644 index 00000000..9a04bee2 --- /dev/null +++ b/test/tm-cases/admonitions_with_fenced_code_blocks.text @@ -0,0 +1,23 @@ +.. note:: + Admonitions are able to contain fenced code blocks + ```python + print('like so') + ``` + +.. warning:: + ```python + print('Consecutive blocks should also be fine') + ``` + ```python + print('Even though fenced code blocks wrap themselves in newlines') + ``` + .. hint:: It should also work nested + ```python + print('ok') + ``` + +```python +# admonitions WITHIN fenced code blocks should NOT be rendered +.. attention:: title + body +``` \ No newline at end of file diff --git a/test/tm-cases/alerts.html b/test/tm-cases/alerts.html new file mode 100644 index 00000000..c5d957b3 --- /dev/null +++ b/test/tm-cases/alerts.html @@ -0,0 +1,24 @@ +
      +Note +

      Useful information that users should know, even when skimming content.

      +
      + +
      +Tip +

      Helpful advice for doing things better or more easily.

      +
      + +
      +Important +

      Key information users need to know to achieve their goal.

      +
      + +
      +Warning +

      Urgent info that needs immediate user attention to avoid problems.

      +
      + +
      +Caution +

      Advises about risks or negative outcomes of certain actions.

      +
      diff --git a/test/tm-cases/alerts.opts b/test/tm-cases/alerts.opts new file mode 100644 index 00000000..2913a414 --- /dev/null +++ b/test/tm-cases/alerts.opts @@ -0,0 +1 @@ +{"extras": ["alerts"]} diff --git a/test/tm-cases/alerts.text b/test/tm-cases/alerts.text new file mode 100644 index 00000000..b00ffbdd --- /dev/null +++ b/test/tm-cases/alerts.text @@ -0,0 +1,15 @@ +> [!NOTE] +> Useful information that users should know, even when skimming content. + +> [!TIP] +> Helpful advice for doing things better or more easily. + +> [!IMPORTANT] +> Key information users need to know to achieve their goal. + +> [!WARNING] +> Urgent info that needs immediate user attention to avoid problems. + +> [!CAUTION] +> +> Advises about risks or negative outcomes of certain actions. diff --git a/test/tm-cases/backslash_escape_empty_links.html b/test/tm-cases/backslash_escape_empty_links.html new file mode 100644 index 00000000..bffeaa51 --- /dev/null +++ b/test/tm-cases/backslash_escape_empty_links.html @@ -0,0 +1,4 @@ +

      link

      + +

      one
      +two

      diff --git a/test/tm-cases/backslash_escape_empty_links.opts b/test/tm-cases/backslash_escape_empty_links.opts new file mode 100644 index 00000000..7228a640 --- /dev/null +++ b/test/tm-cases/backslash_escape_empty_links.opts @@ -0,0 +1 @@ +{"extras": {"breaks": {"on_backslash": True}}} \ No newline at end of file diff --git a/test/tm-cases/backslash_escape_empty_links.text b/test/tm-cases/backslash_escape_empty_links.text new file mode 100644 index 00000000..95b3f7a1 --- /dev/null +++ b/test/tm-cases/backslash_escape_empty_links.text @@ -0,0 +1,4 @@ +[link]() + +one\ +two \ No newline at end of file diff --git a/test/tm-cases/backslash_escape_html_tags.html b/test/tm-cases/backslash_escape_html_tags.html new file mode 100644 index 00000000..51b070f4 --- /dev/null +++ b/test/tm-cases/backslash_escape_html_tags.html @@ -0,0 +1,13 @@ +

      this is <strong>some strong</strong> text

      + +

      <strong>text</strong>

      + +

      text \with double\ escapes

      + +

      how about text \<strong>with triple\</strong> escapes

      + +

      escaped auto-link <https://www.example.com> +not quite escaped auto link \https://www.example.com +escaped auto-link \<https://www.example.com>

      + +

      <!-- and escaped HTML comment --> \ \<!--and another that is-->

      diff --git a/test/tm-cases/backslash_escape_html_tags.text b/test/tm-cases/backslash_escape_html_tags.text new file mode 100644 index 00000000..e26bf2b5 --- /dev/null +++ b/test/tm-cases/backslash_escape_html_tags.text @@ -0,0 +1,13 @@ +this is \some strong\ text + +[\](http://localhost/)text\ + +text \\with double\\ escapes + +how about text \\\with triple\\\ escapes + +escaped auto-link \ +not quite escaped auto link \\ +escaped auto-link \\\ + +\ \\ \\\ \ No newline at end of file diff --git a/test/tm-cases/backslash_removed_by_adjacent_backtick.html b/test/tm-cases/backslash_removed_by_adjacent_backtick.html new file mode 100644 index 00000000..1091b1a7 --- /dev/null +++ b/test/tm-cases/backslash_removed_by_adjacent_backtick.html @@ -0,0 +1,7 @@ +

      hello \world

      + +

      hello \world my favourite letter is w

      + +

      hello \world my favourite code is import pickle

      + +

      hello \world my favourite letter is x

      diff --git a/test/tm-cases/backslash_removed_by_adjacent_backtick.text b/test/tm-cases/backslash_removed_by_adjacent_backtick.text new file mode 100644 index 00000000..4136c904 --- /dev/null +++ b/test/tm-cases/backslash_removed_by_adjacent_backtick.text @@ -0,0 +1,7 @@ +hello \world + +hello \world my favourite letter is `w` + +hello \world my favourite code is `import pickle` + +hello \world my favourite letter is `x` \ No newline at end of file diff --git a/test/tm-cases/basic_safe_mode.html b/test/tm-cases/basic_safe_mode.html index 435a3329..60051078 100644 --- a/test/tm-cases/basic_safe_mode.html +++ b/test/tm-cases/basic_safe_mode.html @@ -6,13 +6,13 @@

      [HTML_REMOVED]alert(1)[HTML_REMOVED]

      -

      link1

      +

      link1

      link2

      -

      link3

      +

      link3

      -

      link4 >[HTML_REMOVED]alert(1)[HTML_REMOVED]

      +

      link4 >[HTML_REMOVED]alert(1)[HTML_REMOVED]

      link5

      @@ -30,6 +30,6 @@

      <img src="javascript:alert(1)"

      -

      ok img

      +

      ok img

      link ok

      diff --git a/test/tm-cases/basic_safe_mode_escape.html b/test/tm-cases/basic_safe_mode_escape.html index af24510c..cd4f04cc 100644 --- a/test/tm-cases/basic_safe_mode_escape.html +++ b/test/tm-cases/basic_safe_mode_escape.html @@ -3,3 +3,5 @@

      <div>yowzer!</div>

      blah

      + +

      foo <!-- bar

      diff --git a/test/tm-cases/basic_safe_mode_escape.text b/test/tm-cases/basic_safe_mode_escape.text index ee042312..baf11d10 100644 --- a/test/tm-cases/basic_safe_mode_escape.text +++ b/test/tm-cases/basic_safe_mode_escape.text @@ -3,3 +3,6 @@ blah blah
      yowzer!
      blah + + +*foo* + + + + + +content here + + there is supposed to be an image here + +Now some bullets: + + * one + * two + + + + + diff --git a/test/tm-cases/block_tags.text b/test/tm-cases/block_tags.text new file mode 100644 index 00000000..12ced98a --- /dev/null +++ b/test/tm-cases/block_tags.text @@ -0,0 +1,23 @@ + + + + + + + + +content here + + there is supposed to be an image here + +Now some bullets: + + * one + * two + + + + + diff --git a/test/tm-cases/break_on_backslash.html b/test/tm-cases/break_on_backslash.html new file mode 100644 index 00000000..71a26425 --- /dev/null +++ b/test/tm-cases/break_on_backslash.html @@ -0,0 +1,6 @@ +

      Github flavoured markdown allows
      +you to insert a backslash with or +without a space, which results in
      +a hard line break, unless it has \ +been escaped. It's still possible to
      +end the line with two spaces for a line break.

      diff --git a/test/tm-cases/break_on_backslash.opts b/test/tm-cases/break_on_backslash.opts new file mode 100644 index 00000000..52f81fdc --- /dev/null +++ b/test/tm-cases/break_on_backslash.opts @@ -0,0 +1 @@ +{'extras': {'breaks': {'on_backslash': True}}} diff --git a/test/tm-cases/break_on_backslash.text b/test/tm-cases/break_on_backslash.text new file mode 100644 index 00000000..8a94983d --- /dev/null +++ b/test/tm-cases/break_on_backslash.text @@ -0,0 +1,6 @@ +Github flavoured markdown allows \ +you to insert a backslash with or +without a space, which results in\ +a hard line break, unless it has \\ +been escaped. It's still possible to +end the line with two spaces for a line break. diff --git a/test/tm-cases/break_on_newline_and_backslash.html b/test/tm-cases/break_on_newline_and_backslash.html new file mode 100644 index 00000000..17706d82 --- /dev/null +++ b/test/tm-cases/break_on_newline_and_backslash.html @@ -0,0 +1,3 @@ +

      The breaks extra allows you to insert a hard break on newlines.
      +You can also insert hard breaks after backslashes
      +which will result in a single break when both are enabled.

      diff --git a/test/tm-cases/break_on_newline_and_backslash.opts b/test/tm-cases/break_on_newline_and_backslash.opts new file mode 100644 index 00000000..5ac49b03 --- /dev/null +++ b/test/tm-cases/break_on_newline_and_backslash.opts @@ -0,0 +1 @@ +{'extras': {'breaks': {'on_backslash': True, 'on_newline': True}}} diff --git a/test/tm-cases/break_on_newline_and_backslash.text b/test/tm-cases/break_on_newline_and_backslash.text new file mode 100644 index 00000000..84c2b826 --- /dev/null +++ b/test/tm-cases/break_on_newline_and_backslash.text @@ -0,0 +1,3 @@ +The breaks extra allows you to insert a hard break on newlines. +You can also insert hard breaks after backslashes \ +which will result in a single break when both are enabled. diff --git a/test/tm-cases/break_on_newline_excessive_br_tags_in_ul.html b/test/tm-cases/break_on_newline_excessive_br_tags_in_ul.html new file mode 100644 index 00000000..19075c61 --- /dev/null +++ b/test/tm-cases/break_on_newline_excessive_br_tags_in_ul.html @@ -0,0 +1,58 @@ +

      Conteúdo

      + +
        +
      • O que é estrutura Co-locada (on premises), o que é estrutura híbrida e o que é estrutura em-nuvem? +
          +
        • Em Nuvem (cloud based) +
            +
          • Uma estrutura em-nuvem tem todos os seus principais recursos providos por um provedor de serviços em nuvem. +
              +
            • Uma definição formal de serviço em nuvem pode ser: +
                +
              • Entrega via internet de um serviço de Tecnologia da Informação, sob demanda, em um modelo de pague-pelo-que-consome. +
                  +
                • Brown Field é quando você migra um serviço existente
                • +
                • Green field é quando você começa do zero na nuvem, alguns também chamam isto de Cloud
                • +
              • +
            • +
          • +
        • +
      • +
      + +

      Ordered List

      + +
        +
      1. A +
          +
        1. B +
            +
          1. C +
              +
            1. D
            2. +
            3. E
            4. +
          2. +
        2. +
      2. +
      + +

      Mixed List

      + +
        +
      1. A +
          +
        • B +
            +
          1. C +
              +
            • D
            • +
            • E
            • +
          2. +
          3. F +
              +
            1. G
            2. +
            3. H
            4. +
          4. +
        • +
      2. +
      diff --git a/test/tm-cases/break_on_newline_excessive_br_tags_in_ul.opts b/test/tm-cases/break_on_newline_excessive_br_tags_in_ul.opts new file mode 100644 index 00000000..a3d91cea --- /dev/null +++ b/test/tm-cases/break_on_newline_excessive_br_tags_in_ul.opts @@ -0,0 +1 @@ +{"extras": ["break-on-newline"]} \ No newline at end of file diff --git a/test/tm-cases/break_on_newline_excessive_br_tags_in_ul.text b/test/tm-cases/break_on_newline_excessive_br_tags_in_ul.text new file mode 100644 index 00000000..aacdd9be --- /dev/null +++ b/test/tm-cases/break_on_newline_excessive_br_tags_in_ul.text @@ -0,0 +1,25 @@ +## Conteúdo +- O que é estrutura Co-locada (on premises), o que é estrutura híbrida e o que é estrutura em-nuvem? + - Em Nuvem (cloud based) + - Uma estrutura em-nuvem tem todos os seus principais recursos providos por um provedor de serviços em nuvem. + - Uma definição formal de serviço em nuvem pode ser: + - Entrega via internet de um serviço de Tecnologia da Informação, sob demanda, em um modelo de pague-pelo-que-consome. + - Brown Field é quando você migra um serviço existente + - Green field é quando você começa do zero na nuvem, alguns também chamam isto de Cloud + +## Ordered List +1. A + 1. B + 1. C + 1. D + 2. E + +## Mixed List +1. A + - B + 1. C + - D + - E + 2. F + 1. G + 2. H \ No newline at end of file diff --git a/test/tm-cases/code_friendly_bold.html b/test/tm-cases/code_friendly_bold.html new file mode 100644 index 00000000..a0c9b16d --- /dev/null +++ b/test/tm-cases/code_friendly_bold.html @@ -0,0 +1 @@ +

      bold_but_not_emphasized

      diff --git a/test/tm-cases/code_friendly_bold.opts b/test/tm-cases/code_friendly_bold.opts new file mode 100644 index 00000000..cd0b9d61 --- /dev/null +++ b/test/tm-cases/code_friendly_bold.opts @@ -0,0 +1 @@ +{"extras": ["code-friendly"]} diff --git a/test/tm-cases/code_friendly_bold.text b/test/tm-cases/code_friendly_bold.text new file mode 100644 index 00000000..c50ffd25 --- /dev/null +++ b/test/tm-cases/code_friendly_bold.text @@ -0,0 +1 @@ +**bold_but_not_emphasized** diff --git a/test/tm-cases/code_friendly_issue638.html b/test/tm-cases/code_friendly_issue638.html new file mode 100644 index 00000000..c28476e3 --- /dev/null +++ b/test/tm-cases/code_friendly_issue638.html @@ -0,0 +1 @@ +

      a_b this should be bold c_d this is bold

      diff --git a/test/tm-cases/code_friendly_issue638.opts b/test/tm-cases/code_friendly_issue638.opts new file mode 100644 index 00000000..245e9828 --- /dev/null +++ b/test/tm-cases/code_friendly_issue638.opts @@ -0,0 +1 @@ +{"extras":["code-friendly"]} \ No newline at end of file diff --git a/test/tm-cases/code_friendly_issue638.text b/test/tm-cases/code_friendly_issue638.text new file mode 100644 index 00000000..a015bf5c --- /dev/null +++ b/test/tm-cases/code_friendly_issue638.text @@ -0,0 +1 @@ +a_b this should be **bold** c_d this is **bold** \ No newline at end of file diff --git a/test/tm-cases/code_friendly_issue646.html b/test/tm-cases/code_friendly_issue646.html new file mode 100644 index 00000000..5c8e0779 --- /dev/null +++ b/test/tm-cases/code_friendly_issue646.html @@ -0,0 +1 @@ +

      A_B

      diff --git a/test/tm-cases/code_friendly_issue646.opts b/test/tm-cases/code_friendly_issue646.opts new file mode 100644 index 00000000..245e9828 --- /dev/null +++ b/test/tm-cases/code_friendly_issue646.opts @@ -0,0 +1 @@ +{"extras":["code-friendly"]} \ No newline at end of file diff --git a/test/tm-cases/code_friendly_issue646.text b/test/tm-cases/code_friendly_issue646.text new file mode 100644 index 00000000..8f94c786 --- /dev/null +++ b/test/tm-cases/code_friendly_issue646.text @@ -0,0 +1 @@ +A_**B** \ No newline at end of file diff --git a/test/tm-cases/code_friendly_issue671.html b/test/tm-cases/code_friendly_issue671.html new file mode 100644 index 00000000..1f41b82d --- /dev/null +++ b/test/tm-cases/code_friendly_issue671.html @@ -0,0 +1,3 @@ +

      __A___B_C_D_E_F_G

      + +

      A_B C_D_E

      diff --git a/test/tm-cases/code_friendly_issue671.opts b/test/tm-cases/code_friendly_issue671.opts new file mode 100644 index 00000000..8eb3b9ef --- /dev/null +++ b/test/tm-cases/code_friendly_issue671.opts @@ -0,0 +1 @@ +{'extras': ['code-friendly']} \ No newline at end of file diff --git a/test/tm-cases/code_friendly_issue671.text b/test/tm-cases/code_friendly_issue671.text new file mode 100644 index 00000000..f3e9bb83 --- /dev/null +++ b/test/tm-cases/code_friendly_issue671.text @@ -0,0 +1,3 @@ +__A___B_C_D_E_F_G + +**A_B** [C_D_E](https://example.com/) \ No newline at end of file diff --git a/test/tm-cases/codespans_inside_link_text.html b/test/tm-cases/codespans_inside_link_text.html new file mode 100644 index 00000000..48027cfb --- /dev/null +++ b/test/tm-cases/codespans_inside_link_text.html @@ -0,0 +1,3 @@ +

      example

      + +

      more examples

      diff --git a/test/tm-cases/codespans_inside_link_text.text b/test/tm-cases/codespans_inside_link_text.text new file mode 100644 index 00000000..92bbab93 --- /dev/null +++ b/test/tm-cases/codespans_inside_link_text.text @@ -0,0 +1,3 @@ +[`example`](https://example.com) + +[more `examples`](https://example.com) \ No newline at end of file diff --git a/test/tm-cases/consecutive_image_links_issue498.html b/test/tm-cases/consecutive_image_links_issue498.html new file mode 100644 index 00000000..776f324a --- /dev/null +++ b/test/tm-cases/consecutive_image_links_issue498.html @@ -0,0 +1,4 @@ +

      A +A +A +A

      diff --git a/test/tm-cases/consecutive_image_links_issue498.text b/test/tm-cases/consecutive_image_links_issue498.text new file mode 100644 index 00000000..34a6a5cb --- /dev/null +++ b/test/tm-cases/consecutive_image_links_issue498.text @@ -0,0 +1,4 @@ +[![A](https://img.shields.io/badge/license-AGPL--3.0-orange?style=flat-square&color=0f6adb&logo=github)](https://github.com/aoaostar) +[![A](https://img.shields.io/badge/license-AGPL--3.0-orange?style=flat-square&color=0f6adb&logo=github)](https://github.com/aoaostar) +[![A](https://img.shields.io/badge/license-AGPL--3.0-orange?style=flat-square&color=0f6adb&logo=github)](https://github.com/aoaostar) +[![A](https://img.shields.io/badge/license-AGPL--3.0-orange?style=flat-square&color=0f6adb&logo=github)](https://github.com/aoaostar) \ No newline at end of file diff --git a/test/tm-cases/consecutive_strong_and_em.html b/test/tm-cases/consecutive_strong_and_em.html new file mode 100644 index 00000000..6478dd07 --- /dev/null +++ b/test/tm-cases/consecutive_strong_and_em.html @@ -0,0 +1 @@ +

      strongemstrong

      diff --git a/test/tm-cases/consecutive_strong_and_em.text b/test/tm-cases/consecutive_strong_and_em.text new file mode 100644 index 00000000..663723f9 --- /dev/null +++ b/test/tm-cases/consecutive_strong_and_em.text @@ -0,0 +1 @@ +**strong***em***strong** diff --git a/test/tm-cases/consecutive_strong_em.html b/test/tm-cases/consecutive_strong_em.html new file mode 100644 index 00000000..b2e92c7b --- /dev/null +++ b/test/tm-cases/consecutive_strong_em.html @@ -0,0 +1,7 @@ +

      a b c

      + +

      a b c

      + +

      a b c

      + +

      a b c

      diff --git a/test/tm-cases/consecutive_strong_em.text b/test/tm-cases/consecutive_strong_em.text new file mode 100644 index 00000000..1fde0000 --- /dev/null +++ b/test/tm-cases/consecutive_strong_em.text @@ -0,0 +1,7 @@ +**a** b **c** + +*a* b *c* + +__a__ b __c__ + +_a_ b _c_ diff --git a/test/tm-cases/data_urls_in_safe_mode.html b/test/tm-cases/data_urls_in_safe_mode.html new file mode 100644 index 00000000..587d980d --- /dev/null +++ b/test/tm-cases/data_urls_in_safe_mode.html @@ -0,0 +1,3 @@ +

      smiley

      + +

      smiley

      diff --git a/test/tm-cases/data_urls_in_safe_mode.opts b/test/tm-cases/data_urls_in_safe_mode.opts new file mode 100644 index 00000000..ccb6a09b --- /dev/null +++ b/test/tm-cases/data_urls_in_safe_mode.opts @@ -0,0 +1 @@ +{'safe_mode': True} diff --git a/test/tm-cases/data_urls_in_safe_mode.text b/test/tm-cases/data_urls_in_safe_mode.text new file mode 100644 index 00000000..512c6119 --- /dev/null +++ b/test/tm-cases/data_urls_in_safe_mode.text @@ -0,0 +1,3 @@ +![smiley](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=) + +[smiley](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=) diff --git a/test/tm-cases/emacs_file_vars.html b/test/tm-cases/emacs_file_vars.html new file mode 100644 index 00000000..1ccd4c67 --- /dev/null +++ b/test/tm-cases/emacs_file_vars.html @@ -0,0 +1,6 @@ + + +

      This markdown text will be converted with the "footnotes" +extra enabled.

      + +

      This test should not raise any errors

      diff --git a/test/tm-cases/emacs_file_vars.opts b/test/tm-cases/emacs_file_vars.opts new file mode 100644 index 00000000..1db0c71f --- /dev/null +++ b/test/tm-cases/emacs_file_vars.opts @@ -0,0 +1 @@ +{"use_file_vars": True} \ No newline at end of file diff --git a/test/tm-cases/emacs_file_vars.text b/test/tm-cases/emacs_file_vars.text new file mode 100644 index 00000000..bc8bf213 --- /dev/null +++ b/test/tm-cases/emacs_file_vars.text @@ -0,0 +1,6 @@ +-*- markdown-extras: footnotes -*- + +This markdown text will be converted with the "footnotes" +extra enabled. + +This test should not raise any errors \ No newline at end of file diff --git a/test/tm-cases/emacs_file_vars_with_metadata.html b/test/tm-cases/emacs_file_vars_with_metadata.html new file mode 100644 index 00000000..bed68625 --- /dev/null +++ b/test/tm-cases/emacs_file_vars_with_metadata.html @@ -0,0 +1,2 @@ + + diff --git a/test/tm-cases/emacs_file_vars_with_metadata.metadata b/test/tm-cases/emacs_file_vars_with_metadata.metadata new file mode 100644 index 00000000..a977d991 --- /dev/null +++ b/test/tm-cases/emacs_file_vars_with_metadata.metadata @@ -0,0 +1,3 @@ +{ + "key": "value" +} \ No newline at end of file diff --git a/test/tm-cases/emacs_file_vars_with_metadata.opts b/test/tm-cases/emacs_file_vars_with_metadata.opts new file mode 100644 index 00000000..1db0c71f --- /dev/null +++ b/test/tm-cases/emacs_file_vars_with_metadata.opts @@ -0,0 +1 @@ +{"use_file_vars": True} \ No newline at end of file diff --git a/test/tm-cases/emacs_file_vars_with_metadata.text b/test/tm-cases/emacs_file_vars_with_metadata.text new file mode 100644 index 00000000..83db1488 --- /dev/null +++ b/test/tm-cases/emacs_file_vars_with_metadata.text @@ -0,0 +1,5 @@ +--- +key: value +--- + +-*- markdown-extras: metadata -*- \ No newline at end of file diff --git a/test/tm-cases/empty_fenced_code_blocks.html b/test/tm-cases/empty_fenced_code_blocks.html new file mode 100644 index 00000000..70cbf9ca --- /dev/null +++ b/test/tm-cases/empty_fenced_code_blocks.html @@ -0,0 +1,20 @@ +
      +
      
      +
      +
      + +
      
      +
      + +

      Pygments removes the empty line whitespace from the next code block

      + +
      +
      
      +
      +
      + +
      
      +
      +
      +
      +
      diff --git a/test/tm-cases/empty_fenced_code_blocks.opts b/test/tm-cases/empty_fenced_code_blocks.opts new file mode 100644 index 00000000..ba411ac7 --- /dev/null +++ b/test/tm-cases/empty_fenced_code_blocks.opts @@ -0,0 +1 @@ +{"extras": ["fenced-code-blocks"]} diff --git a/test/tm-cases/empty_fenced_code_blocks.tags b/test/tm-cases/empty_fenced_code_blocks.tags new file mode 100644 index 00000000..2c03fb5d --- /dev/null +++ b/test/tm-cases/empty_fenced_code_blocks.tags @@ -0,0 +1 @@ +extra fenced-code-blocks pygments diff --git a/test/tm-cases/empty_fenced_code_blocks.text b/test/tm-cases/empty_fenced_code_blocks.text new file mode 100644 index 00000000..edce91ae --- /dev/null +++ b/test/tm-cases/empty_fenced_code_blocks.text @@ -0,0 +1,20 @@ +```shell +``` + +``` +``` + +Pygments removes the empty line whitespace from the next code block +```shell + + + + +``` + +``` + + + + +``` diff --git a/test/tm-cases/ems_across_spans.html b/test/tm-cases/ems_across_spans.html new file mode 100644 index 00000000..daef521f --- /dev/null +++ b/test/tm-cases/ems_across_spans.html @@ -0,0 +1 @@ +

      _confusing ident is _confusing

      diff --git a/test/tm-cases/ems_across_spans.text b/test/tm-cases/ems_across_spans.text new file mode 100644 index 00000000..40cd465c --- /dev/null +++ b/test/tm-cases/ems_across_spans.text @@ -0,0 +1 @@ +**_confusing** ident is **_confusing** \ No newline at end of file diff --git a/test/tm-cases/encode_incomplete_tags_xss_issue625.html b/test/tm-cases/encode_incomplete_tags_xss_issue625.html new file mode 100644 index 00000000..c153cc1b --- /dev/null +++ b/test/tm-cases/encode_incomplete_tags_xss_issue625.html @@ -0,0 +1 @@ +

      <x><img src=x onerror=alert("xss")//><x>

      diff --git a/test/tm-cases/encode_incomplete_tags_xss_issue625.opts b/test/tm-cases/encode_incomplete_tags_xss_issue625.opts new file mode 100644 index 00000000..de64198e --- /dev/null +++ b/test/tm-cases/encode_incomplete_tags_xss_issue625.opts @@ -0,0 +1 @@ +{'safe_mode': 'escape'} \ No newline at end of file diff --git a/test/tm-cases/encode_incomplete_tags_xss_issue625.text b/test/tm-cases/encode_incomplete_tags_xss_issue625.text new file mode 100644 index 00000000..551c08a5 --- /dev/null +++ b/test/tm-cases/encode_incomplete_tags_xss_issue625.text @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/tm-cases/escape_html_comments_safe_mode.html b/test/tm-cases/escape_html_comments_safe_mode.html new file mode 100644 index 00000000..9b2b2bc9 --- /dev/null +++ b/test/tm-cases/escape_html_comments_safe_mode.html @@ -0,0 +1,3 @@ +

      foo <!-- bar

      + +

      foo <!-- bar -->

      diff --git a/test/tm-cases/escape_html_comments_safe_mode.opts b/test/tm-cases/escape_html_comments_safe_mode.opts new file mode 100644 index 00000000..f15d59b8 --- /dev/null +++ b/test/tm-cases/escape_html_comments_safe_mode.opts @@ -0,0 +1 @@ +{'safe_mode': 'escape'} diff --git a/test/tm-cases/escape_html_comments_safe_mode.text b/test/tm-cases/escape_html_comments_safe_mode.text new file mode 100644 index 00000000..c5f3eef2 --- /dev/null +++ b/test/tm-cases/escape_html_comments_safe_mode.text @@ -0,0 +1,3 @@ +*foo* diff --git a/test/tm-cases/escaped_ampersands.html b/test/tm-cases/escaped_ampersands.html new file mode 100644 index 00000000..23bc7879 --- /dev/null +++ b/test/tm-cases/escaped_ampersands.html @@ -0,0 +1,5 @@ +

      &amp; +&lt; +&gt; +&quot; +&#8217;

      diff --git a/test/tm-cases/escaped_ampersands.text b/test/tm-cases/escaped_ampersands.text new file mode 100644 index 00000000..20e6b84e --- /dev/null +++ b/test/tm-cases/escaped_ampersands.text @@ -0,0 +1,5 @@ +\& +\< +\> +\" +\’ \ No newline at end of file diff --git a/test/tm-cases/escaped_html_in_safe_mode.html b/test/tm-cases/escaped_html_in_safe_mode.html new file mode 100644 index 00000000..ddb13060 --- /dev/null +++ b/test/tm-cases/escaped_html_in_safe_mode.html @@ -0,0 +1,3 @@ +

      <abc> +<abc> +<why?

      diff --git a/test/tm-cases/escaped_html_in_safe_mode.opts b/test/tm-cases/escaped_html_in_safe_mode.opts new file mode 100644 index 00000000..de64198e --- /dev/null +++ b/test/tm-cases/escaped_html_in_safe_mode.opts @@ -0,0 +1 @@ +{'safe_mode': 'escape'} \ No newline at end of file diff --git a/test/tm-cases/escaped_html_in_safe_mode.text b/test/tm-cases/escaped_html_in_safe_mode.text new file mode 100644 index 00000000..ea5c1876 --- /dev/null +++ b/test/tm-cases/escaped_html_in_safe_mode.text @@ -0,0 +1,3 @@ +\ +\ +\Inner code blocks should not render as code blocks

      + +
      ```cpp
      +int x = 10;
      +```
      +
      + +

      Without language specifier

      + +
      ```
      +int x = 10;
      +```
      +
      + +

      Double nesting

      + +
      ````
      +```cpp
      +int x = 10;
      +```
      +````
      +
      diff --git a/test/tm-cases/fenced_code_blocks_issue327.opts b/test/tm-cases/fenced_code_blocks_issue327.opts new file mode 100644 index 00000000..91560387 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue327.opts @@ -0,0 +1 @@ +{"extras": ["fenced-code-blocks"]} \ No newline at end of file diff --git a/test/tm-cases/fenced_code_blocks_issue327.tags b/test/tm-cases/fenced_code_blocks_issue327.tags new file mode 100644 index 00000000..2d244483 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue327.tags @@ -0,0 +1 @@ +extra fenced-code-blocks \ No newline at end of file diff --git a/test/tm-cases/fenced_code_blocks_issue327.text b/test/tm-cases/fenced_code_blocks_issue327.text new file mode 100644 index 00000000..88ad0c68 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue327.text @@ -0,0 +1,23 @@ +Inner code blocks should not render as code blocks + +```` +```cpp +int x = 10; +``` +```` + +Without language specifier +```` +``` +int x = 10; +``` +```` + +Double nesting +````` +```` +```cpp +int x = 10; +``` +```` +````` \ No newline at end of file diff --git a/test/tm-cases/fenced_code_blocks_issue355.html b/test/tm-cases/fenced_code_blocks_issue355.html new file mode 100644 index 00000000..6696ede4 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue355.html @@ -0,0 +1,12 @@ +
      +
      some code block
      +
      +
      + +
      yet another code block
      +
      + +

      new line:

      + +
      code everywhere
      +
      diff --git a/test/tm-cases/fenced_code_blocks_issue355.opts b/test/tm-cases/fenced_code_blocks_issue355.opts new file mode 100644 index 00000000..91560387 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue355.opts @@ -0,0 +1 @@ +{"extras": ["fenced-code-blocks"]} \ No newline at end of file diff --git a/test/tm-cases/fenced_code_blocks_issue355.tags b/test/tm-cases/fenced_code_blocks_issue355.tags new file mode 100644 index 00000000..12bca88c --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue355.tags @@ -0,0 +1 @@ +extra fenced-code-blocks pygments \ No newline at end of file diff --git a/test/tm-cases/fenced_code_blocks_issue355.text b/test/tm-cases/fenced_code_blocks_issue355.text new file mode 100644 index 00000000..c7e33b1c --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue355.text @@ -0,0 +1,10 @@ +```python +some code block +``` +``` +yet another code block +``` +new line: +``` +code everywhere +``` \ No newline at end of file diff --git a/test/tm-cases/fenced_code_blocks_issue396.html b/test/tm-cases/fenced_code_blocks_issue396.html new file mode 100644 index 00000000..8a277829 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue396.html @@ -0,0 +1,4 @@ +
      a
      +b
      +c
      +
      diff --git a/test/tm-cases/fenced_code_blocks_issue396.opts b/test/tm-cases/fenced_code_blocks_issue396.opts new file mode 100644 index 00000000..91560387 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue396.opts @@ -0,0 +1 @@ +{"extras": ["fenced-code-blocks"]} \ No newline at end of file diff --git a/test/tm-cases/fenced_code_blocks_issue396.text b/test/tm-cases/fenced_code_blocks_issue396.text new file mode 100644 index 00000000..041d79eb --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue396.text @@ -0,0 +1,5 @@ +``` +a +b +c +``` \ No newline at end of file diff --git a/test/tm-cases/fenced_code_blocks_issue426.html b/test/tm-cases/fenced_code_blocks_issue426.html new file mode 100644 index 00000000..ee3e8ae7 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue426.html @@ -0,0 +1,35 @@ +

      Django Templates

      + +

      NOTES

      + +
        +
      • The name should map to the URL.
      • +
      • No distro or app name prefix, they are namespaced by their dirs already
      • +
      • Since templates are made in python, the are named_with_underscores.html (not web style dashes).
      • +
      + +

      URL PARAMETERS IN THE TEMPLATE

      + +
        +
      • All views (except generic.View) from django.forms.generic inherit from ContextMixin
      • +
      • ContextMixin defines the method get_context_data:

        + +
        +
        def get_context_data(self, **kwargs):
        +    kwargs.setdefault('view', self)
        +    if self.extra_context is not None:
        +        kwargs.update(self.extra_context)
        +    return kwargs
        +
        +
        + +

        So when overriding one must be careful to extends super's kwargs:

        + +
        +
        def get_context_data(self, **kwargs):
        +    kwargs = super().get_context_data(**kwargs)
        +    kwargs['page_title'] = "Documentation"
        +    return kwargs
        +
        +
      • +
      diff --git a/test/tm-cases/fenced_code_blocks_issue426.opts b/test/tm-cases/fenced_code_blocks_issue426.opts new file mode 100644 index 00000000..20052b21 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue426.opts @@ -0,0 +1 @@ +{"extras": ["fenced-code-blocks", "pygments"]} diff --git a/test/tm-cases/fenced_code_blocks_issue426.tags b/test/tm-cases/fenced_code_blocks_issue426.tags new file mode 100644 index 00000000..2c03fb5d --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue426.tags @@ -0,0 +1 @@ +extra fenced-code-blocks pygments diff --git a/test/tm-cases/fenced_code_blocks_issue426.text b/test/tm-cases/fenced_code_blocks_issue426.text new file mode 100644 index 00000000..b272c762 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue426.text @@ -0,0 +1,26 @@ +# Django Templates + +## NOTES + +- The name should map to the URL. +- No distro or app name prefix, they are namespaced by their dirs already +- Since templates are made in python, the are `named_with_underscores.html` (not web style dashes). + +## URL PARAMETERS IN THE TEMPLATE + +- All views (except `generic.View`) from `django.forms.generic` inherit from `ContextMixin` +- `ContextMixin` defines the method `get_context_data`: + ```python + def get_context_data(self, **kwargs): + kwargs.setdefault('view', self) + if self.extra_context is not None: + kwargs.update(self.extra_context) + return kwargs + ``` + So when overriding one must be careful to extends `super`'s `kwargs`: + ```py + def get_context_data(self, **kwargs): + kwargs = super().get_context_data(**kwargs) + kwargs['page_title'] = "Documentation" + return kwargs + ``` \ No newline at end of file diff --git a/test/tm-cases/fenced_code_blocks_issue462.html b/test/tm-cases/fenced_code_blocks_issue462.html new file mode 100644 index 00000000..6049aa67 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue462.html @@ -0,0 +1,12 @@ +

      Section 1

      + +
      +
      x = 1
      +
      +
      + +

      Section 2

      + +
      +test +
      diff --git a/test/tm-cases/fenced_code_blocks_issue462.opts b/test/tm-cases/fenced_code_blocks_issue462.opts new file mode 100644 index 00000000..20052b21 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue462.opts @@ -0,0 +1 @@ +{"extras": ["fenced-code-blocks", "pygments"]} diff --git a/test/tm-cases/fenced_code_blocks_issue462.tags b/test/tm-cases/fenced_code_blocks_issue462.tags new file mode 100644 index 00000000..2c03fb5d --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue462.tags @@ -0,0 +1 @@ +extra fenced-code-blocks pygments diff --git a/test/tm-cases/fenced_code_blocks_issue462.text b/test/tm-cases/fenced_code_blocks_issue462.text new file mode 100644 index 00000000..1a01c576 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_issue462.text @@ -0,0 +1,11 @@ +# Section 1 + +```python +x = 1 +``` + +# Section 2 + +
      +test +
      diff --git a/test/tm-cases/fenced_code_blocks_leading_lang_space.html b/test/tm-cases/fenced_code_blocks_leading_lang_space.html index a61524b3..f5815e5e 100644 --- a/test/tm-cases/fenced_code_blocks_leading_lang_space.html +++ b/test/tm-cases/fenced_code_blocks_leading_lang_space.html @@ -1,3 +1,5 @@ -
      if True:
      +
      +
      if True:
           print "hi"
      -
      +
      +
      diff --git a/test/tm-cases/fenced_code_blocks_safe_highlight.html b/test/tm-cases/fenced_code_blocks_safe_highlight.html index a08985a7..b77f50b1 100644 --- a/test/tm-cases/fenced_code_blocks_safe_highlight.html +++ b/test/tm-cases/fenced_code_blocks_safe_highlight.html @@ -1,12 +1,16 @@ -
      if True:
      +
      +
      if True:
           print "hi"
      -
      +
      +

      That's using the fenced-code-blocks extra with Python syntax coloring, if pygments is installed. See http://github.github.com/github-flavored-markdown/.

      -
      def foo
      -    puts "hi"
      +
      +
      def foo
      +    puts "hi"
       end
      -
      +
      +
      diff --git a/test/tm-cases/fenced_code_blocks_safe_highlight.tags b/test/tm-cases/fenced_code_blocks_safe_highlight.tags index 2c03fb5d..b5480365 100644 --- a/test/tm-cases/fenced_code_blocks_safe_highlight.tags +++ b/test/tm-cases/fenced_code_blocks_safe_highlight.tags @@ -1 +1 @@ -extra fenced-code-blocks pygments +extra fenced-code-blocks pygments pygments>=2.14 diff --git a/test/tm-cases/fenced_code_blocks_safe_highlight_old.html b/test/tm-cases/fenced_code_blocks_safe_highlight_old.html new file mode 100644 index 00000000..105bd8c9 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_safe_highlight_old.html @@ -0,0 +1,16 @@ +
      +
      if True:
      +    print "hi"
      +
      +
      + +

      That's using the fenced-code-blocks extra with Python +syntax coloring, if pygments is installed. See +http://github.github.com/github-flavored-markdown/.

      + +
      +
      def foo
      +    puts "hi"
      +end
      +
      +
      diff --git a/test/tm-cases/fenced_code_blocks_safe_highlight_old.opts b/test/tm-cases/fenced_code_blocks_safe_highlight_old.opts new file mode 100644 index 00000000..ca41f659 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_safe_highlight_old.opts @@ -0,0 +1 @@ +{"extras": ["fenced-code-blocks"], "safe_mode": "escape"} diff --git a/test/tm-cases/fenced_code_blocks_safe_highlight_old.tags b/test/tm-cases/fenced_code_blocks_safe_highlight_old.tags new file mode 100644 index 00000000..46b22d25 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_safe_highlight_old.tags @@ -0,0 +1 @@ +extra fenced-code-blocks pygments pygments<2.14 diff --git a/test/tm-cases/fenced_code_blocks_safe_highlight_old.text b/test/tm-cases/fenced_code_blocks_safe_highlight_old.text new file mode 100644 index 00000000..5c6181cf --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_safe_highlight_old.text @@ -0,0 +1,14 @@ +```python +if True: + print "hi" +``` + +That's using the *fenced-code-blocks* extra with Python +syntax coloring, if `pygments` is installed. See +. + +```ruby +def foo + puts "hi" +end +``` diff --git a/test/tm-cases/fenced_code_blocks_simple.html b/test/tm-cases/fenced_code_blocks_simple.html index 7d05a72f..e1889052 100644 --- a/test/tm-cases/fenced_code_blocks_simple.html +++ b/test/tm-cases/fenced_code_blocks_simple.html @@ -12,3 +12,16 @@
          is indentation maintained?
       
      + +

      Here's one in a list, with another one following (see #580):

      + +
        +
      • List 1 +
        code 1
        +
        +code 2
        +
      • +
      + +
      code 3
      +
      diff --git a/test/tm-cases/fenced_code_blocks_simple.text b/test/tm-cases/fenced_code_blocks_simple.text index 9f2aabad..76efcdd1 100644 --- a/test/tm-cases/fenced_code_blocks_simple.text +++ b/test/tm-cases/fenced_code_blocks_simple.text @@ -14,3 +14,16 @@ Here is one at the end of the file: ``` is indentation maintained? ``` + +Here's one in a list, with another one following (see #580): + +* List 1 + ``` + code 1 + + code 2 + ``` + +``` +code 3 +``` diff --git a/test/tm-cases/fenced_code_blocks_syntax_highlighting.html b/test/tm-cases/fenced_code_blocks_syntax_highlighting.html index a08985a7..b77f50b1 100644 --- a/test/tm-cases/fenced_code_blocks_syntax_highlighting.html +++ b/test/tm-cases/fenced_code_blocks_syntax_highlighting.html @@ -1,12 +1,16 @@ -
      if True:
      +
      +
      if True:
           print "hi"
      -
      +
      +

      That's using the fenced-code-blocks extra with Python syntax coloring, if pygments is installed. See http://github.github.com/github-flavored-markdown/.

      -
      def foo
      -    puts "hi"
      +
      +
      def foo
      +    puts "hi"
       end
      -
      +
      +
      diff --git a/test/tm-cases/fenced_code_blocks_syntax_highlighting.tags b/test/tm-cases/fenced_code_blocks_syntax_highlighting.tags index 2c03fb5d..b5480365 100644 --- a/test/tm-cases/fenced_code_blocks_syntax_highlighting.tags +++ b/test/tm-cases/fenced_code_blocks_syntax_highlighting.tags @@ -1 +1 @@ -extra fenced-code-blocks pygments +extra fenced-code-blocks pygments pygments>=2.14 diff --git a/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.html b/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.html new file mode 100644 index 00000000..105bd8c9 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.html @@ -0,0 +1,16 @@ +
      +
      if True:
      +    print "hi"
      +
      +
      + +

      That's using the fenced-code-blocks extra with Python +syntax coloring, if pygments is installed. See +http://github.github.com/github-flavored-markdown/.

      + +
      +
      def foo
      +    puts "hi"
      +end
      +
      +
      diff --git a/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.opts b/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.opts new file mode 100644 index 00000000..ba411ac7 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.opts @@ -0,0 +1 @@ +{"extras": ["fenced-code-blocks"]} diff --git a/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.tags b/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.tags new file mode 100644 index 00000000..46b22d25 --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.tags @@ -0,0 +1 @@ +extra fenced-code-blocks pygments pygments<2.14 diff --git a/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.text b/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.text new file mode 100644 index 00000000..5c6181cf --- /dev/null +++ b/test/tm-cases/fenced_code_blocks_syntax_highlighting_old.text @@ -0,0 +1,14 @@ +```python +if True: + print "hi" +``` + +That's using the *fenced-code-blocks* extra with Python +syntax coloring, if `pygments` is installed. See +. + +```ruby +def foo + puts "hi" +end +``` diff --git a/test/tm-cases/fenced_code_blocks_syntax_indentation.html b/test/tm-cases/fenced_code_blocks_syntax_indentation.html index 37b5723e..eedd7c65 100644 --- a/test/tm-cases/fenced_code_blocks_syntax_indentation.html +++ b/test/tm-cases/fenced_code_blocks_syntax_indentation.html @@ -1,5 +1,7 @@ -
      def foo():
      +
      +
      def foo():
           print "foo"
       
           print "bar"
      -
      +
      +
      diff --git a/test/tm-cases/footnotes_order_of_appearance.html b/test/tm-cases/footnotes_order_of_appearance.html new file mode 100644 index 00000000..803a5821 --- /dev/null +++ b/test/tm-cases/footnotes_order_of_appearance.html @@ -0,0 +1,34 @@ +
      + +

      title: testing the footnotes bug

      + +

      Lorem ipsum dolor sit amet. Aliqua cillum, eu velit.1 Velit deserunt adipiscing adipiscing ullamco exercitation.

      + +
        +
      • this is a list item
      • +
      • this is a list item2
      • +
      • this is a list item3
      • +
      + +

      Lorem ipsum dolor sit amet. Adipiscing4 adipiscing ullamco, exercitation sint. Exercitation sint, fugiat exercitation voluptate amet.

      + +
      +
      +
        +
      1. +

        this should be the first note 

        +
      2. + +
      3. +

        this should be the second note 

        +
      4. + +
      5. +

        this should be the third note 

        +
      6. + +
      7. +

        this should be the fourth note 

        +
      8. +
      +
      diff --git a/test/tm-cases/footnotes_order_of_appearance.opts b/test/tm-cases/footnotes_order_of_appearance.opts new file mode 100644 index 00000000..9dfee9e2 --- /dev/null +++ b/test/tm-cases/footnotes_order_of_appearance.opts @@ -0,0 +1 @@ +{"extras": ["footnotes"]} diff --git a/test/tm-cases/footnotes_order_of_appearance.text b/test/tm-cases/footnotes_order_of_appearance.text new file mode 100644 index 00000000..87df462e --- /dev/null +++ b/test/tm-cases/footnotes_order_of_appearance.text @@ -0,0 +1,15 @@ +--- +title: testing the footnotes bug +--- +Lorem ipsum dolor sit amet. Aliqua cillum, eu velit.[^note1] Velit deserunt adipiscing adipiscing ullamco exercitation. + +- this is a list item +- this is a list item[^note2] +- this is a list item[^note3] + +Lorem ipsum dolor sit amet. Adipiscing[^note4] adipiscing ullamco, exercitation sint. Exercitation sint, fugiat exercitation voluptate amet. + +[^note1]: this should be the first note +[^note2]: this should be the second note +[^note3]: this should be the third note +[^note4]: this should be the fourth note diff --git a/test/tm-cases/gfm_emphasis.html b/test/tm-cases/gfm_emphasis.html new file mode 100644 index 00000000..0ed679d6 --- /dev/null +++ b/test/tm-cases/gfm_emphasis.html @@ -0,0 +1,269 @@ +

      foo bar

      + +

      a * foo bar*

      + +

      a*"foo"*

      + +
        +
      • a *
      • +
      + +

      foobar

      + +

      5678

      + +

      пристанямстремятся

      + +

      aa_"bb"_cc

      + +

      foo-(bar)

      + +

      _foo*

      + +

      *foo bar *

      + +

      *foo bar +*

      + +

      *(*foo)

      + +

      (foo)

      + +

      foobar

      + +

      _foo bar _

      + +

      _(_foo)

      + +

      (foo)

      + +

      foobar

      + +

      пристанямстремятся

      + +

      foobarbaz

      + +

      (bar).

      + +

      foo bar

      + +

      ** foo bar**

      + +

      a**"foo"**

      + +

      foobar

      + +

      foo bar

      + +

      __ foo bar__

      + +

      __ +foo bar__

      + +

      a__"foo"__

      + +

      foobar

      + +

      5678

      + +

      пристанямстремятся

      + +

      foo, bar, baz

      + +

      foo-(bar)

      + +

      **foo bar **

      + +

      **(**foo)

      + +

      (foo)

      + +

      Gomphocarpus (Gomphocarpus physocarpus, syn. +Asclepias physocarpa)

      + +

      foo "bar" foo

      + +

      foobar

      + +

      __foo bar __

      + +

      __(__foo)

      + +

      (foo)

      + +

      foobar

      + +

      пристанямстремятся

      + +

      foobarbaz

      + +

      (bar).

      + +

      foo bar

      + +

      foo +bar

      + +

      foo bar baz

      + +

      foo bar baz

      + +

      foo bar

      + +

      foo bar

      + +

      foo bar baz

      + +

      foobarbaz

      + +

      foobar

      + +

      foo bar

      + +

      foo bar

      + +

      foobar

      + +

      foobarbaz

      + +

      foobar***baz

      + +

      foo bar baz bim bop

      + +

      foo bar

      + +

      ** is not an empty emphasis

      + +

      **** is not an empty strong emphasis

      + +

      foo bar

      + +

      foo +bar

      + +

      foo bar baz

      + +

      foo bar baz

      + +

      foo bar

      + +

      foo bar

      + +

      foo bar baz

      + +

      foobarbaz

      + +

      foo bar

      + +

      foo bar

      + +

      foo bar baz +bim bop

      + +

      foo bar

      + +

      __ is not an empty emphasis

      + +

      ____ is not an empty strong emphasis

      + +

      foo ***

      + +

      foo *

      + +

      foo _

      + +

      foo *****

      + +

      foo *

      + +

      foo _

      + +

      *foo

      + +

      foo*

      + +

      *foo

      + +

      ***foo

      + +

      foo*

      + +

      foo***

      + +

      foo ___

      + +

      foo _

      + +

      foo *

      + +

      foo _____

      + +

      foo _

      + +

      foo *

      + +

      _foo

      + +

      foo_

      + +

      _foo

      + +

      ___foo

      + +

      foo_

      + +

      foo___

      + +

      foo

      + +

      foo

      + +

      foo

      + +

      foo

      + +

      foo

      + +

      foo

      + +

      foo

      + +

      foo

      + +

      foo

      + +

      foo _bar baz_

      + +

      foo bar *baz bim bam

      + +

      **foo bar baz

      + +

      *foo bar baz

      + +

      *bar*

      + +

      _foo bar_

      + +

      *

      + +

      **

      + +

      __

      + +

      a *

      + +

      a _

      + +

      **ahttp://foo.bar/?q=**

      + +

      __ahttp://foo.bar/?q=__

      + +

      foo*bar

      + +

      _foo bar baz._bim

      + +

      __foo bar __baz bim bam

      + +

      foobar

      + +

      foobar

      diff --git a/test/tm-cases/gfm_emphasis.text b/test/tm-cases/gfm_emphasis.text new file mode 100644 index 00000000..43e01891 --- /dev/null +++ b/test/tm-cases/gfm_emphasis.text @@ -0,0 +1,268 @@ +*foo bar* + +a * foo bar* + +a*"foo"* + +* a * + +foo*bar* + +5*6*78 + +пристаням_стремятся_ + +aa_"bb"_cc + +foo-_(bar)_ + +_foo* + +*foo bar * + +*foo bar +* + +*(*foo) + +*(*foo*)* + +*foo*bar + +_foo bar _ + +_(_foo) + +_(_foo_)_ + +_foo_bar + +_пристаням_стремятся + +_foo_bar_baz_ + +_(bar)_. + +**foo bar** + +** foo bar** + +a**"foo"** + +foo**bar** + +__foo bar__ + +__ foo bar__ + +__ +foo bar__ + +a__"foo"__ + +foo__bar__ + +5__6__78 + +пристаням__стремятся__ + +__foo, __bar__, baz__ + +foo-__(bar)__ + +**foo bar ** + +**(**foo) + +*(**foo**)* + +**Gomphocarpus (*Gomphocarpus physocarpus*, syn. +*Asclepias physocarpa*)** + +**foo "*bar*" foo** + +**foo**bar + +__foo bar __ + +__(__foo) + +_(__foo__)_ + +__foo__bar + +__пристаням__стремятся + +__foo__bar__baz__ + +__(bar)__. + +*foo [bar](/url)* + +*foo +bar* + +_foo __bar__ baz_ + +_foo _bar_ baz_ + +__foo_ bar_ + +*foo *bar** + +*foo **bar** baz* + +*foo**bar**baz* + +*foo**bar* + +***foo** bar* + +*foo **bar*** + +*foo**bar*** + +foo***bar***baz + +foo******bar*********baz + +*foo **bar *baz* bim** bop* + +*foo [*bar*](/url)* + + +** is not an empty emphasis + +**** is not an empty strong emphasis + +**foo [bar](/url)** + +**foo +bar** + +__foo _bar_ baz__ + +__foo __bar__ baz__ + +____foo__ bar__ + +**foo **bar**** + +**foo *bar* baz** + +**foo*bar*baz** + +***foo* bar** + +**foo *bar*** + +**foo *bar **baz** +bim* bop** + +**foo [*bar*](/url)** + +__ is not an empty emphasis + +____ is not an empty strong emphasis + +foo *** + +foo *\** + +foo *_* + +foo ***** + +foo **\*** + +foo **_** + +**foo* + +*foo** + +***foo** + +****foo* + +**foo*** + +*foo**** + +foo ___ + +foo _\__ + +foo _*_ + +foo _____ + +foo __\___ + +foo __*__ + +__foo_ + +_foo__ + +___foo__ + +____foo_ + +__foo___ + +_foo____ + +**foo** + +*_foo_* + +__foo__ + +_*foo*_ + +****foo**** + +____foo____ + +******foo****** + +***foo*** + +_____foo_____ + +*foo _bar* baz_ + +*foo __bar *baz bim__ bam* + +**foo **bar baz** + +*foo *bar baz* + +*[bar*](/url) + +_foo [bar_](/url) + +* + +** + +__ + +*a `*`* + +_a `_`_ + +**a + +__a + +**foo*bar** + +_foo **bar** baz._bim + +**__foo** bar **__baz** bim *bam* + +**foo*bar*** + +***foo*bar** \ No newline at end of file diff --git a/test/tm-cases/gfm_emphasis_issue666.html b/test/tm-cases/gfm_emphasis_issue666.html new file mode 100644 index 00000000..8743e4f1 --- /dev/null +++ b/test/tm-cases/gfm_emphasis_issue666.html @@ -0,0 +1 @@ +

      *test*

      diff --git a/test/tm-cases/gfm_emphasis_issue666.text b/test/tm-cases/gfm_emphasis_issue666.text new file mode 100644 index 00000000..3ac0deb3 --- /dev/null +++ b/test/tm-cases/gfm_emphasis_issue666.text @@ -0,0 +1 @@ +*[*test**](https://example.com/) \ No newline at end of file diff --git a/test/tm-cases/gfm_emphasis_issue671.html b/test/tm-cases/gfm_emphasis_issue671.html new file mode 100644 index 00000000..16f51368 --- /dev/null +++ b/test/tm-cases/gfm_emphasis_issue671.html @@ -0,0 +1,7 @@ +

      A->B

      + +

      A->B

      + +

      *A B_C

      + +

      *A B C_D. X Y Z *

      diff --git a/test/tm-cases/gfm_emphasis_issue671.text b/test/tm-cases/gfm_emphasis_issue671.text new file mode 100644 index 00000000..1a045d70 --- /dev/null +++ b/test/tm-cases/gfm_emphasis_issue671.text @@ -0,0 +1,7 @@ +*A->***B** + +**A->****B** + +**A* B_*C* + +**A* B C_D. X Y Z * diff --git a/test/tm-cases/hash_html_blocks.html b/test/tm-cases/hash_html_blocks.html new file mode 100644 index 00000000..f4a20b0f --- /dev/null +++ b/test/tm-cases/hash_html_blocks.html @@ -0,0 +1,6 @@ +
      +

      Archons of the Colophon

      +

      by Paco Xander Nathan +

      +
      diff --git a/test/tm-cases/hash_html_blocks.text b/test/tm-cases/hash_html_blocks.text new file mode 100644 index 00000000..f4a20b0f --- /dev/null +++ b/test/tm-cases/hash_html_blocks.text @@ -0,0 +1,6 @@ +
      +

      Archons of the Colophon

      +

      by Paco Xander Nathan +

      +
      diff --git a/test/tm-cases/hash_html_blocks_issue686.html b/test/tm-cases/hash_html_blocks_issue686.html new file mode 100644 index 00000000..a6398989 --- /dev/null +++ b/test/tm-cases/hash_html_blocks_issue686.html @@ -0,0 +1,3 @@ +

      test +

      diff --git a/test/tm-cases/hash_html_blocks_issue686.tags b/test/tm-cases/hash_html_blocks_issue686.tags new file mode 100644 index 00000000..7bd1a023 --- /dev/null +++ b/test/tm-cases/hash_html_blocks_issue686.tags @@ -0,0 +1 @@ +issue686 assertionerror \ No newline at end of file diff --git a/test/tm-cases/hash_html_blocks_issue686.text b/test/tm-cases/hash_html_blocks_issue686.text new file mode 100644 index 00000000..a6398989 --- /dev/null +++ b/test/tm-cases/hash_html_blocks_issue686.text @@ -0,0 +1,3 @@ +

      test +

      diff --git a/test/tm-cases/hash_html_blocks_issue_508.html b/test/tm-cases/hash_html_blocks_issue_508.html new file mode 100644 index 00000000..2c176c15 --- /dev/null +++ b/test/tm-cases/hash_html_blocks_issue_508.html @@ -0,0 +1,12 @@ +
      +
      + +
      + +
      + +
      + +
        +
      • A
      • +
      diff --git a/test/tm-cases/hash_html_blocks_issue_508.text b/test/tm-cases/hash_html_blocks_issue_508.text new file mode 100644 index 00000000..dbe7342d --- /dev/null +++ b/test/tm-cases/hash_html_blocks_issue_508.text @@ -0,0 +1,7 @@ +
      +
      +
      +
      +
      + +- A diff --git a/test/tm-cases/hash_nested_html_blocks.html b/test/tm-cases/hash_nested_html_blocks.html new file mode 100644 index 00000000..1e7bc4cb --- /dev/null +++ b/test/tm-cases/hash_nested_html_blocks.html @@ -0,0 +1,7 @@ +
      +
      +
      x = 1
      +
      +
      + +
      diff --git a/test/tm-cases/hash_nested_html_blocks.opts b/test/tm-cases/hash_nested_html_blocks.opts new file mode 100644 index 00000000..9e6bd79a --- /dev/null +++ b/test/tm-cases/hash_nested_html_blocks.opts @@ -0,0 +1 @@ +{"extras": ["fenced-code-blocks", "pygments"]} \ No newline at end of file diff --git a/test/tm-cases/hash_nested_html_blocks.tags b/test/tm-cases/hash_nested_html_blocks.tags new file mode 100644 index 00000000..2c03fb5d --- /dev/null +++ b/test/tm-cases/hash_nested_html_blocks.tags @@ -0,0 +1 @@ +extra fenced-code-blocks pygments diff --git a/test/tm-cases/hash_nested_html_blocks.text b/test/tm-cases/hash_nested_html_blocks.text new file mode 100644 index 00000000..f5ad8bd1 --- /dev/null +++ b/test/tm-cases/hash_nested_html_blocks.text @@ -0,0 +1,5 @@ +
      +```python +x = 1 +``` +
      \ No newline at end of file diff --git a/test/tm-cases/header_ids_4.html b/test/tm-cases/header_ids_4.html index 0d6384ff..f2c32aa5 100644 --- a/test/tm-cases/header_ids_4.html +++ b/test/tm-cases/header_ids_4.html @@ -1,6 +1,6 @@ -

      Fruit заголовок really likes

      +

      Fruit заголовок really likes

      • apples
      • diff --git a/test/tm-cases/highlightjs_lang.html b/test/tm-cases/highlightjs_lang.html index 2ca1490a..6a691b45 100644 --- a/test/tm-cases/highlightjs_lang.html +++ b/test/tm-cases/highlightjs_lang.html @@ -1,23 +1,17 @@ -
        here is some cpp code
        +
        here is some cpp code
         
        -
        some lang-cpp code
        -
        - -
        and some language-cpp code
        -
        - -
        some code without highlighting
        +
        some code without highlighting
         

        That's using the fenced-code-blocks and highlightjs-lang extra.

        Here is an empty one (just to check):

        -
        
        +
        
         

        Here is one at the end of the file:

        -
            is indentation maintained?
        +
            is indentation maintained?
         
        diff --git a/test/tm-cases/highlightjs_lang.text b/test/tm-cases/highlightjs_lang.text index 9073ed34..b1c5882c 100644 --- a/test/tm-cases/highlightjs_lang.text +++ b/test/tm-cases/highlightjs_lang.text @@ -2,14 +2,6 @@ here is some cpp code ``` -```lang-cpp -some lang-cpp code -``` - -```language-cpp -and some language-cpp code -``` - ```nohighlight some code without highlighting ``` diff --git a/test/tm-cases/hr_uniform_characters.html b/test/tm-cases/hr_uniform_characters.html new file mode 100644 index 00000000..2e5a7b95 --- /dev/null +++ b/test/tm-cases/hr_uniform_characters.html @@ -0,0 +1,48 @@ +

        Horizontal rules should probably consist of all of the same characters +EG:

        + +
        + +

        Or

        + +
        + +

        Or

        + +
        + +

        But not any of:

        + +

        --*

        + +

        -*-

        + +

        -**

        + +

        *--

        + +

        -

        + +

        **-

        + +

        **_

        + +

        _

        + +

        *__

        + +

        _**

        + +

        *

        + +

        __-

        + +

        -

        + +

        _--

        + +

        -__

        + +

        -_-

        + +

        --_

        diff --git a/test/tm-cases/hr_uniform_characters.text b/test/tm-cases/hr_uniform_characters.text new file mode 100644 index 00000000..8d5b69d8 --- /dev/null +++ b/test/tm-cases/hr_uniform_characters.text @@ -0,0 +1,49 @@ +Horizontal rules should probably consist of all of the same characters +EG: + +*** + +Or + +--- + +Or + +___ + + +But not any of: + +--* + +-*- + +-** + +*-- + +*-* + +**- + +**_ + +*_* + +*__ + +_** + +_*_ + +__- + +_-_ + +_-- + +-__ + +-_- + +--_ \ No newline at end of file diff --git a/test/tm-cases/html_classes.html b/test/tm-cases/html_classes.html index f40aeda0..4caa7921 100644 --- a/test/tm-cases/html_classes.html +++ b/test/tm-cases/html_classes.html @@ -1,5 +1,5 @@ - + @@ -7,7 +7,7 @@ - + @@ -18,7 +18,7 @@
        Header 1 Header 2
        Cell 1Cell 1 Cell 2 link
        - + @@ -50,4 +50,19 @@ print doit() -

        the google logo

        +

        the google logo +Unordered list:

        + +
          +
        • Item 1
        • +
        • Item 2
        • +
        • Item 3
        • +
        + +

        Ordered list:

        + +
          +
        1. Item 1
        2. +
        3. Item 2
        4. +
        5. Item 3
        6. +
        diff --git a/test/tm-cases/html_classes.opts b/test/tm-cases/html_classes.opts index 828be323..2a11cef3 100644 --- a/test/tm-cases/html_classes.opts +++ b/test/tm-cases/html_classes.opts @@ -7,7 +7,10 @@ "code": "codesyntaxcolor", "img": "custom-image-class", "table": "table table-striped", + "thead": "table-light", "p": "col-xs-3 custom-paragraph-class", + "ul": "custom-unordered-list-class", + "ol": "custom-ordered-list-class" } } } diff --git a/test/tm-cases/html_classes.text b/test/tm-cases/html_classes.text index 190b037d..4cacc579 100644 --- a/test/tm-cases/html_classes.text +++ b/test/tm-cases/html_classes.text @@ -16,3 +16,15 @@ For example: ![the google logo][logo] [logo]: http://www.google.com/images/logo.gif + +Unordered list: + +* Item 1 +* Item 2 +* Item 3 + +Ordered list: + +1. Item 1 +2. Item 2 +3. Item 3 \ No newline at end of file diff --git a/test/tm-cases/html_classes_is_none.html b/test/tm-cases/html_classes_is_none.html new file mode 100644 index 00000000..aaecf804 --- /dev/null +++ b/test/tm-cases/html_classes_is_none.html @@ -0,0 +1,2 @@ +
        print("hello world")
        +
        diff --git a/test/tm-cases/html_classes_is_none.opts b/test/tm-cases/html_classes_is_none.opts new file mode 100644 index 00000000..f1411169 --- /dev/null +++ b/test/tm-cases/html_classes_is_none.opts @@ -0,0 +1 @@ +{"extras": ["html-classes", "fenced-code-blocks"]} \ No newline at end of file diff --git a/test/tm-cases/html_classes_is_none.tags b/test/tm-cases/html_classes_is_none.tags new file mode 100644 index 00000000..263b6963 --- /dev/null +++ b/test/tm-cases/html_classes_is_none.tags @@ -0,0 +1 @@ +extras html-classes diff --git a/test/tm-cases/html_classes_is_none.text b/test/tm-cases/html_classes_is_none.text new file mode 100644 index 00000000..880f0d1a --- /dev/null +++ b/test/tm-cases/html_classes_is_none.text @@ -0,0 +1,3 @@ +``` +print("hello world") +``` \ No newline at end of file diff --git a/test/tm-cases/img_alt_text.html b/test/tm-cases/img_alt_text.html new file mode 100644 index 00000000..dd02a795 --- /dev/null +++ b/test/tm-cases/img_alt_text.html @@ -0,0 +1 @@ +

        a*b*c

        diff --git a/test/tm-cases/img_alt_text.text b/test/tm-cases/img_alt_text.text new file mode 100644 index 00000000..10ddfe77 --- /dev/null +++ b/test/tm-cases/img_alt_text.text @@ -0,0 +1 @@ +![a*b*c](d) diff --git a/test/tm-cases/incomplete_tag_xss_issue694.html b/test/tm-cases/incomplete_tag_xss_issue694.html new file mode 100644 index 00000000..34fe52ad --- /dev/null +++ b/test/tm-cases/incomplete_tag_xss_issue694.html @@ -0,0 +1,2 @@ +

        <iframe +<http:> srcdoc="<script>alert()</script>" a=

        diff --git a/test/tm-cases/incomplete_tag_xss_issue694.opts b/test/tm-cases/incomplete_tag_xss_issue694.opts new file mode 100644 index 00000000..de64198e --- /dev/null +++ b/test/tm-cases/incomplete_tag_xss_issue694.opts @@ -0,0 +1 @@ +{'safe_mode': 'escape'} \ No newline at end of file diff --git a/test/tm-cases/incomplete_tag_xss_issue694.text b/test/tm-cases/incomplete_tag_xss_issue694.text new file mode 100644 index 00000000..0ed4c984 --- /dev/null +++ b/test/tm-cases/incomplete_tag_xss_issue694.text @@ -0,0 +1,2 @@ +
        Year Temperature (low)