├── .dockerignore ├── .git-blame-ignore-revs ├── .git-tag-template ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── codeql-analysis.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pre-commit-hooks.yaml ├── .style.yapf ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── byexample ├── __init__.py ├── autocomplete │ └── autocomplete_bash ├── byexample.py ├── cfg.py ├── cmdline.py ├── common.py ├── concern.py ├── concurrency.py ├── differ.py ├── example.py ├── executor.py ├── expected.py ├── extension.py ├── finder.py ├── init.py ├── jobs.py ├── log.py ├── log_level.py ├── modules │ ├── __init__.py │ ├── clipboard.py │ ├── cond.py │ ├── cpp.py │ ├── delimiters.py │ ├── elixir.py │ ├── gadgets │ │ └── byexample-repl.js │ ├── gdb.py │ ├── go.py │ ├── iasm.py │ ├── java.py │ ├── javascript.py │ ├── php.py │ ├── powershell.py │ ├── progress.py │ ├── python.py │ ├── ruby.py │ ├── rust.py │ └── shell.py ├── options.py ├── parser.py ├── parser_sm.py ├── prof.py ├── regex.py └── runner.py ├── docs ├── _config.yml ├── _includes │ └── idx.html ├── _layouts │ └── default.html ├── advanced │ ├── capture-environment-variables.md │ ├── conditional-execution.md │ ├── echo-filtering.md │ ├── geometry.md │ ├── greedy-lazy-tags.md │ ├── shebang.md │ ├── terminal-emulation.md │ ├── troubleshooting.md │ └── unicode.md ├── assets │ ├── css │ │ └── style.scss │ └── link-solid.svg ├── basic │ ├── capture-and-paste.md │ ├── input.md │ ├── normalize-whitespace.md │ ├── options.md │ ├── setup-and-tear-down.md │ ├── skip-and-pass.md │ └── timeout.md ├── contrib │ ├── concurrency-model.md │ ├── extension-initialization.md │ ├── how-to-define-new-zones-where-to-find-examples.md │ ├── how-to-hook-to-events-with-concerns.md │ └── how-to-support-new-finders-and-languages.md ├── examples │ ├── cpp.cpp │ ├── go.go │ ├── java.java │ ├── javascript.js │ ├── markdown.md │ ├── php.php │ ├── powershell.ps1 │ ├── python.py │ └── ruby.rb ├── index.md ├── languages │ ├── cpp.md │ ├── elixir.md │ ├── gdb.md │ ├── go.md │ ├── iasm.md │ ├── java.md │ ├── javascript.md │ ├── php.md │ ├── powershell.md │ ├── python.md │ ├── ruby.md │ ├── rust.md │ └── shell.md ├── overview │ ├── differences.md │ ├── faq.md │ ├── good-practices.md │ ├── usage.md │ └── where-should-I-write-the-examples.md └── recipes │ ├── advanced-checks.md │ ├── arguments-per-environment.md │ ├── django-integration.md │ ├── handy-shell-snippets.md │ ├── pre-commit.md │ ├── python-doctest.md │ ├── running-with-docker.md │ └── running-with-sudo.md ├── media ├── README.rst ├── demo.gif ├── demo.json ├── internals.png └── logos │ ├── README.md │ ├── bash_logo.png │ ├── byexample_logo.png │ ├── cpp_logo.png │ ├── elixir_logo.png │ ├── gdb_logo.png │ ├── go_logo.png │ ├── iasm_logo.png │ ├── java_logo.png │ ├── javascript_logo.png │ ├── linux_logo.png │ ├── macos_logo.png │ ├── php_logo.png │ ├── powershell_logo.png │ ├── python_logo.png │ ├── ruby_logo.png │ ├── rust_logo.png │ └── windows_logo.png ├── requirements-dev.txt ├── setup.py ├── test ├── Dockerfile ├── Dockerfile-cling ├── consistent-version.md ├── corner.env ├── corner_cases.md ├── coverage.env ├── coverage.py ├── crash.md ├── docker-cling.sh ├── docker.env ├── docker.no-python-shell.env ├── ds │ ├── UTF-8-demo.txt │ ├── about-lic-with-tags.doc │ ├── about-lic.doc │ ├── ascii_with_unicode_example.md │ ├── bad-unicode │ ├── bad │ │ ├── init │ │ │ └── init_failed.py │ │ ├── init_not_called │ │ │ ├── cfg │ │ │ │ └── badconcernnewstyle.py │ │ │ └── chk │ │ │ │ └── badconcernoldstyle.py │ │ ├── pexpect_init │ │ │ └── badrunner.py │ │ ├── pexpect_not_runner │ │ │ └── non_runner.py │ │ ├── syntax │ │ │ └── m.py │ │ └── target │ │ │ ├── invalid │ │ │ └── bad.py │ │ │ ├── missing │ │ │ └── bad.py │ │ │ └── multi │ │ │ └── warn.py │ ├── binary-blob │ ├── blog-database │ ├── capture-bomb.doc │ ├── capture-env.doc │ ├── cli.md │ ├── collisions.doc │ ├── db-stock-model │ ├── doctest-hard-diff.md │ ├── dual.md │ ├── echo-filtering-required.md │ ├── example-with-tabs-in-code.md │ ├── example-with-tabs.md │ ├── file_patterns │ │ ├── foo* │ │ └── foo{1,2} │ ├── first-example.md │ ├── for-linux.args │ ├── for-mac.args │ ├── gauss-example.md │ ├── iasm.md │ ├── lic.doc │ ├── log │ ├── maximum-ctx-input.md │ ├── minimum-ctx-input.md │ ├── mylib1.cpp │ ├── mylib1.h │ ├── mylib2.cpp │ ├── mylib2.h │ ├── mylib3.cpp │ ├── mylib3.h │ ├── mylibC.c │ ├── mylibC.h │ ├── one.md │ ├── options_file │ ├── output-bin │ ├── param-echo.c │ ├── pkg │ │ ├── bar1.py │ │ ├── bopts │ │ ├── bopts-brace │ │ ├── bopts2 │ │ ├── bopts3 │ │ ├── foo1.py │ │ ├── foo2.py │ │ └── zaz.md │ ├── python-tutorial.v1.md │ ├── python-tutorial.v2.md │ ├── shell-example │ ├── sleepy │ │ ├── c.sh │ │ ├── s1.md │ │ ├── s2.md │ │ └── s3.md │ ├── small-terminal.md │ ├── stock.sql │ ├── submod │ │ └── submod.py │ ├── template.args │ ├── terminal-ansi-unicode.md │ └── too-slow.md ├── filter-echo-tagging.env ├── github-pages.sh ├── idx.sh ├── lang-cpp-docker.env ├── lang-cpp.env ├── lang-elixir.env ├── lang-gdb.env ├── lang-go.env ├── lang-iasm.env ├── lang-java.env ├── lang-javascript.env ├── lang-php.env ├── lang-powershell.env ├── lang-python.env ├── lang-ruby.env ├── lang-rust.env ├── lang-shell.env ├── minimum-term-ansi.env ├── minimum.env ├── profiler.env ├── r.py ├── runner_version_matrix.py ├── test.md └── timming_corner_cases.md └── typosquashing ├── Makefile └── setup.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/.git-blame-ignore-revs -------------------------------------------------------------------------------- /.git-tag-template: -------------------------------------------------------------------------------- 1 | 2 | 3 | Changes from previous version: 4 | 5 | Fixes 6 | 7 | Enhancements 8 | 9 | Incompatible changes (command line) 10 | 11 | Incompatible changes (interpreters) 12 | 13 | Incompatible changes (lib) 14 | 15 | Others 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is followed by the steps to reproduce it: 12 | 1. I want to execute this example '...' 13 | 2. Then I run ``byexample`` like this '....' 14 | 3. See error 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **byexample version** 20 | Run ``byexample -V`` to know the version of ``byexample`` and ``python``. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the feature you'd like** 11 | A clear and concise description of what you want to happen. If it is related to a problem you can describe it. 12 | Ex. I'm always frustrated when [...] It will really cool to have [...] 13 | 14 | **Describe alternatives you've considered (optional)** 15 | A clear and concise description of any alternative solutions or features you've considered. 16 | 17 | **Additional context (optional)** 18 | Add any other context or screenshots about the feature request here. 19 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | 18 | jobs: 19 | analyze: 20 | name: Analyze 21 | runs-on: ubuntu-latest 22 | permissions: 23 | actions: read 24 | contents: read 25 | security-events: write 26 | 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | language: [ 'python' ] 31 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 32 | # Learn more: 33 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 34 | 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v2 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v1 42 | with: 43 | languages: ${{ matrix.language }} 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 48 | 49 | - name: Perform CodeQL Analysis 50 | uses: github/codeql-action/analyze@v1 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist 3 | __pycache__ 4 | .coverage 5 | .*.swp 6 | README.rst 7 | w/* 8 | byexample.egg-info 9 | todo 10 | .fnames.tmp 11 | .flinks.tmp 12 | test/ds/some.log 13 | test/ds/django.conf 14 | test/ds/args 15 | prof-traces 16 | .release-notes 17 | .workflow-log 18 | test/ds/good.args 19 | test/autocomplete_byexample.sh 20 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v3.2.0 6 | hooks: 7 | - id: trailing-whitespace 8 | # the following files requires to have a trailing space/tab 9 | # to test how byexample handle those cases 10 | exclude: | 11 | (?x)^( 12 | byexample/modules/java.py| 13 | byexample/modules/ruby.py| 14 | docs/advanced/terminal-emulation.md 15 | )$ 16 | 17 | - id: check-added-large-files 18 | 19 | - repo: https://github.com/pre-commit/mirrors-yapf 20 | rev: v0.31.0 21 | hooks: 22 | - id: yapf 23 | args: [-i, --style=.style.yapf] 24 | files: ^byexample/ 25 | 26 | - repo: https://github.com/commitizen-tools/commitizen 27 | # Install: 28 | # pre-commit install --hook-type commit-msg 29 | # commit type: 30 | # build: think in Makefile 31 | # ci: think in pre-commit or github hooks 32 | # docs 33 | # feat 34 | # fix 35 | # perf 36 | # refactor: think in changes that does not change output but code itself 37 | # style 38 | # test 39 | # chore: used as a fallback, a lot overlap with build and ci 40 | # revert 41 | # bump: bump a new version 42 | rev: v2.29.0 43 | hooks: 44 | - id: commitizen 45 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: byexample 2 | name: Run code snippets to validate them 3 | description: Execute code snippets in your documentation and validate them as regression tests. Your docs will never be outdated again. See https://byexamples.github.io/byexample/recipes/pre-commit 4 | entry: byexample 5 | language: python 6 | require_serial: true 7 | pass_filenames: true 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | See the 4 | [changelog](https://github.com/byexamples/byexample/releases) 5 | at Github. 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | First off, thanks for using and considering contributing to ``byexample``. 4 | 5 | ``byexample`` is an open source project that aims other projects to have 6 | high quality documentation and tests. 7 | 8 | It is for the community and we love to receive contributions from our 9 | community. 10 | 11 | This guideline will help you to go through the process of contributing 12 | from forking and reviewing the code to doing your first pull request. 13 | 14 | ## It is not just contribute code 15 | 16 | Supporting a new language or extending an existing one it is always welcome. 17 | Check this [how to](docs/how_to_support_new_finders_and_languages.md). 18 | 19 | *But it is not just contribute code.* 20 | 21 | Is ``byexample`` producing a hard-to-debug diff or did you found a bug? 22 | [Creating an issue](https://github.com/byexamples/byexample/issues) in 23 | github is as important as writing new code. 24 | 25 | Do you like to write? Write a blog or post in the social medias (I hope for the good :) 26 | 27 | Do you want to contribute but you are not sure where to start? 28 | Pick an issue from [here](https://github.com/byexamples/byexample/issues); 29 | the issues with the label ``good first issue`` is what you are looking for. 30 | 31 | ### Do not worry to do mistakes 32 | 33 | Everyone was new some day. Do your best and ask for help if you need it. 34 | 35 | 36 | ## Modules: the preferred way 37 | 38 | We love to extend ``byexample`` adding new ``modules``. 39 | 40 | Instead of editing the internal ``Parser`` class, extend it through a 41 | parser that can be loaded on the fly using ``modules`` (read this 42 | [how to](docs/how_to_support_new_finders_and_languages.md) if you 43 | didn't) 44 | 45 | Instead of editing the internal ``Runner`` class, try to a ``Concern``. 46 | 47 | If you find that a feature would be cool and the current ``Concern``'s interface 48 | (a set of hooks) is not enough, open an issue and propose an extension 49 | for ``Concern``. 50 | 51 | In this way your contributions can be merged and shipped in the next 52 | release without worrying to be incompatible with previous versions. 53 | 54 | But if you have the feeling that something cool is missing, don't be afraid 55 | and talk about it. 56 | 57 | # Warming up 58 | 59 | Go to ``github`` and make a [fork](https://guides.github.com/activities/forking/). 60 | 61 | Then, you clone it in your computer: 62 | 63 | ```shell 64 | $ git clone https://github.com//byexample.git # byexample: +skip 65 | ``` 66 | 67 | ## Regression tests 68 | 69 | Now, run a small regression tests to make sure you have a good baseline. 70 | 71 | ```shell 72 | $ make lib-test # byexample: +skip 73 | <...> 74 | [PASS] <...> 75 | ``` 76 | 77 | You can run all the examples in the documentation (this will require ``Ruby`` 78 | installed for the examples written in ``Ruby``) 79 | 80 | ```shell 81 | $ make docs-test # byexample: +skip 82 | <...> 83 | [PASS] <...> 84 | ``` 85 | 86 | The full set of test that only require ``python`` and a ``shell`` 87 | can be executed with a single command: 88 | 89 | ```shell 90 | $ make test # byexample: +skip 91 | <...> 92 | [PASS] <...> 93 | ``` 94 | 95 | To run the rest of the tests that will require ``ruby``, ``gdb``, ``gcc`` and 96 | others, we do: 97 | 98 | ```shell 99 | $ make docker-test # byexample: +skip 100 | <...> 101 | [PASS] <...> 102 | ``` 103 | 104 | As you may guessed, this will create a docker container with all the needed 105 | dependencies. 106 | 107 | The first time will take a lot of time because docker will be creating 108 | the container. 109 | 110 | ### Run a single test case 111 | 112 | Use ``byexample`` of course! 113 | 114 | To run the examples of in a doc or source file, just point to it. 115 | 116 | For example, if you fixed a bug in the Parser and you want to check 117 | that you are not introducing any new issue, run its tests in this way: 118 | 119 | ```shell 120 | $ byexample -l python byexample/parser.py # byexample: +skip 121 | ``` 122 | 123 | If the test that you want to run requires an interpreter that you 124 | don't have installed (like ``ruby`` or ``gdb``), you can 125 | start a docker container and run ``byexample`` there: 126 | 127 | ```shell 128 | $ make docker-shell # byexample: +skip 129 | ``` 130 | 131 | # How to submit a contribution 132 | 133 | If your contribution is quite small, open a pull request directly. 134 | 135 | If you think that you need some brainstorming first before working on it 136 | or you may have some question, open a ticket first. Leave the pull request 137 | for later. 138 | 139 | Try to give as much as context as you can. 140 | 141 | If it is a bug, explain what you did and what should be the correct answer. 142 | If it is a new idea, explain why do you think it would be cool? Extra points 143 | if you provide examples! 144 | 145 | No one knows everything so it is ok to ask but expect some delay in the answer, 146 | sometimes some questions are not as easier as you think! 147 | 148 | Be patient and respectful in both sides: if you are doing the question or you 149 | are answering. 150 | 151 | And don't forget, before asking, try to see if you can figure out by your own. 152 | It is ok not to know things, but people will appreciate you efforts. 153 | 154 | -------------------------------------------------------------------------------- /byexample/__init__.py: -------------------------------------------------------------------------------- 1 | '''Write snippets of code in C++, Python, Ruby, and others as documentation and execute them as regression tests.''' 2 | 3 | __version__ = "10.5.2" 4 | 5 | _author = 'Di Paola Martin' 6 | _license = 'GNU GPLv3' 7 | _url = 'https://byexamples.github.io' 8 | 9 | _license_disclaimer = r'''Copyright (C) {author} - {url} 10 | 11 | This program is free software: you can redistribute it and/or modify 12 | it under the terms of the GNU General Public License as published by 13 | the Free Software Foundation, either version 3 of the License, or 14 | (at your option) any later version. 15 | 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | 21 | You should have received a copy of the GNU General Public License 22 | along with this program. If not, see . 23 | ''' 24 | -------------------------------------------------------------------------------- /byexample/autocomplete/autocomplete_bash: -------------------------------------------------------------------------------- 1 | 2 | # Run something, muting output or redirecting it to the debug stream 3 | # depending on the value of _ARC_DEBUG. 4 | # If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC. 5 | __python_argcomplete_run() { 6 | if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then 7 | __python_argcomplete_run_inner "$@" 8 | return 9 | fi 10 | local tmpfile="$(mktemp)" 11 | _ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@" 12 | local code=$? 13 | cat "$tmpfile" 14 | rm "$tmpfile" 15 | return $code 16 | } 17 | 18 | __python_argcomplete_run_inner() { 19 | if [[ -z "${_ARC_DEBUG-}" ]]; then 20 | "$@" 8>&1 9>&2 1>/dev/null 2>&1 21 | else 22 | "$@" 8>&1 9>&2 1>&9 2>&1 23 | fi 24 | } 25 | 26 | _python_argcomplete() { 27 | local IFS=$'\013' 28 | local SHELL_PID=$$ 29 | local SUPPRESS_SPACE=0 30 | if compopt +o nospace 2> /dev/null; then 31 | SUPPRESS_SPACE=1 32 | fi 33 | COMPREPLY=( $(IFS="$IFS" \ 34 | COMP_LINE="$COMP_LINE" \ 35 | COMP_POINT="$COMP_POINT" \ 36 | COMP_TYPE="$COMP_TYPE" \ 37 | _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \ 38 | _ARGCOMPLETE=1 \ 39 | _BYEXAMPLE_ARGCOMPLETE_SHELL_PID=$SHELL_PID \ 40 | _ARGCOMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \ 41 | __python_argcomplete_run "$1") ) 42 | if [[ $? != 0 ]]; then 43 | unset COMPREPLY 44 | elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then 45 | compopt -o nospace 46 | fi 47 | } 48 | complete -o nospace -o bashdefault -F _python_argcomplete byexample 49 | -------------------------------------------------------------------------------- /byexample/byexample.py: -------------------------------------------------------------------------------- 1 | # PYTHON_ARGCOMPLETE_OK 2 | from __future__ import unicode_literals 3 | import os, sys 4 | 5 | if sys.version_info < (3, 0): 6 | print( 7 | "Are you using Python 2.x? Byexample no longer runs in that version. Please upgrade your Python environment." 8 | ) 9 | sys.exit(1) 10 | 11 | from .jobs import Jobs, Status 12 | from .log import init_log_system, shutdown_log_system 13 | 14 | 15 | def execute_examples(filename, harvester, executor, dry): 16 | from .common import human_exceptions 17 | 18 | with human_exceptions("processing the file '%s'" % filename) as exc: 19 | examples = harvester.get_examples_from_file(filename) 20 | if dry: 21 | return executor.dry_execute(examples, filename) 22 | else: 23 | return executor.execute(examples, filename) 24 | 25 | user_aborted = isinstance(exc.get('exc'), KeyboardInterrupt) 26 | error = not user_aborted 27 | return True, True, user_aborted, error 28 | 29 | 30 | def main(args=None): 31 | init_log_system() 32 | 33 | from .cmdline import parse_args 34 | from .common import human_exceptions 35 | from .init import init_byexample 36 | 37 | args = parse_args(args) 38 | 39 | jobs = Jobs(args.jobs, 'multithreading') 40 | with jobs.start_sharer() as sharer: 41 | with human_exceptions('initializing byexample') as exc: 42 | testfiles, cfg = init_byexample(args, sharer) 43 | 44 | if exc: 45 | sys.exit(Status.error) 46 | 47 | ret = jobs.run( 48 | execute_examples, testfiles, cfg['options']['fail_fast'], cfg 49 | ) 50 | 51 | shutdown_log_system() 52 | return ret 53 | -------------------------------------------------------------------------------- /byexample/concurrency.py: -------------------------------------------------------------------------------- 1 | def load_concurrency_engine(concurrency_model): 2 | if concurrency_model in ('singlethreading', 'multithreading'): 3 | from multiprocessing.dummy import Process 4 | from multiprocessing.dummy import Manager as _Threading_Manager 5 | from multiprocessing.dummy import Queue 6 | 7 | # multiprocessing.dummy.Manager is not a context manager so 8 | # it cannot be used exactly like a multiprocessing.Manager 9 | # this class closes the gap between those 10 | class Manager: 11 | def __enter__(self): 12 | return _Threading_Manager() 13 | 14 | def __exit__(self, *args): 15 | pass 16 | 17 | return (Process, Manager, Queue) 18 | 19 | elif concurrency_model == 'multiprocessing': 20 | from multiprocessing import Process 21 | from multiprocessing import Manager 22 | from multiprocessing import Queue 23 | from multiprocessing import set_start_method 24 | 25 | set_start_method('fork') # or 'spawn' or 'forkserver' 26 | return (Process, Manager, Queue) 27 | 28 | else: 29 | raise ValueError( 30 | "Unexpected concurrency model '%s'. Expected singlethreading, multithreading or multiprocessing." 31 | % concurrency_model 32 | ) 33 | 34 | 35 | class _DummyLock(object): 36 | def __enter__(self): 37 | return 38 | 39 | def __exit__(self, *args): 40 | pass 41 | 42 | def acquire(self, *args, **kargs): 43 | pass 44 | 45 | def release(self, *args, **kargs): 46 | pass 47 | -------------------------------------------------------------------------------- /byexample/extension.py: -------------------------------------------------------------------------------- 1 | class Extension: 2 | def __init__(self, *, cfg, **unused): 3 | self.__cfg = cfg 4 | 5 | @property 6 | def cfg(self): 7 | ''' Property to access the configuration object. 8 | 9 | While not enforced, you must not modify this dictionary 10 | as any change will result in an undefined behavior. 11 | 12 | Consider it as read-only. 13 | ''' 14 | try: 15 | return self.__cfg 16 | except AttributeError: 17 | raise AttributeError( 18 | "The cfg property is not set.\nDid you forget to call __init__ on an extension parent class?" 19 | ) from None 20 | 21 | def _was_extension_init_called(self): 22 | ''' Return if the constructor (__init__) was called or not ''' 23 | try: 24 | _ = self.__cfg 25 | return True 26 | except AttributeError: 27 | return False 28 | -------------------------------------------------------------------------------- /byexample/log_level.py: -------------------------------------------------------------------------------- 1 | from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL 2 | 3 | NOTE = INFO + 1 4 | CHAT = INFO - 1 5 | TRACE = DEBUG - 1 6 | 7 | 8 | def str_to_level(s): 9 | s = s.upper() 10 | return globals()[s] 11 | -------------------------------------------------------------------------------- /byexample/modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/byexample/modules/__init__.py -------------------------------------------------------------------------------- /byexample/modules/clipboard.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from byexample.concern import Concern 3 | import byexample.regex as re 4 | from functools import partial 5 | import os 6 | 7 | stability = 'provisional' 8 | 9 | 10 | class PasteError(Exception): 11 | def __init__(self, example, missing): 12 | n = "tag is" if len(missing) == 1 else "tags are" 13 | msg = "You enabled the 'paste' mode and I found some tags\nthat you did not captured before:\n %s" \ 14 | "\nMay be the example from where you copied was skipped or" \ 15 | "\nmay be the %s misspelled." % (', '.join(missing), n) 16 | 17 | Exception.__init__(self, msg) 18 | 19 | 20 | class Clipboard(Concern): 21 | target = 'clipboard' 22 | 23 | def __init__(self, *, sharer, **kargs): 24 | Concern.__init__(self, sharer=sharer, **kargs) 25 | if sharer is None: 26 | # we are in the worker thread, let's get a private copy of 27 | # the environment variables captured (if any) 28 | # this private copy will ensure that no other worker can 29 | # change the values or the examples executed (which can 30 | # change the environment but they will not change this copy) 31 | # this is a way to make the workers more independent. 32 | captured = self.cfg.options['captured_env_vars'] 33 | self.envs = { 34 | name: os.getenv(name, default='') 35 | for name in captured 36 | } 37 | 38 | def extend_option_parser(self, parser): 39 | parser.add_flag( 40 | "paste", 41 | default=False, 42 | help="enable the paste mode of captured texts." 43 | ) 44 | return parser 45 | 46 | def start(self, examples, runners, filepath, options): 47 | # get a copy as the default state of the clipboard 48 | self.clipboard = dict(self.envs) 49 | options['clipboard'] = self.clipboard 50 | 51 | @staticmethod 52 | def repl_from_clipboard(m, clipboard, missing): 53 | tag_name = m.groupdict()['name'] 54 | try: 55 | return clipboard[tag_name] 56 | except KeyError: 57 | missing.append(tag_name) 58 | return m.group(0) # replace it by itself. 59 | 60 | PASTE_RE = re.compile(r"<(?P(?:\w|-|\.)+)>") 61 | 62 | def before_build_regex(self, example, options): 63 | if not options['paste']: 64 | return 65 | 66 | repl = partial( 67 | self.repl_from_clipboard, clipboard=self.clipboard, missing=[] 68 | ) 69 | example.expected_str = re.compile(self.PASTE_RE 70 | ).sub(repl, example.expected_str) 71 | 72 | # do not check for missings: we assume that they are capture tags 73 | 74 | def finish_parse(self, example, options, exception): 75 | if exception is not None: 76 | return 77 | 78 | options.up(example.options) 79 | paste = options['paste'] 80 | options.down() 81 | 82 | if not paste: 83 | return 84 | 85 | missing = [] 86 | repl = partial( 87 | self.repl_from_clipboard, 88 | clipboard=self.clipboard, 89 | missing=missing 90 | ) 91 | example.source = re.compile(self.PASTE_RE).sub(repl, example.source) 92 | 93 | if missing: 94 | raise PasteError(example, missing) 95 | 96 | def finally_example(self, example, options): 97 | got = getattr(example, 'got', None) 98 | if got == None: 99 | return # probably the example failed so we didn't get any output 100 | _, captured = example.expected.get_captures(example, got, options, 0) 101 | self.clipboard.update(captured) 102 | -------------------------------------------------------------------------------- /byexample/modules/cond.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from byexample.concern import Concern 3 | from functools import partial 4 | 5 | stability = 'experimental' 6 | 7 | 8 | class UnknownConditionTag(Exception): 9 | def __init__(self, example, missing): 10 | msg = "You enabled the conditional execution of this example " \ 11 | "based on the tag that *is not* in the clipboard.\n" \ 12 | "May be the example from where you capture it was skipped," \ 13 | "may be the tag '%s' is misspelled or may be the Clipboard " \ 14 | "was disabled." % (missing) 15 | 16 | Exception.__init__(self, msg) 17 | 18 | 19 | class Conditional(Concern): 20 | target = 'conditional' 21 | 22 | def extend_option_parser(self, parser): 23 | mutexg = parser.add_mutually_exclusive_group() 24 | mutexg.add_argument( 25 | "+if", 26 | "+on", 27 | metavar='', 28 | nargs=1, 29 | default=False, 30 | help= 31 | "run the example only if the tag is non-empty; skip the example otherwise." 32 | ) 33 | mutexg.add_argument( 34 | "+unless", 35 | metavar='', 36 | nargs=1, 37 | default=True, 38 | help= 39 | "run the example unless the tag is non-empty; skip the example otherwise." 40 | ) 41 | return parser 42 | 43 | def finish_parse(self, example, options, exception): 44 | if exception is not None: 45 | return 46 | 47 | options.up(example.options) 48 | ifcond = options['if'] 49 | uncond = options['unless'] 50 | options.down() 51 | 52 | if ifcond is not False: 53 | cond = ifcond[0] 54 | neg = True 55 | elif uncond is not True: 56 | cond = uncond[0] 57 | neg = False 58 | else: 59 | return 60 | 61 | clipboard = options.get('clipboard', {}) 62 | if cond not in clipboard: 63 | raise UnknownConditionTag(example, cond) 64 | 65 | skip = bool( 66 | clipboard[cond] 67 | ) # TODO emptiness is enough?: what about strings like '0' and 'false'? 68 | if neg: 69 | skip = not skip 70 | 71 | if skip: 72 | example.options['skip'] = True 73 | -------------------------------------------------------------------------------- /byexample/modules/gadgets/byexample-repl.js: -------------------------------------------------------------------------------- 1 | const _repl_module = require('repl') 2 | // other interesting flags: useGlobal, ignoreUndefined, replMode 3 | var _repl = _repl_module.start({prompt: 'node > ', 4 | useColors: false, 5 | terminal: false, 6 | ignoreUndefined: true}) 7 | -------------------------------------------------------------------------------- /byexample/modules/javascript.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Example: 3 | > function hello() { 4 | . console.log("hello bla world") 5 | . } 6 | 7 | > hello() // byexample: +norm-ws 8 | hello <...> world 9 | 10 | > var j = 2; 11 | > for (var i = 0; i < 4; ++i) { 12 | . j += i; 13 | . }; 14 | 8 15 | 16 | > j + 3 17 | 11 18 | 19 | > console.log("this\n\ 20 | . is a multiline\n\ 21 | . string"); 22 | this 23 | is a multiline 24 | string 25 | 26 | > /* this 27 | . is a multiline 28 | . comment */ 29 | 30 | > 42 31 | 42 32 | 33 | These requires to use +pass because the output from the interpreter 34 | gets mixed with the string typed in. 35 | *However* they never worked. 36 | > const readline = require('readline'); 37 | > const rl = readline.createInterface({ 38 | . input: process.stdin, 39 | . terminal: false 40 | . }); 41 | 42 | > var num; 43 | > rl.question('num: ', (n) => { // byexample: +input +pass +skip 44 | . num = n; 45 | . }); 46 | num: [42] 47 | 48 | > num // byexample: +skip 49 | 42 50 | """ 51 | 52 | from __future__ import unicode_literals 53 | import byexample.regex as re 54 | from byexample.common import constant, abspath 55 | from byexample.parser import ExampleParser 56 | from byexample.finder import ExampleFinder 57 | from byexample.runner import ExampleRunner, PexpectMixin 58 | 59 | stability = 'experimental' 60 | 61 | 62 | class JavascriptPromptFinder(ExampleFinder): 63 | target = 'javascript-prompt' 64 | 65 | @constant 66 | def example_regex(self): 67 | return re.compile( 68 | r''' 69 | (?P 70 | (?:^(?P [ ]*) (?:>)[ ] .*) # PS1 line 71 | (?:\n [ ]* \.[ ] .*)*) # PS2 lines 72 | \n? 73 | ## Want consists of any non-blank lines that do not start with PS1 74 | (?P (?:(?![ ]*$) # Not a blank line 75 | (?![ ]*(?:>)) # Not a line starting with PS1 76 | .+$\n? # But any other line 77 | )*) 78 | ''', re.MULTILINE | re.VERBOSE 79 | ) 80 | 81 | def get_language_of(self, *args, **kargs): 82 | return 'javascript' 83 | 84 | def get_snippet_and_expected(self, match, where): 85 | snippet, expected = ExampleFinder.get_snippet_and_expected( 86 | self, match, where 87 | ) 88 | 89 | snippet = self._remove_prompts(snippet) 90 | return snippet, expected 91 | 92 | def _remove_prompts(self, snippet): 93 | lines = snippet.split("\n") 94 | return '\n'.join(line[2:] for line in lines) 95 | 96 | 97 | class JavascriptParser(ExampleParser): 98 | language = 'javascript' 99 | 100 | @constant 101 | def example_options_string_regex(self): 102 | return re.compile(r'//\s*byexample:\s*([^\n\'"]*)$', re.MULTILINE) 103 | 104 | def extend_option_parser(self, parser): 105 | pass 106 | 107 | 108 | class JavascriptInterpreter(ExampleRunner, PexpectMixin): 109 | language = 'javascript' 110 | 111 | def __init__(self, **kargs): 112 | ExampleRunner.__init__(self, **kargs) 113 | PexpectMixin.__init__( 114 | self, PS1_re=r'node > ', any_PS_re=r'(?:node > )|(?:\.\.\. )' 115 | ) 116 | 117 | def run(self, example, options): 118 | return PexpectMixin._run(self, example, options) 119 | 120 | def _run_impl(self, example, options): 121 | return self._exec_and_wait( 122 | example.source, options, from_example=example 123 | ) 124 | 125 | def interact(self, example, options): 126 | PexpectMixin.interact(self) 127 | 128 | def get_default_cmd(self, *args, **kargs): 129 | return "%e %p %a", { 130 | 'e': '/usr/bin/env', 131 | 'p': 'nodejs', 132 | 'a': [abspath(__file__, 'gadgets', 'byexample-repl.js')] 133 | } 134 | 135 | def get_default_version_cmd(self, *args, **kargs): 136 | return "%e %p %a", { 137 | 'e': '/usr/bin/env', 138 | 'p': 'nodejs', 139 | 'a': ["--version"] 140 | } 141 | 142 | @constant 143 | def get_version(self, options): 144 | return self._get_version(options) 145 | 146 | def initialize(self, options): 147 | cmd = self.build_cmd(options, *self.get_default_cmd()) 148 | 149 | # run! 150 | self._spawn_interpreter(cmd, options) 151 | 152 | self._drop_output() # discard banner and things like that 153 | 154 | def shutdown(self): 155 | self._shutdown_interpreter() 156 | 157 | def cancel(self, example, options): 158 | return False # not supported by nodejs 159 | -------------------------------------------------------------------------------- /byexample/regex.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | 4 | escape = re.escape 5 | compile = re.compile 6 | match = re.match 7 | fullmatch = re.fullmatch 8 | search = re.search 9 | sub = re.sub 10 | subn = re.subn 11 | split = re.split 12 | findall = re.findall 13 | finditer = re.finditer 14 | 15 | # 'purge' is not public, it should not be needed 16 | 17 | # Borrow from regex module its uppercase FLAGS 18 | # so they are accessible from importing this module directly 19 | module = sys.modules[__name__] 20 | for sym in (sym for sym in dir(re) if sym.isupper()): 21 | setattr(module, sym, getattr(re, sym)) 22 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect 2 | title: byexample 3 | description: Write snippets of code in your documentation and execute them as regression tests. 4 | github: 5 | owner_name: Martin Di Paola 6 | owner_url: https://github.com/eldipa 7 | repository_name: byexample 8 | repository_url: https://github.com/byexamples/byexample 9 | uprefix: byexample 10 | -------------------------------------------------------------------------------- /docs/_includes/idx.html: -------------------------------------------------------------------------------- 1 |

