Package: pytest2md
author: gk
version: 20200602Generating Markdown - While Testing Contained ClaimsTable Of ContentsInline Python Function ExecutionFeatureshtml_tablesh_filebash_runmd_from_source_codeTable of ContentsLink ReplacementsSpecCode Repo Hoster Specific Source LinksSetting a link templateLink RefsSummaryTipsIsolationFew things are more annoying than stuff which does not work as announced,
especially when you find out only after an invest of time and energy.Documentation is often prone to produce such situations, since hard to keep
100% in sync with the code evolution.This is a set of tools,generatingdocumentation, while verifying the documented
claims about code behaviour - without the need to adapt the source code, e.g. by modifying
doc strings:When the documentation is using a lot of code examples then a very welcome
additional benefit of writing it like shown is the availability ofsource
code autoformatters.Other Example:This "README.md" was built intothistemplate,
wherehtml comment style placeholdershad been replaced while running pytest ontest_tutorial.Lets run a bash command and assert on its results.
Note that the command is shown in the readme, incl. result and the result
can be asserted upon.$cat"/etc/hosts"|greplocalhost127.0.0.1axc3.axiros.comlocalhostlocalhost.localdomainlocalhost4localhost4.localdomain4
::1localhostlocalhost.localdomainlocalhost6localhost6.localdomain6$ls"/home/gk/repos/pytest2md/tests"assets
__pycache__
test_basics.py
test_changelog.py
test_tutorial.py

$ls-lta/etc/hosts
-rw-r--r--.1rootroot308May823:47/etc/hostsGenerated by:Inline Python Function Executionvia themd_from_source_codefunction you can write fluent markdown
(tested) python combos:hi='hello world'assert'world'inhiprint(hi.replace('world','joe'))The functions are evaluated and documented in the order they show up
within the textblocks.Please keep tripple apostrophes - we split the text blocks,
searching for those.State is kept within the outer pytest function, like normally in python.
I.e. if you require new state, then start a new pytest function.Stdout is redirected to an output collector function, i.e. if you print
this does result in an "Output" block. If the printout starts with
"MARKDOWN:" then we won't wrap that output into fenced code blocks but
display as is.If the string 'breakpoint' occurs in a function body, we won't redirect
standardout for displaying output.Featureshtml_tableht=p2m.html_tableprint(ht([['foo','bar'],['bar','baz']],['h1','h2']))print('As details when summary arg is given:')t=ht([['joe','doe'],['suzie','wong']],['first','last'],summary='names. click to open...',)assert'details'intassert'joe</td'intprint(t)sh_file$cat"test_file.json"{"a":[{"testfile":"created"},"at","Thu Jun  4 00:15:07 2020"]}as details$cat"test_file.json"{"a":[{"testfile":"created"},"at","Thu Jun  4 00:15:07 2020"]}test_file.jsonas linkGenerated by:# if content is given it will create it:p2m.sh_file(fn,lang='javascript',content=c)# summary arg creates a details structure:p2m.sh_file(fn,lang='javascript',content=c,summary='as details')# creates a link (say True to have the filename as link text)p2m.sh_file(fn,lang='javascript',content=c,as_link=True)p2m.sh_file(fn,lang='javascript',content=c,as_link='as link')bash_run$./some_non_existing_command_in_assetsarg1
/bin/sh:/home/gk/repos/pytest2md/tests/assets/some_non_existing_command_in_assets:Nosuchfileordirectory$ls-lta|greptotal|head-n1total84$ls-lta
total84-rw-r--r--.1gkgk14890Jun400:15README.md
drwxr-xr-x.4gkgk4096Jun400:14tests
drwxr-xr-x.8gkgk4096Jun400:09.git
drwxr-xr-x.9gkgk4096Jun400:09.

