Repository: sepandhaghighi/nafas
Branch: master
Commit: 4ee0138cedbf
Files: 40
Total size: 95.8 KB
Directory structure:
gitextract_cnlewpi2/
├── .coveragerc
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ └── feature_request.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── publish.yml
│ └── test.yml
├── .gitignore
├── AUTHORS.md
├── CHANGELOG.md
├── LICENSE
├── MANIFEST.in
├── NAFAS.spec
├── PROGRAMS.md
├── README.md
├── SECURITY.md
├── autopep8.bat
├── autopep8.sh
├── build_exe.bat
├── codecov.yml
├── dev-requirements.txt
├── nafas/
│ ├── __init__.py
│ ├── __main__.py
│ ├── functions.py
│ └── params.py
├── otherfiles/
│ ├── RELEASE.md
│ ├── Version.rc
│ ├── requirements-splitter.py
│ └── version_check.py
├── pytest.ini
├── requirements.txt
├── setup.py
└── test/
├── test.py
├── test_config1.json
├── test_config2.json
└── test_config3.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .coveragerc
================================================
[run]
branch = True
omit =
*/nafas/__main__.py
*/nafas/__init__.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
pragma: no cover
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at me@sepand.tech. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contribution
Changes and improvements are more than welcome! ❤️ Feel free to fork and open a pull request.
Please consider the following :
1. Fork it!
2. Create your feature branch (under `dev` branch)
3. Add your functions/methods to proper files
4. Add standard `docstring` to your functions/methods
5. Add tests for your functions/methods (`doctest` testcases in `test` folder)
6. Pass all CI tests
7. Update `CHANGELOG.md`
- Describe changes under `[Unreleased]` section
8. Submit a pull request into `dev` (please complete the pull request template)
================================================
FILE: .github/FUNDING.yml
================================================
custom: https://github.com/sepandhaghighi/nafas#show-your-support
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: File a bug report
title: "[Bug]: "
body:
- type: markdown
attributes:
value: |
Thanks for your time to fill out this bug report!
- type: input
id: contact
attributes:
label: Contact details
description: How can we get in touch with you if we need more info?
placeholder: ex. email@example.com
validations:
required: false
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Provide a clear and concise description of what the bug is.
placeholder: >
Tell us a description of the bug.
validations:
required: true
- type: textarea
id: step-to-reproduce
attributes:
label: Steps to reproduce
description: Provide details of how to reproduce the bug.
placeholder: >
ex. 1. Go to '...'
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: What did you expect to happen?
placeholder: >
ex. I expected '...' to happen
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: Actual behavior
description: What did actually happen?
placeholder: >
ex. Instead '...' happened
validations:
required: true
- type: dropdown
id: operating-system
attributes:
label: Operating system
description: Which operating system are you using?
options:
- Windows
- macOS
- Linux
default: 0
validations:
required: true
- type: dropdown
id: python-version
attributes:
label: Python version
description: Which version of Python are you using?
options:
- Python 3.14
- Python 3.13
- Python 3.12
- Python 3.11
- Python 3.10
- Python 3.9
- Python 3.8
- Python 3.7
- Python 3.6
default: 1
validations:
required: true
- type: dropdown
id: nafas-version
attributes:
label: Nafas version
description: Which version of Nafas are you using?
options:
- Nafas 1.5
- Nafas 1.4
- Nafas 1.3
- Nafas 1.2
- Nafas 1.1
- Nafas 1.0
- Nafas 0.9
- Nafas 0.8
- Nafas 0.7
- Nafas 0.6
- Nafas 0.5
- Nafas 0.4
- Nafas 0.3
- Nafas 0.2
- Nafas 0.1
default: 0
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.com/invite/CtZUNKJHP4
about: Ask questions and discuss with other Nafas community members
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature Request
description: Suggest a feature for this project
title: "[Feature]: "
body:
- type: textarea
id: description
attributes:
label: Describe the feature you want to add
placeholder: >
I'd like to be able to [...]
validations:
required: true
- type: textarea
id: possible-solution
attributes:
label: Describe your proposed solution
placeholder: >
I think this could be done by [...]
validations:
required: false
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered, if relevant
placeholder: >
Another way to do this would be [...]
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: Additional context
placeholder: >
Add any other context or screenshots about the feature request here.
validations:
required: false
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
#### Reference Issues/PRs
#### What does this implement/fix? Explain your changes.
#### Any other comments?
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: weekly
time: "01:30"
open-pull-requests-limit: 10
target-branch: dev
assignees:
- "sepandhaghighi"
================================================
FILE: .github/workflows/publish.yml
================================================
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: Upload Python Package
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- '*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
deploy:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*.tar.gz
twine upload dist/*.whl
================================================
FILE: .github/workflows/test.yml
================================================
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: CI
on:
push:
branches:
- master
- dev
pull_request:
branches:
- master
- dev
env:
TEST_PYTHON_VERSION: 3.9
TEST_OS: 'ubuntu-22.04'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-2022, macos-15-intel]
python-version: [3.7, 3.8, 3.9, 3.10.5, 3.11.0, 3.12.0, 3.13.0, 3.14.0]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Installation
run: |
python -m pip install --upgrade pip
pip install .
- name: First test
run: |
nafas --version --color="red" --bg-color="blue" --intensity="bright"
nafas --generate-config="test.json"
- name: Install dev-requirements
run: |
python otherfiles/requirements-splitter.py
pip install --upgrade --upgrade-strategy=only-if-needed -r test-requirements.txt
- name: Version check
run: |
python otherfiles/version_check.py
if: matrix.python-version == env.TEST_PYTHON_VERSION
- name: Test with pytest
run: |
python -m pytest test --cov=nafas --cov-report=term
- name: Other tests
run: |
python -m vulture nafas/ setup.py --min-confidence 65 --exclude=__init__.py --sort-by-size
python -m bandit -r nafas -s B311,B605,B607
python -m pydocstyle --match-dir=nafas -v
if: matrix.python-version == env.TEST_PYTHON_VERSION
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
if: matrix.python-version == env.TEST_PYTHON_VERSION && matrix.os == env.TEST_OS
================================================
FILE: .gitignore
================================================
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
.venv/
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
### Example user template template
### Example user template
# IntelliJ project files
.idea
*.iml
out
gen
================================================
FILE: AUTHORS.md
================================================
# Core Developers
----------
- [@sepandhaghighi](http://github.com/sepandhaghighi)
- [@sadrasabouri](https://github.com/sadrasabouri)
# Other Contributors
----------
- [@sarahfard](https://github.com/sarahfard) **
- [@oscarArismendi](https://github.com/oscarArismendi)
- [@AHReccese](https://github.com/AHReccese)
** Graphic designer
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.5] - 2026-05-04
### Added
- Fire program
- Retention program
- Swooning program
- `--generate-config` argument
- `--color` argument
- `--bg-color` argument
- `--intensity` argument
### Changed
- `README.md` modified
- `Python 3.14` added to `test.yml`
- Test system modified
- CLI functions modified
## [1.4] - 2025-07-29
### Added
- `ca1` speaker audio files
- `ca2` speaker audio files
- `au1` speaker audio files
- `au2` speaker audio files
- `uk1` speaker audio files
- `uk2` speaker audio files
### Changed
- Build script updated
- `README.md` modified
- `line` function renamed to `print_line`
- `bpm_calc` function renamed to `calculate_bpm`
- `time_calc` function renamed to `calculate_time`
- `time_average_calc` function renamed to `calculate_average_time`
- `time_convert` function renamed to `convert_time`
- `left_justify` function renamed to `justify_left`
- `justify` function renamed to `justify_text`
- `sound_check` function renamed to `check_sound`
- `nafas_description_print` function renamed to `print_nafas_description`
- `program_details_print` function renamed to `print_program_details`
- `input_filter` function renamed to `filter_input`
- `get_input_standard` function renamed to `get_standard_input`
- `run` function renamed to `run_program`
## [1.3] - 2025-06-27
### Added
- `--speaker` argument
- `us2` speaker audio files
- `in1` speaker audio files
- `in2` speaker audio files
- `cn1` speaker audio files
- `cn2` speaker audio files
### Changed
- `README.md` modified
## [1.2] - 2025-06-04
### Added
- Coherent program
- Breaths Per Minute (BPM)
### Changed
- `description_print` function renamed to `nafas_description_print`
- `program_description_print` function renamed to `program_details_print`
- Python typing features added to all modules
- `Python 3.6` support dropped
- Test system modified
- `PROGRAMS.md` updated
## [1.1] - 2025-04-10
### Added
- Survey form
- `--config` argument
- `--skip-intro` argument
### Changed
- `README.md` modified
## [1.0] - 2025-03-10
### Added
- Calming3 program
- Box program
### Changed
- `README.md` modified
- String templates modified
- `PROGRAMS.md` updated
## [0.9] - 2025-01-06
### Added
- Energizing program
- `PROGRAMS.md`
- Cautions message
### Changed
- `README.md` modified
- `AUTHORS.md` updated
- Menu updated
- Exit bug fixed
- Program details updated
## [0.8] - 2024-11-04
### Added
- Balancing program
- `--silent` argument
- `clear_screen` function
### Changed
- GitHub actions are limited to the `dev` and `master` branches
- Restart mode updated
- Exit bug fixed
- `Python 3.13` added to `test.yml`
- Messages and templates moved to `params.py`
## [0.7] - 2024-08-27
### Added
- `feature_request.yml` template
- `config.yml` for issue template
- `sound_check` function
- `SECURITY.md`
### Changed
- Silence added to the start of sounds
- Bug report template modified
- `playsound` replaced with `nava`
- `nava` added to `requirements.txt`
- Test system modified
- Build system modified
- `get_input_standard` function modified
- `Python 3.11` added to `test.yml`
- `Python 3.12` added to `test.yml`
- `Python 3.5` support dropped
- CLI mode updated
- Exit message updated
- `README.md` modified
## [0.6] - 2022-06-22
### Added
- Calming2 program
### Changed
- Calming program renamed to Calming1
- Logo updated
- `README.md` modified
## [0.5] - 2022-05-09
### Added
- Decision-Making program
- `time_calc` function
- `time_average_calc` function
### Changed
- `AUTHORS.md` updated
- License updated
- `README.md` modified
- `Python 3.10` added to `test.yml`
- `time_convert` function modified
- `get_input_standard` function modified
- Menu updated
- Relax program renamed to Relax1
- 4-7-8 program renamed to Relax2
- 7-11 program renamed to Relax3
## [0.4] - 2021-05-12
### Added
- `start.wav`
- `well_done.wav`
- `preparing.wav`
- `requirements-splitter.py`
- 4-7-8 program
- 7-11 program
### Changed
- Sound speaker changed
- Test system modified
- Menu optimized
## [0.3] - 2021-02-09
### Changed
- Sounds bug in pypi fixed
- Icon modified
- Menu optimized
## [0.2] - 2021-01-29
### Added
- `_playsound_async` function
- `play_sound` function
- `inhale.wav`
- `exhale.wav`
- `sustain.wav`
- `retain.wav`
- `get_sound_path` function
- `program_description_print` function
- `time_convert` function
### Changed
- Menu optimized
- Test system modified
- `get_program_dict` function renamed to `get_program_data`
- `program_dict` parameter renamed to `program_data`
- `input_dict` parameter renamed to `input_data`
- `README.md` updated
## [0.1] - 2020-10-30
### Added
- Clear Mind program
- Relax program
- Calming program
- Power program
- Anti-Stress program
- Anti-Appetite program
- Cigarette Replace program
[Unreleased]: https://github.com/sepandhaghighi/nafas/compare/v1.5...dev
[1.5]: https://github.com/sepandhaghighi/nafas/compare/v1.4...v1.5
[1.4]: https://github.com/sepandhaghighi/nafas/compare/v1.3...v1.4
[1.3]: https://github.com/sepandhaghighi/nafas/compare/v1.2...v1.3
[1.2]: https://github.com/sepandhaghighi/nafas/compare/v1.1...v1.2
[1.1]: https://github.com/sepandhaghighi/nafas/compare/v1.0...v1.1
[1.0]: https://github.com/sepandhaghighi/nafas/compare/v0.9...v1.0
[0.9]: https://github.com/sepandhaghighi/nafas/compare/v0.8...v0.9
[0.8]: https://github.com/sepandhaghighi/nafas/compare/v0.7...v0.8
[0.7]: https://github.com/sepandhaghighi/nafas/compare/v0.6...v0.7
[0.6]: https://github.com/sepandhaghighi/nafas/compare/v0.5...v0.6
[0.5]: https://github.com/sepandhaghighi/nafas/compare/v0.4...v0.5
[0.4]: https://github.com/sepandhaghighi/nafas/compare/v0.3...v0.4
[0.3]: https://github.com/sepandhaghighi/nafas/compare/v0.2...v0.3
[0.2]: https://github.com/sepandhaghighi/nafas/compare/v0.1...v0.2
[0.1]: https://github.com/sepandhaghighi/nafas/compare/c58087a...v0.1
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Sepand Haghighi
Copyright (c) 2020 Sadra Sabouri
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
include LICENSE
include *.md
include *.spec
include *.txt
include *.yml
include *.ini
include nafas/sounds/*.wav
include nafas/sounds/*/*.wav
================================================
FILE: NAFAS.spec
================================================
# -*- mode: python -*-
block_cipher = None
nafas_version = "1.5"
a = Analysis(['nafas/__main__.py'],
pathex=['nafas'],
binaries=[],
datas=[('nafas/sounds/silence.wav', 'nafas/sounds'), ('nafas/sounds/us1/*.wav', 'nafas/sounds/us1')],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name='NAFAS-'+nafas_version,
debug=False,
strip=False,
upx=True,
runtime_tmpdir=None,
icon='otherfiles/icon.ico',
version="otherfiles/Version.rc",
console=True )
================================================
FILE: PROGRAMS.md
================================================
# Nafas Breathing Programs
**Last Update: 2026-02-19**
| **Program** | **Description** | **Level** | **Ratios**<br>**(I:R:E:S)** | **Unit**<br>**(Seconds)** | **Cycles** |
|----------------------|-------------------------------------------------------------------------------------------------------------------------------|-------------------|-----------------------|--------------------------|----------------|
| Clear Mind | Short program to reduce mental fog and improve focus. Useful for developers during coding breaks. Beneficial for those needing quick mental refreshment. | B<br>M<br>A | 1:0:3:0<br>1:0:4:0<br>1:0:5:0 | 3<br>3<br>3 | 35<br>28<br>24 |
| Relax1 | Relaxation-focused program to calm nerves. Ideal for use after intense coding sessions. Useful for programmers managing stress. | B<br>M<br>A | 1:0:2:2<br>1:0:2:3<br>1:0:2:4 | 3<br>3<br>3 | 28<br>24<br>22 |
| Relax2 | Enhanced relaxation program with a focus on breathing depth. Suitable for unwinding after long work sessions. | B<br>M<br>A | 4:7:8:0<br>4:7:8:0<br>4:7:8:0 | 1<br>1<br>1 | 4<br>8<br>12 |
| Relax3 | Deeper relaxation exercise for stress relief and a more meditative state. Ideal for unwinding after complex projects. | B<br>M<br>A | 7:0:11:0<br>7:0:11:0<br>7:0:11:0 | 1<br>1<br>1 | 15<br>20<br>24 |
| Calming1 | Soothing breathing pattern, helpful for reducing anxiety. Beneficial for long working hours and maintaining calm. | B<br>M<br>A | 1:2:1:2<br>1:3:1:3<br>1:4:1:4 | 3<br>3<br>3 | 24<br>22<br>20 |
| Calming2 | An extended version of the calming routine, providing a longer period of relaxation and anxiety reduction. Useful for balancing high-stress situations. | B<br>M<br>A | 5:0:5:5<br>5:0:5:5<br>5:0:5:5 | 1<br>1<br>1 | 4<br>6<br>8 |
| Calming3 | A breathing routine designed to deepen relaxation and improve focus. Suitable for transitioning from mild stress to a state of balance and tranquility. | B<br>M<br>A | 4:0:6:0<br>6:1:8:4<br>4:1:12:1 | 1<br>1<br>1 | 6<br>8<br>10 |
| Power | Energizing breathing, designed for boosting energy and alertness. Suitable for users feeling fatigue. | B<br>M<br>A | 1:2:2:0<br>1:3:2:0<br>1:4:2:0 | 3<br>3<br>3 | 28<br>24<br>20 |
| Harmony | Balancing exercise to promote relaxation and focus. Ideal for programmers looking to maintain concentration. | B<br>M<br>A | 1:3:2:1<br>1:4:2:1<br>1:5:2:1 | 3<br>3<br>3 | 20<br>18<br>16 |
| Anti-Stress | Specialized for stress relief, particularly useful during high-stress tasks or debugging sessions. | B<br>M<br>A | 3:0:0.66:0<br>4:0:0.66:0<br>5:0:0.66:0 | 3<br>3<br>3 | 20<br>17<br>14 |
| Anti-Appetite | Breathing program to manage cravings and avoid unnecessary snacking during work. Beneficial for users seeking appetite control. | B<br>M<br>A | 5:0:5:5<br>6:0:5:5<br>7:0:5:5 | 1<br>1<br>1 | 40<br>38<br>36 |
| Cigarette Replace | Alternative to smoking breaks, encouraging mindful breathing. Suitable for smokers looking to reduce habit triggers. | B<br>M<br>A | 2:1.1:2.2:0.8<br>3:1.1:2.2:0.8<br>4:1.1:2.2:0.8 | 2<br>2<br>2 | 23<br>21<br>19 |
| Decision-Making | Breathing exercise to improve focus before making critical decisions. Useful for developers or managers. | B<br>M<br>A | 5:2:7:0<br>5:2:7:0<br>5:2:7:0 | 1<br>1<br>1 | 6<br>10<br>14 |
| Balancing | Designed for achieving inner balance. Useful for users needing a brief grounding exercise during hectic schedules. | B<br>M<br>A | 6:0:6:0<br>8:1:8:1<br>6:2:6:2 | 1<br>1<br>1 | 6<br>8<br>10 |
| Energizing | Breathing program to recharge energy levels and improve alertness. Ideal for users or professionals needing a quick boost of focus and energy. | B<br>M<br>A | 6:0:4:0<br>6:4:6:1<br>6:6:6:1 | 1<br>1<br>1 | 6<br>8<br>10 |
| Box | A breathing technique involving equal counts of inhaling, retaining, exhaling, and sustaining to enhance focus and calm. | B<br>M<br>A | 4:4:4:4<br>4:4:4:4<br>4:4:4:4 | 1<br>1<br>1 | 4<br>8<br>15 |
| Coherent | Breathing program for optimizing heart rate variability (HRV). It triggers the ideal breath rate for relaxation and emotional regulation. Useful for daily stress management or biofeedback training. | B<br>M<br>A | 5:0:5:0<br>5:0:5:0<br>5:0:5:0 | 1<br>1<br>1 | 30<br>50<br>70 |
| Fire | Agnisar Pranayama, or Fire Breath, is a powerful yoga breathing technique, Agni means fire, and Sara stands for movement; Agnisara Kriya is the process of cleansing or purifying the organs of the abdomen, by the heat (fire) generated during the practice of this breathing technique. | B<br>M<br>A | 3:0:5:12<br>3:0:5:12<br>3:0:5:12 | 1<br>1<br>1 | 10<br>20<br>30 |
| Retention | Kevala Kumbhaka, or Breath Retention, is an advanced yogic practice involving holding the breath after exhalation. This technique promotes deep inner stillness, enhances concentration, and balances the nervous system, making it a powerful tool for achieving mental clarity and heightened awareness. | B<br>M<br>A | 2:0:2:12<br>2:0:2:12<br>2:0:2:12 | 1<br>1<br>1 | 5<br>10<br>15 |
| Swooning | It is a calming yogic breathing technique that promotes relaxation and heightened consciousness. By extending breath retention and focusing inward, it helps to quiet the mind, reduce stress, and promote mental stillness. | B<br>M<br>A | 5:10:7:0<br>5:10:7:0<br>5:10:7:0 | 1<br>1<br>1 | 7<br>12<br>21 |
**Notes**:
- *I: Inhale, R: Retain, E: Exhale, S: Sustain, B: Beginner, M: Medium, A: Advanced*
================================================
FILE: README.md
================================================
<div align="center">
<img src="https://github.com/sepandhaghighi/nafas/raw/master/otherfiles/logo.png" width=320px>
<h1>Nafas: A Breathing Gymnastics Application</h1>
<hr>
<a href="https://badge.fury.io/py/nafas"><img src="https://badge.fury.io/py/nafas.svg" alt="PyPI version"></a>
<a href="https://codecov.io/gh/sepandhaghighi/nafas"><img src="https://codecov.io/gh/sepandhaghighi/nafas/branch/master/graph/badge.svg" alt="Codecov"></a>
<a href="https://www.python.org/"><img src="https://img.shields.io/badge/built%20with-Python3-green.svg" alt="built with Python3"></a>
<a href="https://github.com/sepandhaghighi/nafas"><img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/sepandhaghighi/nafas"></a>
<a href="https://discord.gg/CtZUNKJHP4"><img src="https://img.shields.io/discord/901570530145107978.svg" alt="Discord Channel"></a>
</div>
## Overview
Breathing gymnastics is a system of breathing exercises that focuses on the treatment of various diseases and general health promotion.
**Nafas** is a collection of breathing gymnastics designed to reduce the exhaustion of long working hours.
With multiple breathing patterns, **Nafas** helps you find your way to a detoxified energetic workday and also improves your concentration by increasing the oxygen level.
No need to walk away to take a break, just sit comfortably, run **Nafas** and let the journey begin.
**Nafas** means breath in Persian.
**Nafas** offers a selection of predefined breathing exercise programs.
Each program consists of multiple cycles.
The exercises begin with a gentle preparation phase to help users settle in and focus, followed by a series of timed inhales and exhales.
Between these breaths, the programs incorporate deliberate pauses that allow users to retain and sustain their breath.
These cycles aim to enhance both physical and mental well-being.
<div align="center">
<img src="https://github.com/sepandhaghighi/nafas/raw/master/otherfiles/overview.png" width=300px alt="Nafas Programs' Cycle">
<p>Nafas Programs' Cycle</p>
</div>
<table>
<tr>
<td align="center">Open Hub</td>
<td align="center"><a href="https://www.openhub.net/p/nafas"><img src="https://www.openhub.net/p/nafas/widgets/project_thin_badge.gif"></a></td>
</tr>
<tr>
<td align="center">PyPI Counter</td>
<td align="center"><a href="http://pepy.tech/project/nafas"><img src="http://pepy.tech/badge/nafas"></a></td>
</tr>
<tr>
<td align="center">Github Stars</td>
<td align="center"><a href="https://github.com/sepandhaghighi/nafas"><img src="https://img.shields.io/github/stars/sepandhaghighi/nafas.svg?style=social&label=Stars"></a></td>
</tr>
</table>
<table>
<tr>
<td align="center">Branch</td>
<td align="center">master</td>
<td align="center">dev</td>
</tr>
<tr>
<td align="center">CI</td>
<td align="center"><img src="https://github.com/sepandhaghighi/nafas/actions/workflows/test.yml/badge.svg?branch=master"></td>
<td align="center"><img src="https://github.com/sepandhaghighi/nafas/actions/workflows/test.yml/badge.svg?branch=dev"></td>
</tr>
</table>
<table>
<tr>
<td align="center">Code Quality</td>
<td align="center"><a href="https://app.codacy.com/gh/sepandhaghighi/nafas/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade"><img src="https://app.codacy.com/project/badge/Grade/b2be5ce052bc43a89713ac4aec6f8d11"/></a></td>
<td align="center"><a href="https://www.codefactor.io/repository/github/sepandhaghighi/nafas"><img src="https://www.codefactor.io/repository/github/sepandhaghighi/nafas/badge" alt="CodeFactor" /></a></td>
</tr>
</table>
## Installation
### Source Code
- Download [Version 1.5](https://github.com/sepandhaghighi/nafas/archive/v1.5.zip) or [Latest Source](https://github.com/sepandhaghighi/nafas/archive/dev.zip)
- `pip install .`
### PyPI
- Check [Python Packaging User Guide](https://packaging.python.org/installing/)
- `pip install nafas==1.5`
## Usage
ℹ️ You can use `nafas`, `python -m nafas` to run this program
ℹ️ Checkout the available programs in [PROGRAMS.md](https://github.com/sepandhaghighi/nafas/blob/master/PROGRAMS.md)
### Version
```console
nafas --version
```
### Basic
```console
nafas
```
### Silent Mode
ℹ️ This mode will disable the sound playing system
```console
nafas --silent
```
### Speaker
⚠️ This mode may not be supported on all systems
ℹ️ Customize your experience by choosing from a set of speaker voices to guide you through the exercises
ℹ️ The default speaker is `us1`
ℹ️ You can specify the speaker using the `--speaker`:
```console
nafas --speaker=us1
```
Choose your speaker from the following list:
| ID | Description |
|:--:|:-----------:|
| `us1` | Feminine voice with a US accent |
| `us2` | Masculine voice with a US accent |
| `in1` | Feminine voice with an Indian accent |
| `in2` | Masculine voice with an Indian accent |
| `cn1` | Feminine voice with a Chinese accent |
| `cn2` | Masculine voice with a Chinese accent |
| `ca1` | Feminine voice with a Canadian accent |
| `ca2` | Masculine voice with a Canadian accent |
| `au1` | Feminine voice with an Australian accent |
| `au2` | Masculine voice with an Australian accent |
| `uk1` | Feminine voice with a British accent |
| `uk2` | Masculine voice with a British accent |
### Skip Intro
ℹ️ This mode will skip the introduction
```console
nafas --skip-intro
```
### Color
⚠️ This mode may not be supported on all systems
ℹ️ This mode will change the text color
ℹ️ Valid choices: [`black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `lightblack`, `lightred`, `lightgreen`, `lightyellow`, `lightblue`, `lightmagenta`, `lightcyan`, `lightwhite`]
ℹ️ The default color is `white`
```console
nafas --color="red"
```
### Background Color
⚠️ This mode may not be supported on all systems
ℹ️ This mode will change the background color
ℹ️ Valid choices: [`black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `lightblack`, `lightred`, `lightgreen`, `lightyellow`, `lightblue`, `lightmagenta`, `lightcyan`, `lightwhite`]
ℹ️ The default background color is `black`
```console
nafas --color="red" --bg-color="blue"
```
### Intensity
⚠️ This mode may not be supported on all systems
ℹ️ This mode will change the text intensity
ℹ️ Valid choices: [`normal`, `bright`, `dim`]
ℹ️ The default intensity is `normal`
```console
nafas --color="red" --intensity="bright"
```
### Custom Config
ℹ️ Users can either generate a starter configuration file or load their own custom configurations
#### Generate a Starter Config
ℹ️ This will create a base configuration file at the specified path
```console
nafas --generate-config="new_program.json"
```
#### Load an Existing Config
```console
nafas --config="program1.json"
```
Config example:
```json
{
"name": "program1",
"unit": 2,
"pre": 3,
"cycle": 10,
"ratio": {
"inhale": 2,
"exhale": 2,
"retain": 3,
"sustain": 4
}
}
```
## Screen Record
<div align="center">
<img src="https://github.com/sepandhaghighi/nafas/raw/master/otherfiles/help.gif" alt="Screen Record">
</div>
## Issues & Bug Reports
Just fill an issue and describe it. We'll check it ASAP!
- Please complete the issue template
You can also join our discord server
<a href="https://discord.gg/CtZUNKJHP4">
<img src="https://img.shields.io/discord/901570530145107978.svg?style=for-the-badge" alt="Discord Channel">
</a>
## References
<blockquote>1- <a href="https://pranabreath.info">Prana Breath</a> </blockquote>
<blockquote>2- Rickard, Kathleen Benjamin, Dorothy J. Dunn, and Virginia M. Brouch. "Breathing techniques associated with improved health outcomes." (2015). </blockquote>
<blockquote>3- Zaccaro, Andrea, Andrea Piarulli, Marco Laurino, Erika Garbella, Danilo Menicucci, Bruno Neri, and Angelo Gemignani. "How breath-control can change your life: a systematic review on psycho-physiological correlates of slow breathing." Frontiers in human neuroscience 12 (2018): 353. </blockquote>
<blockquote>4- Brook, Robert D., Lawrence J. Appel, Melvyn Rubenfire, Gbenga Ogedegbe, John D. Bisognano, William J. Elliott, Flavio D. Fuchs et al. "Beyond medications and diet: alternative approaches to lowering blood pressure: a scientific statement from the American Heart Association." Hypertension 61, no. 6 (2013): 1360-1383. </blockquote>
<blockquote>5- Russo, Marc A., Danielle M. Santarelli, and Dean O’Rourke. "The physiological effects of slow breathing in the healthy human." Breathe 13, no. 4 (2017): 298-309. </blockquote>
<blockquote>6- Bujatti, M., and P. Biederer. "Serotonin, noradrenaline, dopamine metabolites in transcendental meditation-technique." Journal of Neural Transmission 39, no. 3 (1976): 257-267. </blockquote>
<blockquote>7- Martarelli, Daniele, Mario Cocchioni, Stefania Scuri, and Pierluigi Pompei. "Diaphragmatic breathing reduces exercise-induced oxidative stress." Evidence-Based Complementary and Alternative Medicine 2011 (2011). </blockquote>
<blockquote>8- <a href="https://www.drweil.com/videos-features/videos/breathing-exercises-4-7-8-breath/">DrWeil, Integrative Medicine & Healthy Living</a> </blockquote>
<blockquote>9- <a href="https://www.hgi.org.uk/resources/delve-our-extensive-library/resources-and-techniques/7-11-breathing-how-does-deep">Human Givens Institute</a> </blockquote>
<blockquote>10- <a href="https://www.inc.com/mithu-storoni/this-2-minute-breathing-exercise-can-help-you-make-better-decisions-according-to-a-new-study.html">This 2-Minute Breathing Exercise Can Help You Make Better Decisions</a> </blockquote>
<blockquote>11- <a href="https://k12.thoughtfullearning.com/minilesson/using-5-5-5-breathing-calm-down">Using 5-5-5 Breathing to Calm Down</a> </blockquote>
<blockquote>12- <a href="https://ttsmp3.com/">Free Text-To-Speech and Text-to-MP3 for US English</a> </blockquote>
<blockquote>13- <a href="https://www.yogabasics.com/practice/pranayama/">Pranayama Breathing Techniques and Tips</a> </blockquote>
<blockquote>14- <a href="https://health.clevelandclinic.org/box-breathing-benefits">Box Breathing Benefits and Techniques</a> </blockquote>
<blockquote>15- <a href="https://www.medicalnewstoday.com/articles/321805#how-to-do-it">Box breathing: How to do it, benefits, and tips</a> </blockquote>
<blockquote>16- <a href="https://pubmed.ncbi.nlm.nih.gov/24380741/">Breathing at a rate of 5.5 breaths per minute with equal inhalation-to-exhalation ratio increases heart rate variability</a> </blockquote>
<blockquote>17- <a href="https://www.youtube.com/watch?v=dPkpW5lqL3E">Coherent Breathing Timer - 6 Breaths Per Minute | 5 Seconds in / 5 Seconds Out | With Bells</a> </blockquote>
<blockquote>18- <a href="https://play.google.com/store/apps/details?id=pranayama.yoga.breathingexercises&hl=en">Pranayama : Breathing Exercise</a></blockquote>
## Cite
If you use **Nafas** in your research, we would appreciate citations to the following paper:
[Sabouri, Sadra, and Sepand Haghighi. "Nafas: Breathing Gymnastics Application." *arXiv preprint arXiv:2412.04667* (2024).](https://arxiv.org/abs/2412.04667)
```bibtex
@article{sabouri2024nafas,
title={Nafas: Breathing Gymnastics Application},
author={Sabouri, Sadra and Haghighi, Sepand},
journal={arXiv preprint arXiv:2412.04667},
year={2024}
}
```
## Show Your Support
<h3>Star This Repo</h3>
Give a ⭐️ if this project helped you!
<h3>Donate to Our Project</h3>
<h4>Bitcoin</h4>
1KtNLEEeUbTEK9PdN6Ya3ZAKXaqoKUuxCy
<h4>Ethereum</h4>
0xcD4Db18B6664A9662123D4307B074aE968535388
<h4>Litecoin</h4>
Ldnz5gMcEeV8BAdsyf8FstWDC6uyYR6pgZ
<h4>Doge</h4>
DDUnKpFQbBqLpFVZ9DfuVysBdr249HxVDh
<h4>Tron</h4>
TCZxzPZLcJHr2qR3uPUB1tXB6L3FDSSAx7
<h4>Ripple</h4>
rN7ZuRG7HDGHR5nof8nu5LrsbmSB61V1qq
<h4>Binance Coin</h4>
bnb1zglwcf0ac3d0s2f6ck5kgwvcru4tlctt4p5qef
<h4>Tether</h4>
0xcD4Db18B6664A9662123D4307B074aE968535388
<h4>Dash</h4>
Xd3Yn2qZJ7VE8nbKw2fS98aLxR5M6WUU3s
<h4>Stellar</h4>
GALPOLPISRHIYHLQER2TLJRGUSZH52RYDK6C3HIU4PSMNAV65Q36EGNL
<h4>Zilliqa</h4>
zil1knmz8zj88cf0exr2ry7nav9elehxfcgqu3c5e5
<h4>Coffeete</h4>
<a href="http://www.coffeete.ir/opensource">
<img src="http://www.coffeete.ir/images/buttons/lemonchiffon.png" style="width:260px;" />
</a>
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
| Version | Supported |
| ------------- | ------------------ |
| 1.5 | :white_check_mark: |
| < 1.5 | :x: |
## Reporting a Vulnerability
Please report security vulnerabilities by email to [me@sepand.tech](mailto:me@sepand.tech "me@sepand.tech").
If the security vulnerability is accepted, a dedicated bugfix release will be issued as soon as possible (depending on the complexity of the fix).
================================================
FILE: autopep8.bat
================================================
python -m autopep8 nafas --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
python -m autopep8 setup.py --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
python -m autopep8 otherfiles --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
================================================
FILE: autopep8.sh
================================================
#!/bin/sh
python -m autopep8 nafas --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
python -m autopep8 setup.py --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
python -m autopep8 otherfiles --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --verbose
================================================
FILE: build_exe.bat
================================================
@echo off
FOR /F "tokens=* USEBACKQ" %%F IN (`python --version`) DO (
SET py_version_str=%%F
)
SET py_version=%py_version_str:~7%
echo Your Python Version : %py_version%
echo Recommended Python Version : ^>= 3.7
echo -----
echo -----
python -m PyInstaller NAFAS.spec
pause
================================================
FILE: codecov.yml
================================================
codecov:
require_ci_to_pass: yes
coverage:
precision: 2
round: up
range: "70...100"
status:
patch:
default:
enabled: no
project:
default:
threshold: 1%
================================================
FILE: dev-requirements.txt
================================================
art==6.5
nava==0.8
colorama==0.4.6
pytest>=4.3.1
pytest-cov>=2.6.1
setuptools>=40.8.0
vulture>=1.0
bandit>=1.5.1
pydocstyle>=6.3.0
================================================
FILE: nafas/__init__.py
================================================
# -*- coding: utf-8 -*-
"""nafas modules."""
from nafas.params import NAFAS_VERSION
__version__ = NAFAS_VERSION
================================================
FILE: nafas/__main__.py
================================================
# -*- coding: utf-8 -*-
"""nafas main."""
import sys
import webbrowser
import argparse
from nafas.functions import print_nafas_description, get_standard_input, filter_input
from nafas.functions import get_program_data, print_program_details, run_program, clear_screen
from nafas.functions import generate_config, load_config, get_rendered_survey_link, print_line
from nafas.functions import set_color, set_bg_color, set_intensity
from nafas.params import NAFAS_VERSION, EXIT_MESSAGE
from nafas.params import CONFIG_GENERATE_SUCCESS_MESSAGE, CONFIG_GENERATE_ERROR_MESSAGE, CONFIG_LOAD_ERROR_MESSAGE
from nafas.params import SURVEY_MESSAGE_1, SURVEY_MESSAGE_2
from nafas.params import SPEAKER_LIST, COLOR_LIST, INTENSITY_LIST
from art import tprint
def parse_args() -> argparse.Namespace:
"""Parse arguments."""
parser = argparse.ArgumentParser()
parser.add_argument('--version', help='version', nargs="?", const=1)
parser.add_argument('--silent', help='silent mode', nargs="?", const=1)
parser.add_argument('--skip-intro', help='skip intro', nargs="?", const=1)
parser.add_argument(
'--generate-config',
help='generate a starter configuration file at the given path',
type=str)
parser.add_argument('--config', help='load an existing configuration file from the given path', type=str)
parser.add_argument(
'--speaker',
help='speaker id',
choices=SPEAKER_LIST,
default=SPEAKER_LIST[0],
type=str.lower)
parser.add_argument('--color', help='text color', type=str.lower, choices=COLOR_LIST)
parser.add_argument('--bg-color', help='background color', type=str.lower, choices=COLOR_LIST)
parser.add_argument('--intensity', help='text intensity', type=str.lower, choices=INTENSITY_LIST)
args = parser.parse_args()
return args
def run(args: argparse.Namespace) -> None:
"""
Run nafas CLI.
:param args: arguments
"""
set_color(args.color)
set_bg_color(args.bg_color)
set_intensity(args.intensity)
silent_flag = args.silent
if args.version:
print(NAFAS_VERSION)
elif args.generate_config:
result = generate_config(args.generate_config)
if not result:
print(CONFIG_GENERATE_ERROR_MESSAGE)
else:
print(CONFIG_GENERATE_SUCCESS_MESSAGE)
sys.exit()
else:
clear_screen()
if not args.skip_intro:
tprint("Nafas")
tprint("v" + str(NAFAS_VERSION))
if silent_flag:
tprint("Silent Mode")
print_nafas_description()
_ = input("Press any key to continue.\n")
exit_flag = False
while not exit_flag:
if args.config:
result = load_config(args.config)
if result["status"]:
data = result["data"]
program_name, level, program_data = data["program_name"], data["program_level"], data["program_data"]
else:
print(CONFIG_LOAD_ERROR_MESSAGE)
sys.exit()
else:
input_data = get_standard_input()
filtered_data = filter_input(input_data)
program_name, level, program_data = get_program_data(filtered_data)
clear_screen()
print_program_details(program_name, level, program_data)
run_program(program_data, args.speaker, silent=silent_flag)
print_line()
survey_link = get_rendered_survey_link(program_name, level, program_data)
print(SURVEY_MESSAGE_1)
print("Survey Link: {link}\n".format(link=survey_link))
if input(SURVEY_MESSAGE_2).lower() == "y":
webbrowser.open(survey_link)
INPUTINDEX = str(
input("Press [R] to restart or any other key to exit."))
if INPUTINDEX.upper() != "R":
exit_flag = True
print(EXIT_MESSAGE)
else:
clear_screen()
def main() -> None:
"""CLI main function."""
try:
args = parse_args()
run(args)
except (KeyboardInterrupt, EOFError):
print(EXIT_MESSAGE)
sys.exit(1)
if __name__ == "__main__":
main()
================================================
FILE: nafas/functions.py
================================================
# -*- coding: utf-8 -*-
"""nafas functions."""
from typing import Dict, List, Tuple
from typing import Generator, Callable, Any, Union, Optional
import time
import json
from nafas.params import NAFAS_LINKS, NAFAS_DESCRIPTION, NAFAS_TIPS, NAFAS_CAUTIONS
from nafas.params import STANDARD_MENU, STANDARD_MENU_ORDER, STEP_MAP
from nafas.params import PROGRAMS, PROGRAM_DETAILS, SOUND_MAP, STEP_TEMPLATE, CYCLE_TEMPLATE
from nafas.params import SOUND_WARNING_MESSAGE, EXIT_MESSAGE, BAD_INPUT_MESSAGE, PROGRAM_END_MESSAGE
from nafas.params import MINUTES_TEMPLATE, SECONDS_TEMPLATE, PROGRAM_TIME_TEMPLATE
from nafas.params import MENU_TEMPLATE_1, MENU_TEMPLATE_2
from nafas.params import CONFIG_VALIDATION_MAP, CONFIG_EXAMPLE
from nafas.params import SURVEY_LINK_TEMPLATE, SURVEY_DATA_TEMPLATE
from nafas.params import NAFAS_VERSION
import nava
from colorama import Fore, Back, Style
import os
from warnings import warn
import sys
def print_line(num: int = 70, char: str = "#") -> None:
"""
Print line.
:param num: number of characters
:param char: character
"""
print(num * char)
def is_int(number: Union[int, float]) -> bool:
"""
Check that input number is int or not.
:param number: input number
"""
if int(number) == number:
return True
return False
def calculate_bpm(program_data: Dict[str, Any]) -> float:
"""
Calculate Breaths Per Minute (BPM).
:param program_data: program data
"""
total_time_per_breath = sum(program_data["ratio"]) * program_data["unit"]
bpm = round(60 / total_time_per_breath, 2)
if is_int(bpm):
bpm = int(bpm)
return bpm
def calculate_time(program_data: Dict[str, Any]) -> float:
"""
Calculate and return the program time.
:param program_data: program data
"""
result = sum(program_data["ratio"]) * program_data["unit"] * \
program_data["cycle"] + program_data["pre"]
return result
def calculate_average_time(program_data: Dict[str, Any]) -> float:
"""
Calculate and return average time of a program in all levels.
:param program_data: program data in all levels
"""
result = 0
level_number = len(program_data)
for program in program_data.values():
result += calculate_time(program)
return result / level_number
def convert_time(input_time: float, average: bool = False) -> str:
"""
Convert input time from sec to MM,SS format.
:param input_time: input time in sec
:param average: average flag
"""
sec = float(input_time)
_days, sec = divmod(sec, 24 * 3600)
_hours, sec = divmod(sec, 3600)
minutes, sec = divmod(sec, 60)
result = ", ".join([
MINUTES_TEMPLATE.format(minutes=minutes),
SECONDS_TEMPLATE.format(seconds=sec),
])
if average:
if sec >= 30:
minutes += 1
result = MINUTES_TEMPLATE.format(minutes=minutes).lstrip("0")
return result
def get_rendered_survey_link(program_name: str, level: str, program_data: Dict[str, Any]) -> str:
"""
Get rendered survey link.
:param program_name: program name
:param level: program level
:param program_data: program data
"""
data = SURVEY_DATA_TEMPLATE.format(
program_name=program_name,
level=level,
ratio_rendered="+%5B" + ",+".join([str(item) for item in program_data['ratio']]) + "%5D",
program_data_unit=program_data['unit'],
program_data_pre=program_data['pre'],
program_data_cycle=program_data['cycle'],
)
return SURVEY_LINK_TEMPLATE.format(data=data, version=NAFAS_VERSION)
def justify_left(words: List[str], width: int) -> str:
"""
Left justify words.
:param words: list of words
:param width: width of each line
"""
return ' '.join(words).ljust(width)
def justify_text(words: List[str], width: int) -> Generator[str, None, None]:
"""
Justify input words.
:param words: list of words
:param width: width of each line
"""
line = []
col = 0
for word in words:
if line and col + len(word) > width:
if len(line) == 1:
yield justify_left(line, width)
else:
# After n + 1 spaces are placed between each pair of
# words, there are r spaces left over; these result in
# wider spaces at the left.
n, r = divmod(width - col + 1, len(line) - 1)
narrow = ' ' * (n + 1)
if r == 0:
yield narrow.join(line)
else:
wide = ' ' * (n + 2)
yield wide.join(line[:r] + [narrow.join(line[r:])])
line, col = [], 0
line.append(word)
col += len(word) + 1
if line:
yield justify_left(line, width)
def check_sound() -> bool:
"""Check sound playing device, return True if sound device is available."""
sound_path = get_sound_path(SOUND_MAP['Silence'])
try:
nava.play(sound_path)
return True
except Exception:
warn(SOUND_WARNING_MESSAGE, RuntimeWarning)
return False
def print_nafas_description() -> None:
"""Print Nafas description."""
print(NAFAS_LINKS)
print_line()
print("\n".join(justify_text(NAFAS_DESCRIPTION.split(), 100)))
print_line()
print(NAFAS_TIPS)
print_line()
print(NAFAS_CAUTIONS)
def print_program_details(program_name: str, level: str, program_data: Dict[str, Any]) -> None:
"""
Print program details.
:param program_name: program name
:param level: program level
:param program_data: program data
"""
cycle = program_data["cycle"]
ratio = program_data["ratio"]
unit = program_data["unit"]
sequence = []
for index, item in enumerate(ratio):
sequence.append("{step}({ratio})".format(step=STEP_MAP[index], ratio=item))
sequence = ", ".join(sequence)
total_time = calculate_time(program_data)
bpm = calculate_bpm(program_data)
print_line()
print(
PROGRAM_DETAILS.format(
name=program_name,
level=level,
cycles=str(cycle),
unit=str(unit),
total_time=convert_time(total_time),
bpm=bpm,
sequence=sequence))
print_line()
time.sleep(1)
def filter_input(input_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Filter input data.
:param input_data: input data
"""
filtered_data = input_data.copy()
if filtered_data["program"] not in STANDARD_MENU["program"]:
filtered_data["program"] = 1
if filtered_data["level"] not in STANDARD_MENU["level"]:
filtered_data["level"] = 1
return filtered_data
def load_config(config_path: str) -> Dict[str, Any]:
"""
Load configuration from a JSON file.
:param config_path: config path
"""
try:
with open(config_path, 'r') as config_file:
config_data = json.load(config_file)
if not validate_config(config_data):
raise Exception
program_data = {
'ratio': [
config_data['ratio']['inhale'],
config_data['ratio']['retain'],
config_data['ratio']['exhale'],
config_data['ratio']['sustain']
],
'unit': config_data['unit'],
'pre': config_data['pre'],
'cycle': config_data['cycle'],
}
return {
"status": True,
"data": {
"program_name": config_data['name'],
"program_level": "Custom",
"program_data": program_data
}
}
except Exception:
return {"status": False, "data": dict()}
def generate_config(config_path: str) -> bool:
"""
Generate a starter configuration file.
:param config_path: config path
"""
try:
with open(config_path, 'w') as config_file:
json.dump(CONFIG_EXAMPLE, config_file)
return True
except Exception:
return False
def validate_config(config_data: Dict[str, Any]) -> bool:
"""
Validate config data. Return True if config data is valid.
:param config_data: config data
"""
result = []
for item1 in CONFIG_VALIDATION_MAP:
if item1 not in config_data:
return False
if isinstance(CONFIG_VALIDATION_MAP[item1], dict):
for item2 in CONFIG_VALIDATION_MAP[item1]:
if item2 not in config_data[item1]:
return False
result.append(isinstance(config_data[item1][item2], CONFIG_VALIDATION_MAP[item1][item2]))
else:
result.append(isinstance(config_data[item1], CONFIG_VALIDATION_MAP[item1]))
return all(result)
def get_standard_input(input_func: Callable = input) -> Dict[str, Any]:
"""
Get inputs from user.
:param input_func : input function
"""
input_data = {"program": 1, "level": 1}
for item in STANDARD_MENU_ORDER:
exit_flag = False
sorted_list = sorted(STANDARD_MENU[item])
print(MENU_TEMPLATE_1.format(item=item))
for i in sorted_list:
if item == "program":
program_name = STANDARD_MENU[item][i]
program_average_time = calculate_average_time(PROGRAMS[program_name])
print(
PROGRAM_TIME_TEMPLATE.format(
index=i,
name=program_name,
average_time=convert_time(
program_average_time,
True)))
else:
print(MENU_TEMPLATE_2.format(index=i, item=STANDARD_MENU[item][i]))
while not exit_flag:
try:
input_data[item] = int(input_func(""))
exit_flag = True
except (KeyboardInterrupt, EOFError):
print("\n" + EXIT_MESSAGE)
sys.exit()
except Exception:
print(BAD_INPUT_MESSAGE)
return input_data
def get_program_data(input_data: Dict[str, Any]) -> Tuple:
"""
Get program data as program name, level and program data.
:param input_data: input data
"""
program_name = STANDARD_MENU["program"][input_data["program"]]
level = STANDARD_MENU["level"][input_data["level"]]
return program_name, level, PROGRAMS[program_name][level]
def get_sound_path(sound_name: str, speaker_id: Optional[str] = None) -> str:
"""
Return direct sound path.
:param sound_name: .wav sound name
:param speaker_id: speaker id
"""
cd, _ = os.path.split(__file__)
if speaker_id is None:
return os.path.join(cd, "sounds", sound_name)
return os.path.join(cd, "sounds", speaker_id, sound_name)
def set_color(color: str) -> None:
"""
Set text color.
:param color: color name
"""
if color:
color = color.strip().upper()
if color.startswith("LIGHT"):
color += "_EX"
print(getattr(Fore, color, ""), end="")
def set_bg_color(bg_color: str) -> None:
"""
Set background color.
:param bg_color: background color name
"""
if bg_color:
bg_color = bg_color.strip().upper()
if bg_color.startswith("LIGHT"):
bg_color += "_EX"
print(getattr(Back, bg_color, ""))
def set_intensity(intensity: str) -> None:
"""
Set text intensity.
:param intensity: intensity name
"""
if intensity:
intensity = intensity.strip().upper()
print(getattr(Style, intensity, ""), end="")
def graphic_counter(delay_time: float) -> None:
"""
Print dots during cycles.
:param delay_time: delay time
"""
for _ in range(int(delay_time)):
time.sleep(1)
print('.', end=' ', flush=True)
remain_time = delay_time - int(delay_time)
time.sleep(remain_time)
if remain_time != 0:
print('.', end=' ', flush=True)
print("")
def play_sound(sound_path: str, enable: bool = True) -> None:
"""
Play inputted sound file.
:param sound_path: sound path
:param enable: enable flag
"""
if enable:
_ = nava.play(sound_path, async_mode=True)
def run_program(program_data: Dict[str, Any], speaker_id: str, silent: bool = False) -> None:
"""
Run program.
:param program_data: program data
:param speaker_id: speaker id
:param silent: silent mode flag
"""
check_sound_flag = False
if not silent:
check_sound_flag = check_sound()
cycle = program_data["cycle"]
ratio = program_data["ratio"]
unit = program_data["unit"]
pre = program_data["pre"]
print("Preparing ", end="", flush=True)
play_sound(get_sound_path(SOUND_MAP['Prepare'], speaker_id), enable=check_sound_flag)
graphic_counter(pre)
print_line()
time.sleep(1)
play_sound(get_sound_path(SOUND_MAP['Start'], speaker_id), enable=check_sound_flag)
print("Start", flush=True)
time.sleep(1)
print_line()
time.sleep(1)
for i in range(cycle):
print(CYCLE_TEMPLATE.format(cycle=str(i + 1), remaining=str(cycle - i - 1)))
time.sleep(1)
for index, item in enumerate(ratio):
if item != 0:
item_name = STEP_MAP[index]
play_sound(get_sound_path(SOUND_MAP[item_name], speaker_id), enable=check_sound_flag)
print(
STEP_TEMPLATE.format(
step=item_name,
time=str(unit * item)), flush=True)
graphic_counter(item * unit)
time.sleep(1)
print_line()
play_sound(get_sound_path(SOUND_MAP['End'], speaker_id), enable=check_sound_flag)
print(PROGRAM_END_MESSAGE, flush=True)
time.sleep(2)
def clear_screen() -> None:
"""Clear screen."""
if sys.platform == "win32":
os.system('cls')
else:
os.system('clear')
================================================
FILE: nafas/params.py
================================================
# -*- coding: utf-8 -*-
"""nafas parameters."""
NAFAS_VERSION = "1.5"
NAFAS_DESCRIPTION = """
Breathing gymnastics is a system of breathing exercises that focuses on the treatment of various diseases and general health promotion.
Nafas is a collection of breathing gymnastics designed to reduce the exhaustion of long working hours.
With multiple breathing patterns, Nafas helps you find your way to a detoxified energetic workday and also improves your concentration by increasing the oxygen level.
No need to walk away to take a break, just sit comfortably, run Nafas and let the journey begin.
"""
NAFAS_LINKS = """
Repository: https://github.com/sepandhaghighi/nafas
Paper: https://arxiv.org/abs/2412.04667
* If you use Nafas in your research, please cite our paper
"""
NAFAS_TIPS = """
Breathing tips:
1. Inhaling is only done through the nose
2. Exhaling, you can use both nose and mouth
3. When exhaling through your mouth, it is recommended to fold the lips
"""
NAFAS_CAUTIONS = """
Cautions:
1. If you have any breathing or respiratory issues, consult your doctor before using Nafas
2. If you have asthma or high blood pressure should not hold the breath
3. If you feel dizzy, nauseous, or lightheaded stop practicing and rest
"""
SOUND_WARNING_MESSAGE = "Your device is not compatible with our underlying sound-playing library. You can refer to https://github.com/openscilab/nava."
EXIT_MESSAGE = "See you. Bye!"
BAD_INPUT_MESSAGE = "[Error] Bad input!"
CONFIG_LOAD_ERROR_MESSAGE = "[Error] Failed to load the configuration file!"
CONFIG_GENERATE_ERROR_MESSAGE = "[Error] Failed to generate the configuration file!"
CONFIG_GENERATE_SUCCESS_MESSAGE = "Configuration file generated successfully!"
PROGRAM_END_MESSAGE = "Well done!"
SURVEY_LINK_TEMPLATE = "https://opsclb.li/nafas/form?version={version}&data={data}"
SURVEY_DATA_TEMPLATE = "%7B\"name\":+\"{program_name}\",+\"level\":+\"{level}\",+\"data\":+%7B\"ratio\":+{ratio_rendered},+\"unit\":+{program_data_unit},+\"pre\":+{program_data_pre},+\"cycle\":+{program_data_cycle}%7D%7D"
SURVEY_MESSAGE_1 = "We are conducting a study to evaluate the usability of Nafas. By taking the following survey, you will contribute to our research and help us improve Nafas. The survey takes less than 5 minutes to complete.\n"
SURVEY_MESSAGE_2 = "Do you want to participate in the survey? (Y/[N])"
PROGRAM_DETAILS = """Program Details:
Name : {name}
Level : {level}
Number of Cycles : {cycles}
Unit : {unit} seconds
Total Time : {total_time}
Breaths per Minute (BPM) : {bpm}
Sequence : {sequence}
"""
MINUTES_TEMPLATE = "{minutes:02.0f} minutes"
SECONDS_TEMPLATE = "{seconds:02.0f} seconds"
PROGRAM_TIME_TEMPLATE = "{index}- {name} (~ {average_time})"
MENU_TEMPLATE_1 = "- Choose a {item}: \n"
MENU_TEMPLATE_2 = "{index}- {item}"
CYCLE_TEMPLATE = "Cycle: {cycle} (Remaining: {remaining})"
STEP_TEMPLATE = "- {step} for {time} seconds"
STANDARD_MENU = {
"program": {
1: "Clear Mind",
2: "Relax1",
3: "Relax2",
4: "Relax3",
5: "Calming1",
6: "Calming2",
7: "Calming3",
8: "Power",
9: "Harmony",
10: "Anti-Stress",
11: "Anti-Appetite",
12: "Cigarette Replace",
13: "Decision-Making",
14: "Balancing",
15: "Energizing",
16: "Box",
17: "Coherent",
18: "Fire",
19: "Retention",
20: "Swooning"},
"level": {
1: "Beginner",
2: "Medium",
3: "Advanced"}}
STANDARD_MENU_ORDER = ["program", "level"]
STEP_MAP = {0: "Inhale", 1: "Retain", 2: "Exhale", 3: "Sustain"}
CLEAR_MIND_BEGINNER = {"ratio": [1, 0, 3, 0], "unit": 3, "pre": 3, "cycle": 35}
CLEAR_MIND_MEDIUM = {"ratio": [1, 0, 4, 0], "unit": 3, "pre": 3, "cycle": 28}
CLEAR_MIND_ADVANCED = {"ratio": [1, 0, 5, 0], "unit": 3, "pre": 3, "cycle": 24}
RELAX_BEGINNER = {"ratio": [1, 0, 2, 2], "unit": 3, "pre": 3, "cycle": 28}
RELAX_MEDIUM = {"ratio": [1, 0, 2, 3], "unit": 3, "pre": 3, "cycle": 24}
RELAX_ADVANCED = {"ratio": [1, 0, 2, 4], "unit": 3, "pre": 3, "cycle": 22}
CALMING_BEGINNER = {"ratio": [1, 2, 1, 2], "unit": 3, "pre": 3, "cycle": 24}
CALMING_MEDIUM = {"ratio": [1, 3, 1, 3], "unit": 3, "pre": 3, "cycle": 22}
CALMING_ADVANCED = {"ratio": [1, 4, 1, 4], "unit": 3, "pre": 3, "cycle": 20}
CALMING2_BEGINNER = {"ratio": [5, 0, 5, 5], "unit": 1, "pre": 3, "cycle": 4}
CALMING2_MEDIUM = {"ratio": [5, 0, 5, 5], "unit": 1, "pre": 3, "cycle": 6}
CALMING2_ADVANCED = {"ratio": [5, 0, 5, 5], "unit": 1, "pre": 3, "cycle": 8}
CALMING3_BEGINNER = {"ratio": [4, 0, 6, 0], "unit": 1, "pre": 3, "cycle": 6}
CALMING3_MEDIUM = {"ratio": [6, 1, 8, 4], "unit": 1, "pre": 3, "cycle": 8}
CALMING3_ADVANCED = {"ratio": [4, 1, 12, 1], "unit": 1, "pre": 3, "cycle": 10}
POWER_BEGINNER = {"ratio": [1, 2, 2, 0], "unit": 3, "pre": 3, "cycle": 28}
POWER_MEDIUM = {"ratio": [1, 3, 2, 0], "unit": 3, "pre": 3, "cycle": 24}
POWER_ADVANCED = {"ratio": [1, 4, 2, 0], "unit": 3, "pre": 3, "cycle": 20}
HARMONY_BEGINNER = {"ratio": [1, 3, 2, 1], "unit": 3, "pre": 3, "cycle": 20}
HARMONY_MEDIUM = {"ratio": [1, 4, 2, 1], "unit": 3, "pre": 3, "cycle": 18}
HARMONY_ADVANCED = {"ratio": [1, 5, 2, 1], "unit": 3, "pre": 3, "cycle": 16}
FIRE_BEGINNER = {"ratio": [3, 0, 5, 12], "unit": 1, "pre": 3, "cycle": 10}
FIRE_MEDIUM = {"ratio": [3, 0, 5, 12], "unit": 1, "pre": 3, "cycle": 20}
FIRE_ADVANCED = {"ratio": [3, 0, 5, 12], "unit": 1, "pre": 3, "cycle": 30}
RETENTION_BEGINNER = {"ratio": [2, 0, 2, 12], "unit": 1, "pre": 3, "cycle": 5}
RETENTION_MEDIUM = {"ratio": [2, 0, 2, 12], "unit": 1, "pre": 3, "cycle": 10}
RETENTION_ADVANCED = {"ratio": [2, 0, 2, 12], "unit": 1, "pre": 3, "cycle": 15}
SWOONING_BEGINNER = {"ratio": [5, 10, 7, 0], "unit": 1, "pre": 3, "cycle": 7}
SWOONING_MEDIUM = {"ratio": [5, 10, 7, 0], "unit": 1, "pre": 3, "cycle": 12}
SWOONING_ADVANCED = {"ratio": [5, 10, 7, 0], "unit": 1, "pre": 3, "cycle": 21}
RELAX2_BEGINNER = {
"ratio": [
4,
7,
8,
0],
"unit": 1,
"pre": 3,
"cycle": 4}
RELAX2_MEDIUM = {
"ratio": [
4,
7,
8,
0],
"unit": 1,
"pre": 3,
"cycle": 8}
RELAX2_ADVANCED = {
"ratio": [
4,
7,
8,
0],
"unit": 1,
"pre": 3,
"cycle": 12}
RELAX3_BEGINNER = {
"ratio": [
7,
0,
11,
0],
"unit": 1,
"pre": 3,
"cycle": 15}
RELAX3_MEDIUM = {
"ratio": [
7,
0,
11,
0],
"unit": 1,
"pre": 3,
"cycle": 20}
RELAX3_ADVANCED = {
"ratio": [
7,
0,
11,
0],
"unit": 1,
"pre": 3,
"cycle": 24}
ANTI_STRESS_BEGINNER = {
"ratio": [
3,
0,
0.66,
0],
"unit": 3,
"pre": 3,
"cycle": 20}
ANTI_STRESS_MEDIUM = {
"ratio": [
4,
0,
0.66,
0],
"unit": 3,
"pre": 3,
"cycle": 17}
ANTI_STRESS_ADVANCED = {
"ratio": [
5,
0,
0.66,
0],
"unit": 3,
"pre": 3,
"cycle": 14}
ANTI_APPETITE_BEGINNER = {
"ratio": [
5,
0,
5,
5],
"unit": 1,
"pre": 3,
"cycle": 40}
ANTI_APPETITE_MEDIUM = {
"ratio": [
6,
0,
5,
5],
"unit": 1,
"pre": 3,
"cycle": 38}
ANTI_APPETITE_ADVANCED = {
"ratio": [
7,
0,
5,
5],
"unit": 1,
"pre": 3,
"cycle": 36}
CIGARETTE_REPLACE_BEGINNER = {
"ratio": [
2,
1.1,
2.2,
0.8],
"unit": 2,
"pre": 3,
"cycle": 23}
CIGARETTE_REPLACE_MEDIUM = {
"ratio": [
3,
1.1,
2.2,
0.8],
"unit": 2,
"pre": 3,
"cycle": 21}
CIGARETTE_REPLACE_ADVANCED = {
"ratio": [
4,
1.1,
2.2,
0.8],
"unit": 2,
"pre": 3,
"cycle": 19}
DECISION_MAKING_BEGINNER = {
"ratio": [
5,
2,
7,
0],
"unit": 1,
"pre": 3,
"cycle": 6}
DECISION_MAKING_MEDIUM = {
"ratio": [
5,
2,
7,
0],
"unit": 1,
"pre": 3,
"cycle": 10}
DECISION_MAKING_ADVANCED = {
"ratio": [
5,
2,
7,
0],
"unit": 1,
"pre": 3,
"cycle": 14}
BALANCING_BEGINNER = {
"ratio": [
6,
0,
6,
0],
"unit": 1,
"pre": 3,
"cycle": 6}
BALANCING_MEDIUM = {
"ratio": [
8,
1,
8,
1],
"unit": 1,
"pre": 3,
"cycle": 8}
BALANCING_ADVANCED = {
"ratio": [
6,
2,
6,
2],
"unit": 1,
"pre": 3,
"cycle": 10}
ENERGIZING_BEGINNER = {
"ratio": [
6,
0,
4,
0],
"unit": 1,
"pre": 3,
"cycle": 6}
ENERGIZING_MEDIUM = {
"ratio": [
6,
4,
6,
1],
"unit": 1,
"pre": 3,
"cycle": 8}
ENERGIZING_ADVANCED = {
"ratio": [
6,
6,
6,
1],
"unit": 1,
"pre": 3,
"cycle": 10}
BOX_BEGINNER = {
"ratio": [
4,
4,
4,
4],
"unit": 1,
"pre": 3,
"cycle": 4}
BOX_MEDIUM = {
"ratio": [
4,
4,
4,
4],
"unit": 1,
"pre": 3,
"cycle": 8}
BOX_ADVANCED = {
"ratio": [
4,
4,
4,
4],
"unit": 1,
"pre": 3,
"cycle": 15}
COHERENT_BEGINNER = {
"ratio": [
5,
0,
5,
0],
"unit": 1,
"pre": 3,
"cycle": 30}
COHERENT_MEDIUM = {
"ratio": [
5,
0,
5,
0],
"unit": 1,
"pre": 3,
"cycle": 50}
COHERENT_ADVANCED = {
"ratio": [
5,
0,
5,
0],
"unit": 1,
"pre": 3,
"cycle": 70}
PROGRAMS = {"Clear Mind": {"Beginner": CLEAR_MIND_BEGINNER,
"Medium": CLEAR_MIND_MEDIUM,
"Advanced": CLEAR_MIND_ADVANCED},
"Relax1": {"Beginner": RELAX_BEGINNER,
"Medium": RELAX_MEDIUM,
"Advanced": RELAX_ADVANCED},
"Calming1": {"Beginner": CALMING_BEGINNER,
"Medium": CALMING_MEDIUM,
"Advanced": CALMING_ADVANCED},
"Calming2": {"Beginner": CALMING2_BEGINNER,
"Medium": CALMING2_MEDIUM,
"Advanced": CALMING2_ADVANCED},
"Calming3": {"Beginner": CALMING3_BEGINNER,
"Medium": CALMING3_MEDIUM,
"Advanced": CALMING3_ADVANCED},
"Power": {"Beginner": POWER_BEGINNER,
"Medium": POWER_MEDIUM,
"Advanced": POWER_ADVANCED},
"Anti-Stress": {"Beginner": ANTI_STRESS_BEGINNER,
"Medium": ANTI_STRESS_MEDIUM,
"Advanced": ANTI_STRESS_ADVANCED},
"Anti-Appetite": {"Beginner": ANTI_APPETITE_BEGINNER,
"Medium": ANTI_APPETITE_MEDIUM,
"Advanced": ANTI_APPETITE_ADVANCED},
"Cigarette Replace": {"Beginner": CIGARETTE_REPLACE_BEGINNER,
"Medium": CIGARETTE_REPLACE_MEDIUM,
"Advanced": CIGARETTE_REPLACE_ADVANCED},
"Harmony": {"Beginner": HARMONY_BEGINNER,
"Medium": HARMONY_MEDIUM,
"Advanced": HARMONY_ADVANCED},
"Relax2": {"Beginner": RELAX2_BEGINNER,
"Medium": RELAX2_MEDIUM,
"Advanced": RELAX2_ADVANCED},
"Relax3": {"Beginner": RELAX3_BEGINNER,
"Medium": RELAX3_MEDIUM,
"Advanced": RELAX3_ADVANCED},
"Decision-Making": {"Beginner": DECISION_MAKING_BEGINNER,
"Medium": DECISION_MAKING_MEDIUM,
"Advanced": DECISION_MAKING_ADVANCED},
"Balancing": {"Beginner": BALANCING_BEGINNER,
"Medium": BALANCING_MEDIUM,
"Advanced": BALANCING_ADVANCED},
"Energizing": {"Beginner": ENERGIZING_BEGINNER,
"Medium": ENERGIZING_MEDIUM,
"Advanced": ENERGIZING_ADVANCED},
"Box": {"Beginner": BOX_BEGINNER,
"Medium": BOX_MEDIUM,
"Advanced": BOX_ADVANCED},
"Coherent": {"Beginner": COHERENT_BEGINNER,
"Medium": COHERENT_MEDIUM,
"Advanced": COHERENT_ADVANCED},
"Fire": {"Beginner": FIRE_BEGINNER,
"Medium": FIRE_MEDIUM,
"Advanced": FIRE_ADVANCED},
"Retention": {"Beginner": RETENTION_BEGINNER,
"Medium": RETENTION_MEDIUM,
"Advanced": RETENTION_ADVANCED},
"Swooning": {"Beginner": SWOONING_BEGINNER,
"Medium": SWOONING_MEDIUM,
"Advanced": SWOONING_ADVANCED},
}
SPEAKER_LIST = [
"us1", "us2",
"in1", "in2",
"cn1", "cn2",
"ca1", "ca2",
"au1", "au2",
"uk1", "uk2",
]
SOUND_MAP = {
"Silence": "silence.wav",
"Start": "start.wav",
"Prepare": "preparing.wav",
"Inhale": "inhale.wav",
"Exhale": "exhale.wav",
"Retain": "retain.wav",
"Sustain": "sustain.wav",
"End": "well_done.wav",
}
CONFIG_VALIDATION_MAP = {
"name": str,
"ratio": {
"inhale": (int, float),
"exhale": (int, float),
"retain": (int, float),
"sustain": (int, float),
},
"unit": (int, float),
"pre": (int, float),
"cycle": int
}
CONFIG_EXAMPLE = {
"name": "unknown",
"unit": 1,
"pre": 3,
"cycle": 5,
"ratio": {
"inhale": 1,
"exhale": 1,
"retain": 1,
"sustain": 1
}
}
COLOR_LIST = [
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
"lightblack",
"lightred",
"lightgreen",
"lightyellow",
"lightblue",
"lightmagenta",
"lightcyan",
"lightwhite",
]
INTENSITY_LIST = ["normal", "bright", "dim"]
================================================
FILE: otherfiles/RELEASE.md
================================================
# Nafas Release Instructions
**Last Update: 2025-06-30**
1. Create the `release` branch under `dev`
2. Update all version tags
1. `setup.py`
2. `README.md`
3. `SECURITY.md`
4. `NAFAS.spec`
5. `otherfiles/version_check.py`
6. `nafas/params.py`
7. `otherfiles/Version.rc`
8. `test/test.py`
3. Update `CHANGELOG.md`
1. Add a new header under `Unreleased` section (Example: `## [0.1] - 2022-08-17`)
2. Add a new compare link to the end of the file (Example: `[0.2]: https://github.com/sepandhaghighi/nafas/compare/v0.1...v0.2`)
3. Update `dev` compare link (Example: `[Unreleased]: https://github.com/sepandhaghighi/nafas/compare/v0.2...dev`)
4. Update `.github/ISSUE_TEMPLATE/bug_report.yml`
1. Add new version tag to `Nafas version` dropbox options
5. Create a PR from `release` to `dev`
1. Title: `Version x.x` (Example: `Version 0.1`)
2. Tag all related issues
3. Labels: `release`
4. Set milestone
5. Wait for all CI pass
6. Need review (**1** reviewer)
7. Squash and merge
8. Delete `release` branch
6. Merge `dev` branch into `master`
1. `git checkout master`
2. `git merge dev`
3. `git push origin master`
4. Wait for all CI pass
7. Build EXE file
1. Run `build_exe.bat` (Use `Python >= 3.7`)
8. Create a new release
1. Target branch: `master`
2. Tag: `vx.x` (Example: `v0.1`)
3. Title: `Version x.x` (Example: `Version 0.1`)
4. Copy changelogs
5. Tag all related issues
6. Upload EXE file
9. Bump!!
10. Close this version issues
11. Close milestone
================================================
FILE: otherfiles/Version.rc
================================================
VSVersionInfo(
ffi=FixedFileInfo(
filevers=(1, 5, 0, 0),
prodvers=(1, 5, 0, 0),
mask=0x3f,
flags=0x0,
OS=0x40004,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
u'040904B0',
[StringStruct(u'CompanyName', u'Nafas Development Team'),
StringStruct(u'FileDescription', u'NAFAS.exe'),
StringStruct(u'FileVersion', u'1.5.0.0'),
StringStruct(u'InternalName', u'NAFAS.exe'),
StringStruct(u'LegalCopyright', u'Copyright (c) 2020-2026 Nafas Development Team'),
StringStruct(u'OriginalFilename', u'NAFAS.exe'),
StringStruct(u'ProductName', u'NAFAS'),
StringStruct(u'ProductVersion', u'1, 5, 0, 0')])
]),
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
]
)
================================================
FILE: otherfiles/requirements-splitter.py
================================================
# -*- coding: utf-8 -*-
"""Requirements splitter."""
test_req = ""
with open('dev-requirements.txt', 'r') as f:
for line in f:
if '==' not in line:
test_req += line
with open('test-requirements.txt', 'w') as f:
f.write(test_req)
================================================
FILE: otherfiles/version_check.py
================================================
# -*- coding: utf-8 -*-
"""Version-check script."""
import os
import sys
import codecs
Failed = 0
VERSION = "1.5"
VERSION_1 = VERSION.split(".")[0]
VERSION_2 = str(int(float(VERSION) * 10 - int(VERSION_1) * 10))
VERSION_3 = str(int(float(VERSION) * 100 - int(VERSION_1)
* 100 - int(VERSION_2) * 10))
VERSION_4 = "0"
SETUP_ITEMS = [
"version='{0}'",
'https://github.com/sepandhaghighi/nafas/tarball/v{0}']
README_ITEMS = [
"[Version {0}](https://github.com/sepandhaghighi/nafas/archive/v{0}.zip)",
#"[Exe-Version {0}](https://github.com/sepandhaghighi/nafas/releases/download/v{0}/NAFAS-{0}.exe)",
#"Run `NAFAS-{0}.exe`",
"pip install nafas=={0}"]
CHANGELOG_ITEMS = [
"## [{0}]",
"https://github.com/sepandhaghighi/nafas/compare/v{0}...dev",
"[{0}]:"]
RC_ITEMS = [
"filevers=({0}, {1}, {2}, {3})",
"prodvers=({0}, {1}, {2}, {3})",
"(u'FileVersion', u'{0}.{1}.{2}.{3}'),",
"(u'ProductVersion', u'{0}, {1}, {2}, {3}')"]
PARAMS_ITEMS = ['NAFAS_VERSION = "{0}"']
SPEC_ITEMS = ['nafas_version = "{0}"']
ISSUE_TEMPLATE_ITEMS = ["- Nafas {0}"]
SECURITY_ITEMS = ["| {0} | :white_check_mark: |", "| < {0} | :x: |"]
FILES = {
"setup.py": SETUP_ITEMS,
"NAFAS.spec": SPEC_ITEMS,
"README.md": README_ITEMS,
"CHANGELOG.md": CHANGELOG_ITEMS,
"SECURITY.md": SECURITY_ITEMS,
os.path.join(
"nafas",
"params.py"): PARAMS_ITEMS,
os.path.join(
".github",
"ISSUE_TEMPLATE",
"bug_report.yml"): ISSUE_TEMPLATE_ITEMS,
}
TEST_NUMBER = len(FILES.keys()) + 1
def print_result(failed: bool = False) -> None:
"""
Print final result.
:param failed: failed flag
:type failed: bool
:return: None
"""
message = "Version tag tests "
if not failed:
print("\n" + message + "passed!")
else:
print("\n" + message + "failed!")
print("Passed : " + str(TEST_NUMBER - Failed) + "/" + str(TEST_NUMBER))
if __name__ == "__main__":
for file_name in FILES.keys():
try:
file_content = codecs.open(
file_name, "r", "utf-8", 'ignore').read()
for test_item in FILES[file_name]:
if file_content.find(test_item.format(VERSION)) == -1:
print("Incorrect version tag in " + file_name)
Failed += 1
break
except Exception as e:
Failed += 1
print("Error in " + file_name + "\n" + "Message : " + str(e))
try:
file_content = codecs.open(
os.path.join(
"otherfiles",
"Version.rc"),
"r",
"utf-8",
'ignore').read()
for test_item in RC_ITEMS:
if file_content.find(
test_item.format(
VERSION_1,
VERSION_2,
VERSION_3,
VERSION_4)) == -1:
print("Incorrect version tag in " + "Version.rc")
Failed += 1
break
except Exception as e:
Failed += 1
print("Error in Version.rc" + "\n" + "Message : " + str(e))
if Failed == 0:
print_result(False)
sys.exit(0)
else:
print_result(True)
sys.exit(1)
================================================
FILE: pytest.ini
================================================
# content of pytest.ini
[pytest]
addopts = --doctest-modules
doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL ELLIPSIS
================================================
FILE: requirements.txt
================================================
art>=1.8
nava>=0.4
colorama>=0.4.5
================================================
FILE: setup.py
================================================
# -*- coding: utf-8 -*-
"""Setup module."""
from typing import List
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
def get_requires() -> List[str]:
"""Read requirements.txt."""
requirements = open("requirements.txt", "r").read()
return list(filter(lambda x: x != "", requirements.split()))
def read_description() -> str:
"""Read README.md and CHANGELOG.md."""
try:
with open("README.md") as r:
description = "\n"
description += r.read()
with open("CHANGELOG.md") as c:
description += "\n"
description += c.read()
return description
except Exception:
return '''Breathing gymnastics application'''
setup(
name='nafas',
packages=['nafas'],
version='1.5',
description='Breathing gymnastics application',
long_description=read_description(),
long_description_content_type='text/markdown',
include_package_data=True,
author='Nafas Development Team',
author_email='me@sepand.tech',
url='https://github.com/sepandhaghighi/nafas',
download_url='https://github.com/sepandhaghighi/nafas/tarball/v1.5',
keywords="breath breathing meditation yoga pranayama",
project_urls={
'Source': 'https://github.com/sepandhaghighi/nafas',
'Discord': 'https://discord.gg/CtZUNKJHP4',
},
install_requires=get_requires(),
python_requires='>=3.7',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Natural Language :: English',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
'Intended Audience :: Developers',
'Intended Audience :: Education',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: Other Audience',
'Topic :: Games/Entertainment',
'Topic :: Utilities',
],
license='MIT',
entry_points={
'console_scripts': [
'nafas = nafas.__main__:main',
]}
)
================================================
FILE: test/test.py
================================================
# -*- coding: utf-8 -*-
"""
>>> import os
>>> import doctest
>>> import io
>>> import contextlib
>>> doctest.ELLIPSIS_MARKER = "ignore_this_message"
>>> from pytest import warns
>>> import shutil
>>> from nafas.functions import *
>>> set_color(None)
>>> f = io.StringIO()
>>> with contextlib.redirect_stdout(f):
... set_color("blue")
>>> "\x1b[34m" in f.getvalue()
True
>>> with contextlib.redirect_stdout(f):
... set_color("lightblue")
>>> "\x1b[94m" in f.getvalue()
True
>>> set_bg_color(None)
>>> with contextlib.redirect_stdout(f):
... set_bg_color("yellow")
>>> "\x1b[43m" in f.getvalue()
True
>>> with contextlib.redirect_stdout(f):
... set_bg_color("lightyellow")
>>> "\x1b[103m" in f.getvalue()
True
>>> set_intensity(None)
>>> with contextlib.redirect_stdout(f):
... set_intensity("dim")
>>> "\x1b[2m" in f.getvalue()
True
>>> clear_screen()
>>> print_line(10,"*")
**********
>>> print_nafas_description()
<BLANKLINE>
Repository: https://github.com/sepandhaghighi/nafas
Paper: https://arxiv.org/abs/2412.04667
* If you use Nafas in your research, please cite our paper
<BLANKLINE>
######################################################################
Breathing gymnastics is a system of breathing exercises that focuses on the treatment of various
diseases and general health promotion. Nafas is a collection of breathing gymnastics designed to
reduce the exhaustion of long working hours. With multiple breathing patterns, Nafas helps you find
your way to a detoxified energetic workday and also improves your concentration by increasing the
oxygen level. No need to walk away to take a break, just sit comfortably, run Nafas and let the
journey begin.
######################################################################
<BLANKLINE>
Breathing tips:
<BLANKLINE>
1. Inhaling is only done through the nose
2. Exhaling, you can use both nose and mouth
3. When exhaling through your mouth, it is recommended to fold the lips
<BLANKLINE>
######################################################################
<BLANKLINE>
Cautions:
<BLANKLINE>
1. If you have any breathing or respiratory issues, consult your doctor before using Nafas
2. If you have asthma or high blood pressure should not hold the breath
3. If you feel dizzy, nauseous, or lightheaded stop practicing and rest
<BLANKLINE>
>>> is_int(2)
True
>>> is_int(2.1)
False
>>> validate_config({"item":2})
False
>>> validate_config({"name": "program1", "unit": 2, "pre": 3, "cycle": 5, "ratio":{"inhale": 1, "exhale":1, "retain":1, "sustain":1}})
True
>>> validate_config({"name": "program1", "unit": 2, "pre": 3, "cycle": 5.2, "ratio":{"inhale": 1, "exhale":1, "retain":1, "sustain":1}})
False
>>> result = load_config(os.path.join("test", "test_config1.json"))
>>> result["status"]
True
>>> result["data"]["program_name"]
'program1'
>>> result["data"]["program_level"]
'Custom'
>>> result["data"]["program_data"]["cycle"]
10
>>> result["data"]["program_data"]["unit"]
2
>>> result["data"]["program_data"]["pre"]
3
>>> result["data"]["program_data"]["ratio"] == [2,3,2,4]
True
>>> result = load_config(os.path.join("test", "test_config2.json"))
>>> result["status"]
False
>>> result = load_config(os.path.join("test", "test_config3.json"))
>>> result["status"]
False
>>> result = generate_config(".")
>>> result
False
>>> result = generate_config("test_config4.json")
>>> result
True
>>> result = load_config("test_config4.json")
>>> result["status"]
True
>>> result["data"]["program_name"]
'unknown'
>>> result["data"]["program_level"]
'Custom'
>>> result["data"]["program_data"]["cycle"]
5
>>> result["data"]["program_data"]["unit"]
1
>>> result["data"]["program_data"]["pre"]
3
>>> result["data"]["program_data"]["ratio"] == [1,1,1,1]
True
>>> print("\\n".join(justify_text(["123"], 2)))
123
>>> print("\\n".join(justify_text(["123"], 1)))
123
>>> print("\\n".join(justify_text(["123"], 0)))
123
>>> print("\\n".join(justify_text("", 2)))
<BLANKLINE>
>>> print("\\n".join(justify_text([" 1", "2", "3"], 2)))
1
2
3
>>> print("\\n".join(justify_text([" 1", "2", "3"], 3)))
1
2
3
>>> input_data = filter_input({"program":1,"level":1})
>>> input_data["program"] == 1
True
>>> input_data["level"] == 1
True
>>> input_data = filter_input({"program":200,"level":5})
>>> input_data["program"] == 1
True
>>> input_data["level"] == 1
True
>>> def test_keyboard_interrupt(i):
... raise KeyboardInterrupt
>>> input_data = get_standard_input(lambda x: "1")
- Choose a program:
<BLANKLINE>
1- Clear Mind (~ 7 minutes)
2- Relax1 (~ 7 minutes)
3- Relax2 (~ 3 minutes)
4- Relax3 (~ 6 minutes)
5- Calming1 (~ 9 minutes)
6- Calming2 (~ 2 minutes)
7- Calming3 (~ 2 minutes)
8- Power (~ 7 minutes)
9- Harmony (~ 7 minutes)
10- Anti-Stress (~ 4 minutes)
11- Anti-Appetite (~ 10 minutes)
12- Cigarette Replace (~ 5 minutes)
13- Decision-Making (~ 2 minutes)
14- Balancing (~ 2 minutes)
15- Energizing (~ 2 minutes)
16- Box (~ 2 minutes)
17- Coherent (~ 8 minutes)
18- Fire (~ 7 minutes)
19- Retention (~ 3 minutes)
20- Swooning (~ 5 minutes)
- Choose a level:
<BLANKLINE>
1- Beginner
2- Medium
3- Advanced
>>> get_standard_input(test_keyboard_interrupt)
Traceback (most recent call last):
...
SystemExit
>>> input_data["program"] == 1
True
>>> input_data["level"] == 1
True
>>> program_name,level,program_data = get_program_data({"program":1,"level":1})
>>> program_data["pre"] == 3
True
>>> program_data["unit"] == 3
True
>>> print_program_details("Clear Mind","Beginner",{"ratio": [1, 0, 3, 0], "unit": 3, "pre": 3, "cycle": 35})
######################################################################
Program Details:
<BLANKLINE>
Name : Clear Mind
<BLANKLINE>
Level : Beginner
<BLANKLINE>
Number of Cycles : 35
<BLANKLINE>
Unit : 3 seconds
<BLANKLINE>
Total Time : 07 minutes, 03 seconds
<BLANKLINE>
Breaths per Minute (BPM) : 5
<BLANKLINE>
Sequence : Inhale(1), Retain(0), Exhale(3), Sustain(0)
<BLANKLINE>
######################################################################
>>> print_program_details("Custom","Beginner",{"ratio": [1, 1, 3, 2], "unit": 1, "pre": 3, "cycle": 35})
######################################################################
Program Details:
<BLANKLINE>
Name : Custom
<BLANKLINE>
Level : Beginner
<BLANKLINE>
Number of Cycles : 35
<BLANKLINE>
Unit : 1 seconds
<BLANKLINE>
Total Time : 04 minutes, 08 seconds
<BLANKLINE>
Breaths per Minute (BPM) : 8.57
<BLANKLINE>
Sequence : Inhale(1), Retain(1), Exhale(3), Sustain(2)
<BLANKLINE>
######################################################################
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3, 0]}, 'us1')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3 seconds
. . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3 seconds
. . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3, 0]}, 'us2', silent=True)
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3 seconds
. . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3 seconds
. . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3.3, 0]}, 'in1')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3.3, 0]}, 'in2')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3.3, 0]}, 'cn1')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3.3, 0]}, 'cn2')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3.3, 0]}, 'ca1')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3.3, 0]}, 'ca2')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3.3, 0]}, 'au1')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3.3, 0]}, 'au2')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3.3, 0]}, 'uk1')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Well done!
>>> run_program({'cycle': 2, 'pre': 3, 'unit': 1, 'ratio': [1, 0, 3.3, 0]}, 'uk2')
Preparing . . .
######################################################################
Start
######################################################################
Cycle: 1 (Remaining: 1)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Cycle: 2 (Remaining: 0)
- Inhale for 1 seconds
.
- Exhale for 3.3 seconds
. . . .
######################################################################
Well done!
>>> sid = play_sound(1)
Traceback (most recent call last):
...
nava.errors.NavaBaseError: Sound file's path should be a string.
>>> try:
... wave_path = os.path.join("nafas", "sounds", "silence.wav")
... temp_path = os.path.join("nafas", "sounds", "temp.wav")
... _ = shutil.move(wave_path, temp_path)
... with warns(RuntimeWarning, match="Your device is not compatible with our underlying sound-playing library. You can refer to https://github.com/openscilab/nava."):
... check_sound_flag = check_sound()
... _ = shutil.move(temp_path, wave_path)
... except Exception:
... pass
>>> check_sound_flag
False
>>> # testing get_rendered_survey_link for multiple cases
>>> get_rendered_survey_link("X", "Medium", {"ratio": [1, 0, 4, 0], "unit": 3, "pre": 3, "cycle": 28})
'https://opsclb.li/nafas/form?version=1.5&data=%7B"name":+"X",+"level":+"Medium",+"data":+%7B"ratio":++%5B1,+0,+4,+0%5D,+"unit":+3,+"pre":+3,+"cycle":+28%7D%7D'
>>> os.remove("test_config4.json")
"""
================================================
FILE: test/test_config1.json
================================================
{
"name": "program1",
"unit": 2,
"pre": 3,
"cycle": 10,
"ratio": {
"inhale": 2,
"exhale": 2,
"retain": 3,
"sustain": 4
}
}
================================================
FILE: test/test_config2.json
================================================
{
"name": "program2",
"unit": 2,
"pre": 3,
"cycle": 10.2,
"ratio": {
"inhale": 2,
"exhale": 2,
"retain": 2,
"sustain": 2
}
}
================================================
FILE: test/test_config3.json
================================================
{
"name": "program3",
"unit": 2,
"pre": 3,
"cycle": 10,
"ratio": {
"inhale": 2,
"exhale": 2,
"retain": 2
}
}
gitextract_cnlewpi2/
├── .coveragerc
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ └── feature_request.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── publish.yml
│ └── test.yml
├── .gitignore
├── AUTHORS.md
├── CHANGELOG.md
├── LICENSE
├── MANIFEST.in
├── NAFAS.spec
├── PROGRAMS.md
├── README.md
├── SECURITY.md
├── autopep8.bat
├── autopep8.sh
├── build_exe.bat
├── codecov.yml
├── dev-requirements.txt
├── nafas/
│ ├── __init__.py
│ ├── __main__.py
│ ├── functions.py
│ └── params.py
├── otherfiles/
│ ├── RELEASE.md
│ ├── Version.rc
│ ├── requirements-splitter.py
│ └── version_check.py
├── pytest.ini
├── requirements.txt
├── setup.py
└── test/
├── test.py
├── test_config1.json
├── test_config2.json
└── test_config3.json
SYMBOL INDEX (32 symbols across 4 files) FILE: nafas/__main__.py function parse_args (line 18) | def parse_args() -> argparse.Namespace: function run (line 42) | def run(args: argparse.Namespace) -> None: function main (line 102) | def main() -> None: FILE: nafas/functions.py function print_line (line 24) | def print_line(num: int = 70, char: str = "#") -> None: function is_int (line 34) | def is_int(number: Union[int, float]) -> bool: function calculate_bpm (line 45) | def calculate_bpm(program_data: Dict[str, Any]) -> float: function calculate_time (line 58) | def calculate_time(program_data: Dict[str, Any]) -> float: function calculate_average_time (line 69) | def calculate_average_time(program_data: Dict[str, Any]) -> float: function convert_time (line 82) | def convert_time(input_time: float, average: bool = False) -> str: function get_rendered_survey_link (line 104) | def get_rendered_survey_link(program_name: str, level: str, program_data... function justify_left (line 123) | def justify_left(words: List[str], width: int) -> str: function justify_text (line 133) | def justify_text(words: List[str], width: int) -> Generator[str, None, N... function check_sound (line 164) | def check_sound() -> bool: function print_nafas_description (line 175) | def print_nafas_description() -> None: function print_program_details (line 186) | def print_program_details(program_name: str, level: str, program_data: D... function filter_input (line 217) | def filter_input(input_data: Dict[str, Any]) -> Dict[str, Any]: function load_config (line 231) | def load_config(config_path: str) -> Dict[str, Any]: function generate_config (line 265) | def generate_config(config_path: str) -> bool: function validate_config (line 279) | def validate_config(config_data: Dict[str, Any]) -> bool: function get_standard_input (line 299) | def get_standard_input(input_func: Callable = input) -> Dict[str, Any]: function get_program_data (line 335) | def get_program_data(input_data: Dict[str, Any]) -> Tuple: function get_sound_path (line 346) | def get_sound_path(sound_name: str, speaker_id: Optional[str] = None) ->... function set_color (line 359) | def set_color(color: str) -> None: function set_bg_color (line 372) | def set_bg_color(bg_color: str) -> None: function set_intensity (line 385) | def set_intensity(intensity: str) -> None: function graphic_counter (line 396) | def graphic_counter(delay_time: float) -> None: function play_sound (line 412) | def play_sound(sound_path: str, enable: bool = True) -> None: function run_program (line 423) | def run_program(program_data: Dict[str, Any], speaker_id: str, silent: b... function clear_screen (line 467) | def clear_screen() -> None: FILE: otherfiles/version_check.py function print_result (line 54) | def print_result(failed: bool = False) -> None: FILE: setup.py function get_requires (line 10) | def get_requires() -> List[str]: function read_description (line 16) | def read_description() -> str:
Condensed preview — 40 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (105K chars).
[
{
"path": ".coveragerc",
"chars": 171,
"preview": "[run]\nbranch = True\nomit =\n */nafas/__main__.py\n */nafas/__init__.py\n[report]\n# Regexes for lines to exclude from "
},
{
"path": ".github/CODE_OF_CONDUCT.md",
"chars": 3346,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 567,
"preview": "# Contribution\t\t\t\n\nChanges and improvements are more than welcome! ❤️ Feel free to fork and open a pull request.\t\t\n\n\nPle"
},
{
"path": ".github/FUNDING.yml",
"chars": 65,
"preview": "custom: https://github.com/sepandhaghighi/nafas#show-your-support"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 2812,
"preview": "name: Bug Report\ndescription: File a bug report\ntitle: \"[Bug]: \"\nbody:\n - type: markdown\n attributes:\n value: |"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 181,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Discord\n url: https://discord.com/invite/CtZUNKJHP4\n about: A"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 967,
"preview": "name: Feature Request\ndescription: Suggest a feature for this project\ntitle: \"[Feature]: \"\nbody:\n - type: textarea\n "
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 112,
"preview": "#### Reference Issues/PRs\n\n#### What does this implement/fix? Explain your changes.\n\n\n#### Any other comments?\n\n"
},
{
"path": ".github/dependabot.yml",
"chars": 203,
"preview": "version: 2\nupdates:\n- package-ecosystem: pip\n directory: \"/\"\n schedule:\n interval: weekly\n time: \"01:30\"\n open-"
},
{
"path": ".github/workflows/publish.yml",
"chars": 1004,
"preview": "# This workflow will upload a Python Package using Twine when a release is created\n# For more information see: https://h"
},
{
"path": ".github/workflows/test.yml",
"chars": 2087,
"preview": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more inform"
},
{
"path": ".gitignore",
"chars": 1219,
"preview": "# Created by .ignore support plugin (hsz.mobi)\n### Python template\n# Byte-compiled / optimized / DLL files\n__pycache__/\n"
},
{
"path": "AUTHORS.md",
"chars": 339,
"preview": "# Core Developers\n----------\n- [@sepandhaghighi](http://github.com/sepandhaghighi)\n- [@sadrasabouri](https://github.com/"
},
{
"path": "CHANGELOG.md",
"chars": 6070,
"preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
},
{
"path": "LICENSE",
"chars": 1106,
"preview": "MIT License\n\nCopyright (c) 2020 Sepand Haghighi\n\nCopyright (c) 2020 Sadra Sabouri\n\nPermission is hereby granted, free of"
},
{
"path": "MANIFEST.in",
"chars": 141,
"preview": "include LICENSE\ninclude *.md\ninclude *.spec\ninclude *.txt\ninclude *.yml\ninclude *.ini\ninclude nafas/sounds/*.wav\ninclude"
},
{
"path": "NAFAS.spec",
"chars": 908,
"preview": "# -*- mode: python -*-\n\nblock_cipher = None\n\nnafas_version = \"1.5\"\n\na = Analysis(['nafas/__main__.py'],\n pat"
},
{
"path": "PROGRAMS.md",
"chars": 6157,
"preview": "# Nafas Breathing Programs\n\n**Last Update: 2026-02-19**\n\n| **Program** | **Description** "
},
{
"path": "README.md",
"chars": 12315,
"preview": "<div align=\"center\">\n<img src=\"https://github.com/sepandhaghighi/nafas/raw/master/otherfiles/logo.png\" width=320px>\n<h1>"
},
{
"path": "SECURITY.md",
"chars": 485,
"preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported |\n| ------------- | ------------------ |\n"
},
{
"path": "autopep8.bat",
"chars": 383,
"preview": "python -m autopep8 nafas --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length 120 --ver"
},
{
"path": "autopep8.sh",
"chars": 393,
"preview": "#!/bin/sh\npython -m autopep8 nafas --recursive --aggressive --aggressive --in-place --pep8-passes 2000 --max-line-length"
},
{
"path": "build_exe.bat",
"chars": 274,
"preview": "@echo off\nFOR /F \"tokens=* USEBACKQ\" %%F IN (`python --version`) DO (\nSET py_version_str=%%F\n)\nSET py_version=%py_versio"
},
{
"path": "codecov.yml",
"chars": 199,
"preview": "codecov:\n require_ci_to_pass: yes\n\ncoverage:\n precision: 2\n round: up\n range: \"70...100\"\n status:\n patch:\n "
},
{
"path": "dev-requirements.txt",
"chars": 132,
"preview": "art==6.5\nnava==0.8\ncolorama==0.4.6\npytest>=4.3.1\npytest-cov>=2.6.1\nsetuptools>=40.8.0\nvulture>=1.0\nbandit>=1.5.1\npydocst"
},
{
"path": "nafas/__init__.py",
"chars": 112,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"nafas modules.\"\"\"\nfrom nafas.params import NAFAS_VERSION\n__version__ = NAFAS_VERSION\n"
},
{
"path": "nafas/__main__.py",
"chars": 4309,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"nafas main.\"\"\"\n\nimport sys\nimport webbrowser\nimport argparse\nfrom nafas.functions import prin"
},
{
"path": "nafas/functions.py",
"chars": 14009,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"nafas functions.\"\"\"\n\nfrom typing import Dict, List, Tuple\nfrom typing import Generator, Calla"
},
{
"path": "nafas/params.py",
"chars": 14501,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"nafas parameters.\"\"\"\n\nNAFAS_VERSION = \"1.5\"\n\nNAFAS_DESCRIPTION = \"\"\"\nBreathing gymnastics is "
},
{
"path": "otherfiles/RELEASE.md",
"chars": 1489,
"preview": "# Nafas Release Instructions\n\n**Last Update: 2025-06-30**\n\n1. Create the `release` branch under `dev`\n2. Update all vers"
},
{
"path": "otherfiles/Version.rc",
"chars": 821,
"preview": "VSVersionInfo(\n ffi=FixedFileInfo(\n filevers=(1, 5, 0, 0),\n prodvers=(1, 5, 0, 0),\n mask=0x3f,\n flags=0x0,\n"
},
{
"path": "otherfiles/requirements-splitter.py",
"chars": 260,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"Requirements splitter.\"\"\"\n\ntest_req = \"\"\n\nwith open('dev-requirements.txt', 'r') as f:\n fo"
},
{
"path": "otherfiles/version_check.py",
"chars": 3331,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"Version-check script.\"\"\"\nimport os\nimport sys\nimport codecs\nFailed = 0\nVERSION = \"1.5\"\n\nVERSI"
},
{
"path": "pytest.ini",
"chars": 135,
"preview": "# content of pytest.ini\n[pytest]\naddopts = --doctest-modules\ndoctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_"
},
{
"path": "requirements.txt",
"chars": 35,
"preview": "art>=1.8\nnava>=0.4\ncolorama>=0.4.5\n"
},
{
"path": "setup.py",
"chars": 2444,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"Setup module.\"\"\"\nfrom typing import List\ntry:\n from setuptools import setup\nexcept ImportE"
},
{
"path": "test/test.py",
"chars": 14491,
"preview": "# -*- coding: utf-8 -*-\n\"\"\"\n>>> import os\n>>> import doctest\n>>> import io\n>>> import contextlib\n>>> doctest.ELLIPSIS_MA"
},
{
"path": "test/test_config1.json",
"chars": 179,
"preview": "{\n \"name\": \"program1\",\n \"unit\": 2,\n \"pre\": 3,\n \"cycle\": 10,\n \"ratio\": {\n \"inhale\": 2,\n \"exh"
},
{
"path": "test/test_config2.json",
"chars": 181,
"preview": "{\n \"name\": \"program2\",\n \"unit\": 2,\n \"pre\": 3,\n \"cycle\": 10.2,\n \"ratio\": {\n \"inhale\": 2,\n \"e"
},
{
"path": "test/test_config3.json",
"chars": 157,
"preview": "{\n \"name\": \"program3\",\n \"unit\": 2,\n \"pre\": 3,\n \"cycle\": 10,\n \"ratio\": {\n \"inhale\": 2,\n \"exh"
}
]
About this extraction
This page contains the full source code of the sepandhaghighi/nafas GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 40 files (95.8 KB), approximately 28.1k tokens, and a symbol index with 32 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.