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**
**(I:R:E:S)** | **Unit**
**(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
M
A | 1:0:3:0
1:0:4:0
1:0:5:0 | 3
3
3 | 35
28
24 | | Relax1 | Relaxation-focused program to calm nerves. Ideal for use after intense coding sessions. Useful for programmers managing stress. | B
M
A | 1:0:2:2
1:0:2:3
1:0:2:4 | 3
3
3 | 28
24
22 | | Relax2 | Enhanced relaxation program with a focus on breathing depth. Suitable for unwinding after long work sessions. | B
M
A | 4:7:8:0
4:7:8:0
4:7:8:0 | 1
1
1 | 4
8
12 | | Relax3 | Deeper relaxation exercise for stress relief and a more meditative state. Ideal for unwinding after complex projects. | B
M
A | 7:0:11:0
7:0:11:0
7:0:11:0 | 1
1
1 | 15
20
24 | | Calming1 | Soothing breathing pattern, helpful for reducing anxiety. Beneficial for long working hours and maintaining calm. | B
M
A | 1:2:1:2
1:3:1:3
1:4:1:4 | 3
3
3 | 24
22
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
M
A | 5:0:5:5
5:0:5:5
5:0:5:5 | 1
1
1 | 4
6
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
M
A | 4:0:6:0
6:1:8:4
4:1:12:1 | 1
1
1 | 6
8
10 | | Power | Energizing breathing, designed for boosting energy and alertness. Suitable for users feeling fatigue. | B
M
A | 1:2:2:0
1:3:2:0
1:4:2:0 | 3
3
3 | 28
24
20 | | Harmony | Balancing exercise to promote relaxation and focus. Ideal for programmers looking to maintain concentration. | B
M
A | 1:3:2:1
1:4:2:1
1:5:2:1 | 3
3
3 | 20
18
16 | | Anti-Stress | Specialized for stress relief, particularly useful during high-stress tasks or debugging sessions. | B
M
A | 3:0:0.66:0
4:0:0.66:0
5:0:0.66:0 | 3
3
3 | 20
17
14 | | Anti-Appetite | Breathing program to manage cravings and avoid unnecessary snacking during work. Beneficial for users seeking appetite control. | B
M
A | 5:0:5:5
6:0:5:5
7:0:5:5 | 1
1
1 | 40
38
36 | | Cigarette Replace | Alternative to smoking breaks, encouraging mindful breathing. Suitable for smokers looking to reduce habit triggers. | B
M
A | 2:1.1:2.2:0.8
3:1.1:2.2:0.8
4:1.1:2.2:0.8 | 2
2
2 | 23
21
19 | | Decision-Making | Breathing exercise to improve focus before making critical decisions. Useful for developers or managers. | B
M
A | 5:2:7:0
5:2:7:0
5:2:7:0 | 1
1
1 | 6
10
14 | | Balancing | Designed for achieving inner balance. Useful for users needing a brief grounding exercise during hectic schedules. | B
M
A | 6:0:6:0
8:1:8:1
6:2:6:2 | 1
1
1 | 6
8
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
M
A | 6:0:4:0
6:4:6:1
6:6:6:1 | 1
1
1 | 6
8
10 | | Box | A breathing technique involving equal counts of inhaling, retaining, exhaling, and sustaining to enhance focus and calm. | B
M
A | 4:4:4:4
4:4:4:4
4:4:4:4 | 1
1
1 | 4
8
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
M
A | 5:0:5:0
5:0:5:0
5:0:5:0 | 1
1
1 | 30
50
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
M
A | 3:0:5:12
3:0:5:12
3:0:5:12 | 1
1
1 | 10
20
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
M
A | 2:0:2:12
2:0:2:12
2:0:2:12 | 1
1
1 | 5
10
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
M
A | 5:10:7:0
5:10:7:0
5:10:7:0 | 1
1
1 | 7
12
21 | **Notes**: - *I: Inhale, R: Retain, E: Exhale, S: Sustain, B: Beginner, M: Medium, A: Advanced* ================================================ FILE: README.md ================================================

Nafas: A Breathing Gymnastics Application


PyPI version Codecov built with Python3 GitHub repo size Discord Channel
## 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.
Nafas Programs' Cycle

Nafas Programs' Cycle

Open Hub
PyPI Counter
Github Stars
Branch master dev
CI
Code Quality CodeFactor
## 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
Screen Record
## 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 Discord Channel ## References
1- Prana Breath
2- Rickard, Kathleen Benjamin, Dorothy J. Dunn, and Virginia M. Brouch. "Breathing techniques associated with improved health outcomes." (2015).
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.
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.
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.
6- Bujatti, M., and P. Biederer. "Serotonin, noradrenaline, dopamine metabolites in transcendental meditation-technique." Journal of Neural Transmission 39, no. 3 (1976): 257-267.
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).
8- DrWeil, Integrative Medicine & Healthy Living
9- Human Givens Institute
10- This 2-Minute Breathing Exercise Can Help You Make Better Decisions
11- Using 5-5-5 Breathing to Calm Down
12- Free Text-To-Speech and Text-to-MP3 for US English
13- Pranayama Breathing Techniques and Tips
14- Box Breathing Benefits and Techniques
15- Box breathing: How to do it, benefits, and tips
16- Breathing at a rate of 5.5 breaths per minute with equal inhalation-to-exhalation ratio increases heart rate variability
17- Coherent Breathing Timer - 6 Breaths Per Minute | 5 Seconds in / 5 Seconds Out | With Bells
18- Pranayama : Breathing Exercise
## 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

