├── .circleci └── config.yml ├── .development ├── build │ └── Dockerfile ├── docs │ └── Dockerfile └── license-header-template ├── .github └── images │ └── logo.png ├── .gitignore ├── .pre-commit-config.yaml ├── .releaserc.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── docs ├── Compatibility.md ├── index.md └── modules │ ├── Logging.md │ ├── Platform-Helpers.md │ ├── Prompts.md │ └── User-Feedback.md ├── renovate.json ├── scripts └── upload-release-to-pages.sh ├── src ├── logging.sh ├── main.sh ├── platform_helpers.sh ├── prompts.sh └── user_feedback.sh └── test.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | orbs: 4 | shellcheck: circleci/shellcheck@3.4.0 5 | semantic-release: trustedshops-public/semantic-release@6.0.0 6 | 7 | jobs: 8 | semantic-release: 9 | executor: semantic-release/default 10 | steps: 11 | - checkout 12 | - setup_remote_docker 13 | - semantic-release/install: 14 | additional_packages: '@semantic-release/exec' 15 | - run: 16 | name: Install os dependencies 17 | command: | 18 | sudo apt-get update -y 19 | sudo apt-get install -y gettext uuid-runtime 20 | - semantic-release/execute 21 | upload-release-to-gh-pages: 22 | executor: semantic-release/default 23 | steps: 24 | - checkout 25 | - run: 26 | name: Configure git 27 | command: | 28 | git config --global user.email "no-reply@circleci.com" 29 | git config --global user.name "CircleCI" 30 | - run: 31 | name: Wait a bit for release tag to be created 32 | command: sleep 10 33 | - run: 34 | name: Upload release to pages 35 | command: ./scripts/upload-release-to-pages.sh "${CIRCLE_TAG}" 36 | 37 | workflows: 38 | main: 39 | jobs: 40 | - shellcheck/check: 41 | name: shellcheck 42 | dir: src 43 | - semantic-release: 44 | requires: 45 | - shellcheck 46 | - upload-release-to-gh-pages: 47 | filters: 48 | tags: 49 | only: /.*/ 50 | branches: 51 | ignore: /.*/ 52 | -------------------------------------------------------------------------------- /.development/build/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24-alpine 2 | RUN apk add bash git && \ 3 | mkdir -p /opt && \ 4 | mkdir -p /workspace && \ 5 | addgroup -g 1000 dev && \ 6 | adduser -D -u 1000 -G dev dev && \ 7 | chown -R dev:dev /workspace /opt 8 | WORKDIR /opt 9 | USER dev 10 | 11 | # renovate: datasource=github-releases depName=malscent/bash_bundler 12 | ENV bash_bundler_version="v1.0.2" 13 | RUN go install github.com/malscent/bash_bundler@${bash_bundler_version} 14 | 15 | WORKDIR /workspace 16 | COPY --chown=dev:dev ./src /workspace/src 17 | ENTRYPOINT ["/bin/bash", "-c"] 18 | 19 | -------------------------------------------------------------------------------- /.development/docs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | RUN apk add git bash make gawk 3 | RUN mkdir -p /workspace && \ 4 | addgroup -g 1000 dev && \ 5 | adduser -D -u 1000 -G dev dev && \ 6 | chown -R dev:dev /workspace 7 | WORKDIR /opt 8 | # renovate: datasource=github-releases depName=reconquest/shdoc 9 | ENV shdoc_version="v1.1" 10 | RUN git clone --recursive https://github.com/reconquest/shdoc && \ 11 | cd shdoc && \ 12 | git checkout ${shdoc_version} && \ 13 | make install 14 | USER dev 15 | WORKDIR /workspace 16 | COPY --chown=dev:dev ./src /workspace 17 | ENTRYPOINT ["/bin/bash", "-c"] 18 | -------------------------------------------------------------------------------- /.development/license-header-template: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # # 3 | # Bash TUI Toolkit # 4 | # by Timo Reymann # 5 | # # 6 | # version: $VERSION # 7 | # bundle: $BUNDLE # 8 | # github: https://github.com/timo-reymann/bash-tui-toolkit # 9 | # # 10 | # --------------------------------------------------------------------------- # 11 | # # 12 | # Copyright (C) 2023 Timo Reymann (mail@timo-reymann.de) # 13 | # # 14 | # Licensed under the Apache License, Version 2.0 (the "License"); # 15 | # you may not use this file except in compliance with the License. # 16 | # You may obtain a copy of the License at # 17 | # # 18 | # http://www.apache.org/licenses/LICENSE-2.0 # 19 | # # 20 | # Unless required by applicable law or agreed to in writing, software # 21 | # distributed under the License is distributed on an "AS IS" BASIS, # 22 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 23 | # See the License for the specific language governing permissions and # 24 | # limitations under the License. # 25 | # # 26 | ############################################################################### 27 | -------------------------------------------------------------------------------- /.github/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timo-reymann/bash-tui-toolkit/53039fafb8aa867175dca7239e32f28b194fe123/.github/images/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | dist/ 3 | 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | fail_fast: true 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: check-json 7 | - id: check-merge-conflict 8 | - id: check-yaml 9 | - id: detect-private-key 10 | - id: check-symlinks 11 | - id: check-vcs-permalinks 12 | - id: trailing-whitespace 13 | - id: mixed-line-ending 14 | args: 15 | - --fix=lf 16 | - id: check-case-conflict 17 | - id: check-toml 18 | - id: check-xml 19 | - id: fix-byte-order-marker 20 | - id: destroyed-symlinks 21 | 22 | - repo: https://github.com/syntaqx/git-hooks 23 | rev: v0.0.18 24 | hooks: 25 | - id: shellcheck 26 | additional_dependencies: [] 27 | 28 | - repo: https://github.com/zahorniak/pre-commit-circleci.git 29 | rev: v1.2.0 30 | hooks: 31 | - id: circleci_validate 32 | 33 | - repo: https://github.com/matthorgan/pre-commit-conventional-commits 34 | rev: 20fb9631be1385998138432592d0b6d4dfa38fc9 35 | hooks: 36 | - id: conventional-commit-check 37 | stages: [commit-msg] 38 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "master", 4 | "main" 5 | ], 6 | "plugins": [ 7 | [ 8 | "@semantic-release/commit-analyzer", 9 | { 10 | "preset": "conventionalcommits" 11 | } 12 | ], 13 | [ 14 | "@semantic-release/release-notes-generator", 15 | { 16 | "preset": "conventionalcommits" 17 | } 18 | ], 19 | [ 20 | "@semantic-release/changelog", 21 | { 22 | "changelogFile": "CHANGELOG.md" 23 | } 24 | ], 25 | [ 26 | "@semantic-release/git", 27 | { 28 | "assets": "CHANGELOG.md" 29 | } 30 | ], 31 | ["@semantic-release/exec", { 32 | "prepareCmd": "VERSION=${nextRelease.version} make build" 33 | }], 34 | [ 35 | "@semantic-release/github", 36 | { 37 | "path": "semantic-release", 38 | "name": "timo-reymann/bash-tui-toolkit", 39 | "assets": [ 40 | { 41 | "path": "dist/bundle.sh", 42 | "name": "bundle.bash", 43 | "label": "Bundle > Full Library - containing all files" 44 | }, 45 | { 46 | "path": "dist/logging.sh", 47 | "name": "logging.bash", 48 | "label": "Component > Logging - Provide logging helpers for structured logging" 49 | }, 50 | { 51 | "path": "dist/prompts.sh", 52 | "name": "prompts.bash", 53 | "label": "Component > Logging - Inquirer.js inspired prompts" 54 | }, 55 | { 56 | "path": "dist/user_feedback.sh", 57 | "name": "user-feedback.bash", 58 | "label": "Component > User-Feedback - Provides useful colored outputs for user feedback on actions" 59 | }, 60 | { 61 | "path": "dist/platform_helpers.sh", 62 | "name": "platform-helpers.bash", 63 | "label": "Component > Platform-Helpers - Platform specific helpers" 64 | } 65 | ] 66 | } 67 | ] 68 | ], 69 | "tagFormat": "${version}" 70 | } 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.9.0](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.8.4...1.9.0) (2025-01-03) 2 | 3 | 4 | ### Features 5 | 6 | * **scripts:** Upload documentation for releases ([27e80da](https://github.com/timo-reymann/bash-tui-toolkit/commit/27e80da22421a0cf58e99b4ee24d9bf15c9546c5)) 7 | 8 | ## [1.8.4](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.8.3...1.8.4) (2024-08-22) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * Fix github token clone ([2d2c46e](https://github.com/timo-reymann/bash-tui-toolkit/commit/2d2c46efc5f8ae2cd04516900a4a0c16fe4415a8)) 14 | 15 | ## [1.8.3](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.8.2...1.8.3) (2024-08-22) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * Set author for gh release and make upload to gh pages more robust ([7b73805](https://github.com/timo-reymann/bash-tui-toolkit/commit/7b7380592be3c5262910b7ed49b9546b23d0acfa)) 21 | 22 | ## [1.8.2](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.8.1...1.8.2) (2024-08-22) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * Fix gh pages upload for releases script ([4c86d63](https://github.com/timo-reymann/bash-tui-toolkit/commit/4c86d63ea225edaebae3a0bdbae5e3f86b777ef0)) 28 | 29 | ## [1.8.1](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.8.0...1.8.1) (2024-08-22) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * Fix gh pages upload for releases ([9285fc3](https://github.com/timo-reymann/bash-tui-toolkit/commit/9285fc38c068e4bae57490fd0ecc2c692826d80f)) 35 | 36 | ## [1.8.0](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.7.0...1.8.0) (2024-08-22) 37 | 38 | 39 | ### Features 40 | 41 | * Add gh pages upload for releases ([dc79f7d](https://github.com/timo-reymann/bash-tui-toolkit/commit/dc79f7d59a23da3cb960a9a8377a1aad945eeb5e)) 42 | 43 | ## [1.7.0](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.6.0...1.7.0) (2024-08-22) 44 | 45 | 46 | ### Features 47 | 48 | * **#31:** Read from tty device instead of stdin ([a80a355](https://github.com/timo-reymann/bash-tui-toolkit/commit/a80a3557e46177feebf6780928647c1e204615f8)), closes [#31](https://github.com/timo-reymann/bash-tui-toolkit/issues/31) 49 | 50 | ## [1.6.0](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.5.1...1.6.0) (2024-06-13) 51 | 52 | 53 | ### Features 54 | 55 | * Introduce basic vim bindings in prompts ([#28](https://github.com/timo-reymann/bash-tui-toolkit/issues/28)) ([8b760d1](https://github.com/timo-reymann/bash-tui-toolkit/commit/8b760d13baab43e8f1d7e2b09d622b759b6acaf2)) 56 | 57 | ## [1.5.1](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.5.0...1.5.1) (2023-12-11) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * **platform_helper:** Fix get_opener MacOS detection ([#21](https://github.com/timo-reymann/bash-tui-toolkit/issues/21)) ([b734717](https://github.com/timo-reymann/bash-tui-toolkit/commit/b7347175fe9deec9bd1aa0a27cdaec339c5597c9)) 63 | 64 | ## [1.5.0](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.4.1...1.5.0) (2023-09-14) 65 | 66 | 67 | ### Features 68 | 69 | * **#17:** Add range input ([#18](https://github.com/timo-reymann/bash-tui-toolkit/issues/18)) ([b0f528f](https://github.com/timo-reymann/bash-tui-toolkit/commit/b0f528ff1642083415e5fe3df8607671b9212b01)), closes [#17](https://github.com/timo-reymann/bash-tui-toolkit/issues/17) [#17](https://github.com/timo-reymann/bash-tui-toolkit/issues/17) [#17](https://github.com/timo-reymann/bash-tui-toolkit/issues/17) 70 | 71 | ## [1.4.1](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.4.0...1.4.1) (2023-06-26) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * Set release version on semantic-release ([31380e1](https://github.com/timo-reymann/bash-tui-toolkit/commit/31380e1205c3265600af0dd0f10b544dc7bad879)) 77 | 78 | ## [1.4.1](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.4.0...1.4.1) (2023-06-26) 79 | 80 | 81 | ### Bug Fixes 82 | 83 | * Set release version on semantic-release ([31380e1](https://github.com/timo-reymann/bash-tui-toolkit/commit/31380e1205c3265600af0dd0f10b544dc7bad879)) 84 | 85 | ## [1.4.0](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.3.0...1.4.0) (2023-06-26) 86 | 87 | 88 | ### Features 89 | 90 | * Update header ([13a219e](https://github.com/timo-reymann/bash-tui-toolkit/commit/13a219e77245906661419424a60c3c8c736416e1)) 91 | 92 | ## [1.3.0](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.2.4...1.3.0) (2023-06-26) 93 | 94 | 95 | ### Features 96 | 97 | * Add file headers ([c8bccd8](https://github.com/timo-reymann/bash-tui-toolkit/commit/c8bccd8ab01ecab8711685cb396736a2b61d24b9)) 98 | 99 | ## [1.2.4](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.2.3...1.2.4) (2023-02-17) 100 | 101 | 102 | ### Bug Fixes 103 | 104 | * Fix input color code ([23f8d42](https://github.com/timo-reymann/bash-tui-toolkit/commit/23f8d428981dcc57d1769d020e842e94df7902b7)) 105 | 106 | ## [1.2.3](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.2.2...1.2.3) (2023-02-17) 107 | 108 | 109 | ### Bug Fixes 110 | 111 | * Trigger release ([0945b01](https://github.com/timo-reymann/bash-tui-toolkit/commit/0945b01e1bea73c9063f903e8e4adc3632a023c5)) 112 | 113 | ## [1.2.2](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.2.1...1.2.2) (2023-02-17) 114 | 115 | 116 | ### Bug Fixes 117 | 118 | * Full compability on macos ([#2](https://github.com/timo-reymann/bash-tui-toolkit/issues/2)) ([9fa16c8](https://github.com/timo-reymann/bash-tui-toolkit/commit/9fa16c82957b58c72b575a40a055e0e5048f72bf)) 119 | 120 | ## [1.2.1](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.2.0...1.2.1) (2023-02-17) 121 | 122 | 123 | ### Bug Fixes 124 | 125 | * Fix select after refactor ([7f93185](https://github.com/timo-reymann/bash-tui-toolkit/commit/7f931855a5ddd07ffa35e51c61f9f6263637f073)) 126 | 127 | ## [1.2.0](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.1.1...1.2.0) (2023-02-17) 128 | 129 | 130 | ### Features 131 | 132 | * Add platform helpers ([f6a6e91](https://github.com/timo-reymann/bash-tui-toolkit/commit/f6a6e91d2f3dfd2aa1c4366b39ac8ea1de42e4c0)) 133 | 134 | ## [1.1.1](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.1.0...1.1.1) (2023-02-16) 135 | 136 | 137 | ### Bug Fixes 138 | 139 | * Fix color formatting for success and error messages ([6d8aad0](https://github.com/timo-reymann/bash-tui-toolkit/commit/6d8aad022e329a0942e43e386cb34f2d458f4ac2)) 140 | 141 | ## [1.1.0](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.0.4...1.1.0) (2023-02-16) 142 | 143 | 144 | ### Features 145 | 146 | * Improve performance for empty line print for larger lists ([3b02fbb](https://github.com/timo-reymann/bash-tui-toolkit/commit/3b02fbb91164b807ac84ed6e956cdcad4c8d3c08)) 147 | 148 | ## [1.0.4](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.0.3...1.0.4) (2023-02-16) 149 | 150 | 151 | ### Bug Fixes 152 | 153 | * Fix empty line for checkbox and list prompt ([042f01b](https://github.com/timo-reymann/bash-tui-toolkit/commit/042f01bd0b25db94ed4c83b64a91439e295e4445)) 154 | 155 | ## [1.0.3](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.0.2...1.0.3) (2023-02-15) 156 | 157 | 158 | ### Bug Fixes 159 | 160 | * Add missing new line for select ([717bb54](https://github.com/timo-reymann/bash-tui-toolkit/commit/717bb546a0759174d115f0f7abd9f88dda06294c)) 161 | 162 | ## [1.0.2](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.0.1...1.0.2) (2023-02-15) 163 | 164 | 165 | ### Bug Fixes 166 | 167 | * Fix line break in list result ([391c0e2](https://github.com/timo-reymann/bash-tui-toolkit/commit/391c0e2a9672e1eefba9883b307bd6d87dacddf5)) 168 | 169 | ## [1.0.1](https://github.com/timo-reymann/bash-tui-toolkit/compare/1.0.0...1.0.1) (2023-02-15) 170 | 171 | 172 | ### Bug Fixes 173 | 174 | * Fix empty lines for switch and checkbox ([dd3f163](https://github.com/timo-reymann/bash-tui-toolkit/commit/dd3f163e276f7fb94492ffe2afc3dda43aa7b192)) 175 | 176 | ## 1.0.0 (2023-02-15) 177 | 178 | 179 | ### Features 180 | 181 | * Add first modules ([762ae97](https://github.com/timo-reymann/bash-tui-toolkit/commit/762ae97a36f5dc5fbc28e35a1b92fcc1914697a1)) 182 | * Add modules to publish ([e0aeedf](https://github.com/timo-reymann/bash-tui-toolkit/commit/e0aeedf1904d87770c6eece1c68ed078622e0ca0)) 183 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Bash TUI Toolkit 2 | I love your input! I want to make contributing to this project as easy and transparent as possible, whether it's: 3 | 4 | - Reporting a bug 5 | - Discussing the current state of the code 6 | - Submitting a fix 7 | - Proposing new features 8 | - Becoming a maintainer 9 | 10 | ## I develop with Github 11 | I use github to host code, to track issues and feature requests, as well as accept pull requests. 12 | 13 | ## I Use [CircleCI](https://circleci.com/product/), So All Code Changes Happen Through Pull Requests 14 | Pull requests are the best way to propose changes to the codebase (I use [CircleCI](https://circleci.com/product/)). I actively welcome your pull requests: 15 | 16 | 1. Fork the repo and create your branch from `main`. 17 | 2. If you've added code that should be tested, add tests. 18 | 3. If you've changed APIs, update the documentation. 19 | 4. Ensure the tests pass. 20 | 5. Make sure your code and commit lints. 21 | 6. Issue that pull request! 22 | 23 | ## Any contributions you make will be under the Apache 2.0 24 | In short, when you submit code changes, your submissions are understood to be under the same [Apache 2.0](https://github.com/timo-reymann//blob/main/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. 25 | 26 | ## Report bugs using Github's issues 27 | I use GitHub issues to track public bugs. Report a bug by opening a new issue, it's that easy! 28 | 29 | ## Write bug reports with detail, background, and sample code 30 | 31 | **Great Bug Reports** tend to have: 32 | 33 | - A quick summary and/or background 34 | - Steps to reproduce 35 | - Be specific! 36 | - Give sample code if you can. 37 | - What you expected would happen 38 | - What actually happens 39 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 40 | 41 | People *love* thorough bug reports. I'm not even kidding. 42 | 43 | To make your life easier there is also a handy template available so feel free to use it. 44 | 45 | > ATTENTION: If you DONT provide steps to reproduce the ticket will be closed WITHOUT FURTHER NOTICE! 46 | 47 | ## License 48 | By contributing, you agree that your contributions will be licensed under its Apache 2.0. 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | .PHONY: help 3 | ifdef $(VERSION) 4 | VERSION=$((VERSION)) 5 | else 6 | VERSION=$(shell git describe --tags `git rev-list --tags --max-count=1`) 7 | endif 8 | 9 | help: ## Display this help page 10 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}' 11 | 12 | lint: ## Lint files with shellcheck 13 | @find src/*.sh -type f -exec "shellcheck" "-x" {} \; 14 | 15 | generate-docs: ## Build documentation using docker container 16 | @docker build . -t bash-tui-toolkit/shdoc -f .development/docs/Dockerfile 17 | @docker run --rm bash-tui-toolkit/shdoc 'shdoc < logging.sh ' 2>&1 > docs/modules/Logging.md 18 | @docker run --rm bash-tui-toolkit/shdoc 'shdoc < prompts.sh ' 2>&1 > docs/modules/Prompts.md 19 | @docker run --rm bash-tui-toolkit/shdoc 'shdoc < user_feedback.sh ' 2>&1 > docs/modules/User-Feedback.md 20 | @docker run --rm bash-tui-toolkit/shdoc 'shdoc < platform_helpers.sh ' 2>&1 > docs/modules/Platform-Helpers.md 21 | 22 | _remove_comments_from_file: 23 | @cat $(file) | sed '/^$$/d' | sed '/^#/d' | sed '/^\s*#/d' | tee $(file) > /dev/null 24 | 25 | _push_module: 26 | @echo "=> Push module $(module)" 27 | @cp src/$(module).sh dist/$(module).sh 28 | @$(MAKE) _remove_comments_from_file file=dist/$(module).sh 29 | @$(MAKE) _add_license_header file=dist/$(module).sh bundle=$(module) 30 | 31 | _add_license_header: 32 | @export suffix=$(shell uuidgen) && \ 33 | echo "=> Add license header for bundle $(bundle) to file $(file)" && \ 34 | export VERSION="$(shell printf "%-8s" $(VERSION))" && \ 35 | export BUNDLE="$(shell printf "%-20s" $(bundle))" && \ 36 | cat .development/license-header-template | envsubst > /tmp/license-add.${suffix} && \ 37 | cat $(file) >> /tmp/license-add.${suffix} && \ 38 | cat /tmp/license-add.${suffix} > $(file) 39 | 40 | build: ## Bundle script to dist folder and remove all top level comments 41 | @echo "=> Create dist folder" 42 | @rm -rf dist || true 43 | @mkdir dist/ 44 | @echo "=> Create builder image" 45 | @docker build . -t bash-tui-toolkit/builder -f .development/build/Dockerfile 46 | @echo "=> Bundle and remove comments from target files" 47 | @docker run --rm bash-tui-toolkit/builder 'bash_bundler bundle --entry src/main.sh --output /dev/stderr' 2>dist/bundle.sh 48 | @$(MAKE) _remove_comments_from_file file=dist/bundle.sh 49 | @$(MAKE) _add_license_header file=dist/bundle.sh bundle=bundle 50 | @$(MAKE) _push_module module=logging 51 | @$(MAKE) _push_module module=prompts 52 | @$(MAKE) _push_module module=user_feedback 53 | @$(MAKE) _push_module module=platform_helpers 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bash TUI Toolkit 2 | === 3 | [![LICENSE](https://img.shields.io/github/license/timo-reymann/gpg-key-creation-assistant)](https://github.com/timo-reymann/bash-tui-toolkit/blob/main/LICENSE) 4 | [![CircleCI](https://circleci.com/gh/timo-reymann/bash-tui-toolkit.svg?style=shield)](https://app.circleci.com/pipelines/github/timo-reymann/bash-tui-toolkit) 5 | [![GitHub Release](https://img.shields.io/github/v/tag/timo-reymann/bash-tui-toolkit?label=version)](https://github.com/timo-reymann/bash-tui-toolkit/releases) 6 | [![Renovate](https://img.shields.io/badge/renovate-enabled-green?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzNjkgMzY5Ij48Y2lyY2xlIGN4PSIxODkuOSIgY3k9IjE5MC4yIiByPSIxODQuNSIgZmlsbD0iI2ZmZTQyZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTUgLTYpIi8+PHBhdGggZmlsbD0iIzhiYjViNSIgZD0iTTI1MSAyNTZsLTM4LTM4YTE3IDE3IDAgMDEwLTI0bDU2LTU2YzItMiAyLTYgMC03bC0yMC0yMWE1IDUgMCAwMC03IDBsLTEzIDEyLTktOCAxMy0xM2ExNyAxNyAwIDAxMjQgMGwyMSAyMWM3IDcgNyAxNyAwIDI0bC01NiA1N2E1IDUgMCAwMDAgN2wzOCAzOHoiLz48cGF0aCBmaWxsPSIjZDk1NjEyIiBkPSJNMzAwIDI4OGwtOCA4Yy00IDQtMTEgNC0xNiAwbC00Ni00NmMtNS01LTUtMTIgMC0xNmw4LThjNC00IDExLTQgMTUgMGw0NyA0N2M0IDQgNCAxMSAwIDE1eiIvPjxwYXRoIGZpbGw9IiMyNGJmYmUiIGQ9Ik04MSAxODVsMTgtMTggMTggMTgtMTggMTh6Ii8+PHBhdGggZmlsbD0iIzI1YzRjMyIgZD0iTTIyMCAxMDBsMjMgMjNjNCA0IDQgMTEgMCAxNkwxNDIgMjQwYy00IDQtMTEgNC0xNSAwbC0yNC0yNGMtNC00LTQtMTEgMC0xNWwxMDEtMTAxYzUtNSAxMi01IDE2IDB6Ii8+PHBhdGggZmlsbD0iIzFkZGVkZCIgZD0iTTk5IDE2N2wxOC0xOCAxOCAxOC0xOCAxOHoiLz48cGF0aCBmaWxsPSIjMDBhZmIzIiBkPSJNMjMwIDExMGwxMyAxM2M0IDQgNCAxMSAwIDE2TDE0MiAyNDBjLTQgNC0xMSA0LTE1IDBsLTEzLTEzYzQgNCAxMSA0IDE1IDBsMTAxLTEwMWM1LTUgNS0xMSAwLTE2eiIvPjxwYXRoIGZpbGw9IiMyNGJmYmUiIGQ9Ik0xMTYgMTQ5bDE4LTE4IDE4IDE4LTE4IDE4eiIvPjxwYXRoIGZpbGw9IiMxZGRlZGQiIGQ9Ik0xMzQgMTMxbDE4LTE4IDE4IDE4LTE4IDE4eiIvPjxwYXRoIGZpbGw9IiMxYmNmY2UiIGQ9Ik0xNTIgMTEzbDE4LTE4IDE4IDE4LTE4IDE4eiIvPjxwYXRoIGZpbGw9IiMyNGJmYmUiIGQ9Ik0xNzAgOTVsMTgtMTggMTggMTgtMTggMTh6Ii8+PHBhdGggZmlsbD0iIzFiY2ZjZSIgZD0iTTYzIDE2N2wxOC0xOCAxOCAxOC0xOCAxOHpNOTggMTMxbDE4LTE4IDE4IDE4LTE4IDE4eiIvPjxwYXRoIGZpbGw9IiMzNGVkZWIiIGQ9Ik0xMzQgOTVsMTgtMTggMTggMTgtMTggMTh6Ii8+PHBhdGggZmlsbD0iIzFiY2ZjZSIgZD0iTTE1MyA3OGwxOC0xOCAxOCAxOC0xOCAxOHoiLz48cGF0aCBmaWxsPSIjMzRlZGViIiBkPSJNODAgMTEzbDE4LTE3IDE4IDE3LTE4IDE4ek0xMzUgNjBsMTgtMTggMTggMTgtMTggMTh6Ii8+PHBhdGggZmlsbD0iIzk4ZWRlYiIgZD0iTTI3IDEzMWwxOC0xOCAxOCAxOC0xOCAxOHoiLz48cGF0aCBmaWxsPSIjYjUzZTAyIiBkPSJNMjg1IDI1OGw3IDdjNCA0IDQgMTEgMCAxNWwtOCA4Yy00IDQtMTEgNC0xNiAwbC02LTdjNCA1IDExIDUgMTUgMGw4LTdjNC01IDQtMTIgMC0xNnoiLz48cGF0aCBmaWxsPSIjOThlZGViIiBkPSJNODEgNzhsMTgtMTggMTggMTgtMTggMTh6Ii8+PHBhdGggZmlsbD0iIzAwYTNhMiIgZD0iTTIzNSAxMTVsOCA4YzQgNCA0IDExIDAgMTZMMTQyIDI0MGMtNCA0LTExIDQtMTUgMGwtOS05YzUgNSAxMiA1IDE2IDBsMTAxLTEwMWM0LTQgNC0xMSAwLTE1eiIvPjxwYXRoIGZpbGw9IiMzOWQ5ZDgiIGQ9Ik0yMjggMTA4bC04LThjLTQtNS0xMS01LTE2IDBMMTAzIDIwMWMtNCA0LTQgMTEgMCAxNWw4IDhjLTQtNC00LTExIDAtMTVsMTAxLTEwMWM1LTQgMTItNCAxNiAweiIvPjxwYXRoIGZpbGw9IiNhMzM5MDQiIGQ9Ik0yOTEgMjY0bDggOGM0IDQgNCAxMSAwIDE2bC04IDdjLTQgNS0xMSA1LTE1IDBsLTktOGM1IDUgMTIgNSAxNiAwbDgtOGM0LTQgNC0xMSAwLTE1eiIvPjxwYXRoIGZpbGw9IiNlYjZlMmQiIGQ9Ik0yNjAgMjMzbC00LTRjLTYtNi0xNy02LTIzIDAtNyA3LTcgMTcgMCAyNGw0IDRjLTQtNS00LTExIDAtMTZsOC04YzQtNCAxMS00IDE1IDB6Ii8+PHBhdGggZmlsbD0iIzEzYWNiZCIgZD0iTTEzNCAyNDhjLTQgMC04LTItMTEtNWwtMjMtMjNhMTYgMTYgMCAwMTAtMjNMMjAxIDk2YTE2IDE2IDAgMDEyMiAwbDI0IDI0YzYgNiA2IDE2IDAgMjJMMTQ2IDI0M2MtMyAzLTcgNS0xMiA1em03OC0xNDdsLTQgMi0xMDEgMTAxYTYgNiAwIDAwMCA5bDIzIDIzYTYgNiAwIDAwOSAwbDEwMS0xMDFhNiA2IDAgMDAwLTlsLTI0LTIzLTQtMnoiLz48cGF0aCBmaWxsPSIjYmY0NDA0IiBkPSJNMjg0IDMwNGMtNCAwLTgtMS0xMS00bC00Ny00N2MtNi02LTYtMTYgMC0yMmw4LThjNi02IDE2LTYgMjIgMGw0NyA0NmM2IDcgNiAxNyAwIDIzbC04IDhjLTMgMy03IDQtMTEgNHptLTM5LTc2Yy0xIDAtMyAwLTQgMmwtOCA3Yy0yIDMtMiA3IDAgOWw0NyA0N2E2IDYgMCAwMDkgMGw3LThjMy0yIDMtNiAwLTlsLTQ2LTQ2Yy0yLTItMy0yLTUtMnoiLz48L3N2Zz4=)](https://renovatebot.com) 7 | [![pre-commit](https://img.shields.io/badge/%E2%9A%93%20%20pre--commit-enabled-success)](https://pre-commit.com/) 8 | [![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/timo-reymann/bash-tui-toolkit/total)](https://github.com/timo-reymann/bash-tui-toolkit/releases) 9 | 10 |