...(outputtruncated-seelinkbelow)Outputofls -lta$ls-ltals -ltaGenerated by:run=partial(p2m.bash_run,cmd_path_from_env=True)# by default we search in normal environ for the command to run# but we provide a switch to search in test assets.# errors are redirected to stdoutres=p2m.bash_run('some_non_existing_command_in_assets arg1',cmd_path_from_env=False,ign_err=True,)assertres[0]['exitcode']!=0res=run('ls -lta | grep total | head -n 1')assert'total'inres[0]['res']res=run('ls -lta',into_file='bash_run.txt')assert'total'inres[0]['res']# Ending with .html it converts ansi escape colors to html:# simple link is created.# (requires pip install ansi2html)run('ls -lta',into_file='bash_run.html')md_from_source_codeInserted markdown from running python.Strings in double apos. are rendered, no need to call a render function.md('Inserted markdown from running python.')print('From output of running python ')Output:From output of running pythonMore  markdownprint('From another function')Output:From another functionStrings can also contain instructions, like this (looked up in p2m.MdInline namespace class)$cd/etc;ls-lta|head-n5total2716drwxr-xr-x.165rootroot12288Jun309:04.
-rw-r--r--.1rootroot139649Jun309:04ld.so.cache
-rw-r--r--.1rootroot67Jun307:44resolv.conf
drwxr-xr-x.2rootroot4096Jun119:02alternativesDefault inline functions (add your own in module headers):print([kforkindir(pytest2md.MdInline)ifnotk.startswith('_')])Output:['bash', 'sh_file']Generated bydefsome_test_function():"""Strings in double apos. are rendered, no need to call a render function."""deffunc1():md('Inserted markdown from running python.')print('From output of running python ')"""> More  markdown"""deffunc2():print('From another function')"""Strings can also contain instructions, like this (looked up in p2m.MdInline namespace class)<bash: cd MY_REPL_DIR; ls -lta | head -n 5>Default inline functions (add your own in module headers):"""defknown():print([kforkindir(pytest2md.MdInline)ifnotk.startswith('_')])# repl dict simply replaces keys with values before any processing:p2m.md_from_source_code(repl={'MY_REPL_DIR':'/etc'})Table of Contentsp2m.write_markdown(with_source_ref=True, make_toc=True)See this tutorial.Link ReplacementsTechnical markdown content wants to link to source code often.
How to get those links working and that convenient?The module does offer also some source finding / link replacement feature,
via themdtoolmodule. The latter link was built simply by this:[mdtool]<SRC>Other example: Thistest_tutorial.pylink was created by replacing "SRC" with the path
to a file matching, under a given directory, prefixed by an arbitrary base URL.SpecThese will be replaced:[title:this,fmatch:test_tutorial,lmatch:line_match] <SRC>(remove space between] and <)title: The link title - text the user readsfmatch: substring match for the link destination filelmatch: Find matching line within that fileshow_raw: Link to raw version of file, not the one rendered by the
repo serverpath: Fix file path (usually derived by fmach)line: Fix the line number of the link (usually done via lmatch)Code Repo Hoster Specific Source LinksGithub, Gitlab, Bitbucked or Plain directory based static content servers
all have their conventional URLs regarding those links.Since all of these are just serving static content w/o js possibilities,
you have to parametrize the intended hoster in your environment, before
running a pytest / push cycle. That way the links will be working on the hoster.Currently we understand the following namespaces for links:{"github":"https://github.com/%(gh_repo_name)s/blob/%(git_rev)s/%(path)s%(line:#L%s)s","github_raw":"https://raw.githubusercontent.com/%(gh_repo_name)s/%(git_rev)s/%(path)s%(line:#L%s)s","static":"file://%(d_repo_base)s/%(path)s","static_raw":"file://%(d_repo_base)s/%(path)s"}Setting a link templateexport MD_LINKS_FOR=github# before running pytest / push<!-- md_links_for: github --># in the markdown template, staticThe latter can be overwritten by environ, should you want to push from time to time
to a different code hoster.Link RefsWe minimize the problem of varying generated target markdown, dependent on the hoster.
How? Likeany problem in IT is solved.By buildingreference linksthe differences of e.g. a README.md for github vs. gitlab is
restricted to the links section on the end of the generated markdown.
In the markdown bodies you'll just see link names, which remain the same.Check the end of therendering resultat the end of this README.md,
in order to see the results for the hoster you are reading this markdown file currently.SummaryAt normal runs of pytest, the link base URL is just a localfile://link,Before pushes one can set via environ (e.g.export MD_LINKS_FOR=github)  these e.g. to the github base URL or the repo.[key-values]constructs are supported as well, extending to beyond
just the base url. Example following:Source code showing is done like this:deftest_sh_code(self):md('Source code showing is done like this:')p2m.sh_code(self.test_sh_code)md('> Is [title:this,fmatch:test_tutorial,lmatch:exotic]<SRC> an exotic form of a recursion? ;-)  ')Isthisan exotic form of a recursion? ;-)Command Summarycat"/etc/hosts"|greplocalhost
ls"/home/gk/repos/pytest2md/tests"ls-lta/etc/hosts
/home/gk/repos/pytest2md/tests/assets/some_non_existing_command_in_assetsarg1
ls-lta|greptotal|head-n1ls-lta
ls-lta*Auto generated bypytest2md, running./tests/test_tutorial.pyTipsSkip "inner" functions, except matching ones:export P2RUN=my_func_name_matchTurn off stdout redirection:export P2MFG=true(for breakpoints in tested modules)Local Renderer:pip install gripto get a local github compliant markdown renderer, reloading after changes of the generated markdown.Using fixtures with unittest style test classesCreate aconftest.pylike:root@localhosttests]# cat conftest.pyimportpytestimportpytest2mdasp2m@pytest.fixture(scope='class')defwrite_md(request):deffin():request.cls.p2m.write_markdown(with_source_ref=True,make_toc=False)request.addfinalizer(fin)then use like:@pytest.mark.usefixtures('write_md')
class TestDevUsage(unittest.TestCase):
    p2m = p2m # on module level: p2m.P2M(__file__, fn_target_md='README.md')

    (...)IsolationNone. If you would screw up your host running pytest normally, then you will
get the same result, when running markdown generating tests.Hereis a bigger tutorial, frompytest2md.