diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f8fc38b..f8af9bc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine tox + pip install -rdev-requirements.txt - name: Run Linter run: | tox -e flake8 diff --git a/dev-requirements.txt b/dev-requirements.txt index 1cc28fb..b927e9a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,10 @@ +mistune==0.8.4 +termcolor +pyyaml setuptools +twine +tox +wheel mypy>=0.761 mypy-extensions>=0.4.3 flake8>=3.7.9 diff --git a/examples/README.md b/examples/README.md index cd24af1..d95467d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -83,13 +83,6 @@ echo "A command that will not be run" Let's breakdown what this embedded yaml annotation is doing. There are two fields in our yaml document ```name``` and ```expected_stdout_lines```. The name field simply provides a name for the step that will be printed to the report that mm.py generates. The expected_stdout_lines field is actually telling mm.py what it should be looking for from stdout when it executes our code block(s). For more on this, checkout [io.md](io.md). - -Code blocks must be tagged as either "bash" or "sh". Code from other languages will be ignored. - - - - - ## CLI ### Help @@ -98,12 +91,13 @@ For a list of options: ``` -usage: mm.py [-h] [--dry-run] [--manual] [--shell SHELL_CMD] markdown_file +usage: mm.py [-h] [--dry-run] [--manual] [--shell SHELL_CMD] [--version] + [MARKDOWN_FILE] Auto validate markdown documentation -positional arguments: optional arguments: -h, --help show this help message and exit + --version Print version and exit + + MARKDOWN_FILE The annotated markdown file to run/execute --dry-run, -d Print out the commands we would run based on - markdown_file + markdown_file --manual, -m If your markdown_file contains manual validation - steps, pause for user input + steps, pause for user input --shell SHELL_CMD, -s SHELL_CMD - Specify a different shell to use + Specify a different shell to use +``` + +### Version + + + +```bash +mm.py --version ``` + + ### Dry Run You can do a dry run to print out exactly what commands will be run using the '-d' flag. diff --git a/mechanical_markdown/__init__.py b/mechanical_markdown/__init__.py index 23f907f..327db29 100644 --- a/mechanical_markdown/__init__.py +++ b/mechanical_markdown/__init__.py @@ -4,8 +4,9 @@ Licensed under the MIT License. """ -__version__ = '0.1.6' - from mechanical_markdown.recipe import Recipe as MechanicalMarkdown +from mechanical_markdown.parsers import MarkdownAnnotationError + +__version__ = '0.1.8' -__all__ = [MechanicalMarkdown] +__all__ = [MechanicalMarkdown, MarkdownAnnotationError] diff --git a/mechanical_markdown/__main__.py b/mechanical_markdown/__main__.py index e1a52b5..074f9a5 100644 --- a/mechanical_markdown/__main__.py +++ b/mechanical_markdown/__main__.py @@ -4,7 +4,7 @@ Licensed under the MIT License. """ -from mechanical_markdown import MechanicalMarkdown +import mechanical_markdown import sys import argparse @@ -12,23 +12,39 @@ def main(): parse_args = argparse.ArgumentParser(description='Auto validate markdown documentation') - parse_args.add_argument('markdown_file', metavar='markdown_file', nargs=1, type=argparse.FileType('r')) - parse_args.add_argument('--dry-run', '-d', - dest='dry_run', + group = parse_args.add_argument_group() + group.add_argument('markdown_file', + metavar='MARKDOWN_FILE', + nargs='?', + type=argparse.FileType('r'), + help="The annotated markdown file to run/execute") + group.add_argument('--dry-run', '-d', + dest='dry_run', + action='store_true', + help='Print out the commands we would run based on markdown_file') + group.add_argument('--manual', '-m', + dest='manual', + action='store_true', + help='If your markdown_file contains manual validation steps, pause for user input') + group.add_argument('--shell', '-s', + dest='shell_cmd', + default='bash -c', + help='Specify a different shell to use') + parse_args.add_argument('--version', + dest='print_version', action='store_true', - help='Print out the commands we would run based on markdown_file') - parse_args.add_argument('--manual', '-m', - dest='manual', - action='store_true', - help='If your markdown_file contains manual validation steps, pause for user input') - parse_args.add_argument('--shell', '-s', - dest='shell_cmd', - default='bash -c', - help='Specify a different shell to use') + help='Print version and exit') args = parse_args.parse_args() - body = args.markdown_file[0].read() - r = MechanicalMarkdown(body) + if args.print_version: + parse_args.exit(status=0, message='{} version:\nv{}'.format(parse_args.prog, mechanical_markdown.__version__)) + + if args.markdown_file is None: + parse_args.error('You must provide exactly one markdown file to operate on.\n\ + Try "{} -h" for more info'.format(parse_args.prog)) + + body = args.markdown_file.read() + r = mechanical_markdown.MechanicalMarkdown(body) success = True if args.dry_run: diff --git a/mechanical_markdown/parsers.py b/mechanical_markdown/parsers.py index ecf4be0..fedd73b 100644 --- a/mechanical_markdown/parsers.py +++ b/mechanical_markdown/parsers.py @@ -14,6 +14,10 @@ end_token = 'END_STEP' +class MarkdownAnnotationError(Exception): + pass + + class HTMLCommentParser(HTMLParser): def __init__(self, **kwargs): super().__init__(**kwargs) @@ -41,7 +45,7 @@ def block_html(self, text): comment_body = comment_parser.comment_text if comment_body.find(end_token) >= 0: if self.current_step is None: - return "" + raise MarkdownAnnotationError("Unexpected found".format(end_token)) self.all_steps.append(self.current_step) self.current_step = None return "" diff --git a/mechanical_markdown/recipe.py b/mechanical_markdown/recipe.py index 1df9229..ff2bfe2 100644 --- a/mechanical_markdown/recipe.py +++ b/mechanical_markdown/recipe.py @@ -5,7 +5,7 @@ """ from mistune import Markdown -from mechanical_markdown.parsers import RecipeParser +from mechanical_markdown.parsers import RecipeParser, end_token, MarkdownAnnotationError class Recipe: @@ -13,6 +13,8 @@ def __init__(self, markdown): parser = RecipeParser() md = Markdown(parser, extensions=('fenced-code',)) md(markdown) + if parser.current_step is not None: + raise MarkdownAnnotationError('Reached end of input searching for '.format(end_token)) self.all_steps = parser.all_steps def exectute_steps(self, manual, default_shell='bash -c'): diff --git a/setup.py b/setup.py index 9e1f549..324d0b8 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ """ from setuptools import setup, find_packages +from mechanical_markdown import __version__ as version # The text of the README file README = "" @@ -18,7 +19,7 @@ # This call to setup() does all the work setup( name="mechanical-markdown", - version="0.1.6", + version=version, description="Run markdown recipes as shell scripts", long_description=README, long_description_content_type="text/markdown", diff --git a/tests/test_mechanical_markdown.py b/tests/test_mechanical_markdown.py index a491a92..06fd625 100644 --- a/tests/test_mechanical_markdown.py +++ b/tests/test_mechanical_markdown.py @@ -8,7 +8,7 @@ import subprocess import unittest -from mechanical_markdown import MechanicalMarkdown +from mechanical_markdown import MechanicalMarkdown, MarkdownAnnotationError from unittest.mock import patch, MagicMock, call @@ -437,3 +437,34 @@ def test_different_shell(self): stderr=subprocess.PIPE, universal_newlines=True, env=os.environ) + + def test_missing_end_tag_throws_exception(self): + test_data = """ + + +```bash +echo "test" +``` + +""" + with self.assertRaises(MarkdownAnnotationError): + MechanicalMarkdown(test_data) + + def test_missing_extra_tag_throws_exception(self): + test_data = """ + + +```bash +echo "test" +``` + + + + +""" + with self.assertRaises(MarkdownAnnotationError): + MechanicalMarkdown(test_data) diff --git a/tox.ini b/tox.ini index 77d5398..8642c8f 100644 --- a/tox.ini +++ b/tox.ini @@ -33,4 +33,6 @@ usedevelop = False deps = flake8 commands = flake8 --config setup.cfg . +commands_pre = + pip3 install -rdev-requirements.txt