├── .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 |
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 |
--------------------------------------------------------------------------------