Star This Repo

Give a ⭐️ if this project helped you!

Donate to Our Project

Bitcoin

1KtNLEEeUbTEK9PdN6Ya3ZAKXaqoKUuxCy

Ethereum

0xcD4Db18B6664A9662123D4307B074aE968535388

Litecoin

Ldnz5gMcEeV8BAdsyf8FstWDC6uyYR6pgZ

Doge

DDUnKpFQbBqLpFVZ9DfuVysBdr249HxVDh

Tron

TCZxzPZLcJHr2qR3uPUB1tXB6L3FDSSAx7

Ripple

rN7ZuRG7HDGHR5nof8nu5LrsbmSB61V1qq

Binance Coin

bnb1zglwcf0ac3d0s2f6ck5kgwvcru4tlctt4p5qef

Tether

0xcD4Db18B6664A9662123D4307B074aE968535388

Dash

Xd3Yn2qZJ7VE8nbKw2fS98aLxR5M6WUU3s

Stellar

GALPOLPISRHIYHLQER2TLJRGUSZH52RYDK6C3HIU4PSMNAV65Q36EGNL

Zilliqa

zil1knmz8zj88cf0exr2ry7nav9elehxfcgqu3c5e5

Coffeete

================================================ 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() Repository: https://github.com/sepandhaghighi/nafas Paper: https://arxiv.org/abs/2412.04667 * If you use Nafas in your research, please cite our paper ###################################################################### 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. ###################################################################### 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 ###################################################################### 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 >>> 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))) >>> 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: 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: 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: Name : Clear Mind Level : Beginner Number of Cycles : 35 Unit : 3 seconds Total Time : 07 minutes, 03 seconds Breaths per Minute (BPM) : 5 Sequence : Inhale(1), Retain(0), Exhale(3), Sustain(0) ###################################################################### >>> print_program_details("Custom","Beginner",{"ratio": [1, 1, 3, 2], "unit": 1, "pre": 3, "cycle": 35}) ###################################################################### Program Details: Name : Custom Level : Beginner Number of Cycles : 35 Unit : 1 seconds Total Time : 04 minutes, 08 seconds Breaths per Minute (BPM) : 8.57 Sequence : Inhale(1), Retain(1), Exhale(3), Sustain(2) ###################################################################### >>> 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 } }