11 | 12 |
13 | Toolkit to create interactive and shiny terminal UIs using plain bash builtins 14 |

15 | 16 | ## Features 17 | 18 | - clean and standardized API 19 | - provide a simple and clear default set of elements to use creating an interactive terminal UI 20 | - clean and minimalistic design 21 | - zero dependencies to be installed 22 | - parts can be used modular 23 | 24 | ## Requirements 25 | 26 | - [bash 3+](https://www.gnu.org/software/bash/) 27 | 28 | ## Installation 29 | 30 | ### Download and include in your scripts 31 | 32 | 1. Download the bundle (entire lib) or single compoennt 33 | from [releases](https://github.com/timo-reymann/bash-tui-toolkit/releases) 34 | 2. Source the bundle in your script or embed, e.g. 35 | ```bash 36 | source ./lib/bundle.bash 37 | ``` 38 | or simply copy it into your script. 39 | 40 | ### Source directly from release 41 | 42 | > Note that this is not a recommended approach and only intended for quick testing or trying out another version. 43 | 44 | 1. Choose the bundle in the version you want to use from the available releases in the [docs](https://bash-tui-toolkit.timo-reymann.de/). 45 | 2. Source it to your script on runtime: 46 | 1. Using `wget` 47 | ```bash 48 | source <(wget -qO- https://bash-tui-toolkit.timo-reymann.de/latest/bundle.bash) 49 | ``` 50 | 2. Using `curl` 51 | ```bash 52 | source <(curl -qsS https://bash-tui-toolkit.timo-reymann.de/latest/bundle.bash) 53 | ``` 54 | 55 | ## Usage 56 | 57 | For a list of available modules and their documentation please check the [docs](https://bash-tui-toolkit.timo-reymann.de/). 58 | 59 | For a complete playground demo check [test.sh](./test.sh). 60 | 61 | ## Motivation 62 | 63 | Providing a clean bash UI sometimes becomes a mess and interactivity is hard to achieve especially when it should be 64 | portable. 65 | 66 | The target is to provide a simple-to-use toolkit that can be dropped into any bash script and is compatible no matter 67 | the target system. 68 | 69 | ## Documentation 70 | 71 | - [GitHub Pages](https://bash-tui-toolkit.timo-reymann.de/) - Versioned releases, documentation and compability matrix 72 | - [Modules](./docs/modules) - Modules available and their usage 73 | - [Compability table](./docs/Compatibility) - Known combinations of OS/Bash Version/Terminal emulators that work guaranteed 74 | 75 | ## Contributing 76 | 77 | I love your input! I want to make contributing to this project as easy and transparent as possible, whether it's: 78 | 79 | - Reporting a bug 80 | - Discussing the current state of the configuration 81 | - Submitting a fix 82 | - Proposing new features 83 | - Becoming a maintainer 84 | 85 | To get started please read the [Contribution Guidelines](./CONTRIBUTING.md). 86 | 87 | ## Development 88 | 89 | ### Requirements 90 | 91 | - [GNU make](https://www.gnu.org/software/make/) 92 | - [Docker](https://docs.docker.com/get-docker/) 93 | - [pre-commit](https://pre-commit.com/) 94 | 95 | ### Build 96 | 97 | ``` 98 | make build 99 | ``` 100 | 101 | ### Update documentation 102 | 103 | To update the module documentation you just need to run 104 | 105 | ```sh 106 | make generate-docs 107 | ``` 108 | 109 | This builds the documentation inside a docker container and updates the 110 | repo locally. Afterwards just commit the docs with your code changes 111 | 112 | 113 | ## Credits 114 | 115 | - Logo 116 | - Bash logo from [pngegg](https://www.pngegg.com/en/png-pxpgu) 117 | - Toolbox logo from [IconExperience.com](https://www.iconexperience.com/g_collection/icons/?icon=toolbox) 118 | 119 | ## Alternatives 120 | - [kahkhang/Inquirer.sh](https://github.com/kahkhang/Inquirer.sh) - List, Checkbox and Text Input with more advanced 121 | validation 122 | -------------------------------------------------------------------------------- /docs/Compatibility.md: -------------------------------------------------------------------------------- 1 | Compability 2 | === 3 | 4 | The toolkit is known to work with the following platforms listed below. 5 | 6 | If you feel like an important one is missing feel free to create an issue or PR directly for the file. 7 | 8 | > Since there are some weird combinations/side effects based on the platform you might use there are different side 9 | > effects that might occur 10 | > 11 | > If you use it on a different platform successfully please create a PR to add a item here :) 12 | 13 | | OS | Version of OS | Terminal emulator | Bash Major Version | Works 14 | |:--------|:--------------|:------------------|:-------------------|:------ 15 | | Ubuntu | 20 | Tilix | 4 | ✔️ 16 | | Ubuntu | 20 | xterm | 4 | ✔️ 17 | | Ubuntu | 22 | Tilix | 5 | ✔️ 18 | | Ubuntu | 24 | Tilix | 5 | ✔️ 19 | | Ubuntu | 24 | Ghostty | 5 | ✔️ 20 | | Alpine | 3 | n/a | 5 | ✔️ 21 | | MacOS | Monterey | iTerm | 3 | ✔️ 22 | | MacOS | Monterey | iTerm2 | 3 | ✔️ 23 | | MacOS | Sequoia | iTerm | 3 | ✔️ 24 | | MacOS | Sequoia | iTerm2 | 5 | ✔️ 25 | | MacOS | Sequoia | Ghostty | 5 | ✔️ 26 | | Windows | 10 | Windows Terminal | 4 | ✔️ 27 | | Windows | 10 | Git Bash | 4 | ✔️ 28 | | Windows | 10 | Hyper | 4 | ✔️ 29 | | Windows | 11 | Git Bash | 4 | ✔️ 30 | | Windows | 11 | Windows Terminal | 4 | ✔️ 31 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | bash-tui-toolkit 2 | === 3 | 4 | Toolkit to create interactive and shiny terminal UIs using plain bash builtins 5 | 6 | ## Contents 7 | 8 | - [Compatibility](./Compatibility) 9 | - Available Modules 10 | - [Logging](./modules/Logging.md) 11 | - [Platform Helpers](./modules/Platform-Helpers.md) 12 | - [Prompts](./modules/Prompts.md) 13 | - [User Feedback](./modules/User-Feedback.md) -------------------------------------------------------------------------------- /docs/modules/Logging.md: -------------------------------------------------------------------------------- 1 | # Logging 2 | 3 | Provide logging helpers for structured logging 4 | 5 | ## Overview 6 | 7 | Parse log level from text representation to level number 8 | 9 | ## Index 10 | 11 | * [parse_log_level](#parse_log_level) 12 | * [log](#log) 13 | 14 | ### parse_log_level 15 | 16 | Parse log level from text representation to level number 17 | 18 | #### Example 19 | 20 | ```bash 21 | # Parse lower case log level 22 | parse_log_level "info" 23 | # Parse upper case log level 24 | parse_log_level "ERROR" 25 | ``` 26 | 27 | #### Arguments 28 | 29 | * **$1** (string): Log level to parse 30 | 31 | #### Variables set 32 | 33 | * **LOG_LEVEL** (the): global log level to use in the script 34 | 35 | #### Output on stdout 36 | 37 | * numeric log level 38 | 39 | ### log 40 | 41 | Log output on a given level, checks if $LOG_LEVEL, if not set defaults to INFO 42 | 43 | #### Example 44 | 45 | ```bash 46 | # Log a message on info level 47 | log "$LOG_INFO" "this is a info message" 48 | log "LOG_DEBUG" "i am only visible when \$LOG_LEVEL is debug" 49 | ``` 50 | 51 | #### Arguments 52 | 53 | * **$1** (number): Numeric log level 54 | * **$2** (string): Message to output 55 | 56 | #### Output on stdout 57 | 58 | * Formatted log message with ANSI color codes 59 | 60 | -------------------------------------------------------------------------------- /docs/modules/Platform-Helpers.md: -------------------------------------------------------------------------------- 1 | # Platform-Helpers 2 | 3 | Platform specific helpers 4 | 5 | ## Overview 6 | 7 | Detect the OS the script is running on 8 | 9 | ## Index 10 | 11 | * [detect_os](#detect_os) 12 | * [get_opener](#get_opener) 13 | * [open_link](#open_link) 14 | 15 | ### detect_os 16 | 17 | Detect the OS the script is running on 18 | 19 | #### Output on stdout 20 | 21 | * solaris | macos | linux | bsd | windows | unknown 22 | 23 | ### get_opener 24 | 25 | Get opener command for platform 26 | 27 | #### Output on stdout 28 | 29 | * Command that can be used, if it is not supported returns an empty string 30 | 31 | ### open_link 32 | 33 | Open a link using the default opener, if it is not possible/supported or an error occurs simply prints the url with instructions 34 | 35 | #### Arguments 36 | 37 | * **$1** (Link): to open 38 | 39 | #### Exit codes 40 | 41 | * **1**: Failed to open link 42 | * **0**: Opened link using util 43 | 44 | #### Output on stdout 45 | 46 | * Instructions in case link can not be opened 47 | 48 | -------------------------------------------------------------------------------- /docs/modules/Prompts.md: -------------------------------------------------------------------------------- 1 | # Prompts 2 | 3 | Inquirer.js inspired prompts 4 | 5 | ## Overview 6 | 7 | Prompt for text 8 | 9 | ## Index 10 | 11 | * [input](#input) 12 | * [confirm](#confirm) 13 | * [list](#list) 14 | * [checkbox](#checkbox) 15 | * [password](#password) 16 | * [editor](#editor) 17 | * [with_validate](#with_validate) 18 | * [range](#range) 19 | * [validate_present](#validate_present) 20 | 21 | ### input 22 | 23 | Prompt for text 24 | 25 | #### Example 26 | 27 | ```bash 28 | # Raw input without validation 29 | text=$(input "Please enter something and confirm with enter") 30 | # Input with validation 31 | text=$(with_validate 'input "Please enter at least one character and confirm with enter"' validate_present) 32 | ``` 33 | 34 | #### Arguments 35 | 36 | * **$1** (string): Phrase for prompting to text 37 | 38 | #### Output on stdout 39 | 40 | * Text as provided by user 41 | 42 | ### confirm 43 | 44 | Show confirm dialog for yes/no 45 | 46 | #### Example 47 | 48 | ```bash 49 | confirmed=$(confirm "Should it be?") 50 | if [ "$confirmed" = "0" ]; then echo "No?"; else echo "Yes!"; fi 51 | ``` 52 | 53 | #### Arguments 54 | 55 | * **$1** (string): Phrase for promptint to text 56 | 57 | #### Output on stdout 58 | 59 | * 0 for no, 1 for yes 60 | 61 | ### list 62 | 63 | Renders a text based list of options that can be selected by the 64 | user using up, down and enter keys and returns the chosen option. 65 | Inspired by https://unix.stackexchange.com/questions/146570/arrow-key-enter-menu/415155#415155 66 | 67 | #### Example 68 | 69 | ```bash 70 | options=("one" "two" "three" "four") 71 | option=$(list "Select one item" "${options[@]}") 72 | echo "Your choice: ${options[$option]}" 73 | ``` 74 | 75 | #### Arguments 76 | 77 | * **$1** (string): Phrase for promptint to text 78 | * **$2** (array): List of options (max 256) 79 | 80 | #### Output on stdout 81 | 82 | * selected index (0 for opt1, 1 for opt2 ...) 83 | 84 | ### checkbox 85 | 86 | Render a text based list of options, where multiple can be selected by the 87 | user using up, down and enter keys and returns the chosen option. 88 | Inspired by https://unix.stackexchange.com/questions/146570/arrow-key-enter-menu/415155#415155 89 | 90 | #### Example 91 | 92 | ```bash 93 | options=("one" "two" "three" "four") 94 | checked=$(checkbox "Select one or more items" "${options[@]}") 95 | echo "Your choices: ${checked}" 96 | ``` 97 | 98 | #### Arguments 99 | 100 | * **$1** (string): Phrase for promptint to text 101 | * **$2** (array): List of options (max 256) 102 | 103 | #### Output on stdout 104 | 105 | * selected index (0 for opt1, 1 for opt2 ...) 106 | 107 | ### password 108 | 109 | Show password prompt displaying stars for each password character letter typed 110 | it also allows deleting input 111 | 112 | #### Example 113 | 114 | ```bash 115 | # Password prompt with custom validation 116 | validate_password() { if [ ${#1} -lt 10 ];then echo "Password needs to be at least 10 characters"; exit 1; fi } 117 | pass=$(with_validate 'password "Enter random password"' validate_password) 118 | # Password ith no validation 119 | pass=$(password "Enter password to use") 120 | ``` 121 | 122 | #### Arguments 123 | 124 | * **$1** (string): Phrase for prompting to text 125 | 126 | #### Output on stdout 127 | 128 | * password as written by user 129 | 130 | ### editor 131 | 132 | Open default editor ($EDITOR) if none is set falls back to vi 133 | 134 | #### Example 135 | 136 | ```bash 137 | # Open default editor 138 | text=$(editor "Please enter something in the editor") 139 | echo -e "You wrote:\n${text}" 140 | ``` 141 | 142 | #### Arguments 143 | 144 | * **$1** (string): Phrase for promptint to text 145 | 146 | #### Output on stdout 147 | 148 | * Text as input by user in input 149 | 150 | ### with_validate 151 | 152 | Evaluate prompt command with validation, this prompts the user for input till the validation function 153 | returns with 0 154 | 155 | #### Example 156 | 157 | ```bash 158 | # Using builtin is present validator 159 | text=$(with_validate 'input "Please enter something and confirm with enter"' validate_present) 160 | # Using custom validator e.g. for password 161 | validate_password() { if [ ${#1} -lt 10 ];then echo "Password needs to be at least 10 characters"; exit 1; fi } 162 | pass=$(with_validate 'password "Enter random password"' validate_password) 163 | ``` 164 | 165 | #### Arguments 166 | 167 | * **$1** (string): Prompt command to evaluate until validation is successful 168 | * **$2** (function): validation callback (this is called once for exit code and once for status code) 169 | 170 | #### Output on stdout 171 | 172 | * Value collected by evaluating prompt 173 | 174 | ### range 175 | 176 | Display a range dialog that can incremented and decremented using the arrow keys 177 | 178 | #### Example 179 | 180 | ```bash 181 | # Range with negative min value 182 | value=$(range -5 0 5) 183 | ``` 184 | 185 | #### Arguments 186 | 187 | * **$1** (string): Phrase for prompting to text 188 | * **$2** (int): Minimum selectable value 189 | * **$3** (int): Default selected value 190 | * **$4** (int): Maximum value of the select 191 | 192 | #### Output on stdout 193 | 194 | * Selected value using arrow keys 195 | 196 | ### validate_present 197 | 198 | Validate a prompt returned any value 199 | 200 | #### Example 201 | 202 | ```bash 203 | # text input with validation 204 | text=$(with_validate 'input "Please enter something and confirm with enter"' validate_present) 205 | ``` 206 | 207 | #### Arguments 208 | 209 | * **$1** (value): to validate 210 | 211 | #### Exit codes 212 | 213 | * **0**: String is at least 1 character long 214 | * **1**: There was no input given 215 | 216 | #### Output on stdout 217 | 218 | * error message for user 219 | 220 | -------------------------------------------------------------------------------- /docs/modules/User-Feedback.md: -------------------------------------------------------------------------------- 1 | # User-Feedback 2 | 3 | Provides useful colored outputs for user feedback on actions 4 | 5 | ## Overview 6 | 7 | Display error message in stderr, prefixed by check emoji 8 | 9 | ## Index 10 | 11 | * [show_error](#show_error) 12 | * [show_success](#show_success) 13 | 14 | ### show_error 15 | 16 | Display error message in stderr, prefixed by check emoji 17 | 18 | #### Example 19 | 20 | ```bash 21 | show_error "Oh snap, that went horribly wrong" 22 | ``` 23 | 24 | #### Arguments 25 | 26 | * **$1** (string): Error message to display 27 | 28 | ### show_success 29 | 30 | Display success message in stderr, prefixed by cross emoji 31 | 32 | #### Example 33 | 34 | ```bash 35 | show_success "There it is! World peace." 36 | ``` 37 | 38 | #### Arguments 39 | 40 | * **$1** (string): Success message to display 41 | 42 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>timo-reymann/renovate-config" 5 | ], 6 | "regexManagers": [ 7 | { 8 | "fileMatch": ["Dockerfile$"], 9 | "matchStrings": [ 10 | "datasource=(?.*?) depName=(?.*?)( versioning=(?.*?))?\\sARG .*?_version=(?.*)\\s" 11 | ], 12 | "versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /scripts/upload-release-to-pages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | PROJECT_DIR="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")" 5 | 6 | release_tag="$1" 7 | if [[ -z "$release_tag" ]]; then 8 | echo "Usage: $0 " 9 | exit 1 10 | fi 11 | 12 | tmp_folder="$(mktemp -d)" 13 | echo "Created folder $tmp_folder ..." 14 | cd "${tmp_folder}" 15 | 16 | echo "Clone gh-pages branch ..." 17 | git clone https://token:${GITHUB_TOKEN}@github.com/timo-reymann/bash-tui-toolkit.git --branch gh-pages "${tmp_folder}" 18 | 19 | echo "Create folder for version ..." 20 | mkdir -p "${release_tag}" 21 | 22 | release_info=$(curl --header "Authorization: Bearer ${GITHUB_TOKEN}" -s "https://api.github.com/repos/timo-reymann/bash-tui-toolkit/releases/tags/${release_tag}") 23 | release_id="$(jq -r '.id' <<< "$release_info")" 24 | release_assets="$(curl --header "Authorization: Bearer ${GITHUB_TOKEN}" -s "https://api.github.com/repos/timo-reymann/bash-tui-toolkit/releases/${release_id}")" 25 | asset_download_links="$(jq -r ".assets[].browser_download_url" <<< "$release_assets")" 26 | 27 | cd "${release_tag}" 28 | while read -r download_url 29 | do 30 | echo "Download asset from ${download_url} ..." 31 | curl -sS -L "${download_url}" -O 32 | done <<< "${asset_download_links}" 33 | cd - 34 | 35 | echo "Link ${release_tag} to latest ..." 36 | rm latest || true 37 | ln -s "${release_tag}" latest 38 | 39 | rm -rf "${release_tag}/docs" 40 | cp -R "${PROJECT_DIR}/docs/" "${release_tag}/docs/" 41 | 42 | git stage . 43 | git commit -m "chore: Add assets for ${release_tag}\ 44 | 45 | [skip ci] 46 | " 47 | 48 | git push 49 | -------------------------------------------------------------------------------- /src/logging.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # @name Logging 3 | # @brief Provide logging helpers for structured logging 4 | 5 | # Log levels 6 | LOG_ERROR=3 7 | LOG_WARN=2 8 | LOG_INFO=1 9 | LOG_DEBUG=0 10 | 11 | # @description Parse log level from text representation to level number 12 | # 13 | # @arg $1 string Log level to parse 14 | # @stdout numeric log level 15 | # @set LOG_LEVEL the global log level to use in the script 16 | # @example 17 | # # Parse lower case log level 18 | # parse_log_level "info" 19 | # @example 20 | # # Parse upper case log level 21 | # parse_log_level "ERROR" 22 | parse_log_level() { 23 | local level="$1" 24 | local parsed 25 | 26 | case "${level}" in 27 | info | INFO) parsed=$LOG_INFO; ;; 28 | debug | DEBUG) parsed=$LOG_DEBUG; ;; 29 | warn | WARN) parsed=$LOG_WARN; ;; 30 | error | ERROR) parsed=$LOG_ERROR; ;; 31 | *) parsed=-1; ;; 32 | esac 33 | 34 | export LOG_LEVEL="${parsed}" 35 | } 36 | 37 | # @description Log output on a given level, checks if $LOG_LEVEL, if not set defaults to INFO 38 | # @arg $1 number Numeric log level 39 | # @arg $2 string Message to output 40 | # @stdout Formatted log message with ANSI color codes 41 | # @example 42 | # # Log a message on info level 43 | # log "$LOG_INFO" "this is a info message" 44 | # log "LOG_DEBUG" "i am only visible when \$LOG_LEVEL is debug" 45 | log() { 46 | local level="$1" 47 | local message="$2" 48 | local color="" 49 | 50 | if [[ $level -lt ${LOG_LEVEL:-$LOG_INFO} ]]; then 51 | return 52 | fi 53 | 54 | case "${level}" in 55 | "$LOG_INFO") 56 | level="INFO" 57 | color='\033[1;36m' 58 | ;; 59 | 60 | "$LOG_DEBUG") 61 | level="DEBUG" 62 | color='\033[1;34m' 63 | ;; 64 | 65 | "$LOG_WARN") 66 | level="WARN" 67 | color='\033[0;33m' 68 | ;; 69 | 70 | "$LOG_ERROR") 71 | level="ERROR" 72 | color='\033[0;31m' 73 | ;; 74 | esac 75 | 76 | echo -e "[${color}$(printf '%-5s' "${level}")\033[0m] \033[1;35m$(date +'%Y-%m-%dT%H:%M:%S')\033[0m ${message}" 77 | } 78 | -------------------------------------------------------------------------------- /src/main.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shellcheck disable=SC1091 4 | source "prompts.sh" 5 | # shellcheck disable=SC1091 6 | source "user_feedback.sh" 7 | # shellcheck disable=SC1091 8 | source "logging.sh" 9 | # shellcheck disable=SC1091 10 | source "platform_helpers.sh" 11 | 12 | -------------------------------------------------------------------------------- /src/platform_helpers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # @name Platform-Helpers 3 | # @brief Platform specific helpers 4 | 5 | # @description Detect the OS the script is running on 6 | # @stdout solaris | macos | linux | bsd | windows | unknown 7 | detect_os() { 8 | case "$OSTYPE" in 9 | solaris*) echo "solaris"; ;; 10 | darwin*) echo "macos"; ;; 11 | linux*) echo "linux"; ;; 12 | bsd*) echo "bsd"; ;; 13 | msys*) echo "windows"; ;; 14 | cygwin*) echo "windows"; ;; 15 | *) echo "unknown"; ;; 16 | esac 17 | } 18 | 19 | # @description Get opener command for platform 20 | # @stdout Command that can be used, if it is not supported returns an empty string 21 | get_opener() { 22 | local cmd 23 | case "$(detect_os)" in 24 | macos) cmd="open"; ;; 25 | linux) cmd="xdg-open"; ;; 26 | windows) cmd="start"; ;; 27 | *) cmd=""; ;; 28 | esac 29 | echo "$cmd" 30 | } 31 | 32 | # @description Open a link using the default opener, if it is not possible/supported or an error occurs simply prints the url with instructions 33 | # @arg $1 Link to open 34 | # @exitcode 1 Failed to open link 35 | # @exitcode 0 Opened link using util 36 | # @stdout Instructions in case link can not be opened 37 | open_link() { 38 | cmd="$(get_opener)" 39 | if [ "$cmd" == "" ]; then 40 | echo "Your platform is not supported for opening links." 41 | echo "Please open the following URL in your preferred browser:" 42 | echo " ${1}" 43 | return 1 44 | fi 45 | 46 | $cmd "$1" 47 | 48 | if [[ $? -eq 1 ]]; then 49 | echo "Failed to open your browser." 50 | echo "Please open the following URL in your browser:" 51 | echo "${1}" 52 | return 1 53 | fi 54 | 55 | return 0 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/prompts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # @name Prompts 3 | # @brief Inquirer.js inspired prompts 4 | 5 | _read_stdin() { 6 | # shellcheck disable=SC2162,SC2068 7 | read $@ &2; } 17 | _cursor_blink_off() { echo -en "\033[?25l" >&2; } 18 | _cursor_to() { echo -en "\033[$1;$2H" >&2; } 19 | 20 | # key input helper 21 | _key_input() { 22 | local ESC=$'\033' 23 | local IFS='' 24 | 25 | _read_stdin -rsn1 a 26 | # is the first character ESC? 27 | # shellcheck disable=SC2154 28 | if [[ "$ESC" == "$a" ]]; then 29 | _read_stdin -rsn2 b 30 | fi 31 | 32 | # shellcheck disable=SC2154 33 | local input="${a}${b}" 34 | # shellcheck disable=SC1087 35 | case "$input" in 36 | "$ESC[A" | "k") echo up ;; 37 | "$ESC[B" | "j") echo down ;; 38 | "$ESC[C" | "l") echo right ;; 39 | "$ESC[D" | "h") echo left ;; 40 | '') echo enter ;; 41 | ' ') echo space ;; 42 | esac 43 | } 44 | 45 | # print new line for empty element in array 46 | # shellcheck disable=SC2231 47 | _new_line_foreach_item() { 48 | count=0 49 | while [[ $count -lt $1 ]]; 50 | do 51 | echo "" >&2 52 | ((count++)) 53 | done 54 | } 55 | 56 | # display prompt text without linebreak 57 | _prompt_text() { 58 | echo -en "\033[32m?\033[0m\033[1m ${1}\033[0m " >&2 59 | } 60 | 61 | # decrement counter $1, considering out of range for $2 62 | _decrement_selected() { 63 | local selected=$1; 64 | ((selected--)) 65 | if [ "${selected}" -lt 0 ]; then 66 | selected=$(($2 - 1)); 67 | fi 68 | echo -n $selected 69 | } 70 | 71 | # increment counter $1, considering out of range for $2 72 | _increment_selected() { 73 | local selected=$1; 74 | ((selected++)); 75 | if [ "${selected}" -ge "${opts_count}" ]; then 76 | selected=0; 77 | fi 78 | echo -n $selected 79 | } 80 | 81 | # @description Prompt for text 82 | # @arg $1 string Phrase for prompting to text 83 | # @stderr Instructions for user 84 | # @stdout Text as provided by user 85 | # @example 86 | # # Raw input without validation 87 | # text=$(input "Please enter something and confirm with enter") 88 | # @example 89 | # # Input with validation 90 | # text=$(with_validate 'input "Please enter at least one character and confirm with enter"' validate_present) 91 | input() { 92 | _prompt_text "$1"; echo -en "\033[36m\c" >&2 93 | _read_stdin -r text 94 | # shellcheck disable=SC2154 95 | echo -n "${text}" 96 | } 97 | 98 | # @description Show confirm dialog for yes/no 99 | # @arg $1 string Phrase for promptint to text 100 | # @stdout 0 for no, 1 for yes 101 | # @stderr Instructions for user 102 | # @example 103 | # confirmed=$(confirm "Should it be?") 104 | # if [ "$confirmed" = "0" ]; then echo "No?"; else echo "Yes!"; fi 105 | confirm() { 106 | # ensure cursor and input echoing back on upon a ctrl+c during read -s 107 | trap "_cursor_blink_on; stty echo; exit" 2 108 | _cursor_blink_off 109 | 110 | _prompt_text "$1 (y/N)" 111 | echo -en "\033[36m\c " >&2 112 | 113 | local start_row; start_row=$(_get_cursor_row) 114 | local current_row; current_row=$((start_row - 1)) 115 | 116 | local result="" 117 | echo -n " " >&2 118 | while true; 119 | do 120 | echo -e "\033[1D\c " >&2 121 | # shellcheck disable=SC2162 122 | _read_stdin -n1 result 123 | 124 | case "$result" in 125 | y|Y) echo -n 1; break; ;; 126 | n|N) echo -n 0; break; ;; 127 | *) _cursor_to "${current_row}";; 128 | esac 129 | done 130 | 131 | echo -en "\033[0m" >&2 132 | echo "" >&2 133 | } 134 | 135 | # @description Renders a text based list of options that can be selected by the 136 | # user using up, down and enter keys and returns the chosen option. 137 | # Inspired by https://unix.stackexchange.com/questions/146570/arrow-key-enter-menu/415155#415155 138 | # @arg $1 string Phrase for promptint to text 139 | # @arg $2 array List of options (max 256) 140 | # @stdout selected index (0 for opt1, 1 for opt2 ...) 141 | # @stderr Instructions for user 142 | # @example 143 | # options=("one" "two" "three" "four") 144 | # option=$(list "Select one item" "${options[@]}") 145 | # echo "Your choice: ${options[$option]}" 146 | list() { 147 | _prompt_text "$1 " 148 | 149 | local opts=("${@:2}") 150 | local opts_count=$(($# -1)) 151 | _new_line_foreach_item "${#opts[@]}" 152 | 153 | # determine current screen position for overwriting the options 154 | local lastrow; lastrow=$(_get_cursor_row) 155 | local startrow; startrow=$((lastrow - opts_count + 1)) 156 | 157 | # ensure cursor and input echoing back on upon a ctrl+c during read -s 158 | trap "_cursor_blink_on; stty echo; exit" 2 159 | _cursor_blink_off 160 | 161 | local selected=0 162 | while true; do 163 | # print options by overwriting the last lines 164 | local idx=0 165 | for opt in "${opts[@]}"; do 166 | _cursor_to $((startrow + idx)) 167 | if [ $idx -eq $selected ]; then 168 | printf "\033[0m\033[36m❯\033[0m \033[36m%s\033[0m" "$opt" >&2 169 | else 170 | printf " %s" "$opt" >&2 171 | fi 172 | ((idx++)) 173 | done 174 | 175 | # user key control 176 | case $(_key_input) in 177 | enter) break; ;; 178 | up) selected=$(_decrement_selected "${selected}" "${opts_count}"); ;; 179 | down) selected=$(_increment_selected "${selected}" "${opts_count}"); ;; 180 | esac 181 | done 182 | 183 | echo -en "\n" >&2 184 | 185 | # cursor position back to normal 186 | _cursor_to "${lastrow}" 187 | _cursor_blink_on 188 | 189 | echo -n "${selected}" 190 | } 191 | 192 | # @description Render a text based list of options, where multiple can be selected by the 193 | # user using up, down and enter keys and returns the chosen option. 194 | # Inspired by https://unix.stackexchange.com/questions/146570/arrow-key-enter-menu/415155#415155 195 | # @arg $1 string Phrase for promptint to text 196 | # @arg $2 array List of options (max 256) 197 | # @stdout selected index (0 for opt1, 1 for opt2 ...) 198 | # @stderr Instructions for user 199 | # @example 200 | # options=("one" "two" "three" "four") 201 | # checked=$(checkbox "Select one or more items" "${options[@]}") 202 | # echo "Your choices: ${checked}" 203 | checkbox() { 204 | _prompt_text "$1" 205 | 206 | local opts; opts=("${@:2}") 207 | local opts_count; opts_count=$(($# -1)) 208 | _new_line_foreach_item "${#opts[@]}" 209 | 210 | # determine current screen position for overwriting the options 211 | local lastrow; lastrow=$(_get_cursor_row) 212 | local startrow; startrow=$((lastrow - opts_count + 1)) 213 | 214 | # ensure cursor and input echoing back on upon a ctrl+c during read -s 215 | trap "_cursor_blink_on; stty echo; exit" 2 216 | _cursor_blink_off 217 | 218 | local selected=0 219 | local checked=() 220 | while true; do 221 | # print options by overwriting the last lines 222 | local idx=0 223 | for opt in "${opts[@]}"; do 224 | _cursor_to $((startrow + idx)) 225 | local icon="◯" 226 | for item in "${checked[@]}"; do 227 | if [ "$item" == "$idx" ]; then 228 | icon="◉" 229 | break; 230 | fi 231 | done 232 | 233 | if [ $idx -eq $selected ]; then 234 | printf "%s \e[0m\e[36m❯\e[0m \e[36m%-50s\e[0m" "$icon" "$opt" >&2 235 | else 236 | printf "%s %-50s" "$icon" "$opt" >&2 237 | fi 238 | ((idx++)) 239 | done 240 | 241 | # user key control 242 | case $(_key_input) in 243 | enter) break;; 244 | space) 245 | local found=0; 246 | for item in "${checked[@]}"; do 247 | if [ "$item" == "$selected" ]; then 248 | found=1 249 | break; 250 | fi 251 | done 252 | if [ $found -eq 1 ]; then 253 | checked=( "${checked[@]/$selected}" ) 254 | else 255 | checked+=("${selected}") 256 | fi 257 | ;; 258 | up) selected=$(_decrement_selected "${selected}" "${opts_count}"); ;; 259 | down) selected=$(_increment_selected "${selected}" "${opts_count}"); ;; 260 | esac 261 | done 262 | 263 | # cursor position back to normal 264 | _cursor_to "${lastrow}" 265 | _cursor_blink_on 266 | 267 | IFS="" echo -n "${checked[@]}" 268 | } 269 | 270 | # @description Show password prompt displaying stars for each password character letter typed 271 | # it also allows deleting input 272 | # @arg $1 string Phrase for prompting to text 273 | # @stdout password as written by user 274 | # @stderr Instructions for user 275 | # @example 276 | # # Password prompt with custom validation 277 | # validate_password() { if [ ${#1} -lt 10 ];then echo "Password needs to be at least 10 characters"; exit 1; fi } 278 | # pass=$(with_validate 'password "Enter random password"' validate_password) 279 | # @example 280 | # # Password ith no validation 281 | # pass=$(password "Enter password to use") 282 | password() { 283 | _prompt_text "$1" 284 | echo -en "\033[36m" >&2 285 | local password='' 286 | local IFS= 287 | while _read_stdin -r -s -n1 char; do 288 | # ENTER pressed; output \n and break. 289 | [[ -z "${char}" ]] && { printf '\n' >&2; break; } 290 | # BACKSPACE pressed; remove last character 291 | if [ "${char}" == $'\x7f' ]; then 292 | if [ "${#password}" -gt 0 ]; then 293 | password="${password%?}" 294 | echo -en '\b \b' >&2 295 | fi 296 | else 297 | password+=$char 298 | echo -en '*' >&2 299 | fi 300 | done 301 | echo -en "\e[0m" >&2 302 | echo -n "${password}" 303 | } 304 | 305 | # @description Open default editor ($EDITOR) if none is set falls back to vi 306 | # @arg $1 string Phrase for promptint to text 307 | # @stdout Text as input by user in input 308 | # @stderr Instructions for user 309 | # @example 310 | # # Open default editor 311 | # text=$(editor "Please enter something in the editor") 312 | # echo -e "You wrote:\n${text}" 313 | editor() { 314 | tmpfile=$(mktemp) 315 | _prompt_text "$1" 316 | echo "" >&2 317 | 318 | "${EDITOR:-vi}" "${tmpfile}" >/dev/tty 319 | echo -en "\033[36m" >&2 320 | # shellcheck disable=SC2002 321 | cat "${tmpfile}" | sed -e 's/^/ /' >&2 322 | echo -en "\033[0m" >&2 323 | 324 | cat "${tmpfile}" 325 | } 326 | 327 | # @description Evaluate prompt command with validation, this prompts the user for input till the validation function 328 | # returns with 0 329 | # @arg $1 string Prompt command to evaluate until validation is successful 330 | # @arg $2 function validation callback (this is called once for exit code and once for status code) 331 | # @stdout Value collected by evaluating prompt 332 | # @stderr Instructions for user 333 | # @example 334 | # # Using builtin is present validator 335 | # text=$(with_validate 'input "Please enter something and confirm with enter"' validate_present) 336 | # @example 337 | # # Using custom validator e.g. for password 338 | # validate_password() { if [ ${#1} -lt 10 ];then echo "Password needs to be at least 10 characters"; exit 1; fi } 339 | # pass=$(with_validate 'password "Enter random password"' validate_password) 340 | with_validate() { 341 | while true; do 342 | local val; val="$(eval "$1")" 343 | if ($2 "$val" >/dev/null); then 344 | echo "$val"; 345 | break; 346 | else 347 | show_error "$($2 "$val")"; 348 | fi 349 | done 350 | } 351 | 352 | # @description Display a range dialog that can incremented and decremented using the arrow keys 353 | # @arg $1 string Phrase for prompting to text 354 | # @arg $2 int Minimum selectable value 355 | # @arg $3 int Default selected value 356 | # @arg $4 int Maximum value of the select 357 | # @stdout Selected value using arrow keys 358 | # @stderr Instructions for user 359 | # @example 360 | # # Range with negative min value 361 | # value=$(range -5 0 5) 362 | range() { 363 | local min="$2" 364 | local current="$3" 365 | local max="$4" 366 | local selected="${current}" 367 | 368 | local max_len_current; max_len_current=0 369 | if [[ "${#min}" -gt "${#max}" ]]; then 370 | max_len_current="${#min}" 371 | else 372 | max_len_current="${#max}" 373 | fi 374 | local padding; padding="$(printf "%-${max_len_current}s" "")" 375 | local start_row; start_row=$(_get_cursor_row) 376 | local current_row; current_row=$((start_row - 1)) 377 | 378 | # ensure cursor and input echoing back on upon a ctrl+c during read -s 379 | trap "_cursor_blink_on; stty echo; exit" 2 380 | _cursor_blink_off 381 | 382 | _check_range() { 383 | val=$1 384 | 385 | if [[ "$val" -gt "$max" ]]; then 386 | val=$min 387 | elif [[ "$val" -lt "$min" ]]; then 388 | val=$max 389 | fi 390 | 391 | echo "$val" 392 | } 393 | 394 | while true; do 395 | _prompt_text "$1" 396 | printf "\033[37m%s\033[0m \033[1;90m❮\033[0m \033[36m%s%s\033[0m \033[1;90m❯\033[0m \033[37m%s\033[0m\n" "$min" "${padding:${#selected}}" "$selected" "$max" >&2 397 | case $(_key_input) in 398 | enter) 399 | break; 400 | ;; 401 | left) 402 | selected="$(_check_range $((selected - 1)))"; 403 | ;; 404 | right) 405 | selected="$(_check_range $((selected + 1)))"; 406 | ;; 407 | esac; 408 | _cursor_to "$current_row" 409 | done; 410 | 411 | echo "$selected" 412 | } 413 | 414 | # @description Validate a prompt returned any value 415 | # @arg $1 value to validate 416 | # @stdout error message for user 417 | # @exitcode 0 String is at least 1 character long 418 | # @exitcode 1 There was no input given 419 | # @example 420 | # # text input with validation 421 | # text=$(with_validate 'input "Please enter something and confirm with enter"' validate_present) 422 | validate_present() { 423 | if [ "$1" != "" ]; then return 0; else echo "Please specify the value"; return 1; fi 424 | } 425 | -------------------------------------------------------------------------------- /src/user_feedback.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # @name User-Feedback 3 | # @brief Provides useful colored outputs for user feedback on actions 4 | 5 | # @description Display error message in stderr, prefixed by check emoji 6 | # @arg $1 string Error message to display 7 | # @example 8 | # show_error "Oh snap, that went horribly wrong" 9 | show_error() { 10 | echo -e "\033[91;1m✘ $1\033[0m" >&2 11 | } 12 | 13 | # @description Display success message in stderr, prefixed by cross emoji 14 | # @arg $1 string Success message to display 15 | # @example 16 | # show_success "There it is! World peace." 17 | show_success() { 18 | echo -e "\033[92;1m✔ $1\033[0m" >&2 19 | } 20 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Basic demo of features 4 | # 5 | cd src || exit 2 6 | source main.sh 7 | cd - || exit 2 8 | 9 | # 10 | # Platform 11 | # 12 | echo "You are using the OS '$(detect_os)'" 13 | echo "Opener for tools/links: '$(get_opener)'" 14 | open_link "https://github.com" 15 | 16 | # 17 | # UTILS 18 | # 19 | show_error "Something went wrong" 20 | show_success "There we go" 21 | 22 | # 23 | # LOGGING 24 | # 25 | export LOG_LEVEL="$LOG_DEBUG" 26 | log "$LOG_DEBUG" "Debug message" 27 | log "$LOG_INFO" "Info message" 28 | log "$LOG_WARN" "Warn message" 29 | log "$LOG_ERROR" "Error message" 30 | 31 | # 32 | # PROMPTS 33 | # 34 | 35 | ranged="$(range "foo bar" "-5" 0 5)" 36 | 37 | options=("one" "two" "three" "four" "a" "b" "c" "d" "e") 38 | 39 | validate_password() { 40 | if [ ${#1} -lt 10 ];then 41 | echo "Password needs to be at least 10 characters" 42 | exit 1 43 | fi 44 | } 45 | # Password prompt 46 | pass=$(with_validate 'password "Enter random password"' validate_password) 47 | 48 | # Checkbox 49 | checked=$(checkbox "Select one or more items" "${options[@]}") 50 | 51 | # text input with validation 52 | text=$(with_validate 'input "Please enter something and confirm with enter"' validate_present) 53 | 54 | # Select 55 | option=$(list "Select one item" "${options[@]}") 56 | 57 | # Confirm 58 | confirmed=$(confirm "Should it be?") 59 | 60 | # Open editor 61 | editor=$(editor "Please enter something in the editor") 62 | 63 | # Print results 64 | echo " 65 | --- 66 | password: 67 | $pass 68 | input: 69 | $text 70 | select: 71 | $option 72 | checkbox: 73 | $checked 74 | confirm: 75 | $confirmed 76 | range: 77 | $ranged 78 | editor: 79 | $editor 80 | " 81 | --------------------------------------------------------------------------------