├── .github └── workflows │ └── mirror-to-codeberg.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── cli2gui ├── __init__.py ├── application │ ├── __init__.py │ ├── application.py │ └── application2args.py ├── decorators.py ├── gui │ ├── FiraCode-Regular.ttf │ ├── __init__.py │ ├── abstract_gui.py │ ├── dearpygui_wrapper.py │ ├── helpers.py │ └── pysimplegui_wrapper.py ├── models.py └── tojson │ ├── __init__.py │ ├── argparse2json.py │ ├── click2json.py │ ├── docopt2json.py │ ├── getopt2json.py │ └── optparse2json.py ├── comparison.md ├── documentation ├── reference │ ├── README.md │ └── cli2gui │ │ ├── application │ │ ├── application.md │ │ ├── application2args.md │ │ └── index.md │ │ ├── decorators.md │ │ ├── gui │ │ ├── abstract_gui.md │ │ ├── dearpygui_wrapper.md │ │ ├── helpers.md │ │ ├── index.md │ │ └── pysimplegui_wrapper.md │ │ ├── index.md │ │ ├── models.md │ │ └── tojson │ │ ├── argparse2json.md │ │ ├── click2json.md │ │ ├── docopt2json.md │ │ ├── getopt2json.md │ │ ├── index.md │ │ └── optparse2json.md └── tutorials │ └── README.md ├── pyproject.toml ├── readme-assets ├── icons │ ├── name.png │ └── proj-icon.png └── screenshots │ ├── dearpygui.png │ └── freesimplegui.png ├── requirements.txt └── tests ├── __init__.py ├── argparse ├── __init__.py ├── another_file.md ├── file.md ├── test_10.py ├── test_11.py ├── test_16.py ├── test_18.py ├── test_22.py ├── test_24.py ├── test_advanced.py ├── test_advanced_fsg.py ├── test_advanced_fsgqt.py ├── test_advanced_fsgweb.py ├── test_advanced_psg.py └── test_simple.py ├── click ├── __init__.py ├── test_boolean.py ├── test_choice.py ├── test_feat_switches.py └── test_simple.py ├── custom(argparse) ├── __init__.py ├── test_advanced.py └── test_simple.py ├── dephell_argparse ├── __init__.py ├── test_advanced.py └── test_simple.py ├── docopt ├── __init__.py ├── test_11.py └── test_simple.py ├── getopt ├── __init__.py └── test_simple.py └── optparse ├── __init__.py ├── test_11.py └── test_simple.py /.github/workflows/mirror-to-codeberg.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Sync repo to the Codeberg mirror 3 | name: Repo sync GitHub -> Codeberg 4 | on: 5 | push: 6 | branches: 7 | - '**' 8 | 9 | jobs: 10 | codeberg: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - uses: spyoungtech/mirror-action@v0.7.0 17 | with: 18 | REMOTE: "https://codeberg.org/FredHappyface/Cli2Gui.git" 19 | GIT_USERNAME: FredHappyface 20 | GIT_PASSWORD: ${{ secrets.CODEBERG_PASSWORD }} 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | uv.lock 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | # pytype static type analyzer 138 | .pytype/ 139 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.9.6 4 | hooks: 5 | - id: ruff 6 | args: [ --fix ] 7 | - id: ruff-format 8 | 9 | - repo: https://github.com/RobertCraigie/pyright-python 10 | rev: v1.1.394 11 | hooks: 12 | - id: pyright 13 | 14 | - repo: local 15 | hooks: 16 | - id: generate requirements 17 | name: generate requirements 18 | entry: uv export --no-hashes --no-dev -o requirements.txt 19 | language: system 20 | pass_filenames: false 21 | - id: safety 22 | name: safety 23 | entry: uv run safety 24 | language: system 25 | pass_filenames: false 26 | - id: make docs 27 | name: make docs 28 | entry: uv run handsdown --cleanup -o documentation/reference 29 | language: system 30 | pass_filenames: false 31 | - id: build package 32 | name: build package 33 | entry: uv build 34 | language: system 35 | pass_filenames: false 36 | 37 | 38 | - repo: https://github.com/pre-commit/pre-commit-hooks 39 | rev: v5.0.0 40 | hooks: 41 | - id: trailing-whitespace 42 | - id: end-of-file-fixer 43 | - id: check-case-conflict 44 | - id: check-executables-have-shebangs 45 | - id: check-json 46 | - id: check-merge-conflict 47 | - id: check-shebang-scripts-are-executable 48 | - id: check-symlinks 49 | - id: check-toml 50 | - id: check-vcs-permalinks 51 | - id: check-yaml 52 | - id: detect-private-key 53 | - id: mixed-line-ending 54 | 55 | - repo: https://github.com/boidolr/pre-commit-images 56 | rev: v1.8.4 57 | hooks: 58 | - id: optimize-jpg 59 | - id: optimize-png 60 | - id: optimize-svg 61 | - id: optimize-webp 62 | 63 | exclude: "tests/data|documentation/reference" 64 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All major and minor version changes will be documented in this file. Details of 4 | patch-level version changes can be found in [commit messages](../../commits/master). 5 | 6 | ## 2025 - 2025/02/18 7 | 8 | - fpg write to file picker #24 9 | - dpg add file picker #16 10 | 11 | ## 2024.3 - 2024/10/05 12 | 13 | - Code improvements 14 | 15 | ## 2024.2 - 2024/10/04 16 | 17 | - Add `freesimplegui` 18 | - Update docs 19 | 20 | ## 2024.1 - 2024/03/19 21 | 22 | - Add `dearpygui` support as default, (download the `psg` extra for `pysimplegui` support) 23 | - fix types for `argparse..add_mutually_exclusive_group()` 24 | 25 | ## 2024 - 2024/01/20 26 | 27 | - Update deps 28 | - Code improvements 29 | 30 | ## 2023 - 2023/08/31 31 | 32 | - Update deps 33 | 34 | ## 2022.3 - 2022/12/31 35 | 36 | - Feature, support defaults https://github.com/FHPythonUtils/Cli2Gui/issues/11 37 | - Use full module namespace in-place of relative imports 38 | - Use `Enum` for widget types. eg. `types.ItemType.Bool` 39 | - Update internal types 40 | - Add more supported types for other parsers. e.g `click` 41 | - Argparse supports: Bool, Int, Choice, File, Text 42 | - Click supports: Bool, Int, Choice, Text 43 | - DocOpt supports: Bool, Text 44 | - GetOpt supports: Bool, Text 45 | - Optparse supports: Bool, Int, Choice, Text 46 | 47 | ## 2022.2.1 - 2022/12/30 48 | 49 | - Fix https://github.com/FHPythonUtils/Cli2Gui/issues/13 50 | 51 | ## 2022.2 - 2022/09/02 52 | 53 | - Fix https://github.com/FHPythonUtils/Cli2Gui/issues/10, basic support for subparsers. `parser.add_subparsers()` 54 | 55 | ```py 56 | parser = argparse.ArgumentParser(description="this is an example parser") 57 | subparsers = parser.add_subparsers(help='types of A') 58 | parser.add_argument("-v",) 59 | 60 | a_parser = subparsers.add_parser("A") 61 | b_parser = subparsers.add_parser("B") 62 | 63 | a_parser.add_argument("something", choices=['a1', 'a2']) 64 | 65 | args = parser.parse_args() 66 | ``` 67 | 68 | ## 2022.1 - 2022/04/07 69 | 70 | - Fix https://github.com/FHPythonUtils/Cli2Gui/issues/7 71 | - `catpandoc` is now optional https://github.com/FHPythonUtils/Cli2Gui/issues/5 72 | 73 | ## 2022 - 2022/01/24 74 | 75 | - Bump pillow version (CVE-2022-22815, CVE-2022-22816, CVE-2022-22817) 76 | - Update deps 77 | 78 | ## 2021.2.1 - 2021/10/14 79 | 80 | - Use pre-commit to enforce reasonable standards + consistency 81 | - Update readme with improved docs on installing and running python (fairly generic) 82 | - Remove classifiers for license + python versions and rely on poetry to generate these 83 | - Update tooling config (pyproject.toml) 84 | 85 | ## 2021.2 - 2021/07/24 86 | 87 | - Use enum for parser + gui 88 | - Use datatypes + typeddict... 89 | - Add option for end user to select parser at runtime https://github.com/FHPythonUtils/Cli2Gui/issues/4 90 | - Replace 'if' case/switch with function mappings 91 | 92 | ## 2021.1 - 2021/06/06 93 | 94 | - reformat 95 | - improve documentation 96 | - typing improvements 97 | - use relative imports 98 | - update pyproject.toml 99 | 100 | ## 2021 - 2021/01/18 101 | 102 | - Modelled the radio groups 103 | 104 | ## 2020.9.1 - 2020/10/14 105 | 106 | - New Pillow release 107 | - Typing fixes as recommended here https://github.com/microsoft/pylance-release/issues/485 108 | 109 | ## 2020.9 - 2020/10/13 110 | 111 | - Added typing (drop py < 3.7) 112 | - Update docstrings 113 | - Update internal representation (tidy up) 114 | - Use flavours for additional pysimplegui modules install `cli2gui[web]` and 115 | `cli2gui[qt]` for the respective versions 116 | - Modernize parts of the codebase (eg. decorators.py) 117 | - Use camelCase for variables 118 | 119 | ## 2020.8.1 - 2020/05/06 120 | 121 | - Updated classifiers 122 | 123 | ## 2020.8 - 2020/04/27 124 | 125 | - Added dephell_argparse support 126 | 127 | ## 2020.7.1 - 2020/04/24 128 | 129 | - Added catch for ResourceWarning when running in `python -Wd` 130 | 131 | ## 2020.7 - 2020/04/16 132 | 133 | - using poetry and dephell build systems 134 | 135 | ## 2020.6 - 2020/03/24 136 | 137 | - added rudimentary click support 138 | 139 | ## 2020.5 - 2020/03/22 140 | 141 | - added menu 142 | - included part of catpandoc to achieve this (excluding catimage as this leads 143 | to a circular import 😱) 144 | - updated documentation to reflect this 145 | - updated requirements.txt 146 | 147 | ## 2020.4 148 | 149 | - bump 150 | 151 | ## 2020.3 - 2020/03/17 152 | 153 | - can use pysimplegui, pysimpleguiqt, pysimpleguiweb 154 | - updated readme and added data structures documentation 155 | - bugfixes 156 | - lint fixes 157 | 158 | ## 2020.3 - 2020/03/12 159 | 160 | - added docopt parser 161 | - base24 scheme can be used as theme 162 | - Updated run_function. If not specified, program continues as normal 163 | (can only run once) 164 | 165 | ## 2020.2 - 2020/03/12 166 | 167 | - Fix 168 | 169 | ## 2020.1 - 2020/03/12 170 | 171 | - Updated readme 172 | - added images 173 | - added getopt parser 174 | - added optparse parser 175 | - Program icon is to left of title 176 | - Streamlined argparse2json 177 | - refactor 178 | 179 | ## 2020 - 2020/03/06 180 | 181 | - First release 182 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 FredHappyface 4 | Copyright (c) 2013-2017 Chris 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub top language](https://img.shields.io/github/languages/top/FHPythonUtils/Cli2Gui.svg?style=for-the-badge&cacheSeconds=28800)](../../) 2 | [![Issues](https://img.shields.io/github/issues/FHPythonUtils/Cli2Gui.svg?style=for-the-badge&cacheSeconds=28800)](../../issues) 3 | [![License](https://img.shields.io/github/license/FHPythonUtils/Cli2Gui.svg?style=for-the-badge&cacheSeconds=28800)](/LICENSE.md) 4 | [![Commit activity](https://img.shields.io/github/commit-activity/m/FHPythonUtils/Cli2Gui.svg?style=for-the-badge&cacheSeconds=28800)](../../commits/master) 5 | [![Last commit](https://img.shields.io/github/last-commit/FHPythonUtils/Cli2Gui.svg?style=for-the-badge&cacheSeconds=28800)](../../commits/master) 6 | [![PyPI Downloads](https://img.shields.io/pypi/dm/cli2gui.svg?style=for-the-badge&cacheSeconds=28800)](https://pypistats.org/packages/cli2gui) 7 | [![PyPI Total Downloads](https://img.shields.io/badge/dynamic/json?style=for-the-badge&label=total%20downloads&query=%24.total_downloads&url=https%3A%2F%2Fapi%2Epepy%2Etech%2Fapi%2Fv2%2Fprojects%2Fcli2gui)](https://pepy.tech/project/cli2gui) 8 | [![PyPI Version](https://img.shields.io/pypi/v/cli2gui.svg?style=for-the-badge&cacheSeconds=28800)](https://pypi.org/project/cli2gui) 9 | 10 | 11 | # Cli2Gui 12 | 13 | Project Icon 14 | 15 | **Project Description:** 16 | 17 | `Cli2Gui` enables you to convert command-line interface (CLI) applications into graphical user 18 | interfaces (GUIs) with minimal effort. Designed to accommodate a wide variety of use cases, 19 | this library allows developers to maintain a CLI while seamlessly enabling a GUI option for 20 | popular Python parsers like `argparse`, `docopt` with GUI frameworks such as `freesimplegui`, 21 | `dearpygui`, and more. By using `Cli2Gui`, developers can extend the accessibility of their 22 | applications, catering to both command-line users and those who prefer interactive graphical tools. 23 | 24 | With a decorator-based approach, `Cli2Gui` allows you to keep your CLI logic intact while adding 25 | GUI support as needed. Customization options such as theming, custom icons, and program descriptions 26 | make it easy to enhance the user experience. Whether you are working on small scripts or complex 27 | tools, `Cli2Gui` provides a flexible and user-friendly way to bridge the gap between command-line 28 | and graphical interfaces, offering convenience for both developers and end-users. 29 | 30 | - [Screenshots](#screenshots) 31 | - [Documentation](#documentation) 32 | - [Install With PIP](#install-with-pip) 33 | - [Language information](#language-information) 34 | - [Built for](#built-for) 35 | - [Building](#building) 36 | - [Download Project](#download-project) 37 | - [Clone](#clone) 38 | - [Using The Command Line](#using-the-command-line) 39 | - [Using GitHub Desktop](#using-github-desktop) 40 | - [Download Zip File](#download-zip-file) 41 | - [Community Files](#community-files) 42 | - [Licence](#licence) 43 | - [Changelog](#changelog) 44 | - [Code of Conduct](#code-of-conduct) 45 | - [Contributing](#contributing) 46 | - [Security](#security) 47 | - [Support](#support) 48 | 49 | ## Screenshots 50 | 51 |
52 | dearpygui 53 | freesimplegui 54 |
55 | 56 | 57 | ## Documentation 58 | 59 | A high-level overview of how the documentation is organized organized will help you know 60 | where to look for certain things: 61 | 62 | - [Tutorials](/documentation/tutorials) take you by the hand through a series of steps to get 63 | started using the software. Start here if you’re new. 64 | - The [Technical Reference](/documentation/reference) documents APIs and other aspects of the 65 | machinery. This documentation describes how to use the classes and functions at a lower level 66 | and assume that you have a good high-level understanding of the software. 67 | 71 | 72 | ## Install With PIP 73 | 74 | ```python 75 | pip install cli2gui 76 | ``` 77 | 78 | Head to https://pypi.org/project/cli2gui/ for more info 79 | 80 | ## Language information 81 | 82 | ### Built for 83 | 84 | This program has been written for Python versions 3.8 - 3.11 and has been tested with both 3.8 and 85 | 3.11 86 | 87 | ## Building 88 | 89 | This project uses https://github.com/FHPythonUtils/FHMake to automate most of the building. This 90 | command generates the documentation, updates the requirements.txt and builds the library artefacts 91 | 92 | Note the functionality provided by fhmake can be approximated by the following 93 | 94 | ```sh 95 | handsdown --cleanup -o documentation/reference 96 | poetry export -f requirements.txt --output requirements.txt 97 | poetry export -f requirements.txt --with dev --output requirements_optional.txt 98 | poetry build 99 | ``` 100 | 101 | `fhmake audit` can be run to perform additional checks 102 | 103 | ## Download Project 104 | 105 | ### Clone 106 | 107 | #### Using The Command Line 108 | 109 | 1. Press the Clone or download button in the top right 110 | 2. Copy the URL (link) 111 | 3. Open the command line and change directory to where you wish to 112 | clone to 113 | 4. Type 'git clone' followed by URL in step 2 114 | 115 | ```bash 116 | git clone https://github.com/FHPythonUtils/Cli2Gui 117 | ``` 118 | 119 | More information can be found at 120 | https://help.github.com/en/articles/cloning-a-repository 121 | 122 | #### Using GitHub Desktop 123 | 124 | 1. Press the Clone or download button in the top right 125 | 2. Click open in desktop 126 | 3. Choose the path for where you want and click Clone 127 | 128 | More information can be found at 129 | https://help.github.com/en/desktop/contributing-to-projects/cloning-a-repository-from-github-to-github-desktop 130 | 131 | ### Download Zip File 132 | 133 | 1. Download this GitHub repository 134 | 2. Extract the zip archive 135 | 3. Copy/ move to the desired location 136 | 137 | ## Community Files 138 | 139 | ### Licence 140 | 141 | MIT License 142 | Copyright (c) FredHappyface 143 | (See the [LICENSE](/LICENSE.md) for more information.) 144 | 145 | ### Changelog 146 | 147 | See the [Changelog](/CHANGELOG.md) for more information. 148 | 149 | ### Code of Conduct 150 | 151 | Online communities include people from many backgrounds. The *Project* 152 | contributors are committed to providing a friendly, safe and welcoming 153 | environment for all. Please see the 154 | [Code of Conduct](https://github.com/FHPythonUtils/.github/blob/master/CODE_OF_CONDUCT.md) 155 | for more information. 156 | 157 | ### Contributing 158 | 159 | Contributions are welcome, please see the 160 | [Contributing Guidelines](https://github.com/FHPythonUtils/.github/blob/master/CONTRIBUTING.md) 161 | for more information. 162 | 163 | ### Security 164 | 165 | Thank you for improving the security of the project, please see the 166 | [Security Policy](https://github.com/FHPythonUtils/.github/blob/master/SECURITY.md) 167 | for more information. 168 | 169 | ### Support 170 | 171 | Thank you for using this project, I hope it is of use to you. Please be aware that 172 | those involved with the project often do so for fun along with other commitments 173 | (such as work, family, etc). Please see the 174 | [Support Policy](https://github.com/FHPythonUtils/.github/blob/master/SUPPORT.md) 175 | for more information. 176 | -------------------------------------------------------------------------------- /cli2gui/__init__.py: -------------------------------------------------------------------------------- 1 | """Entry point for the program.""" 2 | 3 | from __future__ import annotations 4 | 5 | from cli2gui.decorators import Cli2Gui, Click2Gui 6 | 7 | _ = (Cli2Gui, Click2Gui) 8 | -------------------------------------------------------------------------------- /cli2gui/application/__init__.py: -------------------------------------------------------------------------------- 1 | """The GUI application and related functionality.""" 2 | -------------------------------------------------------------------------------- /cli2gui/application/application.py: -------------------------------------------------------------------------------- 1 | """Application here uses PySimpleGUI.""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | import sys 7 | from typing import Any 8 | 9 | from cli2gui import models 10 | from cli2gui.application.application2args import argFormat 11 | from cli2gui.gui import helpers 12 | from cli2gui.gui.dearpygui_wrapper import DearPyGuiWrapper 13 | from cli2gui.gui.pysimplegui_wrapper import PySimpleGUIWrapper 14 | 15 | 16 | def run(buildSpec: models.FullBuildSpec) -> Any: 17 | """Establish the main entry point. 18 | 19 | Args: 20 | ---- 21 | buildSpec (types.FullBuildSpec): args that customise the application such as the theme 22 | or the function to run 23 | 24 | """ 25 | 26 | # Set the theme 27 | theme = helpers.get_base24_theme(buildSpec.theme, buildSpec.darkTheme) 28 | 29 | buildSpec.gui = buildSpec.gui.replace("pysimplegui", "psg").replace("freesimplegui", "fsg") 30 | 31 | if buildSpec.gui in [ 32 | "psg", 33 | "psgqt", 34 | "psgweb", 35 | "fsg", 36 | # "fsgqt", # cannot test on windows 37 | # "fsgweb", # bug in remi prevents this from working 38 | ]: 39 | gui_wrapper = PySimpleGUIWrapper 40 | else: 41 | gui_wrapper = DearPyGuiWrapper 42 | 43 | if gui_wrapper is PySimpleGUIWrapper: 44 | gui = gui_wrapper(theme, buildSpec.gui) 45 | elif gui_wrapper is DearPyGuiWrapper: 46 | gui = gui_wrapper(theme) 47 | 48 | def quit_callback() -> None: 49 | sys.exit(0) 50 | 51 | def run_callback(values: dict[str, Any]) -> None: 52 | args = argFormat(values, buildSpec.parser) 53 | if not buildSpec.run_function: 54 | return args 55 | buildSpec.run_function(args) 56 | return None 57 | 58 | try: 59 | gui.main(buildSpec=buildSpec, quit_callback=quit_callback, run_callback=run_callback) 60 | 61 | except KeyboardInterrupt: 62 | logging.error("Application Exited Early!") # noqa: TRY400 63 | -------------------------------------------------------------------------------- /cli2gui/application/application2args.py: -------------------------------------------------------------------------------- 1 | """Functions to create args from key/value pairs.""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import optparse 7 | from pathlib import Path 8 | from typing import Any 9 | 10 | from cli2gui.models import SEP, ParserType 11 | 12 | 13 | def processValue(key: str, value: str) -> tuple[str, Any]: 14 | if SEP not in key: 15 | return key, value or None 16 | key, _type = key.split(SEP, maxsplit=1) 17 | if len(str(value)) == 0 or value is None: 18 | return key, None 19 | if _type == "ItemType.Bool": 20 | return key, bool(value) 21 | if "ItemType.File" in _type: 22 | _, mode, encoding = _type.split(";", maxsplit=2) 23 | if "b" in mode: 24 | return key, open(value, mode=mode) 25 | return key, open(value, mode=mode, encoding=encoding) 26 | if _type == "ItemType.Path": 27 | return key, Path(value) 28 | if _type == "ItemType.Int": 29 | return key, int(value) 30 | if _type == "ItemType.Text": 31 | return key, value 32 | if _type == "ItemType.Float": 33 | return key, float(value) 34 | if _type == "ItemType.List": 35 | return key, value 36 | if _type == "ItemType.Tuple": 37 | return key, value 38 | if _type == "ItemType.DateTime": 39 | return key, value 40 | 41 | return key, value 42 | 43 | 44 | def argparseFormat(values: dict[str, Any]) -> argparse.Namespace: 45 | """Format args for argparse.""" 46 | args = {} 47 | for key, _value in values.items(): 48 | cleankey, value = processValue(key, _value) 49 | args[cleankey] = value 50 | return argparse.Namespace(**args) 51 | 52 | 53 | def optparseFormat(values: dict[str, Any]) -> tuple[optparse.Values, list[str]]: 54 | """Format args for optparse.""" 55 | args = {} 56 | for key, _value in values.items(): 57 | cleankey, value = processValue(key, _value) 58 | args[cleankey] = value 59 | return (optparse.Values(args), []) 60 | 61 | 62 | def getoptFormat(values: dict[str, Any]) -> tuple[list[Any], list[Any]]: 63 | """Format args for getopt.""" 64 | return ([processValue(key, _value) for key, _value in values.items() if _value], []) 65 | 66 | 67 | def docoptFormat(values: dict[str, Any]) -> dict[str, Any]: 68 | """Format args for docopt.""" 69 | import docopt 70 | 71 | args = {} 72 | for key, _value in values.items(): 73 | cleankey, value = processValue(key, _value) 74 | args[cleankey] = value 75 | return docopt.Dict(args) 76 | 77 | 78 | def clickFormat(values: dict[str, Any]) -> list[Any]: 79 | """Format args for click.""" 80 | args = [] 81 | for key, _value in values.items(): 82 | val = str(_value) 83 | if not callable(key) and len(val) > 0: 84 | cleankey, value = processValue(key, _value) 85 | args.extend([cleankey, value]) 86 | return args 87 | 88 | 89 | def argFormat(values: dict[str, Any], argumentParser: str | ParserType) -> Any: 90 | """Format the args for the desired parser. 91 | 92 | Args: 93 | ---- 94 | values (dict[str, Any]): values from simple gui 95 | argumentParser (str): argument parser to use 96 | 97 | Returns: 98 | ------- 99 | Any: args 100 | 101 | """ 102 | convertMap = { 103 | ParserType.OPTPARSE: optparseFormat, 104 | ParserType.ARGPARSE: argparseFormat, 105 | ParserType.DEPHELL_ARGPARSE: argparseFormat, 106 | ParserType.DOCOPT: docoptFormat, 107 | ParserType.GETOPT: getoptFormat, 108 | ParserType.CLICK: clickFormat, 109 | } 110 | if argumentParser in convertMap: 111 | return convertMap[argumentParser](values) 112 | return None 113 | -------------------------------------------------------------------------------- /cli2gui/decorators.py: -------------------------------------------------------------------------------- 1 | """Decorator and entry point for the program.""" 2 | 3 | from __future__ import annotations 4 | 5 | import getopt 6 | import sys 7 | import warnings 8 | from argparse import ArgumentParser 9 | from collections.abc import Callable 10 | from optparse import OptionParser 11 | from pathlib import Path 12 | from shlex import quote 13 | from typing import Any, Iterable 14 | 15 | from cli2gui.application import application 16 | from cli2gui.models import BuildSpec, FullBuildSpec, GUIType, ParserType 17 | from cli2gui.tojson import ( 18 | argparse2json, 19 | click2json, 20 | docopt2json, 21 | getopt2json, 22 | optparse2json, 23 | ) 24 | 25 | DO_COMMAND = "--cli2gui" 26 | DO_NOT_COMMAND = "--disable-cli2gui" 27 | 28 | 29 | def createFromParser( 30 | selfParser: Any, 31 | argsParser: tuple[Any, ...], 32 | kwargsParser: dict[Any, Any], 33 | sourcePath: str, 34 | buildSpec: BuildSpec, 35 | **kwargs: dict[Any, Any], 36 | ) -> FullBuildSpec: 37 | """Generate a buildSpec from a parser. 38 | 39 | Args: 40 | ---- 41 | selfParser (Any): A parser that acts on self. eg. ArgumentParser.parse_args 42 | argsParser (tuple[Any, ...]): A parser that acts on function 43 | arguments. eg. getopt.getopt 44 | kwargsParser (dict[Any, Any]): A parser that acts on named params 45 | sourcePath (str): Program source path 46 | buildSpec (BuildSpec): Build spec 47 | **kwargs (dict[Any, Any]): kwargs 48 | 49 | Returns: 50 | ------- 51 | types.FullBuildSpec: buildSpec to be used by the application 52 | 53 | Raises: 54 | ------ 55 | RuntimeError: Throw error if incorrect parser selected 56 | 57 | """ 58 | _ = kwargsParser 59 | runCmd = kwargs.get("target") 60 | if runCmd is None: 61 | if hasattr(sys, "frozen"): 62 | runCmd = quote(sourcePath) 63 | else: 64 | runCmd = f"{quote(sys.executable)} -u {quote(sourcePath)}" 65 | buildSpec.program_name = buildSpec.program_name or Path(sys.argv[0]).name.replace(".py", "") 66 | 67 | # CUSTOM: this seems like a pretty poor pattern to use... 68 | if buildSpec.parser == ParserType.CUSTOM: 69 | buildSpec.parser = input( 70 | f"!Custom parser selected! Choose one of: {[x.value for x in ParserType]}" 71 | ) 72 | if buildSpec.parser not in ParserType._value2member_map_: 73 | msg = f"!Custom parser must be one of: {[x.value for x in ParserType]}" 74 | raise RuntimeError(msg) 75 | 76 | parser = buildSpec.parser 77 | # Select parser 78 | convertMap = { 79 | "self": { 80 | ParserType.OPTPARSE: optparse2json.convert, 81 | ParserType.ARGPARSE: argparse2json.convert, 82 | ParserType.DEPHELL_ARGPARSE: argparse2json.convert, 83 | ParserType.DOCOPT: docopt2json.convert, 84 | }, 85 | "args": { 86 | ParserType.GETOPT: getopt2json.convert, 87 | }, 88 | } 89 | if parser in convertMap["self"]: 90 | return FullBuildSpec( 91 | **convertMap["self"][parser](selfParser).__dict__, **buildSpec.__dict__ 92 | ) 93 | if parser in convertMap["args"]: 94 | return FullBuildSpec( 95 | **convertMap["args"][parser](argsParser).__dict__, **buildSpec.__dict__ 96 | ) 97 | 98 | # click is unique in behaviour so we cant use the mapping -_- 99 | if parser == ParserType.CLICK: 100 | return FullBuildSpec( 101 | **click2json.convert(buildSpec.run_function).__dict__, **buildSpec.__dict__ 102 | ) 103 | 104 | msg = f"!Parser must be one of: {[x.value for x in ParserType]}" 105 | raise RuntimeError(msg) 106 | 107 | 108 | def Click2Gui( 109 | run_function: Callable[..., Any], 110 | gui: str | GUIType = "dearpygui", 111 | theme: str | list[str] = "", 112 | darkTheme: str | list[str] = "", 113 | image: str = "", 114 | program_name: str = "", 115 | program_description: str = "", 116 | max_args_shown: int = 5, 117 | menu: str | dict[str, Any] = "", 118 | **kwargs: dict[str, Any], 119 | ) -> None: 120 | """Use this decorator in the function containing the argument parser. 121 | Serializes data to JSON and launches the Cli2Gui application. 122 | 123 | Args: 124 | ---- 125 | run_function (Callable[..., Any]): The name of the function to call eg. 126 | gui (str, optional): Override the gui to use. Current options are: 127 | "dearpygui", "pysimplegui", "pysimpleguiqt","pysimpleguiweb","freesimplegui", 128 | Defaults to "dearpygui". 129 | theme (Union[str, list[str]], optional): Set a base24 theme. Can 130 | also pass a base24 scheme file. eg. one-light.yaml. Defaults to "". 131 | darkTheme (Union[str, list[str]], optional): Set a base24 dark 132 | theme variant. Can also pass a base24 scheme file. eg. one-dark.yaml. 133 | Defaults to "". 134 | image (str, optional): Set the program icon. File 135 | extensions can be any that PIL supports. Defaults to "". 136 | program_name (str, optional): Override the program name. 137 | Defaults to "". 138 | program_description (str, optional): Override the program 139 | description. Defaults to "". 140 | max_args_shown (int, optional): Maximum number of args shown before 141 | using a scrollbar. Defaults to 5. 142 | menu (Union[dict[str, Any]], optional): Add a menu to the program. 143 | Defaults to "". eg. THIS_DIR = str(Path(__file__).resolve().parent) 144 | menu={"File": THIS_DIR + "/file.md"} 145 | **kwargs (dict[Any, Any]): kwargs 146 | 147 | Returns: 148 | ------- 149 | Any: Runs the application 150 | 151 | """ 152 | bSpec = BuildSpec( 153 | run_function=run_function, 154 | parser=ParserType.CLICK, 155 | gui=gui, 156 | theme=theme, 157 | darkTheme=darkTheme, 158 | image=image, 159 | program_name=program_name, 160 | program_description=program_description, 161 | max_args_shown=max_args_shown, 162 | menu=menu, 163 | ) 164 | 165 | buildSpec = createFromParser( 166 | None, (), kwargs, sys.argv[0], bSpec, **{**locals(), **locals()["kwargs"]} 167 | ) 168 | return application.run(buildSpec) 169 | 170 | 171 | def Cli2Gui( 172 | run_function: Callable[..., Any], 173 | auto_enable: bool = False, 174 | parser: str | ParserType = "argparse", 175 | gui: str | ParserType = "dearpygui", 176 | theme: str | list[str] = "", 177 | darkTheme: str | list[str] = "", 178 | image: str = "", 179 | program_name: str = "", 180 | program_description: str = "", 181 | max_args_shown: int = 5, 182 | menu: str | dict[str, Any] = "", 183 | ) -> Any: 184 | """Use this decorator in the function containing the argument parser. 185 | Serialises data to JSON and launches the Cli2Gui application. 186 | 187 | Args: 188 | ---- 189 | run_function (Callable[..., Any]): The name of the function to call eg. 190 | auto_enable (bool, optional): Enable the GUI by default. If enabled by 191 | default requires `--disable-cli2gui`, otherwise requires `--cli2gui`. 192 | Defaults to False. 193 | parser (str, optional): Override the parser to use. Current 194 | options are: "argparse", "getopt", "optparse", "docopt", 195 | "dephell_argparse". Defaults to "argparse". 196 | gui (str, optional): Override the gui to use. Current options are: 197 | "dearpygui", "pysimplegui", "pysimpleguiqt","pysimpleguiweb","freesimplegui", 198 | Defaults to "dearpygui". 199 | theme (Union[str, list[str]], optional): Set a base24 theme. Can 200 | also pass a base24 scheme file. eg. one-light.yaml. Defaults to "". 201 | darkTheme (Union[str, list[str]], optional): Set a base24 dark 202 | theme variant. Can also pass a base24 scheme file. eg. one-dark.yaml. 203 | Defaults to "". 204 | image (str, optional): Set the program icon. File 205 | extensions can be any that PIL supports. Defaults to "". 206 | program_name (str, optional): Override the program name. 207 | Defaults to "". 208 | program_description (str, optional): Override the program 209 | description. Defaults to "". 210 | max_args_shown (int, optional): Maximum number of args shown before 211 | using a scrollbar. Defaults to 5. 212 | menu (Union[dict[str, Any]], optional): Add a menu to the program. 213 | Defaults to "". eg. THIS_DIR = str(Path(__file__).resolve().parent) 214 | menu={"File": THIS_DIR + "/file.md"} 215 | 216 | Returns: 217 | ------- 218 | Any: Runs the application 219 | 220 | """ 221 | bSpec = BuildSpec( 222 | run_function=run_function, 223 | parser=parser, 224 | gui=gui, 225 | theme=theme, 226 | darkTheme=darkTheme, 227 | image=image, 228 | program_name=program_name, 229 | program_description=program_description, 230 | max_args_shown=max_args_shown, 231 | menu=menu, 232 | ) 233 | 234 | def build(callingFunction: Callable[..., Any]) -> Callable[..., Any]: 235 | """Generate the buildspec and run the GUI. 236 | 237 | Args: 238 | ---- 239 | callingFunction (Callable[..., Any]): The calling function eg. 240 | ArgumentParser.parse_args 241 | 242 | Returns: 243 | ------- 244 | Callable[..., Any]: some calling function 245 | 246 | """ 247 | 248 | def runCli2Gui(self: Any, *args: Iterable[Any], **kwargs: dict[str, Any]) -> None: 249 | """Run the gui/ application. 250 | 251 | :return None: the gui/ application 252 | """ 253 | buildSpec = createFromParser( 254 | self, 255 | args, 256 | kwargs, 257 | callingFunction.__name__, 258 | bSpec, 259 | **{**locals(), **locals()["kwargs"]}, 260 | ) 261 | return application.run(buildSpec) 262 | 263 | def inner(*args: tuple[Any, Any], **kwargs: dict[Any, Any]) -> Any: 264 | """Replace the inner functions with run_cli2gui. eg. When. 265 | ArgumentParser.parse_args is called, do run_cli2gui. 266 | 267 | Returns 268 | ------- 269 | Any: Do the calling_function 270 | 271 | """ 272 | getopt.getopt = runCli2Gui 273 | getopt.gnu_getopt = runCli2Gui 274 | OptionParser.parse_args = runCli2Gui 275 | ArgumentParser.parse_args = runCli2Gui 276 | try: 277 | import docopt 278 | 279 | docopt.docopt = runCli2Gui 280 | except ImportError: 281 | pass 282 | try: 283 | import dephell_argparse 284 | 285 | dephell_argparse.Parser.parse_args = runCli2Gui 286 | except ImportError: 287 | pass 288 | # Using type=argparse.FileType('r') leads to a resource warning 289 | with warnings.catch_warnings(): 290 | warnings.filterwarnings("ignore", category=ResourceWarning) 291 | return callingFunction(*args, **kwargs) 292 | 293 | inner.__name__ = callingFunction.__name__ 294 | return inner 295 | 296 | def runWithoutCli2Gui(callingFunction: Callable[..., Any]) -> Callable[..., Any]: 297 | def inner(*args: Iterable[Any], **kwargs: dict[Any, Any]) -> Any: 298 | # Using type=argparse.FileType('r') leads to a resource warning 299 | with warnings.catch_warnings(): 300 | warnings.filterwarnings("ignore", category=ResourceWarning) 301 | return callingFunction(*args, **kwargs) 302 | 303 | inner.__name__ = callingFunction.__name__ 304 | return inner 305 | 306 | """If enabled by default requires do_not_command, otherwise requires do_command.""" 307 | if (not auto_enable and DO_COMMAND not in sys.argv) or ( 308 | auto_enable and DO_NOT_COMMAND in sys.argv 309 | ): 310 | if DO_NOT_COMMAND in sys.argv: 311 | sys.argv.remove(DO_NOT_COMMAND) 312 | return runWithoutCli2Gui 313 | return build 314 | 315 | 316 | if __name__ == "__main__": 317 | pass 318 | -------------------------------------------------------------------------------- /cli2gui/gui/FiraCode-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/cli2gui/gui/FiraCode-Regular.ttf -------------------------------------------------------------------------------- /cli2gui/gui/__init__.py: -------------------------------------------------------------------------------- 1 | """Package containing GUI classes and functionality, responsible for driving the various 2 | GUI backends supported by cli2gui.""" 3 | -------------------------------------------------------------------------------- /cli2gui/gui/abstract_gui.py: -------------------------------------------------------------------------------- 1 | """Abstract base class for GUI wrappers.""" 2 | 3 | from __future__ import annotations 4 | 5 | from abc import ABC, abstractmethod 6 | from typing import Any, Callable 7 | 8 | from cli2gui import models 9 | 10 | 11 | class AbstractGUI(ABC): 12 | """Abstract base class for GUI wrappers.""" 13 | 14 | @abstractmethod 15 | def __init__(self) -> None: 16 | """Abstract base class for GUI wrappers.""" 17 | 18 | @abstractmethod 19 | def main( 20 | self, 21 | buildSpec: models.FullBuildSpec, 22 | quit_callback: Callable[[], None], 23 | run_callback: Callable[[dict[str, Any]], None], 24 | ) -> None: 25 | """Abstract method for the main function.""" 26 | raise NotImplementedError 27 | -------------------------------------------------------------------------------- /cli2gui/gui/dearpygui_wrapper.py: -------------------------------------------------------------------------------- 1 | """Wrapper class for Dear PyGui.""" 2 | 3 | from __future__ import annotations 4 | 5 | from pathlib import Path 6 | from typing import Any, Callable 7 | 8 | import dearpygui.dearpygui as dpg 9 | 10 | from cli2gui.gui import helpers 11 | from cli2gui.gui.abstract_gui import AbstractGUI 12 | from cli2gui.models import SEP, FullBuildSpec, Group, Item, ItemType 13 | 14 | THISDIR = Path(__file__).resolve().parent 15 | 16 | 17 | def hex_to_rgb(hex_code: str) -> tuple[int, int, int, int]: 18 | """Convert a color hex code to a tuple of integers (r, g, b).""" 19 | hex_code = hex_code.lstrip("#") 20 | r = int(hex_code[0:2], 16) 21 | g = int(hex_code[2:4], 16) 22 | b = int(hex_code[4:6], 16) 23 | return (r, g, b, 255) 24 | 25 | 26 | class DearPyGuiWrapper(AbstractGUI): 27 | """Wrapper class for Dear PyGui.""" 28 | 29 | def __init__(self, base24Theme: list[str]) -> None: 30 | """Dearpygui wrapper class. 31 | 32 | :param list[str] base24Theme: list representing a base24 theme. Containing 24 elements 33 | (of hex strings like "#e7e7e9") 34 | """ 35 | self.base24Theme = base24Theme 36 | super().__init__() 37 | 38 | def _helpText(self, item: Item) -> None: 39 | dpg.add_text( 40 | helpers.stringSentencecase(f"\n- {item.dest}: {item.commands}"), 41 | color=hex_to_rgb(self.base24Theme[13]), 42 | ) 43 | dpg.add_text(helpers.stringSentencecase(item.help)) 44 | 45 | def _helpFlagWidget(self, item: Item) -> None: 46 | with dpg.group(horizontal=False): 47 | self._helpText(item) 48 | dpg.add_checkbox(tag=item.dest, default_value=(item.default or False)) 49 | 50 | def _helpTextWidget(self, item: Item) -> None: 51 | with dpg.group(horizontal=False): 52 | self._helpText(item) 53 | dpg.add_input_text(tag=item.dest, default_value=(item.default or "")) 54 | 55 | def _helpFloatCounterWidget(self, item: Item) -> None: 56 | with dpg.group(horizontal=False): 57 | self._helpText(item) 58 | dpg.add_input_float( 59 | format="%.9f", 60 | tag=item.dest, 61 | default_value=float(item.default or 0), 62 | min_value=-(2**16), 63 | max_value=2**16, 64 | step=1, 65 | step_fast=10, 66 | ) 67 | 68 | def _helpIntCounterWidget(self, item: Item) -> None: 69 | with dpg.group(horizontal=False): 70 | self._helpText(item) 71 | dpg.add_input_int( 72 | tag=item.dest, 73 | default_value=int(item.default or 0), 74 | min_value=-(2**16), 75 | max_value=2**16, 76 | step=1, 77 | step_fast=10, 78 | ) 79 | 80 | def _helpFileWidget(self, item: Item) -> None: 81 | """Create a UI element with an input text field and a file picker.""" 82 | 83 | def file_picker_callback(_sender: str, app_data: dict[str, Any]) -> None: 84 | """Update the input text field with the selected file path.""" 85 | 86 | file_path = "" 87 | # User may have selected and edited, or just written a name 88 | user_input_path = Path(app_data.get("file_path_name", "")) 89 | 90 | # Get the selection if possible 91 | selected_path = Path(next(iter(app_data.get("selections", {}).values()), "")) 92 | 93 | if user_input_path.stem == selected_path.stem: 94 | file_path = selected_path 95 | # User may have selected and edited, or just written a name 96 | else: 97 | file_path = user_input_path.stem + Path(item.default or "").suffix 98 | dpg.set_value(item.dest, str(file_path)) 99 | 100 | with dpg.group(horizontal=False): 101 | self._helpText(item) 102 | 103 | dpg.add_input_text(tag=item.dest, default_value=(item.default or "")) 104 | 105 | dpg.add_button( 106 | label="Browse", callback=lambda: dpg.show_item(f"{item.dest}_file_dialog") 107 | ) 108 | 109 | with dpg.file_dialog( 110 | directory_selector=False, 111 | show=False, 112 | callback=file_picker_callback, 113 | id=f"{item.dest}_file_dialog", 114 | width=650, 115 | height=400, 116 | file_count=1, 117 | ): 118 | dpg.add_file_extension(".*", color=hex_to_rgb(self.base24Theme[13])) 119 | 120 | def _helpDropdownWidget(self, item: Item) -> None: 121 | with dpg.group(horizontal=False): 122 | self._helpText(item) 123 | dpg.add_combo(tag=item.dest, items=item.additional_properties["choices"]) 124 | 125 | def addWidgetFromItem(self, item: Item) -> None: 126 | """Select a widget based on the item type. 127 | 128 | :param Item item: the item 129 | """ 130 | functionMap = { 131 | ItemType.Bool: self._helpFlagWidget, 132 | ItemType.File: self._helpFileWidget, 133 | ItemType.FileWrite: self._helpFileWidget, 134 | ItemType.Path: self._helpFileWidget, 135 | ItemType.Choice: self._helpDropdownWidget, 136 | ItemType.Int: self._helpIntCounterWidget, 137 | ItemType.Text: self._helpTextWidget, 138 | ItemType.Float: self._helpFloatCounterWidget, 139 | ItemType.List: self._helpTextWidget, 140 | ItemType.Tuple: self._helpTextWidget, 141 | ItemType.DateTime: self._helpTextWidget, 142 | } 143 | if item.type in functionMap: 144 | return functionMap[item.type](item) 145 | return None 146 | 147 | def addItemsAndGroups( 148 | self, 149 | section: Group, 150 | ) -> list[Item]: 151 | """Items and groups and return a list of these so we can get values from the dpg widgets. 152 | 153 | :param Group section: section with a name to display and items 154 | :return list[Item]: flattened list of items 155 | """ 156 | dpg.add_text( 157 | f"=== {helpers.stringTitlecase(section.name, ' ')} ===", 158 | color=hex_to_rgb(self.base24Theme[14]), 159 | ) 160 | 161 | items = [] 162 | 163 | for item in section.arg_items: 164 | if item.type == ItemType.RadioGroup: 165 | rGroup = item.additional_properties["radio"] 166 | for rElement in rGroup: 167 | self.addWidgetFromItem(rElement) 168 | items.append(rElement) 169 | else: 170 | self.addWidgetFromItem(item) 171 | items.append(item) 172 | for group in section.groups: 173 | items.extend(self.addItemsAndGroups(group)) 174 | 175 | return items 176 | 177 | def open_menu_item(self, sender: str, _app_data: None) -> None: 178 | """Open a menu item. 179 | 180 | :param _type_ sender: file to open 181 | :param _type_ _app_data: [unused] 182 | """ 183 | f = Path(sender) 184 | if f.is_file(): 185 | with dpg.window(label=f.name, width=825, height=400, horizontal_scrollbar=True): 186 | dpg.add_text(helpers.read_file(sender)) 187 | else: 188 | with dpg.window(label="Error"): 189 | dpg.add_text(f"File {sender} Not Found :(") 190 | 191 | def main( 192 | self, 193 | buildSpec: FullBuildSpec, 194 | quit_callback: Callable[[], None], 195 | run_callback: Callable[[dict[str, Any]], None], 196 | ) -> None: 197 | """Run the gui (dpg) with a given buildSpec, quit_callback, and run_callback. 198 | 199 | - Theming + Configure dpg 200 | - Menu Prep 201 | - Create Window, set up Menu and Widgets 202 | - Then, start dpg 203 | 204 | :param FullBuildSpec buildSpec: Full cli parse/ build spec 205 | :param Callable[[], None] quit_callback: generic callable used to quit 206 | :param Callable[[dict[str, Any]], None] run_callback: generic callable used to run 207 | """ 208 | 209 | ################ 210 | # Theming + Configure dpg 211 | ################ 212 | 213 | dpg.create_context() 214 | with dpg.font_registry(): 215 | default_font = dpg.add_font(f"{THISDIR}/FiraCode-Regular.ttf", 17) 216 | 217 | with dpg.theme() as global_theme, dpg.theme_component(dpg.mvAll): 218 | dpg.add_theme_color( 219 | dpg.mvThemeCol_WindowBg, 220 | hex_to_rgb(self.base24Theme[16]), 221 | category=dpg.mvThemeCat_Core, 222 | ) 223 | dpg.add_theme_color( 224 | dpg.mvThemeCol_FrameBg, 225 | hex_to_rgb(self.base24Theme[17]), 226 | category=dpg.mvThemeCat_Core, 227 | ) 228 | dpg.add_theme_color( 229 | dpg.mvThemeCol_PopupBg, 230 | hex_to_rgb(self.base24Theme[17]), 231 | category=dpg.mvThemeCat_Core, 232 | ) 233 | dpg.add_theme_color( 234 | dpg.mvThemeCol_FrameBgActive, 235 | hex_to_rgb(self.base24Theme[17]), 236 | category=dpg.mvThemeCat_Core, 237 | ) 238 | dpg.add_theme_color( 239 | dpg.mvThemeCol_FrameBgHovered, 240 | hex_to_rgb(self.base24Theme[17]), 241 | category=dpg.mvThemeCat_Core, 242 | ) 243 | dpg.add_theme_color( 244 | dpg.mvThemeCol_Text, 245 | hex_to_rgb(self.base24Theme[6]), 246 | category=dpg.mvThemeCat_Core, 247 | ) 248 | dpg.add_theme_color( 249 | dpg.mvThemeCol_MenuBarBg, 250 | hex_to_rgb(self.base24Theme[0]), 251 | category=dpg.mvThemeCat_Core, 252 | ) 253 | dpg.add_theme_color( 254 | dpg.mvThemeCol_ScrollbarBg, 255 | hex_to_rgb(self.base24Theme[2]), 256 | category=dpg.mvThemeCat_Core, 257 | ) 258 | dpg.add_theme_color( 259 | dpg.mvThemeCol_ScrollbarGrab, 260 | hex_to_rgb(self.base24Theme[17]), 261 | category=dpg.mvThemeCat_Core, 262 | ) 263 | dpg.add_theme_color( 264 | dpg.mvThemeCol_Header, 265 | hex_to_rgb(self.base24Theme[0]), 266 | category=dpg.mvThemeCat_Core, 267 | ) 268 | dpg.add_theme_color( 269 | dpg.mvThemeCol_HeaderHovered, 270 | hex_to_rgb(self.base24Theme[1]), 271 | category=dpg.mvThemeCat_Core, 272 | ) 273 | dpg.add_theme_color( 274 | dpg.mvThemeCol_HeaderActive, 275 | hex_to_rgb(self.base24Theme[1]), 276 | category=dpg.mvThemeCat_Core, 277 | ) 278 | dpg.add_theme_color( 279 | dpg.mvThemeCol_ScrollbarGrabActive, 280 | hex_to_rgb(self.base24Theme[1]), 281 | category=dpg.mvThemeCat_Core, 282 | ) 283 | dpg.add_theme_color( 284 | dpg.mvThemeCol_ScrollbarGrabHovered, 285 | hex_to_rgb(self.base24Theme[1]), 286 | category=dpg.mvThemeCat_Core, 287 | ) 288 | dpg.add_theme_color( 289 | dpg.mvThemeCol_Button, 290 | hex_to_rgb(self.base24Theme[1]), 291 | category=dpg.mvThemeCat_Core, 292 | ) 293 | dpg.add_theme_color( 294 | dpg.mvThemeCol_ButtonHovered, 295 | hex_to_rgb(self.base24Theme[2]), 296 | category=dpg.mvThemeCat_Core, 297 | ) 298 | dpg.add_theme_color( 299 | dpg.mvThemeCol_ButtonActive, 300 | hex_to_rgb(self.base24Theme[2]), 301 | category=dpg.mvThemeCat_Core, 302 | ) 303 | dpg.add_theme_color( 304 | dpg.mvThemeCol_Border, 305 | hex_to_rgb(self.base24Theme[2]), 306 | category=dpg.mvThemeCat_Core, 307 | ) 308 | dpg.add_theme_color( 309 | dpg.mvThemeCol_BorderShadow, 310 | hex_to_rgb(self.base24Theme[2]), 311 | category=dpg.mvThemeCat_Core, 312 | ) 313 | dpg.add_theme_color( 314 | dpg.mvThemeCol_CheckMark, 315 | hex_to_rgb(self.base24Theme[14]), 316 | category=dpg.mvThemeCat_Core, 317 | ) 318 | dpg.add_theme_color( 319 | dpg.mvThemeCol_TitleBg, 320 | hex_to_rgb(self.base24Theme[1]), 321 | category=dpg.mvThemeCat_Core, 322 | ) 323 | dpg.add_theme_color( 324 | dpg.mvThemeCol_TitleBgActive, 325 | hex_to_rgb(self.base24Theme[14]), 326 | category=dpg.mvThemeCat_Core, 327 | ) 328 | dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 5, category=dpg.mvThemeCat_Core) 329 | dpg.add_theme_style(dpg.mvStyleVar_FrameBorderSize, 1, category=dpg.mvThemeCat_Core) 330 | 331 | dpg.bind_theme(global_theme) 332 | dpg.bind_font(default_font) 333 | 334 | ################ 335 | # Menu Prep 336 | ################ 337 | 338 | if len(buildSpec.menu) == 0: 339 | buildSpec.menu = {} 340 | 341 | elif isinstance(buildSpec.menu, str): 342 | buildSpec.menu = {"File": buildSpec.menu} 343 | 344 | ################ 345 | # Create Window, set up Menu and Widgets 346 | ################ 347 | 348 | # Define "Run" and "Exit" buttons 349 | def _run_callback() -> None: 350 | _items: list[Item] = [item for item in items if item.dest] 351 | myd = {} 352 | for item in _items: 353 | value = dpg.get_value(item.dest) 354 | key = f"{item.dest}{SEP}{item.type}" 355 | if item.type in [ItemType.File, ItemType.FileWrite]: 356 | prop = item.additional_properties 357 | key += f";{prop.get('file_mode')};{prop.get('file_encoding')}" 358 | 359 | myd[key] = value 360 | 361 | run_callback(myd) 362 | 363 | def close_dpg() -> None: 364 | dpg.destroy_context() 365 | quit_callback() 366 | 367 | dpg.create_viewport( 368 | title=buildSpec.program_name, 369 | width=875, 370 | height=min(max(400, 120 * buildSpec.max_args_shown), 1080), 371 | ) 372 | 373 | dpg.set_exit_callback(close_dpg) 374 | 375 | with dpg.window(label="", tag="primary", on_close=close_dpg): 376 | if len(buildSpec.menu) > 0: 377 | with dpg.menu_bar(), dpg.menu(label="Open"): 378 | for menu_item in buildSpec.menu: 379 | dpg.add_menu_item( 380 | label=menu_item, 381 | tag=buildSpec.menu[menu_item], 382 | callback=self.open_menu_item, 383 | ) 384 | # Program Description 385 | dpg.add_text( 386 | helpers.stringSentencecase( 387 | buildSpec.program_description or buildSpec.parser_description 388 | ) 389 | ) 390 | 391 | # Add widgets 392 | items = [] 393 | for widget in buildSpec.widgets: 394 | items.extend(self.addItemsAndGroups(widget)) 395 | 396 | dpg.add_button(label="Run", callback=_run_callback) 397 | dpg.add_button(label="Exit", callback=close_dpg) 398 | 399 | ################ 400 | # Start dpg 401 | ################ 402 | 403 | dpg.setup_dearpygui() 404 | dpg.show_viewport() 405 | dpg.set_primary_window(window="primary", value=True) 406 | dpg.start_dearpygui() 407 | dpg.destroy_context() 408 | -------------------------------------------------------------------------------- /cli2gui/gui/helpers.py: -------------------------------------------------------------------------------- 1 | """Basic helper package containing commonly used functions, such as isDarkMode, 2 | get_base24_theme etc.""" 3 | 4 | from __future__ import annotations 5 | 6 | from pathlib import Path 7 | 8 | try: 9 | from getostheme import isDarkMode 10 | except ImportError: 11 | 12 | def isDarkMode() -> bool: 13 | """Monkeypatch for getostheme.isDarkMode.""" 14 | return True 15 | 16 | 17 | import yaml 18 | 19 | 20 | def _themeFromFile(themeFile: str) -> list[str]: 21 | """Set the base24 theme from a base24 scheme.yaml to the application. 22 | 23 | Args: 24 | ---- 25 | themeFile (str): path to file 26 | 27 | Returns: 28 | ------- 29 | list[str]: theme to set 30 | 31 | """ 32 | schemeDictTheme = yaml.safe_load(Path(themeFile).read_text(encoding="utf-8")) 33 | return ["#" + schemeDictTheme["palette"][f"base{x:02X}"] for x in range(24)] 34 | 35 | 36 | def get_base24_theme( 37 | theme: str | list[str], 38 | darkTheme: str | list[str], 39 | ) -> list[str]: 40 | """Set the base24 theme to the application. 41 | 42 | Args: 43 | ---- 44 | theme (Union[str, list[str]]): the light theme 45 | darkTheme (Union[str, list[str]]): the dark theme 46 | 47 | """ 48 | # Light theme 49 | theme = theme or [ 50 | "#e7e7e9", 51 | "#dfdfe1", 52 | "#cacace", 53 | "#a0a1a7", 54 | "#696c77", 55 | "#383a42", 56 | "#202227", 57 | "#090a0b", 58 | "#ca1243", 59 | "#c18401", 60 | "#febb2a", 61 | "#50a14f", 62 | "#0184bc", 63 | "#4078f2", 64 | "#a626a4", 65 | "#986801", 66 | "#f0f0f1", 67 | "#fafafa", 68 | "#ec2258", 69 | "#f4a701", 70 | "#6db76c", 71 | "#01a7ef", 72 | "#709af5", 73 | "#d02fcd", 74 | ] 75 | if isinstance(theme, str): 76 | theme = _themeFromFile(theme) 77 | 78 | # Dark theme 79 | darkTheme = darkTheme or [ 80 | "#282c34", 81 | "#3f4451", 82 | "#4f5666", 83 | "#545862", 84 | "#9196a1", 85 | "#abb2bf", 86 | "#e6e6e6", 87 | "#ffffff", 88 | "#e06c75", 89 | "#d19a66", 90 | "#e5c07b", 91 | "#98c379", 92 | "#56b6c2", 93 | "#61afef", 94 | "#c678dd", 95 | "#be5046", 96 | "#21252b", 97 | "#181a1f", 98 | "#ff7b86", 99 | "#efb074", 100 | "#b1e18b", 101 | "#63d4e0", 102 | "#67cdff", 103 | "#e48bff", 104 | ] 105 | if isinstance(darkTheme, str): 106 | darkTheme = _themeFromFile(darkTheme) 107 | 108 | return darkTheme if isDarkMode() else theme 109 | 110 | 111 | def stringTitlecase(string: str, splitStr: str = "ALL") -> str: 112 | """Convert a string to title case.""" 113 | 114 | titleCase = "" 115 | if len(string) > 0: 116 | if splitStr == "ALL": 117 | titleCase = " ".join( 118 | (part[0].upper() + part[1:]) for part in string.replace("-", "_").split("_") 119 | ) 120 | else: 121 | titleCase = " ".join((part[0].upper() + part[1:]) for part in string.split(splitStr)) 122 | return titleCase 123 | 124 | 125 | def stringSentencecase(string: str) -> str: 126 | """Convert a string to sentence case.""" 127 | 128 | if string: 129 | return string[0].upper() + string[1:] 130 | return "" 131 | 132 | 133 | def read_file(file_path: str, maxLines: int = 200) -> str: 134 | """Get the contents of a file path, attempt to parse with catpandoc..pandoc2plain. 135 | 136 | :param str file_path: path to the file (absolute recommended) 137 | :return str: file contents 138 | """ 139 | try: 140 | from catpandoc.application import pandoc2plain 141 | 142 | lines = pandoc2plain(file_path, 80).split("\n") 143 | except ImportError: 144 | lines = Path(file_path).read_text(encoding="utf-8").split("\n") 145 | 146 | if len(lines) > maxLines: 147 | popupText = "\n".join(lines[:maxLines]) + "\n\nMORE TEXT IN SRC FILE" 148 | else: 149 | popupText = "\n".join(lines) 150 | 151 | return popupText 152 | -------------------------------------------------------------------------------- /cli2gui/gui/pysimplegui_wrapper.py: -------------------------------------------------------------------------------- 1 | """Wrapper class for PySimpleGUI.""" 2 | 3 | from __future__ import annotations 4 | 5 | import io 6 | import logging 7 | from typing import Any, Callable 8 | 9 | from PIL import Image, ImageTk 10 | 11 | from cli2gui.gui import helpers 12 | from cli2gui.gui.abstract_gui import AbstractGUI 13 | from cli2gui.models import SEP, FullBuildSpec, Group, Item, ItemType 14 | 15 | 16 | class PySimpleGUIWrapper(AbstractGUI): 17 | """Wrapper class for PySimpleGUI.""" 18 | 19 | def __init__(self, base24Theme: list[str], psg_lib: str) -> None: 20 | """PySimpleGUI wrapper class. 21 | 22 | :param list[str] base24Theme: list representing a base24 theme. Containing 24 elements 23 | (of hex strings like "#e7e7e9") 24 | :param str psg_lib: string representing the pysimplegui lib to use 25 | """ 26 | super().__init__() 27 | 28 | if psg_lib == "psg": 29 | import PySimpleGUI as gui_lib 30 | elif psg_lib == "psgqt": 31 | import PySimpleGUIQt as gui_lib 32 | elif psg_lib == "psgweb": 33 | import PySimpleGUIWeb as gui_lib 34 | elif psg_lib == "fsgqt": 35 | import FreeSimpleGUIQt as gui_lib 36 | elif psg_lib == "fsgweb": 37 | import FreeSimpleGUIWeb as gui_lib 38 | else: 39 | import FreeSimpleGUI as gui_lib 40 | 41 | self.sg = gui_lib 42 | self.psg_lib = psg_lib 43 | self.sizes = { 44 | "title_size": 18, 45 | "label_size": (30, None), 46 | "input_size": (30, 1), 47 | "button": (10, 1), 48 | "padding": (5, 10), 49 | "help_text_size": 14, 50 | "text_size": 11, 51 | } 52 | 53 | if psg_lib not in ["psg", "fsg"]: 54 | self.sizes = { 55 | "title_size": 18, 56 | "label_size": (600, None), 57 | "input_size": (30, 1), 58 | "button": (10, 1), 59 | "padding": (5, 10), 60 | "help_text_size": 14, 61 | "text_size": 11, 62 | } 63 | accent = {"red": 8, "blue": 13, "green": 11, "purple": 14} 64 | self.sg.LOOK_AND_FEEL_TABLE["theme"] = { 65 | "BACKGROUND": base24Theme[16], 66 | "TEXT": base24Theme[6], 67 | "INPUT": base24Theme[17], 68 | "TEXT_INPUT": base24Theme[6], 69 | "SCROLL": base24Theme[17], 70 | "BUTTON": (base24Theme[6], base24Theme[0]), 71 | "PROGRESS": (base24Theme[accent["purple"]], base24Theme[0]), 72 | "BORDER": 0, 73 | "SLIDER_DEPTH": 0, 74 | "PROGRESS_DEPTH": 0, 75 | } 76 | self.sg.theme("theme") 77 | 78 | def _inputText(self, key: str, default: str | None = None) -> Any: 79 | """Return an input text field.""" 80 | return self.sg.InputText( 81 | default or "", 82 | size=self.sizes["input_size"], 83 | pad=self.sizes["padding"], 84 | key=key, 85 | font=("sans", self.sizes["text_size"]), 86 | ) 87 | 88 | def _spin(self, key: str, default: str | None = None) -> Any: 89 | """Return an input text field.""" 90 | return self.sg.Spin( 91 | list(range(-50, 51)), 92 | initial_value=default or 0, 93 | size=self.sizes["input_size"], 94 | pad=self.sizes["padding"], 95 | key=key, 96 | font=("sans", self.sizes["text_size"]), 97 | ) 98 | 99 | def _check(self, key: str, default: str | None = None) -> Any: 100 | """Return a checkbox.""" 101 | return self.sg.Check( 102 | "", 103 | size=self.sizes["input_size"], 104 | pad=self.sizes["padding"], 105 | key=key, 106 | default=bool(default or ""), 107 | ) 108 | 109 | def _button(self, text: str) -> Any: 110 | """Return a button.""" 111 | return self.sg.Button( 112 | text, 113 | size=self.sizes["button"], 114 | pad=self.sizes["padding"], 115 | font=("sans", self.sizes["text_size"]), 116 | ) 117 | 118 | def _label(self, text: str, font: int = 11) -> Any: 119 | """Return a label.""" 120 | return self.sg.Text( 121 | text, 122 | size=( 123 | int(self.sizes["label_size"][0] * 11 / font), 124 | self.sizes["label_size"][1], 125 | ), 126 | pad=self.sizes["padding"], 127 | font=("sans", font), 128 | ) 129 | 130 | def _dropdown(self, key: str, argItems: list[str]) -> Any: 131 | """Return a dropdown.""" 132 | return self.sg.Drop( 133 | tuple(argItems), 134 | size=self.sizes["input_size"], 135 | pad=self.sizes["padding"], 136 | key=key, 137 | ) 138 | 139 | def _fileBrowser( 140 | self, 141 | key: str, 142 | default: str | None = None, 143 | _type: ItemType = ItemType.File, 144 | additional_properties: dict | None = None, 145 | ) -> list[Any]: 146 | """Return a fileBrowser button and field.""" 147 | prop = additional_properties or {} 148 | 149 | height = self.sizes["input_size"][1] 150 | width = self.sizes["input_size"][0] 151 | 152 | key = f"{key}{SEP}{_type}" 153 | if _type in [ItemType.FileWrite, ItemType.File]: 154 | key += f";{prop.get('file_mode')};{prop.get('file_encoding')}" 155 | 156 | browser = self.sg.FileBrowse( 157 | key="@@" + key, 158 | size=(int(width / 3), height), 159 | pad=(0, self.sizes["padding"][1]), 160 | ) 161 | 162 | if _type in [ItemType.FileWrite, ItemType.Path]: 163 | browser = self.sg.SaveAs( 164 | button_text="Select/Create", 165 | key="@@" + key, 166 | size=(int(width / 3), height), 167 | pad=(0, self.sizes["padding"][1]), 168 | ) 169 | fb: list[Any] = [ 170 | self.sg.InputText( 171 | default or "", 172 | size=(width - int(width / 3), height), 173 | pad=(0, self.sizes["padding"][1]), 174 | key=key, 175 | font=("sans", self.sizes["text_size"]), 176 | ), 177 | browser, 178 | ] 179 | return fb 180 | 181 | """Different sized labels 182 | """ 183 | 184 | def _helpArgName(self, displayName: str, commands: list[str]) -> Any: 185 | """Return a label for the arg name.""" 186 | return self._label("- " + helpers.stringTitlecase(displayName) + ": " + str(commands), 14) 187 | 188 | def _helpArgHelp(self, helpText: str) -> Any: 189 | """Return a label for the arg help text.""" 190 | return self._label(helpers.stringSentencecase(helpText)) 191 | 192 | def _helpArgNameAndHelp(self, commands: list[str], helpText: str, displayName: str) -> Any: 193 | """Return a column containing the argument name and help text.""" 194 | return self.sg.Column( 195 | [[self._helpArgName(displayName, commands)], [self._helpArgHelp(helpText)]], 196 | pad=(0, 0), 197 | ) 198 | 199 | def _title(self, text: str, image: str = "") -> list[Any]: 200 | """Return a set of self that make up the application header.""" 201 | programTitle: list[Any] = [ 202 | self.sg.Text(text, pad=self.sizes["padding"], font=("sans", self.sizes["title_size"])) 203 | ] 204 | if image: 205 | programTitle = [ 206 | self.sg.Image(data=self.getImgData(image, first=True)), 207 | self.sg.Text( 208 | text, 209 | pad=self.sizes["padding"], 210 | font=("sans", self.sizes["title_size"]), 211 | ), 212 | ] 213 | return programTitle 214 | 215 | """Generate help widget group 216 | """ 217 | 218 | def _helpFlagWidget( 219 | self, 220 | item: Item, 221 | ) -> list[Any]: 222 | """Return a set of self that make up an arg with true/ false.""" 223 | return [ 224 | self._helpArgNameAndHelp(item.commands, item.help, item.display_name), 225 | self.sg.Column( 226 | [[self._check(f"{item.dest}{SEP}{item.type}", default=item.default)]], pad=(0, 0) 227 | ), 228 | ] 229 | 230 | def _helpTextWidget( 231 | self, 232 | item: Item, 233 | ) -> list[Any]: 234 | """Return a set of self that make up an arg with text.""" 235 | return [ 236 | self._helpArgNameAndHelp(item.commands, item.help, item.display_name), 237 | self.sg.Column( 238 | [[self._inputText(f"{item.dest}{SEP}{item.type}", default=item.default)]], 239 | pad=(0, 0), 240 | ), 241 | ] 242 | 243 | def _helpCounterWidget( 244 | self, 245 | item: Item, 246 | ) -> list[Any]: 247 | """Return a set of self that make up an arg with text.""" 248 | return [ 249 | self._helpArgNameAndHelp(item.commands, item.help, item.display_name), 250 | self.sg.Column( 251 | [[self._spin(f"{item.dest}{SEP}{item.type}", default=item.default)]], pad=(0, 0) 252 | ), 253 | ] 254 | 255 | def _helpFileWidget( 256 | self, 257 | item: Item, 258 | ) -> list[Any]: 259 | """Return a set of self that make up an arg with a file.""" 260 | return [ 261 | self._helpArgNameAndHelp(item.commands, item.help, item.display_name), 262 | self.sg.Column( 263 | [self._fileBrowser(item.dest, item.default, item.type, item.additional_properties)], 264 | pad=(0, 0), 265 | ), 266 | ] 267 | 268 | def _helpDropdownWidget( 269 | self, 270 | item: Item, 271 | ) -> list[Any]: 272 | """Return a set of self that make up an arg with a choice.""" 273 | return [ 274 | self._helpArgNameAndHelp(item.commands, item.help, item.display_name), 275 | self.sg.Column( 276 | [ 277 | [ 278 | self._dropdown( 279 | f"{item.dest}{SEP}{item.type}", item.additional_properties["choices"] 280 | ) 281 | ] 282 | ], 283 | pad=(0, 0), 284 | ), 285 | ] 286 | 287 | def addWidgetFromItem(self, item: Item) -> list[Any]: 288 | """Select a widget based on the item type. 289 | 290 | :param Item item: the item 291 | """ 292 | functionMap = { 293 | ItemType.Bool: self._helpFlagWidget, 294 | ItemType.File: self._helpFileWidget, 295 | ItemType.FileWrite: self._helpFileWidget, 296 | ItemType.Path: self._helpFileWidget, 297 | ItemType.Choice: self._helpDropdownWidget, 298 | ItemType.Int: self._helpCounterWidget, 299 | ItemType.Text: self._helpTextWidget, 300 | ItemType.Float: self._helpTextWidget, 301 | ItemType.List: self._helpTextWidget, 302 | ItemType.Tuple: self._helpTextWidget, 303 | ItemType.DateTime: self._helpTextWidget, 304 | } 305 | if item.type in functionMap: 306 | return functionMap[item.type]( 307 | item, 308 | ) 309 | return [] 310 | 311 | def generatePopup( 312 | self, 313 | buildSpec: FullBuildSpec, 314 | values: dict[Any, Any] | list[Any], 315 | ) -> Any: 316 | """Create the popup window. 317 | 318 | Args: 319 | ---- 320 | buildSpec (FullBuildSpec): [description] 321 | values (Union[dict[Any, Any]): Returned when a button is clicked. Such 322 | as the menu 323 | 324 | Returns: 325 | ------- 326 | Window: A PySimpleGui Window 327 | 328 | """ 329 | maxLines = 30 if self.psg_lib == "psgqt" else 200 330 | popupText = helpers.read_file(buildSpec.menu[values[0]], maxLines) 331 | 332 | if self.psg_lib in ["psg", "fsg"]: 333 | popupLayout = [ 334 | self._title(values[0]), 335 | [ 336 | self.sg.Column( 337 | [ 338 | [ 339 | self.sg.Text( 340 | text=popupText, 341 | size=(850, maxLines + 10), 342 | font=("Courier", self.sizes["text_size"]), 343 | ) 344 | ] 345 | ], 346 | size=(850, 400), 347 | pad=(0, 0), 348 | scrollable=True, 349 | vertical_scroll_only=True, 350 | ) 351 | ], 352 | ] 353 | else: 354 | popupLayout = [ 355 | self._title(values[0]), 356 | [ 357 | self.sg.Text( 358 | text=popupText, 359 | size=(850, (self.sizes["text_size"]) * (2 * maxLines + 10)), 360 | font=("Courier", self.sizes["text_size"]), 361 | ) 362 | ], 363 | ] 364 | return self.sg.Window( 365 | values[0], 366 | popupLayout, 367 | alpha_channel=0.95, 368 | icon=self.getImgData(buildSpec.image, first=True) if buildSpec.image else None, 369 | ) 370 | 371 | def addItemsAndGroups( 372 | self, 373 | section: Group, 374 | ) -> list[list[Any]]: 375 | """Items and groups and return a list of psg Elements. 376 | 377 | :param Group section: section with a name to display and items 378 | :return list[list[Element]]: updated argConstruct 379 | 380 | """ 381 | 382 | argConstruct: list[list[Any]] = [] 383 | 384 | argConstruct.append([self._label(helpers.stringTitlecase(section.name, " "), 14)]) 385 | for item in section.arg_items: 386 | if item.type == ItemType.RadioGroup: 387 | rGroup = item.additional_properties["radio"] 388 | for rElement in rGroup: 389 | argConstruct.append(self.addWidgetFromItem(rElement)) 390 | else: 391 | argConstruct.append(self.addWidgetFromItem(item)) 392 | for group in section.groups: 393 | argConstruct.extend(self.addItemsAndGroups(group)) 394 | return argConstruct 395 | 396 | def createLayout( 397 | self, 398 | buildSpec: FullBuildSpec, 399 | menu: str | list[str], 400 | ) -> list[list[Any]]: 401 | """Create the pysimplegui layout from the build spec. 402 | 403 | :param FullBuildSpec buildSpec: build spec containing widget 404 | :param str | list[str] menu: menu definition. containing menu keys 405 | 406 | :return list[list[Any]]: list of self (layout list) 407 | 408 | """ 409 | argConstruct = [] 410 | for widget in buildSpec.widgets: 411 | argConstruct.extend(self.addItemsAndGroups(widget)) 412 | 413 | # Set the layout 414 | layout: list[list[Any]] = [[]] 415 | if isinstance(menu, list): 416 | layout: list[list[Any]] = [[self.sg.Menu([["Open", menu]], tearoff=True)]] 417 | 418 | layout.extend( 419 | [ 420 | self._title(str(buildSpec.program_name), buildSpec.image), 421 | [ 422 | self._label( 423 | helpers.stringSentencecase( 424 | buildSpec.program_description 425 | if buildSpec.program_description 426 | else buildSpec.parser_description 427 | ) 428 | ) 429 | ], 430 | ] 431 | ) 432 | if len(argConstruct) > buildSpec.max_args_shown and self.psg_lib in ( 433 | "psg", 434 | "fsg", 435 | ): 436 | layout.append( 437 | [ 438 | self.sg.Column( 439 | argConstruct, 440 | size=( 441 | 850, 442 | min( 443 | max( 444 | 280, 445 | buildSpec.max_args_shown 446 | * 3.5 447 | * (self.sizes["help_text_size"] + self.sizes["text_size"]), 448 | ), 449 | 700, 450 | ), 451 | ), 452 | pad=(0, 0), 453 | scrollable=True, 454 | vertical_scroll_only=True, 455 | ) 456 | ] 457 | ) 458 | else: 459 | layout.extend(argConstruct) 460 | layout.append([self._button("Run"), self._button("Exit")]) 461 | return layout 462 | 463 | def main( 464 | self, 465 | buildSpec: FullBuildSpec, 466 | quit_callback: Callable[[], None], 467 | run_callback: Callable[[dict[str, Any]], None], 468 | ) -> None: 469 | """Run the gui (psg) with a given buildSpec, quit_callback, and run_callback. 470 | 471 | :param FullBuildSpec buildSpec: Full cli parse/ build spec 472 | :param Callable[[], None] quit_callback: generic callable used to quit 473 | :param Callable[[dict[str, Any]], None] run_callback: generic callable used to run 474 | """ 475 | menu = list(buildSpec.menu) if buildSpec.menu else "" 476 | 477 | layout = self.createLayout(buildSpec=buildSpec, menu=menu) 478 | 479 | # Build window from args 480 | window = self.sg.Window( 481 | buildSpec.program_name, 482 | layout, 483 | alpha_channel=0.95, 484 | icon=self.getImgData(buildSpec.image, first=True) if buildSpec.image else None, 485 | ) 486 | 487 | # While the application is running 488 | while True: 489 | eventAndValues: tuple[Any, dict[Any, Any] | list[Any]] = window.read() 490 | event, values = eventAndValues 491 | if event in (None, "Exit"): 492 | quit_callback() 493 | try: 494 | # Create and open the popup window for the menu item 495 | if values is not None: 496 | if 0 in values and values[0] is not None: 497 | popup = self.generatePopup(buildSpec, values) 498 | popup.read() 499 | args = {} 500 | for key in values: 501 | if key != 0: 502 | args[key] = values[key] 503 | run_callback(args) 504 | 505 | except Exception: 506 | logging.exception("Something went wrong: ") 507 | 508 | def getImgData(self, imagePath: str, *, first: bool = False) -> bytes: 509 | """Generate image data using PIL.""" 510 | img = Image.open(imagePath) 511 | img.thumbnail((self.sizes["title_size"] * 3, self.sizes["title_size"] * 3)) 512 | if first: # tkinter is inactive the first time 513 | bio = io.BytesIO() 514 | img.save(bio, format="PNG") 515 | del img 516 | return bio.getvalue() 517 | return ImageTk.PhotoImage(img) # type:ignore[type-error] 518 | -------------------------------------------------------------------------------- /cli2gui/models.py: -------------------------------------------------------------------------------- 1 | """Types for cli2gui.""" 2 | 3 | from __future__ import annotations 4 | 5 | from collections.abc import Callable 6 | from dataclasses import dataclass 7 | from enum import Enum 8 | from typing import Any 9 | 10 | SEP = "#%#" 11 | 12 | 13 | @dataclass 14 | class BuildSpec: 15 | """Representation for the BuildSpec.""" 16 | 17 | run_function: Callable[..., Any] 18 | parser: str | ParserType 19 | gui: str | GUIType 20 | theme: str | list[str] 21 | darkTheme: str | list[str] 22 | image: str 23 | program_name: str 24 | program_description: str 25 | max_args_shown: int 26 | menu: str | dict[str, Any] 27 | 28 | 29 | @dataclass 30 | class Item: 31 | """Representation for an arg_item.""" 32 | 33 | type: ItemType 34 | display_name: str 35 | commands: list[str] 36 | help: str 37 | dest: str 38 | default: Any 39 | required: bool = False 40 | choices: list[Any] = None 41 | nargs: str = None 42 | additional_properties: dict[str, Any] = None 43 | 44 | 45 | class ItemType(Enum): 46 | """Enum of ItemTypes.""" 47 | 48 | RadioGroup = "RadioGroup" 49 | Bool = "Bool" 50 | File = "File" 51 | FileWrite = "FileWrite" 52 | Path = "Path" 53 | Choice = "Choice" 54 | Int = "Int" 55 | Text = "Text" 56 | Float = "Float" 57 | List = "List" 58 | Tuple = "Tuple" 59 | DateTime = "DateTime" 60 | 61 | 62 | @dataclass 63 | class Group: 64 | """Representation for an argument group.""" 65 | 66 | name: str 67 | arg_items: list[Item] 68 | groups: list[Group] | list[Any] 69 | 70 | 71 | @dataclass 72 | class ParserRep: 73 | """Representation for a parser.""" 74 | 75 | parser_description: str 76 | widgets: list[Group] 77 | 78 | 79 | @dataclass 80 | class FullBuildSpec: 81 | """Representation for the FullBuildSpec (BuildSpec + ParserRep).""" 82 | 83 | run_function: Callable[..., Any] 84 | parser: str 85 | gui: str 86 | theme: str | list[str] 87 | darkTheme: str | list[str] 88 | image: str 89 | program_name: str 90 | program_description: str 91 | max_args_shown: int 92 | menu: str | dict[str, Any] 93 | parser_description: str 94 | widgets: list[Group] 95 | 96 | 97 | # Supported parser types 98 | class ParserType(str, Enum): 99 | """Supported parser types. 100 | 101 | """ 102 | 103 | OPTPARSE = "optparse" 104 | ARGPARSE = "argparse" 105 | DEPHELL_ARGPARSE = "dephell_argparse" 106 | DOCOPT = "docopt" 107 | GETOPT = "getopt" 108 | CLICK = "click" 109 | CUSTOM = "input()" # this seems like a pretty poor pattern to use 110 | 111 | 112 | # Supported gui types 113 | class GUIType(str, Enum): 114 | """Supported gui types. 115 | 116 | """ 117 | 118 | PSG = "pysimplegui" 119 | WEB = "pysimpleguiweb" 120 | QT = "pysimpleguiqt" 121 | FSG = "freesimplegui" 122 | FSGWEB = "freesimpleguiweb" 123 | FSGQT = "freesimpleguiqt" 124 | DPG = "dearpygui" 125 | -------------------------------------------------------------------------------- /cli2gui/tojson/__init__.py: -------------------------------------------------------------------------------- 1 | """Package containing transforms for each supported parser/cli library, responsible for 2 | taking each parser representation (and BuildSpec) and generating a FullBuildSpec.""" 3 | -------------------------------------------------------------------------------- /cli2gui/tojson/argparse2json.py: -------------------------------------------------------------------------------- 1 | """Generate a dict describing argparse arguments. 2 | pylint and pylance both want me to not access protected methods - I know better ;). 3 | """ 4 | # ruff: noqa: SLF001 5 | 6 | from __future__ import annotations 7 | 8 | import argparse 9 | from argparse import ( 10 | Action, 11 | _CountAction, 12 | _HelpAction, 13 | _MutuallyExclusiveGroup, 14 | _StoreFalseAction, 15 | _StoreTrueAction, 16 | _SubParsersAction, 17 | ) 18 | from os import path 19 | from pathlib import Path 20 | from sys import argv 21 | from typing import Any, Generator, TypedDict 22 | 23 | from cli2gui.models import Group, Item, ItemType, ParserRep 24 | 25 | 26 | class ArgparseGroup(TypedDict): 27 | """Class to represent an ArgparseGroup.""" 28 | 29 | name: str 30 | arg_items: list[argparse.Action] 31 | groups: list[ArgparseGroup] | list[Any] 32 | 33 | 34 | def iterParsers( 35 | parser: argparse.ArgumentParser, 36 | ) -> list[tuple[str, argparse.ArgumentParser]]: 37 | """Iterate over name, parser pairs.""" 38 | defaultParser = [ 39 | ("::cli2gui/default", parser), 40 | ] 41 | candidateSubparsers = [ 42 | action for action in parser._actions if isinstance(action, _SubParsersAction) 43 | ] 44 | if len(candidateSubparsers) == 0: 45 | return defaultParser 46 | 47 | return defaultParser + list(candidateSubparsers[0].choices.items()) 48 | 49 | 50 | def isDefaultProgname(name: str, subparser: argparse.ArgumentParser) -> bool: 51 | """Identify if the passed name is the default program name.""" 52 | return subparser.prog == f"{path.split(argv[0])[-1]} {name}" 53 | 54 | 55 | def chooseName(name: str, subparser: argparse.ArgumentParser) -> str: 56 | """Get the program name.""" 57 | return name if isDefaultProgname(name, subparser) else subparser.prog 58 | 59 | 60 | def containsActions( 61 | actionA: list[argparse.Action], actionB: list[argparse.Action] 62 | ) -> set[argparse.Action]: 63 | """Check if any actions(a) are present in actions(b).""" 64 | return set(actionA).intersection(set(actionB)) 65 | 66 | 67 | def reapplyMutexGroups( 68 | mutexGroups: list[argparse._MutuallyExclusiveGroup], 69 | actionGroups: list[Any], 70 | ) -> list[Any]: 71 | """_argparse stores mutually exclusive groups independently. 72 | of all other groups. So, they must be manually re-combined 73 | with the groups/subgroups to which they were originally declared 74 | in order to have them appear in the correct location in the UI. 75 | 76 | Order is attempted to be preserved by inserting the MutexGroup 77 | into the _actions list at the first occurrence of any item 78 | where the two groups intersect. 79 | """ 80 | 81 | def swapActions(actions: list[Action]) -> list[Action]: 82 | for mutexgroup in mutexGroups: 83 | mutexActions = mutexgroup._group_actions 84 | if containsActions(mutexActions, actions): 85 | # make a best guess as to where we should store the group 86 | targetindex = actions.index(mutexgroup._group_actions[0]) 87 | # insert the _ArgumentGroup container 88 | actions[targetindex] = mutexgroup 89 | # remove the duplicated individual actions 90 | actions = [action for action in actions if action not in mutexActions] 91 | return actions 92 | 93 | return [ 94 | group.update({"arg_items": swapActions(group["arg_items"])}) or group 95 | for group in actionGroups 96 | ] 97 | 98 | 99 | def extractRawGroups(actionGroup: argparse._ArgumentGroup) -> ArgparseGroup: 100 | """Recursively extract argument groups and associated actions from ParserGroup objects.""" 101 | return { 102 | "name": str(actionGroup.title), 103 | # List of arg_items that are not help messages 104 | "arg_items": [ 105 | action for action in actionGroup._group_actions if not isinstance(action, _HelpAction) 106 | ], 107 | "groups": [extractRawGroups(group) for group in actionGroup._action_groups], 108 | } 109 | 110 | 111 | def fileActionToJson(action: argparse.Action, widget: ItemType) -> Item: 112 | """Convert an action of type Path or argparse.FileType to an Item.""" 113 | item = actionToJson(action=action, widget=widget) 114 | if isinstance(action.type, argparse.FileType): 115 | item.additional_properties = { 116 | **item.additional_properties, 117 | "file_mode": action.type._mode, 118 | "file_encoding": action.type._encoding, 119 | } 120 | return item 121 | 122 | 123 | def actionToJson(action: argparse.Action, widget: ItemType) -> Item: 124 | """Generate json for an action and set the widget - used by the application.""" 125 | choices = [str(choice) for choice in action.choices] if action.choices else [] 126 | return Item( 127 | type=widget, 128 | display_name=str(action.metavar or action.dest), 129 | help=str(action.help), 130 | commands=list(action.option_strings), 131 | dest=action.dest, 132 | default=action.default, 133 | additional_properties={"choices": choices, "nargs": action.nargs}, 134 | ) 135 | 136 | 137 | def buildRadioGroup(mutexGroup: _MutuallyExclusiveGroup) -> Item: 138 | """Create a radio group for a mutex group of arguments.""" 139 | commands = [action.option_strings for action in mutexGroup._group_actions] 140 | return Item( 141 | display_name="", 142 | help="", 143 | dest="", 144 | default="", 145 | type=ItemType.RadioGroup, 146 | commands=commands, 147 | additional_properties={"radio": list(categorizeItems(mutexGroup._group_actions))}, 148 | ) 149 | 150 | 151 | def categorizeItems( 152 | actions: list[argparse.Action], 153 | ) -> Generator[Item, None, None]: 154 | """Catergorise each action and generate json.""" 155 | for action in actions: 156 | if isinstance(action, _MutuallyExclusiveGroup): 157 | yield buildRadioGroup(action) 158 | elif isinstance(action, (_StoreTrueAction, _StoreFalseAction)): 159 | yield actionToJson(action, ItemType.Bool) 160 | elif isinstance(action, _CountAction): 161 | yield actionToJson(action, ItemType.Int) 162 | elif action.choices: 163 | yield actionToJson(action, ItemType.Choice) 164 | 165 | elif isinstance(action.type, argparse.FileType) and "w" in action.type._mode: 166 | yield fileActionToJson(action, ItemType.FileWrite) 167 | 168 | elif isinstance(action.type, argparse.FileType): 169 | yield fileActionToJson(action, ItemType.File) 170 | elif action.type is Path: 171 | yield actionToJson(action, ItemType.Path) 172 | 173 | elif action.type is int: 174 | yield actionToJson(action, ItemType.Int) 175 | elif action.type is float: 176 | yield actionToJson(action, ItemType.Float) 177 | else: 178 | yield actionToJson(action, ItemType.Text) 179 | 180 | 181 | def categorizeGroups(groups: list[ArgparseGroup]) -> list[Group]: 182 | """Categorize the parser groups and arg_items.""" 183 | return [ 184 | Group( 185 | name=group["name"], 186 | arg_items=list(categorizeItems(group["arg_items"])), 187 | groups=categorizeGroups(group["groups"]), 188 | ) 189 | for group in groups 190 | ] 191 | 192 | 193 | def stripEmpty(groups: list[ArgparseGroup]) -> list[ArgparseGroup]: 194 | """Remove groups where group['arg_items'] is false.""" 195 | return [group for group in groups if group["arg_items"]] 196 | 197 | 198 | def process(parser: argparse.ArgumentParser) -> list[Group]: 199 | """Reapply the mutex groups and then categorize them and the arg_items under the parser.""" 200 | mutexGroups = parser._mutually_exclusive_groups 201 | rawActionGroups = [ 202 | extractRawGroups(group) for group in parser._action_groups if group._group_actions 203 | ] 204 | correctedActionGroups = reapplyMutexGroups(mutexGroups, rawActionGroups) 205 | return categorizeGroups(stripEmpty(correctedActionGroups)) 206 | 207 | 208 | def convert(parser: argparse.ArgumentParser) -> ParserRep: 209 | """Convert argparse to a dict. 210 | 211 | Args: 212 | ---- 213 | parser (argparse.ArgumentParser): argparse parser 214 | 215 | Returns: 216 | ------- 217 | ParserRep: dictionary representing parser object 218 | 219 | """ 220 | widgets = [] 221 | for _, subparser in iterParsers(parser): 222 | widgets.extend(process(subparser)) 223 | 224 | return ParserRep( 225 | parser_description=f"{parser.prog}: {parser.description or ''}", widgets=widgets 226 | ) 227 | -------------------------------------------------------------------------------- /cli2gui/tojson/click2json.py: -------------------------------------------------------------------------------- 1 | """Generate a dict describing optparse arguments.""" 2 | 3 | from __future__ import annotations 4 | 5 | import contextlib 6 | from typing import Any, Generator 7 | 8 | from cli2gui.models import Group, Item, ItemType, ParserRep 9 | 10 | 11 | def extract(parser: Any) -> list[Group]: 12 | """Get the actions as json for the parser.""" 13 | try: 14 | argumentList = [ 15 | Group( 16 | name="Positional Arguments", 17 | arg_items=list(categorize([parser.commands[key] for key in parser.commands])), 18 | groups=[], 19 | ) 20 | ] 21 | except AttributeError: 22 | argumentList: list[Group] = [] 23 | argumentList.append( 24 | Group(name="Optional Arguments", arg_items=list(categorize(parser.params)), groups=[]) 25 | ) 26 | return argumentList 27 | 28 | 29 | def actionToJson(action: Any, widget: ItemType, other: dict | None = None) -> Item: 30 | """Generate json for an action and set the widget - used by the application.""" 31 | nargs = "" 32 | with contextlib.suppress(AttributeError): 33 | nargs = action.params[0].nargs if len(action.params) > 0 else "" or "" 34 | 35 | commands = action.opts + action.secondary_opts 36 | return Item( 37 | type=widget, 38 | display_name=action.name, 39 | help=action.help, 40 | commands=commands, 41 | dest=action.callback or commands[0], 42 | default=action.default, 43 | additional_properties={"nargs": nargs, **(other or {})}, 44 | ) 45 | 46 | 47 | def categorize(actions: list[Any]) -> Generator[Item, None, None]: 48 | """Catergorise each action and generate json.""" 49 | import click 50 | 51 | for action in actions: 52 | if isinstance(action.type, click.Choice): 53 | yield actionToJson(action, ItemType.Choice, {"choices": action.type.choices}) 54 | elif isinstance(action.type, click.types.IntParamType): 55 | yield actionToJson(action, ItemType.Int) 56 | elif isinstance(action.type, click.types.FloatParamType): 57 | yield actionToJson(action, ItemType.Float) 58 | elif isinstance(action.type, click.types.BoolParamType): 59 | yield actionToJson(action, ItemType.Bool) 60 | elif isinstance(action.type, click.types.Path): 61 | yield actionToJson(action, ItemType.Path) 62 | else: 63 | yield actionToJson(action, ItemType.Text) 64 | 65 | 66 | def convert(parser: Any) -> ParserRep: 67 | """Convert click to a dict. 68 | 69 | Args: 70 | ---- 71 | parser (click.core.Command): click parser 72 | 73 | Returns: 74 | ------- 75 | ParserRep: dictionary representing parser object 76 | 77 | """ 78 | return ParserRep(parser_description="", widgets=extract(parser)) 79 | -------------------------------------------------------------------------------- /cli2gui/tojson/docopt2json.py: -------------------------------------------------------------------------------- 1 | """Generate a dict for docopt.""" 2 | 3 | from __future__ import annotations 4 | 5 | import re 6 | from typing import Any, Iterator 7 | 8 | from cli2gui.models import Group, Item, ItemType, ParserRep 9 | 10 | 11 | def actionToJson(action: tuple[str, str, int, Any, str], widget: ItemType, *, isPos: bool) -> Item: 12 | """Generate json for an action and set the widget - used by the application.""" 13 | 14 | if isPos or len(action) < 5: 15 | return Item( 16 | type=widget, 17 | display_name=action[0], 18 | help=action[1], 19 | commands=[action[0]], 20 | dest=action[0], 21 | default=None, 22 | additional_properties={"nargs": ""}, 23 | ) 24 | 25 | default = action[3] if action[3] != "" else None 26 | return Item( 27 | type=widget, 28 | display_name=(action[1] or action[0]).replace("-", " ").strip(), 29 | help=action[4], 30 | commands=[x for x in action[0:2] if x != ""], 31 | dest=action[1] or action[0], 32 | default=default, 33 | additional_properties={"nargs": action[2]}, 34 | ) 35 | 36 | 37 | def categorize( 38 | actions: list[tuple[str, str, int, Any, str]], *, isPos: bool = False 39 | ) -> Iterator[Item]: 40 | """Catergorise each action and generate json. 41 | 42 | Each action is in the form (short, long, argcount, value, help_message) 43 | 44 | """ 45 | for action in actions: 46 | # ('-h', '--help', 0, False, 'show this help message and exit') 47 | if action[0] == "-h" and action[1] == "--help": 48 | pass 49 | elif not isPos and action[2] == 0: 50 | yield actionToJson(action, ItemType.Bool, isPos=isPos) 51 | else: 52 | yield actionToJson(action, ItemType.Text, isPos=isPos) 53 | 54 | 55 | def extract(parser: Any) -> list[Group]: 56 | """Get the actions as json for the parser.""" 57 | return [ 58 | Group( 59 | name="Positional Arguments", 60 | arg_items=list(categorize(parsePos(parser), isPos=True)), 61 | groups=[], 62 | ), 63 | Group(name="Optional Arguments", arg_items=list(categorize(parseOpt(parser))), groups=[]), 64 | ] 65 | 66 | 67 | def parseSection(name: str, source: str) -> list[str]: 68 | """Taken from docopt.""" 69 | pattern = re.compile( 70 | "^([^\n]*" + name + "[^\n]*\n?(?:[ \t].*?(?:\n|$))*)", 71 | re.IGNORECASE | re.MULTILINE, 72 | ) 73 | return [s.strip() for s in pattern.findall(source)] 74 | 75 | 76 | def parse(optionDescription: str) -> tuple[str, str, int, Any, str]: 77 | """Parse an option help text, adapted from docopt.""" 78 | short, long, argcount, value = "", "", 0, False 79 | options, _, description = optionDescription.strip().partition(" ") 80 | options = options.replace(",", " ").replace("=", " ") 81 | for section in options.split(): 82 | if section.startswith("--"): 83 | long = section 84 | elif section.startswith("-"): 85 | short = section 86 | else: 87 | argcount = 1 88 | if argcount > 0: 89 | matched = re.findall(r"\[default: (.*)\]", description, flags=re.IGNORECASE) 90 | value = matched[0] if matched else "" 91 | return (short, long, argcount, value, description.strip()) 92 | 93 | 94 | def parseOpt(doc: Any) -> list[tuple[str, str, int, Any, str]]: 95 | """Parse an option help text, adapted from docopt.""" 96 | defaults = [] 97 | for _section in parseSection("options:", doc): 98 | _, _, section = _section.partition(":") 99 | split = re.split(r"\n[ \t]*(-\S+?)", "\n" + section)[1:] 100 | split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] 101 | options = [parse(s) for s in split if s.startswith("-")] 102 | defaults += options 103 | return defaults 104 | 105 | 106 | def parsePos(doc: str) -> list[tuple[str, str]]: 107 | """Parse positional arguments from docstring.""" 108 | defaults = [] 109 | for _section in parseSection("arguments:", doc): 110 | _, _, section = _section.partition(":") 111 | defaults.append( 112 | tuple(col.strip() for col in section.strip().partition(" ") if len(col.strip()) > 0) 113 | ) 114 | return defaults 115 | 116 | 117 | def convert(parser: Any) -> ParserRep: 118 | """Convert getopt to a dict. 119 | 120 | Args: 121 | ---- 122 | parser (Any): docopt parser 123 | 124 | Returns: 125 | ------- 126 | ParserRep: dictionary representing parser object 127 | 128 | """ 129 | return ParserRep(parser_description="", widgets=extract(parser)) 130 | -------------------------------------------------------------------------------- /cli2gui/tojson/getopt2json.py: -------------------------------------------------------------------------------- 1 | """Generate a dict for getopt.""" 2 | 3 | from __future__ import annotations 4 | 5 | from collections.abc import Callable 6 | from typing import Generator 7 | 8 | from cli2gui.models import Group, Item, ItemType, ParserRep 9 | 10 | # ruff: noqa: SLF001 11 | 12 | 13 | def actionToJson(action: str, widget: ItemType, *, short: bool = True) -> Item: 14 | """Convert an arg to json, behave in the same way as argparse hence the large 15 | amount of duplication. 16 | """ 17 | return Item( 18 | type=widget, 19 | display_name=action, 20 | help="", 21 | commands=[("-" if short else "--") + action], 22 | dest=("-" if short else "--") + action, 23 | default=None, 24 | additional_properties={}, 25 | ) 26 | 27 | 28 | def catLong(actions: list[str]) -> Generator[Item, None, None]: 29 | """Categorize long args.""" 30 | for action in actions: 31 | # True/ false 32 | if "=" in action: 33 | yield actionToJson(action[:-1], ItemType.Text, short=False) 34 | else: 35 | yield actionToJson(action, ItemType.Bool, short=False) 36 | 37 | 38 | def catShort(actions: list[str]) -> Generator[Item, None, None]: 39 | """Categorize short args.""" 40 | index = 0 41 | while index < len(actions): 42 | try: 43 | # True/ false 44 | if ":" in actions[index + 1]: 45 | yield actionToJson(actions[index], ItemType.Text) 46 | index += 2 47 | else: 48 | yield actionToJson(actions[index], ItemType.Bool) 49 | index += 1 50 | except IndexError: 51 | yield actionToJson(actions[index], ItemType.Bool) 52 | break 53 | 54 | 55 | def process( 56 | group: list[str], 57 | groupName: str, 58 | categorize: Callable[[list[str]], Generator[Item, None, None]], 59 | ) -> list[Group]: 60 | """Generate a group (or section).""" 61 | return [Group(name=groupName, arg_items=list(categorize(group)), groups=[])] 62 | 63 | 64 | def convert(parser: tuple[list[str], list[str]]) -> ParserRep: 65 | """Convert getopt to a dict. 66 | 67 | Args: 68 | ---- 69 | parser (tuple[list[str], list[str]]): getopt parser 70 | 71 | Returns: 72 | ------- 73 | ParserRep: dictionary representing parser object 74 | 75 | """ 76 | return ParserRep( 77 | parser_description="", 78 | widgets=process(parser[0], "Short Args", catShort) 79 | + process(parser[1], "Long Args", catLong), 80 | ) 81 | -------------------------------------------------------------------------------- /cli2gui/tojson/optparse2json.py: -------------------------------------------------------------------------------- 1 | """Generate a dict describing optparse arguments. 2 | 3 | pylint and pylance both want me to not access protected methods - I know better ;) 4 | """ 5 | 6 | # ruff: noqa: SLF001 7 | from __future__ import annotations 8 | 9 | import optparse 10 | from typing import Generator 11 | 12 | from cli2gui.models import Group, Item, ItemType, ParserRep 13 | 14 | 15 | def extractOptions(optionGroup: optparse.OptionGroup) -> Group: 16 | """Get the actions as json for each item under a group.""" 17 | return Group( 18 | name=optionGroup.title, 19 | arg_items=list( 20 | categorize( 21 | [action for action in optionGroup.option_list if action.action not in "help"] 22 | ) 23 | ), 24 | groups=[], 25 | ) 26 | 27 | 28 | def extractGroups(parser: optparse.OptionParser) -> Group: 29 | """Get the actions as json for each item and group under the parser.""" 30 | argItems = list( 31 | categorize([action for action in parser.option_list if action.action not in "help"]) 32 | ) 33 | return Group( 34 | name="Arguments", 35 | arg_items=argItems, 36 | groups=[extractOptions(group) for group in parser.option_groups], 37 | ) 38 | 39 | 40 | def actionToJson(action: optparse.Option, widget: ItemType) -> Item: 41 | """Generate json for an action and set the widget - used by the application.""" 42 | choices = action.choices or [] # type: ignore[general-type-issues] # choices is confirmed to exist\ 43 | default = action.default if action.default != ("NO", "DEFAULT") else None 44 | return Item( 45 | type=widget, 46 | display_name=str(action.metavar or action.dest), 47 | help=str(action.help), 48 | commands=action._long_opts + action._short_opts, 49 | dest=action.dest or "", 50 | default=default, 51 | additional_properties={ 52 | "nargs": str(action.nargs or ""), 53 | "choices": choices, 54 | }, 55 | ) 56 | 57 | 58 | def categorize(actions: list[optparse.Option]) -> Generator[Item, None, None]: 59 | """Catergorise each action and generate json.""" 60 | for action in actions: 61 | # _actions which are either, store_bool, etc.. 62 | if action.action in ("store_true", "store_false"): 63 | yield actionToJson(action, ItemType.Bool) 64 | # _actions which are of type _CountAction 65 | elif action.choices: # type: ignore[general-type-issues] # choices is confirmed to exist 66 | yield actionToJson(action, ItemType.Choice) 67 | elif action.action in ("count",): 68 | yield actionToJson(action, ItemType.Int) 69 | else: 70 | yield actionToJson(action, ItemType.Text) 71 | 72 | 73 | def convert(parser: optparse.OptionParser) -> ParserRep: 74 | """Convert argparse to a dict. 75 | 76 | Args: 77 | ---- 78 | parser (optparse.OptionParser): optparse parser 79 | 80 | Returns: 81 | ------- 82 | ParserRep: dictionary representing parser object 83 | 84 | """ 85 | return ParserRep(parser_description="", widgets=[extractGroups(parser)]) 86 | -------------------------------------------------------------------------------- /comparison.md: -------------------------------------------------------------------------------- 1 | # Comparison to similar projects 2 | 3 | Do let me know if any of these are incorrect. Some of the comparisons are 4 | based off documentation/ the readme 5 | 6 | ## Parser Support 7 | 8 | | Parser | Cli2Gui | [Gooey](https://github.com/chriskiehl/Gooey) | Quick | 9 | | --------------------------------------------------------------- | ------------------- | -------------------------------------------- | ----- | 10 | | [Argparse](https://docs.python.org/3/library/argparse.html) | ✔ | ✔ | ❌ | 11 | | [Optparse](https://docs.python.org/3/library/optparse.html) | ✔ | ❌ | ❌ | 12 | | [DocOpt](https://github.com/docopt/docopt) | ✔ | ❌ | ❌ | 13 | | [Click](https://github.com/pallets/click) | ✔ * | ❌ | ✔ | 14 | | [GetOpt](https://docs.python.org/3/library/getopt.html) | ✔ | ❌ | ❌ | 15 | | [Dephell Argparse](https://github.com/dephell/dephell_argparse) | ✔ | ❌ | ❌ | 16 | 17 | ```none 18 | * Partial support (use [Click2Gui](#click2gui)) 19 | 20 | This works for simpler programs but sadly falls flat for more complex programs 21 | ``` 22 | 23 | ## GUI Toolkit Support 24 | 25 | | GUI Toolkits | Cli2Gui | Gooey | Quick | 26 | | ------------ | ------- | ----- | ----- | 27 | | Tkinter | ✔ | ❌ | ❌ | 28 | | WxWidgets | ❌ | ✔ | ❌ | 29 | | Qt | ✔ | ❌ | ✔ | 30 | | Gtk | ❌ | ❌ | ❌ | 31 | | Web | ✔ | ❌ | ❌ | 32 | 33 | ## GUI Feature Support 34 | 35 | | Basic GUI | Cli2Gui | Gooey | Quick | 36 | | -------------------------- | ------- | ---------------- | ---------------- | 37 | | Override name/ description | ✔ | ✔ | ❌ | 38 | | Theming | ✔ | ⚠ Limited | ⚠ Limited | 39 | | DarkMode | ✔ | ❌ | ✔ | 40 | | Window Size | ✔ | ✔ | ❌ | 41 | | Element Size | ✔ | ❌ | ❌ | 42 | | Custom Images | ✔ | ✔ | ❌ | 43 | 44 | Cli2Gui is pretty lacking in these features and will probably remain that way 45 | to ease maintainability - the primary aim is to support multiple argparse 46 | libraries over fancy widgets 47 | 48 | | Advanced GUI | Cli2Gui | Gooey | Quick | 49 | | ---------------------- | ------- | ----- | ----- | 50 | | Dropdown | ✔ | ✔ | ✔ | 51 | | Slider | ❌ | ✔ | ✔ | 52 | | Tabs | ❌ | ✔ | ✔ | 53 | | Menus | ✔ | ✔ | ❌ | 54 | | Max Args before Scroll | ✔ | ❌ | ❌ | 55 | -------------------------------------------------------------------------------- /documentation/reference/README.md: -------------------------------------------------------------------------------- 1 | # Cli2gui Index 2 | 3 | > Auto-generated documentation index. 4 | 5 | A full list of `Cli2gui` project modules. 6 | 7 | - [Cli2gui](cli2gui/index.md#cli2gui) 8 | - [Application](cli2gui/application/index.md#application) 9 | - [Application](cli2gui/application/application.md#application) 10 | - [Application2args](cli2gui/application/application2args.md#application2args) 11 | - [Decorators](cli2gui/decorators.md#decorators) 12 | - [Gui](cli2gui/gui/index.md#gui) 13 | - [AbstractGUI](cli2gui/gui/abstract_gui.md#abstractgui) 14 | - [DearPyGuiWrapper](cli2gui/gui/dearpygui_wrapper.md#dearpyguiwrapper) 15 | - [Helpers](cli2gui/gui/helpers.md#helpers) 16 | - [PySimpleGUIWrapper](cli2gui/gui/pysimplegui_wrapper.md#pysimpleguiwrapper) 17 | - [Models](cli2gui/models.md#models) 18 | - [Tojson](cli2gui/tojson/index.md#tojson) 19 | - [Argparse2json](cli2gui/tojson/argparse2json.md#argparse2json) 20 | - [Click2json](cli2gui/tojson/click2json.md#click2json) 21 | - [Docopt2json](cli2gui/tojson/docopt2json.md#docopt2json) 22 | - [Getopt2json](cli2gui/tojson/getopt2json.md#getopt2json) 23 | - [Optparse2json](cli2gui/tojson/optparse2json.md#optparse2json) 24 | -------------------------------------------------------------------------------- /documentation/reference/cli2gui/application/application.md: -------------------------------------------------------------------------------- 1 | # Application 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Application](./index.md#application) / Application 4 | 5 | > Auto-generated documentation for [cli2gui.application.application](../../../../cli2gui/application/application.py) module. 6 | 7 | - [Application](#application) 8 | - [run](#run) 9 | 10 | ## run 11 | 12 | [Show source in application.py:16](../../../../cli2gui/application/application.py#L16) 13 | 14 | Establish the main entry point. 15 | 16 | #### Arguments 17 | 18 | ---- 19 | - `buildSpec` *types.FullBuildSpec* - args that customise the application such as the theme 20 | or the function to run 21 | 22 | #### Signature 23 | 24 | ```python 25 | def run(buildSpec: models.FullBuildSpec) -> Any: ... 26 | ``` -------------------------------------------------------------------------------- /documentation/reference/cli2gui/application/application2args.md: -------------------------------------------------------------------------------- 1 | # Application2args 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Application](./index.md#application) / Application2args 4 | 5 | > Auto-generated documentation for [cli2gui.application.application2args](../../../../cli2gui/application/application2args.py) module. 6 | 7 | - [Application2args](#application2args) 8 | - [argFormat](#argformat) 9 | - [argparseFormat](#argparseformat) 10 | - [clickFormat](#clickformat) 11 | - [docoptFormat](#docoptformat) 12 | - [getoptFormat](#getoptformat) 13 | - [optparseFormat](#optparseformat) 14 | - [processValue](#processvalue) 15 | 16 | ## argFormat 17 | 18 | [Show source in application2args.py:89](../../../../cli2gui/application/application2args.py#L89) 19 | 20 | Format the args for the desired parser. 21 | 22 | #### Arguments 23 | 24 | ---- 25 | values (dict[str, Any]): values from simple gui 26 | - `argumentParser` *str* - argument parser to use 27 | 28 | #### Returns 29 | 30 | ------- 31 | - `Any` - args 32 | 33 | #### Signature 34 | 35 | ```python 36 | def argFormat(values: dict[str, Any], argumentParser: str | ParserType) -> Any: ... 37 | ``` 38 | 39 | 40 | 41 | ## argparseFormat 42 | 43 | [Show source in application2args.py:44](../../../../cli2gui/application/application2args.py#L44) 44 | 45 | Format args for argparse. 46 | 47 | #### Signature 48 | 49 | ```python 50 | def argparseFormat(values: dict[str, Any]) -> argparse.Namespace: ... 51 | ``` 52 | 53 | 54 | 55 | ## clickFormat 56 | 57 | [Show source in application2args.py:78](../../../../cli2gui/application/application2args.py#L78) 58 | 59 | Format args for click. 60 | 61 | #### Signature 62 | 63 | ```python 64 | def clickFormat(values: dict[str, Any]) -> list[Any]: ... 65 | ``` 66 | 67 | 68 | 69 | ## docoptFormat 70 | 71 | [Show source in application2args.py:67](../../../../cli2gui/application/application2args.py#L67) 72 | 73 | Format args for docopt. 74 | 75 | #### Signature 76 | 77 | ```python 78 | def docoptFormat(values: dict[str, Any]) -> dict[str, Any]: ... 79 | ``` 80 | 81 | 82 | 83 | ## getoptFormat 84 | 85 | [Show source in application2args.py:62](../../../../cli2gui/application/application2args.py#L62) 86 | 87 | Format args for getopt. 88 | 89 | #### Signature 90 | 91 | ```python 92 | def getoptFormat(values: dict[str, Any]) -> tuple[list[Any], list[Any]]: ... 93 | ``` 94 | 95 | 96 | 97 | ## optparseFormat 98 | 99 | [Show source in application2args.py:53](../../../../cli2gui/application/application2args.py#L53) 100 | 101 | Format args for optparse. 102 | 103 | #### Signature 104 | 105 | ```python 106 | def optparseFormat(values: dict[str, Any]) -> tuple[optparse.Values, list[str]]: ... 107 | ``` 108 | 109 | 110 | 111 | ## processValue 112 | 113 | [Show source in application2args.py:13](../../../../cli2gui/application/application2args.py#L13) 114 | 115 | #### Signature 116 | 117 | ```python 118 | def processValue(key: str, value: str) -> tuple[str, Any]: ... 119 | ``` -------------------------------------------------------------------------------- /documentation/reference/cli2gui/application/index.md: -------------------------------------------------------------------------------- 1 | # Application 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / Application 4 | 5 | > Auto-generated documentation for [cli2gui.application](../../../../cli2gui/application/__init__.py) module. 6 | 7 | - [Application](#application) 8 | - [Modules](#modules) 9 | 10 | ## Modules 11 | 12 | - [Application](./application.md) 13 | - [Application2args](./application2args.md) -------------------------------------------------------------------------------- /documentation/reference/cli2gui/decorators.md: -------------------------------------------------------------------------------- 1 | # Decorators 2 | 3 | [Cli2gui Index](../README.md#cli2gui-index) / [Cli2gui](./index.md#cli2gui) / Decorators 4 | 5 | > Auto-generated documentation for [cli2gui.decorators](../../../cli2gui/decorators.py) module. 6 | 7 | - [Decorators](#decorators) 8 | - [Cli2Gui](#cli2gui) 9 | - [Click2Gui](#click2gui) 10 | - [createFromParser](#createfromparser) 11 | 12 | ## Cli2Gui 13 | 14 | [Show source in decorators.py:171](../../../cli2gui/decorators.py#L171) 15 | 16 | Use this decorator in the function containing the argument parser. 17 | Serialises data to JSON and launches the Cli2Gui application. 18 | 19 | #### Arguments 20 | 21 | ---- 22 | run_function (Callable[..., Any]): The name of the function to call eg. 23 | - `auto_enable` *bool, optional* - Enable the GUI by default. If enabled by 24 | default requires `--disable-cli2gui`, otherwise requires `--cli2gui`. 25 | Defaults to False. 26 | - `parser` *str, optional* - Override the parser to use. Current 27 | - `options` *are* - "argparse", "getopt", "optparse", "docopt", 28 | "dephell_argparse". Defaults to "argparse". 29 | - `gui` *str, optional* - Override the gui to use. Current options are: 30 | "dearpygui", "pysimplegui", "pysimpleguiqt","pysimpleguiweb","freesimplegui", 31 | Defaults to "dearpygui". 32 | theme (Union[str, list[str]], optional): Set a base24 theme. Can 33 | also pass a base24 scheme file. eg. one-light.yaml. Defaults to "". 34 | darkTheme (Union[str, list[str]], optional): Set a base24 dark 35 | theme variant. Can also pass a base24 scheme file. eg. one-dark.yaml. 36 | Defaults to "". 37 | - `image` *str, optional* - Set the program icon. File 38 | extensions can be any that PIL supports. Defaults to "". 39 | - `program_name` *str, optional* - Override the program name. 40 | Defaults to "". 41 | - `program_description` *str, optional* - Override the program 42 | description. Defaults to "". 43 | - `max_args_shown` *int, optional* - Maximum number of args shown before 44 | using a scrollbar. Defaults to 5. 45 | menu (Union[dict[str, Any]], optional): Add a menu to the program. 46 | Defaults to "". eg. THIS_DIR = str(Path(__file__).resolve().parent) 47 | - `menu={"File"` - THIS_DIR + "/file.md"} 48 | 49 | #### Returns 50 | 51 | ------- 52 | - `Any` - Runs the application 53 | 54 | #### Signature 55 | 56 | ```python 57 | def Cli2Gui( 58 | run_function: Callable[..., Any], 59 | auto_enable: bool = False, 60 | parser: str | ParserType = "argparse", 61 | gui: str | ParserType = "dearpygui", 62 | theme: str | list[str] = "", 63 | darkTheme: str | list[str] = "", 64 | image: str = "", 65 | program_name: str = "", 66 | program_description: str = "", 67 | max_args_shown: int = 5, 68 | menu: str | dict[str, Any] = "", 69 | ) -> Any: ... 70 | ``` 71 | 72 | 73 | 74 | ## Click2Gui 75 | 76 | [Show source in decorators.py:108](../../../cli2gui/decorators.py#L108) 77 | 78 | Use this decorator in the function containing the argument parser. 79 | Serializes data to JSON and launches the Cli2Gui application. 80 | 81 | #### Arguments 82 | 83 | ---- 84 | run_function (Callable[..., Any]): The name of the function to call eg. 85 | - `gui` *str, optional* - Override the gui to use. Current options are: 86 | "dearpygui", "pysimplegui", "pysimpleguiqt","pysimpleguiweb","freesimplegui", 87 | Defaults to "dearpygui". 88 | theme (Union[str, list[str]], optional): Set a base24 theme. Can 89 | also pass a base24 scheme file. eg. one-light.yaml. Defaults to "". 90 | darkTheme (Union[str, list[str]], optional): Set a base24 dark 91 | theme variant. Can also pass a base24 scheme file. eg. one-dark.yaml. 92 | Defaults to "". 93 | - `image` *str, optional* - Set the program icon. File 94 | extensions can be any that PIL supports. Defaults to "". 95 | - `program_name` *str, optional* - Override the program name. 96 | Defaults to "". 97 | - `program_description` *str, optional* - Override the program 98 | description. Defaults to "". 99 | - `max_args_shown` *int, optional* - Maximum number of args shown before 100 | using a scrollbar. Defaults to 5. 101 | menu (Union[dict[str, Any]], optional): Add a menu to the program. 102 | Defaults to "". eg. THIS_DIR = str(Path(__file__).resolve().parent) 103 | - `menu={"File"` - THIS_DIR + "/file.md"} 104 | **kwargs (dict[Any, Any]): kwargs 105 | 106 | #### Returns 107 | 108 | ------- 109 | - `Any` - Runs the application 110 | 111 | #### Signature 112 | 113 | ```python 114 | def Click2Gui( 115 | run_function: Callable[..., Any], 116 | gui: str | GUIType = "dearpygui", 117 | theme: str | list[str] = "", 118 | darkTheme: str | list[str] = "", 119 | image: str = "", 120 | program_name: str = "", 121 | program_description: str = "", 122 | max_args_shown: int = 5, 123 | menu: str | dict[str, Any] = "", 124 | **kwargs: dict[str, Any] 125 | ) -> None: ... 126 | ``` 127 | 128 | 129 | 130 | ## createFromParser 131 | 132 | [Show source in decorators.py:29](../../../cli2gui/decorators.py#L29) 133 | 134 | Generate a buildSpec from a parser. 135 | 136 | #### Arguments 137 | 138 | ---- 139 | - `selfParser` *Any* - A parser that acts on self. eg. ArgumentParser.parse_args 140 | argsParser (tuple[Any, ...]): A parser that acts on function 141 | arguments. eg. getopt.getopt 142 | kwargsParser (dict[Any, Any]): A parser that acts on named params 143 | - `sourcePath` *str* - Program source path 144 | - `buildSpec` *BuildSpec* - Build spec 145 | **kwargs (dict[Any, Any]): kwargs 146 | 147 | #### Returns 148 | 149 | ------- 150 | - `types.FullBuildSpec` - buildSpec to be used by the application 151 | 152 | #### Raises 153 | 154 | ------ 155 | - `RuntimeError` - Throw error if incorrect parser selected 156 | 157 | #### Signature 158 | 159 | ```python 160 | def createFromParser( 161 | selfParser: Any, 162 | argsParser: tuple[Any, ...], 163 | kwargsParser: dict[Any, Any], 164 | sourcePath: str, 165 | buildSpec: BuildSpec, 166 | **kwargs: dict[Any, Any] 167 | ) -> FullBuildSpec: ... 168 | ``` 169 | 170 | #### See also 171 | 172 | - [BuildSpec](./models.md#buildspec) 173 | - [FullBuildSpec](./models.md#fullbuildspec) -------------------------------------------------------------------------------- /documentation/reference/cli2gui/gui/abstract_gui.md: -------------------------------------------------------------------------------- 1 | # AbstractGUI 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Gui](./index.md#gui) / AbstractGUI 4 | 5 | > Auto-generated documentation for [cli2gui.gui.abstract_gui](../../../../cli2gui/gui/abstract_gui.py) module. 6 | 7 | - [AbstractGUI](#abstractgui) 8 | - [AbstractGUI](#abstractgui-1) 9 | - [AbstractGUI().main](#abstractgui()main) 10 | 11 | ## AbstractGUI 12 | 13 | [Show source in abstract_gui.py:11](../../../../cli2gui/gui/abstract_gui.py#L11) 14 | 15 | Abstract base class for GUI wrappers. 16 | 17 | #### Signature 18 | 19 | ```python 20 | class AbstractGUI(ABC): 21 | @abstractmethod 22 | def __init__(self) -> None: ... 23 | ``` 24 | 25 | ### AbstractGUI().main 26 | 27 | [Show source in abstract_gui.py:18](../../../../cli2gui/gui/abstract_gui.py#L18) 28 | 29 | Abstract method for the main function. 30 | 31 | #### Signature 32 | 33 | ```python 34 | @abstractmethod 35 | def main( 36 | self, 37 | buildSpec: models.FullBuildSpec, 38 | quit_callback: Callable[[], None], 39 | run_callback: Callable[[dict[str, Any]], None], 40 | ) -> None: ... 41 | ``` -------------------------------------------------------------------------------- /documentation/reference/cli2gui/gui/dearpygui_wrapper.md: -------------------------------------------------------------------------------- 1 | # DearPyGuiWrapper 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Gui](./index.md#gui) / DearPyGuiWrapper 4 | 5 | > Auto-generated documentation for [cli2gui.gui.dearpygui_wrapper](../../../../cli2gui/gui/dearpygui_wrapper.py) module. 6 | 7 | - [DearPyGuiWrapper](#dearpyguiwrapper) 8 | - [DearPyGuiWrapper](#dearpyguiwrapper-1) 9 | - [DearPyGuiWrapper()._helpFileWidget](#dearpyguiwrapper()_helpfilewidget) 10 | - [DearPyGuiWrapper().addItemsAndGroups](#dearpyguiwrapper()additemsandgroups) 11 | - [DearPyGuiWrapper().addWidgetFromItem](#dearpyguiwrapper()addwidgetfromitem) 12 | - [DearPyGuiWrapper().main](#dearpyguiwrapper()main) 13 | - [DearPyGuiWrapper().open_menu_item](#dearpyguiwrapper()open_menu_item) 14 | - [hex_to_rgb](#hex_to_rgb) 15 | 16 | ## DearPyGuiWrapper 17 | 18 | [Show source in dearpygui_wrapper.py:26](../../../../cli2gui/gui/dearpygui_wrapper.py#L26) 19 | 20 | Wrapper class for Dear PyGui. 21 | 22 | #### Signature 23 | 24 | ```python 25 | class DearPyGuiWrapper(AbstractGUI): 26 | def __init__(self, base24Theme: list[str]) -> None: ... 27 | ``` 28 | 29 | #### See also 30 | 31 | - [AbstractGUI](./abstract_gui.md#abstractgui) 32 | 33 | ### DearPyGuiWrapper()._helpFileWidget 34 | 35 | [Show source in dearpygui_wrapper.py:80](../../../../cli2gui/gui/dearpygui_wrapper.py#L80) 36 | 37 | Create a UI element with an input text field and a file picker. 38 | 39 | #### Signature 40 | 41 | ```python 42 | def _helpFileWidget(self, item: Item) -> None: ... 43 | ``` 44 | 45 | #### See also 46 | 47 | - [Item](../models.md#item) 48 | 49 | ### DearPyGuiWrapper().addItemsAndGroups 50 | 51 | [Show source in dearpygui_wrapper.py:147](../../../../cli2gui/gui/dearpygui_wrapper.py#L147) 52 | 53 | Items and groups and return a list of these so we can get values from the dpg widgets. 54 | 55 | #### Arguments 56 | 57 | - `section` *Group* - section with a name to display and items 58 | 59 | #### Returns 60 | 61 | Type: *list[Item]* 62 | flattened list of items 63 | 64 | #### Signature 65 | 66 | ```python 67 | def addItemsAndGroups(self, section: Group) -> list[Item]: ... 68 | ``` 69 | 70 | #### See also 71 | 72 | - [Group](../models.md#group) 73 | - [Item](../models.md#item) 74 | 75 | ### DearPyGuiWrapper().addWidgetFromItem 76 | 77 | [Show source in dearpygui_wrapper.py:125](../../../../cli2gui/gui/dearpygui_wrapper.py#L125) 78 | 79 | Select a widget based on the item type. 80 | 81 | #### Arguments 82 | 83 | - `item` *Item* - the item 84 | 85 | #### Signature 86 | 87 | ```python 88 | def addWidgetFromItem(self, item: Item) -> None: ... 89 | ``` 90 | 91 | #### See also 92 | 93 | - [Item](../models.md#item) 94 | 95 | ### DearPyGuiWrapper().main 96 | 97 | [Show source in dearpygui_wrapper.py:191](../../../../cli2gui/gui/dearpygui_wrapper.py#L191) 98 | 99 | Run the gui (dpg) with a given buildSpec, quit_callback, and run_callback. 100 | 101 | - Theming + Configure dpg 102 | - Menu Prep 103 | - Create Window, set up Menu and Widgets 104 | - Then, start dpg 105 | 106 | #### Arguments 107 | 108 | - `buildSpec` *FullBuildSpec* - Full cli parse/ build spec 109 | :param Callable[[], None] quit_callback: generic callable used to quit 110 | :param Callable[[dict[str, Any]], None] run_callback: generic callable used to run 111 | 112 | #### Signature 113 | 114 | ```python 115 | def main( 116 | self, 117 | buildSpec: FullBuildSpec, 118 | quit_callback: Callable[[], None], 119 | run_callback: Callable[[dict[str, Any]], None], 120 | ) -> None: ... 121 | ``` 122 | 123 | #### See also 124 | 125 | - [FullBuildSpec](../models.md#fullbuildspec) 126 | 127 | ### DearPyGuiWrapper().open_menu_item 128 | 129 | [Show source in dearpygui_wrapper.py:177](../../../../cli2gui/gui/dearpygui_wrapper.py#L177) 130 | 131 | Open a menu item. 132 | 133 | #### Arguments 134 | 135 | - `sender` *_type_* - file to open 136 | - `_app_data` *_type_* - [unused] 137 | 138 | #### Signature 139 | 140 | ```python 141 | def open_menu_item(self, sender: str, _app_data: None) -> None: ... 142 | ``` 143 | 144 | 145 | 146 | ## hex_to_rgb 147 | 148 | [Show source in dearpygui_wrapper.py:17](../../../../cli2gui/gui/dearpygui_wrapper.py#L17) 149 | 150 | Convert a color hex code to a tuple of integers (r, g, b). 151 | 152 | #### Signature 153 | 154 | ```python 155 | def hex_to_rgb(hex_code: str) -> tuple[int, int, int, int]: ... 156 | ``` -------------------------------------------------------------------------------- /documentation/reference/cli2gui/gui/helpers.md: -------------------------------------------------------------------------------- 1 | # Helpers 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Gui](./index.md#gui) / Helpers 4 | 5 | > Auto-generated documentation for [cli2gui.gui.helpers](../../../../cli2gui/gui/helpers.py) module. 6 | 7 | - [Helpers](#helpers) 8 | - [_themeFromFile](#_themefromfile) 9 | - [get_base24_theme](#get_base24_theme) 10 | - [isDarkMode](#isdarkmode) 11 | - [read_file](#read_file) 12 | - [stringSentencecase](#stringsentencecase) 13 | - [stringTitlecase](#stringtitlecase) 14 | 15 | ## _themeFromFile 16 | 17 | [Show source in helpers.py:20](../../../../cli2gui/gui/helpers.py#L20) 18 | 19 | Set the base24 theme from a base24 scheme.yaml to the application. 20 | 21 | #### Arguments 22 | 23 | ---- 24 | - `themeFile` *str* - path to file 25 | 26 | #### Returns 27 | 28 | ------- 29 | - `list[str]` - theme to set 30 | 31 | #### Signature 32 | 33 | ```python 34 | def _themeFromFile(themeFile: str) -> list[str]: ... 35 | ``` 36 | 37 | 38 | 39 | ## get_base24_theme 40 | 41 | [Show source in helpers.py:36](../../../../cli2gui/gui/helpers.py#L36) 42 | 43 | Set the base24 theme to the application. 44 | 45 | #### Arguments 46 | 47 | ---- 48 | theme (Union[str, list[str]]): the light theme 49 | darkTheme (Union[str, list[str]]): the dark theme 50 | 51 | #### Signature 52 | 53 | ```python 54 | def get_base24_theme( 55 | theme: str | list[str], darkTheme: str | list[str] 56 | ) -> list[str]: ... 57 | ``` 58 | 59 | 60 | 61 | ## isDarkMode 62 | 63 | [Show source in helpers.py:12](../../../../cli2gui/gui/helpers.py#L12) 64 | 65 | Monkeypatch for getostheme.isDarkMode. 66 | 67 | #### Signature 68 | 69 | ```python 70 | def isDarkMode() -> bool: ... 71 | ``` 72 | 73 | 74 | 75 | ## read_file 76 | 77 | [Show source in helpers.py:133](../../../../cli2gui/gui/helpers.py#L133) 78 | 79 | Get the contents of a file path, attempt to parse with catpandoc..pandoc2plain. 80 | 81 | #### Arguments 82 | 83 | - `file_path` *str* - path to the file (absolute recommended) 84 | 85 | #### Returns 86 | 87 | Type: *str* 88 | file contents 89 | 90 | #### Signature 91 | 92 | ```python 93 | def read_file(file_path: str, maxLines: int = 200) -> str: ... 94 | ``` 95 | 96 | 97 | 98 | ## stringSentencecase 99 | 100 | [Show source in helpers.py:125](../../../../cli2gui/gui/helpers.py#L125) 101 | 102 | Convert a string to sentence case. 103 | 104 | #### Signature 105 | 106 | ```python 107 | def stringSentencecase(string: str) -> str: ... 108 | ``` 109 | 110 | 111 | 112 | ## stringTitlecase 113 | 114 | [Show source in helpers.py:111](../../../../cli2gui/gui/helpers.py#L111) 115 | 116 | Convert a string to title case. 117 | 118 | #### Signature 119 | 120 | ```python 121 | def stringTitlecase(string: str, splitStr: str = "ALL") -> str: ... 122 | ``` -------------------------------------------------------------------------------- /documentation/reference/cli2gui/gui/index.md: -------------------------------------------------------------------------------- 1 | # Gui 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / Gui 4 | 5 | > Auto-generated documentation for [cli2gui.gui](../../../../cli2gui/gui/__init__.py) module. 6 | 7 | - [Gui](#gui) 8 | - [Modules](#modules) 9 | 10 | ## Modules 11 | 12 | - [AbstractGUI](./abstract_gui.md) 13 | - [DearPyGuiWrapper](./dearpygui_wrapper.md) 14 | - [Helpers](./helpers.md) 15 | - [PySimpleGUIWrapper](./pysimplegui_wrapper.md) -------------------------------------------------------------------------------- /documentation/reference/cli2gui/gui/pysimplegui_wrapper.md: -------------------------------------------------------------------------------- 1 | # PySimpleGUIWrapper 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Gui](./index.md#gui) / PySimpleGUIWrapper 4 | 5 | > Auto-generated documentation for [cli2gui.gui.pysimplegui_wrapper](../../../../cli2gui/gui/pysimplegui_wrapper.py) module. 6 | 7 | - [PySimpleGUIWrapper](#pysimpleguiwrapper) 8 | - [PySimpleGUIWrapper](#pysimpleguiwrapper-1) 9 | - [PySimpleGUIWrapper()._button](#pysimpleguiwrapper()_button) 10 | - [PySimpleGUIWrapper()._check](#pysimpleguiwrapper()_check) 11 | - [PySimpleGUIWrapper()._dropdown](#pysimpleguiwrapper()_dropdown) 12 | - [PySimpleGUIWrapper()._fileBrowser](#pysimpleguiwrapper()_filebrowser) 13 | - [PySimpleGUIWrapper()._helpArgHelp](#pysimpleguiwrapper()_helparghelp) 14 | - [PySimpleGUIWrapper()._helpArgName](#pysimpleguiwrapper()_helpargname) 15 | - [PySimpleGUIWrapper()._helpArgNameAndHelp](#pysimpleguiwrapper()_helpargnameandhelp) 16 | - [PySimpleGUIWrapper()._helpCounterWidget](#pysimpleguiwrapper()_helpcounterwidget) 17 | - [PySimpleGUIWrapper()._helpDropdownWidget](#pysimpleguiwrapper()_helpdropdownwidget) 18 | - [PySimpleGUIWrapper()._helpFileWidget](#pysimpleguiwrapper()_helpfilewidget) 19 | - [PySimpleGUIWrapper()._helpFlagWidget](#pysimpleguiwrapper()_helpflagwidget) 20 | - [PySimpleGUIWrapper()._helpTextWidget](#pysimpleguiwrapper()_helptextwidget) 21 | - [PySimpleGUIWrapper()._inputText](#pysimpleguiwrapper()_inputtext) 22 | - [PySimpleGUIWrapper()._label](#pysimpleguiwrapper()_label) 23 | - [PySimpleGUIWrapper()._spin](#pysimpleguiwrapper()_spin) 24 | - [PySimpleGUIWrapper()._title](#pysimpleguiwrapper()_title) 25 | - [PySimpleGUIWrapper().addItemsAndGroups](#pysimpleguiwrapper()additemsandgroups) 26 | - [PySimpleGUIWrapper().addWidgetFromItem](#pysimpleguiwrapper()addwidgetfromitem) 27 | - [PySimpleGUIWrapper().createLayout](#pysimpleguiwrapper()createlayout) 28 | - [PySimpleGUIWrapper().generatePopup](#pysimpleguiwrapper()generatepopup) 29 | - [PySimpleGUIWrapper().getImgData](#pysimpleguiwrapper()getimgdata) 30 | - [PySimpleGUIWrapper().main](#pysimpleguiwrapper()main) 31 | 32 | ## PySimpleGUIWrapper 33 | 34 | [Show source in pysimplegui_wrapper.py:16](../../../../cli2gui/gui/pysimplegui_wrapper.py#L16) 35 | 36 | Wrapper class for PySimpleGUI. 37 | 38 | #### Signature 39 | 40 | ```python 41 | class PySimpleGUIWrapper(AbstractGUI): 42 | def __init__(self, base24Theme: list[str], psg_lib: str) -> None: ... 43 | ``` 44 | 45 | #### See also 46 | 47 | - [AbstractGUI](./abstract_gui.md#abstractgui) 48 | 49 | ### PySimpleGUIWrapper()._button 50 | 51 | [Show source in pysimplegui_wrapper.py:109](../../../../cli2gui/gui/pysimplegui_wrapper.py#L109) 52 | 53 | Return a button. 54 | 55 | #### Signature 56 | 57 | ```python 58 | def _button(self, text: str) -> Any: ... 59 | ``` 60 | 61 | ### PySimpleGUIWrapper()._check 62 | 63 | [Show source in pysimplegui_wrapper.py:99](../../../../cli2gui/gui/pysimplegui_wrapper.py#L99) 64 | 65 | Return a checkbox. 66 | 67 | #### Signature 68 | 69 | ```python 70 | def _check(self, key: str, default: str | None = None) -> Any: ... 71 | ``` 72 | 73 | ### PySimpleGUIWrapper()._dropdown 74 | 75 | [Show source in pysimplegui_wrapper.py:130](../../../../cli2gui/gui/pysimplegui_wrapper.py#L130) 76 | 77 | Return a dropdown. 78 | 79 | #### Signature 80 | 81 | ```python 82 | def _dropdown(self, key: str, argItems: list[str]) -> Any: ... 83 | ``` 84 | 85 | ### PySimpleGUIWrapper()._fileBrowser 86 | 87 | [Show source in pysimplegui_wrapper.py:139](../../../../cli2gui/gui/pysimplegui_wrapper.py#L139) 88 | 89 | Return a fileBrowser button and field. 90 | 91 | #### Signature 92 | 93 | ```python 94 | def _fileBrowser( 95 | self, 96 | key: str, 97 | default: str | None = None, 98 | _type: ItemType = ItemType.File, 99 | additional_properties: dict | None = None, 100 | ) -> list[Any]: ... 101 | ``` 102 | 103 | #### See also 104 | 105 | - [ItemType](../models.md#itemtype) 106 | 107 | ### PySimpleGUIWrapper()._helpArgHelp 108 | 109 | [Show source in pysimplegui_wrapper.py:188](../../../../cli2gui/gui/pysimplegui_wrapper.py#L188) 110 | 111 | Return a label for the arg help text. 112 | 113 | #### Signature 114 | 115 | ```python 116 | def _helpArgHelp(self, helpText: str) -> Any: ... 117 | ``` 118 | 119 | ### PySimpleGUIWrapper()._helpArgName 120 | 121 | [Show source in pysimplegui_wrapper.py:184](../../../../cli2gui/gui/pysimplegui_wrapper.py#L184) 122 | 123 | Return a label for the arg name. 124 | 125 | #### Signature 126 | 127 | ```python 128 | def _helpArgName(self, displayName: str, commands: list[str]) -> Any: ... 129 | ``` 130 | 131 | ### PySimpleGUIWrapper()._helpArgNameAndHelp 132 | 133 | [Show source in pysimplegui_wrapper.py:192](../../../../cli2gui/gui/pysimplegui_wrapper.py#L192) 134 | 135 | Return a column containing the argument name and help text. 136 | 137 | #### Signature 138 | 139 | ```python 140 | def _helpArgNameAndHelp( 141 | self, commands: list[str], helpText: str, displayName: str 142 | ) -> Any: ... 143 | ``` 144 | 145 | ### PySimpleGUIWrapper()._helpCounterWidget 146 | 147 | [Show source in pysimplegui_wrapper.py:243](../../../../cli2gui/gui/pysimplegui_wrapper.py#L243) 148 | 149 | Return a set of self that make up an arg with text. 150 | 151 | #### Signature 152 | 153 | ```python 154 | def _helpCounterWidget(self, item: Item) -> list[Any]: ... 155 | ``` 156 | 157 | #### See also 158 | 159 | - [Item](../models.md#item) 160 | 161 | ### PySimpleGUIWrapper()._helpDropdownWidget 162 | 163 | [Show source in pysimplegui_wrapper.py:268](../../../../cli2gui/gui/pysimplegui_wrapper.py#L268) 164 | 165 | Return a set of self that make up an arg with a choice. 166 | 167 | #### Signature 168 | 169 | ```python 170 | def _helpDropdownWidget(self, item: Item) -> list[Any]: ... 171 | ``` 172 | 173 | #### See also 174 | 175 | - [Item](../models.md#item) 176 | 177 | ### PySimpleGUIWrapper()._helpFileWidget 178 | 179 | [Show source in pysimplegui_wrapper.py:255](../../../../cli2gui/gui/pysimplegui_wrapper.py#L255) 180 | 181 | Return a set of self that make up an arg with a file. 182 | 183 | #### Signature 184 | 185 | ```python 186 | def _helpFileWidget(self, item: Item) -> list[Any]: ... 187 | ``` 188 | 189 | #### See also 190 | 191 | - [Item](../models.md#item) 192 | 193 | ### PySimpleGUIWrapper()._helpFlagWidget 194 | 195 | [Show source in pysimplegui_wrapper.py:218](../../../../cli2gui/gui/pysimplegui_wrapper.py#L218) 196 | 197 | Return a set of self that make up an arg with true/ false. 198 | 199 | #### Signature 200 | 201 | ```python 202 | def _helpFlagWidget(self, item: Item) -> list[Any]: ... 203 | ``` 204 | 205 | #### See also 206 | 207 | - [Item](../models.md#item) 208 | 209 | ### PySimpleGUIWrapper()._helpTextWidget 210 | 211 | [Show source in pysimplegui_wrapper.py:230](../../../../cli2gui/gui/pysimplegui_wrapper.py#L230) 212 | 213 | Return a set of self that make up an arg with text. 214 | 215 | #### Signature 216 | 217 | ```python 218 | def _helpTextWidget(self, item: Item) -> list[Any]: ... 219 | ``` 220 | 221 | #### See also 222 | 223 | - [Item](../models.md#item) 224 | 225 | ### PySimpleGUIWrapper()._inputText 226 | 227 | [Show source in pysimplegui_wrapper.py:78](../../../../cli2gui/gui/pysimplegui_wrapper.py#L78) 228 | 229 | Return an input text field. 230 | 231 | #### Signature 232 | 233 | ```python 234 | def _inputText(self, key: str, default: str | None = None) -> Any: ... 235 | ``` 236 | 237 | ### PySimpleGUIWrapper()._label 238 | 239 | [Show source in pysimplegui_wrapper.py:118](../../../../cli2gui/gui/pysimplegui_wrapper.py#L118) 240 | 241 | Return a label. 242 | 243 | #### Signature 244 | 245 | ```python 246 | def _label(self, text: str, font: int = 11) -> Any: ... 247 | ``` 248 | 249 | ### PySimpleGUIWrapper()._spin 250 | 251 | [Show source in pysimplegui_wrapper.py:88](../../../../cli2gui/gui/pysimplegui_wrapper.py#L88) 252 | 253 | Return an input text field. 254 | 255 | #### Signature 256 | 257 | ```python 258 | def _spin(self, key: str, default: str | None = None) -> Any: ... 259 | ``` 260 | 261 | ### PySimpleGUIWrapper()._title 262 | 263 | [Show source in pysimplegui_wrapper.py:199](../../../../cli2gui/gui/pysimplegui_wrapper.py#L199) 264 | 265 | Return a set of self that make up the application header. 266 | 267 | #### Signature 268 | 269 | ```python 270 | def _title(self, text: str, image: str = "") -> list[Any]: ... 271 | ``` 272 | 273 | ### PySimpleGUIWrapper().addItemsAndGroups 274 | 275 | [Show source in pysimplegui_wrapper.py:371](../../../../cli2gui/gui/pysimplegui_wrapper.py#L371) 276 | 277 | Items and groups and return a list of psg Elements. 278 | 279 | #### Arguments 280 | 281 | - `section` *Group* - section with a name to display and items 282 | 283 | #### Returns 284 | 285 | Type: *list[list[Element]]* 286 | updated argConstruct 287 | 288 | #### Signature 289 | 290 | ```python 291 | def addItemsAndGroups(self, section: Group) -> list[list[Any]]: ... 292 | ``` 293 | 294 | #### See also 295 | 296 | - [Group](../models.md#group) 297 | 298 | ### PySimpleGUIWrapper().addWidgetFromItem 299 | 300 | [Show source in pysimplegui_wrapper.py:287](../../../../cli2gui/gui/pysimplegui_wrapper.py#L287) 301 | 302 | Select a widget based on the item type. 303 | 304 | #### Arguments 305 | 306 | - `item` *Item* - the item 307 | 308 | #### Signature 309 | 310 | ```python 311 | def addWidgetFromItem(self, item: Item) -> list[Any]: ... 312 | ``` 313 | 314 | #### See also 315 | 316 | - [Item](../models.md#item) 317 | 318 | ### PySimpleGUIWrapper().createLayout 319 | 320 | [Show source in pysimplegui_wrapper.py:396](../../../../cli2gui/gui/pysimplegui_wrapper.py#L396) 321 | 322 | Create the pysimplegui layout from the build spec. 323 | 324 | #### Arguments 325 | 326 | - `buildSpec` *FullBuildSpec* - build spec containing widget 327 | :param str | list[str] menu: menu definition. containing menu keys 328 | 329 | #### Returns 330 | 331 | Type: *list[list[Any]]* 332 | list of self (layout list) 333 | 334 | #### Signature 335 | 336 | ```python 337 | def createLayout( 338 | self, buildSpec: FullBuildSpec, menu: str | list[str] 339 | ) -> list[list[Any]]: ... 340 | ``` 341 | 342 | #### See also 343 | 344 | - [FullBuildSpec](../models.md#fullbuildspec) 345 | 346 | ### PySimpleGUIWrapper().generatePopup 347 | 348 | [Show source in pysimplegui_wrapper.py:311](../../../../cli2gui/gui/pysimplegui_wrapper.py#L311) 349 | 350 | Create the popup window. 351 | 352 | #### Arguments 353 | 354 | ---- 355 | - `buildSpec` *FullBuildSpec* - [description] 356 | values (Union[dict[Any, Any]): Returned when a button is clicked. Such 357 | as the menu 358 | 359 | #### Returns 360 | 361 | ------- 362 | - `Window` - A PySimpleGui Window 363 | 364 | #### Signature 365 | 366 | ```python 367 | def generatePopup( 368 | self, buildSpec: FullBuildSpec, values: dict[Any, Any] | list[Any] 369 | ) -> Any: ... 370 | ``` 371 | 372 | #### See also 373 | 374 | - [FullBuildSpec](../models.md#fullbuildspec) 375 | 376 | ### PySimpleGUIWrapper().getImgData 377 | 378 | [Show source in pysimplegui_wrapper.py:508](../../../../cli2gui/gui/pysimplegui_wrapper.py#L508) 379 | 380 | Generate image data using PIL. 381 | 382 | #### Signature 383 | 384 | ```python 385 | def getImgData(self, imagePath: str, first: bool = False) -> bytes: ... 386 | ``` 387 | 388 | ### PySimpleGUIWrapper().main 389 | 390 | [Show source in pysimplegui_wrapper.py:463](../../../../cli2gui/gui/pysimplegui_wrapper.py#L463) 391 | 392 | Run the gui (psg) with a given buildSpec, quit_callback, and run_callback. 393 | 394 | #### Arguments 395 | 396 | - `buildSpec` *FullBuildSpec* - Full cli parse/ build spec 397 | :param Callable[[], None] quit_callback: generic callable used to quit 398 | :param Callable[[dict[str, Any]], None] run_callback: generic callable used to run 399 | 400 | #### Signature 401 | 402 | ```python 403 | def main( 404 | self, 405 | buildSpec: FullBuildSpec, 406 | quit_callback: Callable[[], None], 407 | run_callback: Callable[[dict[str, Any]], None], 408 | ) -> None: ... 409 | ``` 410 | 411 | #### See also 412 | 413 | - [FullBuildSpec](../models.md#fullbuildspec) -------------------------------------------------------------------------------- /documentation/reference/cli2gui/index.md: -------------------------------------------------------------------------------- 1 | # Cli2gui 2 | 3 | [Cli2gui Index](../README.md#cli2gui-index) / Cli2gui 4 | 5 | > Auto-generated documentation for [cli2gui](../../../cli2gui/__init__.py) module. 6 | 7 | - [Cli2gui](#cli2gui) 8 | - [Modules](#modules) 9 | 10 | ## Modules 11 | 12 | - [Application](application/index.md) 13 | - [Decorators](./decorators.md) 14 | - [Gui](gui/index.md) 15 | - [Models](./models.md) 16 | - [Tojson](tojson/index.md) -------------------------------------------------------------------------------- /documentation/reference/cli2gui/models.md: -------------------------------------------------------------------------------- 1 | # Models 2 | 3 | [Cli2gui Index](../README.md#cli2gui-index) / [Cli2gui](./index.md#cli2gui) / Models 4 | 5 | > Auto-generated documentation for [cli2gui.models](../../../cli2gui/models.py) module. 6 | 7 | - [Models](#models) 8 | - [BuildSpec](#buildspec) 9 | - [FullBuildSpec](#fullbuildspec) 10 | - [GUIType](#guitype) 11 | - [Group](#group) 12 | - [Item](#item) 13 | - [ItemType](#itemtype) 14 | - [ParserRep](#parserrep) 15 | - [ParserType](#parsertype) 16 | 17 | ## BuildSpec 18 | 19 | [Show source in models.py:14](../../../cli2gui/models.py#L14) 20 | 21 | Representation for the BuildSpec. 22 | 23 | #### Signature 24 | 25 | ```python 26 | class BuildSpec: ... 27 | ``` 28 | 29 | 30 | 31 | ## FullBuildSpec 32 | 33 | [Show source in models.py:80](../../../cli2gui/models.py#L80) 34 | 35 | Representation for the FullBuildSpec (BuildSpec + ParserRep). 36 | 37 | #### Signature 38 | 39 | ```python 40 | class FullBuildSpec: ... 41 | ``` 42 | 43 | 44 | 45 | ## GUIType 46 | 47 | [Show source in models.py:113](../../../cli2gui/models.py#L113) 48 | 49 | Supported gui types. 50 | 51 | #### Signature 52 | 53 | ```python 54 | class GUIType(str, Enum): ... 55 | ``` 56 | 57 | 58 | 59 | ## Group 60 | 61 | [Show source in models.py:63](../../../cli2gui/models.py#L63) 62 | 63 | Representation for an argument group. 64 | 65 | #### Signature 66 | 67 | ```python 68 | class Group: ... 69 | ``` 70 | 71 | 72 | 73 | ## Item 74 | 75 | [Show source in models.py:30](../../../cli2gui/models.py#L30) 76 | 77 | Representation for an arg_item. 78 | 79 | #### Signature 80 | 81 | ```python 82 | class Item: ... 83 | ``` 84 | 85 | 86 | 87 | ## ItemType 88 | 89 | [Show source in models.py:45](../../../cli2gui/models.py#L45) 90 | 91 | Enum of ItemTypes. 92 | 93 | #### Signature 94 | 95 | ```python 96 | class ItemType(Enum): ... 97 | ``` 98 | 99 | 100 | 101 | ## ParserRep 102 | 103 | [Show source in models.py:72](../../../cli2gui/models.py#L72) 104 | 105 | Representation for a parser. 106 | 107 | #### Signature 108 | 109 | ```python 110 | class ParserRep: ... 111 | ``` 112 | 113 | 114 | 115 | ## ParserType 116 | 117 | [Show source in models.py:98](../../../cli2gui/models.py#L98) 118 | 119 | Supported parser types. 120 | 121 | #### Signature 122 | 123 | ```python 124 | class ParserType(str, Enum): ... 125 | ``` -------------------------------------------------------------------------------- /documentation/reference/cli2gui/tojson/argparse2json.md: -------------------------------------------------------------------------------- 1 | # Argparse2json 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Tojson](./index.md#tojson) / Argparse2json 4 | 5 | > Auto-generated documentation for [cli2gui.tojson.argparse2json](../../../../cli2gui/tojson/argparse2json.py) module. 6 | 7 | - [Argparse2json](#argparse2json) 8 | - [ArgparseGroup](#argparsegroup) 9 | - [actionToJson](#actiontojson) 10 | - [buildRadioGroup](#buildradiogroup) 11 | - [categorizeGroups](#categorizegroups) 12 | - [categorizeItems](#categorizeitems) 13 | - [chooseName](#choosename) 14 | - [containsActions](#containsactions) 15 | - [convert](#convert) 16 | - [extractRawGroups](#extractrawgroups) 17 | - [fileActionToJson](#fileactiontojson) 18 | - [isDefaultProgname](#isdefaultprogname) 19 | - [iterParsers](#iterparsers) 20 | - [process](#process) 21 | - [reapplyMutexGroups](#reapplymutexgroups) 22 | - [stripEmpty](#stripempty) 23 | 24 | ## ArgparseGroup 25 | 26 | [Show source in argparse2json.py:26](../../../../cli2gui/tojson/argparse2json.py#L26) 27 | 28 | Class to represent an ArgparseGroup. 29 | 30 | #### Signature 31 | 32 | ```python 33 | class ArgparseGroup(TypedDict): ... 34 | ``` 35 | 36 | 37 | 38 | ## actionToJson 39 | 40 | [Show source in argparse2json.py:123](../../../../cli2gui/tojson/argparse2json.py#L123) 41 | 42 | Generate json for an action and set the widget - used by the application. 43 | 44 | #### Signature 45 | 46 | ```python 47 | def actionToJson(action: argparse.Action, widget: ItemType) -> Item: ... 48 | ``` 49 | 50 | #### See also 51 | 52 | - [ItemType](../models.md#itemtype) 53 | - [Item](../models.md#item) 54 | 55 | 56 | 57 | ## buildRadioGroup 58 | 59 | [Show source in argparse2json.py:137](../../../../cli2gui/tojson/argparse2json.py#L137) 60 | 61 | Create a radio group for a mutex group of arguments. 62 | 63 | #### Signature 64 | 65 | ```python 66 | def buildRadioGroup(mutexGroup: _MutuallyExclusiveGroup) -> Item: ... 67 | ``` 68 | 69 | #### See also 70 | 71 | - [Item](../models.md#item) 72 | 73 | 74 | 75 | ## categorizeGroups 76 | 77 | [Show source in argparse2json.py:181](../../../../cli2gui/tojson/argparse2json.py#L181) 78 | 79 | Categorize the parser groups and arg_items. 80 | 81 | #### Signature 82 | 83 | ```python 84 | def categorizeGroups(groups: list[ArgparseGroup]) -> list[Group]: ... 85 | ``` 86 | 87 | #### See also 88 | 89 | - [ArgparseGroup](#argparsegroup) 90 | - [Group](../models.md#group) 91 | 92 | 93 | 94 | ## categorizeItems 95 | 96 | [Show source in argparse2json.py:151](../../../../cli2gui/tojson/argparse2json.py#L151) 97 | 98 | Catergorise each action and generate json. 99 | 100 | #### Signature 101 | 102 | ```python 103 | def categorizeItems(actions: list[argparse.Action]) -> Generator[Item, None, None]: ... 104 | ``` 105 | 106 | #### See also 107 | 108 | - [Item](../models.md#item) 109 | 110 | 111 | 112 | ## chooseName 113 | 114 | [Show source in argparse2json.py:55](../../../../cli2gui/tojson/argparse2json.py#L55) 115 | 116 | Get the program name. 117 | 118 | #### Signature 119 | 120 | ```python 121 | def chooseName(name: str, subparser: argparse.ArgumentParser) -> str: ... 122 | ``` 123 | 124 | 125 | 126 | ## containsActions 127 | 128 | [Show source in argparse2json.py:60](../../../../cli2gui/tojson/argparse2json.py#L60) 129 | 130 | Check if any actions(a) are present in actions(b). 131 | 132 | #### Signature 133 | 134 | ```python 135 | def containsActions( 136 | actionA: list[argparse.Action], actionB: list[argparse.Action] 137 | ) -> set[argparse.Action]: ... 138 | ``` 139 | 140 | 141 | 142 | ## convert 143 | 144 | [Show source in argparse2json.py:208](../../../../cli2gui/tojson/argparse2json.py#L208) 145 | 146 | Convert argparse to a dict. 147 | 148 | #### Arguments 149 | 150 | ---- 151 | - `parser` *argparse.ArgumentParser* - argparse parser 152 | 153 | #### Returns 154 | 155 | ------- 156 | - `ParserRep` - dictionary representing parser object 157 | 158 | #### Signature 159 | 160 | ```python 161 | def convert(parser: argparse.ArgumentParser) -> ParserRep: ... 162 | ``` 163 | 164 | #### See also 165 | 166 | - [ParserRep](../models.md#parserrep) 167 | 168 | 169 | 170 | ## extractRawGroups 171 | 172 | [Show source in argparse2json.py:99](../../../../cli2gui/tojson/argparse2json.py#L99) 173 | 174 | Recursively extract argument groups and associated actions from ParserGroup objects. 175 | 176 | #### Signature 177 | 178 | ```python 179 | def extractRawGroups(actionGroup: argparse._ArgumentGroup) -> ArgparseGroup: ... 180 | ``` 181 | 182 | #### See also 183 | 184 | - [ArgparseGroup](#argparsegroup) 185 | 186 | 187 | 188 | ## fileActionToJson 189 | 190 | [Show source in argparse2json.py:111](../../../../cli2gui/tojson/argparse2json.py#L111) 191 | 192 | Convert an action of type Path or argparse.FileType to an Item. 193 | 194 | #### Signature 195 | 196 | ```python 197 | def fileActionToJson(action: argparse.Action, widget: ItemType) -> Item: ... 198 | ``` 199 | 200 | #### See also 201 | 202 | - [ItemType](../models.md#itemtype) 203 | - [Item](../models.md#item) 204 | 205 | 206 | 207 | ## isDefaultProgname 208 | 209 | [Show source in argparse2json.py:50](../../../../cli2gui/tojson/argparse2json.py#L50) 210 | 211 | Identify if the passed name is the default program name. 212 | 213 | #### Signature 214 | 215 | ```python 216 | def isDefaultProgname(name: str, subparser: argparse.ArgumentParser) -> bool: ... 217 | ``` 218 | 219 | 220 | 221 | ## iterParsers 222 | 223 | [Show source in argparse2json.py:34](../../../../cli2gui/tojson/argparse2json.py#L34) 224 | 225 | Iterate over name, parser pairs. 226 | 227 | #### Signature 228 | 229 | ```python 230 | def iterParsers( 231 | parser: argparse.ArgumentParser, 232 | ) -> list[tuple[str, argparse.ArgumentParser]]: ... 233 | ``` 234 | 235 | 236 | 237 | ## process 238 | 239 | [Show source in argparse2json.py:198](../../../../cli2gui/tojson/argparse2json.py#L198) 240 | 241 | Reapply the mutex groups and then categorize them and the arg_items under the parser. 242 | 243 | #### Signature 244 | 245 | ```python 246 | def process(parser: argparse.ArgumentParser) -> list[Group]: ... 247 | ``` 248 | 249 | #### See also 250 | 251 | - [Group](../models.md#group) 252 | 253 | 254 | 255 | ## reapplyMutexGroups 256 | 257 | [Show source in argparse2json.py:67](../../../../cli2gui/tojson/argparse2json.py#L67) 258 | 259 | _argparse stores mutually exclusive groups independently. 260 | of all other groups. So, they must be manually re-combined 261 | with the groups/subgroups to which they were originally declared 262 | in order to have them appear in the correct location in the UI. 263 | 264 | Order is attempted to be preserved by inserting the MutexGroup 265 | into the _actions list at the first occurrence of any item 266 | where the two groups intersect. 267 | 268 | #### Signature 269 | 270 | ```python 271 | def reapplyMutexGroups( 272 | mutexGroups: list[argparse._MutuallyExclusiveGroup], actionGroups: list[Any] 273 | ) -> list[Any]: ... 274 | ``` 275 | 276 | 277 | 278 | ## stripEmpty 279 | 280 | [Show source in argparse2json.py:193](../../../../cli2gui/tojson/argparse2json.py#L193) 281 | 282 | Remove groups where group['arg_items'] is false. 283 | 284 | #### Signature 285 | 286 | ```python 287 | def stripEmpty(groups: list[ArgparseGroup]) -> list[ArgparseGroup]: ... 288 | ``` 289 | 290 | #### See also 291 | 292 | - [ArgparseGroup](#argparsegroup) -------------------------------------------------------------------------------- /documentation/reference/cli2gui/tojson/click2json.md: -------------------------------------------------------------------------------- 1 | # Click2json 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Tojson](./index.md#tojson) / Click2json 4 | 5 | > Auto-generated documentation for [cli2gui.tojson.click2json](../../../../cli2gui/tojson/click2json.py) module. 6 | 7 | - [Click2json](#click2json) 8 | - [actionToJson](#actiontojson) 9 | - [categorize](#categorize) 10 | - [convert](#convert) 11 | - [extract](#extract) 12 | 13 | ## actionToJson 14 | 15 | [Show source in click2json.py:29](../../../../cli2gui/tojson/click2json.py#L29) 16 | 17 | Generate json for an action and set the widget - used by the application. 18 | 19 | #### Signature 20 | 21 | ```python 22 | def actionToJson(action: Any, widget: ItemType, other: dict | None = None) -> Item: ... 23 | ``` 24 | 25 | #### See also 26 | 27 | - [ItemType](../models.md#itemtype) 28 | - [Item](../models.md#item) 29 | 30 | 31 | 32 | ## categorize 33 | 34 | [Show source in click2json.py:47](../../../../cli2gui/tojson/click2json.py#L47) 35 | 36 | Catergorise each action and generate json. 37 | 38 | #### Signature 39 | 40 | ```python 41 | def categorize(actions: list[Any]) -> Generator[Item, None, None]: ... 42 | ``` 43 | 44 | #### See also 45 | 46 | - [Item](../models.md#item) 47 | 48 | 49 | 50 | ## convert 51 | 52 | [Show source in click2json.py:66](../../../../cli2gui/tojson/click2json.py#L66) 53 | 54 | Convert click to a dict. 55 | 56 | #### Arguments 57 | 58 | ---- 59 | - `parser` *click.core.Command* - click parser 60 | 61 | #### Returns 62 | 63 | ------- 64 | - `ParserRep` - dictionary representing parser object 65 | 66 | #### Signature 67 | 68 | ```python 69 | def convert(parser: Any) -> ParserRep: ... 70 | ``` 71 | 72 | #### See also 73 | 74 | - [ParserRep](../models.md#parserrep) 75 | 76 | 77 | 78 | ## extract 79 | 80 | [Show source in click2json.py:11](../../../../cli2gui/tojson/click2json.py#L11) 81 | 82 | Get the actions as json for the parser. 83 | 84 | #### Signature 85 | 86 | ```python 87 | def extract(parser: Any) -> list[Group]: ... 88 | ``` 89 | 90 | #### See also 91 | 92 | - [Group](../models.md#group) -------------------------------------------------------------------------------- /documentation/reference/cli2gui/tojson/docopt2json.md: -------------------------------------------------------------------------------- 1 | # Docopt2json 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Tojson](./index.md#tojson) / Docopt2json 4 | 5 | > Auto-generated documentation for [cli2gui.tojson.docopt2json](../../../../cli2gui/tojson/docopt2json.py) module. 6 | 7 | - [Docopt2json](#docopt2json) 8 | - [actionToJson](#actiontojson) 9 | - [categorize](#categorize) 10 | - [convert](#convert) 11 | - [extract](#extract) 12 | - [parse](#parse) 13 | - [parseOpt](#parseopt) 14 | - [parsePos](#parsepos) 15 | - [parseSection](#parsesection) 16 | 17 | ## actionToJson 18 | 19 | [Show source in docopt2json.py:11](../../../../cli2gui/tojson/docopt2json.py#L11) 20 | 21 | Generate json for an action and set the widget - used by the application. 22 | 23 | #### Signature 24 | 25 | ```python 26 | def actionToJson( 27 | action: tuple[str, str, int, Any, str], widget: ItemType, isPos: bool 28 | ) -> Item: ... 29 | ``` 30 | 31 | #### See also 32 | 33 | - [ItemType](../models.md#itemtype) 34 | - [Item](../models.md#item) 35 | 36 | 37 | 38 | ## categorize 39 | 40 | [Show source in docopt2json.py:37](../../../../cli2gui/tojson/docopt2json.py#L37) 41 | 42 | Catergorise each action and generate json. 43 | 44 | Each action is in the form (short, long, argcount, value, help_message) 45 | 46 | #### Signature 47 | 48 | ```python 49 | def categorize( 50 | actions: list[tuple[str, str, int, Any, str]], isPos: bool = False 51 | ) -> Iterator[Item]: ... 52 | ``` 53 | 54 | #### See also 55 | 56 | - [Item](../models.md#item) 57 | 58 | 59 | 60 | ## convert 61 | 62 | [Show source in docopt2json.py:117](../../../../cli2gui/tojson/docopt2json.py#L117) 63 | 64 | Convert getopt to a dict. 65 | 66 | #### Arguments 67 | 68 | ---- 69 | - `parser` *Any* - docopt parser 70 | 71 | #### Returns 72 | 73 | ------- 74 | - `ParserRep` - dictionary representing parser object 75 | 76 | #### Signature 77 | 78 | ```python 79 | def convert(parser: Any) -> ParserRep: ... 80 | ``` 81 | 82 | #### See also 83 | 84 | - [ParserRep](../models.md#parserrep) 85 | 86 | 87 | 88 | ## extract 89 | 90 | [Show source in docopt2json.py:55](../../../../cli2gui/tojson/docopt2json.py#L55) 91 | 92 | Get the actions as json for the parser. 93 | 94 | #### Signature 95 | 96 | ```python 97 | def extract(parser: Any) -> list[Group]: ... 98 | ``` 99 | 100 | #### See also 101 | 102 | - [Group](../models.md#group) 103 | 104 | 105 | 106 | ## parse 107 | 108 | [Show source in docopt2json.py:76](../../../../cli2gui/tojson/docopt2json.py#L76) 109 | 110 | Parse an option help text, adapted from docopt. 111 | 112 | #### Signature 113 | 114 | ```python 115 | def parse(optionDescription: str) -> tuple[str, str, int, Any, str]: ... 116 | ``` 117 | 118 | 119 | 120 | ## parseOpt 121 | 122 | [Show source in docopt2json.py:94](../../../../cli2gui/tojson/docopt2json.py#L94) 123 | 124 | Parse an option help text, adapted from docopt. 125 | 126 | #### Signature 127 | 128 | ```python 129 | def parseOpt(doc: Any) -> list[tuple[str, str, int, Any, str]]: ... 130 | ``` 131 | 132 | 133 | 134 | ## parsePos 135 | 136 | [Show source in docopt2json.py:106](../../../../cli2gui/tojson/docopt2json.py#L106) 137 | 138 | Parse positional arguments from docstring. 139 | 140 | #### Signature 141 | 142 | ```python 143 | def parsePos(doc: str) -> list[tuple[str, str]]: ... 144 | ``` 145 | 146 | 147 | 148 | ## parseSection 149 | 150 | [Show source in docopt2json.py:67](../../../../cli2gui/tojson/docopt2json.py#L67) 151 | 152 | Taken from docopt. 153 | 154 | #### Signature 155 | 156 | ```python 157 | def parseSection(name: str, source: str) -> list[str]: ... 158 | ``` -------------------------------------------------------------------------------- /documentation/reference/cli2gui/tojson/getopt2json.md: -------------------------------------------------------------------------------- 1 | # Getopt2json 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Tojson](./index.md#tojson) / Getopt2json 4 | 5 | > Auto-generated documentation for [cli2gui.tojson.getopt2json](../../../../cli2gui/tojson/getopt2json.py) module. 6 | 7 | - [Getopt2json](#getopt2json) 8 | - [actionToJson](#actiontojson) 9 | - [catLong](#catlong) 10 | - [catShort](#catshort) 11 | - [convert](#convert) 12 | - [process](#process) 13 | 14 | ## actionToJson 15 | 16 | [Show source in getopt2json.py:13](../../../../cli2gui/tojson/getopt2json.py#L13) 17 | 18 | Convert an arg to json, behave in the same way as argparse hence the large 19 | amount of duplication. 20 | 21 | #### Signature 22 | 23 | ```python 24 | def actionToJson(action: str, widget: ItemType, short: bool = True) -> Item: ... 25 | ``` 26 | 27 | #### See also 28 | 29 | - [ItemType](../models.md#itemtype) 30 | - [Item](../models.md#item) 31 | 32 | 33 | 34 | ## catLong 35 | 36 | [Show source in getopt2json.py:28](../../../../cli2gui/tojson/getopt2json.py#L28) 37 | 38 | Categorize long args. 39 | 40 | #### Signature 41 | 42 | ```python 43 | def catLong(actions: list[str]) -> Generator[Item, None, None]: ... 44 | ``` 45 | 46 | #### See also 47 | 48 | - [Item](../models.md#item) 49 | 50 | 51 | 52 | ## catShort 53 | 54 | [Show source in getopt2json.py:38](../../../../cli2gui/tojson/getopt2json.py#L38) 55 | 56 | Categorize short args. 57 | 58 | #### Signature 59 | 60 | ```python 61 | def catShort(actions: list[str]) -> Generator[Item, None, None]: ... 62 | ``` 63 | 64 | #### See also 65 | 66 | - [Item](../models.md#item) 67 | 68 | 69 | 70 | ## convert 71 | 72 | [Show source in getopt2json.py:64](../../../../cli2gui/tojson/getopt2json.py#L64) 73 | 74 | Convert getopt to a dict. 75 | 76 | #### Arguments 77 | 78 | ---- 79 | parser (tuple[list[str], list[str]]): getopt parser 80 | 81 | #### Returns 82 | 83 | ------- 84 | - `ParserRep` - dictionary representing parser object 85 | 86 | #### Signature 87 | 88 | ```python 89 | def convert(parser: tuple[list[str], list[str]]) -> ParserRep: ... 90 | ``` 91 | 92 | #### See also 93 | 94 | - [ParserRep](../models.md#parserrep) 95 | 96 | 97 | 98 | ## process 99 | 100 | [Show source in getopt2json.py:55](../../../../cli2gui/tojson/getopt2json.py#L55) 101 | 102 | Generate a group (or section). 103 | 104 | #### Signature 105 | 106 | ```python 107 | def process( 108 | group: list[str], 109 | groupName: str, 110 | categorize: Callable[[list[str]], Generator[Item, None, None]], 111 | ) -> list[Group]: ... 112 | ``` 113 | 114 | #### See also 115 | 116 | - [Group](../models.md#group) 117 | - [Item](../models.md#item) -------------------------------------------------------------------------------- /documentation/reference/cli2gui/tojson/index.md: -------------------------------------------------------------------------------- 1 | # Tojson 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / Tojson 4 | 5 | > Auto-generated documentation for [cli2gui.tojson](../../../../cli2gui/tojson/__init__.py) module. 6 | 7 | - [Tojson](#tojson) 8 | - [Modules](#modules) 9 | 10 | ## Modules 11 | 12 | - [Argparse2json](./argparse2json.md) 13 | - [Click2json](./click2json.md) 14 | - [Docopt2json](./docopt2json.md) 15 | - [Getopt2json](./getopt2json.md) 16 | - [Optparse2json](./optparse2json.md) -------------------------------------------------------------------------------- /documentation/reference/cli2gui/tojson/optparse2json.md: -------------------------------------------------------------------------------- 1 | # Optparse2json 2 | 3 | [Cli2gui Index](../../README.md#cli2gui-index) / [Cli2gui](../index.md#cli2gui) / [Tojson](./index.md#tojson) / Optparse2json 4 | 5 | > Auto-generated documentation for [cli2gui.tojson.optparse2json](../../../../cli2gui/tojson/optparse2json.py) module. 6 | 7 | - [Optparse2json](#optparse2json) 8 | - [actionToJson](#actiontojson) 9 | - [categorize](#categorize) 10 | - [convert](#convert) 11 | - [extractGroups](#extractgroups) 12 | - [extractOptions](#extractoptions) 13 | 14 | ## actionToJson 15 | 16 | [Show source in optparse2json.py:40](../../../../cli2gui/tojson/optparse2json.py#L40) 17 | 18 | Generate json for an action and set the widget - used by the application. 19 | 20 | #### Signature 21 | 22 | ```python 23 | def actionToJson(action: optparse.Option, widget: ItemType) -> Item: ... 24 | ``` 25 | 26 | #### See also 27 | 28 | - [ItemType](../models.md#itemtype) 29 | - [Item](../models.md#item) 30 | 31 | 32 | 33 | ## categorize 34 | 35 | [Show source in optparse2json.py:58](../../../../cli2gui/tojson/optparse2json.py#L58) 36 | 37 | Catergorise each action and generate json. 38 | 39 | #### Signature 40 | 41 | ```python 42 | def categorize(actions: list[optparse.Option]) -> Generator[Item, None, None]: ... 43 | ``` 44 | 45 | #### See also 46 | 47 | - [Item](../models.md#item) 48 | 49 | 50 | 51 | ## convert 52 | 53 | [Show source in optparse2json.py:73](../../../../cli2gui/tojson/optparse2json.py#L73) 54 | 55 | Convert argparse to a dict. 56 | 57 | #### Arguments 58 | 59 | ---- 60 | - `parser` *optparse.OptionParser* - optparse parser 61 | 62 | #### Returns 63 | 64 | ------- 65 | - `ParserRep` - dictionary representing parser object 66 | 67 | #### Signature 68 | 69 | ```python 70 | def convert(parser: optparse.OptionParser) -> ParserRep: ... 71 | ``` 72 | 73 | #### See also 74 | 75 | - [ParserRep](../models.md#parserrep) 76 | 77 | 78 | 79 | ## extractGroups 80 | 81 | [Show source in optparse2json.py:28](../../../../cli2gui/tojson/optparse2json.py#L28) 82 | 83 | Get the actions as json for each item and group under the parser. 84 | 85 | #### Signature 86 | 87 | ```python 88 | def extractGroups(parser: optparse.OptionParser) -> Group: ... 89 | ``` 90 | 91 | #### See also 92 | 93 | - [Group](../models.md#group) 94 | 95 | 96 | 97 | ## extractOptions 98 | 99 | [Show source in optparse2json.py:15](../../../../cli2gui/tojson/optparse2json.py#L15) 100 | 101 | Get the actions as json for each item under a group. 102 | 103 | #### Signature 104 | 105 | ```python 106 | def extractOptions(optionGroup: optparse.OptionGroup) -> Group: ... 107 | ``` 108 | 109 | #### See also 110 | 111 | - [Group](../models.md#group) -------------------------------------------------------------------------------- /documentation/tutorials/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Tutorial: How to Use `Cli2Gui` in Your Project 4 | 5 | `Cli2Gui` is a Python package that allows you to transform command-line interface (CLI) applications 6 | into graphical user interfaces (GUIs). It supports popular parsers like `argparse`, and different 7 | GUI libraries, like `freesimplegui`. In this tutorial, we will walk through how to integrate 8 | `Cli2Gui` into your Python project. 9 | 10 | - [Setup](#setup) 11 | - [Basic Usage](#basic-usage) 12 | - [Basic CLI Application](#basic-cli-application) 13 | - [Adding `Cli2Gui`](#adding-cli2gui) 14 | - [Advanced Example with Argument Groups](#advanced-example-with-argument-groups) 15 | - [Customizing the GUI](#customizing-the-gui) 16 | 17 | ## Setup 18 | 19 | For the examples below, install the `Cli2Gui` package by running: 20 | 21 | ```bash 22 | pip install cli2gui[fsg] 23 | ``` 24 | 25 | Note: If you need the functionality provided by these libraries, use the following extras: 26 | 27 | - **psg**: For PySimpleGUI support, adding easy-to-build, functional GUIs. 28 | - **fsg**: For FreeSimpleGUI, a lightweight, streamlined GUI option. 29 | - **web**: To run your app in a web browser via PySimpleGUIWeb. 30 | - **qt**: For PySimpleGUIQt, creating polished, native desktop GUIs. 31 | - **pandoc**: To pretty print markdown files and similar 32 | 33 | ## Basic Usage 34 | 35 | The primary way to use `Cli2Gui` is through a decorator that you add to your main function. The 36 | decorator transforms your CLI into a GUI when necessary. 37 | 38 | Here's a simple example of how to integrate `Cli2Gui` into a basic program: 39 | 40 | ### Basic CLI Application 41 | 42 | ```python 43 | import argparse 44 | 45 | def run(args): 46 | print(args.arg) 47 | 48 | def main(): 49 | parser = argparse.ArgumentParser(description="Example parser") 50 | parser.add_argument("arg", type=str, help="Positional argument") 51 | args = parser.parse_args() 52 | run(args) 53 | 54 | if __name__ == "__main__": 55 | main() 56 | ``` 57 | 58 | In this basic example, we have a CLI app that takes a positional argument. Next, we will enhance 59 | it with `Cli2Gui` to provide a GUI option. 60 | 61 | ### Adding `Cli2Gui` 62 | 63 | We'll now decorate the `main` function with `Cli2Gui`: 64 | 65 | ```python 66 | from cli2gui import Cli2Gui 67 | 68 | # Define the function that will handle the program's logic 69 | def run(args): 70 | print(args.arg) 71 | 72 | # Use Cli2Gui as a decorator to convert CLI into a GUI, using freesimplegui 73 | @Cli2Gui(run_function=run, gui="freesimplegui") 74 | def main(): 75 | parser = argparse.ArgumentParser(description="Example parser with GUI support") 76 | parser.add_argument("arg", type=str, help="Positional argument") 77 | args = parser.parse_args() 78 | run(args) 79 | 80 | if __name__ == "__main__": 81 | main() 82 | ``` 83 | 84 | The `Cli2Gui` decorator wraps around the `main` function and adds support for both CLI and GUI. 85 | You can now run this program in two ways: 86 | 87 | - **CLI Mode**: `python3 main.py` 88 | - **GUI Mode**: run `python3 main.py --cli2gui` and the GUI will appear, allowing you to enter 89 | the arguments interactively. 90 | 91 | ## Advanced Example with Argument Groups 92 | 93 | Let’s expand on the previous example with a more complex argument structure, including mutually 94 | exclusive groups, optional arguments, and file inputs. We will also demonstrate how to add a 95 | menu to the GUI. 96 | 97 | ```python 98 | from cli2gui import Cli2Gui 99 | import argparse 100 | from pathlib import Path 101 | 102 | # Define the function that will handle the program's logic 103 | def handle(args): 104 | print(args) 105 | 106 | # Define the CLI function with advanced arguments 107 | @Cli2Gui( 108 | run_function=handle, 109 | gui="freesimplegui", 110 | menu={ 111 | "File": "path/to/file.md", 112 | "Another File": "path/to/another_file.md", 113 | } 114 | ) 115 | def cli(): 116 | parser = argparse.ArgumentParser(description="Advanced CLI with GUI support") 117 | 118 | # Positional arguments 119 | parser.add_argument("positional", help="Positional argument") 120 | parser.add_argument("positional_file", type=argparse.FileType("r"), help="Positional file input") 121 | 122 | # Optional arguments 123 | parser.add_argument("--optional", help="Optional argument") 124 | parser.add_argument("--store-true", action="store_true", help="Store true") 125 | parser.add_argument("--store-false", action="store_false", help="Store false") 126 | parser.add_argument("--store", help="Store value") 127 | parser.add_argument("--count", action="count", help="Count occurrences") 128 | parser.add_argument("--choices", choices=["choice1", "choice2"], help="Pick a choice") 129 | parser.add_argument("--somefile", type=argparse.FileType("r"), help="Optional file input") 130 | 131 | # Mutually exclusive group 132 | group = parser.add_argument_group("Image options") 133 | mxg = group.add_mutually_exclusive_group() 134 | mxg.add_argument("--mxg-true", action="store_true", help="Mutually exclusive store true") 135 | mxg.add_argument("--mxg-false", action="store_false", help="Mutually exclusive store false") 136 | mxg.add_argument("--mxg", help="Mutually exclusive store") 137 | mxg.add_argument("--mxg-count", action="count", help="Mutually exclusive count") 138 | mxg.add_argument("--mxg-choices", choices=["choice1", "choice2"], help="Mutually exclusive choice") 139 | 140 | args = parser.parse_args() 141 | handle(args) 142 | 143 | if __name__ == "__main__": 144 | cli() 145 | ``` 146 | 147 | In this example: 148 | 149 | - **run_function**: The function to be executed when the user clicks the "Run" button in the GUI. This is the `handle()` function 150 | - **gui**: Specify the GUI framework to use. Supported options are: `"dearpygui"`, `"pysimplegui"`, `"pysimpleguiqt"`, `"pysimpleguiweb"`, `"freesimplegui"`. Defaults to `"dearpygui"`. In the example, this is `"freesimplegui"`. 151 | - **menu**: Add a custom menu to the GUI. Example: `{"File": "/path/to/file.md"}`. 152 | 153 | ## Customizing the GUI 154 | 155 | You can customize various aspects of the GUI, including the theme, icon, and program name. Here’s 156 | how you can apply some customization options: 157 | 158 | ```python 159 | @Cli2Gui( 160 | run_function=handle, 161 | gui="freesimplegui", 162 | theme="one-light.yaml", 163 | darkTheme="one-dark.yaml", 164 | image="path/to/icon.png", 165 | program_name="My Custom Program", 166 | program_description="This is a custom description" 167 | ) 168 | def cli(): 169 | # Argument parsing logic here 170 | pass 171 | ``` 172 | 173 | In this example: 174 | 175 | - **run_function**: The function to be executed when the user clicks the "Run" button in the GUI. This is the `handle()` function 176 | - **gui**: Specify the GUI framework to use. Supported options are: `"dearpygui"`, `"pysimplegui"`, `"pysimpleguiqt"`, `"pysimpleguiweb"`, `"freesimplegui"`. Defaults to `"dearpygui"`. In the example, this is `"freesimplegui"`. 177 | - **theme**: Set a base24 theme or provide a base24 scheme file (e.g., `"one-light.yaml"`). 178 | - **darkTheme**: Specify a dark theme variant using a base24 scheme or file (e.g., `"one-dark.yaml"`). 179 | - **image**: Define the program icon. Supported formats are those compatible with the Python Imaging Library (PIL). 180 | - **program_name**: Override the default program name with a custom name. 181 | - **program_description**: Provide a custom description for the program. 182 | - **menu**: Add a custom menu to the GUI. Example: `{"File": "/path/to/file.md"}`. 183 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "cli2gui" 3 | version = "2025" 4 | description = "Use this module to convert a cli program to a gui" 5 | authors = [{ name = "FredHappyface" }] 6 | requires-python = ">=3.9" 7 | readme = "README.md" 8 | license = "mit" 9 | classifiers = [ 10 | "Environment :: Console", 11 | "Environment :: MacOS X", 12 | "Environment :: Web Environment", 13 | "Environment :: Win32 (MS Windows)", 14 | "Environment :: X11 Applications :: Qt", 15 | "Development Status :: 5 - Production/Stable", 16 | "Intended Audience :: Developers", 17 | "Intended Audience :: Education", 18 | "Natural Language :: English", 19 | "Operating System :: OS Independent", 20 | "Programming Language :: Python :: Implementation :: CPython", 21 | "Topic :: Software Development :: Libraries :: Python Modules", 22 | "Topic :: Utilities", 23 | ] 24 | dependencies = [ 25 | "dearpygui>=2.0.0", 26 | "getostheme>=2024.0.1", 27 | "pillow>=11.1.0", 28 | "pyyaml>=6.0.2", 29 | ] 30 | 31 | [project.optional-dependencies] 32 | psg = ["pysimplegui<6,>=5.0.3"] 33 | fsg = ["freesimplegui<6,>=5.1.1"] 34 | # fsgweb = ["freesimpleguiWeb>=1.0.0"] 35 | # fsgqt = ["freesimpleguiQt>=1.0.0"] 36 | web = ["PySimpleGUIWeb<2,>=0.39.0"] 37 | qt = ["PySimpleGUIQt<6,>=5.0.0"] 38 | pandoc = ["catpandoc<2026,>=2024"] 39 | 40 | [project.urls] 41 | Homepage = "https://github.com/FHPythonUtils/Cli2Gui" 42 | Repository = "https://github.com/FHPythonUtils/Cli2Gui" 43 | Documentation = "https://github.com/FHPythonUtils/Cli2Gui/blob/master/README.md" 44 | 45 | [dependency-groups] 46 | dev = [ 47 | "click>=8.1.8", 48 | "coverage>=7.6.12", 49 | "dephell-argparse>=0.1.3", 50 | "docopt>=0.6.2", 51 | "handsdown>=2.1.0", 52 | "pyright>=1.1.394", 53 | "pytest>=8.3.4", 54 | "ruff>=0.9.6", 55 | "safety>=3.3.0", 56 | ] 57 | 58 | [build-system] 59 | requires = ["hatchling"] 60 | build-backend = "hatchling.build" 61 | 62 | [tool.ruff] 63 | line-length = 100 64 | indent-width = 4 65 | target-version = "py38" 66 | 67 | [tool.ruff.lint] 68 | select = ["ALL"] 69 | ignore = [ 70 | "ANN401", # allow dynamically typed expressions (typing.Any) 71 | "COM812", # enforce trailing comma 72 | "D2", # pydocstyle formatting 73 | "ISC001", 74 | "N802", "N803", "N806", "N812", "N813", "N815", # pep8 naming 75 | "PLR09", # pylint refactor too many 76 | "TCH", # type check blocks 77 | "W191" # ignore this to allow tabs 78 | ] 79 | fixable = ["ALL"] 80 | 81 | [tool.ruff.lint.per-file-ignores] 82 | "**/{tests,docs,tools}/*" = ["D", "S101", "E402", "T201", "ERA001"] 83 | 84 | [tool.ruff.lint.flake8-tidy-imports] 85 | ban-relative-imports = "all" # Disallow all relative imports. 86 | 87 | [tool.ruff.format] 88 | indent-style = "tab" 89 | docstring-code-format = true 90 | line-ending = "lf" 91 | 92 | [tool.pyright] 93 | venvPath = "." 94 | venv = ".venv" 95 | -------------------------------------------------------------------------------- /readme-assets/icons/name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/readme-assets/icons/name.png -------------------------------------------------------------------------------- /readme-assets/icons/proj-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/readme-assets/icons/proj-icon.png -------------------------------------------------------------------------------- /readme-assets/screenshots/dearpygui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/readme-assets/screenshots/dearpygui.png -------------------------------------------------------------------------------- /readme-assets/screenshots/freesimplegui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/readme-assets/screenshots/freesimplegui.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv export --no-hashes --no-dev -o requirements.txt 3 | -e . 4 | dearpygui==2.0.0 5 | getostheme==2024.0.1 6 | pillow==11.1.0 7 | pyyaml==6.0.2 8 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/tests/__init__.py -------------------------------------------------------------------------------- /tests/argparse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/tests/argparse/__init__.py -------------------------------------------------------------------------------- /tests/argparse/another_file.md: -------------------------------------------------------------------------------- 1 | ## Decorator 2 | 3 | ```python 4 | @Cli2Gui(run_function, auto_enable=False, parser="argparse", gui="pysimplegui", 5 | theme="", darkTheme="", image="", program_name="", 6 | program_description="", max_args_shown=5, **kwargs) 7 | ``` 8 | 9 | ## Using the decorator in your project 10 | 11 | ### run_function (optional) 12 | 13 | The name of the function to call eg. main(args). Defaults to None. If not 14 | specified, program continues as normal (can only run once) 15 | 16 | ```python 17 | def main(args): 18 | print(args.arg) 19 | 20 | @Cli2Gui(run_function=main) 21 | def cli(): 22 | parser = argparse.ArgumentParser(description="this is an example parser") 23 | parser.add_argument("arg", type=str, 24 | help="positional arg") 25 | args = parser.parse_args() 26 | main(args) 27 | ``` 28 | 29 | ### auto_enable (optional) 30 | 31 | Enable the GUI by default. If enabled by default requires `--disable-cli2gui`, otherwise requires `--cli2gui` 32 | 33 | ```python 34 | @Cli2Gui(auto_enable=False) 35 | ``` 36 | 37 | ### parser (optional) 38 | 39 | Override the parser to use, defaults to argparse. Current options are: 40 | "argparse", "getopt", "optparse", "docopt", "dephell_argparse" 41 | 42 | ```python 43 | @Cli2Gui(parser="argparse") 44 | ``` 45 | 46 | ### gui (optional) 47 | 48 | Override the gui to use. Current options are: 49 | "pysimplegui", "pysimpleguiqt","pysimpleguiweb". Defaults to "pysimplegui". 50 | 51 | pysimplegui is the recommended option 52 | 53 | ```python 54 | @Cli2Gui(gui="pysimplegui") 55 | ``` 56 | 57 | ### theme (optional) 58 | 59 | Set a base24 theme. Can also pass a base24 scheme file. eg. `one-light.yaml` 60 | 61 | ```python 62 | @Cli2Gui(theme=["#e7e7e9", "#dfdfe1", "#cacace", "#a0a1a7", "#696c77", 63 | "#383a42", "#202227", "#090a0b", "#ca1243", "#c18401", "#febb2a", 64 | "#50a14f", "#0184bc", "#4078f2", "#a626a4", "#986801", "#f0f0f1", 65 | "#fafafa", "#ec2258", "#f4a701", "#6db76c", "#01a7ef", "#709af5", 66 | "#d02fcd"]) 67 | ``` 68 | 69 | ### darkTheme (optional) 70 | 71 | Set a base24 dark theme variant. Can also pass a base24 scheme file. eg. 72 | `one-dark.yaml` 73 | 74 | ```python 75 | @Cli2Gui(darkTheme=["#282c34", "#3f4451", "#4f5666", "#545862", "#9196a1", 76 | "#abb2bf", "#e6e6e6", "#ffffff", "#e06c75", "#d19a66", "#e5c07b", 77 | "#98c379", "#56b6c2", "#61afef", "#c678dd", "#be5046", "#21252b", 78 | "#181a1f", "#ff7b86", "#efb074", "#b1e18b", "#63d4e0", "#67cdff", 79 | "#e48bff"]) 80 | ``` 81 | 82 | ### image (optional) 83 | 84 | Set the program icon. File extensions can be any that PIL supports 85 | 86 | ```python 87 | @Cli2Gui(image="path/to/image.png") 88 | ``` 89 | 90 | ### program_name (optional) 91 | 92 | Override the program name 93 | 94 | ```python 95 | @Cli2Gui(program_name="custom name") 96 | ``` 97 | 98 | ### program_description (optional) 99 | 100 | Override the program description 101 | 102 | ```python 103 | @Cli2Gui(program_description="this is a custom description") 104 | ``` 105 | 106 | ### max_args_shown (optional) 107 | 108 | Maximum number of args shown before using a scrollbar 109 | 110 | ```python 111 | @Cli2Gui(max_args_shown=5) 112 | ``` 113 | 114 | ### menu (optional) 115 | 116 | Add a menu to the program. Defaults to None. eg. 117 | 118 | ```python 119 | THIS_DIR = str(Path(__file__).resolve().parent) 120 | menu={"File": THIS_DIR + "/file.md"} 121 | ``` 122 | 123 | Works significantly better with pysimplegui than pysimpleguiqt 124 | 125 | ```python 126 | @Cli2Gui(menu={"File": THIS_DIR + "/file.md", "Another File": THIS_DIR + "/another_file.md", }) 127 | ``` 128 | 129 | ## Click 130 | 131 | ```python 132 | def Click2Gui(run_function, gui="pysimplegui", theme="", darkTheme="", 133 | image="", program_name="", program_description="", 134 | max_args_shown=5, menu="", **kwargs): 135 | ``` 136 | 137 | Very similar to the decorator but with the following differences... 138 | 139 | ### run_function (required) 140 | 141 | Specify the click function to use. (attempts were made to offer full program 142 | support however this behaved very poorly) 143 | 144 | ### parser (not applicable) 145 | 146 | As this is exclusively for click, this option is not present 147 | -------------------------------------------------------------------------------- /tests/argparse/file.md: -------------------------------------------------------------------------------- 1 | # Example file 1 2 | 3 | Did you know that cli2gui supports a range of parsers, here's a comparison of support vs 4 | some other popular projects 5 | 6 | | Parser | Cli2Gui | Gooey | Quick | 7 | | ---------------- | -------------------- | ------------------ | ------------------ | 8 | | Argparse | ✔ | ✔ | ❌ | 9 | | Optparse | ✔ | ❌ | ❌ | 10 | | DocOpt | ✔ | ❌ | ❌ | 11 | | Click | ✔ * | ❌ | ✔ | 12 | | GetOpt | ✔ | ❌ | ❌ | 13 | | Dephell Argparse | ✔ | ❌ | ❌ | 14 | -------------------------------------------------------------------------------- /tests/argparse/test_10.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | from pathlib import Path 4 | 5 | THISDIR = Path(__file__).resolve().parent 6 | sys.path.insert(0, str(THISDIR.parent.parent)) 7 | from cli2gui import Cli2Gui 8 | 9 | 10 | def run(args: argparse.Namespace) -> None: 11 | print(args) 12 | 13 | 14 | def main() -> None: 15 | parser = argparse.ArgumentParser(description="this is an example parser") 16 | subparsers = parser.add_subparsers(help="types of A") 17 | parser.add_argument( 18 | "-v", 19 | ) 20 | 21 | a_parser = subparsers.add_parser("A") 22 | _b_parser = subparsers.add_parser("B") 23 | 24 | a_parser.add_argument("something", choices=["a1", "a2"]) 25 | 26 | args = parser.parse_args() 27 | run(args) 28 | 29 | 30 | decorator_function = Cli2Gui( 31 | run_function=run, 32 | auto_enable=True, 33 | ) 34 | 35 | gui = decorator_function(main) 36 | 37 | if __name__ == "__main__": 38 | gui() 39 | -------------------------------------------------------------------------------- /tests/argparse/test_11.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | from pathlib import Path 4 | 5 | THISDIR = Path(__file__).resolve().parent 6 | sys.path.insert(0, str(THISDIR.parent.parent)) 7 | from cli2gui import Cli2Gui 8 | 9 | 10 | def run(args: argparse.Namespace) -> None: 11 | print(args.arg) 12 | 13 | 14 | def main() -> None: 15 | parser = argparse.ArgumentParser(description="this is an example parser") 16 | parser.add_argument("--arg", type=str, default="foo", help="keyword arg") 17 | parser.add_argument("--bool", action="store_true", default=True, help="boolean arg") 18 | args = parser.parse_args() 19 | run(args) 20 | 21 | 22 | decorator_function = Cli2Gui( 23 | run_function=run, 24 | auto_enable=True, 25 | ) 26 | 27 | gui = decorator_function(main) 28 | 29 | if __name__ == "__main__": 30 | gui() 31 | -------------------------------------------------------------------------------- /tests/argparse/test_16.py: -------------------------------------------------------------------------------- 1 | """Tests an advanced parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = Path(__file__).resolve().parent 10 | sys.path.insert(0, str(THISDIR.parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: argparse.Namespace) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | try: 18 | args.write_path.write_bytes(b"test") 19 | except AttributeError: 20 | print("Oops! write_path was never specified") 21 | 22 | 23 | @Cli2Gui( 24 | run_function=handle, 25 | menu={ 26 | "File": f"{THISDIR}/file.md", 27 | "Another File": f"{THISDIR}/another_file.md", 28 | }, 29 | ) 30 | def cli() -> None: 31 | """Cli entrypoint.""" 32 | parser = argparse.ArgumentParser("Simple Parser") 33 | 34 | # Positional and file 35 | parser.add_argument("positional", help="positional arg") 36 | parser.add_argument( 37 | "positional_file", type=argparse.FileType("r"), help="positional arg for a file" 38 | ) 39 | parser.add_argument( 40 | "write_file", type=argparse.FileType("wb"), help="positional arg for a file" 41 | ) 42 | 43 | parser.add_argument("write_path", type=Path, help="positional arg for a file") 44 | parser.add_argument( 45 | "write_path_default", 46 | type=Path, 47 | help="positional arg for a file", 48 | default=Path("example.specialext"), 49 | ) 50 | 51 | args = parser.parse_args() 52 | 53 | handle(args) 54 | 55 | 56 | cli() 57 | -------------------------------------------------------------------------------- /tests/argparse/test_18.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from threading import Thread 3 | 4 | from cli2gui import Cli2Gui 5 | 6 | THREAD: Thread = None 7 | import argparse 8 | from pathlib import Path 9 | 10 | 11 | def main() -> None: 12 | parser = argparse.ArgumentParser(description="Web scraper with aria2c output") 13 | parser.add_argument( 14 | "urls_file", 15 | help="Input file containing URLs", 16 | type=argparse.FileType("r", encoding="utf-8"), 17 | ) 18 | parser.add_argument( 19 | "aria2c_file", 20 | help="Output file for aria2c download links", 21 | type=Path, 22 | ) 23 | parser.add_argument("--timeout", type=int, default=5000, help="Timeout per page (ms)") 24 | parser.add_argument("--max-workers", type=int, default=2, help="Maximum number of workers") 25 | parser.add_argument( 26 | "--save-trace", 27 | action="store_true", 28 | help="Save trace files (for debugging only)", 29 | ) 30 | parser.add_argument( 31 | "--skip-edge", 32 | action="store_true", 33 | help="Don't use Edge", 34 | ) 35 | args = parser.parse_args() 36 | blocking_run(args) 37 | 38 | 39 | def blocking_run(args: argparse.Namespace) -> None: 40 | asyncio.run(run(args)) 41 | 42 | 43 | async def run(args: argparse.Namespace) -> None: 44 | print(args) 45 | print(args.timeout) 46 | 47 | 48 | def wrapper(args: argparse.Namespace) -> None: 49 | global THREAD 50 | 51 | if THREAD is not None and THREAD.is_alive(): 52 | print("Task already running") 53 | return 54 | 55 | THREAD = Thread(target=blocking_run, args=(args,)) 56 | THREAD.start() 57 | 58 | 59 | decorator_function = Cli2Gui( 60 | run_function=wrapper, 61 | auto_enable=True, 62 | program_name="Test program #18", 63 | ) 64 | 65 | 66 | gui = decorator_function(main) 67 | 68 | if __name__ == "__main__": 69 | gui() 70 | -------------------------------------------------------------------------------- /tests/argparse/test_22.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from threading import Thread 3 | 4 | from cli2gui import Cli2Gui 5 | 6 | THREAD: Thread = None 7 | import argparse 8 | from pathlib import Path 9 | 10 | 11 | def main() -> None: 12 | parser = argparse.ArgumentParser(description="Web scraper with aria2c output") 13 | parser.add_argument( 14 | "urls_file", 15 | help="Input file containing URLs", 16 | type=argparse.FileType("r", encoding="utf-8"), 17 | ) 18 | parser.add_argument( 19 | "aria2c_file", 20 | help="Output file for aria2c download links", 21 | type=Path, 22 | ) 23 | parser.add_argument("--timeout", type=int, default=5000, help="Timeout per page (ms)") 24 | parser.add_argument("--max-workers", type=int, default=2, help="Maximum number of workers") 25 | parser.add_argument( 26 | "--save-trace", 27 | action="store_true", 28 | help="Save trace files (for debugging only)", 29 | ) 30 | parser.add_argument( 31 | "--skip-edge", 32 | action="store_true", 33 | help="Don't use Edge", 34 | ) 35 | args = parser.parse_args() 36 | blocking_run(args) 37 | 38 | 39 | def blocking_run(args: argparse.Namespace) -> None: 40 | asyncio.run(run(args)) 41 | 42 | 43 | async def run(args: argparse.Namespace) -> None: 44 | print(args) 45 | print(args.timeout) 46 | 47 | 48 | def wrapper(args: argparse.Namespace) -> None: 49 | global THREAD 50 | 51 | if THREAD is not None and THREAD.is_alive(): 52 | print("Task already running") 53 | return 54 | 55 | THREAD = Thread(target=blocking_run, args=(args,)) 56 | THREAD.start() 57 | 58 | 59 | decorator_function = Cli2Gui( 60 | run_function=wrapper, 61 | auto_enable=True, 62 | program_name="Test program #22", 63 | gui="freesimplegui", 64 | ) 65 | 66 | 67 | gui = decorator_function(main) 68 | 69 | if __name__ == "__main__": 70 | gui() 71 | -------------------------------------------------------------------------------- /tests/argparse/test_24.py: -------------------------------------------------------------------------------- 1 | """Tests an advanced parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = Path(__file__).resolve().parent 10 | sys.path.insert(0, str(THISDIR.parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: argparse.Namespace) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | try: 18 | args.write_path.write_bytes(b"test") 19 | except AttributeError: 20 | print("Oops! write_path was never specified") 21 | 22 | 23 | @Cli2Gui( 24 | run_function=handle, 25 | menu={ 26 | "File": f"{THISDIR}/file.md", 27 | "Another File": f"{THISDIR}/another_file.md", 28 | }, 29 | gui="freesimplegui", 30 | ) 31 | def cli() -> None: 32 | """Cli entrypoint.""" 33 | parser = argparse.ArgumentParser("Simple Parser") 34 | 35 | # Positional and file 36 | parser.add_argument("positional", help="positional arg") 37 | parser.add_argument( 38 | "positional_file", type=argparse.FileType("r"), help="positional arg for a file" 39 | ) 40 | parser.add_argument( 41 | "write_file", type=argparse.FileType("wb"), help="positional arg for a file" 42 | ) 43 | 44 | parser.add_argument("write_path", type=Path, help="positional arg for a file") 45 | 46 | args = parser.parse_args() 47 | 48 | handle(args) 49 | 50 | 51 | cli() 52 | -------------------------------------------------------------------------------- /tests/argparse/test_advanced.py: -------------------------------------------------------------------------------- 1 | """Tests an advanced parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = Path(__file__).resolve().parent 10 | sys.path.insert(0, str(THISDIR.parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: argparse.Namespace) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | 18 | 19 | @Cli2Gui( 20 | run_function=handle, 21 | menu={ 22 | "File": f"{THISDIR}/file.md", 23 | "Another File": f"{THISDIR}/another_file.md", 24 | }, 25 | ) 26 | def cli() -> None: 27 | """Cli entrypoint.""" 28 | parser = argparse.ArgumentParser("Simple Parser") 29 | 30 | # Positional and file 31 | parser.add_argument("positional", help="positional arg") 32 | parser.add_argument( 33 | "positional-file", type=argparse.FileType("r"), help="positional arg for a file" 34 | ) 35 | parser.add_argument("--optional", help="optional arg") 36 | 37 | # Store true, false, store, count, choices 38 | parser.add_argument("--store-true", action="store_true", help="optional arg store true") 39 | parser.add_argument("--store-false", action="store_false", help="optional arg store false") 40 | parser.add_argument("--store", action="store", help="optional arg store") 41 | parser.add_argument("--count", action="count", help="optional arg count") 42 | parser.add_argument( 43 | "--choices", 44 | action="store", 45 | choices=["choice1", "choice2"], 46 | help="optional arg store with choices", 47 | ) 48 | parser.add_argument( 49 | "--somefile", 50 | type=argparse.FileType("r"), 51 | required=False, 52 | ) 53 | 54 | group = parser.add_argument_group( 55 | "choose one of the following", "use the following arguments to change the look of the image" 56 | ) 57 | mxg = group.add_mutually_exclusive_group() 58 | 59 | mxg.add_argument("--mxg-true", action="store_true", help="mutually exclusive arg store true") 60 | mxg.add_argument("--mxg-false", action="store_false", help="mutually exclusive arg store false") 61 | mxg.add_argument("--mxg", action="store", help="mutually exclusive arg store") 62 | mxg.add_argument("--mxg-count", action="count", help="mutually exclusive arg count") 63 | mxg.add_argument( 64 | "--mxg-choices", 65 | action="store", 66 | choices=["choice1", "choice2"], 67 | help="mutually exclusive arg store with choices", 68 | ) 69 | 70 | args = parser.parse_args() 71 | 72 | handle(args) 73 | 74 | 75 | cli() 76 | -------------------------------------------------------------------------------- /tests/argparse/test_advanced_fsg.py: -------------------------------------------------------------------------------- 1 | """Tests an advanced parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = Path(__file__).resolve().parent 10 | sys.path.insert(0, str(THISDIR.parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: argparse.Namespace) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | 18 | 19 | @Cli2Gui( 20 | run_function=handle, 21 | menu={ 22 | "File": f"{THISDIR}/file.md", 23 | "Another File": f"{THISDIR}/another_file.md", 24 | }, 25 | gui="freesimplegui", 26 | ) 27 | def cli() -> None: 28 | """Cli entrypoint.""" 29 | parser = argparse.ArgumentParser("Simple Parser") 30 | 31 | # Positional and file 32 | parser.add_argument("positional", help="positional arg") 33 | parser.add_argument( 34 | "positional-file", type=argparse.FileType("r"), help="positional arg for a file" 35 | ) 36 | parser.add_argument("--optional", help="optional arg") 37 | 38 | # Store true, false, store, count, choices 39 | parser.add_argument("--store-true", action="store_true", help="optional arg store true") 40 | parser.add_argument("--store-false", action="store_false", help="optional arg store false") 41 | parser.add_argument("--store", action="store", help="optional arg store") 42 | parser.add_argument("--count", action="count", help="optional arg count") 43 | parser.add_argument( 44 | "--choices", 45 | action="store", 46 | choices=["choice1", "choice2"], 47 | help="optional arg store with choices", 48 | ) 49 | parser.add_argument( 50 | "--somefile", 51 | type=argparse.FileType("r"), 52 | required=False, 53 | ) 54 | 55 | group = parser.add_argument_group( 56 | "choose one of the following", "use the following arguments to change the look of the image" 57 | ) 58 | mxg = group.add_mutually_exclusive_group() 59 | 60 | mxg.add_argument("--mxg-true", action="store_true", help="mutually exclusive arg store true") 61 | mxg.add_argument("--mxg-false", action="store_false", help="mutually exclusive arg store false") 62 | mxg.add_argument("--mxg", action="store", help="mutually exclusive arg store") 63 | mxg.add_argument("--mxg-count", action="count", help="mutually exclusive arg count") 64 | mxg.add_argument( 65 | "--mxg-choices", 66 | action="store", 67 | choices=["choice1", "choice2"], 68 | help="mutually exclusive arg store with choices", 69 | ) 70 | 71 | args = parser.parse_args() 72 | 73 | handle(args) 74 | 75 | 76 | cli() 77 | -------------------------------------------------------------------------------- /tests/argparse/test_advanced_fsgqt.py: -------------------------------------------------------------------------------- 1 | """Tests an advanced parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = Path(__file__).resolve().parent 10 | sys.path.insert(0, str(THISDIR.parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: argparse.Namespace) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | 18 | 19 | @Cli2Gui( 20 | run_function=handle, 21 | menu={ 22 | "File": f"{THISDIR}/file.md", 23 | "Another File": f"{THISDIR}/another_file.md", 24 | }, 25 | gui="fsgqt", 26 | ) 27 | def cli() -> None: 28 | """Cli entrypoint.""" 29 | parser = argparse.ArgumentParser("Simple Parser") 30 | 31 | # Positional and file 32 | parser.add_argument("positional", help="positional arg") 33 | parser.add_argument( 34 | "positional-file", type=argparse.FileType("r"), help="positional arg for a file" 35 | ) 36 | parser.add_argument("--optional", help="optional arg") 37 | 38 | # Store true, false, store, count, choices 39 | parser.add_argument("--store-true", action="store_true", help="optional arg store true") 40 | parser.add_argument("--store-false", action="store_false", help="optional arg store false") 41 | parser.add_argument("--store", action="store", help="optional arg store") 42 | parser.add_argument("--count", action="count", help="optional arg count") 43 | parser.add_argument( 44 | "--choices", 45 | action="store", 46 | choices=["choice1", "choice2"], 47 | help="optional arg store with choices", 48 | ) 49 | parser.add_argument( 50 | "--somefile", 51 | type=argparse.FileType("r"), 52 | required=False, 53 | ) 54 | 55 | group = parser.add_argument_group( 56 | "choose one of the following", "use the following arguments to change the look of the image" 57 | ) 58 | mxg = group.add_mutually_exclusive_group() 59 | 60 | mxg.add_argument("--mxg-true", action="store_true", help="mutually exclusive arg store true") 61 | mxg.add_argument("--mxg-false", action="store_false", help="mutually exclusive arg store false") 62 | mxg.add_argument("--mxg", action="store", help="mutually exclusive arg store") 63 | mxg.add_argument("--mxg-count", action="count", help="mutually exclusive arg count") 64 | mxg.add_argument( 65 | "--mxg-choices", 66 | action="store", 67 | choices=["choice1", "choice2"], 68 | help="mutually exclusive arg store with choices", 69 | ) 70 | 71 | args = parser.parse_args() 72 | 73 | handle(args) 74 | 75 | 76 | cli() 77 | -------------------------------------------------------------------------------- /tests/argparse/test_advanced_fsgweb.py: -------------------------------------------------------------------------------- 1 | """Tests an advanced parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = Path(__file__).resolve().parent 10 | sys.path.insert(0, str(THISDIR.parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: argparse.Namespace) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | 18 | 19 | @Cli2Gui( 20 | run_function=handle, 21 | menu={ 22 | "File": f"{THISDIR}/file.md", 23 | "Another File": f"{THISDIR}/another_file.md", 24 | }, 25 | gui="freesimpleguiweb", 26 | ) 27 | def cli() -> None: 28 | """Cli entrypoint.""" 29 | parser = argparse.ArgumentParser("Simple Parser") 30 | 31 | # Positional and file 32 | parser.add_argument("positional", help="positional arg") 33 | parser.add_argument( 34 | "positional-file", type=argparse.FileType("r"), help="positional arg for a file" 35 | ) 36 | parser.add_argument("--optional", help="optional arg") 37 | 38 | # Store true, false, store, count, choices 39 | parser.add_argument("--store-true", action="store_true", help="optional arg store true") 40 | parser.add_argument("--store-false", action="store_false", help="optional arg store false") 41 | parser.add_argument("--store", action="store", help="optional arg store") 42 | parser.add_argument("--count", action="count", help="optional arg count") 43 | parser.add_argument( 44 | "--choices", 45 | action="store", 46 | choices=["choice1", "choice2"], 47 | help="optional arg store with choices", 48 | ) 49 | parser.add_argument( 50 | "--somefile", 51 | type=argparse.FileType("r"), 52 | required=False, 53 | ) 54 | 55 | group = parser.add_argument_group( 56 | "choose one of the following", "use the following arguments to change the look of the image" 57 | ) 58 | mxg = group.add_mutually_exclusive_group() 59 | 60 | mxg.add_argument("--mxg-true", action="store_true", help="mutually exclusive arg store true") 61 | mxg.add_argument("--mxg-false", action="store_false", help="mutually exclusive arg store false") 62 | mxg.add_argument("--mxg", action="store", help="mutually exclusive arg store") 63 | mxg.add_argument("--mxg-count", action="count", help="mutually exclusive arg count") 64 | mxg.add_argument( 65 | "--mxg-choices", 66 | action="store", 67 | choices=["choice1", "choice2"], 68 | help="mutually exclusive arg store with choices", 69 | ) 70 | 71 | args = parser.parse_args() 72 | 73 | handle(args) 74 | 75 | 76 | cli() 77 | -------------------------------------------------------------------------------- /tests/argparse/test_advanced_psg.py: -------------------------------------------------------------------------------- 1 | """Tests an advanced parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = Path(__file__).resolve().parent 10 | sys.path.insert(0, str(THISDIR.parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: argparse.Namespace) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | 18 | 19 | @Cli2Gui( 20 | run_function=handle, 21 | menu={ 22 | "File": f"{THISDIR}/file.md", 23 | "Another File": f"{THISDIR}/another_file.md", 24 | }, 25 | gui="pysimplegui", 26 | ) 27 | def cli() -> None: 28 | """Cli entrypoint.""" 29 | parser = argparse.ArgumentParser("Simple Parser") 30 | 31 | # Positional and file 32 | parser.add_argument("positional", help="positional arg") 33 | parser.add_argument( 34 | "positional-file", type=argparse.FileType("r"), help="positional arg for a file" 35 | ) 36 | parser.add_argument("--optional", help="optional arg") 37 | 38 | # Store true, false, store, count, choices 39 | parser.add_argument("--store-true", action="store_true", help="optional arg store true") 40 | parser.add_argument("--store-false", action="store_false", help="optional arg store false") 41 | parser.add_argument("--store", action="store", help="optional arg store") 42 | parser.add_argument("--count", action="count", help="optional arg count") 43 | parser.add_argument( 44 | "--choices", 45 | action="store", 46 | choices=["choice1", "choice2"], 47 | help="optional arg store with choices", 48 | ) 49 | parser.add_argument( 50 | "--somefile", 51 | type=argparse.FileType("r"), 52 | required=False, 53 | ) 54 | 55 | group = parser.add_argument_group( 56 | "choose one of the following", "use the following arguments to change the look of the image" 57 | ) 58 | mxg = group.add_mutually_exclusive_group() 59 | 60 | mxg.add_argument("--mxg-true", action="store_true", help="mutually exclusive arg store true") 61 | mxg.add_argument("--mxg-false", action="store_false", help="mutually exclusive arg store false") 62 | mxg.add_argument("--mxg", action="store", help="mutually exclusive arg store") 63 | mxg.add_argument("--mxg-count", action="count", help="mutually exclusive arg count") 64 | mxg.add_argument( 65 | "--mxg-choices", 66 | action="store", 67 | choices=["choice1", "choice2"], 68 | help="mutually exclusive arg store with choices", 69 | ) 70 | 71 | args = parser.parse_args() 72 | 73 | handle(args) 74 | 75 | 76 | cli() 77 | -------------------------------------------------------------------------------- /tests/argparse/test_simple.py: -------------------------------------------------------------------------------- 1 | """Tests a simple parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = str(Path(__file__).resolve().parent) 10 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: argparse.Namespace) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | 18 | 19 | @Cli2Gui(run_function=handle) 20 | def cli() -> None: 21 | """Cli entrypoint.""" 22 | parser = argparse.ArgumentParser("Simple Parser") 23 | 24 | # Positional and file 25 | parser.add_argument("positional", help="positional arg") 26 | parser.add_argument( 27 | "positional-file", type=argparse.FileType("r"), help="positional arg for a file" 28 | ) 29 | parser.add_argument("--optional", help="optional arg") 30 | 31 | # Store true, false, store, count, choices 32 | parser.add_argument("--store-true", action="store_true", help="optional arg store true") 33 | parser.add_argument("--store-false", action="store_false", help="optional arg store false") 34 | parser.add_argument("--store", action="store", help="optional arg store") 35 | parser.add_argument("--count", action="count", help="optional arg count") 36 | parser.add_argument( 37 | "--choices", 38 | action="store", 39 | choices=["choice1", "choice2"], 40 | help="optional arg store with choices", 41 | ) 42 | 43 | args = parser.parse_args() 44 | 45 | handle(args) 46 | 47 | 48 | cli() 49 | -------------------------------------------------------------------------------- /tests/click/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/tests/click/__init__.py -------------------------------------------------------------------------------- /tests/click/test_boolean.py: -------------------------------------------------------------------------------- 1 | """Tests a simple parser 2 | 3 | Program from https://click.palletsprojects.com/en/7.x/#documentation 4 | """ 5 | 6 | from __future__ import annotations 7 | 8 | import sys 9 | from pathlib import Path 10 | 11 | import click 12 | 13 | THISDIR = str(Path(__file__).resolve().parent) 14 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 15 | from cli2gui import Click2Gui 16 | 17 | 18 | @click.command() 19 | @click.option("--print-version", default=True, help="Print the version?") 20 | def hello(*, print_version: bool) -> None: 21 | if print_version: 22 | print("v1.0") 23 | 24 | 25 | Click2Gui(run_function=hello) 26 | # hello() 27 | -------------------------------------------------------------------------------- /tests/click/test_choice.py: -------------------------------------------------------------------------------- 1 | """Program from https://click.palletsprojects.com/en/8.1.x/""" 2 | 3 | from __future__ import annotations 4 | 5 | import sys 6 | from pathlib import Path 7 | 8 | import click 9 | 10 | THISDIR = str(Path(__file__).resolve().parent) 11 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 12 | from cli2gui import Click2Gui 13 | 14 | 15 | @click.command() 16 | @click.option("--hash-type", type=click.Choice(["MD5", "SHA1"], case_sensitive=False)) 17 | def digest(hash_type: str) -> None: 18 | click.echo(hash_type) 19 | 20 | 21 | Click2Gui(run_function=digest) 22 | -------------------------------------------------------------------------------- /tests/click/test_feat_switches.py: -------------------------------------------------------------------------------- 1 | """Program from https://click.palletsprojects.com/en/8.1.x/""" 2 | 3 | from __future__ import annotations 4 | 5 | import sys 6 | from pathlib import Path 7 | 8 | import click 9 | 10 | THISDIR = str(Path(__file__).resolve().parent) 11 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 12 | import sys 13 | 14 | from cli2gui import Click2Gui 15 | 16 | 17 | @click.command() 18 | @click.option("--upper", "transformation", flag_value="upper", default=True) 19 | @click.option("--lower", "transformation", flag_value="lower") 20 | def info(transformation) -> None: 21 | click.echo(getattr(sys.platform, transformation)()) 22 | 23 | 24 | Click2Gui(run_function=info) 25 | -------------------------------------------------------------------------------- /tests/click/test_simple.py: -------------------------------------------------------------------------------- 1 | """Tests a simple parser 2 | 3 | Program from https://click.palletsprojects.com/en/7.x/#documentation 4 | """ 5 | 6 | from __future__ import annotations 7 | 8 | import sys 9 | from pathlib import Path 10 | 11 | import click 12 | 13 | THISDIR = str(Path(__file__).resolve().parent) 14 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 15 | from cli2gui import Click2Gui 16 | 17 | 18 | @click.command() 19 | @click.option("--count", default=1, help="Number of greetings.") 20 | @click.option("--name", prompt="Your name", help="The person to greet.") 21 | def hello(count: int, name: str) -> None: 22 | """Simple program that greets NAME for a total of COUNT times.""" 23 | for _index in range(count): 24 | click.echo(f"Hello {name}!") 25 | 26 | 27 | Click2Gui(run_function=hello) 28 | # ⬇️ This is how you call the function without a GUI 29 | # hello() 30 | -------------------------------------------------------------------------------- /tests/custom(argparse)/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/tests/custom(argparse)/__init__.py -------------------------------------------------------------------------------- /tests/custom(argparse)/test_advanced.py: -------------------------------------------------------------------------------- 1 | """Tests an advanced parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = str(Path(__file__).resolve().parent) 10 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: argparse.Namespace) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | 18 | 19 | @Cli2Gui(parser="input()", run_function=handle) 20 | def cli() -> None: 21 | """Cli entrypoint.""" 22 | parser = argparse.ArgumentParser("Simple Parser") 23 | 24 | # Positional and file 25 | parser.add_argument("positional", help="positional arg") 26 | parser.add_argument( 27 | "positional-file", type=argparse.FileType("r"), help="positional arg for a file" 28 | ) 29 | parser.add_argument("--optional", help="optional arg") 30 | 31 | # Store true, false, store, count, choices 32 | parser.add_argument("--store-true", action="store_true", help="optional arg store true") 33 | parser.add_argument("--store-false", action="store_false", help="optional arg store false") 34 | parser.add_argument("--store", action="store", help="optional arg store") 35 | parser.add_argument("--count", action="count", help="optional arg count") 36 | parser.add_argument( 37 | "--choices", 38 | action="store", 39 | choices=["choice1", "choice2"], 40 | help="optional arg store with choices", 41 | ) 42 | 43 | group = parser.add_argument_group( 44 | "choose one of the following", "use the following arguments to change the look of the image" 45 | ) 46 | mxg = group.add_mutually_exclusive_group() 47 | 48 | mxg.add_argument("--mxg-true", action="store_true", help="mutually exclusive arg store true") 49 | mxg.add_argument("--mxg-false", action="store_false", help="mutually exclusive arg store false") 50 | mxg.add_argument("--mxg", action="store", help="mutually exclusive arg store") 51 | mxg.add_argument("--mxg-count", action="count", help="mutually exclusive arg count") 52 | mxg.add_argument( 53 | "--mxg-choices", 54 | action="store", 55 | choices=["choice1", "choice2"], 56 | help="mutually exclusive arg store with choices", 57 | ) 58 | 59 | args = parser.parse_args() 60 | 61 | handle(args) 62 | 63 | 64 | cli() 65 | -------------------------------------------------------------------------------- /tests/custom(argparse)/test_simple.py: -------------------------------------------------------------------------------- 1 | """Tests a simple parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = str(Path(__file__).resolve().parent) 10 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: argparse.Namespace) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | 18 | 19 | @Cli2Gui(parser="input()", run_function=handle) 20 | def cli() -> None: 21 | """Cli entrypoint.""" 22 | parser = argparse.ArgumentParser("Simple Parser") 23 | 24 | # Positional and file 25 | parser.add_argument("positional", help="positional arg") 26 | parser.add_argument( 27 | "positional-file", type=argparse.FileType("r"), help="positional arg for a file" 28 | ) 29 | parser.add_argument("--optional", help="optional arg") 30 | 31 | # Store true, false, store, count, choices 32 | parser.add_argument("--store-true", action="store_true", help="optional arg store true") 33 | parser.add_argument("--store-false", action="store_false", help="optional arg store false") 34 | parser.add_argument("--store", action="store", help="optional arg store") 35 | parser.add_argument("--count", action="count", help="optional arg count") 36 | parser.add_argument( 37 | "--choices", 38 | action="store", 39 | choices=["choice1", "choice2"], 40 | help="optional arg store with choices", 41 | ) 42 | 43 | args = parser.parse_args() 44 | 45 | handle(args) 46 | 47 | 48 | cli() 49 | -------------------------------------------------------------------------------- /tests/dephell_argparse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/tests/dephell_argparse/__init__.py -------------------------------------------------------------------------------- /tests/dephell_argparse/test_advanced.py: -------------------------------------------------------------------------------- 1 | """Tests an advanced parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | import dephell_argparse 10 | 11 | THISDIR = str(Path(__file__).resolve().parent) 12 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 13 | from cli2gui import Cli2Gui 14 | 15 | 16 | def handle(args: argparse.Namespace) -> None: 17 | """Handle the args.""" 18 | print(args) 19 | 20 | 21 | @Cli2Gui(run_function=handle, parser="dephell_argparse") 22 | def cli() -> None: 23 | """Cli entrypoint.""" 24 | parser = dephell_argparse.Parser() 25 | 26 | # Positional and file 27 | parser.add_argument("positional", help="positional arg") 28 | parser.add_argument("--optional", help="optional arg") 29 | 30 | # Store true, false, store, count, choices 31 | parser.add_argument("--store-true", action="store_true", help="optional arg store true") 32 | parser.add_argument("--store-false", action="store_false", help="optional arg store false") 33 | parser.add_argument("--store", action="store", help="optional arg store") 34 | parser.add_argument("--count", action="count", help="optional arg count") 35 | parser.add_argument( 36 | "--choices", 37 | action="store", 38 | choices=["choice1", "choice2"], 39 | help="optional arg store with choices", 40 | ) 41 | 42 | group = parser.add_argument_group( 43 | "choose one of the following", "use the following arguments to change the look of the image" 44 | ) 45 | mxg = group.add_mutually_exclusive_group() 46 | 47 | mxg.add_argument("--mxg-true", action="store_true", help="mutually exclusive arg store true") 48 | mxg.add_argument("--mxg-false", action="store_false", help="mutually exclusive arg store false") 49 | mxg.add_argument("--mxg", action="store", help="mutually exclusive arg store") 50 | mxg.add_argument("--mxg-count", action="count", help="mutually exclusive arg count") 51 | mxg.add_argument( 52 | "--mxg-choices", 53 | action="store", 54 | choices=["choice1", "choice2"], 55 | help="mutually exclusive arg store with choices", 56 | ) 57 | 58 | args = parser.parse_args() 59 | 60 | handle(args) 61 | 62 | 63 | cli() 64 | -------------------------------------------------------------------------------- /tests/dephell_argparse/test_simple.py: -------------------------------------------------------------------------------- 1 | """Tests a simple parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import sys 7 | from pathlib import Path 8 | 9 | import dephell_argparse 10 | 11 | THISDIR = str(Path(__file__).resolve().parent) 12 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 13 | from cli2gui import Cli2Gui 14 | 15 | 16 | def handle(args: argparse.Namespace) -> None: 17 | """Handle the args.""" 18 | print(args) 19 | 20 | 21 | @Cli2Gui(run_function=handle, parser="dephell_argparse") 22 | def cli() -> None: 23 | """Cli entrypoint.""" 24 | parser = dephell_argparse.Parser() 25 | 26 | # Positional and file 27 | parser.add_argument("positional", help="positional arg") 28 | parser.add_argument("--optional", help="optional arg") 29 | 30 | # Store true, false, store, count, choices 31 | parser.add_argument("--store-true", action="store_true", help="optional arg store true") 32 | parser.add_argument("--store-false", action="store_false", help="optional arg store false") 33 | parser.add_argument("--store", action="store", help="optional arg store") 34 | parser.add_argument("--count", action="count", help="optional arg count") 35 | parser.add_argument( 36 | "--choices", 37 | action="store", 38 | choices=["choice1", "choice2"], 39 | help="optional arg store with choices", 40 | ) 41 | 42 | args = parser.parse_args() 43 | 44 | handle(args) 45 | 46 | 47 | cli() 48 | -------------------------------------------------------------------------------- /tests/docopt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/tests/docopt/__init__.py -------------------------------------------------------------------------------- /tests/docopt/test_11.py: -------------------------------------------------------------------------------- 1 | """ 2 | usage: 3 | simple.py [-h] [--optional OPTIONAL] [--store-true] [--store-false] 4 | [--store STORE] [--count] [--choices {choice1,choice2}] PATH 5 | 6 | Arguments: 7 | PATH positional arg 8 | 9 | Options: 10 | -h, --help show this help message and exit 11 | --optional OPTIONAL optional arg 12 | --store-true optional arg store true 13 | --store-false optional arg store false 14 | --store STORE optional arg store [default: store me] 15 | --count optional arg count 16 | --choices {choice1,choice2} 17 | optional arg store with choices 18 | 19 | """ 20 | 21 | from __future__ import annotations 22 | 23 | import sys 24 | from pathlib import Path 25 | from typing import Any 26 | 27 | import docopt 28 | 29 | THISDIR = str(Path(__file__).resolve().parent) 30 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 31 | from cli2gui import Cli2Gui 32 | 33 | 34 | def handle(args: dict[str, Any]) -> None: 35 | """Handle the args.""" 36 | print(type(args)) 37 | print(args) 38 | 39 | 40 | @Cli2Gui(run_function=handle, parser="docopt") 41 | def cli() -> None: 42 | """Cli entrypoint.""" 43 | args = docopt.docopt(__doc__) 44 | handle(args) 45 | 46 | 47 | cli() 48 | -------------------------------------------------------------------------------- /tests/docopt/test_simple.py: -------------------------------------------------------------------------------- 1 | """ 2 | usage: 3 | simple.py [-h] [--optional OPTIONAL] [--store-true] [--store-false] 4 | [--store STORE] [--count] [--choices {choice1,choice2}] PATH 5 | 6 | Arguments: 7 | PATH positional arg 8 | 9 | Options: 10 | -h, --help show this help message and exit 11 | --optional OPTIONAL optional arg 12 | --store-true optional arg store true 13 | --store-false optional arg store false 14 | --store STORE optional arg store 15 | --count optional arg count 16 | --choices {choice1,choice2} 17 | optional arg store with choices 18 | 19 | """ 20 | 21 | from __future__ import annotations 22 | 23 | import sys 24 | from pathlib import Path 25 | from typing import Any 26 | 27 | import docopt 28 | 29 | THISDIR = str(Path(__file__).resolve().parent) 30 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 31 | from cli2gui import Cli2Gui 32 | 33 | 34 | def handle(args: dict[str, Any]) -> None: 35 | """Handle the args.""" 36 | print(args) 37 | 38 | 39 | @Cli2Gui(run_function=handle, parser="docopt") 40 | def cli() -> None: 41 | """Cli entrypoint.""" 42 | args = docopt.docopt(__doc__) 43 | handle(args) 44 | 45 | 46 | cli() 47 | -------------------------------------------------------------------------------- /tests/getopt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/tests/getopt/__init__.py -------------------------------------------------------------------------------- /tests/getopt/test_simple.py: -------------------------------------------------------------------------------- 1 | """Tests a simple parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import getopt 6 | import sys 7 | from pathlib import Path 8 | 9 | THISDIR = str(Path(__file__).resolve().parent) 10 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 11 | from cli2gui import Cli2Gui 12 | 13 | 14 | def handle(args: tuple[list, list]) -> None: 15 | """Handle the args.""" 16 | print(args) 17 | 18 | 19 | @Cli2Gui(run_function=handle, parser="getopt") 20 | def cli() -> None: 21 | """Cli entrypoint.""" 22 | options = getopt.getopt( 23 | sys.argv[1:], 24 | "ts:c:o", 25 | [ 26 | "store-true", 27 | "store=", 28 | "count=", 29 | "choice=", 30 | ], 31 | ) 32 | handle(options) 33 | 34 | 35 | cli() 36 | -------------------------------------------------------------------------------- /tests/optparse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FHPythonUtils/Cli2Gui/3b0c3a8be83c8b9d0e0f40da8cb56d1fb1944183/tests/optparse/__init__.py -------------------------------------------------------------------------------- /tests/optparse/test_11.py: -------------------------------------------------------------------------------- 1 | """Tests a simple parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import optparse 6 | import sys 7 | from pathlib import Path 8 | from typing import Any 9 | 10 | THISDIR = str(Path(__file__).resolve().parent) 11 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 12 | from cli2gui import Cli2Gui 13 | 14 | 15 | def handle(args: tuple[Any, list[str]]) -> None: 16 | """Handle the args.""" 17 | print(type(args)) 18 | print(args) 19 | print(type(args[0])) 20 | print(args[0]) 21 | print(args[1]) 22 | 23 | 24 | @Cli2Gui(run_function=handle, parser="optparse") 25 | def cli() -> None: 26 | """Cli entrypoint.""" 27 | parser = optparse.OptionParser("Simple Parser") 28 | 29 | parser.add_option("--optional", help="optional arg") 30 | 31 | # Store true, false, store, count, choices 32 | parser.add_option( 33 | "--store-true", action="store_true", help="optional arg store true", default=True 34 | ) 35 | parser.add_option( 36 | "--store-false", action="store_false", help="optional arg store false", default=True 37 | ) 38 | parser.add_option("--store", action="store", help="optional arg store", default="store me") 39 | parser.add_option("--count", action="count", help="optional arg count", default=1) 40 | parser.add_option( 41 | "--choices", 42 | action="store", 43 | choices=["choice1", "choice2"], 44 | help="optional arg store with choices", 45 | ) 46 | 47 | args = parser.parse_args() 48 | 49 | handle(args) 50 | 51 | 52 | cli() 53 | -------------------------------------------------------------------------------- /tests/optparse/test_simple.py: -------------------------------------------------------------------------------- 1 | """Tests a simple parser""" 2 | 3 | from __future__ import annotations 4 | 5 | import optparse 6 | import sys 7 | from pathlib import Path 8 | from typing import Any 9 | 10 | THISDIR = str(Path(__file__).resolve().parent) 11 | sys.path.insert(0, str(Path(THISDIR).parent.parent)) 12 | from cli2gui import Cli2Gui 13 | 14 | 15 | def handle(args: tuple[Any, list[str]]) -> None: 16 | """Handle the args.""" 17 | print(args) 18 | 19 | 20 | @Cli2Gui(run_function=handle, parser="optparse") 21 | def cli() -> None: 22 | """Cli entrypoint.""" 23 | parser = optparse.OptionParser("Simple Parser") 24 | 25 | parser.add_option("--optional", help="optional arg") 26 | 27 | # Store true, false, store, count, choices 28 | parser.add_option("--store-true", action="store_true", help="optional arg store true") 29 | parser.add_option("--store-false", action="store_false", help="optional arg store false") 30 | parser.add_option("--store", action="store", help="optional arg store") 31 | parser.add_option("--count", action="count", help="optional arg count") 32 | parser.add_option( 33 | "--choices", 34 | action="store", 35 | choices=["choice1", "choice2"], 36 | help="optional arg store with choices", 37 | ) 38 | 39 | args = parser.parse_args() 40 | 41 | handle(args) 42 | 43 | 44 | cli() 45 | --------------------------------------------------------------------------------