Quick Overview

2 | 3 | 11 | 12 |

Basics Options

13 | 14 | 23 | 24 |

Advanced Options

25 | 26 | 37 | 38 |

How to Contribute

39 | 40 | 48 | 49 |

Languages Supported

50 | 51 | 64 | 65 |

Recipes and Tricks

66 | 67 | 77 | 78 |

Support Needed for...

79 | 80 | 84 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 35 | {% seo %} 36 | 37 | 38 | 39 |
40 |
41 | 42 |

{{ site.title | default: site.github.repository_name }}

43 |
44 |

{{ site.description | default: site.github.project_tagline }}

45 | {% if site.github.is_project_page %} 46 | View project on GitHub 47 | {% endif %} 48 | {% if site.github.is_user_page %} 49 | Follow me on GitHub 50 | {% endif %} 51 |
52 |
53 | 54 |
55 |
56 |
57 | {{ content }} 58 |
59 | 60 | 80 |
81 |
82 | 85 | {% if site.google_analytics %} 86 | 94 | {% endif %} 95 | 96 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /docs/advanced/capture-environment-variables.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Environment Variables 11 | 12 | `byexample` passes the environment variables to the 13 | interpreters and they are available from the examples and modules. 14 | 15 | This is the default and quite handy if you want to make your 16 | documentation *configurable* for *scripting*. 17 | 18 | Let's say you have a [shell](/{{ site.uprefix }}/languages/shell.md) 19 | example: 20 | 21 | ```shell 22 | $ if [ "$PWD" != "/root" ]; then 23 | > echo "You must run this script from /root" 24 | > fi 25 | You must run this script from /root 26 | ``` 27 | 28 | 29 | ## Pasting a captured environment variable 30 | 31 | But what if we want to *capture* their value and 32 | [paste them](/{{ site.uprefix }}/basic/capture-and-paste.md) as part of 33 | an example? 34 | 35 | Consider an example where a Python example *depends* on the current 36 | directory. 37 | 38 | Because you could be running `byexample` from *anywhere*, such example 39 | would likely fail: the output is not deterministic. 40 | 41 | However you could [paste](/{{ site.uprefix 42 | }}/basic/capture-and-paste.md) the environment variable `$PWD` which has 43 | the current directory as the expected output. 44 | 45 | ```shell 46 | $ cat test/ds/capture-env.doc # byexample: +rm=  -tags 47 |     >>> import os 48 |     >>> assert '' == os.getcwd() # byexample: +paste 49 | ``` 50 | 51 | You just need to pass which variables from the environment will be in 52 | the clipboard of `byexample` so they can be pasted. 53 | 54 | ```shell 55 | $ byexample -l python --capture-env-var PWD test/ds/capture-env.doc 56 | <...> 57 | [PASS] Pass: 2 Fail: 0 Skip: 0 58 | ``` 59 | 60 | You can capture as many environment variables as you want: 61 | 62 | ```shell 63 | $ byexample -l python --capture-env-vars PWD,HOME,USER test/ds/capture-env.doc 64 | <...> 65 | [PASS] Pass: 2 Fail: 0 Skip: 0 66 | ``` 67 | 68 | ## Conditional execution on a captured environment variable 69 | 70 | Perhaps the most useful combination is with 71 | [conditionals](/{{site.uprefix }}/advanced/conditional-execution.md). 72 | 73 | Consider the following toy-example that it is executed *unless* the 74 | given environment variable is passed: 75 | 76 | ```shell 77 | $ cat test/ds/capture-bomb.doc # byexample: +rm=  78 |     $ echo 'Booom!' # byexample: +unless=bomb_disabled 79 |     The bomb should not explode! 80 | ``` 81 | 82 | Running the example with an empty or unset variable `bomb_disabled` will 83 | make the example to run and fail (as expected, the bomb exploded). 84 | 85 | ```shell 86 | $ echo $bomb_disabled # empty, it is not set 87 | $ byexample -l shell --capture-env-var bomb_disabled test/ds/capture-bomb.doc 88 | <...> 89 | Expected: 90 | The bomb should not explode! 91 | Got: 92 | Booom! 93 | <...> 94 | [FAIL] Pass: 0 Fail: 1 Skip: 0 95 | ``` 96 | 97 | But setting anything to the variable we can *skip* the execution of the 98 | example. 99 | 100 | ```shell 101 | $ export bomb_disabled=some 102 | $ byexample -l shell --capture-env-var bomb_disabled test/ds/capture-bomb.doc 103 | <...> 104 | [PASS] Pass: 0 Fail: 0 Skip: 1 105 | ``` 106 | 107 | > **Note:** in both cases `bomb_disabled` was put in the clipboard, this 108 | > is required because `+unless` will complain if the variable is not 109 | > there (empty or not empty). 110 | 111 | > *New* in ``byexample 10.1.0``. This feature is marked as ``experimental``. 112 | -------------------------------------------------------------------------------- /docs/advanced/conditional-execution.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Conditional Execution 11 | 12 | ``byexample`` allows *conditional execution* of an example. 13 | 14 | Consider the following real situation: 15 | 16 | ``byexample`` supports the execution of shell commands using different 17 | shells: ``dash``, ``ksh`` and ``bash``. 18 | 19 | To prove this I should write three examples, each using a different 20 | shell. 21 | 22 | But what happen if the environment where ``byexample`` is running does not 23 | have one of the shells? 24 | 25 | Failing is not fun. Forcing the user to install all the shells 26 | just to run the documentation/test is not fun either. 27 | 28 | To address this, ``byexample`` allows you to check if a condition matches 29 | before executing an example. 30 | 31 | First, we test if a given shell exists and we 32 | [capture](/{{ site.uprefix }}/basic/capture-and-paste.md) the output: 33 | 34 | ```shell 35 | $ hash ksh 2>/dev/null && echo "installed" 36 | 37 | ``` 38 | 39 | If the ``ksh`` shell is installed in the system, the ```` tag 40 | will contain the *non-empty string* ``installed``. If not, it will be *empty*. 41 | 42 | Then we can write the conditional example: 43 | 44 | ```shell 45 | $ byexample -l shell -o '+shell=ksh' test/ds/shell-example # byexample: +if=ksh-installed 46 | <...> 47 | [PASS] Pass: 14 Fail: 0 Skip: 0 48 | ``` 49 | 50 | The ``+if`` option receives the name of a tag: if this capture tag is empty, the 51 | example is [skipped](/{{ site.uprefix }}/basic/skip-and-pass.md), 52 | otherwise it is executed as usual. 53 | 54 | The ``+on`` is an alias of ``+if``; ``+unless`` works the same way but 55 | it negates the condition. 56 | 57 | ```shell 58 | $ echo non-empty-string 59 | 60 | 61 | $ echo executed # byexample: +if=good 62 | executed 63 | 64 | $ echo executed # byexample: +on=good 65 | executed 66 | 67 | $ echo not-executed # byexample: +unless=good 68 | this-will-never-run 69 | ``` 70 | 71 | > *New* in ``byexample 8.1.0``. This feature is marked as ``experimental``. 72 | -------------------------------------------------------------------------------- /docs/advanced/echo-filtering.md: -------------------------------------------------------------------------------- 1 | 10 | # Echo filtering 11 | 12 | When you type something in a console or in an interactive interpreter, 13 | what you type is *echoed* so you can see what you are typing. 14 | 15 | `byexample` disables this so it can capture the output of an example 16 | without having the *typed* example mixed with it. 17 | 18 | However not all the interpreters honor this and they may leave turned on 19 | the echo anyway. 20 | 21 | For example this shell example uses `stty` to re-enable the echoing: 22 | 23 | ```shell 24 | $ echo "normal output" 25 | normal output 26 | 27 | $ stty echo 28 | 29 | $ echo "normal output" 30 | echo "normal output" 31 | normal output 32 | ``` 33 | 34 | > *Changed* in `byexample 11.0.0`: since `11.0.0`, the echo is disabled 35 | > before executing each example. This makes the execution more robust 36 | > against changes in the terminal settings. 37 | > Before `11.0.0` the echo was disabled only on the interpreter startup 38 | > only. 39 | > You may change this using `-x-turn-echo-off` but be careful. 40 | 41 | Having to expect what you typed is ugly and confusing for your readers. 42 | 43 | `byexample` has the *experimental* ability to suppress the echoing with 44 | `+force-echo-filtering`: 45 | 46 | 47 | ```shell 48 | $ echo "normal output" # byexample: +force-echo-filtering 49 | normal output 50 | ``` 51 | 52 | You can pass `+force-echo-filtering` from the command line to take effect 53 | on all the examples of all the languages but this is not encouraged 54 | and since `byexample 11.0.0` you can enforce the echo filtering 55 | per language with `+force-echo-filtering-for` 56 | 57 | ```shell 58 | $ byexample -l shell,python -o "+force-echo-filtering-for=shell" test/ds/echo-filtering-required.md # byexample: +timeout 8 59 | <...> 60 | File test/ds/echo-filtering-required.md, <...> seconds 61 | [PASS] <...> 62 | ``` 63 | 64 | Both `+force-echo-filtering` and `+force-echo-filtering-for` from the 65 | command line is not allowed. 66 | 67 | ```shell 68 | $ byexample -l shell,python -o "+force-echo-filtering-for=shell +force-echo-filtering" test/ds/echo-filtering-required.md # byexample: +timeout 8 69 | <...> 70 | argument +force-echo-filtering: not allowed with argument +force-echo-filtering-for 71 | <...> 72 | ``` 73 | 74 | > *New* in `byexample 11.0.0`: before `11.0.0`, the only way to apply 75 | > the filtering to all the examples was using `+force-echo-filtering` 76 | > from the command line but unfortunately that affects all the rest 77 | > of interpreters that may not require the filtering. 78 | > 79 | > Since `11.0.0` you should use `+force-echo-filtering-for` in the command 80 | > line to affect all the examples of the language(s) selected and 81 | > use `+force-echo-filtering` **only** when you want to enable the 82 | > filtering in a particular example. 83 | 84 | 85 | ## Limitations and restrictions 86 | 87 | Forcing a filtering comes with some restrictions: 88 | - the example will use a full [terminal emulation](/{{ site.uprefix}}/advanced/terminal-emulation), 89 | in other words `+force-echo-filtering` implies `+term=ansi`. 90 | - due the emulation, your output will be limited by the 91 | [geometry](/{{ site.uprefix }}/advanced/geometry) 92 | of the emulated terminal; you may have to set this too at the begin 93 | of your document. 94 | - the echo must exist otherwise the filter may filter part of your 95 | output. 96 | - it is an *experimental* feature. 97 | 98 | If possible, try to disable the echo from the interpreter itself and 99 | relay on `+force-echo-filtering` as a last resort. 100 | 101 | For example, in `python` you can use `termios` to control the echoing: 102 | 103 | ```python 104 | >>> import sys 105 | >>> from termios import tcgetattr, tcsetattr, TCSANOW, ECHO 106 | 107 | >>> def set_echo_mode(enable): 108 | ... fd = sys.stdin.fileno() 109 | ... attrs = tcgetattr(fd) 110 | ... 111 | ... if enable: 112 | ... attrs[3] |= ECHO 113 | ... else: 114 | ... attrs[3] &= ~ECHO 115 | ... 116 | ... tcsetattr(fd, TCSANOW, attrs) 117 | 118 | >>> set_echo_mode(False) 119 | <...> 120 | ``` 121 | 122 | -------------------------------------------------------------------------------- /docs/advanced/geometry.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Terminal Geometry 11 | 12 | When ``byexample`` runs a set of examples, it spawns one or more runners 13 | inside a [virtual terminal](/{{ site.uprefix }}/advanced/terminal-emulation) 14 | that is 24 lines high and 80 columns wide. 15 | 16 | The dimension or geometry can affect how the runner will print in the 17 | terminal. 18 | 19 | Consider the following ``Python`` examples that show a short and a long 20 | list of numbers: 21 | 22 | ```python 23 | >>> list(range(10)) 24 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 25 | 26 | >>> list(range(25)) 27 | [0, 28 | 1, 29 | <...> 30 | 24] 31 | ``` 32 | 33 | ``Python``'s pretty printer prints the first in one line because its width is 34 | less than 80; the second example, longer than the first, will span multiple 35 | lines. 36 | 37 | ``byexample`` allows you to control the geometry of the *virtual terminal*: 38 | 39 | ```python 40 | >>> list(range(25)) # byexample: +geometry 24x127 41 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, <...>, 20, 21, 22, 23, 24] 42 | ``` 43 | 44 | The syntax is simply ``x`` 45 | 46 | ## A warning note 47 | 48 | Changing the geometry of the *virtual terminal* is **totally dependent** 49 | on the runner/interpreter used. 50 | 51 | By default ``byexample`` sends a ``SIGWINCH`` signal to the interpreter 52 | which may or may not have an effect. 53 | 54 | In addition to that, for ``Python``, its pretty printer is modified too. 55 | 56 | 104 | 105 | ## Changing geometry from the start 106 | 107 | You may decide to set the geometry from the beginning. In this 108 | case it will affect all the interpreters. 109 | 110 | Consider the examples in ``small-terminal.md`` 111 | 112 | ```shell 113 | $ byexample -l python,shell test/ds/small-terminal.md 114 | <...> 115 | File "test/ds/small-terminal.md", line 2 116 | Failed example: 117 | echo ${LINES}x${COLUMNS} 118 | Expected: 119 | 24x60 120 | Got: 121 | 24x80 122 | <...> 123 | File "test/ds/small-terminal.md", line 6 124 | Failed example: 125 | ['aaaaa', 'aaaaa', 'aaaaa', 'aaaaa', 'aaaaa', 'aaaaa', 'aaaaa'] 126 | Expected: 127 | ['aaaaa', 128 | 'aaaaa', 129 | 'aaaaa', 130 | <...> 131 | 'aaaaa'] 132 | Got: 133 | ['aaaaa', 'aaaaa', 'aaaaa', 'aaaaa', 'aaaaa', 'aaaaa', 'aaaaa'] 134 | <...> 135 | ``` 136 | 137 | They fail because the examples are expecting to be executed 138 | in a smaller terminal of size 24x60. 139 | 140 | Here we have them pass: 141 | 142 | ```shell 143 | $ byexample -l python,shell -o '+geometry 24x60' test/ds/small-terminal.md 144 | <...> 145 | File test/ds/small-terminal.md, 2/2 test ran in <...> seconds 146 | [PASS] Pass: 2 Fail: 0 Skip: 0 147 | ``` 148 | 149 | ``byexample`` will pass the geometry to the runner in two special environment 150 | variables: ``LINES`` and ``COLUMNS``. 151 | 152 | This is only done at the beginning, if you change the geometry later these 153 | variables may not be updated. 154 | -------------------------------------------------------------------------------- /docs/advanced/greedy-lazy-tags.md: -------------------------------------------------------------------------------- 1 | # Greedy and Lazy Tags 2 | 3 | A tag is marked with the symbols ``<`` and ``>`` and can be either of two types: 4 | named like ```` or unnamed like ``<...>``. 5 | 6 | Both kinds of tags can match anything but there is a small difference. 7 | 8 | Because the named tags are used to [capture](/{{ site.uprefix }}/basic/capture-and-paste) 9 | a given string and [paste](/{{ site.uprefix }}/basic/capture-and-paste) it later, 10 | it is assumed that a named tag is intended to 11 | match a small string, therefore the regex used is non-greedy or lazy (``.*?``). 12 | 13 | The usage of unnamed tags is more diffuse: they can be used to ignore 14 | small portions or large multiline ones. 15 | 16 | The heuristic is that the unnamed tags are non-greedy by 17 | default but the unamed tags *at the end of a line* are greedy (``.*``). 18 | 19 | Here are some examples of the implications of this difference. 20 | 21 | Consider the following string. 22 | 23 | ```python 24 | >>> a = '''x 1 oneline 25 | ... x 2 twoline 26 | ... x 3 fooline 27 | ... x 4 barline''' 28 | ``` 29 | 30 | If we are interested in only the last two numbers we could write 31 | something like this: 32 | 33 | ```python 34 | >>> print(a) 35 | <...> 36 | x fooline 37 | x barline 38 | 39 | >>> print("""foo: ''\nbar: ''""") # byexample: +paste 40 | foo: '3' 41 | bar: '4' 42 | ``` 43 | 44 | This works because the unnamed tag is at the end of the line and therefore 45 | its regex is greedy. 46 | 47 | If this wasn't the case, the named tag below will probably be forced to 48 | capture more strings than intended. 49 | 50 | Here is the same example but instead of using an unnamed tag, we use 51 | a named tag to force it to be non-greedy. See what happens with the ``foo`` 52 | and ``bar`` captures: 53 | 54 | ```python 55 | >>> print(a) 56 | 57 | x fooline 58 | x barline 59 | 60 | >>> print("""foo: ''\nbar: ''""") # byexample: +paste 61 | foo: '2 twoline 62 | x 3' 63 | bar: '4' 64 | ``` 65 | 66 | Consider now another example. It works because the unnamed tags are non-greedy 67 | except on the end (in this example we want to capture Joe's token) 68 | 69 | ```python 70 | >>> print('{user=john,attr=2,token=53,age=33;user=joe,attr=3,token=111,age=33;user=jane,attr=12,token=153,age=3}') 71 | <...>user=joe,<...>,token=,<...> 72 | 73 | >>> print("""token: ''""") # byexample: +paste 74 | token: '111' 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/advanced/shebang.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Shebang 11 | 12 | The examples are executed by a specific runner based on the language of 13 | the examples. 14 | 15 | In general, the runner is an interactive interpreter like ``irb`` for ``Ruby`` 16 | or ``python`` for ``Python``. 17 | 18 | Sometimes it is convenient to change how the interpreter is executed for: 19 | - using another one (must be compatible) 20 | - adding or removing environment variables 21 | - redirecting the standard error 22 | - executing it remotely 23 | 24 | Consider the following example that prints interesting things to standard output 25 | and debug/uninteresting things to standard error: 26 | 27 | ``` 28 | $ cat test/ds/blog-database # byexample: +rm=  29 |   >>> from __future__ import print_function 30 |   >>> import sys 31 |   32 |   >>> def load_database(): 33 |   ... print("Loading...") 34 |   ... print("debug 314kb", file=sys.stderr) 35 |   ... print("Done") 36 |   37 |   >>> load_database() 38 |   Loading... 39 |   Done 40 |   41 | ``` 42 | 43 | Running this will fail because the debug print will be mixed with the normal 44 | prints and the example above expects only the *normal* outputs. 45 | 46 | ``` 47 | $ byexample -l python test/ds/blog-database 48 | <...> 49 | Expected: 50 | Loading... 51 | Done 52 | Got: 53 | Loading... 54 | debug 314kb 55 | Done 56 | <...> 57 | ``` 58 | 59 | Yes, changing the example solves this but what happen if you cannot change it? 60 | 61 | What you can do is to redirect the standard error of the interpreter, 62 | ``python`` in this case, using the ``-x-shebang`` option: 63 | 64 | ``` 65 | $ byexample -l python \ 66 | > -x-shebang "python:/bin/sh -c '%e %p %a 2>/dev/null'" \ 67 | > test/ds/blog-database 68 | <...> 69 | [PASS] Pass: 4 Fail: 0 Skip: 0 70 | ``` 71 | 72 | Don't be scared, the expression ``python:/bin/sh -c '%e %p %a 2>/dev/null'`` 73 | sets how to execute a runner for ``python``. 74 | 75 | The ``%e``, ``%p``, ``%a`` tokens are replaced by ``byexample`` with the 76 | environment, program name and arguments. 77 | 78 | Each runner has its own set of values for those tokens. 79 | 80 | To simplify let's assume that ``%e`` and ``%a`` are empty and ``%p`` 81 | is ``python``. 82 | 83 | So the shebang after the substitutions is ``/bin/sh -c 'python 2>/dev/null'`` 84 | 85 | This one in turns means: spawn a ``/bin/sh`` shell with ``-c`` and 86 | ``'python 2>/dev/null'`` as arguments. 87 | 88 | ``-c`` means execute the next argument as a shell command, so this will 89 | execute ``python 2>/dev/null`` and the ``2>/dev/null`` mean that the standard 90 | error should be discarded. 91 | 92 | If your shell-fu is a little rusty and the shebang is too magic, don't worry 93 | I had the same problem; *it's for very specific situations* and you shouldn't 94 | need to worry about this most of the time. 95 | 96 | In addition to `%e`, `%p` and `%a` you can use `%w` to replace it with 97 | the current working directory. 98 | 99 | > *New* in ``byexample 11.0.0``: the `%w` wildcard. 100 | 101 | If you need more specific customization you may want to consider 102 | [creating your own runner](/{{ site.uprefix }}/contrib/how-to-support-new-finders-and-languages). 103 | Go ahead, it is much easier than you think. 104 | -------------------------------------------------------------------------------- /docs/advanced/troubleshooting.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Troubleshooting 11 | 12 | `byexample` does its best effort to detect common errors and display a 13 | solution but not always is easy. 14 | 15 | ## Interpreter not found 16 | 17 | `byexample` uses for most of the languages an *external* interpreter 18 | which must be installed already (`byexample` will not install it) 19 | and be available from the `PATH`. 20 | 21 | In the following example I'm explicitly using an non-existent `python` 22 | interpreter to show you the error: 23 | 24 | ```shell 25 | $ byexample -l python -x-shebang 'python:python2.99' test/crash.md # byexample: +rm=  26 | [w] Initialization of Python Runner failed. 27 | [!] Something went wrong processing the file 'test/crash.md': 28 | The command was not found or was not executable. 29 | The full command line tried is as follows: 30 | python2.99 31 |   32 | This could happen because you do not have it installed or 33 | it is not in the PATH. 34 |   35 | Rerun with -vvv to get a full stack trace. 36 | ``` 37 | 38 | If the interpreter is installed but `byexample` cannot find it, you can 39 | use `-x-shebang` to put an explicit path. 40 | 41 | Something like `-x-shebang 'python:/foo/bar/python2.99'`. See more about 42 | [-x-shebang](/{{ site.uprefix }}/advanced/shebang) to 43 | see the complete syntax. 44 | 45 | If you still have troubles, considere 46 | [opening a ticket](https://github.com/byexamples/byexample/issues). 47 | 48 | ## Prompt not found / Timeout 49 | 50 | Every example has a timeout. If `byexample` does not detect the *prompt* 51 | from the underlying interpreter it will assume that the example hanged. 52 | 53 | ```shell 54 | $ byexample -l python --timeout 1 --ff test/ds/too-slow.md # byexample: +timeout=15 55 | <...> 56 | File "test/ds/too-slow.md", line 7 57 | Failed example: 58 | sleep(1.1) 59 | => Execution timedout at example 3 of 5. 60 | - This could be because the example just ran too slow (try add more time 61 | with +timeout=) or the example is "syntactically incorrect" and 62 | the interpreter hang (may be you forgot a parenthesis or something like that?). 63 | <...> 64 | [FAIL] Pass: 2 Fail: 1 Skip: 2 65 | ``` 66 | 67 | A timeout can happen because the example is taking too much time or the 68 | example is *syntactically incorrect*. 69 | 70 | When an interpreter runs an *syntactically incorrect* example, it may 71 | confuse and wait for more input from the user, input that it will not 72 | receive. 73 | 74 | Solution? Do a quick check 75 | [increasing the timeout](/{{ site.uprefix }}/basic/timeout) with `+timeout=N`. 76 | If the problem disappears the example was taking too much time. 77 | 78 | If the problem persists, it is probably a syntax error somewhere. 79 | 80 | If the error happens in the first example or during the initialization 81 | of the interpreter/runner, it may be an incompatibility with 82 | `byexample`. Consider 83 | [opening a ticket](https://github.com/byexamples/byexample/issues) in 84 | that case. 85 | 86 | 87 | ## Interpreter closed unexpectedly 88 | 89 | When you run an example, `byexample` uses a interpreter or runner for 90 | its execution depending of the language that the example was written. 91 | 92 | If the example si Python, `byexample` will use a `python` interpreter. 93 | 94 | The interpreter may close unexpectedly. `byexample` will detect this and 95 | it will abort the execution. 96 | 97 | You will see something like this: 98 | 99 | ```shell 100 | $ byexample -l python test/crash.md # byexample: +rm=  101 | <...> 102 | File "test/crash.md", line 2 103 | Failed example: 104 | exit() # I am a bad example ;) 105 | => Execution of example 1 of 1 crashed. 106 | - Interpreter closed unexpectedly: the interpreter or runner closed unexpectedly. 107 | This could happen because the example triggered a close/shutdown/exit action, 108 | the interpreter was killed by someone else or because the interpreter just crashed. 109 |   110 | If the interpreter is just crashing, it may be possible to find a workaround, 111 | you can open an issue at https://github.com/byexamples/byexample/issues 112 |   113 | => Execution aborted at example 1 of 1. 114 | - Some resources may had not been cleaned. 115 |   116 | File test/crash.md, 1/1 test ran in <...> 117 | [ABORT] Pass: 0 Fail: 0 Skip: 0 118 | ``` 119 | 120 | Why an interpreter may close? There are a few possibilities: 121 | 122 | - the example explicitly request the close of the interpreter (like in 123 | the example above). 124 | - the example, somehow, typed `ctrl-D` (linux) or `ctrl-z` (windows) 125 | which most of the interpreters see this as a close signal. 126 | - the interpreter was *killed* by someone else (like someone runs `kill 127 | -15`). 128 | - if this happens during the *initialization* of the interpreter, may 129 | be it is not supported by `byexample`. 130 | - the interpreter just crashed. 131 | 132 | What to do? Try to write the smallest example possible that triggers the 133 | close. You may find which piece of your example is making the close. 134 | 135 | If the interpreter is just crashing or it is failing during the 136 | initialization phase, consider 137 | [opening a ticket](https://github.com/byexamples/byexample/issues). May 138 | be it is possible to code a workaround. 139 | 140 | 141 | -------------------------------------------------------------------------------- /docs/assets/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @import "{{ site.theme }}"; 5 | 6 | $code-bg-color: #f3f6fa !default; 7 | $code-text-color: #567482 !default; 8 | $border-color: #dce6f0 !default; 9 | 10 | code { 11 | padding: 2px 4px; 12 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 13 | font-size: 0.9rem; 14 | color: $code-text-color; 15 | background-color: $code-bg-color; 16 | border-radius: 0.3rem; 17 | } 18 | 19 | pre { 20 | padding: 0.8rem; 21 | margin-top: 0; 22 | margin-bottom: 1rem; 23 | font: 1rem Consolas, "Liberation Mono", Menlo, Courier, monospace; 24 | color: $code-text-color; 25 | word-wrap: normal; 26 | background-color: $code-bg-color; 27 | border: solid 1px $border-color; 28 | border-radius: 0.3rem; 29 | 30 | > code { 31 | padding: 0; 32 | margin: 0; 33 | font-size: 0.9rem; 34 | color: $code-text-color; 35 | word-break: normal; 36 | white-space: pre; 37 | background: transparent; 38 | border: 0; 39 | } 40 | } 41 | 42 | 43 | header h2 { 44 | width: 710px; 45 | } 46 | 47 | /* Tablet Portrait size to standard 960 (devices and browsers) */ 48 | @media only screen and (min-width: 768px) and (max-width: 959px) { 49 | header h2 { 50 | width: 550px; 51 | font-size: 25px; 52 | } 53 | 54 | header { 55 | padding-top: 25px; 56 | padding-bottom: 25px; 57 | } 58 | } 59 | 60 | .logos img, .demo img { 61 | vertical-align: middle; 62 | display: block; 63 | margin-left: auto; 64 | margin-right: auto; 65 | } 66 | 67 | .demo img { 68 | padding-bottom: 20px; 69 | } 70 | 71 | .logos h3 { 72 | text-align: center; 73 | margin-top: 10px; 74 | } 75 | 76 | .logos h3:before { 77 | content: none !important; 78 | } 79 | 80 | header a.button { 81 | box-sizing: initial; 82 | } 83 | 84 | aside#sidebar h3 { 85 | font-size: 12px; 86 | } 87 | 88 | table { 89 | width: max-content; 90 | margin: 16px 16px; 91 | } 92 | 93 | th { 94 | padding: 3px 16px; 95 | } 96 | 97 | .take-home-message { 98 | font-family: 'Architects Daughter', 'Helvetica Neue', Helvetica, Arial, serif; 99 | font-size: 18px; 100 | margin-bottom: 10px; 101 | font-weight: 500; 102 | border: solid 1px #dce6f0; 103 | background-color: #f8f8f8; 104 | border-radius: 0.3rem; 105 | padding: 10px; 106 | } 107 | -------------------------------------------------------------------------------- /docs/assets/link-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/basic/capture-and-paste.md: -------------------------------------------------------------------------------- 1 | 2 | # Capture and Paste 3 | 4 | Use ```` to capture some output 5 | and *paste it* in the next examples. 6 | 7 | This quite useful if you have some non-deterministic data that 8 | you want to use later: 9 | 10 | ```python 11 | >>> def gen_number(): 12 | ... n = 42 13 | ... print("Generating: %i" % n) 14 | ... return n 15 | 16 | >>> a = gen_number() 17 | Generating: 18 | 19 | >>> a == # byexample: +paste 20 | True 21 | ``` 22 | 23 | You can even paste it in the ``expected`` of the examples that follows: 24 | 25 | ```python 26 | >>> a # byexample: +paste 27 | 28 | 29 | >>> a # byexample: +paste -capture 30 | 31 | ``` 32 | 33 | > Disabling the capture with `-tags` or `-capture` you can be sure that 34 | > the tags come from a previous example 35 | > and they are not captured from the current example but it is not mandatory. 36 | 37 | Pasting works across languages: this is a very convenient way to copy and 38 | paste some data from one language to another. 39 | 40 | ```ruby 41 | >> puts "This is not very random: " # byexample: +paste 42 | This is not very random: 42 43 | ``` 44 | 45 | ## Capture 46 | 47 | ``byexample`` will always try to capture the most smaller string first. 48 | 49 | If your named tags are capturing more data than you want, 50 | you may want to know what [strategies](/{{ site.uprefix }}/advanced/greedy-lazy-tags) 51 | ``byexample`` uses. 52 | 53 | Using unamed tags ``<...>`` or adding more context around the named tag fixes 54 | the problem most of the time. 55 | 56 | ### Valid names 57 | 58 | Only alphanumeric characters plus the minus and the dot symbols 59 | are valid characters for a tag name and a name must starts with 60 | a letter or with a dot. 61 | 62 | This restricted set prevents unwanted false positives: 63 | 64 | ```python 65 | >>> def fib(): 66 | ... pass 67 | 68 | >>> fib 69 | > 70 | ``` 71 | 72 | ## Disable the tags 73 | 74 | With `-tags` all the tags are taken as literal: the capture tags 75 | (``) and the unnamed tags (`<...>`). 76 | 77 | ```python 78 | >>> print("hey <...><...>") # byexample: -tags 79 | hey <...><...> 80 | ``` 81 | 82 | With `-capture` only the capture tags are taken as literal. 83 | 84 | ```python 85 | >>> print("hey <...> <...>") # byexample: -capture 86 | hey <...> <...> 87 | ``` 88 | 89 | > *New* in `byexample 10.3.0`: the `-capture` flag was added. 90 | 91 | ## Limitation 92 | 93 | If an example fails, its named tags are not captured. 94 | 95 | This may lead to other example fail or abort because the tags 96 | that they used cannot be pasted. 97 | 98 | This can be a little problematic in the examples that are supposed 99 | to do the [clean up](/{{ site.uprefix }}/basic/setup-and-tear-down) with ``-skip``. 100 | -------------------------------------------------------------------------------- /docs/basic/normalize-whitespace.md: -------------------------------------------------------------------------------- 1 | # Normalize Whitespace 2 | 3 | Replace any sequence of whitespace by a single one. 4 | 5 | It is particular useful if the output is a little messy: 6 | you can put extra spaces and new lines and it will 7 | still be valid. 8 | 9 | Consider this example that prints a table but, for 10 | some reason, it uses only a single space as separator: 11 | 12 | ```python 13 | >>> print("Name Age kev 22 luc 23") 14 | Name Age kev 22 luc 23 15 | ``` 16 | 17 | It looks ugly, does it? 18 | 19 | We can add extra spaces and new lines to write a better 20 | example and combine it with ``+norm-ws`` so those extra spaces 21 | do not count. 22 | 23 | ```python 24 | >>> print("Name Age kev 22 luc 23") # byexample: +norm-ws 25 | Name Age 26 | kev 22 27 | luc 23 28 | ``` 29 | 30 | Here is another example, this time written in ``Ruby``: 31 | 32 | ```ruby 33 | >> Array(0...20) # byexample: +norm-ws 34 | => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 35 | 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /docs/basic/setup-and-tear-down.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Setup and Tear Down 11 | 12 | Sometimes you need to handle resources in the tests: you need to make 13 | sure that you have them before running any test and you need to release 14 | them at the end. 15 | 16 | Think in a web server, a virtual machine, a database connection. 17 | 18 | Let's take the last one. 19 | 20 | Imagine that you want to show how to interact with your database to explain 21 | the underlying model and relationships. You obviously need to *setup* the 22 | database first with some dataset in order to show how to interact with it. 23 | 24 | It is also reasonable to *tear down* the database connection at the end. 25 | 26 | You could write: 27 | 28 | ``` 29 | $ cat test/ds/db-stock-model # byexample: +rm=  30 | This is a quick introduction to the database schema. 31 |   >>> import sqlite3 32 |   >>> c = sqlite3.connect(':memory:') 33 |   >>> _ = c.executescript(open('test/ds/stock.sql').read()) # ---> # byexample: +fail-fast 34 |   35 | Get the stocks' prices 36 |   >>> _ = c.execute('select price from stocks') 37 |   38 | Do not forget to close the connection 39 |   >>> c.close() # ---> # byexample: -skip 40 |   41 | ``` 42 | 43 | In a happy and perfect world this should run smoothly: 44 | 45 | ``` 46 | $ byexample -l python test/ds/db-stock-model 47 | <...> 48 | File test/ds/db-stock-model, 5/5 test ran in <...> seconds 49 | [PASS] Pass: 5 Fail: 0 Skip: 0 50 | ``` 51 | 52 | Now, if for some reason you cannot load the initial dump, it makes sense 53 | to stop the whole execution: this is known as ``fail fast`` and it is 54 | archived with the ``+fail-fast`` flag. 55 | 56 | This should *skip* all the examples, however no matter what happen, 57 | you always want to leave a clean environment and close the connection: 58 | you can force this saying that the example must *not* be skipped (``-skip``). 59 | 60 | Check what happen if we delete the sql file: the example ``c.executescript`` 61 | should fail and because ``+fail-safe`` is in effect for, the rest of the 62 | example should be *skippped* except the last one because explicitly 63 | says *do not skip* me with ``-skip``. 64 | 65 | ``` 66 | $ mv test/ds/stock.sql test/ds/renamed.sql 67 | 68 | $ byexample -l python test/ds/db-stock-model 69 | <...> 70 | File test/ds/db-stock-model, 5/5 test ran in <...> seconds 71 | [FAIL] Pass: 3 Fail: 1 Skip: 1 72 | ``` 73 | 74 | 78 | 79 | > **Note:** in some cases ``byexample`` may not be able to 80 | > recover the control of the runner after a *timeout*. 81 | > 82 | > In those cases or when the runner crash directly, the execution 83 | > will abort immediately *without* executing any example even if 84 | > they have ``-skip``. This is because these kind of failures may had left the 85 | > interpreter in a invalid state and the execution cannot be resumed. 86 | > 87 | > The best strategy would be create a [concern module](/{{ site.uprefix }}/contrib/how-to-hook-to-events-with-concerns) 88 | > and hook to the ``finish`` event and perform there all the clean up, if any. 89 | > 90 | > **Changed** in ``byexample 8.0.0``: before, a timeout had always 91 | > ended in an abort without doing any clean up. But in ``8.0.0`` this changed 92 | > and an abort is much more rare. See [timeout doc](/{{ site.uprefix }}/basic/timeout). 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /docs/basic/skip-and-pass.md: -------------------------------------------------------------------------------- 1 | # Skip and Pass 2 | 3 | ``skip`` will skip the example completely while ``pass`` will execute it 4 | normally but it will not check the output. 5 | 6 | See the difference between those two in these ``Python`` examples: 7 | 8 | ```python 9 | >>> def f(): 10 | ... print("Choosing a random number...") 11 | ... return 42 12 | 13 | >>> a = 1 14 | >>> a = f() # this assignment will not be executed # byexample: +skip 15 | 16 | >>> a 17 | 1 18 | 19 | >>> a = f() # execute the code but ignore the output # byexample: +pass 20 | 21 | >>> a 22 | 42 23 | 24 | >>> a = f() # the output is not ignored so we must check for it 25 | Choosing a random number... 26 | ``` 27 | 28 | See how to use ``-skip`` to support 29 | [clean ups](/{{ site.uprefix }}/basic/setup-and-tear-down). 30 | -------------------------------------------------------------------------------- /docs/basic/timeout.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | # Timeout 11 | 12 | The execution of each example has a timeout: if the example takes longer 13 | it will abort. 14 | 15 | This timeout can be changed of course. 16 | 17 | ```python 18 | >>> import time 19 | >>> time.sleep(2.5) # byexample: +timeout=4 20 | ``` 21 | 22 | The timeout can be controlled per example with ``+timeout`` or it 23 | can be changed from the command line with ``--timeout`` to affect the 24 | *all* the examples. 25 | 26 | See a timeout in action: 27 | 28 | ``` 29 | $ byexample -l python --timeout 1 --ff test/ds/too-slow.md # byexample: +timeout=15 30 | <...> 31 | File "test/ds/too-slow.md", line 7 32 | Failed example: 33 | sleep(1.1) 34 | => Execution timedout at example 3 of 5. 35 | - This could be because the example just ran too slow (try add more time 36 | with +timeout=) or the example is "syntactically incorrect" and 37 | the interpreter hang (may be you forgot a parenthesis or something like that?). 38 | <...> 39 | [FAIL] Pass: 2 Fail: 1 Skip: 2 40 | ``` 41 | 42 | ## Why an example could timeout? 43 | 44 | The first reason could be that the example is just too slow. 45 | 46 | Try to increase the timeout to a larger number and see if it works; 47 | if not, then it is something else. 48 | 49 | Another reason could be that the example is *syntactically incorrect* 50 | or *incomplete* and the underlying interpreter is waiting for more 51 | input. 52 | 53 | Something like this incomplete ``Python`` print: 54 | 55 | ```python 56 | >>> print("some... # byexample: +skip 57 | ``` 58 | 59 | > The use of ``+skip`` is for testing purposes only, otherwise 60 | > the example would timeout of course. 61 | 62 | ## Recovery of a timeout 63 | 64 | A timeout is considered a critical issue. 65 | 66 | When an example fails due a timeout, ``byexample`` has no control 67 | over the runner and it will try to *recover it back* typically 68 | sending an *interrupt* or ``ctrl-c`` to the runner. 69 | 70 | If ``byexample`` recovers the control, the execution resumes and 71 | continues as usual. 72 | 73 | But if not, the interpreter may hang for a long time or forever 74 | so further executions will timeout too. 75 | 76 | In this case the execution is *aborted*. 77 | 78 | No other example will be executed nor even if they have the ``-skip`` option 79 | which can be problematic for the 80 | [cleaning up](/{{ site.uprefix }}/basic/setup-and-tear-down) process. 81 | 82 | If the recovery is a problem, you can disable it with 83 | ``-x-not-recover-timeout``. 84 | 85 | Compare the output of the following example with the previous one. Note 86 | how the rest of the examples are *not executed* at all and that 87 | the final status is ``ABORT`` 88 | 89 | ``` 90 | $ byexample -l python --timeout 1 --ff -x-not-recover-timeout test/ds/too-slow.md # byexample: +timeout=10 91 | <...> 92 | File "test/ds/too-slow.md", line 7 93 | Failed example: 94 | sleep(1.1) 95 | => Execution timedout at example 3 of 5. 96 | - This could be because the example just ran too slow (try add more time 97 | with +timeout=) or the example is "syntactically incorrect" and 98 | the interpreter hang (may be you forgot a parenthesis or something like that?). 99 | <...> 100 | => Execution aborted at example 3 of 5. 101 | - Some resources may had not been cleaned. 102 | <...> 103 | [ABORT] Pass: 2 Fail: 1 Skip: 0 104 | ``` 105 | 106 | > **New** in ``byexample 8.0.0``: before, a timeout had always 107 | > ended in an abort. 108 | 109 | > **Note:** the ability of recovering depends of each interpreter or runner. 110 | > See their documentation for more details. 111 | -------------------------------------------------------------------------------- /docs/contrib/how-to-define-new-zones-where-to-find-examples.md: -------------------------------------------------------------------------------- 1 | # Define New Zones Where to Find Examples 2 | 3 | There are three different ways in which ``byexample`` can be extended: 4 | 5 | - define zones where to find examples 6 | - support new languages: how to find them and how to run them 7 | - perform arbitrary actions during the execution 8 | 9 | ``byexample`` uses the concept of modules: a python file with some extension 10 | classes defined there. Modules can be loaded using ``--modules `` 11 | from the command line. 12 | 13 | What extension classes will depend of what you want to extend or customize. 14 | 15 | In this ``how-to`` we will see how to define new zones where to 16 | find examples. 17 | 18 | Check [how to support new finders and languages](/{{ site.uprefix }}/contrib/how-to-support-new-finders-and-languages) 19 | and [how to hook to events with concerns](/{{ site.uprefix }}/contrib/how-to-hook-to-events-with-concerns) 20 | for a ``how-to`` about the first two items. 21 | 22 | ## How to define new zones 23 | 24 | ``byexample`` searches for examples in particular *zones* of a file. 25 | What zones will depend of the kind of file. 26 | 27 | For Python files it searches them in the docstrings, in Ruby files it 28 | searches them in the comments. 29 | 30 | By default, if no particular zone is defined ``byexample`` searches the 31 | example in the whole file. 32 | 33 | But searching in the whole file without any structure could lead to false 34 | positives. 35 | 36 | You can define new zones for a type of files just creating a ``ZoneDelimiter`` 37 | subclass. 38 | 39 | Imagine that you want to find examples in a HTML file and you want to ignore 40 | everything except the code between ``
`` and ``
`` tags. 41 | 42 | This is what you need to write: 43 | 44 | ```python 45 | >>> from byexample import regex as re 46 | >>> from byexample.finder import ZoneDelimiter 47 | 48 | >>> class HTMLPreBlockDelimiter(ZoneDelimiter): 49 | ... target = '.html' 50 | ... 51 | ... def zone_regex(self): 52 | ... return re.compile(r'
(?P.*?)
', re.DOTALL | re.UNICODE) 53 | ... 54 | ... def get_zone(self, match, where): 55 | ... return ZoneDelimiter.get_zone(self, match, where) 56 | ``` 57 | 58 | That's it. 59 | 60 | The ``target`` indicates the file extension of the files 61 | that will be delimited by this code. It can be a string with a single extension 62 | file or a list or set of several extensions. 63 | 64 | The ``zone_regex`` method should return a regular expression to find and capture 65 | the zones. 66 | 67 | And optionally, the ``get_zone`` can be overridden to post-process the captured 68 | string: use it to remove any spurious string that may had been captured. 69 | 70 | > *Changed* in `byexample 10.0.0`. Before `10.0.0` you could return a 71 | > Python regular expression but from `10.0.0` and on, you need to return 72 | > the regular expressions created by `byexample.regex`. The module is 73 | > almost identical to Python's `re` so the required changes are minimal. 74 | 75 | 76 | ## Concurrency model 77 | 78 | Each `ZoneDelimiter` instance will be created *once* during the setup of 79 | `byexample` and then it will be created *once per job thread*. 80 | 81 | By default there is only one job thread but more threads can be added 82 | with the `--jobs` option. 83 | 84 | The instances are independent and therefore thread-safe. 85 | 86 | If you want to *share* data among them you will have to use a 87 | thread-safe structures created by a `sharer` and store them 88 | in a `namespace`. 89 | 90 | In the [concurrency model](/{{ site.uprefix }}/contrib/concurrency-model) 91 | documentation it is explained and in 92 | [byexample/modules/progress.py](https://github.com/byexamples/byexample/tree/master/byexample/modules/progress.py) 93 | you can see a concrete example. 94 | 95 | > *Changed* in `byexample 10.0.0`. Before `10.0.0` you were forced to 96 | > use `multiprocessing` by hand but in `10.0.0` the concurrency model 97 | > is hidden so you cannot relay on `multiprocessing` because `byexample` 98 | > may not use processes at all! 99 | > `sharer` and `namespace` are objects that hide the details while 100 | > allowing you to have the same power. 101 | 102 | ## `ZoneDelimiter` initialization 103 | 104 | If you extend `ZoneDelimiter` and decide to implement your own `__init__`, 105 | you must ensure that you call `ZoneDelimiter`'s `__init__` method 106 | passing to it all the keyword-only arguments that you received. 107 | 108 | Once done that, you can use the `self.cfg` property to access any 109 | configuration set in `byexample` including the flags/options set 110 | (`self.cfg.options`). 111 | 112 | In the `__init__` you can also change the value of `target` to something 113 | different. This allows you to change what type of files you are going to 114 | processes based on the configuration or you can disable the zone finder 115 | entirely setting `self.target = None`. 116 | 117 | See 118 | [Extension initialization](/{{ site.uprefix }}/contrib/extension-initialization) 119 | for more about this and some troubleshooting. 120 | 121 | > *New* in ``byexample 11.0.0``: `self.cfg` was introduced. 122 | -------------------------------------------------------------------------------- /docs/contrib/how-to-hook-to-events-with-concerns.md: -------------------------------------------------------------------------------- 1 | # How to Hook to Events 2 | 3 | There are three different ways in which ``byexample`` can be extended: 4 | 5 | - define zones where to find examples 6 | - support new languages: how to find them and how to run them 7 | - perform arbitrary actions during the execution 8 | 9 | ``byexample`` uses the concept of modules: a python file with some extension 10 | classes defined there. Modules can be loaded using ``--modules `` 11 | from the command line. 12 | 13 | What extension classes will depend of what you want to extend or customize. 14 | 15 | In this ``how-to`` we will see how to hook to events and perform arbitrary 16 | actions during the execution. 17 | 18 | Check [how to define new zones where to find examples](/{{ site.uprefix }}/contrib/how-to-define-new-zones-where-to-find-examples) 19 | and [how to support new finders and languages](/{{ site.uprefix }}/contrib/how-to-support-new-finders-and-languages) 20 | for a ``how-to`` about the first two items. 21 | 22 | Let's show this by example. 23 | 24 | ## How to perform arbitrary actions during the execution: Concern 25 | 26 | During the execution of the whole set of examples, ``byexample`` will execute 27 | some callbacks or *hooks* at particular moments like before running an example or 28 | after it failed. 29 | 30 | The set of hooks are collected into the ``Concern`` interface (also known as 31 | Cross-Cutting ``Concern``). 32 | 33 | You can create and add your own to extend the capabilities of ``byexample``: 34 | 35 | - show the progress of the execution 36 | - log / report generation for export 37 | - log execution time history for future execution time prediction (estimate) 38 | - turn on/off debugging, coverage and profile facilities 39 | - others... 40 | 41 | ### Eg: Dump the Script 42 | 43 | Let's imagine that we want to save in a file all the examples' code without 44 | the expected strings nor anything else. 45 | 46 | ```python 47 | >>> from byexample.concern import Concern 48 | 49 | >>> class DumpScript(Concern): 50 | ... target = 'dump-script' 51 | ... 52 | ... def start_run(self, examples, runners, filepath): 53 | ... self.f = open(filepath + ".script") 54 | ... 55 | ... def end_run(self, failed, user_aborted, crashed): 56 | ... self.f.close() 57 | ... 58 | ... def start_example(self, example, options): 59 | ... self.f.write(example.source) 60 | ``` 61 | 62 | See the documentation of the class ``Concern`` in 63 | [byexample/concern.py](https://github.com/byexamples/byexample/tree/master/byexample/concern.py) to get a description of all the 64 | possible hooks and when they are called. 65 | 66 | ``byexample`` uses this mechanism to generate a progress bar in 67 | [byexample/modules/progress.py](https://github.com/byexamples/byexample/tree/master/byexample/modules/progress.py). 68 | 69 | 70 | ## Concurrency model 71 | 72 | Each `Concern` instance will be created *once* during the setup of 73 | `byexample` and then it will be created *once per job* thread. 74 | 75 | By default there is only one job thread but more threads can be added 76 | with the `--jobs` option. 77 | 78 | If you want to *share* data among them you will have to use a 79 | thread-safe structures created by a `sharer` and store them 80 | in a `namespace`. 81 | 82 | In the [concurrency model](/{{ site.uprefix }}/contrib/concurrency-model) 83 | documentation it is explained and in 84 | [byexample/modules/progress.py](https://github.com/byexamples/byexample/tree/master/byexample/modules/progress.py) 85 | you can see a concrete example. 86 | 87 | > *Changed* in `byexample 10.0.0`. Before `10.0.0` you were forced to 88 | > use `multiprocessing` by hand but in `10.0.0` the concurrency model 89 | > is hidden so you cannot relay on `multiprocessing` because `byexample` 90 | > may not use processes at all! 91 | > `sharer` and `namespace` are objects that hide the details while 92 | > allowing you to have the same power. 93 | 94 | ## `Concern` initialization 95 | 96 | If you extend `Concern` and decide to implement your own `__init__`, 97 | you must ensure that you call `Concern`'s `__init__` method 98 | passing to it all the keyword-only arguments that you received. 99 | 100 | Once done that, you can use the `self.cfg` property to access any 101 | configuration set in `byexample` including the flags/options set 102 | (`self.cfg.options`). 103 | 104 | In the `__init__` you can also change the value of `target` to something 105 | different. For a `Concern` this is typically used to enable/disable 106 | the concern object based on the configuration by just setting 107 | `self.target = "some string"` (enable) or `self.target = None` 108 | (disable). 109 | 110 | See 111 | [Extension initialization](/{{ site.uprefix }}/contrib/extension-initialization) 112 | for more about this and some troubleshooting. 113 | 114 | > *New* in ``byexample 11.0.0``: `self.cfg` was introduced. 115 | -------------------------------------------------------------------------------- /docs/examples/cpp.cpp: -------------------------------------------------------------------------------- 1 | /* Byexample will look for examples in any language 2 | * inside of the C/C++ multi line comments. 3 | * 4 | * This is an example in Python 5 | * >>> 1 + 2 6 | * 3 7 | 8 | Of course, we support C/C++ examples as well! 9 | ?: int i = 0; 10 | ?: i + 2 11 | (int) 2 12 | 13 | ?: #include 14 | ?: void foo() { 15 | :: std::cout << "hello!\n"; 16 | :: } 17 | 18 | ?: foo(); 19 | hello! 20 | */ 21 | 22 | int awesome() { 23 | /* 24 | * Here is another example, Shell this time: 25 | * $ echo "C/C++ rocks!" 26 | * C/C++ rocks! 27 | * */ 28 | return 1; 29 | } 30 | 31 | /* */ 32 | // Byexample will not search for examples in this kind of 33 | // comments 34 | // 35 | // So this, is not an example 36 | // >>> 3 * 4 37 | // infinite 38 | // 39 | /* */ 40 | -------------------------------------------------------------------------------- /docs/examples/go.go: -------------------------------------------------------------------------------- 1 | /* 2 | Byexample will look for examples in any language 3 | inside of the Go block-comments: 4 | 5 | This is an example in Python 6 | >>> 1 + 2 7 | 3 8 | 9 | And this is another example in Go 10 | > 2 + 2 11 | : 4 12 | 13 | Here is another example in Go 14 | > func foo() { 15 | . fmt.Println("hello!") 16 | . } 17 | 18 | > foo() 19 | hello! 20 | */ 21 | 22 | func awesome() { 23 | /******* 24 | * Here is another example, Shell this time, inside a block 25 | * comment: 26 | * 27 | * $ echo "Go rocks!" 28 | * Go rocks! 29 | * 30 | *******/ 31 | return 1 \ 32 | > 2; // this line will not be confused with a Go example 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /docs/examples/java.java: -------------------------------------------------------------------------------- 1 | /* Byexample will look for examples in any language 2 | * inside of the C/C++ multi line comments. 3 | * 4 | * This is an example in Python 5 | * >>> 1 + 2 6 | * 3 7 | 8 | Of course, we support Java examples as well! 9 | j> int i = 0; 10 | j> i + 2 11 | => 2 12 | 13 | j> void foo() { 14 | .. System.out.println("hello!"); 15 | .. } 16 | 17 | j> foo(); 18 | hello! 19 | */ 20 | 21 | class Super { 22 | public int awesome() { 23 | /* 24 | * Here is another example, Shell this time: 25 | * $ echo "Java rocks!" 26 | * Java rocks! 27 | * */ 28 | return 1; 29 | } 30 | } 31 | 32 | /* */ 33 | // Byexample will not search for examples in this kind of 34 | // comments 35 | // 36 | // So this, is not an example 37 | // >>> 3 * 4 38 | // infinite 39 | // 40 | /* */ 41 | -------------------------------------------------------------------------------- /docs/examples/javascript.js: -------------------------------------------------------------------------------- 1 | /* Byexample will look for examples in any language 2 | * inside of the Javascript multi line comments. 3 | * 4 | * This is an example in Python 5 | * >>> 1 + 2 6 | * 3 7 | 8 | Of course, we support Javascript examples as well! 9 | > var i = 0; 10 | > i + 2 11 | 2 12 | 13 | > function foo() { 14 | . console.log("hello!"); 15 | . } 16 | 17 | > foo(); 18 | hello! 19 | */ 20 | 21 | function awesome() { 22 | /* 23 | * Here is another example, Shell this time: 24 | * $ echo "Javascript rocks!" 25 | * Javascript rocks! 26 | * */ 27 | return 1 28 | } 29 | 30 | /* */ 31 | // Byexample will not search for examples in this kind of 32 | // comments 33 | // 34 | // So this, is not an example 35 | // >>> 3 * 4 36 | // infinite 37 | // 38 | /* */ 39 | -------------------------------------------------------------------------------- /docs/examples/markdown.md: -------------------------------------------------------------------------------- 1 | # Quick Example in Markdown 2 | 3 | Write your Markdown as usual, putting you examples 4 | inside of a Markdown Fenced-Code block or a HTML Comment block. 5 | 6 | ### Markdown Fenced-Code block 7 | 8 | ```python 9 | This is a Python example: 10 | 11 | >>> 1 + 2 12 | 3 13 | ``` 14 | 15 | ### HTML Comment block 16 | 17 | Or inside of a HTML Comment block (because it is ignored 18 | you will not see it): 19 | 20 | 24 | 25 | One line comments are ok but useless: 26 | 27 | 28 | 29 | 30 | 31 | ### Final comments 32 | 33 | > This is not a Javascript example 34 | >> Nor this a Ruby example 35 | 36 | -------------------------------------------------------------------------------- /docs/examples/php.php: -------------------------------------------------------------------------------- 1 | /* Byexample will look for examples in any language 2 | * inside of the PHP multi line comments. 3 | * 4 | * This is an example in Python 5 | * >>> 1 + 2 6 | * 3 7 | 8 | Of course, we support PHP examples as well! 9 | php> $i = 0; 10 | php> print_r($i + 2); 11 | 2 12 | 13 | php> function foo() { 14 | ...> echo("hello!"); 15 | ...> } 16 | 17 | php> foo(); 18 | hello! 19 | */ 20 | 21 | int awesome() { 22 | /* 23 | * Here is another example, Shell this time: 24 | * $ echo "PHP rocks!" 25 | * PHP rocks! 26 | * */ 27 | return 1 28 | } 29 | 30 | /* */ 31 | // Byexample will not search for examples in this kind of 32 | // comments 33 | // 34 | // So this, is not an example 35 | // >>> 3 * 4 36 | // infinite 37 | // 38 | /* */ 39 | -------------------------------------------------------------------------------- /docs/examples/powershell.ps1: -------------------------------------------------------------------------------- 1 | # Byexample will look for examples in any language 2 | # inside of the PowerShell comment lines that are *consecutive*. 3 | # 4 | # This is an example in Python 5 | # >>> 1 + 2 6 | # 3 7 | # 8 | # And this is another example in PowerShell 9 | # PS> 2 + 2 10 | # 4 11 | 12 | # Of course, we support PowerShell examples as well! 13 | # PS> $i = 0; 14 | # PS> $i + 2 15 | # 2 16 | # 17 | # PS> function Get-Foo { 18 | # --> echo "hello!" 19 | # --> } 20 | # 21 | # PS> Get-Foo 22 | # hello! 23 | 24 | function Be-Awesome { 25 | ##### 26 | # Here is another example, Shell this time: 27 | # $ echo "PowerShell rocks!" 28 | # PowerShell rocks! 29 | ##### 30 | 31 | # But this will not be confused with a Shell 32 | # example even if it begins with a $ because 33 | # it is not inside of a comment. 34 | $ok = 1 35 | } 36 | 37 | -------------------------------------------------------------------------------- /docs/examples/python.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Byexample will look for examples in any language 3 | inside of the Python multi line strings (those that starts 4 | with three single or double quotes). 5 | 6 | This is an example in Python 7 | >>> 1 + 2 8 | 3 9 | 10 | More examples: 11 | >>> i = 0 12 | >>> i + 2 13 | 2 14 | 15 | >>> def foo(): 16 | ... print("hello!") 17 | 18 | >>> foo() 19 | hello! 20 | ''' 21 | 22 | def awesome(): 23 | r""" 24 | Here is another example, Shell this time: 25 | $ echo "Python rocks!" 26 | Python rocks! 27 | """ 28 | return 1 29 | 30 | ''' ''' 31 | # >>> 1 + 2 32 | # No, this is not an example. 33 | ''' ''' 34 | 35 | files with invalid syntax (like this one!) may affect the ability of 36 | byexample to find the docstrings and the examples but byexample will try 37 | to do its best. 38 | -------------------------------------------------------------------------------- /docs/examples/ruby.rb: -------------------------------------------------------------------------------- 1 | # Byexample will look for examples in any language 2 | # inside of the Ruby comment lines that are *consecutive*. 3 | # 4 | # This is an example in Python 5 | # >>> 1 + 2 6 | # 3 7 | # 8 | # And this is another example in Ruby (even if the # are misaligned) 9 | # >> 2 + 2 10 | # => 4 11 | 12 | # Of course, we support Ruby examples as well! 13 | # >> i = 0; 14 | # >> i + 2 15 | # => 2 16 | # 17 | # >> def foo 18 | # .. puts "hello!" 19 | # .. end 20 | # 21 | # >> foo 22 | # hello! 23 | 24 | def awesome 25 | ##### 26 | # Here is another example, Shell this time: 27 | # $ echo "Ruby rocks!" 28 | # Ruby rocks! 29 | ##### 30 | return 1 \ 31 | >> 2; ## this line will not be confused with a Ruby example 32 | end 33 | 34 | -------------------------------------------------------------------------------- /docs/languages/elixir.md: -------------------------------------------------------------------------------- 1 | # Elixir 2 | 3 | Run the `Elixir` examples calling `byexample` as: 4 | 5 | ```shell 6 | $ byexample -l elixir your-file-here # byexample: +skip 7 | ``` 8 | 9 | You need the default interpreter ``iex`` installed first. 10 | Check its [download page](https://elixir-lang.org) 11 | 12 | > **Stability**: ``unsupported`` - it may work but currently it is not 13 | > possible to offer *any* guarantees. 14 | > [Contributions from the community are needed!](https://github.com/byexamples/byexample/tree/master/CONTRIBUTING.md) 15 | 16 | > **Note**: ``byexample`` will work with older version of the interpreter, 17 | ``IEx`` however it will do several *hacks*. The recommended version is 18 | 1.9.0 or superior. 19 | 20 | 21 | 22 | 23 | ## Pretty print 24 | 25 | ``byexample`` changes the default IEx's ``width`` to a smaller 26 | value (32) so nested structures are break into multiline prints. 27 | (pretty print). 28 | 29 | ```elixir 30 | iex> %{:a => 1, 2 => :b, 3 => %{:c => 0, :d => %{:x => 0, :y => :e}}} 31 | => %{ 32 | 2 => :b, 33 | 3 => %{ 34 | c: 0, 35 | d: %{x: 0, y: :e} 36 | }, 37 | :a => 1 38 | } 39 | ``` 40 | 41 | **Note:** ``byexample`` uses ``Inspect`` to pretty print and the output of 42 | this had changed in the past so there are not any warranties. 43 | The output shown correspond to ``IEx`` version 1.9.2. 44 | 45 | 46 | ### The object returned 47 | 48 | Because everything in ``Elixir`` is an expression, everything returns a result 49 | and it is printed by ``IEx``. 50 | 51 | This is annoying if you want to write several ``Elixir`` lines without checking 52 | the results of each one. 53 | 54 | For this reason, ``byexample`` suppress the representation of the object 55 | returned unless the example has a ``=>``. 56 | 57 | In the following case, the result of each expression is not printed and 58 | therefor they are **not** checked: 59 | 60 | ```elixir 61 | iex> 1 + 2 62 | 63 | iex> IO.puts("hello") 64 | hello 65 | ``` 66 | 67 | Now, compare it with this. It is the same example but the objects returned 68 | are checked too. 69 | 70 | ```elixir 71 | iex> 1 + 2 72 | => 3 73 | 74 | iex> IO.puts("hello") 75 | hello 76 | => :ok 77 | ``` 78 | 79 | If you want to check all the expressions, you can force to print all the 80 | objects returned using the ``+elixir-expr-print=true``. 81 | 82 | On the other hand, you can disable it forever 83 | with ``+elixir-expr-print=false``. 84 | 85 | The default is ``+elixir-expr-print=auto``. 86 | 87 | **Note:** ``byexample`` uses ``inspect_fun`` to customize this which it is 88 | available since ``IEx`` version 1.9. If you are stuck with an older version 89 | you can use the ``dont_display_result`` hack. 90 | 91 | ### ``dont_display_result`` hack 92 | 93 | For older version of ``IEx``, the only way to suppress the display of 94 | a result is adding ``; IEx.dont_display_result`` at the end of each 95 | ``Elixir`` line. 96 | 97 | ``byexample`` can do this automatically if you enable this with 98 | ``+elixir-dont-display-hack``. 99 | 100 | However this is a *hack* and it will not always will work. 101 | 102 | For example, if your example ends with a comment, you will be commenting 103 | out also the ``; IEx.dont_display_result``: 104 | 105 | ``` 106 | valid example here # valid comment ; IEx.dont_display_result 107 | ``` 108 | 109 | Also, the ``;`` *may* create unexpected warnings. 110 | 111 | ### Terminal support 112 | 113 | To work with the current Elixir interpreter, ``iex``, the ANSI 114 | [terminal emulator](/{{ site.uprefix }}/advanced/terminal-emulation) is 115 | enabled by default (``+term=ansi``) and cannot be disabled. 116 | 117 | Also, the [terminal geometry](/{{ site.uprefix }}/advanced/geometry) 118 | cannot by changed after launching the interpreter 119 | so the option ``+geometry`` cannot be used in an example (but it can be 120 | used from the command line) 121 | 122 | The amount of rows of the terminal has a minimum value of 2048 and this limit 123 | is really important: if your outputs have more than 2048 lines you will need 124 | to increase the geometry or the results may be undefined. 125 | 126 | The same for the width of the terminal: minimum of 1024 columns. 127 | 128 | ### Echoed input lines 129 | 130 | If the snippet has a very long line, greater than the terminal's width, 131 | the last part of the line that does not fit in the terminal will be *echoed* 132 | in the output of the example. 133 | 134 | This is an annoying artifact due how ``iex`` works. 135 | 136 | A simple workaround is to make the lines of the code in the snippet 137 | shorter or increase the 138 | [terminal width](/{{ site.uprefix }}/advanced/geometry). 139 | 140 | ### Type text 141 | 142 | The [type](/{{ site.uprefix }}/basic/input) 143 | feature (`+type`) is not supported. 144 | 145 | ## Elixir specific options 146 | 147 | ``` 148 | $ byexample -l elixir --show-options # byexample: +norm-ws 149 | <...> 150 | elixir's specific options 151 | ------------------------- 152 | <...>: 153 | +elixir-dont-display-hack 154 | required for IEx < 1.9. 155 | +elixir-expr-print {auto,true,false} 156 | print the expression's value (true); suppress it 157 | (false); or print it only if the example has a => 158 | (auto, the default) 159 | ``` 160 | -------------------------------------------------------------------------------- /docs/languages/gdb.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # GDB the GNU debugger 7 | 8 | ``byexample`` can interpret and run examples for a ``GDB`` session. 9 | 10 | Run `byexample` as: 11 | 12 | ```shell 13 | $ byexample -l gdb your-file-here # byexample: +skip 14 | ``` 15 | 16 | You need to have the debugger installed first on your system, check 17 | its [download page](https://www.gnu.org/software/gdb/download/). 18 | 19 | > **Stability**: ``experimental`` - non backward compatibility changes are 20 | > possible or even removal between versions (even patch versions). 21 | 22 | 23 | 24 | 25 | ## Quick example 26 | 27 | To show you this, let's first create a program to debug: 28 | 29 | ```cpp 30 | $ cat test/ds/param-echo.c # byexample: -capture 31 | #include 32 | int main(int argc, char* argv[]) { 33 | for (; argc > 0; --argc) 34 | printf("%s\n", argv[argc-1]); 35 | return 0; 36 | } 37 | ``` 38 | 39 | ``` 40 | $ gcc -o w/param-echo.exe -ggdb -O0 test/ds/param-echo.c # byexample: +timeout=10 41 | ``` 42 | 43 | The program is quite simple, it just prints its parameters in reverse order 44 | 45 | ``` 46 | $ ./w/param-echo.exe 47 | ./w/param-echo.exe 48 | 49 | $ ./w/param-echo.exe foo bar 50 | bar 51 | foo 52 | ./w/param-echo.exe 53 | ``` 54 | 55 | ## Find interactive examples 56 | 57 | Now, let's debug it with ``GDB`` 58 | 59 | ``byexample`` uses the ``(gdb)`` string as the primary prompt to find 60 | ``GDB`` examples like these: 61 | 62 | ``` 63 | (gdb) file ./w/param-echo.exe 64 | Reading symbols <...> 65 | 66 | (gdb) start foo bar 67 | <...> 68 | Starting program: <...> 69 | 70 | (gdb) print argc 71 | $1 = 3 72 | 73 | (gdb) print argv[1] 74 | $2 = "foo" 75 | ``` 76 | 77 | ## GDB specific options 78 | 79 | ``` 80 | $ byexample -l gdb --show-options # byexample: +norm-ws 81 | <...> 82 | gdb's specific options 83 | ---------------------- 84 | None. 85 | ``` 86 | -------------------------------------------------------------------------------- /docs/languages/iasm.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | # iasm: the interactive assembler 12 | 13 | Run the `iasm` examples calling `byexample` as: 14 | 15 | ```shell 16 | $ byexample -l iasm your-file-here # byexample: +skip 17 | ``` 18 | 19 | You will have to install `iasm` first: 20 | 21 | ```shell 22 | $ pip install iasm # byexample: +skip 23 | ``` 24 | 25 | Or you can download the code from 26 | [here](https://github.com/bad-address/iasm) 27 | 28 | > **Stability**: ``experimental`` - non backward compatibility changes are 29 | > possible or even removal between versions (even patch versions). 30 | 31 | > *New* in ``byexample 10.1.0``. 32 | 33 | ### Versions tested 34 | 35 | We tested `byexample` with the following versions of the language 36 | and the underlying runner or interpreter: 37 | 38 | 39 | 40 | | Language | Runner/Interpreter | 41 | |------------|----------------------| 42 | | latest | 0.2.0 | 43 | 44 | 45 | 46 | ## Set architecture and operation mode 47 | 48 | `iasm` is capable to emulate different architectures. 49 | 50 | The settings can be passed to `iasm` from the command line of 51 | `byexample`. Once set they cannot be changed at runtime. 52 | 53 | ```shell 54 | $ byexample -l iasm -o '+iasm-arch=x86 +iasm-mode=64 +iasm-code-size=102400 +iasm-pc=0' test/ds/iasm.md # byexample: +timeout=8 55 | <...> 56 | [PASS] Pass: 1 Fail: 0 Skip: 0 57 | ``` 58 | 59 | See the [`iasm` documentation](https://github.com/bad-address/iasm) 60 | for more information about those. 61 | 62 | ## Find interactive examples 63 | 64 | For ``iasm``, ``byexample`` uses the ``:>`` string as the primary prompt 65 | and ``->`` as the secondary prompt. 66 | 67 | In `iasm` the example is compiled and executed as the architecture 68 | configured on start up, ARM by default: 69 | 70 | ```nasm 71 | :> mov r0, #4 72 | :> mov r1, #8 73 | ``` 74 | 75 | Examples that begin with `;!` are executed as Python statement that can 76 | operate with the registers and with the memory `M`: 77 | 78 | ```python 79 | :> ;! print(r0 + r1) 80 | 12 81 | ``` 82 | 83 | ## Showing the registers 84 | 85 | `iasm` allows you to show the values of the registers by name or 86 | pattern. 87 | 88 | ```python 89 | :> ;! show('r[0-9]', 'r1[0-9]') 90 | ------ - ------ - ------ - ------ ----- 91 | r0 4 r1 8 r2 0 r3 0 92 | r4 0 r5 0 r6 0 r7 0 93 | r8 0 r9/sb 0 r10 0 r11/fp 0 94 | r12/ip 0 r13/sp 0 r14/lr 0 r15/pc 100:8 95 | ------ - ------ - ------ - ------ ----- 96 | ``` 97 | 98 | Add `stick=True` to display those registers every time. 99 | 100 | ```python 101 | :> ;! show('r[0-3]', stick=True) 102 | -- - -- - -- - -- - 103 | r0 4 r1 8 r2 0 r3 0 104 | -- - -- - -- - -- - 105 | 106 | :> mov r2, r1 107 | -- - -- - -- - -- - 108 | r0 4 r1 8 r2 8 r3 0 109 | -- - -- - -- - -- - 110 | ``` 111 | 112 | Call `show(stick=True)` to restore the defaults: 113 | 114 | ```python 115 | :> ;! show(stick=True) 116 | ``` 117 | 118 | ## `byexample` options 119 | 120 | The `byexample` options can be set in the assembly comments (`;`) 121 | and in the Python comments (`#`): 122 | 123 | ```nasm 124 | :> r1024-nonexist ; byexample: +skip 125 | ``` 126 | 127 | ```python 128 | :> ;! r1024-nonexist # byexample: +skip 129 | ``` 130 | 131 | ## Known limitations 132 | 133 | ### Type text 134 | 135 | The [type](/{{ site.uprefix }}/basic/input) 136 | feature (`+type`) is not supported. 137 | 138 | ### Terminal support 139 | 140 | To work with `iasm`, the ANSI 141 | [terminal emulator](/{{ site.uprefix }}/advanced/terminal-emulation) is 142 | enabled by default (``+term=ansi``) and cannot be disabled. 143 | 144 | Also, the [terminal geometry](/{{ site.uprefix }}/advanced/geometry) 145 | cannot by changed after launching the interpreter 146 | so the option ``+geometry`` cannot be used in an example (but it can be 147 | used from the command line) 148 | 149 | The amount of rows of the terminal has a minimum value of 2048 and this limit 150 | is really important: if your outputs have more than 2048 lines you will need 151 | to increase the geometry or the results may be undefined. 152 | 153 | The same for the width of the terminal: minimum of 1024 columns. 154 | 155 | ## iasm specific options 156 | 157 | ``` 158 | $ byexample -l iasm --show-options # byexample: +norm-ws 159 | <...> 160 | iasm's specific options 161 | ----------------------- 162 | <...>: 163 | +iasm-arch architecture name (arm, x86, sparc, ...); see iasm 164 | documentation. 165 | +iasm-mode mode (arm, 32, 64, ...); see iasm documentation. 166 | +iasm-code-size size of the code segment; see iasm documentation. 167 | +iasm-pc starting address, value of the program counter; see 168 | iasm documentation. 169 | ``` 170 | -------------------------------------------------------------------------------- /docs/languages/php.md: -------------------------------------------------------------------------------- 1 | # PHP 2 | 3 | Run the `PHP` examples calling `byexample` as: 4 | 5 | ```shell 6 | $ byexample -l php your-file-here # byexample: +skip 7 | ``` 8 | 9 | To support PHP, ``byexample`` relays in the interactive mode of the ``php`` 10 | interpreter. 11 | 12 | From PHP 5.1.0 this is available as long as the interpreter is compiled with 13 | ``readline`` support. See [interactive.php](https://www.php.net/manual/en/features.commandline.interactive.php). 14 | 15 | > **Stability**: ``unsupported`` - it may work but currently it is not 16 | > possible to offer *any* guarantees. 17 | > [Contributions from the community are needed!](https://github.com/byexamples/byexample/tree/master/CONTRIBUTING.md) 18 | 19 | 20 | 21 | 22 | 23 | ### Variable and function definitions 24 | 25 | Variables are local the scope and cannot be used inside of a function: 26 | there are not global and there is not support for closures in PHP. 27 | 28 | ```php 29 | php> $a = 2; 30 | php> function foo($b) { 31 | ...> echo $b; 32 | ...> } 33 | 34 | php> foo($a); 35 | 2 36 | ``` 37 | 38 | Note how all the expressions must end with a ``;``. 39 | 40 | ### Syntax errors 41 | 42 | ``byexample`` will show you the syntax errors detected by ``php``. 43 | You can even check for them as part of the normal output: 44 | 45 | ```php 46 | php> for ($i = 0; $i < unknown; $i+=1) { 47 | ...> echo $i; 48 | ...> } 49 | PHP Notice: Use of undefined constant unknown - assumed 'unknown' in php shell code on line <...> 50 | ``` 51 | 52 | ## Known limitations 53 | 54 | ### Explicit pretty print 55 | 56 | To pretty print a variable or expression, you need to call ``var_dump`` 57 | or ``print_r`` (or other that you want) *explicitly*. 58 | 59 | ```php 60 | php> $arr = [1, [1.5, 1.6], 2]; 61 | 62 | php> $arr; // nothing is printed 63 | 64 | php> var_dump($arr); 65 | array(3) { 66 | [0]=> 67 | int(1) 68 | [1]=> 69 | array(2) { 70 | [0]=> 71 | float(1.5) 72 | [1]=> 73 | float(1.6) 74 | } 75 | [2]=> 76 | int(2) 77 | } 78 | 79 | php> print_r($arr); // byexample: +norm-ws 80 | Array 81 | ( 82 | [0] => 1 83 | [1] => Array 84 | ( 85 | [0] => 1.5 86 | [1] => 1.6 87 | ) 88 | [2] => 2 89 | ) 90 | ``` 91 | 92 | ``var_dump`` and ``print_r`` may add some extra spaces and 93 | new lines (especially ``print_r``) that will interfere with the output. 94 | For complex structures using ``+norm-ws`` fixes the problem. 95 | 96 | ### Terminal support 97 | 98 | To work with the current PHP interpreter, the ANSI 99 | [terminal emulator](/{{ site.uprefix }}/advanced/terminal-emulation) is 100 | enabled by default (``+term=ansi``) and cannot be disabled. 101 | 102 | Also, the [terminal geometry](/{{ site.uprefix }}/advanced/geometry) 103 | cannot by changed after launching the interpreter 104 | so the option ``+geometry`` cannot be used in an example (but it can be 105 | used from the command line) 106 | 107 | The amount of rows of the terminal has a minimum value of 2048 and this limit 108 | is really important: if your outputs have more than 2048 lines you will need 109 | to increase the geometry or the results may be undefined. 110 | 111 | The same for the width of the terminal: minimum of 1024 columns. 112 | 113 | ### Echoed input lines 114 | 115 | If the PHP snippet has a very long line, greater than the terminal's width, 116 | the last part of the line that does not fit in the terminal will be *echoed* 117 | in the output of the example. 118 | 119 | This is an annoying artifact due how ``php -a`` works. 120 | 121 | A simple workaround is to make the lines of the code in the snippet 122 | shorter or increase the 123 | [terminal width](/{{ site.uprefix }}/advanced/geometry). 124 | 125 | ### Abort on a timeout 126 | 127 | If a PHP example takes too long and 128 | [timeout](/{{ site.uprefix }}/basic/timeout), the whole execution 129 | timeout. 130 | 131 | ### Type text 132 | 133 | The [type](/{{ site.uprefix }}/basic/input) 134 | feature (`+type`) is not supported. 135 | 136 | ## PHP specific options 137 | 138 | ``` 139 | $ byexample -l php --show-options # byexample: +norm-ws 140 | <...> 141 | php's specific options 142 | ---------------------- 143 | None. 144 | ``` 145 | -------------------------------------------------------------------------------- /docs/languages/powershell.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | # PowerShell 12 | 13 | ``byexample`` supports Microsoft's ``PowerShell``. 14 | 15 | Run the Microsoft's `PowerShell` examples calling `byexample` as: 16 | 17 | ```shell 18 | $ byexample -l pwsh your-file-here # byexample: +skip 19 | ``` 20 | 21 | Currently `byexample` only supports the version for Linux that 22 | you can install from the [official 23 | site](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-linux)). 24 | 25 | To run the `PowerShell` examples in a file you need to select `pwsh` as 26 | the language: 27 | 28 | ```shell 29 | $ byexample -l pwsh docs/languages/powershell.md # byexample: +timeout=8 30 | <...> 31 | [PASS] Pass: <...> 32 | ``` 33 | 34 | > **Stability**: ``experimental`` - non backward compatibility changes are 35 | > possible or even removal between versions (even patch versions). 36 | 37 | > *New* in ``byexample 10.1.0``. 38 | 39 | ### Versions tested 40 | 41 | We tested `byexample` with the following versions of the language 42 | and the underlying runner or interpreter: 43 | 44 | 45 | 46 | | Language | Runner/Interpreter | 47 | |------------|----------------------| 48 | | latest | 7.2.5 | 49 | 50 | 51 | 52 | ## Find interactive examples 53 | 54 | For ``PowerShell``, ``byexample`` uses the ``PS>`` string as the primary prompt 55 | and ``-->`` as the secondary prompt. 56 | 57 | ```shell 58 | PS> $ComputerName = 'DC01', 'WEB01' 59 | 60 | PS> foreach ($Computer in $ComputerName) { 61 | --> echo $Computer 62 | --> } 63 | DC01 64 | WEB01 65 | ``` 66 | 67 | ### Syntax errors 68 | 69 | Syntax errors are detected and can be part of the example. The format of 70 | the message will depend of the version of `PowerShell` however. 71 | 72 | ```shell 73 | PS> echo @"this 74 | ParserError: 75 | Line | 76 | 1 | echo @"this 77 | | ~ 78 | | No characters are allowed after a here-string header<...> 79 | ``` 80 | 81 | ## Known limitations 82 | 83 | ### Type text 84 | 85 | The [type](/{{ site.uprefix }}/basic/input) 86 | feature (`+type`) is supported *but* you have to `+pass` the example. 87 | 88 | In other words, you cannot check its output. 89 | 90 | This limitation comes from `PowerShell` that it does not disable the 91 | echo of the terminal and what you type gets mixed with the output in 92 | unexpected/unpredictable ways. 93 | 94 | ```shell 95 | PS> $num = Read-Host # byexample: +input +pass 96 | [i love 42] 97 | PS> echo $num 98 | i love 42 99 | 100 | PS> $num = Read-Host num # byexample: +input +pass 101 | num: [i prefer 47!] 102 | PS> echo $num 103 | i prefer 47! 104 | ``` 105 | 106 | If you don't set `+pass` you will get a warning. 107 | 108 | ### Terminal support 109 | 110 | To work with `PowerShell`, the ANSI 111 | [terminal emulator](/{{ site.uprefix }}/advanced/terminal-emulation) is 112 | enabled by default (``+term=ansi``) and cannot be disabled. 113 | 114 | Also, the [terminal geometry](/{{ site.uprefix }}/advanced/geometry) 115 | cannot by changed after launching the interpreter 116 | so the option ``+geometry`` cannot be used in an example (but it can be 117 | used from the command line) 118 | 119 | The amount of rows of the terminal has a minimum value of 128 and this limit 120 | is really important: if your outputs have more than 128 lines you will need 121 | to increase the geometry or the results may be undefined. 122 | 123 | The same for the width of the terminal: minimum of 128 columns. 124 | 125 | ## PowerShell specific options 126 | 127 | ``` 128 | $ byexample -l pwsh --show-options # byexample: +norm-ws 129 | <...> 130 | pwsh's specific options 131 | ----------------------- 132 | None. 133 | ``` 134 | -------------------------------------------------------------------------------- /docs/languages/ruby.md: -------------------------------------------------------------------------------- 1 | # Ruby 2 | 3 | Run the `Ruby` examples calling `byexample` as: 4 | 5 | ```shell 6 | $ byexample -l ruby your-file-here # byexample: +skip 7 | ``` 8 | 9 | You need the default interpreter ``irb`` installed first. 10 | Check its [download page](https://www.ruby-lang.org/en/downloads/) 11 | 12 | > **Stability**: ``provisional`` - low impact non backward compatibility 13 | > changes may occur between versions; but in general a change like that 14 | > will happen only between major versions. 15 | 16 | ### Versions tested 17 | 18 | We tested `byexample` with the following versions of the language 19 | and the underlying runner or interpreter: 20 | 21 | 22 | 23 | | Language | Runner/Interpreter | 24 | |------------|----------------------| 25 | | 2.4 | 0.9.6 | 26 | | 2.5 | 0.9.6 | 27 | | 2.6 | 1.0.0 | 28 | | 2.7 | 1.2.6 | 29 | | 3.0 | 1.3.5 | 30 | | 3.1 | 1.4.1 | 31 | 32 | 33 | 34 | ## Find interactive examples 35 | 36 | For ``Ruby``, ``byexample`` uses the ``>>`` string as the primary prompt 37 | and ``..`` as the secondary prompt. 38 | 39 | 40 | ```ruby 41 | >> a = 1 42 | >> b = 2 43 | >> a + b 44 | => 3 45 | 46 | >> def g(a, b, c) 47 | .. c += a 48 | .. c += b 49 | .. 50 | .. return c 51 | .. end 52 | 53 | >> g(1, 2, 3) 54 | => 6 55 | ``` 56 | 57 | ## Pretty print 58 | 59 | ``byexample`` changes the default IRB's ``inspector`` and uses ``pp`` 60 | (pretty print). 61 | 62 | If you want, you can use the IRB's default one with 63 | the option ``-ruby-pretty-print`` 64 | 65 | ```ruby 66 | >> {3=>{5=>Array(0..20), 4=>"aaaaaaaa"}, 1 => 2} 67 | => {1=>2, 68 | 3=> 69 | {4=>"aaaaaaaa", 70 | 5=> 71 | [0, 72 | 1, 73 | <...> 74 | 19, 75 | 20]}} 76 | ``` 77 | 78 | > **Changed** in ``byexample 8.0.0``: make sure that a ``Hash`` 79 | > is printed in a deterministic way with its keys sorted. 80 | > Before ``byexample 8.0.0`` the order was undefined. 81 | 82 | > **Changed** in `byexample 10.0.4`: IRB `> 1.2.2` adds a newline 83 | > between the `=>` marker and the output if it spans more than one line. 84 | > To maintain backward compatibility `byexample` will suppress that 85 | > newline by default. 86 | > If you don't want that you can pass `+ruby-start-large-output-in-new-line` 87 | > flag in the [command line](/{{ site.uprefix }}/basic/options) with `-o`. 88 | 89 | ### The object returned 90 | 91 | Because everything in Ruby is an expression, everything returns a result. 92 | 93 | This is annoying if you want to write several ``Ruby`` lines without checking 94 | the results. 95 | 96 | For this reason, ``byexample`` suppress the representation of the object 97 | returned unless the example has a ``=>``. 98 | 99 | In the following case, the result of each expression is not printed and 100 | therefor they are not checked: 101 | 102 | ```ruby 103 | >> 1 + 2 104 | 105 | >> puts "hello" 106 | hello 107 | ``` 108 | 109 | Now, compare it with this. It is the same example but the objects returned 110 | are checked too. 111 | 112 | ```ruby 113 | >> 1 + 2 114 | => 3 115 | 116 | >> puts "hello" 117 | hello 118 | => nil 119 | ``` 120 | 121 | If you want to check all the expressions, you can force to print all the 122 | objects returned using the ``+ruby-expr-print=true``. 123 | 124 | On the other hand, you can disable it forever 125 | with ``+ruby-expr-print=false``. 126 | 127 | The default is ``+ruby-expr-print=auto``. 128 | 129 | ## Ruby specific options 130 | 131 | ``` 132 | $ byexample -l ruby --show-options # byexample: +norm-ws 133 | <...> 134 | ruby's specific options 135 | ----------------------- 136 | <...>: 137 | +ruby-pretty-print enable the pretty print enhancement. 138 | +ruby-expr-print {auto,true,false} 139 | print the expression's value (true); suppress it 140 | (false); or print it only if the example has a => 141 | (auto, the default) 142 | +ruby-start-large-output-in-new-line 143 | add a newline after the => if the output that follows 144 | does not fit in a single line. (irb >= 1.2.2) 145 | <...> 146 | ``` 147 | -------------------------------------------------------------------------------- /docs/languages/rust.md: -------------------------------------------------------------------------------- 1 | # Rust 2 | 3 | Run the `Rust` examples calling `byexample` as: 4 | 5 | ```shell 6 | $ byexample -l rust your-file-here # byexample: +skip 7 | ``` 8 | 9 | You need the have installed `evcxr`, an interactive interpreter 10 | for `Rust`. 11 | 12 | Check its [download page](https://github.com/google/evcxr) 13 | 14 | > **Note**: current versions of `evcxr` (0.10.0) has a high run time: 15 | > around 20 seconds for starting up the runner and around 2 seconds per 16 | > example. There is [an ongoing work](https://github.com/google/evcxr/issues/184) 17 | > to improve this. 18 | 19 | > **Stability**: ``experimental`` - non backward compatibility changes are 20 | > possible or even removal between versions (even patch versions). 21 | 22 | > *New* in ``byexample 10.2.0``. 23 | 24 | 25 | 26 | 27 | ## Find interactive examples 28 | 29 | For ``Rust``, ``byexample`` uses the ``>>`` string as the primary prompt 30 | and ``::`` as the secondary prompt. 31 | 32 | 33 | ```rust 34 | >> 1 + 2 35 | 3 36 | 37 | >> fn hello() { 38 | :: println!("hello bla world"); // classic 39 | :: } 40 | 41 | >> hello(); // byexample: +norm-ws 42 | hello <...> world 43 | ``` 44 | 45 | > Currently the flags/options can only be set in the single-line 46 | > comments (`//`) 47 | 48 | ### The object returned 49 | 50 | As you may know in `Rust` almost everything is an expression and 51 | `byexample` will take and print the value of the expression. 52 | 53 | ```rust 54 | >> 1 + 2 55 | 3 56 | ``` 57 | 58 | These expressions can be turn into a statement appending a semicolon 59 | in which case `byexample` will not print anything. 60 | 61 | ```rust 62 | >> 1 + 2; 63 | ``` 64 | 65 | ## Pretty print 66 | 67 | `byexample` uses the default *pretty print* of Rust with the format 68 | `"{:?}"`. 69 | 70 | This works quite well out of the box for native objects and objects with 71 | the `#[derive(Debug)]`: 72 | 73 | ```rust 74 | >> #[derive(Debug)] 75 | :: struct Point { 76 | :: x: f32, 77 | :: y: f32, 78 | :: } 79 | 80 | >> let p1 = Point { x: 2.0, y: 3.0 }; 81 | >> p1 82 | Point { x: 2.0, y: 3.0 } 83 | 84 | >> let array = [1, 2, 3]; 85 | >> let tuple = (1, true, 2.3); 86 | 87 | >> array 88 | [1, 2, 3] 89 | >> tuple 90 | (1, true, 2.3) 91 | ``` 92 | 93 | > **Note**: pretty print for arrays and tuple are supported but only 94 | > up to 12 elements. This is a restriction of Rust. 95 | 96 | ## Known limitations 97 | 98 | 99 | ### Runtime performance 100 | 101 | `evcxr` has a high runtime cost. `byexample` waits up to 30 seconds for 102 | the interpreter to be up and up to 8 seconds per example. 103 | 104 | If you want to increase these timeouts you can do it with 105 | `-x-dfl-timeout` and `--timeout`. 106 | 107 | ### Output arrives late 108 | 109 | `evcxr` *may* tell `byexample` that an example finished 110 | *before* it really did. `byexample` works around this and waits a little 111 | after each example execution. 112 | 113 | You can control how much time `byexample` will wait with 114 | `-x-delayafterprompt`. The default is a quarter of a second. 115 | 116 | If you run an example and this fails because the last part of the 117 | expected output is missing **and** that output appears at the begin 118 | of the *next* example, you are hitting this limitation. 119 | 120 | Try to increment the wait time with `-x-delayafterprompt`. 121 | 122 | ### Slices and closures 123 | 124 | Slices are not supported if they are written in the main scope 125 | but there is no problem if the slices are defined in the scope of a 126 | function 127 | 128 | ```rust 129 | >> let array = [1, 2, 3]; 130 | 131 | >> // this will not work 132 | >> let slice: &[i32] = &array[0..2]; // byexample: +skip 133 | 134 | >> // but this is perfectly fine 135 | >> fn bar(slice: &[i32]) { 136 | :: println!("{:?}", slice); 137 | :: } 138 | 139 | >> bar(&array[0..2]); 140 | [1, 2] 141 | ``` 142 | 143 | The same limitation happens with closures: they are not supported in the 144 | main scope but in the functions are okay. 145 | 146 | ```rust 147 | >> let i = 4; 148 | 149 | >> // this will not work 150 | >> let closure_implicit = |j| i + j; // byexample: +skip 151 | 152 | >> // but this is perfectly fine 153 | >> fn baz() { 154 | :: let i = 4; 155 | :: let closure_implicit = |j| i + j; 156 | :: println!("{:?}", closure_implicit(2)); 157 | :: } 158 | 159 | >> baz(); 160 | 6 161 | ``` 162 | 163 | ### Type text 164 | 165 | The [type](/{{ site.uprefix }}/basic/input) 166 | feature (`+type`) is not supported. 167 | 168 | ## Rust specific options 169 | 170 | ``` 171 | $ byexample -l rust --show-options # byexample: +norm-ws 172 | <...> 173 | rust's specific options 174 | ----------------------- 175 | None. 176 | ``` 177 | -------------------------------------------------------------------------------- /docs/overview/good-practices.md: -------------------------------------------------------------------------------- 1 | # Good practices 2 | 3 | Writing with `byexample` serves two purposes: have a good quality 4 | documentation and a nice set of automated tests. 5 | 6 | Here are some good practices and tips that will help you along the way: 7 | 8 | - **Tell a story:** imagine that you are explaining your awesome tool or 9 | feature to a friend, now write it. It is much easier to read a *story* 10 | than a boring manual reference. If you get stuck, just start 11 | writing disconnected phrases; once you start writing, you will feel 12 | how the words flow. 13 | - Keep an eye on the **orthography**: it has not be perfect, but a good 14 | orthography helps a lot to the reader. Use tools for corrections and 15 | a translator if you are not a native speaker. 16 | - **Make it beautiful:** no fancy stuff is needed, just a little of 17 | [markdown](https://en.wikipedia.org/wiki/Hyperlink) is all what you need: 18 | use bold and italics to **highlight** the words that you want to stress and 19 | use links to connect your tests. 20 | - **Be defensive:** don't assume that your tests will run in a clean 21 | environment; clean it at the begin and 22 | [fail fast](https://byexamples.github.io/byexample/basic/setup-and-tear-down) 23 | if you can't. 24 | - **Be a good citizen:** play nice and clean the environment once you 25 | [finish](https://byexamples.github.io/byexample/basic/setup-and-tear-down). 26 | - **Be agile:** `byexample` is flexible, you can 27 | [combine](https://byexamples.github.io/byexample/recipes/advanced-checks) 28 | several interpreters in same document and do strict or relaxed checks as you 29 | need. Keep it simple, *because it is*. 30 | -------------------------------------------------------------------------------- /docs/recipes/advanced-checks.md: -------------------------------------------------------------------------------- 1 | # Advanced Checks 2 | 3 | ### Rich comparison 4 | 5 | Imagine that you want to check how many words has a file 6 | but you cannot check the *exact* count. 7 | 8 | Instead you are ok if the file has *more than x* words. 9 | 10 | How to do that in ``byexample``? 11 | 12 | Just *combine different runners* 13 | [capturing](/{{ site.uprefix }}/basic/capture-and-paste) 14 | the output of one 15 | and [pasting](/{{ site.uprefix }}/basic/capture-and-paste) 16 | it into another where the comparison that you 17 | want can be much easier to implement. 18 | 19 | > For me, Python is my first choice 20 | 21 | ```shell 22 | $ wc -w test/ds/about-lic.doc | xargs 23 | test/ds/about-lic.doc 24 | ``` 25 | 26 | ```python 27 | >>> > 50 # byexample: +paste 28 | True 29 | ``` 30 | 31 | ### Use the pretty printer 32 | 33 | True story: a program logs into a file a set of dictionaries, 34 | something like this: 35 | 36 | ```shell 37 | $ cat test/ds/log 38 | Sep 30 15:44:01 system: Loaded modules 39 | Sep 30 15:44:01 system: Status {"network": {"eth0": True, "wifi0": False, "wifi1": False, "eth1": True, "eth2": False}, "disk": True, "io": True} 40 | ``` 41 | 42 | Unfortunately the ``Status`` dictionary may be printed with its keys 43 | in an *arbitrary order* so checking it directly will not always work. 44 | 45 | We could parse the log and do a more manual check... **or** we could 46 | think out of the box and *combine* different runners again: 47 | 48 | ```shell 49 | $ cat test/ds/log 50 | Sep 30 15:44:01 system: Loaded modules 51 | Sep 30 15:44:01 system: Status 52 | ``` 53 | 54 | ```python 55 | >>> # byexample: +paste +diff=ndiff 56 | {'disk': True, 57 | 'io': True, 58 | 'network': {'eth0': True, 59 | 'eth1': True, 60 | 'eth2': False, 61 | 'wifi0': False, 62 | 'wifi1': False}} 63 | ``` 64 | 65 | Some runners like Python and Ruby enable by default a *pretty printer*: 66 | native objects, specially the deeply nested ones, are printed in a *nice 67 | deterministic human way*. 68 | 69 | > Tip: for long and complex outputs like this one, you may want to use 70 | > a [differ](/{{ site.uprefix }}/docs/overview/differences.md) 71 | > that can highlight the small differences in case the example fail. 72 | 73 | -------------------------------------------------------------------------------- /docs/recipes/arguments-per-environment.md: -------------------------------------------------------------------------------- 1 | 9 | # Dynamic options based on the environment 10 | 11 | Imagine that you want to executes some documents/source codes but 12 | want to skip some of them **if** you are running in, let's say, MacOS. 13 | 14 | You could do something like this: 15 | 16 | ```shell 17 | $ cat test/ds/for-mac.args 18 | -l=python,shell 19 | --skip 20 | byexample/prof.py 21 | docs/overview/good-practices.md 22 | -- 23 | byexample/*.py 24 | docs/overview/*.md 25 | 26 | $ cat test/ds/for-linux.args 27 | -l=python,shell 28 | -- 29 | byexample/*.py 30 | docs/overview/*.md 31 | 32 | $ if [ "$(uname)" = "Darwin" ]; then 33 | > byexample @test/ds/for-mac.args 34 | > else 35 | > byexample @test/ds/for-linux.args 36 | > fi 37 | ``` 38 | 39 | Indeed `$(uname)` will return `"Darwin"` on MacOS and yes, `byexample` 40 | then will be executed with the additional arguments from 41 | `test/ds/for-mac.args`. 42 | 43 | But if your skills with shell scripting are a little rusted, or you have 44 | a much complicated situation that depends on a lot on the environment, 45 | you may want to try something else. 46 | 47 | Consider `test/ds/for-mac.args` and `test/ds/for-linux.args`, they are quite 48 | similar and only differ but just a little. 49 | 50 | You could use a *templating* system to generate the argument file from a 51 | template: 52 | 53 | ```shell 54 | $ cat test/ds/template.args 55 | -l=python,shell 56 | {% if osname == "Darwin" %} 57 | --skip 58 | byexample/prof.py 59 | docs/overview/good-practices.md 60 | {% endif %} 61 | -- 62 | byexample/*.py 63 | docs/overview/*.md 64 | ``` 65 | 66 | Then: 67 | 68 | ```shell 69 | $ osname=$(uname) j2 test/ds/template.args > test/ds/good.args 70 | $ byexample @test/ds/good.args 71 | ``` 72 | 73 | `j2` is one of many engines based on 74 | [Jinja2](https://jinja.palletsprojects.com/en/3.1.x/). 75 | I used [kolypto/j2cli](https://github.com/kolypto/j2cli) 76 | but other implementations exist like 77 | [mattrobenolt/jinja2-cli](https://github.com/mattrobenolt/jinja2-cli). 78 | 79 | Of course, using a template engine is just an idea. See if it helps 80 | you!! 81 | 82 | 85 | 86 | -------------------------------------------------------------------------------- /docs/recipes/django-integration.md: -------------------------------------------------------------------------------- 1 | # Django Integration 2 | 3 | Starting from `byexample` 9.0.1, we have integration with 4 | [Django](https://www.djangoproject.com/). 5 | 6 | When you are developing an *app* in Django it is common 7 | to explore it using a Python shell with all the settings 8 | needed by Django already loaded. 9 | 10 | You probably do: 11 | 12 | ```shell 13 | $ python manage.py shell # byexample: +skip 14 | ``` 15 | 16 | To run `byexample` inside the same environment having all 17 | the settings preloaded you just need a *custom* 18 | [shebang](/{{ site.uprefix }}/advanced/shebang) 19 | 20 | ```shell 21 | $ byexample -l python -x-shebang 'python:%p %a manage.py shell -i python' # byexample: +skip 22 | ``` 23 | 24 | That's too much to type! 25 | 26 | Remember that you can save part of the command line in a file: 27 | 28 | ```shell 29 | $ cat - > test/ds/django.conf < -x-shebang=python:%p %a manage.py shell -i python 31 | > EOF 32 | 33 | $ byexample -l python @test/ds/django.conf # byexample: +skip 34 | ``` 35 | 36 | ## How it works? 37 | 38 | `-x-shebang 'python:%p %a manage.py shell -i python'` tells `byexample` to 39 | use `%p %a manage.py shell -i python` as the interpreter to execute 40 | the examples written in Python. 41 | 42 | `%p %a manage.py shell -i python` will run something similar 43 | to `python manage.py shell` except that some extra flags are needed 44 | to customize the interpreter (hidden behind the magic `%a`). 45 | 46 | Check [shebang](/{{ site.uprefix }}/advanced/shebang) for more details. 47 | 48 | Other difference is that `manage.py` will use the more human friendly 49 | `ipython` interpreter. 50 | 51 | Currently `byexample` has no support for it 52 | ([pull requests](https://github.com/byexamples/byexample/tree/master/CONTRIBUTING.md) are welcome!) so 53 | the extra flag `-i python` tells `manage.py` to use the classic 54 | `python` interpreter. 55 | 56 | 59 | -------------------------------------------------------------------------------- /docs/recipes/handy-shell-snippets.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # Handy Shell Snippets 7 | 8 | ### Wait for a tcp port 9 | 10 | Wait for a tcp port is open and accepting connections. 11 | You may want to combine this with a 12 | [timeout](/{{ site.uprefix }}/basic/timeout) and with a 13 | [fail fast](/{{ site.uprefix }}/basic/setup-and-tear-down). 14 | 15 | ```shell 16 | $ wait_port() { 17 | > while ! nc -z 127.0.0.1 $1 >/dev/null 2>&1; do sleep 0.5; done 18 | > } 19 | 20 | $ wait_port 80 # byexample: +fail-fast +timeout=5 +skip 21 | ``` 22 | 23 | > Ignore the ``+skip`` option. 24 | 25 | If instead you want to pick a free port do something like this: 26 | 27 | ```shell 28 | $ free_port() { 29 | > for port in {1500..65000}; do ss -tln | grep -q ":$port " || echo "Port $port" && break; done 30 | > } 31 | 32 | $ free_port # byexample: +fail-fast +unless=on-macos 33 | Port 34 | ``` 35 | 36 | Like before you may want to combine this with a 37 | [fail fast](/{{ site.uprefix }}/basic/setup-and-tear-down) option and you 38 | can use the 39 | [capture and paste](/{{ site.uprefix }}/basic/capture-and-paste) functionality 40 | to save and use the port later without parsing the output yourself. 41 | 42 | > Note: `ss` is an "utility to investigate sockets". You may use 43 | > the older `netstat -tan` of the same purpose. 44 | 45 | ### Lock a file 46 | 47 | Use ``flock`` to synchronize your programs and avoid a race condition. 48 | 49 | Combine this with a 50 | [fail fast](/{{ site.uprefix }}/basic/setup-and-tear-down) to fail quickly 51 | if the lock cannot be obtained and with a 52 | [skip](/{{ site.uprefix }}/basic/setup-and-tear-down) to make your that 53 | sure unlock the file at the end: 54 | 55 | ```shell 56 | $ # try to get the lock, fail fast if we cannot 57 | $ exec {fd}>>test/ds/f && flock -n $fd || echo "Lock failed" # byexample: +fail-fast +unless=on-macos 58 | 59 | $ # your code here 60 | 61 | $ # release the lock, do not skip these steps to avoid deadlocks 62 | $ flock -u $lockfd # byexample: -skip +pass +unless=on-macos 63 | $ exec {lockfd}>&- # byexample: -skip +pass +unless=on-macos 64 | $ rm -f test/ds/f # byexample: -skip +pass 65 | ``` 66 | 67 | ### Keep tracking a log 68 | 69 | You have a program that logs something of your interest *asynchronously*. 70 | 71 | Use ``tail -f`` (or ``tailf``) to keep track of 72 | the log file *in the background*, only then run your program and 73 | lastly bring the ``tail`` back to the foreground to do the check. 74 | 75 | 79 | 80 | Here we do the first part using 81 | [+stop-on-silence](/{{ site.uprefix }}/languages/shell.md) to send it 82 | to the background: 83 | 84 | ```shell 85 | $ tail -f test/ds/some.log # byexample: +stop-on-silence 86 | ``` 87 | 88 | Then we run the asynchronous command (here you put *your* command) 89 | 90 | ```shell 91 | $ (sleep 0.5 ; echo 'very important message!' >> test/ds/some.log) & 92 | [] 93 | ``` 94 | 95 | And finally, we bring back the ``tail`` and check. We extend the 96 | [timeout](/{{ site.uprefix }}/basic/timeout) 97 | to give the ``echo`` an opportunity to complete and log. 98 | 99 | ```shell 100 | $ fg %1 # byexample: +stop-on-timeout +timeout=1 101 | tail -f test/ds/some.log 102 | very important message! 103 | ``` 104 | 105 | > Did you notice the difference between ``+stop-on-silence`` and 106 | > ``+stop-on-timeout``? The former sends the program to the background 107 | > if ``byexample`` does not detect any output from it after a small 108 | > fraction of time (aka silence). The latter does the same but when 109 | > the timeout is over. 110 | 111 | Because ``fg %1`` will never end we *need* to 112 | send it the background again and not fail with a timeout (that's why 113 | we use `+stop-on-timeout`). 114 | 115 | To finish it, we can kill it like any other process. You typically 116 | do not want to [skip](/{{ site.uprefix }}/basic/setup-and-tear-down) this. 117 | 118 | ```shell 119 | $ kill %% ; fg ; wait # byexample: -skip 120 | tail -f test/ds/some.log 121 | Terminated<...> 122 | ``` 123 | 124 | ### No POSIX-conformant Bash 125 | 126 | By default, `byexample` uses `bash` in POSIX-conformant mode. 127 | 128 | If you execute an shell example and you get a syntax error, you may be 129 | using a non-POSIX syntax. 130 | 131 | You can disable the POSIX-conformant from *within* `bash` with `set 132 | +o posix`: 133 | 134 | ```shell 135 | $ echo $POSIXLY_CORRECT # this Bash's variable says yes if we are in POSIX 136 | y 137 | 138 | $ set +o posix 139 | $ echo $POSIXLY_CORRECT # we are not longer in POSIX mode, happy hacking 140 | 141 | $ set -o posix 142 | $ echo $POSIXLY_CORRECT # back to the default of byexample 143 | y 144 | ``` 145 | 146 | 149 | 150 | -------------------------------------------------------------------------------- /docs/recipes/pre-commit.md: -------------------------------------------------------------------------------- 1 | # Pre-Commit 2 | 3 | [pre-commit](https://pre-commit.com/) is a framework for managing and 4 | maintaining multi-language pre-commit (git) hooks. 5 | 6 | Starting from `10.4.0`, `byexample` supports `pre-commit` so you can 7 | run the tests automatically before each commit. 8 | 9 | The proposed configuration that you need to write in 10 | `.pre-commit-config.yaml` in your git repository is: 11 | 12 | ```yaml 13 | repos: 14 | - repo: https://github.com/byexamples/byexample 15 | rev: 10.5.2 16 | hooks: 17 | - id: byexample 18 | types_or: [markdown, python] 19 | args: [--no-progress-bar, --jobs=2, -l=python, -l=shell] 20 | ``` 21 | 22 | The configuration above says that `pre-commit` will run `byexample` for 23 | all the `markdown` and `python` files that you modified before the 24 | commit. 25 | 26 | You can add more *types* to the list of course! `markdown` and `python` 27 | are just an example. 28 | 29 | The `args` are the arguments that are passed as they are to 30 | `byexample`. 31 | 32 | The most important ones are the language specification. In the example 33 | above `-l=python` and `-l=shell` means that `byexample` will search and 34 | execute Python and Shell snippets (inside the `markdown` and `python` 35 | files). 36 | 37 | `--no-progress-bar` disables the progress bar of `byexample`. It is not 38 | mandatory but it makes the output of `pre-commit` a little nicer. 39 | 40 | Any other argument can be passed, in the above example `--jobs=2` makes 41 | `byexample` to process 2 files at the same time. 42 | 43 | It is possible to write the arguments for `byexample` in a separated file, 44 | let's name it `boptions`, and load it as follows: 45 | 46 | ```yaml 47 | repos: 48 | - repo: https://github.com/byexamples/byexample 49 | rev: 10.5.2 50 | hooks: 51 | - id: byexample 52 | types_or: [markdown, python] 53 | args: ['@boptions'] 54 | ``` 55 | 56 | This is perhaps a little cleaner that writing the arguments in 57 | `.pre-commit-config.yaml` but it is a matter of taste. 58 | 59 | 60 | > *New* in ``byexample 10.4.0``. 61 | 62 | ## Gotchas when using `pre-commit` for Python 63 | 64 | `pre-commit` runs `byexample` in its own *private* environment. If your 65 | examples requires Python non-standard libraries, these libraries 66 | will not be in the private environment and your examples will fail. 67 | 68 | This is a design decision of `pre-commit`, however it is possible 69 | to workaround this and escape from the *private* environment and enter 70 | in the environment of *your* preference. 71 | 72 | `byexample` allows you to [change](/{{ site.uprefix }}/advanced/shebang) 73 | how the interpreter is executed so you 74 | can call *explicitly* the `python` binary of *your* environment. 75 | 76 | Here we added `-x-shebang` and `"python:%e ./your-env/bin/python %a"`. You 77 | must change the path to your `python` binary in your environment. 78 | 79 | ```yaml 80 | hooks: 81 | - id: byexample 82 | types_or: [markdown, python] 83 | args: [--no-progress-bar, -l=python, -x-shebang, "python:%e ./your-env/bin/python %a"] 84 | ``` 85 | 86 | Another way to workaround this is to do a helper script `envpy.sh` that activates 87 | and deactivates your environment: 88 | 89 | ```shell 90 | #!/bin/bash 91 | source your-env/bin/activate 92 | python $@ 93 | deactivate 94 | ``` 95 | 96 | Then, call that script as the `python` binary in the 97 | [shebang](/{{site.uprefix }}/advanced/shebang): 98 | 99 | ```yaml 100 | hooks: 101 | - id: byexample 102 | types_or: [markdown, python] 103 | args: [--no-progress-bar, -l=python, -x-shebang, "python:%e ./envpy %a"] 104 | ``` 105 | -------------------------------------------------------------------------------- /docs/recipes/running-with-docker.md: -------------------------------------------------------------------------------- 1 | # How to use a docker image 2 | 3 | Imagine that you want to use an interpreter that it is not installed in 4 | your system but it is in a docker image. 5 | 6 | For example, the C++ interpreter 7 | [cling](https://github.com/root-project/cling) is available in the 8 | [eldipa/cling](https://hub.docker.com/r/eldipa/cling) docker image. 9 | 10 | Let's say that you want to use that. 11 | 12 | First, of course, you need to download the image from the docker 13 | registry: 14 | 15 | ```shell 16 | $ sudo docker pull eldipa/cling # byexample: +skip 17 | ``` 18 | 19 | Now, create a script to run `cling` in the container: 20 | 21 | ```shell 22 | #!/bin/bash 23 | docker run --rm -it -v "$(pwd):/mnt" -w /mnt eldipa/cling cling "$@" 24 | ``` 25 | 26 | Call the script `docker-cling.sh`. 27 | 28 | Assuming it is saved in `/home/john/`, give it execution permissions with 29 | `chmod u+x /home/john/docker-cling.sh`. 30 | 31 | The script will start a temporal interactive docker container and run 32 | inside `cling` and it will mount the current directory from your host to 33 | `/mnt`. 34 | 35 | When the script is executed by `byexample`, the current directory will 36 | be the same of `byexample`. 37 | 38 | Finally, run `byexample` with a custom 39 | [shebang](/{{ site.uprefix}}/advanced/shebang) and an extra timeout 40 | (running docker is a little slow): 41 | 42 | ```shell 43 | $ byexample -l cpp -x-shebang="cpp:sudo /home/john/docker-cling.sh %a" -x-dfl-timeout 30 # byexample: +skip 44 | ``` 45 | 46 | If you prefer you can write this long command line in a file for easy 47 | reuse: 48 | 49 | ```shell 50 | # boptions.args file 51 | -l=cpp 52 | -x-shebang=cpp:sudo /home/john/docker-cling.sh %a 53 | -x-dfl-timeout=30 54 | ``` 55 | 56 | So you can then run `byexample` as follows: 57 | 58 | ```shell 59 | $ byexample @boptions.args # byexample: +skip 60 | ``` 61 | 62 | Shorter, isn't? 63 | 64 | Of course this example for C++ can be applied to any 65 | language/interpreter and used in combination with interpreters that 66 | don't require of docker. 67 | 68 | The following combines C++ (in a docker) and Python (in the host) 69 | without a problem: 70 | 71 | ```shell 72 | $ byexample @boptions.args -l python # byexample: +skip 73 | ``` 74 | 75 | ## Make `sudo` passwordless to avoid timeouts 76 | 77 | In the example above I use `sudo` to run `docker-cling.sh`. 78 | This is in general required because running `docker` is a privileged 79 | operation. 80 | 81 | Unfortunately this makes `sudo` to ask for a password 82 | and `byexample` currently does not support that. 83 | 84 | The solution is to make `sudo` passwordless for `docker-cling.sh` 85 | editing your `sudoers` file with `visudo`. 86 | 87 | Run `sudo visudo` and add the following line: 88 | 89 | ``` 90 | john ALL=(root) NOPASSWD: /home/john/docker-cling.sh 91 | ``` 92 | 93 | This tells `sudo` to not ask for a password (`NOPASSWD`) when the user 94 | `john` is trying to execute the program 95 | `/home/john/docker-cling.sh` to run it as 96 | `root`. 97 | 98 | Of course you will have to put *your* username and the full path of 99 | `docker-cling.sh` in that line. 100 | 101 | You can test that the `sudoers` rule is properly working running 102 | `sudo /home/john/docker-cling.sh` by hand. It should open the `cling` 103 | shell without asking a password. 104 | 105 | ### Issue: `sudo` still requires a password / prompt not found 106 | 107 | If you see something like 108 | 109 | ```shell 110 | $ byexample @boptions.args # byexample: +skip 111 | [w] Initialization of Cpp Runner failed. 112 | [!] Something went wrong processing the file <...>: 113 | Prompt not found: the code is taking too long to finish or there is a syntax error. 114 | 115 | Last 1000 bytes read: 116 | [sudo] password for john: 117 | 118 | Rerun with -vvv to get a full stack trace. 119 | ``` 120 | 121 | That indicates that `sudo` is not passwordless for `docker-cling.sh` yet. 122 | 123 | Check that you used the correct full path to the `docker-cling.sh` 124 | script in both `byexample` and in the `sudoers` file. (Something like 125 | `/home/john/docker-cling.sh`). 126 | 127 | Check also that you correctly typed the username. 128 | 129 | Then, run `sudo /home/john/docker-cling.sh` and it should not require a 130 | password. 131 | 132 | Ensure also that the script does not call `sudo` itself (the script 133 | should run `docker run...` and not `sudo docker run...`). 134 | 135 | `sudo` will not give you a hint of what it is the problem. If something 136 | does not match perfectly, `sudo` will fallback and ask a password. 137 | 138 | ### Issue: docker permission denied 139 | 140 | If you get something like 141 | 142 | ``` 143 | docker: Got permission denied 144 | ``` 145 | 146 | That means that you need to use `sudo` as explained above. 147 | -------------------------------------------------------------------------------- /docs/recipes/running-with-sudo.md: -------------------------------------------------------------------------------- 1 | # Running with `sudo` 2 | 3 | In general it is a bad idea to run arbitrary programs with `sudo`, 4 | specially programs that are for verification like `byexample`. 5 | 6 | However there are cases that it is required. 7 | 8 | ## Running a single command with `sudo` (passwordless version) 9 | 10 | For example you may need to run some code in a **privileged** `shell`, 11 | like the following: 12 | 13 | ```shell 14 | $ sudo test/ds/script-requires-root.sh # byexample: +skip 15 | Checking <...> 16 | Done. 17 | ``` 18 | 19 | That will work only if `sudo` does not ask a password. 20 | 21 | You can achieve this making `test/ds/script-requires-root.sh` 22 | passwordless editing you `sudoers` file with `visudo` and adding the 23 | following line: 24 | 25 | 26 | ``` 27 | john ALL=(root) NOPASSWD: /home/john/full-path-here/test/ds/script-requires-root.sh 28 | ``` 29 | 30 | This tells `sudo` to not ask for a password (`NOPASSWD`) when the user 31 | `john` is trying to execute the program 32 | `/home/john/full-path-here/test/ds/script-requires-root.sh` to run it as 33 | `root`. 34 | 35 | ## Running a single command with `sudo` (password version) 36 | 37 | Now you *could* run `sudo` and let it to ask for a password and you 38 | *could* type the password in from `byexample` using 39 | [+type](/{{ site.uprefix }}/basic/input) but you need to realize that 40 | that would mean that you are typing your password in from of all your 41 | readers: 42 | 43 | ```shell 44 | $ sudo test/ds/script-requires-root.sh # byexample: +type +skip 45 | <...>password for john: [your-plain-text-password-here] 46 | Checking <...> 47 | Done. 48 | ``` 49 | 50 | A preferible approach could have the password in an environment variable 51 | named `PASS` and *paste it* with 52 | [+paste](/{{ site.uprefix }}/basic/capture-and-paste): 53 | 54 | 55 | ```shell 56 | $ sudo test/ds/script-requires-root.sh # byexample: +paste +type +skip 57 | <...>password for user: [] 58 | Checking <...> 59 | Done. 60 | ``` 61 | 62 | Then, you should run `byexample` passing that `PASS` variable and 63 | [capturing the environment](/{{ site.uprefix }}/advanced/capture-environment-variables) 64 | so the variable is available in the example and be pasted into. 65 | 66 | Something like this: 67 | 68 | ```shell 69 | $ PASS=johnpasswordhere byexample -l shell --capture-env-var PASS test/ds/doc-with-sudo.md # byexample: +skip 70 | [PASS] Pass: 1 Fail: 0 Skip: 0 71 | ``` 72 | 73 | ### Uncertainty due `sudo` caching the password 74 | 75 | Passing a password will work but **beware**. 76 | 77 | The example where you are typing the password (with or without having it 78 | pasted from `PASS`) *depends* on `sudo` *asking* the password in the first 79 | place. 80 | 81 | `sudo` caches the password and subsequent calls to `sudo` will not 82 | ask for the password again. But this cache expires after a while (in my 83 | system is after 15 minutes). 84 | 85 | 86 | If you have a long running examples, this may cause you some troubles 87 | as you could not tell when a call to `sudo` will or will not ask for a 88 | password. 89 | 90 | To be certain you could call `sudo -k` to clean up the cache and be sure 91 | that the next call to `sudo` will ask you a password. 92 | 93 | ## Running the entire shell with `sudo` 94 | 95 | If instead of having to run a few commands as root you need an entire 96 | shell session, you can pass `sudo` in the 97 | [shebang](/{{ site.uprefix }}/advanced/shebang): 98 | 99 | ```shell 100 | $ byexample -l shell -x-shebang 'shell:%e sudo %p %a' test/ds/doc-without-sudo.md # byexample: +skip 101 | [PASS] Pass: 1 Fail: 0 Skip: 0 102 | ``` 103 | 104 | This simplifies your documentation avoiding all the `sudo` calls but 105 | there is a catch. 106 | 107 | At the moment `byexample` does not support passing passwords to the 108 | shebang so you will have to make the shell program (typically `bash`) 109 | *passwordless*. 110 | 111 | If making **any** `sudo bash` passwordless scares you (and it should!), 112 | we can lower slightly the risk with an auxiliary script that opens `bash` (or other 113 | shell [supported by `byexample`](/{{ site.uprefix }}/languages/shell)): 114 | 115 | ``` 116 | #!/bin/sh 117 | bash "$@" 118 | ``` 119 | 120 | We make the auxiliary script `open-bash.sh` passwordless editing 121 | `sudoers`: 122 | 123 | ``` 124 | john ALL=(root) NOPASSWD: /home/john/full-path-here/test/ds/open-bash.sh 125 | ``` 126 | 127 | With this only `sudo test/ds/open-bash.sh` will be passwordless, other 128 | calls to `sudo bash` will require a password as usual. 129 | 130 | Finally we tell `byexample` to use that script instead of `bash`: 131 | 132 | ```shell 133 | $ byexample -l shell -x-shebang 'shell:%e sudo test/ds/open-bash.sh %a' test/ds/doc-without-sudo.md # byexample: +skip 134 | [PASS] Pass: 1 Fail: 0 Skip: 0 135 | ``` 136 | 137 | The advantage is that we can create 138 | `/home/john/full-path-here/test/ds/open-bash.sh` before running 139 | `byexample` and delete it after. Assuming that you don't allow anyone 140 | else to create the file, this is relatively safe. 141 | 142 | If you have trouble with this or you feel that `byexample` is missing a 143 | feature, don-t be afraid and [open an issue in Github](https://github.com/byexamples/byexample/issues). 144 | -------------------------------------------------------------------------------- /media/README.rst: -------------------------------------------------------------------------------- 1 | Demo for ``byexmaple``. 2 | * asciinema 1.4.0 3 | * asciicast2gif 4 | * docker run --rm -v /home/user/media:/data asciinema/asciicast2gif -t solarized-dark -w 60 -h 18 demo.json demo.gif 5 | 6 | Thanks to `asciinema `_ and to `asciicast2gif `_. 7 | 8 | Link "anchor" icon from `fontawesome ` 9 | -------------------------------------------------------------------------------- /media/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/demo.gif -------------------------------------------------------------------------------- /media/internals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/internals.png -------------------------------------------------------------------------------- /media/logos/README.md: -------------------------------------------------------------------------------- 1 | The Python logo is a trademark of the Python Software Foundation. [1](https://www.python.org/community/logos/) 2 | The Node.js (Javascript) logo is a trademark of Joyent, Inc.[2](https://nodejs.org/es/about/resources/) 3 | The Ruby logo is Copyright © 2006, Yukihiro Matsumoto. [3](https://www.ruby-lang.org/en/about/logo/) 4 | The Bash logo. [4](https://github.com/odb/official-bash-logo) 5 | The GDB logo. [5](https://www.gnu.org/software/gdb/mascot/) 6 | The C++ logo created by Jeremy Kratz. [6](https://github.com/isocpp/logos) 7 | The Elixir logo. [7](https://elixir-lang.org/) 8 | The PHP logo designed by Vincent Pontier. [8](https://www.php.net/) 9 | The Linux logo. [9](https://www.linux.org/) 10 | The MacOS logo. [10](http://www.apple.com/) 11 | The Windows logo. [11](https://www.microsoft.com) 12 | The ByExample logo created by R.V. Facultad de Ingeniería Equipo de Quimera Taller Símbolo licensed for Personal Use. [12](https://www.freepng.es/png-zb54ue/) 13 | The Microsoft's PowerShell logo. [13](https://en.wikipedia.org/wiki/PowerShell) 14 | The iasm logo. [14](https://github.com/bad-address/iasm) 15 | The Go logo. [15](https://golang.org/) 16 | The Rust logo. [16](https://www.rust-lang.org/) 17 | The Java logo. [17](https://docs.oracle.com/javase/tutorial/index.html) 18 | -------------------------------------------------------------------------------- /media/logos/bash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/bash_logo.png -------------------------------------------------------------------------------- /media/logos/byexample_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/byexample_logo.png -------------------------------------------------------------------------------- /media/logos/cpp_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/cpp_logo.png -------------------------------------------------------------------------------- /media/logos/elixir_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/elixir_logo.png -------------------------------------------------------------------------------- /media/logos/gdb_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/gdb_logo.png -------------------------------------------------------------------------------- /media/logos/go_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/go_logo.png -------------------------------------------------------------------------------- /media/logos/iasm_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/iasm_logo.png -------------------------------------------------------------------------------- /media/logos/java_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/java_logo.png -------------------------------------------------------------------------------- /media/logos/javascript_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/javascript_logo.png -------------------------------------------------------------------------------- /media/logos/linux_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/linux_logo.png -------------------------------------------------------------------------------- /media/logos/macos_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/macos_logo.png -------------------------------------------------------------------------------- /media/logos/php_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/php_logo.png -------------------------------------------------------------------------------- /media/logos/powershell_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/powershell_logo.png -------------------------------------------------------------------------------- /media/logos/python_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/python_logo.png -------------------------------------------------------------------------------- /media/logos/ruby_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/ruby_logo.png -------------------------------------------------------------------------------- /media/logos/rust_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/rust_logo.png -------------------------------------------------------------------------------- /media/logos/windows_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/media/logos/windows_logo.png -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | yapf 2 | twine 3 | coverage 4 | j2cli 5 | wheel 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # https://packaging.python.org/en/latest/distributing.html 2 | # https://github.com/pypa/sampleproject 3 | 4 | from setuptools import setup, find_packages 5 | from codecs import open 6 | from os import path, system 7 | 8 | import sys, re 9 | 10 | here = path.abspath(path.dirname(__file__)) 11 | 12 | try: 13 | system('''pandoc -f markdown-raw_html -o '%(dest_rst)s' '%(src_md)s' ''' % { 14 | 'dest_rst': path.join(here, 'README.rst'), 15 | 'src_md': path.join(here, 'README.md'), 16 | }) 17 | 18 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 19 | long_description = f.read() 20 | 21 | # strip out any HTML comment|tag 22 | long_description = re.sub(r'', '', long_description, 23 | flags=re.DOTALL|re.MULTILINE) 24 | long_description = re.sub(r'', '', long_description, 25 | flags=re.DOTALL|re.MULTILINE) 26 | 27 | with open(path.join(here, 'README.rst'), 'w', encoding='utf-8') as f: 28 | f.write(long_description) 29 | 30 | except: 31 | print("Generation of the documentation failed. " + \ 32 | "Do you have 'pandoc' installed?") 33 | 34 | long_description = __doc__ 35 | 36 | # load __version__, __doc__, _author, _license and _url 37 | exec(open(path.join(here, 'byexample', '__init__.py')).read()) 38 | 39 | # the following are the required dependencies 40 | # without them, we cannot run byexample 41 | # NOTE: keep this list in sync with byexample/cmdline.py 42 | required_deps=[ 43 | 'pexpect>=4,<5', # pexpect 4.x.x required 44 | 'appdirs>=1.4.3,<2', # appdirs 1.4.x (x >= 3) required 45 | 'termscraper>=0.10,<0.11', # termscraper 0.10.x 46 | 'bracex>=2.1.0,<3', # bracex 2.x required (with x >= 1) 47 | 'importlib-resources>=5.5.0,<6.0.0', # importlib-resources 5.y.x (y >= 5) 48 | ] 49 | 50 | # these, on the other hand, are optional nice to have 51 | # dependencies. we'll install them by default but if they 52 | # are not present, byexample will run normally. 53 | # NOTE: keep this list in sync with byexample/cmdline.py 54 | nice_to_have_deps=[ 55 | 'tqdm>=4,<5', # tqdm 4.x.x required 56 | 'pygments>=2,<3', # pygments 2.x.x required 57 | 'argcomplete>=2.0.0,<2.1.0' # argcomplete 2.x.x required 58 | ] 59 | 60 | # run 61 | # python setup.py install --byexample-minimal 62 | # to install only the required dependencies 63 | if '--byexample-minimal' in sys.argv: 64 | sys.argv.remove('--byexample-minimal') 65 | install_deps = required_deps 66 | 67 | else: 68 | install_deps = required_deps + nice_to_have_deps 69 | 70 | setup( 71 | name='byexample', 72 | version=__version__, 73 | 74 | description=__doc__, 75 | long_description=long_description, 76 | 77 | url=_url, 78 | 79 | # Author details 80 | author=_author, 81 | author_email='use-github-issues@example.com', 82 | 83 | license=_license, 84 | 85 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 86 | classifiers=[ 87 | 'Development Status :: 5 - Production/Stable', 88 | 'Intended Audience :: Developers', 89 | 'Topic :: Documentation', 90 | 'Topic :: Software Development :: Documentation', 91 | 'Topic :: Software Development :: Testing', 92 | 93 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 94 | 95 | 'Programming Language :: Python :: 2', 96 | 'Programming Language :: Python :: 3', 97 | 'Programming Language :: Python :: 2.7', 98 | 'Programming Language :: Python :: 3.4', 99 | 'Programming Language :: Python :: 3.5', 100 | 'Programming Language :: Python :: 3.6', 101 | 'Programming Language :: Python :: 3.7', 102 | 'Programming Language :: Python :: 3.8', 103 | 'Programming Language :: Python :: 3.9', 104 | 'Programming Language :: Python :: 3.10', 105 | 'Programming Language :: C', 106 | 'Programming Language :: C++', 107 | 'Programming Language :: JavaScript', 108 | 'Programming Language :: Other', 109 | 'Programming Language :: Other Scripting Engines', 110 | 'Programming Language :: PHP', 111 | 'Programming Language :: Ruby', 112 | 'Programming Language :: Unix Shell', 113 | ], 114 | 115 | python_requires='>=3.7', 116 | install_requires=install_deps, 117 | 118 | keywords='doctest documentation test testing', 119 | 120 | packages=find_packages(), 121 | data_files=[("", ["LICENSE"])], 122 | package_data={'byexample':["modules/gadgets/*", "autocomplete/*"]}, 123 | 124 | entry_points={ 125 | 'console_scripts': [ 126 | 'byexample = byexample.byexample:main', 127 | ], 128 | } 129 | ) 130 | 131 | -------------------------------------------------------------------------------- /test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster-slim 2 | 3 | RUN apt-get update \ 4 | && DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends install -y \ 5 | # Dependencies for Cling, the interpreter of C++ 6 | # Due licensing cling does not offer a binary for Debian so we need 7 | # all of this to compile cling ourselves 8 | git \ 9 | gcc \ 10 | g++ \ 11 | debhelper \ 12 | devscripts \ 13 | gnupg \ 14 | wget \ 15 | cmake \ 16 | python \ 17 | ca-certificates \ 18 | sudo \ 19 | # Install Python, necessary to run byexample and to run the examples written 20 | # in Python. Pick only Python 3 21 | python3 \ 22 | python3-pip \ 23 | python3-setuptools \ 24 | # Different shells to test the examples written in Shell 25 | dash \ 26 | ksh \ 27 | bash \ 28 | # Interpreters for Ruby, Javascript and GDB 29 | ruby \ 30 | nodejs \ 31 | gdb \ 32 | # Vim, of course, to write the tests. 33 | vim \ 34 | less \ 35 | # Interpreters for PHP and Elixir 36 | # (Elixir require a modern repository and Erlang/OTP platform) 37 | php-cli 38 | 39 | RUN DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends install -y \ 40 | elixir 41 | 42 | 43 | # Erlang 44 | #RUN wget https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb && dpkg -i erlang-solutions_2.0_all.deb \ 45 | # && DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends install -y \ 46 | # esl-erlang \ 47 | # && apt-get clean \ 48 | # && rm -rf /var/lib/apt/lists/ 49 | 50 | # Cling compilation and installation 51 | #RUN wget https://raw.githubusercontent.com/root-project/cling/master/tools/packaging/cpt.py \ 52 | # && chmod +x cpt.py \ 53 | # && ./cpt.py --last-stable=tar -y --use-wget --no-test \ 54 | # && ln -s /root/ci/build/builddir/bin/cling /usr/bin/cling 55 | 56 | 57 | # Install byexample, you can run later "pip3 install -U byexample" to 58 | # get the latest version or "pip3 install -e ." to install a dev version 59 | CMD /bin/bash 60 | -------------------------------------------------------------------------------- /test/Dockerfile-cling: -------------------------------------------------------------------------------- 1 | FROM continuumio/miniconda3 2 | 3 | RUN conda install -y -c conda-forge/label/cf202003 cling \ 4 | && conda clean -y --all 5 | 6 | # Build the container with 7 | # 8 | # $ sudo docker build -f Dockerfile-cling -t mycling . # <- don't forget this dot at the end! 9 | # 10 | # Change 'mycling' to the name that you want. 11 | # 12 | # To use this docker container with byexample to run C++ examples 13 | # run byexample as 14 | # 15 | # $ dockercmd="sudo docker run --rm -it -v :/mnt -w /mnt mycling cling %a" 16 | # $ byexample -l cpp -x-shebang="cpp:$dockercmd" 17 | # 18 | # The container will have access to , where should be your files, 19 | # via the local /mnt folder, which will become the working directory. 20 | # 21 | # See the documentation of "docker run" for more information 22 | # 23 | -------------------------------------------------------------------------------- /test/consistent-version.md: -------------------------------------------------------------------------------- 1 | Ensure that the current git tag (version of `byexample`) is written 2 | correctly in different parts of the project, source code and 3 | documentation. 4 | 5 | ```shell 6 | $ echo ${BYEXAMPLE_NEXT_VERSION:-$(git describe --abbrev=0)} 7 | 8 | 9 | $ grep -c "" README.md # byexample: +paste 10 | 1 11 | 12 | $ grep -c "" byexample/__init__.py # byexample: +paste 13 | 1 14 | 15 | $ grep -c "rev: " docs/recipes/pre-commit.md # byexample: +paste 16 | 2 17 | ``` 18 | -------------------------------------------------------------------------------- /test/corner.env: -------------------------------------------------------------------------------- 1 | --ff 2 | --timeout=8 3 | --languages=python,shell 4 | -------------------------------------------------------------------------------- /test/coverage.env: -------------------------------------------------------------------------------- 1 | --ff 2 | --languages=python,shell 3 | --timeout=4 4 | --skip=docs/recipes/python-doctest.md 5 | -------------------------------------------------------------------------------- /test/coverage.py: -------------------------------------------------------------------------------- 1 | from byexample.concern import Concern 2 | import coverage 3 | 4 | class ByexampleCoverage(Concern): 5 | target = 'byexample-coverage' 6 | 7 | def __init__(self, **unused): 8 | pass 9 | 10 | def start(self, examples, runners, filepath, options): 11 | self.runners = runners 12 | self.options = options 13 | for runner in runners: 14 | if runner.language == 'python': 15 | coverage_start_code = r''' 16 | from coverage import Coverage as _cov_class 17 | _cov_instance = _cov_class(source=['byexample'], auto_data=True) 18 | _cov_instance.start() 19 | ''' 20 | runner._exec_and_wait(coverage_start_code, options, timeout=10) 21 | break 22 | 23 | 24 | def finish(self, *args): 25 | for runner in self.runners: 26 | if runner.language == 'python': 27 | coverage_end_code = r''' 28 | _cov_instance.stop() 29 | _cov_instance.save() 30 | ''' 31 | runner._exec_and_wait(coverage_end_code, self.options, timeout=10) 32 | break 33 | -------------------------------------------------------------------------------- /test/crash.md: -------------------------------------------------------------------------------- 1 | ```python 2 | >>> exit() # I am a bad example ;) 3 | ``` 4 | -------------------------------------------------------------------------------- /test/docker-cling.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run --rm -it -v "$(pwd):/mnt" -w /mnt eldipa/cling cling "$@" 3 | -------------------------------------------------------------------------------- /test/docker.env: -------------------------------------------------------------------------------- 1 | --jobs=1 2 | --pretty=all 3 | --languages=python,shell,ruby,gdb,javascript,php,elixir 4 | --timeout=8 5 | --encoding=utf-8 6 | -x-shebang=python:%e python3 %a 7 | -------------------------------------------------------------------------------- /test/docker.no-python-shell.env: -------------------------------------------------------------------------------- 1 | --jobs=1 2 | --pretty=all 3 | --languages=ruby,gdb,javascript,php,elixir 4 | --encoding=utf-8 5 | --timeout=8 6 | -------------------------------------------------------------------------------- /test/ds/about-lic-with-tags.doc: -------------------------------------------------------------------------------- 1 | This example is to show you something about GPL 2 | $ cat test/ds/lic.doc 3 | To protect , we need to prevent others from 4 | or . Therefore, you have 5 | certain responsibilities if you distribute copies of the software, or if 6 | you modify it: . 7 | -------------------------------------------------------------------------------- /test/ds/about-lic.doc: -------------------------------------------------------------------------------- 1 | This example is to show you something about GPL 2 | $ cat test/ds/lic.doc 3 | To protect your rights, we need to prevent others from denying you 4 | these rights or asking you to surrender the rights. Therefore, you have 5 | certain responsibilities if you distribute copies of the software, or if 6 | you modify it: responsibilities to respect the freedom of others. 7 | -------------------------------------------------------------------------------- /test/ds/ascii_with_unicode_example.md: -------------------------------------------------------------------------------- 1 | ```shell 2 | $ cat docs/advanced/unicode.md 3 | ``` 4 | -------------------------------------------------------------------------------- /test/ds/bad-unicode: -------------------------------------------------------------------------------- 1 | Those would fail: 2 | 3 | $ echo 'por-éjemplo' 4 | por ejemplo 5 | 6 | $ echo 'по-примеру!' 7 | по примеру 8 | 9 | $ echo '例によっ!て' 10 | 例によって 11 | 12 | -------------------------------------------------------------------------------- /test/ds/bad/init/init_failed.py: -------------------------------------------------------------------------------- 1 | from byexample.concern import Concern 2 | 3 | stability = 'provisional' 4 | 5 | class BadInit(Concern): 6 | target = 'badinit' 7 | 8 | def __init__(self, **kargs): 9 | Concern.__init__(self, **kargs) 10 | 11 | # This will fail and we expect the exception to be caught 12 | # by byexample initialization process 13 | print(self.noattr) 14 | -------------------------------------------------------------------------------- /test/ds/bad/init_not_called/cfg/badconcernnewstyle.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from byexample.concern import Concern 3 | import byexample.regex as re 4 | from functools import partial 5 | import os 6 | 7 | stability = 'provisional' 8 | 9 | class BadConcernNewStyle(Concern): 10 | target = 'badconcernnewstyle' 11 | 12 | def __init__(self, **kargs): 13 | # Not calling Concern.__init__ is an error 14 | self.verbosity = self.cfg.verbosity 15 | self.encoding = self.cfg.encoding 16 | -------------------------------------------------------------------------------- /test/ds/bad/init_not_called/chk/badconcernoldstyle.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from byexample.concern import Concern 3 | import byexample.regex as re 4 | from functools import partial 5 | import os 6 | 7 | stability = 'provisional' 8 | 9 | class BadConcernOldStyle(Concern): 10 | target = 'badconcernoldstyle' 11 | 12 | def __init__(self, verbosity, encoding, **kargs): 13 | # Not calling Concern.__init__ is an error 14 | self.verbosity = verbosity 15 | self.encoding = encoding 16 | -------------------------------------------------------------------------------- /test/ds/bad/pexpect_init/badrunner.py: -------------------------------------------------------------------------------- 1 | from byexample.runner import ExampleRunner, PexpectMixin 2 | 3 | stability = 'experimental' 4 | 5 | class BadRunner(ExampleRunner, PexpectMixin): 6 | language = 'badrunner' 7 | 8 | def __init__(self, **kargs): 9 | # Calling PexpectMixin before ExampleRunner.__init__ is an error 10 | PexpectMixin.__init__( 11 | self, PS1_re=r'\(gdb\)[ ]', any_PS_re=r'\(gdb\)[ ]' 12 | ) 13 | 14 | ExampleRunner.__init__(self, **kargs) 15 | 16 | -------------------------------------------------------------------------------- /test/ds/bad/pexpect_not_runner/non_runner.py: -------------------------------------------------------------------------------- 1 | from byexample.runner import PexpectMixin 2 | from byexample.concern import Concern 3 | 4 | stability = 'experimental' 5 | 6 | class BadNonRunner(Concern, PexpectMixin): 7 | target = 'bad_not_runner' 8 | 9 | def __init__(self, **kargs): 10 | Concern.__init__(self, **kargs) 11 | 12 | # We cannot inherit from PexpectMixin if we don't 13 | # inherit from ExampleRunner too 14 | PexpectMixin.__init__( 15 | self, PS1_re=r'\(gdb\)[ ]', any_PS_re=r'\(gdb\)[ ]' 16 | ) 17 | -------------------------------------------------------------------------------- /test/ds/bad/syntax/m.py: -------------------------------------------------------------------------------- 1 | # Just a bad python code (syntax error) 2 | class -not-a-valid-code: 3 | pass 4 | -------------------------------------------------------------------------------- /test/ds/bad/target/invalid/bad.py: -------------------------------------------------------------------------------- 1 | from byexample.concern import Concern 2 | 3 | stability = 'provisional' 4 | 5 | class BadTarget(Concern): 6 | # This is wrong, a target cannot be a list. 7 | target = ['bogusmodule'] 8 | 9 | def __init__(self, **kargs): 10 | Concern.__init__(self, **kargs) 11 | -------------------------------------------------------------------------------- /test/ds/bad/target/missing/bad.py: -------------------------------------------------------------------------------- 1 | from byexample.concern import Concern 2 | 3 | stability = 'provisional' 4 | 5 | class BadTarget(Concern): 6 | def __init__(self, **kargs): 7 | Concern.__init__(self, **kargs) 8 | # 'target' attribute is missing, 9 | # byexample will complain about this 10 | assert not hasattr(self, 'target') 11 | -------------------------------------------------------------------------------- /test/ds/bad/target/multi/warn.py: -------------------------------------------------------------------------------- 1 | from byexample.finder import ZoneDelimiter 2 | 3 | stability = 'provisional' 4 | 5 | class MultiTargetDuplicated(ZoneDelimiter): 6 | # This is not an error but clearly a typo. 7 | target = ['foo', 'foo'] 8 | 9 | class MultiTargetEmpty(ZoneDelimiter): 10 | # This is not an error but setting to None is better 11 | # to make explicit the intention 12 | target = [] 13 | -------------------------------------------------------------------------------- /test/ds/binary-blob: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/test/ds/binary-blob -------------------------------------------------------------------------------- /test/ds/blog-database: -------------------------------------------------------------------------------- 1 | >>> from __future__ import print_function 2 | >>> import sys 3 | 4 | >>> def load_database(): 5 | ... print("Loading...") 6 | ... print("debug 314kb", file=sys.stderr) 7 | ... print("Done") 8 | 9 | >>> load_database() 10 | Loading... 11 | Done 12 | -------------------------------------------------------------------------------- /test/ds/capture-bomb.doc: -------------------------------------------------------------------------------- 1 | $ echo 'Booom!' # byexample: +unless=bomb_disabled 2 | The bomb should not explode! 3 | -------------------------------------------------------------------------------- /test/ds/capture-env.doc: -------------------------------------------------------------------------------- 1 | >>> import os 2 | >>> assert '' == os.getcwd() # byexample: +paste 3 | -------------------------------------------------------------------------------- /test/ds/cli.md: -------------------------------------------------------------------------------- 1 | ```shell 2 | $ echo -e "This is a broken \033[23;2Hmessage\033[23;77H" 3 | <...>This is a broken message! 4 | ``` 5 | 6 | -------------------------------------------------------------------------------- /test/ds/collisions.doc: -------------------------------------------------------------------------------- 1 | 2 | The # is a Shell prompt but also a comment in Python and Ruby 3 | The >>> is a Python prompt and >> and # >> are valid Ruby prompts 4 | 5 | The following is ambiguous because it could be a Shell or a Ruby 6 | example 7 | 8 | # >> 1 + 2 9 | err 10 | 11 | It is ambiguous even if one is larger than the other (the Shell 12 | example spans 2 lines, the first Ruby example just 1) 13 | 14 | # >> 1 + 2 15 | >> 16 | 17 | This case is not ambiguous: the first example is in Ruby and spans 18 | 2 lines, the "second" should be a Shell example of 1 line but we 19 | consider take the first example as valid. 20 | 21 | >> puts "# 1 + 2" 22 | # 1 + 2 23 | 24 | If those two are actually two examples, we could print a warning 25 | saying that the Ruby example is shadowing the Shell example, "put 26 | and extra line in between them". 27 | 28 | -------------------------------------------------------------------------------- /test/ds/db-stock-model: -------------------------------------------------------------------------------- 1 | This is a quick introduction to the database schema. 2 | >>> import sqlite3 3 | >>> c = sqlite3.connect(':memory:') 4 | >>> _ = c.executescript(open('test/ds/stock.sql').read()) # ---> # byexample: +fail-fast 5 | 6 | Get the stocks' prices 7 | >>> _ = c.execute('select price from stocks') 8 | 9 | Do not forget to close the connection 10 | >>> c.close() # ---> # byexample: -skip 11 | 12 | -------------------------------------------------------------------------------- /test/ds/doctest-hard-diff.md: -------------------------------------------------------------------------------- 1 | 2 | ```python 3 | >>> import pprint 4 | 5 | >>> def set_breakpoint(func, lineno, file): 6 | ... addr = 0x18172 # random 7 | ... path = 'workdir-random-path-here/' + file 8 | ... bkt = {'addr': hex(addr), 9 | ... 'file': file, 10 | ... 'fullname': path, 11 | ... 'func': func, 12 | ... 'line': str(lineno), 13 | ... 'original-location': addr, 14 | ... 'thread': ['1', '1'], 15 | ... 'thread-groups': ['i1'], 16 | ... 'times': '0', 17 | ... 'type': 'breakpoint'} 18 | ... 19 | ... return {'debugger-id': 0x1221, # more random stuff 20 | ... 'results': {'bkpts': [bkt]}, 21 | ... 'type': 'Sync'} 22 | 23 | >>> b1 = set_breakpoint('main', 5, 'example.c') 24 | >>> pprint.pprint(b1) # doctest: +ELLIPSIS 25 | {'debugger-id': ... 26 | 'results': {'bkpts': [{'addr': ..., 27 | 'file': 'example.c', 28 | 'fullname': ..., 29 | 'func': 'main', 30 | 'line': '5', 31 | 'original-location': ..., 32 | 'thread': ['1', '1'], 33 | 'thread-group': ['i1'], 34 | 'times': '0', 35 | 'type': 'breakpoint'}]}, 36 | 'type': 'Sync'} 37 | 38 | >>> True # this should not be executed if the above example failed and FAIL_FAST is enabled 39 | False 40 | 41 | ``` 42 | -------------------------------------------------------------------------------- /test/ds/dual.md: -------------------------------------------------------------------------------- 1 | This `.md` test file should work even if `+py-doctest` is enabled or not 2 | form the command line so the following should not be altered by that 3 | as `<...>` (aka `+tags`) should work by default regardless of 4 | `+py-doctest`. 5 | 6 | ```shell 7 | $ echo "foo" 8 | f<...> 9 | ``` 10 | 11 | Run `+py-doctest +ELLIPSIS` which enables `+tags` for this example: 12 | 13 | ```python 14 | >>> print("foo") # byexample: +py-doctest +ELLIPSIS 15 | f... 16 | ``` 17 | 18 | This example is not affected by `+py-doctest` which disables `+tags` 19 | because `+py-doctest` was applied to the example above. 20 | 21 | ```shell 22 | $ echo "foo" 23 | f<...> 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /test/ds/echo-filtering-required.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | `stty echo` will turn the echo on so the example requires an active echo 9 | filtering 10 | 11 | ```shell 12 | $ stty echo 13 | 14 | $ echo "normal output" 15 | normal output 16 | ``` 17 | 18 | But for Python we don't require the filtering so we must not activate it 19 | 20 | ```python 21 | >>> print(1) # byexample: +unless=on-macos 22 | 1 23 | ``` 24 | 25 | ```shell 26 | $ stty echo 27 | 28 | $ echo "normal output" # byexample: +force-echo-filtering 29 | normal output 30 | ``` 31 | -------------------------------------------------------------------------------- /test/ds/example-with-tabs-in-code.md: -------------------------------------------------------------------------------- 1 | 2 | ```python 3 | >>> print(" <-this is a tab") # byexample: +term=as-is 4 | <-this is a tab 5 | ``` 6 | -------------------------------------------------------------------------------- /test/ds/example-with-tabs.md: -------------------------------------------------------------------------------- 1 | 2 | ```python 3 | >>> print(" <-spaces") 4 | <-spaces 5 | ``` 6 | -------------------------------------------------------------------------------- /test/ds/file_patterns/foo*: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/test/ds/file_patterns/foo* -------------------------------------------------------------------------------- /test/ds/file_patterns/foo{1,2}: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byexamples/byexample/a8bea7607f743a5c2ffbbf82e51367518d87a10a/test/ds/file_patterns/foo{1,2} -------------------------------------------------------------------------------- /test/ds/first-example.md: -------------------------------------------------------------------------------- 1 | This a Markdown file with some examples embebed 2 | like this one: 3 | ```python 4 | >>> 1 + 2 5 | 3 6 | ``` 7 | However anything outside of a code block or comment 8 | is ignored: 9 | >>> like this, this is not an example 10 | so it is not executed 11 | -------------------------------------------------------------------------------- /test/ds/for-linux.args: -------------------------------------------------------------------------------- 1 | -l=python,shell 2 | -- 3 | byexample/*.py 4 | docs/overview/*.md 5 | 6 | -------------------------------------------------------------------------------- /test/ds/for-mac.args: -------------------------------------------------------------------------------- 1 | -l=python,shell 2 | --skip 3 | byexample/prof.py 4 | docs/overview/good-practices.md 5 | -- 6 | byexample/*.py 7 | docs/overview/*.md 8 | -------------------------------------------------------------------------------- /test/ds/gauss-example.md: -------------------------------------------------------------------------------- 1 | This a Markdown file with an example of 2 | how to count the sum of numbers: 3 | 4 | ```python 5 | >>> cnt = 0 6 | >>> for i in range(100): 7 | ... cnt += i 8 | 9 | >>> cnt 10 | 42 11 | ``` 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/ds/iasm.md: -------------------------------------------------------------------------------- 1 | 2 | ```nasm 3 | :> ;! M 4 | [0x0-0x18fff] (sz 0x19000) 5 | ``` 6 | -------------------------------------------------------------------------------- /test/ds/lic.doc: -------------------------------------------------------------------------------- 1 | To protect your rights, we need to prevent no-one from denying you 2 | these rights or asking you to surrender the rights. Therefore, you don't have 3 | certain responsibilities if you distribute copies of the software, or if 4 | you modify it: responsibilities to respect the freedom of others. 5 | -------------------------------------------------------------------------------- /test/ds/log: -------------------------------------------------------------------------------- 1 | Sep 30 15:44:01 system: Loaded modules 2 | Sep 30 15:44:01 system: Status {"network": {"eth0": True, "wifi0": False, "wifi1": False, "eth1": True, "eth2": False}, "disk": True, "io": True} 3 | -------------------------------------------------------------------------------- /test/ds/maximum-ctx-input.md: -------------------------------------------------------------------------------- 1 | ```python 2 | >>> x = input("Some large text: ") # byexample: +type 3 | Some typo! text: [foo] 4 | 5 | >>> x 6 | 'foo' 7 | ``` 8 | -------------------------------------------------------------------------------- /test/ds/minimum-ctx-input.md: -------------------------------------------------------------------------------- 1 | ```python 2 | >>> x = input("say: ") # byexample: +type 3 | sa<...>y: [foo] 4 | 5 | >>> x 6 | 'foo' 7 | ``` 8 | -------------------------------------------------------------------------------- /test/ds/mylib1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int mylib1_foo(int i) { 4 | std::cout << "calling my lib1\n"; 5 | return i * 2; 6 | } 7 | -------------------------------------------------------------------------------- /test/ds/mylib1.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB1 2 | #define LIB1 3 | int mylib1_foo(int i); 4 | #endif 5 | 6 | -------------------------------------------------------------------------------- /test/ds/mylib2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int mylib2_foo(int i) { 4 | std::cout << "increased performance with lib2\n"; 5 | return i * 3; 6 | } 7 | -------------------------------------------------------------------------------- /test/ds/mylib2.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB2 2 | #define LIB2 3 | int mylib2_foo(int i); 4 | #endif 5 | -------------------------------------------------------------------------------- /test/ds/mylib3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int mylib3_foo(int i) { 4 | std::cout << "lib3, four times better\n"; 5 | return i * 4; 6 | } 7 | -------------------------------------------------------------------------------- /test/ds/mylib3.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB3 2 | #define LIB3 3 | int mylib3_foo(int i); 4 | #endif 5 | 6 | -------------------------------------------------------------------------------- /test/ds/mylibC.c: -------------------------------------------------------------------------------- 1 | int mylibC_foo(int j) { 2 | // this line is valid in C but not in C++ 3 | // because in C++ 'new' is a reserved word 4 | int new = j + 1; 5 | return new; 6 | } 7 | -------------------------------------------------------------------------------- /test/ds/mylibC.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_C 2 | #define LIB_C 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | int mylibC_foo(int i); 9 | 10 | #ifdef __cplusplus 11 | } 12 | #endif 13 | #endif 14 | 15 | -------------------------------------------------------------------------------- /test/ds/one.md: -------------------------------------------------------------------------------- 1 | ```python 2 | >>> 1 + 2 3 | 3 4 | ``` 5 | -------------------------------------------------------------------------------- /test/ds/options_file: -------------------------------------------------------------------------------- 1 | # Options and their arguments are separated by a = or by a space 2 | -l python 3 | --options=+norm-ws 4 | 5 | # But if the option receives more than one argument, all of them 6 | # must be in its own line 7 | --skip 8 | test/ds/pkg/foo1.py 9 | test/ds/pkg/foo2.py 10 | 11 | # This wouldn't work: 12 | #--skip test/ds/pkg/foo1.py test/ds/pkg/foo2.py 13 | -------------------------------------------------------------------------------- /test/ds/output-bin: -------------------------------------------------------------------------------- 1 | $ cat test/ds/binary-blob 2 | [Bin Start]<...>[Bin End] 3 | -------------------------------------------------------------------------------- /test/ds/param-echo.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main(int argc, char* argv[]) { 3 | for (; argc > 0; --argc) 4 | printf("%s\n", argv[argc-1]); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /test/ds/pkg/bar1.py: -------------------------------------------------------------------------------- 1 | ''' 2 | >>> print("bar1") 3 | bar1 4 | ''' 5 | -------------------------------------------------------------------------------- /test/ds/pkg/bopts: -------------------------------------------------------------------------------- 1 | --skip=test/ds/pkg/foo*.py 2 | -- 3 | test/ds/pkg/*.py 4 | -------------------------------------------------------------------------------- /test/ds/pkg/bopts-brace: -------------------------------------------------------------------------------- 1 | --skip=test/ds/pkg/foo{1..2}.py 2 | -- 3 | test/ds/pkg/*.{py,md} 4 | 5 | -------------------------------------------------------------------------------- /test/ds/pkg/bopts2: -------------------------------------------------------------------------------- 1 | --skip 2 | test/ds/pkg/foo1.py 3 | test/ds/pkg/foo2.py 4 | -- 5 | test/ds/pkg/foo1.py 6 | test/ds/pkg/foo2.py 7 | test/ds/pkg/bar1.py 8 | -------------------------------------------------------------------------------- /test/ds/pkg/bopts3: -------------------------------------------------------------------------------- 1 | --skip=test/ds/pkg/foo1.py test/ds/pkg/foo2.py 2 | -- 3 | test/ds/pkg/*.py 4 | -------------------------------------------------------------------------------- /test/ds/pkg/foo1.py: -------------------------------------------------------------------------------- 1 | ''' 2 | >>> print("foo1") 3 | foo1 4 | ''' 5 | -------------------------------------------------------------------------------- /test/ds/pkg/foo2.py: -------------------------------------------------------------------------------- 1 | ''' 2 | >>> print("foo2") 3 | foo2 4 | ''' 5 | -------------------------------------------------------------------------------- /test/ds/pkg/zaz.md: -------------------------------------------------------------------------------- 1 | ```python 2 | >>> 1 + 2 3 | 3 4 | ``` 5 | -------------------------------------------------------------------------------- /test/ds/python-tutorial.v1.md: -------------------------------------------------------------------------------- 1 | This is a 101 Python tutorial 2 | The following is an example written in Python about arithmetics 3 | 4 | ``` 5 | >>> from __future__ import print_function 6 | >>> 1 + 2 7 | 3 8 | ``` 9 | 10 | The next examples show you about complex numbers in Python 11 | 12 | ``` 13 | >>> 2j * 2 14 | 4 15 | 16 | >>> 2j + 4j 17 | 6 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /test/ds/python-tutorial.v2.md: -------------------------------------------------------------------------------- 1 | This is a 101 Python tutorial 2 | The following is an example written in Python about arithmetics 3 | 4 | ``` 5 | >>> from __future__ import print_function 6 | >>> 1 + 2 7 | 3 8 | ``` 9 | 10 | The next examples show you about complex numbers in Python 11 | 12 | ``` 13 | >>> 2j * 2 14 | 4j 15 | 16 | >>> 2j + 4j 17 | 6j 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /test/ds/shell-example: -------------------------------------------------------------------------------- 1 | Background process 2 | ------------------ 3 | 4 | $ sleep 4 >/dev/null 2>&1 & 5 | <...> 6 | 7 | $ echo "$!" 8 | 9 | 10 | $ jobs -l # byexample: +paste +norm-ws 11 | [] Running<...> 12 | 13 | $ kill %% ; wait # byexample: +timeout=5 +norm-ws +paste 14 | []<...> Terminated<...> 15 | 16 | 17 | Job control and monitoring 18 | -------------------------- 19 | 20 | $ set +m 21 | $ sleep 1 >/dev/null 2>&1 & 22 | <...> 23 | 24 | $ echo "$!" 25 | 26 | 27 | $ jobs -l # byexample: +paste +norm-ws 28 | [] Running<...> 29 | 30 | $ set -m # byexample: +pass -skip 31 | 32 | 33 | Stop on silence 34 | --------------- 35 | 36 | $ echo "some log line" > w/msg.log 37 | $ tail -f w/msg.log # byexample: +stop-on-silence 38 | some log line 39 | 40 | $ echo "another log line" >> w/msg.log 41 | 42 | $ fg # byexample: +stop-on-silence 43 | tail -f w/msg.log<...> 44 | another log line 45 | 46 | 47 | Clean up 48 | -------- 49 | 50 | $ kill -9 $(jobs -p) && wait # byexample: -skip +pass 51 | -------------------------------------------------------------------------------- /test/ds/sleepy/c.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | trap 'echo INT ; sleep 2 ; echo Bye' INT 3 | echo Start 4 | sleep 3 5 | echo BOOM 6 | -------------------------------------------------------------------------------- /test/ds/sleepy/s1.md: -------------------------------------------------------------------------------- 1 | ```python 2 | >>> import time 3 | >>> time.sleep(20) # byexample: +timeout=25 4 | ``` 5 | -------------------------------------------------------------------------------- /test/ds/sleepy/s2.md: -------------------------------------------------------------------------------- 1 | ```python 2 | >>> import time 3 | >>> time.sleep(20) # byexample: +timeout=25 4 | ``` 5 | -------------------------------------------------------------------------------- /test/ds/sleepy/s3.md: -------------------------------------------------------------------------------- 1 | ```python 2 | >>> import time 3 | >>> time.sleep(20) # byexample: +timeout=25 4 | ``` 5 | -------------------------------------------------------------------------------- /test/ds/small-terminal.md: -------------------------------------------------------------------------------- 1 | ``` 2 | $ echo ${LINES}x${COLUMNS} 3 | 24x60 4 | ``` 5 | ``` 6 | >>> ['aaaaa', 'aaaaa', 'aaaaa', 'aaaaa', 'aaaaa', 'aaaaa', 'aaaaa'] 7 | ['aaaaa', 8 | 'aaaaa', 9 | 'aaaaa', 10 | 'aaaaa', 11 | 'aaaaa', 12 | 'aaaaa', 13 | 'aaaaa'] 14 | ``` 15 | 16 | -------------------------------------------------------------------------------- /test/ds/stock.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE stocks 2 | (date text, trans text, symbol text, qty real, price real); 3 | 4 | INSERT INTO stocks VALUES ('2006-01-05','BUY','RHAT',100,35.14); 5 | INSERT INTO stocks VALUES ('2006-03-28', 'BUY', 'IBM', 1000, 45.00); 6 | INSERT INTO stocks VALUES ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00); 7 | INSERT INTO stocks VALUES ('2006-04-06', 'SELL', 'IBM', 500, 53.00); 8 | -------------------------------------------------------------------------------- /test/ds/submod/submod.py: -------------------------------------------------------------------------------- 1 | from byexample.concern import Concern 2 | import multiprocessing 3 | 4 | class SubMod(Concern): 5 | target = 'submod' 6 | 7 | def __init__(self, prepare_subprocess_call, **kargs): 8 | Concern.__init__(self, prepare_subprocess_call=prepare_subprocess_call, **kargs) 9 | # keep a reference to this function helper 10 | self.prepare_subprocess_call = prepare_subprocess_call 11 | 12 | @classmethod 13 | def watch_in_bg(cls, foo, bar): 14 | print("--->", foo, bar) 15 | 16 | def start_example(self, *args): 17 | # prepare_subprocess_call takes the 'target' function 18 | # and an optional 'args' and 'kwargs' arguments 19 | # like multiprocessing.Process does. 20 | # 21 | # it will return a dictionary that be unpacked 22 | # with the double '**' directly into multiprocessing.Process 23 | # call 24 | ctx = multiprocessing.get_context('spawn') 25 | self.child = ctx.Process( 26 | **self.prepare_subprocess_call( 27 | target=SubMod.watch_in_bg, 28 | args=(42,), 29 | kwargs={'bar': 'bg'} 30 | ) 31 | ) 32 | self.child.start() # Start the child process as usual 33 | 34 | def end_example(self, *args): 35 | self.child.join() 36 | 37 | -------------------------------------------------------------------------------- /test/ds/template.args: -------------------------------------------------------------------------------- 1 | -l=python,shell 2 | {% if osname == "Darwin" %} 3 | --skip 4 | byexample/prof.py 5 | docs/overview/good-practices.md 6 | {% endif %} 7 | -- 8 | byexample/*.py 9 | docs/overview/*.md 10 | -------------------------------------------------------------------------------- /test/ds/too-slow.md: -------------------------------------------------------------------------------- 1 | ```python 2 | >>> from time import sleep # byexample: +timeout=4 3 | 4 | >>> print("Initializing (it can take some time...):") 5 | Initializing (it can take some time...): 6 | 7 | >>> sleep(1.1) 8 | 9 | >>> print("Initialization done") 10 | Initialization done 11 | 12 | >>> print("Ready for action") 13 | Ready for action 14 | ``` 15 | -------------------------------------------------------------------------------- /test/filter-echo-tagging.env: -------------------------------------------------------------------------------- 1 | --jobs=2 2 | --pretty=all 3 | --languages=python,shell 4 | --ff 5 | --timeout=4 6 | # disable the default "turn echo off" 7 | # so we have an interpreter echoing the examples 8 | # that we sent to them 9 | -x-turn-echo-off-on-spawn=False 10 | -x-turn-echo-off=False 11 | # force the filtering of the echo 12 | -o=+force-echo-filtering 13 | -------------------------------------------------------------------------------- /test/github-pages.sh: -------------------------------------------------------------------------------- 1 | echo -e 'source "https://rubygems.org"\ngem "github-pages"' > Gemfile 2 | bundle install && cd docs && bundle exec jekyll build 3 | 4 | -------------------------------------------------------------------------------- /test/idx.sh: -------------------------------------------------------------------------------- 1 | # file names from the docs folder (only markdown files, exclude examples folder) 2 | ls -1 docs/*/*.md | sed 's/\.md$//' | sed 's/^docs//g' | grep -v '^/examples/' | sort > .fnames.tmp 3 | 4 | # links to the docs files 5 | grep "site.uprefix" docs/_includes/idx.html | sed 's/^.*site.uprefix }}//g' | sed 's/\([^"]*\)".*$/\1/g' | sort > .flinks.tmp 6 | 7 | # print any difference: missing links to existent files/dangling links pointing to nowhere 8 | diff .fnames.tmp .flinks.tmp | grep '^[<>]' | sed 's/^/Dangling link: /g' | sort 9 | 10 | # return the same exit code than: 11 | diff .fnames.tmp .flinks.tmp > /dev/null 12 | -------------------------------------------------------------------------------- /test/lang-cpp-docker.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=8 5 | --language=cpp,shell,python 6 | -x-shebang=cpp:sudo %w/test/docker-cling.sh %a 7 | -x-dfl-timeout=30 8 | docs/languages/cpp.md 9 | docs/examples/cpp.cpp 10 | byexample/modules/cpp.py 11 | docs/advanced/geometry.md 12 | docs/advanced/terminal-emulation.md 13 | -------------------------------------------------------------------------------- /test/lang-cpp.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=8 5 | --language=cpp,shell,python 6 | docs/languages/cpp.md 7 | docs/examples/cpp.cpp 8 | byexample/modules/cpp.py 9 | docs/advanced/geometry.md 10 | docs/advanced/terminal-emulation.md 11 | -------------------------------------------------------------------------------- /test/lang-elixir.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=8 5 | --language=elixir,shell,python 6 | docs/languages/elixir.md 7 | byexample/modules/elixir.py 8 | docs/advanced/geometry.md 9 | docs/advanced/terminal-emulation.md 10 | -------------------------------------------------------------------------------- /test/lang-gdb.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=8 5 | --language=gdb,shell,python 6 | docs/languages/gdb.md 7 | byexample/modules/gdb.py 8 | -------------------------------------------------------------------------------- /test/lang-go.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=4 5 | --language=go,shell,python 6 | docs/languages/go.md 7 | docs/examples/go.go 8 | byexample/modules/go.py 9 | -------------------------------------------------------------------------------- /test/lang-iasm.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=8 5 | --language=iasm,shell,python 6 | docs/languages/iasm.md 7 | byexample/modules/iasm.py 8 | docs/advanced/geometry.md 9 | -------------------------------------------------------------------------------- /test/lang-java.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=4 5 | --language=java,shell,python 6 | docs/languages/java.md 7 | docs/examples/java.java 8 | byexample/modules/java.py 9 | -------------------------------------------------------------------------------- /test/lang-javascript.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=8 5 | --language=javascript,shell,python 6 | -x-shebang=javascript:%e node %a 7 | docs/languages/javascript.md 8 | docs/examples/javascript.js 9 | byexample/modules/javascript.py 10 | -------------------------------------------------------------------------------- /test/lang-php.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=8 5 | --language=php,shell,python 6 | docs/languages/php.md 7 | docs/examples/php.php 8 | byexample/modules/php.py 9 | docs/advanced/geometry.md 10 | docs/advanced/terminal-emulation.md 11 | -------------------------------------------------------------------------------- /test/lang-powershell.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=8 5 | --language=pwsh,shell,python 6 | docs/languages/powershell.md 7 | docs/examples/powershell.ps1 8 | byexample/modules/powershell.py 9 | -------------------------------------------------------------------------------- /test/lang-python.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=4 5 | --language=python,shell 6 | docs/languages/python.md 7 | docs/examples/python.py 8 | byexample/modules/python.py 9 | -------------------------------------------------------------------------------- /test/lang-ruby.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=4 5 | --language=ruby,shell,python 6 | docs/languages/ruby.md 7 | docs/examples/ruby.rb 8 | byexample/modules/ruby.py 9 | -------------------------------------------------------------------------------- /test/lang-rust.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=8 5 | --language=rust,shell,python 6 | docs/languages/rust.md 7 | byexample/modules/rust.py 8 | -------------------------------------------------------------------------------- /test/lang-shell.env: -------------------------------------------------------------------------------- 1 | -v 2 | --pretty=all 3 | --ff 4 | --timeout=4 5 | --language=python,shell 6 | docs/languages/shell.md 7 | byexample/modules/shell.py 8 | -------------------------------------------------------------------------------- /test/minimum-term-ansi.env: -------------------------------------------------------------------------------- 1 | --jobs=2 2 | --pretty=all 3 | --languages=python,shell 4 | --ff 5 | --timeout=20 6 | -o 7 | +term=ansi +geometry=800x127 8 | -------------------------------------------------------------------------------- /test/minimum.env: -------------------------------------------------------------------------------- 1 | --jobs=2 2 | --pretty=all 3 | --languages=python,shell 4 | --ff 5 | --timeout=4 6 | -------------------------------------------------------------------------------- /test/profiler.env: -------------------------------------------------------------------------------- 1 | -q 2 | --languages=python,shell 3 | --ff 4 | --timeout=8 5 | -x-shebang=python:%e -u BYEXAMPLE_PROFILE %p %a 6 | -------------------------------------------------------------------------------- /test/r.py: -------------------------------------------------------------------------------- 1 | import sys, os, contextlib 2 | try: 3 | import coverage 4 | 5 | @contextlib.contextmanager 6 | def coverage_measure(): 7 | cov = coverage.Coverage(source=['byexample'], auto_data=True, concurrency='thread') 8 | cov.start() 9 | try: 10 | yield 11 | finally: 12 | cov.stop() 13 | 14 | except ImportError: 15 | @contextlib.contextmanager 16 | def coverage_measure(): 17 | print("Warning: coverage is not installed") 18 | yield 19 | 20 | if __name__ == '__main__': 21 | if os.environ.get('BYEXAMPLE_COVERAGE_TEST'): 22 | with coverage_measure(): 23 | from byexample.byexample import main 24 | sys.exit(main()) 25 | 26 | else: 27 | from byexample.byexample import main 28 | sys.exit(main()) 29 | 30 | -------------------------------------------------------------------------------- /test/runner_version_matrix.py: -------------------------------------------------------------------------------- 1 | import sys, re, collections, pprint 2 | from tabulate import tabulate 3 | 4 | lang_re = re.compile( 5 | r'''^Lang[ ] 6 | (?P \w+)[ ] 7 | test[ ]? 8 | (?: 9 | \( (?P [^)]+) \) | [^(] 10 | ) 11 | ''', re.VERBOSE) 12 | 13 | runner_re = re.compile( 14 | r''' (?P \w+)[ ] 15 | Runner's[ ]version:[ ] 16 | \( (?P [^)]+) \)$ 17 | ''', re.VERBOSE) 18 | 19 | failed_re = re.compile( 20 | r''' Failed[ ]to[ ]obtain[ ](?P \w+)[ ] 21 | Runner's[ ]version.*$ 22 | ''', re.VERBOSE) 23 | 24 | def parse_line(line): 25 | ''' 26 | >>> parse_line(r"Lang Ruby test (3.1) ... Ruby Runner's version: (1.4.1)") 27 | ('Ruby', '3.1', '1.4.1') 28 | 29 | >>> parse_line(r"Lang Ruby test ... Ruby Runner's version: (1.4.1)") 30 | ('Ruby', 'latest', '1.4.1') 31 | 32 | >>> parse_line(r"Lang Ruby test ... Failed to obtain Ruby Runner's version ...") 33 | ('Ruby', 'latest', 'unknown (to review)') 34 | 35 | >>> parse_line(r"Lang Ruby test ... Failed to obtain Python Runner's version ...") 36 | None 37 | ''' 38 | 39 | # "Lang Ruby test (3.1) ... Ruby Runner's version: (1.4.1)" 40 | # |--------------------| 41 | m = lang_re.match(line) 42 | assert m 43 | 44 | language, lang_version = m.group('language', 'lang_version') 45 | if not lang_version: 46 | lang_version = 'latest' 47 | 48 | # "Lang Ruby test (3.1, v123) ... Ruby Runner's version: (1.4.1)" 49 | runner_version = None 50 | if ',' in lang_version: 51 | lang_version, runner_version = [s.strip() for s in lang_version.split(',')] 52 | 53 | # "Lang Ruby test (3.1) ... Failed to obtain Ruby Runner's version ..." 54 | # ^^^^ |-----------------^^^^-----------------| 55 | m = failed_re.search(line) 56 | if m: 57 | language2 = m.group('language') 58 | if language == language2: 59 | # this case means that something went wrong when byexample 60 | # tried to retrieve the version of the runner 61 | if runner_version is None: 62 | runner_version = '-' 63 | return language, lang_version, runner_version 64 | 65 | # "Lang Ruby test (3.1) ... Failed to obtain Python Runner's version ..." 66 | # ^^^^ |-----------------^^^^^^-----------------| 67 | # In this case the failure is not a failure for our language of 68 | # interest so we skip the line 69 | return language, lang_version, None 70 | 71 | # "Lang Ruby test (3.1) ... Ruby Runner's version: (0.9.6)" 72 | # ^^^^ |^^^^--------------------------| 73 | m = runner_re.search(line) 74 | if not m: 75 | return language, lang_version, None 76 | 77 | language2, runner_version = m.group('language', 'runner_version') 78 | if language != language2: 79 | # Not our language of interest, skip the line 80 | return language, lang_version, None 81 | 82 | return language, lang_version, runner_version 83 | 84 | matrix = collections.defaultdict(dict) 85 | with open(sys.argv[1], 'rt') as f: 86 | for line in f: 87 | 88 | # If it is not a specific job for testing a language, skip it 89 | if not line.startswith('Lang '): 90 | continue 91 | 92 | # If it is not the specific step that run the tests, skip it 93 | if 'Run ' not in line or 'make lang-' not in line: 94 | continue 95 | 96 | language, lang_version, runner_version = parse_line(line) 97 | if runner_version: 98 | matrix[language][lang_version] = runner_version 99 | else: 100 | if lang_version not in matrix[language]: 101 | matrix[language][lang_version] = 'unknown' 102 | 103 | begin_marker = '' 104 | end_marker = '' 105 | 106 | fname_template = 'docs/languages/{}.md' 107 | 108 | for language, data in matrix.items(): 109 | data = [[lang_ver, runner_ver] for lang_ver, runner_ver in data.items()] 110 | 111 | doc_file = fname_template.format(language.lower()) 112 | with open(doc_file, 'rt') as f: 113 | doc = f.read() 114 | 115 | if begin_marker not in doc or end_marker not in doc: 116 | raise Exception(f"Markers are missing in {doc_file}") 117 | 118 | head, remain = doc.split(begin_marker, 1) 119 | _, tail = remain.split(end_marker, 1) 120 | del remain 121 | 122 | table = tabulate( 123 | data, 124 | ["Language", "Runner/Interpreter"], 125 | tablefmt='github', 126 | disable_numparse=True 127 | ) 128 | 129 | doc = head + begin_marker + '\n\n' + table + '\n\n' + end_marker + tail 130 | with open(doc_file, 'wt') as f: 131 | f.write(doc) 132 | -------------------------------------------------------------------------------- /test/timming_corner_cases.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | ## Shutdown 11 | 12 | Ensure that if the program is slow and takes some reasonable time to 13 | shutdown, don't raise a Timeout after stopping it 14 | 15 | ```shell 16 | $ test/ds/sleepy/c.sh # byexample: +timeout=10 +stop-on-silence +stop-signal=interrupt 17 | Start 18 | ``` 19 | 20 | Run three very slow tests and send `byexample` to the background 21 | as soon as possible. 22 | 23 | ```shell 24 | $ byexample -l python test/ds/sleepy/{s1,s2,s3}.md # byexample: +stop-on-silence 7 25 | ``` 26 | 27 | Now send a Ctrl-C (SIGINT) to abort the execution. It is expected that 28 | the shutdown will take time to finish the current jobs (20 secs) but it 29 | will not take the full time to run (60 secs) because it was aborted. 30 | 31 | ```shell 32 | $ sleep 1 ; kill -2 %% # byexample: +pass +timeout=3 33 | $ fg # byexample: +timeout 25 +rm=~ 34 | byexample --pretty none -l python test/ds/sleepy/{s1,s2,s3}.md 35 | [i] User aborted. Waiting to finish the current active executions... 36 | ~ 37 | File test/ds/sleepy/s1.md, 2/2 test ran in <...> seconds 38 | [PASS] Pass: 2 Fail: 0 Skip: 0 39 | ``` 40 | 41 | Forcing a shutdown doing a Ctrl-C (SIGINT) twice and more will 42 | start breaking all the code but it will abort the execution faster. 43 | 44 | Note how the timeouts sum up less than 20 secs proving that `byexample` 45 | didn't complete any execution (it was aborted by hard) and finished. 46 | 47 | ```shell 48 | $ byexample -l python test/ds/sleepy/{s1,s2,s3}.md # byexample: +stop-on-silence 7 49 | 50 | $ sleep 1 ; kill -2 %% # byexample: +pass +timeout=3 51 | $ fg # byexample: +rm=~ +stop-on-silence 3 52 | byexample --pretty none -l python test/ds/sleepy/{s1,s2,s3}.md 53 | [i] User aborted. Waiting to finish the current active executions... 54 | 55 | $ sleep 1 ; kill -2 %% # byexample: +pass +timeout=3 56 | $ fg # byexample: +rm=~ +stop-on-silence 3 57 | byexample --pretty none -l python test/ds/sleepy/{s1,s2,s3}.md 58 | [w] Not waiting for the current active executions to finish... 59 | Pressing more times Ctrl-C will force an immediate shutdown 60 | but it will leave resources uncleaned (dangerous/unsafe). 61 | 62 | $ sleep 1 ; kill -2 %% # byexample: +pass +timeout=3 63 | $ fg # byexample: +timeout 3 +rm=~ 64 | <...> 65 | KeyboardInterrupt<...> 66 | ``` 67 | -------------------------------------------------------------------------------- /typosquashing/Makefile: -------------------------------------------------------------------------------- 1 | python_bin ?= python 2 | 3 | all: 4 | @exit 1 5 | 6 | dist: 7 | rm -Rf dist/ build/ *.egg-info 8 | $(python_bin) setup.py sdist bdist_wheel 9 | rm -Rf build/ *.egg-info 10 | 11 | upload: dist 12 | twine upload dist/*.tar.gz dist/*.whl 13 | -------------------------------------------------------------------------------- /typosquashing/setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if not 'sdist' in sys.argv: 4 | sys.exit(''' 5 | *** Please install the `byexample` package instead of `byexamples` (notice the `s` at the end) *** 6 | This is just a dummy package to prevent typo-squashing. 7 | 8 | Install `byexample` from PyPI with 9 | 10 | pip install byexample 11 | 12 | You can visit the home page and download it manually from 13 | 14 | https://byexamples.github.io/ 15 | https://pypi.org/project/byexample/ 16 | ''') 17 | 18 | from setuptools import setup, find_packages 19 | from codecs import open 20 | from os import path, system 21 | 22 | import sys, re 23 | 24 | here = path.abspath(path.dirname(__file__)) 25 | 26 | # load __version__, __doc__, _author, _license and _url 27 | exec(open(path.join(here, '..', 'byexample', '__init__.py')).read()) 28 | long_description = __doc__ 29 | 30 | setup( 31 | name='byexamples', 32 | version='0.0', 33 | description='Dummy package that points to byexample (without s at the end)', 34 | url=_url, 35 | author=_author, 36 | author_email='use-github-issues@example.com', 37 | 38 | license=_license 39 | ) 40 | --------------------------------------------------------------------------------