├── .github └── workflows │ ├── doc-deploy.yml │ └── lint_and_test.yml ├── .gitignore ├── Makefile ├── README.md ├── docs ├── .gitmodules ├── .hugo_build.lock └── src │ ├── .hugo_build.lock │ ├── archetypes │ └── default.md │ ├── config.yaml │ ├── content │ └── posts │ │ └── get-started.md │ ├── resources │ └── _gen │ │ └── assets │ │ └── sass │ │ ├── style.sass_5ad6f408b0e3e473c748aac88af0ea18.content │ │ └── style.sass_5ad6f408b0e3e473c748aac88af0ea18.json │ └── static │ └── images │ └── logos │ ├── logo.png │ ├── logo.svg │ └── logo_snake.svg ├── logo └── logo.png ├── poetry.lock ├── pyproject.toml ├── pythonlings ├── __init__.py ├── __main__.py ├── adapters │ └── cmd.py ├── domain │ └── exercises.py ├── exercises │ ├── 1_variables │ │ ├── variables1.py │ │ ├── variables2.py │ │ ├── variables3.py │ │ ├── variables4.py │ │ └── variables5.py │ └── 2_data_structures │ │ └── data_structures1.py ├── i18n │ ├── data_structures.en.yml │ ├── data_structures.pt.yml │ ├── p.en.yml │ ├── p.pt.yml │ ├── variables.en.yml │ └── variables.pt.yml └── services │ ├── exercises.py │ └── logging.py └── tests ├── __init__.py ├── conftest.py ├── fixtures └── 1_examples │ ├── exercise_sample_fail.py │ ├── exercise_sample_success.py │ └── exercise_sample_to_do.py └── test_pythonlings.py /.github/workflows/doc-deploy.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Set a branch to deploy 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | submodules: false # Fetch Hugo themes (true OR recursive) 15 | fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod 16 | 17 | - name: Setup Hugo 18 | uses: peaceiris/actions-hugo@v2 19 | with: 20 | hugo-version: 'latest' 21 | extended: true 22 | 23 | - name: Build 24 | run: | 25 | make doc-get-theme 26 | make doc-build 27 | - name: Deploy 28 | uses: peaceiris/actions-gh-pages@v3 29 | if: github.ref == 'refs/heads/master' 30 | with: 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | publish_dir: ./docs 33 | -------------------------------------------------------------------------------- /.github/workflows/lint_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Lint and test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | python-version: ["3.7", "3.8", "3.9"] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install poetry 23 | poetry install 24 | - name: Test with Pytest trough Poetry 25 | run: | 26 | # stop the build if there are Python syntax errors or undefined names 27 | poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 28 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 29 | poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=pythonlings/exercises,tests/fixtures 30 | - name: Test with pytest 31 | run: | 32 | poetry run pytest 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | # Distribution / packaging 9 | .Python 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | downloads/ 14 | eggs/ 15 | .eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | share/python-wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | cover/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/src/_build/ 72 | docs/src/resources/_gen/ 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | # For a library or package, you might want to ignore these files since the code is 86 | # intended to run in multiple environments; otherwise, check them in: 87 | # .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # poetry 97 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 98 | # This is especially recommended for binary packages to ensure reproducibility, and is more 99 | # commonly ignored for libraries. 100 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 101 | #poetry.lock 102 | 103 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 104 | __pypackages__/ 105 | 106 | # Celery stuff 107 | celerybeat-schedule 108 | celerybeat.pid 109 | 110 | # SageMath parsed files 111 | *.sage.py 112 | 113 | # Environments 114 | .env 115 | .venv 116 | env/ 117 | venv/ 118 | ENV/ 119 | env.bak/ 120 | venv.bak/ 121 | 122 | # Spyder project settings 123 | .spyderproject 124 | .spyproject 125 | 126 | # Rope project settings 127 | .ropeproject 128 | 129 | # mkdocs documentation 130 | /site 131 | 132 | # mypy 133 | .mypy_cache/ 134 | .dmypy.json 135 | dmypy.json 136 | 137 | # Pyre type checker 138 | .pyre/ 139 | 140 | # pytype static type analyzer 141 | .pytype/ 142 | 143 | # Cython debug symbols 144 | cython_debug/ 145 | 146 | # PyCharm 147 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 148 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 149 | # and can be added to the global gitignore or merged into this file. For a more nuclear 150 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 151 | #.idea/ 152 | .DS_Store -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | doc-build: 2 | cd docs/src && hugo --destination ../ && cd .. 3 | 4 | doc-get-theme: 5 | # Clone the Fresh theme 6 | git clone https://github.com/StefMa/hugo-fresh docs/src/themes/hugo-fresh 7 | 8 | doc-clean: 9 | cd docs && ls -1 | grep -v "src" | xargs rm -r && cd .. 10 | 11 | lint: 12 | poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=pythonlings/exercises,tests/fixtures 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pythonlings is a multilingual utility to help newcomers learn Python through exercises, with a variety of themes! 5 | 6 | ## Setup your language 7 | 8 | Actually, the following languages are supported: 9 | 10 | - en (default) 11 | - pt 12 | 13 | To choose your language, just export `PYTHONLINGS_LANGUAGE` with the desired language. 14 | Example: 15 | 16 | 17 | export PYTHONLINGS_LANGUAGE=pt 18 | 19 | ## Install dependencies 20 | 21 | poetry install 22 | 23 | 24 | ## Usage 25 | 26 | poetry shell 27 | python -m pythonlings start 28 | 29 | 30 | ## Tests 31 | 32 | poetry run pytest 33 | -------------------------------------------------------------------------------- /docs/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/themes/hugo-fresh"] 2 | path = src/themes/hugo-fresh 3 | url = https://github.com/StefMa/hugo-fresh 4 | -------------------------------------------------------------------------------- /docs/.hugo_build.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigmages/pythonlings/08f7ce1a36a01890d037e2b16808492791ef0f6e/docs/.hugo_build.lock -------------------------------------------------------------------------------- /docs/src/.hugo_build.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigmages/pythonlings/08f7ce1a36a01890d037e2b16808492791ef0f6e/docs/src/.hugo_build.lock -------------------------------------------------------------------------------- /docs/src/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/src/config.yaml: -------------------------------------------------------------------------------- 1 | baseURL: https://sigmages.github.io/pythonlings/ 2 | languageCode: en-us 3 | title: Hugo Fresh Theme 4 | theme: hugo-fresh 5 | googleAnalytics: # Put in your tracking code without quotes like this: UA-XXXXXX... 6 | # Disables warnings 7 | disableKinds: 8 | - taxonomy 9 | - taxonomyTerm 10 | markup: 11 | goldmark: 12 | renderer: 13 | unsafe: true # Allows you to write raw html in your md files 14 | 15 | params: 16 | # Open graph allows easy social sharing. If you don't want it you can set it to false or just delete the variable 17 | openGraph: true 18 | # Used as meta data; describe your site to make Google Bots happy 19 | description: 20 | navbarlogo: 21 | # Logo (from static/images/logos/___) 22 | image: logos/logo_snake.svg 23 | link: /pythonlings 24 | font: 25 | name: "Open Sans" 26 | sizes: [400,600] 27 | hero: 28 | # Main hero title 29 | title: Aprenda. Aprimore! 30 | # Hero subtitle (optional) 31 | subtitle: Com Pythonlings você aprende através de desafios que progredirão de dificuldade, lhe ensinando algo novo! 32 | # Button text 33 | buttontext: Get started 34 | # Where the main hero button links to 35 | buttonlink: "/pythonlings/posts/get-started" 36 | # Hero image (from static/images/___) 37 | # image: illustrations/worker.svg 38 | image: logos/logo.svg 39 | # Footer logos (from static/images/logos/clients/___.svg) 40 | # clientlogos: 41 | # - systek 42 | # - tribe 43 | # - kromo 44 | # - infinite 45 | # - gutwork 46 | # Customizable navbar. For a dropdown, add a "sublinks" list. 47 | navbar: 48 | - title: About 49 | url: /about 50 | - title: Get started 51 | url: /pythonlings/posts/get-started 52 | button: true 53 | # section1: 54 | # title: Great power comes 55 | # subtitle: with great responsibility 56 | # tiles: 57 | # - title: App builder 58 | # icon: mouse-globe 59 | # text: This is some explanatory text that is on two rows 60 | # url: / 61 | # buttonText: Free trial 62 | # - title: Cloud integration 63 | # icon: laptop-cloud 64 | # text: This is some explanatory text that is on two rows 65 | # url: / 66 | # buttonText: Get started 67 | # - title: Add-ons & plugins 68 | # icon: plug-cloud 69 | # text: This is some explanatory text that is on two rows 70 | # url: / 71 | # buttonText: Get started 72 | # section2: 73 | # title: You're here because you want the best 74 | # subtitle: And we know it 75 | # features: 76 | # - title: Powerful and unified interface 77 | # text: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornare magna eros, eu pellentesque tortor vestibulum ut. Maecenas non massa sem. Etiam finibus odio quis feugiat facilisis. 78 | # # Icon (from /images/illustrations/icons/___.svg) 79 | # icon: laptop-globe 80 | # - title: Cross-device synchronisation 81 | # text: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornare magna eros, eu pellentesque tortor vestibulum ut. Maecenas non massa sem. Etiam finibus odio quis feugiat facilisis. 82 | # icon: doc-sync 83 | # - title: Nomad system 84 | # text: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornare magna eros, eu pellentesque tortor vestibulum ut. Maecenas non massa sem. Etiam finibus odio quis feugiat facilisis. 85 | # icon: mobile-feed 86 | # section3: 87 | # title: One platform 88 | # subtitle: To rule them all 89 | # image: illustrations/mockups/app-mockup.png 90 | # buttonText: Get started 91 | # buttonLink: "#" 92 | # section4: 93 | # title: Opiniões 94 | # subtitle: Veja a opinião de alguns usuários que aprenderam algo novo com Pythonlings! 95 | # clients: 96 | # - name: Albert Einstein 97 | # quote: No início eu não confiei muito, mas os exercícios são muito bons! 98 | # job: Físico 99 | # img: 2 100 | # - name: Nikola Tesla 101 | # quote: Até um exotérico como eu consegui aprender alguma coisa, recomendo! 102 | # job: Físico 103 | # img: 3 104 | # section5: true 105 | footer: 106 | # Logo (from /images/logos/___) 107 | logo: logo_snake.svg 108 | # Social Media Title 109 | socialmediatitle: Nos encontre em 110 | # Social media links (GitHub, Twitter, etc.). All are optional. 111 | socialmedia: 112 | - link: https://github.com/sigmages/pythonlings 113 | # Icons are from Font Awesome 114 | icon: github 115 | # - link: https://dribbble.com/# 116 | # icon: dribbble 117 | # - link: https://facebook.com/# 118 | # icon: facebook 119 | # - link: https://twitter.com/lucperkins 120 | # icon: twitter 121 | # - link: https://bitbucket.org/# 122 | # icon: bitbucket 123 | bulmalogo: true 124 | quicklinks: 125 | column1: 126 | title: "Docs" 127 | links: 128 | - text: Get started 129 | link: / 130 | - text: User guides 131 | link: / 132 | - text: Admin guide 133 | link: / 134 | - text: Developers 135 | link: / 136 | column2: 137 | title: "Blog" 138 | links: 139 | - text: Latest news 140 | link: /blog/first 141 | - text: Tech articles 142 | link: /blog/second 143 | -------------------------------------------------------------------------------- /docs/src/content/posts/get-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Get Started 3 | sidebar: true 4 | sidebarlogo: logo_snake 5 | --- 6 | 7 | 8 | Pythonlings é um utilitário para ajudar iniciantes a aprender Python através de exercícios, em uma variedade de temas! 9 | 10 | ## Escolha sua linguagem 11 | 12 | Atualmente, as seguintes linguagens são suportadas: 13 | 14 | - en (default) 15 | - pt 16 | 17 | Para escolher sua linguagem, apenas exporte `PYTHONLINGS_LANGUAGE` com a sigla da palavra desejada: 18 | 19 | Exemplo: 20 | 21 | 22 | export PYTHONLINGS_LANGUAGE=pt 23 | 24 | 25 | ## Uso 26 | 27 | Inicie o ambiente com poetry: 28 | 29 | poetry install 30 | poetry shell 31 | 32 | Após a instalação das dependências e as pre-configurações padrões serem aplicadas, você poderá executar a ferramenta: 33 | 34 | python -m pythonlings start 35 | 36 | Com isso, os exemplos começarão a ser executados em ordem tópico a tópico, os arquivos de exemplos estarão contidos dentro de `pythonlings/exercises`, você deverá fazer com que os arquivos sejam executados **sem problemas** corrigindo-os para que passem nos testes, remova a linha de comentário `# I AM NOT DONE` do exercício atual para que o Pythonlings o execute. 37 | 38 | ### Comandos 39 | 40 | ``` 41 | usage: __main__.py [-h] {start,exec} ... 42 | 43 | Pythonlings é um utilitário para ajudar iniciantes a aprender Python através de exercícios, em uma variedade de temas! 44 | 45 | positional arguments: 46 | {start,exec} 47 | start Inicia o Pythonlings! Executando e assistindo os arquivos de exercício em ordem. 48 | exec Executa apenas um exercicio, você apenas deve prover o caminho relativo ou absoluto do arquivo do exercicio. 49 | 50 | optional arguments: 51 | -h, --help show this help message and exit 52 | ``` 53 | 54 | ## Desenvolvimento 55 | 56 | Para os desenvolvedores, inicialmente execute `poetry install` para ter um ambiente local isolado. 57 | 58 | ## Tests 59 | 60 | poetry run pytest 61 | 62 | -------------------------------------------------------------------------------- /docs/src/resources/_gen/assets/sass/style.sass_5ad6f408b0e3e473c748aac88af0ea18.json: -------------------------------------------------------------------------------- 1 | {"Target":"css/style.css","MediaType":"text/css","Data":{}} -------------------------------------------------------------------------------- /docs/src/static/images/logos/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigmages/pythonlings/08f7ce1a36a01890d037e2b16808492791ef0f6e/docs/src/static/images/logos/logo.png -------------------------------------------------------------------------------- /docs/src/static/images/logos/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/src/static/images/logos/logo_snake.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigmages/pythonlings/08f7ce1a36a01890d037e2b16808492791ef0f6e/logo/logo.png -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "atomicwrites" 3 | version = "1.4.0" 4 | description = "Atomic file writes." 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 8 | 9 | [[package]] 10 | name = "attrs" 11 | version = "21.4.0" 12 | description = "Classes Without Boilerplate" 13 | category = "dev" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 16 | 17 | [package.extras] 18 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] 19 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 20 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] 21 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] 22 | 23 | [[package]] 24 | name = "black" 25 | version = "22.3.0" 26 | description = "The uncompromising code formatter." 27 | category = "main" 28 | optional = false 29 | python-versions = ">=3.6.2" 30 | 31 | [package.dependencies] 32 | click = ">=8.0.0" 33 | mypy-extensions = ">=0.4.3" 34 | pathspec = ">=0.9.0" 35 | platformdirs = ">=2" 36 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 37 | typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} 38 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 39 | 40 | [package.extras] 41 | colorama = ["colorama (>=0.4.3)"] 42 | d = ["aiohttp (>=3.7.4)"] 43 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 44 | uvloop = ["uvloop (>=0.15.2)"] 45 | 46 | [[package]] 47 | name = "click" 48 | version = "8.1.2" 49 | description = "Composable command line interface toolkit" 50 | category = "main" 51 | optional = false 52 | python-versions = ">=3.7" 53 | 54 | [package.dependencies] 55 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 56 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 57 | 58 | [[package]] 59 | name = "colorama" 60 | version = "0.4.4" 61 | description = "Cross-platform colored terminal text." 62 | category = "main" 63 | optional = false 64 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 65 | 66 | [[package]] 67 | name = "flake8" 68 | version = "4.0.1" 69 | description = "the modular source code checker: pep8 pyflakes and co" 70 | category = "main" 71 | optional = false 72 | python-versions = ">=3.6" 73 | 74 | [package.dependencies] 75 | importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} 76 | mccabe = ">=0.6.0,<0.7.0" 77 | pycodestyle = ">=2.8.0,<2.9.0" 78 | pyflakes = ">=2.4.0,<2.5.0" 79 | 80 | [[package]] 81 | name = "importlib-metadata" 82 | version = "4.0.0" 83 | description = "Read metadata from Python packages" 84 | category = "main" 85 | optional = false 86 | python-versions = ">=3.6" 87 | 88 | [package.dependencies] 89 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 90 | zipp = ">=0.5" 91 | 92 | [package.extras] 93 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 94 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 95 | 96 | [[package]] 97 | name = "mccabe" 98 | version = "0.6.1" 99 | description = "McCabe checker, plugin for flake8" 100 | category = "main" 101 | optional = false 102 | python-versions = "*" 103 | 104 | [[package]] 105 | name = "more-itertools" 106 | version = "8.12.0" 107 | description = "More routines for operating on iterables, beyond itertools" 108 | category = "dev" 109 | optional = false 110 | python-versions = ">=3.5" 111 | 112 | [[package]] 113 | name = "mypy-extensions" 114 | version = "0.4.3" 115 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 116 | category = "main" 117 | optional = false 118 | python-versions = "*" 119 | 120 | [[package]] 121 | name = "packaging" 122 | version = "21.3" 123 | description = "Core utilities for Python packages" 124 | category = "dev" 125 | optional = false 126 | python-versions = ">=3.6" 127 | 128 | [package.dependencies] 129 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 130 | 131 | [[package]] 132 | name = "pathspec" 133 | version = "0.9.0" 134 | description = "Utility library for gitignore style pattern matching of file paths." 135 | category = "main" 136 | optional = false 137 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 138 | 139 | [[package]] 140 | name = "platformdirs" 141 | version = "2.5.1" 142 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 143 | category = "main" 144 | optional = false 145 | python-versions = ">=3.7" 146 | 147 | [package.extras] 148 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] 149 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] 150 | 151 | [[package]] 152 | name = "pluggy" 153 | version = "0.13.1" 154 | description = "plugin and hook calling mechanisms for python" 155 | category = "dev" 156 | optional = false 157 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 158 | 159 | [package.dependencies] 160 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 161 | 162 | [package.extras] 163 | dev = ["pre-commit", "tox"] 164 | 165 | [[package]] 166 | name = "py" 167 | version = "1.11.0" 168 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 169 | category = "dev" 170 | optional = false 171 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 172 | 173 | [[package]] 174 | name = "pycodestyle" 175 | version = "2.8.0" 176 | description = "Python style guide checker" 177 | category = "main" 178 | optional = false 179 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 180 | 181 | [[package]] 182 | name = "pyflakes" 183 | version = "2.4.0" 184 | description = "passive checker of Python programs" 185 | category = "main" 186 | optional = false 187 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 188 | 189 | [[package]] 190 | name = "pyparsing" 191 | version = "3.0.8" 192 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 193 | category = "dev" 194 | optional = false 195 | python-versions = ">=3.6.8" 196 | 197 | [package.extras] 198 | diagrams = ["railroad-diagrams", "jinja2"] 199 | 200 | [[package]] 201 | name = "pytest" 202 | version = "5.4.3" 203 | description = "pytest: simple powerful testing with Python" 204 | category = "dev" 205 | optional = false 206 | python-versions = ">=3.5" 207 | 208 | [package.dependencies] 209 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 210 | attrs = ">=17.4.0" 211 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 212 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 213 | more-itertools = ">=4.0.0" 214 | packaging = "*" 215 | pluggy = ">=0.12,<1.0" 216 | py = ">=1.5.0" 217 | wcwidth = "*" 218 | 219 | [package.extras] 220 | checkqa-mypy = ["mypy (==v0.761)"] 221 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 222 | 223 | [[package]] 224 | name = "pytest-mock" 225 | version = "3.7.0" 226 | description = "Thin-wrapper around the mock package for easier use with pytest" 227 | category = "dev" 228 | optional = false 229 | python-versions = ">=3.7" 230 | 231 | [package.dependencies] 232 | pytest = ">=5.0" 233 | 234 | [package.extras] 235 | dev = ["pre-commit", "tox", "pytest-asyncio"] 236 | 237 | [[package]] 238 | name = "python-i18n" 239 | version = "0.3.9" 240 | description = "Translation library for Python" 241 | category = "main" 242 | optional = false 243 | python-versions = "*" 244 | 245 | [package.extras] 246 | yaml = ["pyyaml (>=3.10)"] 247 | 248 | [[package]] 249 | name = "pyyaml" 250 | version = "6.0" 251 | description = "YAML parser and emitter for Python" 252 | category = "main" 253 | optional = false 254 | python-versions = ">=3.6" 255 | 256 | [[package]] 257 | name = "tomli" 258 | version = "2.0.1" 259 | description = "A lil' TOML parser" 260 | category = "main" 261 | optional = false 262 | python-versions = ">=3.7" 263 | 264 | [[package]] 265 | name = "typed-ast" 266 | version = "1.5.3" 267 | description = "a fork of Python 2 and 3 ast modules with type comment support" 268 | category = "main" 269 | optional = false 270 | python-versions = ">=3.6" 271 | 272 | [[package]] 273 | name = "typing-extensions" 274 | version = "4.1.1" 275 | description = "Backported and Experimental Type Hints for Python 3.6+" 276 | category = "main" 277 | optional = false 278 | python-versions = ">=3.6" 279 | 280 | [[package]] 281 | name = "watchdog" 282 | version = "2.1.7" 283 | description = "Filesystem events monitoring" 284 | category = "main" 285 | optional = false 286 | python-versions = ">=3.6" 287 | 288 | [package.extras] 289 | watchmedo = ["PyYAML (>=3.10)"] 290 | 291 | [[package]] 292 | name = "wcwidth" 293 | version = "0.2.5" 294 | description = "Measures the displayed width of unicode strings in a terminal" 295 | category = "dev" 296 | optional = false 297 | python-versions = "*" 298 | 299 | [[package]] 300 | name = "zipp" 301 | version = "3.8.0" 302 | description = "Backport of pathlib-compatible object wrapper for zip files" 303 | category = "main" 304 | optional = false 305 | python-versions = ">=3.7" 306 | 307 | [package.extras] 308 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 309 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] 310 | 311 | [metadata] 312 | lock-version = "1.1" 313 | python-versions = "^3.7" 314 | content-hash = "215ac07153eaa88ac74c5b1cdfeaf0609b21a24f3606a8389e21da133af4251f" 315 | 316 | [metadata.files] 317 | atomicwrites = [ 318 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 319 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 320 | ] 321 | attrs = [ 322 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 323 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 324 | ] 325 | black = [ 326 | {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, 327 | {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, 328 | {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, 329 | {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, 330 | {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, 331 | {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, 332 | {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, 333 | {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, 334 | {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, 335 | {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, 336 | {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, 337 | {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, 338 | {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, 339 | {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, 340 | {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, 341 | {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, 342 | {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, 343 | {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, 344 | {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, 345 | {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, 346 | {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, 347 | {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, 348 | {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, 349 | ] 350 | click = [ 351 | {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"}, 352 | {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"}, 353 | ] 354 | colorama = [ 355 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 356 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 357 | ] 358 | flake8 = [ 359 | {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 360 | {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 361 | ] 362 | importlib-metadata = [ 363 | {file = "importlib_metadata-4.0.0-py3-none-any.whl", hash = "sha256:19192b88d959336bfa6bdaaaef99aeafec179eca19c47c804e555703ee5f07ef"}, 364 | {file = "importlib_metadata-4.0.0.tar.gz", hash = "sha256:2e881981c9748d7282b374b68e759c87745c25427b67ecf0cc67fb6637a1bff9"}, 365 | ] 366 | mccabe = [ 367 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 368 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 369 | ] 370 | more-itertools = [ 371 | {file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"}, 372 | {file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"}, 373 | ] 374 | mypy-extensions = [ 375 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 376 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 377 | ] 378 | packaging = [ 379 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 380 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 381 | ] 382 | pathspec = [ 383 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 384 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 385 | ] 386 | platformdirs = [ 387 | {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, 388 | {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, 389 | ] 390 | pluggy = [ 391 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 392 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 393 | ] 394 | py = [ 395 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 396 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 397 | ] 398 | pycodestyle = [ 399 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 400 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 401 | ] 402 | pyflakes = [ 403 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 404 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 405 | ] 406 | pyparsing = [ 407 | {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, 408 | {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, 409 | ] 410 | pytest = [ 411 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, 412 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, 413 | ] 414 | pytest-mock = [ 415 | {file = "pytest-mock-3.7.0.tar.gz", hash = "sha256:5112bd92cc9f186ee96e1a92efc84969ea494939c3aead39c50f421c4cc69534"}, 416 | {file = "pytest_mock-3.7.0-py3-none-any.whl", hash = "sha256:6cff27cec936bf81dc5ee87f07132b807bcda51106b5ec4b90a04331cba76231"}, 417 | ] 418 | python-i18n = [ 419 | {file = "python-i18n-0.3.9.tar.gz", hash = "sha256:df97f3d2364bf3a7ebfbd6cbefe8e45483468e52a9e30b909c6078f5f471e4e8"}, 420 | {file = "python_i18n-0.3.9-py3-none-any.whl", hash = "sha256:bda5b8d889ebd51973e22e53746417bd32783c9bd6780fd27cadbb733915651d"}, 421 | ] 422 | pyyaml = [ 423 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 424 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 425 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 426 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 427 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 428 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 429 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 430 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 431 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 432 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 433 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 434 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 435 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 436 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 437 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 438 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 439 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 440 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 441 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 442 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 443 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 444 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 445 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 446 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 447 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 448 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 449 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 450 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 451 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 452 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 453 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 454 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 455 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 456 | ] 457 | tomli = [ 458 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 459 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 460 | ] 461 | typed-ast = [ 462 | {file = "typed_ast-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ad3b48cf2b487be140072fb86feff36801487d4abb7382bb1929aaac80638ea"}, 463 | {file = "typed_ast-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:542cd732351ba8235f20faa0fc7398946fe1a57f2cdb289e5497e1e7f48cfedb"}, 464 | {file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc2c11ae59003d4a26dda637222d9ae924387f96acae9492df663843aefad55"}, 465 | {file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd5df1313915dbd70eaaa88c19030b441742e8b05e6103c631c83b75e0435ccc"}, 466 | {file = "typed_ast-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:e34f9b9e61333ecb0f7d79c21c28aa5cd63bec15cb7e1310d7d3da6ce886bc9b"}, 467 | {file = "typed_ast-1.5.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f818c5b81966d4728fec14caa338e30a70dfc3da577984d38f97816c4b3071ec"}, 468 | {file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3042bfc9ca118712c9809201f55355479cfcdc17449f9f8db5e744e9625c6805"}, 469 | {file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4fff9fdcce59dc61ec1b317bdb319f8f4e6b69ebbe61193ae0a60c5f9333dc49"}, 470 | {file = "typed_ast-1.5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8e0b8528838ffd426fea8d18bde4c73bcb4167218998cc8b9ee0a0f2bfe678a6"}, 471 | {file = "typed_ast-1.5.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ef1d96ad05a291f5c36895d86d1375c0ee70595b90f6bb5f5fdbee749b146db"}, 472 | {file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed44e81517364cb5ba367e4f68fca01fba42a7a4690d40c07886586ac267d9b9"}, 473 | {file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f60d9de0d087454c91b3999a296d0c4558c1666771e3460621875021bf899af9"}, 474 | {file = "typed_ast-1.5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9e237e74fd321a55c90eee9bc5d44be976979ad38a29bbd734148295c1ce7617"}, 475 | {file = "typed_ast-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee852185964744987609b40aee1d2eb81502ae63ee8eef614558f96a56c1902d"}, 476 | {file = "typed_ast-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:27e46cdd01d6c3a0dd8f728b6a938a6751f7bd324817501c15fb056307f918c6"}, 477 | {file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d64dabc6336ddc10373922a146fa2256043b3b43e61f28961caec2a5207c56d5"}, 478 | {file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8cdf91b0c466a6c43f36c1964772918a2c04cfa83df8001ff32a89e357f8eb06"}, 479 | {file = "typed_ast-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:9cc9e1457e1feb06b075c8ef8aeb046a28ec351b1958b42c7c31c989c841403a"}, 480 | {file = "typed_ast-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e20d196815eeffb3d76b75223e8ffed124e65ee62097e4e73afb5fec6b993e7a"}, 481 | {file = "typed_ast-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:37e5349d1d5de2f4763d534ccb26809d1c24b180a477659a12c4bde9dd677d74"}, 482 | {file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f1a27592fac87daa4e3f16538713d705599b0a27dfe25518b80b6b017f0a6d"}, 483 | {file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8831479695eadc8b5ffed06fdfb3e424adc37962a75925668deeb503f446c0a3"}, 484 | {file = "typed_ast-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:20d5118e494478ef2d3a2702d964dae830aedd7b4d3b626d003eea526be18718"}, 485 | {file = "typed_ast-1.5.3.tar.gz", hash = "sha256:27f25232e2dd0edfe1f019d6bfaaf11e86e657d9bdb7b0956db95f560cceb2b3"}, 486 | ] 487 | typing-extensions = [ 488 | {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, 489 | {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, 490 | ] 491 | watchdog = [ 492 | {file = "watchdog-2.1.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:177bae28ca723bc00846466016d34f8c1d6a621383b6caca86745918d55c7383"}, 493 | {file = "watchdog-2.1.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d1cf7dfd747dec519486a98ef16097e6c480934ef115b16f18adb341df747a4"}, 494 | {file = "watchdog-2.1.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f14ce6adea2af1bba495acdde0e510aecaeb13b33f7bd2f6324e551b26688ca"}, 495 | {file = "watchdog-2.1.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4d0e98ac2e8dd803a56f4e10438b33a2d40390a72750cff4939b4b274e7906fa"}, 496 | {file = "watchdog-2.1.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:81982c7884aac75017a6ecc72f1a4fedbae04181a8665a34afce9539fc1b3fab"}, 497 | {file = "watchdog-2.1.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0b4a1fe6201c6e5a1926f5767b8664b45f0fcb429b62564a41f490ff1ce1dc7a"}, 498 | {file = "watchdog-2.1.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6e6ae29b72977f2e1ee3d0b760d7ee47896cb53e831cbeede3e64485e5633cc8"}, 499 | {file = "watchdog-2.1.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b9777664848160449e5b4260e0b7bc1ae0f6f4992a8b285db4ec1ef119ffa0e2"}, 500 | {file = "watchdog-2.1.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:19b36d436578eb437e029c6b838e732ed08054956366f6dd11875434a62d2b99"}, 501 | {file = "watchdog-2.1.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b61acffaf5cd5d664af555c0850f9747cc5f2baf71e54bbac164c58398d6ca7b"}, 502 | {file = "watchdog-2.1.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e877c70245424b06c41ac258023ea4bd0c8e4ff15d7c1368f17cd0ae6e351dd"}, 503 | {file = "watchdog-2.1.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d802d65262a560278cf1a65ef7cae4e2bc7ecfe19e5451349e4c67e23c9dc420"}, 504 | {file = "watchdog-2.1.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b3750ee5399e6e9c69eae8b125092b871ee9e2fcbd657a92747aea28f9056a5c"}, 505 | {file = "watchdog-2.1.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:ed6d9aad09a2a948572224663ab00f8975fae242aa540509737bb4507133fa2d"}, 506 | {file = "watchdog-2.1.7-py3-none-manylinux2014_armv7l.whl", hash = "sha256:b26e13e8008dcaea6a909e91d39b629a39635d1a8a7239dd35327c74f4388601"}, 507 | {file = "watchdog-2.1.7-py3-none-manylinux2014_i686.whl", hash = "sha256:0908bb50f6f7de54d5d31ec3da1654cb7287c6b87bce371954561e6de379d690"}, 508 | {file = "watchdog-2.1.7-py3-none-manylinux2014_ppc64.whl", hash = "sha256:bdcbf75580bf4b960fb659bbccd00123d83119619195f42d721e002c1621602f"}, 509 | {file = "watchdog-2.1.7-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:81a5861d0158a7e55fe149335fb2bbfa6f48cbcbd149b52dbe2cd9a544034bbd"}, 510 | {file = "watchdog-2.1.7-py3-none-manylinux2014_s390x.whl", hash = "sha256:03b43d583df0f18782a0431b6e9e9965c5b3f7cf8ec36a00b930def67942c385"}, 511 | {file = "watchdog-2.1.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ae934e34c11aa8296c18f70bf66ed60e9870fcdb4cc19129a04ca83ab23e7055"}, 512 | {file = "watchdog-2.1.7-py3-none-win32.whl", hash = "sha256:49639865e3db4be032a96695c98ac09eed39bbb43fe876bb217da8f8101689a6"}, 513 | {file = "watchdog-2.1.7-py3-none-win_amd64.whl", hash = "sha256:340b875aecf4b0e6672076a6f05cfce6686935559bb6d34cebedee04126a9566"}, 514 | {file = "watchdog-2.1.7-py3-none-win_ia64.whl", hash = "sha256:351e09b6d9374d5bcb947e6ac47a608ec25b9d70583e9db00b2fcdb97b00b572"}, 515 | {file = "watchdog-2.1.7.tar.gz", hash = "sha256:3fd47815353be9c44eebc94cc28fe26b2b0c5bd889dafc4a5a7cbdf924143480"}, 516 | ] 517 | wcwidth = [ 518 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 519 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 520 | ] 521 | zipp = [ 522 | {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, 523 | {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, 524 | ] 525 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pythonlings" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Ronald-TR "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.7" 9 | watchdog = "^2.1.6" 10 | colorama = "^0.4.4" 11 | click = "^8.0.4" 12 | python-i18n = "^0.3.9" 13 | flake8 = "^4.0.1" 14 | black = "^22.1.0" 15 | importlib-metadata = "4.0" 16 | PyYAML = "^6.0" 17 | 18 | [tool.poetry.dev-dependencies] 19 | pytest = "^5.2" 20 | pytest-mock = "^3.7.0" 21 | 22 | [build-system] 23 | requires = ["poetry-core>=1.0.0"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /pythonlings/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | -------------------------------------------------------------------------------- /pythonlings/__main__.py: -------------------------------------------------------------------------------- 1 | from pythonlings.adapters.cmd import run 2 | 3 | if __name__ == "__main__": 4 | run() 5 | -------------------------------------------------------------------------------- /pythonlings/adapters/cmd.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import i18n 4 | import argparse 5 | 6 | from pythonlings.services.exercises import process_exercises, process_single_exercise 7 | 8 | i18n.config.set("locale", os.getenv("PYTHONLINGS_LANGUAGE", "en")) 9 | i18n.load_path.append("pythonlings/i18n") 10 | _ = i18n.t 11 | 12 | 13 | def run(): 14 | parser = argparse.ArgumentParser(description=_("p.description")) 15 | parser.set_defaults(func=parser.print_help, type=bool) 16 | 17 | subparsers = parser.add_subparsers(dest="command") 18 | start_command = subparsers.add_parser("start", help=_("p.start_help")) 19 | start_command.set_defaults(func=process_exercises) 20 | 21 | exec_command = subparsers.add_parser("exec", help=_("p.exec_help")) 22 | exec_command.set_defaults(func=process_single_exercise) 23 | 24 | if len(sys.argv) == 1: 25 | parser.print_help(sys.stderr) 26 | sys.exit(1) 27 | 28 | argx = parser.parse_args() 29 | argx.func(argx) 30 | -------------------------------------------------------------------------------- /pythonlings/domain/exercises.py: -------------------------------------------------------------------------------- 1 | from colorama import Fore 2 | import subprocess as subp 3 | import i18n 4 | import os 5 | from abc import ABC, abstractmethod 6 | 7 | _ = i18n.t 8 | 9 | 10 | class BaseExercise(ABC): 11 | @abstractmethod 12 | def is_not_done(self): 13 | ... 14 | 15 | @abstractmethod 16 | def process(self) -> None: 17 | ... 18 | 19 | def __str__(self) -> str: 20 | raise NotImplementedError 21 | 22 | def __bool__(self) -> str: 23 | raise NotImplementedError 24 | 25 | 26 | class Exercise: 27 | def __init__(self, filepath=None) -> None: 28 | self.fp = filepath 29 | self.error = False 30 | self.output = None 31 | self.to_do = None 32 | self.is_not_done() 33 | self.package, self.name = self.exercise_metadata() 34 | 35 | def exercise_metadata(self): 36 | package = os.path.basename(os.path.dirname(self.fp)).split('_')[1] 37 | exercise_name = os.path.splitext(os.path.basename(self.fp))[0] 38 | return package, exercise_name 39 | 40 | def __str__(self) -> str: 41 | hint = _(f'{self.package}.{self.name}') 42 | error_msg = f""""{self.fp} {Fore.RED}[{_('p.error_flag')}] 43 | {Fore.GREEN} 44 | {hint} 45 | {Fore.RESET} 46 | {self.output} 47 | """ 48 | success_msg = f"""{self.fp} {Fore.GREEN}[{_('p.success_flag')}]!{Fore.RESET} 49 | """ 50 | makeitpass_msg = f""""{self.fp} {Fore.CYAN}[{_('p.make_it_pass_flag')}]! 51 | {Fore.GREEN} 52 | {hint}{Fore.RESET} 53 | """ 54 | if self.to_do: 55 | return makeitpass_msg 56 | 57 | return error_msg if self.error else success_msg 58 | 59 | def __bool__(self) -> bool: 60 | """Bool protocol for class 61 | Returns: 62 | bool: True if the exercise pass, False otherwise. 63 | """ 64 | return not self.error 65 | 66 | def is_not_done(self) -> bool: 67 | with open(self.fp, "r") as fp: 68 | self.to_do = "# I AM NOT DONE" in fp.read() 69 | return self.to_do 70 | 71 | def process(self) -> None: 72 | if self.is_not_done(): 73 | return 74 | command = ["python", self.fp] 75 | r = subp.run(command, stdout=subp.PIPE, stderr=subp.PIPE) 76 | self.error = bool(r.returncode) 77 | self.output = r.stderr if self.error else r.stdout 78 | self.output = self.output.decode() 79 | -------------------------------------------------------------------------------- /pythonlings/exercises/1_variables/variables1.py: -------------------------------------------------------------------------------- 1 | # I AM NOT DONE 2 | 3 | a_integer = 1 4 | a_float = 0.1 5 | a_string = "Hello World!" 6 | a_bytes = b"Hello" 7 | a_boolean = True 8 | a_tuple = (1, 2, "3", False) 9 | 10 | assert isinstance(a_integer, int) 11 | assert isinstance(a_float, float) 12 | assert isinstance(a_string, str) 13 | assert isinstance(a_bytes, bytes) 14 | assert isinstance(a_boolean, True) 15 | assert isinstance(a_tuple, tuple) 16 | -------------------------------------------------------------------------------- /pythonlings/exercises/1_variables/variables2.py: -------------------------------------------------------------------------------- 1 | # I AM NOT DONE 2 | 3 | x = 1.0 4 | y = 1 5 | z = "Hello" 6 | 7 | # DON'T EDIT THE TESTS 8 | assert isinstance(x, int) 9 | assert isinstance(y, float) 10 | assert isinstance(z, bytes) 11 | -------------------------------------------------------------------------------- /pythonlings/exercises/1_variables/variables3.py: -------------------------------------------------------------------------------- 1 | # I AM NOT DONE 2 | 3 | x = y = z = "Hello!" 4 | a, b, c = "a", "b", "c" 5 | 6 | # DON'T EDIT THE TESTS 7 | assert x == y == z == "Hello!" 8 | assert a == "a" 9 | assert b == "b" 10 | assert c == "c" 11 | -------------------------------------------------------------------------------- /pythonlings/exercises/1_variables/variables4.py: -------------------------------------------------------------------------------- 1 | # I AM NOT DONE 2 | 3 | hello = "Hello " 4 | world = "World!" 5 | hello_world = hello + world 6 | 7 | one = 1 8 | two = "2" 9 | one_plus_two = one + two 10 | 11 | # DON'T EDIT THE TESTS 12 | assert hello_world == "Hello World!" 13 | assert one_plus_two == 3 14 | -------------------------------------------------------------------------------- /pythonlings/exercises/1_variables/variables5.py: -------------------------------------------------------------------------------- 1 | # I AM NOT DONE 2 | 3 | hello_immutable_string = "Hello" 4 | 5 | hello_mutable_list = ["H", "e", "l", "l", "o"] 6 | hello_mutable_dict = {"Hello": "World"} 7 | hello_mutable_set = {1, 2, 3} 8 | 9 | hello_immutable_string[0] = "H" 10 | hello_mutable_list[0] = "I WORK" 11 | 12 | # DON'T EDIT THE TESTS 13 | assert "".join(hello_mutable_list) == "I WORKello" 14 | -------------------------------------------------------------------------------- /pythonlings/exercises/2_data_structures/data_structures1.py: -------------------------------------------------------------------------------- 1 | # I AM NOT DONE 2 | 3 | my_list = [0, "hello", True, ["other list"]] # or my_list = list() 4 | 5 | my_list[0] = 1 6 | my_list.append(3) 7 | my_list.append(4) 8 | my_list.pop() 9 | 10 | # DON'T EDIT THE TESTS 11 | assert len(my_list) == 4 12 | assert my_list[0] == 1 13 | assert my_list[1] == "world" 14 | assert my_list[2] == False 15 | -------------------------------------------------------------------------------- /pythonlings/i18n/data_structures.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | data_structures1: | 3 | In Python, the lists are mutable data structures extremely flexible that we can use to store anything! 4 | Once created, we can add, remove or change their values through indices. 5 | 6 | Make the test pass keeping the list with the same requisites from the test cases. 7 | -------------------------------------------------------------------------------- /pythonlings/i18n/data_structures.pt.yml: -------------------------------------------------------------------------------- 1 | en: 2 | data_structures1: | 3 | Em Python, listas são estruturas de dados mutáveis extremamente flexíveis que podemos usar 4 | para armazenar qualquer coisa! Após criadas, nós podemos adicionar, remover e mudar seus valores através dos índices. 5 | 6 | Faça o teste passar deixando a lista com os requisitos dos casos de teste. 7 | -------------------------------------------------------------------------------- /pythonlings/i18n/p.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | description: Pythonlings is a utility to help newcomers learn Python through exercises, with a variety of themes! 3 | start_help: Start Pythonlings! Executing and watching the exercise files in order. 4 | exec_help: Executes a single exercise, you just need to give the absolute or the relative path. 5 | success_flag: SUCCESS 6 | make_it_pass_flag: MAKE IT PASS 7 | error_flag: ERROR -------------------------------------------------------------------------------- /pythonlings/i18n/p.pt.yml: -------------------------------------------------------------------------------- 1 | pt: 2 | description: Pythonlings é um utilitário para ajudar iniciantes a aprender Python através de exercícios, em uma variedade de temas! 3 | start_help: Inicia o Pythonlings! Executando e assistindo os arquivos de exercício em ordem. 4 | exec_help: Executa apenas um exercicio, você apenas deve prover o caminho relativo ou absoluto do arquivo do exercicio. 5 | success_flag: SUCESSO 6 | make_it_pass_flag: FAÇA-ME PASSAR 7 | error_flag: ERRO -------------------------------------------------------------------------------- /pythonlings/i18n/variables.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | variables1: | 3 | Python is a language that is object oriented, strong and dynamic typing 4 | This means that the interpreter will dynamically set the variable type during attribuitions 5 | But once typed, the variable will have a type, respecting the protocols and behaviors of their type. 6 | variables2: | 7 | You have to fix this program by attribuiting the right types to the variables! 8 | Remember that the types are attributed during the variable initialization. 9 | If you change the value, you change te final variable type! 10 | variables3: | 11 | In Python you can assign a single value to multiple variables at same time. 12 | You still can assign multiple values to multiple variables, this techique is called unpacking. 13 | variables4: | 14 | Variables can be used to compound other variables trough operators and protocols (that we'll see later), 15 | behind the scenes, operators in Python uses protocols implemented by types to solve the operations. 16 | For now, we can assume that the types "int" and "str" implements the addition protocol, allowing us to sum values *at same type*, obtaining a new as result. 17 | variables5: | 18 | In Python, we have the concept of mutable and immutable types. 19 | Immutable types are: int, str, boolean, float and tuples. 20 | Mutable types are: list, dict and set. 21 | 22 | To immutable variables, this means that once created, we cannot change their values in memory (by acessing index or calling methods). 23 | But, mutable types allow us to change their values in memory. 24 | Immutable types are "cheap" to acces and "expensive" to change, whereas mutable types tend to invert these characteristics. 25 | Please keep this in mind, we will discuss it better in "Data Structures" module. 26 | -------------------------------------------------------------------------------- /pythonlings/i18n/variables.pt.yml: -------------------------------------------------------------------------------- 1 | pt: 2 | variables1: | 3 | Python é uma linguagem orientada a objetos e com tipagem forte e dinâmica 4 | Isso significa que o interpretador vai dinamicamente atribuir o tipo da variavel durante a atribuição. 5 | Porém uma vez tipado, a variavel vai ter um tipo forte, respeitando os protocolos e comportamentos do seu tipo. 6 | variables2: | 7 | Você deverá corrigir os casos de testes atribuindo os tipos corretos as variáveis! 8 | Lembre-se que os tipos são atribuidos durante a inicialização das variáveis. 9 | Se voê mudar o valor atribuído, você muda o tipo da variavel! 10 | variables3: | 11 | Em Python você consegue atribuir um único valor em multiplas variávesl ao mesmo tempo. 12 | Você também consegue atribuir multiplos valores à multiplas variáveis na mesma expressão, 13 | essa tecnica é chamada de "unpacking" (desempacotamento). 14 | variables4: | 15 | Variáveis podem ser usadas para compor outras variáveis através de operadores e protocolos (que veremos adiante), 16 | por baixo dos panos, operadores no Python utilizam os protocolos implementados pelos seus tipos para resolver as operações. 17 | Por hora, podemos assumir que os tipos "int" e "str" implementam estes protocolos de adição, nos possibilitando somar valores *do mesmo tipo*, obtendo um novo como resultado. 18 | variables5: | 19 | Em Python, nós temos o conceito de tipos mutáveis e imutáveis. 20 | Tipos imutáveis são: int, str, boolean, float e tuples. 21 | Tipos mutáveis são: list, dict e set. 22 | 23 | Para variáveis imutáveis, isso significa que uma vez criadas, nós não podemos mudar seus valores em memória (através de indices ou métodos). 24 | Porém, tipos mutáveis nos permitem mudar seus valores em memória. 25 | 26 | Tipos imutáveis são "baratos" para acessar e "caros" para mudar, enquanto tipos mutáveis tendem a inverter estas caracteristicas. 27 | Por favor mantenha isso em mente, nós iremos discuti-los melhor no módulo de "Data Structures". 28 | -------------------------------------------------------------------------------- /pythonlings/services/exercises.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | from functools import partial, lru_cache 4 | import argparse 5 | 6 | from watchdog.observers import Observer 7 | from watchdog.events import PatternMatchingEventHandler 8 | from pythonlings.domain.exercises import Exercise 9 | from pythonlings.services.logging import logger 10 | import i18n 11 | _ = i18n.t 12 | 13 | 14 | def get_exercises_root() -> str: 15 | """Returns the exercises path root 16 | 17 | Raises: 18 | FileNotFoundError: If exercises directory not found 19 | 20 | Returns: 21 | str: Exercises root 22 | """ 23 | path = os.path.join(os.getcwd(), "pythonlings", "exercises") 24 | if not os.path.exists(path): 25 | raise FileNotFoundError( 26 | f"Path: {path} does not exist." 27 | "Are you running Pythonlings in repository root?" 28 | ) 29 | return path 30 | 31 | 32 | @lru_cache(maxsize=1) 33 | def safe_print(msg: str): 34 | logger.info(msg) 35 | 36 | 37 | def on_modified(event, exercise) -> None: 38 | exercise.process() 39 | safe_print(str(exercise)) 40 | 41 | 42 | def observe_exercise_until_pass(exercise: Exercise) -> None: 43 | event_handler = PatternMatchingEventHandler( 44 | patterns=["*.py"], ignore_directories=True 45 | ) 46 | event_handler.on_modified = partial(on_modified, exercise=exercise) 47 | observer = Observer() 48 | observer.schedule(event_handler, os.path.dirname(exercise.fp), recursive=False) 49 | observer.start() 50 | try: 51 | while exercise.error or exercise.to_do: 52 | time.sleep(1) 53 | else: 54 | raise StopIteration 55 | except StopIteration: 56 | observer.stop() 57 | observer.join() 58 | 59 | except KeyboardInterrupt: 60 | observer.stop() 61 | observer.join() 62 | exit(0) 63 | 64 | 65 | def process_exercises(args: argparse.Namespace) -> None: 66 | root = get_exercises_root() 67 | for path, subdirs, files in os.walk(root): 68 | subdirs.sort() 69 | for file in sorted(files): 70 | fp = os.path.join(path, file) 71 | exercise = Exercise(fp) 72 | exercise.process() 73 | logger.info(exercise) 74 | if exercise.error or exercise.to_do: 75 | observe_exercise_until_pass(exercise) 76 | 77 | 78 | def process_single_exercise(args: argparse.Namespace) -> None: 79 | raise NotImplementedError 80 | -------------------------------------------------------------------------------- /pythonlings/services/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | logging.basicConfig( 5 | level=logging.INFO, format="%(asctime)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S" 6 | ) 7 | 8 | logger = logging.getLogger('pythonlings') 9 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigmages/pythonlings/08f7ce1a36a01890d037e2b16808492791ef0f6e/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import os 4 | import i18n 5 | 6 | 7 | @pytest.fixture 8 | def fixtures_dir(): 9 | return os.path.join(os.path.dirname(__file__), "fixtures") 10 | 11 | 12 | def pytest_configure(): 13 | # setup i18n before tests 14 | i18n.config.set("locale", os.getenv("PYTHONLINGS_LANGUAGE", "en")) 15 | i18n.load_path.append("pythonlings/i18n") 16 | -------------------------------------------------------------------------------- /tests/fixtures/1_examples/exercise_sample_fail.py: -------------------------------------------------------------------------------- 1 | raise Exception("Sample test exception") 2 | -------------------------------------------------------------------------------- /tests/fixtures/1_examples/exercise_sample_success.py: -------------------------------------------------------------------------------- 1 | print("Hello World!") 2 | -------------------------------------------------------------------------------- /tests/fixtures/1_examples/exercise_sample_to_do.py: -------------------------------------------------------------------------------- 1 | # I AM NOT DONE 2 | print("Hello World!") 3 | -------------------------------------------------------------------------------- /tests/test_pythonlings.py: -------------------------------------------------------------------------------- 1 | from pythonlings import __version__ 2 | from pythonlings.services.exercises import get_exercises_root 3 | from pythonlings.domain.exercises import Exercise 4 | from uuid import uuid4 5 | import os 6 | import pytest 7 | 8 | 9 | def test_version(): 10 | assert __version__ == "0.1.0" 11 | 12 | 13 | def test_get_exercises_root_success(): 14 | get_exercises_root() 15 | 16 | 17 | def test_get_exercises_root_fail(mocker): 18 | invalid_dir = uuid4().hex 19 | mocker.patch("os.getcwd", return_value=invalid_dir) 20 | with pytest.raises(FileNotFoundError): 21 | get_exercises_root() 22 | 23 | 24 | def test_exercise_success(fixtures_dir): 25 | epath = os.path.join(fixtures_dir, "1_examples", "exercise_sample_success.py") 26 | exercise = Exercise(epath) 27 | exercise.process() 28 | 29 | assert exercise.error is False 30 | assert bool(exercise) is True 31 | assert "[SUCCESS]" in str(exercise) 32 | 33 | 34 | def test_exercise_to_do(fixtures_dir): 35 | epath = os.path.join(fixtures_dir, "1_examples", "exercise_sample_to_do.py") 36 | exercise = Exercise(epath) 37 | exercise.process() 38 | 39 | assert exercise.to_do is True 40 | assert bool(exercise) is True 41 | assert "[MAKE IT PASS]" in str(exercise) 42 | 43 | 44 | def test_exercise_fail(fixtures_dir): 45 | epath = os.path.join(fixtures_dir, "1_examples", "exercise_sample_fail.py") 46 | exercise = Exercise(epath) 47 | exercise.process() 48 | 49 | assert exercise.error is True 50 | assert bool(exercise) is False 51 | assert "[ERROR]" in str(exercise) 52 | --------------------------------------------------------------------------------