├── .githooks └── pre-commit ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── bash_unit.yml │ └── shellcheck.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── VERSION.md ├── assets ├── bashew.jpg └── bashful.md ├── bashew ├── bashew.sh ├── doc ├── features.md ├── functions.md └── prep_dotenv.awk ├── template ├── .env.example ├── .github │ └── workflows │ │ ├── bash_unit.yml │ │ └── shellcheck.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── VERSION.md ├── bitbucket-pipelines.yml └── script.sh └── tests ├── disabled └── disabled_sourced.sh ├── run_tests.sh ├── test_bashew.sh ├── test_bashew_init.sh ├── test_basic.sh └── test_functions.sh /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # cf https://gist.github.com/wookietreiber/3bf8621274caafed543fca6a3feab284#file-git-hook-shellcheck-sh 3 | set -efu -o pipefail 4 | 5 | # returns staged files 6 | function staged.files { 7 | if git rev-parse --verify HEAD &> /dev/null 8 | then 9 | against=HEAD 10 | else 11 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 12 | fi 13 | 14 | git diff-index --cached --name-only $against | 15 | grep \ 16 | -e '\.bashrc' \ 17 | -e '\.zshrc' \ 18 | -e '\.bash_profile' \ 19 | -e '\.sh$' || true 20 | } 21 | 22 | declare -a paths=() 23 | 24 | while read -r file 25 | do 26 | [[ -f "$file" ]] && paths[${#paths[@]}+1]=$file 27 | done < <(staged.files) 28 | 29 | if [[ ${#paths[@]} -eq 0 ]] 30 | then 31 | echo -e '\e[1m\e[32m[info]\e[0m \e[1mshellcheck\e[0m no files in stage' 32 | else 33 | if ! command -v shellcheck &> /dev/null 34 | then 35 | echo -e '\e[1m\e[33m[warn]\e[0m \e[1mshellcheck\e[0m is not installed' >&2 36 | exit 0 37 | fi 38 | 39 | stage=$(mktemp -d) 40 | trap 'rm -r $stage' EXIT INT TERM 41 | 42 | for path in "${paths[@]}" 43 | do 44 | file="$stage/$path" 45 | 46 | mkdir -p "$(dirname "$file")" 47 | git show ":$path" > "$file" 48 | done 49 | 50 | pushd "$stage" &> /dev/null 51 | shellcheck "${paths[@]}" 52 | popd &> /dev/null 53 | fi -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Call script with parameters ... 16 | 2. Type ... 17 | 3. Wait for ... seconds 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. MacOS] 28 | - bash version (bash --version) 29 | - Script version 30 | - copy/paste output of script.sh -v 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/bash_unit.yml: -------------------------------------------------------------------------------- 1 | name: bash_unit CI 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | 8 | jobs: 9 | ubuntu: 10 | runs-on: ubuntu-latest 11 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Check for basic execution 17 | run: ls ./*.sh | xargs bash 18 | 19 | - name: Unit testing with bash_unit 20 | run: curl -s https://raw.githubusercontent.com/pgrange/bash_unit/master/bash_unit -o tests/bash_unit && chmod +x tests/bash_unit && tests/bash_unit tests/test_* 21 | 22 | macos: 23 | runs-on: macos-latest 24 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]') && contains(toJSON(github.event.commits.*.message), '[macos]')" 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: install MacOS dependencies with brew install 30 | run: brew install gawk 31 | 32 | - name: Check for basic execution 33 | run: ls ./*.sh | xargs bash 34 | 35 | - name: Unit testing with bash_unit 36 | run: curl -s https://raw.githubusercontent.com/pgrange/bash_unit/master/bash_unit -o tests/bash_unit && chmod +x tests/bash_unit && tests/bash_unit tests/test_* 37 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | name: Shellcheck CI 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | 8 | jobs: 9 | shellcheck: 10 | runs-on: ubuntu-latest 11 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Check for code quality errors 17 | run: ls ./*.sh | xargs shellcheck 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | log/ 2 | .idea/ 3 | .template* 4 | .tmp 5 | .bashew* 6 | log 7 | /temp/ 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ![Changelog v1.0.0](https://img.shields.io/badge/CHANGELOG-v1.0.0-orange) 2 | # CHANGELOG 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | - put requirements/dependencies in separate file 10 | 11 | ## [1.20.2] - 2023-04-22 12 | ### Added/changed 13 | - MOD: README.md script.sh 14 | - shellcheck warnings/cleanup bashew 15 | 16 | ## [1.15.3] - 2021-04-11 17 | ### Added/changed 18 | - detect unicode in testing 19 | - check unicode support for ubuntu 20 | - test CI 21 | 22 | ## [1.13.8] - 2021-02-11 23 | ### Added/changed 24 | - added features.md 25 | - MOD: README.md 26 | 27 | ## [1.13.6] - 2021-01-31 28 | ### Added/changed 29 | - Update issue templates 30 | - Merge branch 'master' of github.com:pforret/bashew 31 | - ADD: CONTRIBUTING.md 32 | 33 | ## [1.1.0] - 2020-08-09 34 | ### Added/changed 35 | - Implement 'bashew.sh script' 36 | - Implement 'bashew.sh project' 37 | - test basher install 38 | - random script name generator 39 | - fix bash_unit and shellcheck warnings 40 | 41 | ## [0.0.1] - 2020-08-06 42 | ### Added 43 | - based on pforret/bash-boilerplate 44 | - split customisation script and actual template script 45 | - support repos based on original template script 46 | - support creating stand-alone scripts 47 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at peter@forret.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## How do I... 4 | 5 | * [Use This Guide](#introduction)? 6 | * Ask or Say Something? 🤔🐛😱 7 | * [Request Support](#request-support) 8 | * [Report an Error or Bug](#report-an-error-or-bug) 9 | * [Request a Feature](#request-a-feature) 10 | * Make Something? 🤓👩🏽‍💻📜🍳 11 | * [Project Setup](#project-setup) 12 | * [Contribute Documentation](#contribute-documentation) 13 | * [Contribute Code](#contribute-code) 14 | * Manage Something ✅🙆🏼💃👔 15 | * [Provide Support on Issues](#provide-support-on-issues) 16 | * [Label Issues](#label-issues) 17 | * [Clean Up Issues and PRs](#clean-up-issues-and-prs) 18 | * [Review Pull Requests](#review-pull-requests) 19 | * [Merge Pull Requests](#merge-pull-requests) 20 | * [Tag a Release](#tag-a-release) 21 | * [Join the Project Team](#join-the-project-team) 22 | * Add a Guide Like This One [To My Project](#attribution)? 🤖😻👻 23 | 24 | ## Introduction 25 | 26 | Thank you so much for your interest in contributing!. All types of contributions are encouraged and valued. See the [table of contents](#toc) for different ways to help and details about how this project handles them!📝 27 | 28 | Please make sure to read the relevant section before making your contribution! It will make it a lot easier for us maintainers to make the most of it and smooth out the experience for all involved. 💚 29 | 30 | ## Request Support 31 | 32 | If you have a question about this project, how to use it, or just need clarification about something: 33 | 34 | * Open an Issue at https://github.com/pforret/bashew/issues 35 | * Provide as much context as you can about what you're running into. 36 | * Provide project and platform versions (bash, basher, etc), depending on what seems relevant. If not, please be ready to provide that information if maintainers ask for it. 37 | 38 | Once it's filed: 39 | 40 | * The project team will [label the issue](#label-issues). 41 | * Someone will try to have a response soon. 42 | * If you or the maintainers don't respond to an issue for 30 days, the [issue will be closed](#clean-up-issues-and-prs). 43 | If you want to come back to it, reply (once, please), and we'll reopen the existing issue. Please avoid filing new issues as extensions of one you already made. 44 | 45 | ## Report an Error or Bug 46 | 47 | If you run into an error or bug with the project: 48 | 49 | * Open an Issue at https://github.com/pforret/bashew/issues 50 | * Include *reproduction steps* that someone else can follow to recreate the bug or error on their own. 51 | * Provide project and platform versions (OS, bash, basher, etc), depending on what seems relevant. If not, please be ready to provide that information if maintainers ask for it. 52 | 53 | Once it's filed: 54 | 55 | * The project team will [label the issue](#label-issues). 56 | * A team member will try to reproduce the issue with your provided steps. If there are no repro steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. 57 | * If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#contribute-code). 58 | * If you or the maintainers don't respond to an issue for 30 days, the [issue will be closed](#clean-up-issues-and-prs). If you want to come back to it, reply (once, please), and we'll reopen the existing issue. Please avoid filing new issues as extensions of one you already made. 59 | * `critical` issues may be left open, depending on perceived immediacy and severity, even past the 30 day deadline. 60 | 61 | ## Request a Feature 62 | 63 | If the project doesn't do something you need or want it to do: 64 | 65 | * Open an Issue at https://github.com/pforret/bashew/issues 66 | * Provide as much context as you can about what you're running into. 67 | * Please be clear about why existing features and alternatives would not work for you. 68 | 69 | Once it's filed: 70 | 71 | * The project team will [label the issue](#label-issues). 72 | * The project team will evaluate the feature request, possibly asking you more questions to understand its purpose and any relevant requirements. If the issue is closed, the team will convey their reasoning and suggest an alternative path forward. 73 | * If the feature request is accepted, it will be marked for implementation with `feature-accepted`, which can then be done by either by a core team member or by anyone in the community who wants to [contribute code](#contribute-code). 74 | 75 | Note: The team is unlikely to be able to accept every single feature request that is filed. Please understand if they need to say no. 76 | 77 | ## Project Setup 78 | 79 | So you want to contribute some code! That's great! This project uses GitHub Pull Requests to manage contributions, so [read up on how to fork a GitHub project and file a PR](https://guides.github.com/activities/forking) if you've never done it before. 80 | 81 | If this seems like a lot or you aren't able to do all this setup, you might also be able to [edit the files directly](https://help.github.com/articles/editing-files-in-another-user-s-repository/) without having to do any of this setup. Yes, [even code](#contribute-code). 82 | 83 | If you want to go the usual route and run the project locally, though: 84 | 85 | * [Fork the project](https://guides.github.com/activities/forking/#fork) 86 | 87 | Then in your terminal: 88 | * `cd path/to/your/clone` 89 | 90 | And you should be ready to go! 91 | 92 | ## Contribute Documentation 93 | 94 | Documentation is a super important, critical part of this project. Docs are how we keep track of what we're doing, how, and why. It's how we stay on the same page about our policies. And it's how we tell others everything they need in order to be able to use this project -- or contribute to it. So thank you in advance. 95 | 96 | Documentation contributions of any size are welcome! Feel free to file a PR even if you're just rewording a sentence to be more clear, or fixing a spelling mistake! 97 | 98 | To contribute documentation: 99 | 100 | * [Set up the project](#project-setup). 101 | * Edit or add any relevant documentation. 102 | * Make sure your changes are formatted correctly and consistently with the rest of the documentation. 103 | * Re-read what you wrote, and run a spellchecker on it to make sure you didn't miss anything. 104 | * Write clear, concise commit message(s) using [conventional-changelog format](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md). Documentation commits should use `docs(): `. 105 | * Go to https://github.com/pforret/bashew/pulls and open a new pull request with your changes. 106 | * If your PR is connected to an open issue, add a line in your PR's description that says `Fixes: #123`, where `#123` is the number of the issue you're fixing. 107 | 108 | Once you've filed the PR: 109 | 110 | * One or more maintainers will use GitHub's review feature to review your PR. 111 | * If the maintainer asks for any changes, edit your changes, push, and ask for another review. 112 | * If the maintainer decides to pass on your PR, they will thank you for the contribution and explain why they won't be accepting the changes. That's ok! We still really appreciate you taking the time to do it, and we don't take that lightly. 💚 113 | * If your PR gets accepted, it will be marked as such, and merged into the `latest` branch soon after. Your contribution will be distributed to the masses next time the maintainers [tag a release](#tag-a-release) 114 | 115 | ## Contribute Code 116 | 117 | We like code commits a lot! They're super handy, and they keep the project going and doing the work it needs to do to be useful to others. 118 | 119 | Code contributions of just about any size are acceptable! 120 | 121 | The main difference between code contributions and documentation contributions is that contributing code requires inclusion of relevant tests for the code being added or changed. Contributions without accompanying tests will be held off until a test is added, unless the maintainers consider the specific tests to be either impossible, or way too much of a burden for such a contribution. 122 | 123 | To contribute code: 124 | 125 | * [Set up the project](#project-setup). 126 | * Make any necessary changes to the source code. 127 | * Include any [additional documentation](#contribute-documentation) the changes might need. 128 | * Write tests that verify that your contribution works as expected. 129 | * Write clear, concise commit message(s) using [conventional-changelog format](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md). 130 | * Dependency updates, additions, or removals must be in individual commits, and the message must use the format: `(deps): PKG@VERSION`, where `` is any of the usual `conventional-changelog` prefixes, at your discretion. 131 | * Go to https://github.com/pforret/bashew/pulls and open a new pull request with your changes. 132 | * If your PR is connected to an open issue, add a line in your PR's description that says `Fixes: #123`, where `#123` is the number of the issue you're fixing. 133 | 134 | Once you've filed the PR: 135 | 136 | * Barring special circumstances, maintainers will not review PRs until all checks pass (Travis, AppVeyor, etc). 137 | * One or more maintainers will use GitHub's review feature to review your PR. 138 | * If the maintainer asks for any changes, edit your changes, push, and ask for another review. Additional tags (such as `needs-tests`) will be added depending on the review. 139 | * If the maintainer decides to pass on your PR, they will thank you for the contribution and explain why they won't be accepting the changes. That's ok! We still really appreciate you taking the time to do it, and we don't take that lightly. 💚 140 | * If your PR gets accepted, it will be marked as such, and merged into the `latest` branch soon after. Your contribution will be distributed to the masses next time the maintainers [tag a release](#tag-a-release) 141 | 142 | ## Provide Support on Issues 143 | 144 | Helping out other users with their questions is a really awesome way of contributing to any community. It's not uncommon for most of the issues on an open source projects being support-related questions by users trying to understand something they ran into, or find their way around a known bug. 145 | 146 | Sometimes, the `support` label will be added to things that turn out to actually be other things, like bugs or feature requests. In that case, suss out the details with the person who filed the original issue, add a comment explaining what the bug is, and change the label from `support` to `bug` or `feature`. If you can't do this yourself, @mention a maintainer so they can do it. 147 | 148 | In order to help other folks out with their questions: 149 | 150 | * Go to the issue tracker and [filter open issues by the `support` label](https://github.com/pforret/bashew/issues?q=is%3Aopen+is%3Aissue+label%3Asupport). 151 | * Read through the list until you find something that you're familiar enough with to give an answer to. 152 | * Respond to the issue with whatever details are needed to clarify the question, or get more details about what's going on. 153 | * Once the discussion wraps up and things are clarified, either close the issue, or ask the original issue filer (or a maintainer) to close it for you. 154 | 155 | Some notes on picking up support issues: 156 | 157 | * Avoid responding to issues you don't know you can answer accurately. 158 | * As much as possible, try to refer to past issues with accepted answers. Link to them from your replies with the `#123` format. 159 | * Be kind and patient with users -- often, folks who have run into confusing things might be upset or impatient. This is ok. Try to understand where they're coming from, and if you're too uncomfortable with the tone, feel free to stay away or withdraw from the issue. (note: if the user is outright hostile or is violating the CoC, [refer to the Code of Conduct](CODE_OF_CONDUCT.md) to resolve the conflict). 160 | 161 | ## Label Issues 162 | 163 | One of the most important tasks in handling issues is labeling them usefully and accurately. All other tasks involving issues ultimately rely on the issue being classified in such a way that relevant parties looking to do their own tasks can find them quickly and easily. 164 | 165 | In order to label issues, [open up the list of unlabeled issues](https://github.com/pforret/bashew/issues?q=is%3Aopen+is%3Aissue+no%3Alabel) and, **from newest to oldest**, read through each one and apply issue labels according to the table below. If you're unsure about what label to apply, skip the issue and try the next one: don't feel obligated to label each and every issue yourself! 166 | 167 | Label | Apply When | Notes 168 | --- | --- | --- 169 | `bug` | Cases where the code (or documentation) is behaving in a way it wasn't intended to. | If something is happening that surprises the *user* but does not go against the way the code is designed, it should use the `enhancement` label. 170 | `critical` | Added to `bug` issues if the problem described makes the code completely unusable in a common situation. | 171 | `documentation` | Added to issues or pull requests that affect any of the documentation for the project. | Can be combined with other labels, such as `bug` or `enhancement`. 172 | `duplicate` | Added to issues or PRs that refer to the exact same issue as another one that's been previously labeled. | Duplicate issues should be marked and closed right away, with a message referencing the issue it's a duplicate of (with `#123`) 173 | `enhancement` | Added to [feature requests](#request-a-feature), PRs, or documentation issues that are purely additive: the code or docs currently work as expected, but a change is being requested or suggested. | 174 | `help wanted` | Applied by [Committers](#join-the-project-team) to issues and PRs that they would like to get outside help for. Generally, this means it's lower priority for the maintainer team to itself implement, but that the community is encouraged to pick up if they so desire | Never applied on first-pass labeling. 175 | `in-progress` | Applied by [Committers](#join-the-project-team) to PRs that are pending some work before they're ready for review. | The original PR submitter should @mention the team member that applied the label once the PR is complete. 176 | `performance` | This issue or PR is directly related to improving performance. | 177 | `refactor` | Added to issues or PRs that deal with cleaning up or modifying the project for the betterment of it. | 178 | `starter` | Applied by [Committers](#join-the-project-team) to issues that they consider good introductions to the project for people who have not contributed before. These are not necessarily "easy", but rather focused around how much context is necessary in order to understand what needs to be done for this project in particular. | Existing project members are expected to stay away from these unless they increase in priority. 179 | `support` | This issue is either asking a question about how to use the project, clarifying the reason for unexpected behavior, or possibly reporting a `bug` but does not have enough detail yet to determine whether it would count as such. | The label should be switched to `bug` if reliable reproduction steps are provided. Issues primarily with unintended configurations of a user's environment are not considered bugs, even if they cause crashes. 180 | `tests` | This issue or PR either requests or adds primarily tests to the project. | If a PR is pending tests, that will be handled through the [PR review process](#review-pull-requests) 181 | `wontfix` | Labelers may apply this label to issues that clearly have nothing at all to do with the project or are otherwise entirely outside of its scope/sphere of influence. [Committers](#join-the-project-team) may apply this label and close an issue or PR if they decide to pass on an otherwise relevant issue. | The issue or PR should be closed as soon as the label is applied, and a clear explanation provided of why the label was used. Contributors are free to contest the labeling, but the decision ultimately falls on committers as to whether to accept something or not. 182 | 183 | ## Clean Up Issues and PRs 184 | 185 | Issues and PRs can go stale after a while. Maybe they're abandoned. Maybe the team will just plain not have time to address them any time soon. 186 | 187 | In these cases, they should be closed until they're brought up again or the interaction starts over. 188 | 189 | To clean up issues and PRs: 190 | 191 | * Search the issue tracker for issues or PRs, and add the term `updated:<=YYYY-MM-DD`, where the date is 30 days before today. 192 | * Go through each issue *from oldest to newest*, and close them if **all of the following are true**: 193 | * not opened by a maintainer 194 | * not marked as `critical` 195 | * not marked as `starter` or `help wanted` (these might stick around for a while, in general, as they're intended to be available) 196 | * no explicit messages in the comments asking for it to be left open 197 | * does not belong to a milestone 198 | * Leave a message when closing saying "Cleaning up stale issue. Please reopen or ping us if and when you're ready to resume this. See https://github.com/pforret/bashew/blob/latest/CONTRIBUTING.md#clean-up-issues-and-prs for more details." 199 | 200 | ## Review Pull Requests 201 | 202 | While anyone can comment on a PR, add feedback, etc, PRs are only *approved* by team members with Issue Tracker or higher permissions. 203 | 204 | PR reviews use [GitHub's own review feature](https://help.github.com/articles/about-pull-request-reviews/), which manages comments, approval, and review iteration. 205 | 206 | Some notes: 207 | 208 | * You may ask for minor changes ("nitpicks"), but consider whether they are really blockers to merging: try to err on the side of "approve, with comments". 209 | * *ALL PULL REQUESTS* should be covered by a test: either by a previously-failing test, an existing test that covers the entire functionality of the submitted code, or new tests to verify any new/changed behavior. All tests must also pass and follow established conventions. Test coverage should not drop, unless the specific case is considered reasonable by maintainers. 210 | * Please make sure you're familiar with the code or documentation being updated, unless it's a minor change (spellchecking, minor formatting, etc). You may @mention another project member who you think is better suited for the review, but still provide a non-approving review of your own. 211 | * Be extra kind: people who submit code/doc contributions are putting themselves in a pretty vulnerable position, and have put time and care into what they've done (even if that's not obvious to you!) -- always respond with respect, be understanding, but don't feel like you need to sacrifice your standards for their sake, either. Just don't be a jerk about it? 212 | 213 | ## Merge Pull Requests 214 | 215 | Project Team will merge pull requests after review. 216 | 217 | ## Tag A Release 218 | 219 | Project Team will bump tags/version with [setver](https://github.com/pforret/setver) as a patch or minor versionupdate. 220 | Releases are done manually for important changes. 221 | 222 | 223 | ## Join the Project Team 224 | 225 | ### Ways to Join 226 | 227 | There are many ways to contribute! Most of them don't require any official status unless otherwise noted. That said, there's a couple of positions that grant special repository abilities, and this section describes how they're granted and what they do. 228 | 229 | All of the below positions are granted based on the project team's needs, as well as their consensus opinion about whether they would like to work with the person and think that they would fit well into that position. The process is relatively informal, and it's likely that people who express interest in participating can just be granted the permissions they'd like. 230 | 231 | You can spot a collaborator on the repo by looking for the `[Collaborator]` or `[Owner]` tags next to their names. 232 | 233 | Permission | Description 234 | --- | --- 235 | Issue Tracker | Granted to contributors who express a strong interest in spending time on the project's issue tracker. These tasks are mainly [labeling issues](#label-issues), [cleaning up old ones](#clean-up-issues-and-prs), and [reviewing pull requests](#review-pull-requests), as well as all the usual things non-team-member contributors can do. Issue handlers should not merge pull requests, tag releases, or directly commit code themselves: that should still be done through the usual pull request process. Becoming an Issue Handler means the project team trusts you to understand enough of the team's process and context to implement it on the issue tracker. 236 | Committer | Granted to contributors who want to handle the actual pull request merges, tagging new versions, etc. Committers should have a good level of familiarity with the codebase, and enough context to understand the implications of various changes, as well as a good sense of the will and expectations of the project team. 237 | Admin/Owner | Granted to people ultimately responsible for the project, its community, etc. 238 | 239 | ## Attribution 240 | 241 | This guide was generated using the WeAllJS `CONTRIBUTING.md` generator. [Make your own](https://npm.im/weallcontribute)! 242 | 243 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Peter Forret 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Shellcheck CI](https://github.com/pforret/bashew/actions/workflows/shellcheck.yml/badge.svg)](https://github.com/pforret/bashew/actions/workflows/shellcheck.yml) 2 | [![bash_unit CI](https://github.com/pforret/bashew/actions/workflows/bash_unit.yml/badge.svg)](https://github.com/pforret/bashew/actions/workflows/bash_unit.yml) 3 | [![version](https://img.shields.io/github/v/tag/pforret/bashew)](https://github.com/pforret/bashew/tags) 4 | [![version](https://img.shields.io/github/v/release/pforret/bashew)](https://github.com/pforret/bashew/releases) 5 | 6 | Part of [![part of Bashful Scripting network](https://img.shields.io/badge/bashful-scripting-orange)](https://blog.forret.com/portfolio/bashful/) network 7 | / 8 | Install with [![basher install](https://img.shields.io/badge/basher-install-white?logo=gnu-bash&style=flat)](https://www.basher.it/package/) 9 | 10 | # bashew.sh 11 | 12 | ![Bashew Logo](assets/bashew.jpg) 13 | 14 | bash script / project creator 15 | 16 | ## ⏳ TL;DR 17 | 18 | to create a new stand-alone **SCRIPT** (just a xyz.sh script), with option parsing, color output (cf `1.`) 19 | 20 | bashew.sh script 21 | 22 | to create a new standalone script **PROJECT** (in a folder, with README) (cf `2.`) 23 | 24 | bashew.sh project 25 | 26 | to initialize a bashew-based **REPO** with CI/CD you just cloned (cf `3.`) 27 | 28 | bashew init 29 | 30 | ## 🎯 Features 31 | 32 | ### Self-contained 33 | * all code contained in 1 single file (no external dependencies) 34 | * comes with `README.md`, `CHANGELOG.md`, ... markdown files 35 | * edit only `Script:main()` function and subroutines in beginning of script, all template code is at the end of the script 36 | 37 | ### Option parsing 38 | * definition of flags/options/parameters in 1 place only 39 | * automatic creation of usage text based on definition above 40 | * short/long option parsing, based on definition above 41 | * option `--lorem [value]` is available inside the script as `$lorem` 42 | 43 | ### [function library](doc/functions.md) 44 | * `IO:` functions for IO, with intelligent color usage (not when output is piped) (e.g. `IO.success`, `IO.die`) 45 | * `Str:` functions for string manipulation (e.g `Str:lower`, `Str:digest`) 46 | * `Os:` functions for e.g. required program checking (e.g. `Os:require convert imagemagick`) 47 | 48 | ### [batteries included](doc/features.md) 49 | * read multiple `.env` configuration files 50 | * predefined `--quiet` (no output) and `--verbose` (more output) modes 51 | * folder for temporary files (with automatic cleanup) 52 | * folder for log files (with automatic cleanup) 53 | * correct determination of script installation folder (resolve symbolic links) 54 | * easy CI/CD for Github (with shellcheck) 55 | 56 | ## 🔥 Usage 57 | 58 | ```ini 59 | Program: bashew 1.18.2 by peter@forret.com 60 | Updated: May 1 16:49:18 2022 61 | Description: package_description 62 | Usage: bashew [-h] [-q] [-v] [-f] [-l ] [-t ] [-n ] 63 | Flags, options and parameters: 64 | -h|--help : [flag] show usage [default: off] 65 | -q|--quiet : [flag] no output [default: off] 66 | -v|--verbose : [flag] output more [default: off] 67 | -f|--force : [flag] do not ask for confirmation (always yes) [default: off] 68 | -l|--log_dir : [option] folder for debug files [default: /Users/pforret/log/bashew] 69 | -t|--tmp_dir : [option] folder for temp files [default: /tmp/bashew] 70 | -n|--name : [option] name of new script or project 71 | : [parameter] action to perform: script/project/init/update 72 | ``` 73 | 74 | ### 1. create new bash script (without repo) 75 | ```shell 76 | bashew.sh script # will interactively ask for author & script details 77 | bashew.sh -f script # will create new script with random name 78 | bashew.sh -f -n "../list.sh" script # will create new script ../list.sh 79 | ``` 80 | 81 | Example: 82 | ```console 83 | $ bashew script 84 | ⏳ 1. first we need the information of the author 85 | Author full name (pforret) > Peter Forret 86 | Author email (peter@forret.com) > 87 | Author (github) username (pforret) > 88 | ⏳ 2. now we need the path and name of this new script/repo 89 | Script name (./bespoke_bunny.sh) > 90 | ⏳ 3. give some description of what the script should do 91 | Script description (This is my script bespoke_bunny) > process log files 92 | ⏳ Creating script ./bespoke_bunny.sh ... 93 | ./bespoke_bunny.sh 94 | 95 | $ bashew -f script 96 | ⏳ Creating script ./mediums_appease.sh ... 97 | ./mediums_appease.sh 98 | ``` 99 | 100 | ### 2. create new bash project folder/repo (with README.md, CI/CD) 101 | ```console 102 | $ bashew project # will interactively ask for author & script details 103 | or 104 | $ bashew -f project # will create new project with random name 105 | or 106 | $ bashew -f -n "tango" project # will create new project in folder "tango" 107 | ``` 108 | 109 | Example: 110 | ```console 111 | $ bashew -f project 112 | ⏳ Creating project ./bounden_brawled ... 113 | CHANGELOG.md README.md VERSION.md LICENSE .gitignore .env.example bounden_brawled.sh bitbucket-pipelines .github 114 | ✅ next step: 'cd ./bounden_brawled' and start scripting! 115 | ``` 116 | 117 | ### 3. create a bash script repo, with CI/CD, with README, with tests, with versioning ... 118 | 119 | * on [github.com/pforret/bashew](https://github.com/pforret/bashew), click on '**Use this template**' 120 | * then clone your new repo 121 | ```console 122 | $ git clone https://github.com//.git 123 | $ cd 124 | $ ./bashew.sh init # will ask for details and initialise/clean up the repo 125 | ``` 126 | 127 | #### and then, if you have [setver.sh](https://github.com/pforret/setver): 128 | ```console 129 | $ setver push # will commit and push new code 130 | $ setver new patch # will set new version to 0.0.1 131 | $ setver set 1.0.0 # when your first working version is committed 132 | ``` 133 | 134 | ### 4. git clone into new repo 135 | ```console 136 | $ git clone --depth=1 https://github.com/pforret/bashew.git 137 | $ cd 138 | $ ./bashew.sh init # will ask for details and iniialise/clean up the repo 139 | ``` 140 | 141 | ## 🚀 Installation 142 | 143 | * manually 144 | ````console 145 | $ git clone https://github.com/pforret/bashew.git 146 | $ ln -s bashew/bashew.sh /usr/local/bin 147 | ```` 148 | 149 | * or with [basher](https://github.com/basherpm/basher) package manager 150 | [![basher install](https://img.shields.io/badge/basher-install-white?logo=gnu-bash&style=flat)](https://basher.gitparade.com/package/) 151 | 152 | ````console 153 | $ basher install pforret/bashew 154 | ```` 155 | 156 | ## 🦪 Built with Bashew 157 | 158 | * [bumpkeys](https://github.com/pforret/bumpkeys): upgrade your SSH keys for better security 159 | * [crontask](https://github.com/pforret/crontask): call scripts or URLs from a crontab file, with optional logging and webhook upon success/failure 160 | * [jekyll_taxonomy](https://github.com/pforret/jekyll_taxonomy): Generate tag and category pages for Jekyll static sites 161 | * [m1_homebrew](https://github.com/pforret/m1_homebrew): Install homebrew in native mode on Apple MacOS ARM 162 | * [mkdox](https://github.com/pforret/mkdox): create and run Mkdocs Material websites using Docker image 163 | * [netcheck](https://github.com/pforret/netcheck): test network: interfaces, gateway, router, internet 164 | * [note](https://github.com/pforret/note): Manage your notes, todo, ... with this nifty script 165 | * [pa](https://github.com/pforret/pa): like "php artisan" but more intelligent (use optimal PHP version for the project 166 | * [progressbar](https://github.com/pforret/progressbar): Easy, clever progress bar for (bash) scripts 167 | * [rexec](https://github.com/pforret/rexec): repeat a command and be alerted when the output changes 168 | * [saild](https://github.com/pforret/saild): Start up your Laravel Sail dev setup in one go - Docker, Browser, Shell 169 | * [screenshots](https://github.com/pforret/screenshots): Let GitHub automatically make 📸 screenshots of all your websites 170 | * [setver](https://github.com/pforret/setver): Easy semver tool -- get/set git version (one-line superfast git commit) 171 | * [shaml](https://github.com/pforret/shaml): Read YAML files inside bash scripts 172 | * [shini](https://github.com/pforret/shini): Read INI files inside bash scripts 173 | * [shlaunch](https://github.com/pforret/shlaunch): Launch desktop/GUI apps from CLI (e.g. Chrome, PHPStorm, Photoshop ...) 174 | * [shlorem](https://github.com/pforret/shlorem): Lorem Ipsum generator for the command line 175 | * [shmixcloud](https://github.com/pforret/shmixcloud): download Mixcloud shows and add album art to m4a files 176 | * [shoarma](https://github.com/pforret/shoarma): Static Image Site Generator - make e.g. Jekyll posts from folder of images 177 | * [shtext](https://github.com/pforret/shtext): Text manipulation in bash, by always using the fastest method 178 | * [shwiki](https://github.com/pforret/shwiki): Wikipedia CLI in bash 179 | * [shwordle](https://github.com/pforret/shwordle): Wordle-clone with variable # of letters and multiple languages 180 | * [splashmark](https://github.com/pforret/splashmark): download/create (unsplash/pixabay/replicate) pics and resize/add effects/add attribution/watermark 181 | * [teams-cli](https://github.com/cinemapub/teams-cli): Send messages to MS Teams channels from CLI 182 | * [xkcd](https://github.com/pforret/xkcd): View a XKCD comic in your console/TTY 183 | 184 | 185 | ## 🙏 Acknowledgements 186 | 187 | * [bash_unit](https://github.com/pgrange/bash_unit): bash unit testing enterprise edition framework (used for CI/CD) 188 | * [shellcheck](https://github.com/koalaman/shellcheck): a static analysis tool for shell scripts (used for CI/CD) 189 | * [bash-boilerplate (2012)](https://github.com/oxyc/bash-boilerplate) on which I based my [bash-boilerplate (2020)](https://github.com/pforret/bash-boilerplate) which eventually became this [bashew](https://github.com/pforret/bashew) 190 | * Bash documentation from [Google](https://google.github.io/styleguide/shellguide.html), [BashPitfalls](https://mywiki.wooledge.org/BashPitfalls), [Microsoft](https://github.com/microsoft/code-with-engineering-playbook/blob/master/code-reviews/recipes/Bash.md) 191 | 192 | ## 🤔 What's that name? Bashew? 193 | * derived from 'bash new' 194 | * rhymes with cashew 195 | 196 | ## Stargazers over time 197 | 198 | [![Stargazers over time](https://starchart.cc/pforret/bashew.svg)](https://starchart.cc/pforret/bashew) 199 | -------------------------------------------------------------------------------- /VERSION.md: -------------------------------------------------------------------------------- 1 | 1.21.2 2 | -------------------------------------------------------------------------------- /assets/bashew.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pforret/bashew/04aab22dfba76896e75bde5d75376a317def24ac/assets/bashew.jpg -------------------------------------------------------------------------------- /assets/bashful.md: -------------------------------------------------------------------------------- 1 | [![basher install](https://img.shields.io/badge/basher-install-white?logo=gnu-bash&style=flat)](https://basher.gitparade.com/package/) - Basher install script directory 2 | 3 | [![bashew inside](https://img.shields.io/badge/bashew-bash_template-yellow?logo=github&style=flat)](https://github.com/pforret/bashew) - Bash script template 4 | 5 | [![Bashful Thinking Newsletter](https://img.shields.io/badge/bashful-bash_newsletter-blue?logo=minutemailer)](https://www.getrevue.co/profile/bashful) - Bash scripting newsletter 6 | 7 | [![Bashful Thinking Podcast](https://img.shields.io/badge/bashful-bash_podcast-blue?logo=youtube)](https://www.getrevue.co/profile/bashful) - Bash scripting podcast 8 | -------------------------------------------------------------------------------- /bashew: -------------------------------------------------------------------------------- 1 | bashew.sh -------------------------------------------------------------------------------- /bashew.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | script_version="0.0.1" # if there is a VERSION.md in this script's folder, it will take priority for version number 3 | readonly script_author="peter@forret.com" 4 | readonly script_created="2020-08-05" 5 | readonly run_as_root=-1 # run_as_root: 0 = don't check anything / 1 = script MUST run as root / -1 = script MAY NOT run as root 6 | 7 | list_options() { 8 | echo -n " 9 | #commented lines will be filtered 10 | flag|h|help|show usage 11 | flag|q|quiet|no output 12 | flag|v|verbose|output more 13 | flag|f|force|do not ask for confirmation (always yes) 14 | option|l|log_dir|folder for IO:debug files |$HOME/log/$script_prefix 15 | option|t|tmp_dir|folder for temp files|/tmp/$script_prefix 16 | option|n|name|name of new script or project 17 | param|1|action|action to perform: script/project/init/update 18 | " | grep -v '^#' 19 | } 20 | 21 | list_examples() { 22 | echo -n " 23 | $script_basename script : create new (stand-alone) script (interactive) 24 | $script_basename project : create new bash script repo (interactive) 25 | $script_basename init : initialize this repo as a new project (when generated from the 'bashew' template repo) 26 | $script_basename update : update $script_basename to latest version (git pull) 27 | " | grep -v '^$' 28 | } 29 | ## Put your helper scripts here 30 | get_author_data() { 31 | # $1 = proposed script/project name 32 | 33 | ## always have something as author data 34 | guess_fullname="$(whoami)" 35 | guess_username="$guess_fullname" 36 | guess_email="$guess_fullname@$(hostname)" 37 | 38 | # if there is prior data, use that 39 | [[ -n ${BASHEW_AUTHOR_FULLNAME:-} ]] && guess_fullname="$BASHEW_AUTHOR_FULLNAME" 40 | [[ -n ${BASHEW_AUTHOR_EMAIL:-} ]] && guess_email="$BASHEW_AUTHOR_EMAIL" 41 | [[ -n ${BASHEW_AUTHOR_USERNAME:-} ]] && guess_username="$BASHEW_AUTHOR_USERNAME" 42 | 43 | # if there is git config data, use that 44 | # shellcheck disable=SC2154 45 | if [[ -n "$git_repo_root" ]]; then 46 | guess_fullname=$(git config user.name) 47 | guess_email=$(git config user.email) 48 | guess_username=$(git config remote.origin.url | cut -d: -f2) 49 | # git@github.com:pforret/bashew.git => pforret/bashew.git 50 | guess_username=$(dirname "$guess_username") 51 | # pforret/bashew.git => pforret 52 | guess_username=$(basename "$guess_username") 53 | fi 54 | 55 | if ((force)); then 56 | author_fullname="$guess_fullname" 57 | author_email="$guess_email" 58 | author_username="$guess_username" 59 | new_name="$1" 60 | clean_name=$(basename "$new_name" .sh) 61 | new_description="This is my script $clean_name" 62 | else 63 | IO:announce "1. first we need the information of the author" 64 | author_fullname=$(ask "Author full name " "$guess_fullname") 65 | author_email=$(ask "Author email " "$guess_email") 66 | author_username=$(ask "Author (github) username" "$guess_username") 67 | export BASHEW_AUTHOR_FULLNAME="$author_fullname" 68 | export BASHEW_AUTHOR_EMAIL="$author_email" 69 | export BASHEW_AUTHOR_USERNAME="$author_username" 70 | 71 | IO:announce "2. now we need the path and name of this new script/repo" 72 | new_name=$(ask "Script name" "$1") 73 | 74 | IO:announce "3. give some description of what the script should do" 75 | clean_name=$(basename "$new_name" .sh) 76 | new_description=$(ask "Script description" "This is my script $clean_name") 77 | fi 78 | } 79 | 80 | copy_and_replace() { 81 | local input="$1" 82 | local output="$2" 83 | 84 | if [[ ! -f "$input" ]]; then 85 | return 0 86 | fi 87 | awk \ 88 | -v author_fullname="$author_fullname" \ 89 | -v author_username="$author_username" \ 90 | -v author_email="$author_email" \ 91 | -v package_name="$clean_name" \ 92 | -v package_description="$new_description" \ 93 | -v meta_today="$execution_day" \ 94 | -v meta_year="$execution_year" \ 95 | -v bashew_version="$script_version" \ 96 | '{ 97 | gsub(/author_name/,author_fullname); 98 | gsub(/author_username/,author_username); 99 | gsub(/author@email.com/,author_email); 100 | gsub(/package_name/,package_name); 101 | gsub(/package_description/,package_description); 102 | gsub(/meta_today/,meta_today); 103 | gsub(/meta_year/,meta_year); 104 | gsub(/bashew_version/,bashew_version); 105 | print; 106 | }' \ 107 | < "$input" \ 108 | > "$output" 109 | } 110 | 111 | random_word() { 112 | ( 113 | if aspell -v > /dev/null 2>&1; then 114 | aspell -d en dump master | aspell -l en expand 115 | elif [[ -f /usr/share/dict/words ]]; then 116 | # works on MacOS 117 | cat /usr/share/dict/words 118 | elif [[ -f /usr/dict/words ]]; then 119 | cat /usr/dict/words 120 | else 121 | printf 'zero,one,two,three,four,five,six,seven,eight,nine,ten,alfa,bravo,charlie,delta,echo,foxtrot,golf,hotel,india,juliet,kilo,lima,mike,november,oscar,papa,quebec,romeo,sierra,tango,uniform,victor,whiskey,xray,yankee,zulu%.0s' {1..3000} | 122 | tr ',' "\n" 123 | fi 124 | ) | 125 | awk 'length($1) > 2 && length($1) < 8 {print}' | 126 | grep -v "'" | 127 | grep -v " " | 128 | awk "NR == $RANDOM {print tolower(\$0)}" 129 | } 130 | 131 | delete_stuff() { 132 | if [[ -d "$1" ]]; then 133 | IO:debug "Delete folder [$1]" 134 | rm -fr "$1" 135 | fi 136 | if [[ -f "$1" ]]; then 137 | IO:debug "Delete file [$1]" 138 | rm "$1" 139 | fi 140 | } 141 | 142 | main() { 143 | IO:debug "Program: $script_basename $script_version" 144 | IO:debug "Updated: $script_modified" 145 | IO:debug "Run as : $USER@$HOSTNAME" 146 | # add programs you need in your script here, like tar, wget, ffmpeg, rsync ... 147 | require_binary tput 148 | require_binary uname 149 | require_binary git 150 | local model="script" 151 | 152 | # shellcheck disable=SC2154 153 | case "${action,,}" in 154 | script | new) 155 | if [[ -n "${name:-}" ]] && [[ ! "$name" == " " ]]; then 156 | IO:debug "Using [$name] as name" 157 | get_author_data "$name" 158 | else 159 | local random_name 160 | random_name="$(random_word)_$(random_word).sh" 161 | IO:debug "Using [$random_name] as name" 162 | get_author_data "./$random_name" 163 | fi 164 | IO:announce "Creating script $new_name ..." 165 | # shellcheck disable=SC2154 166 | copy_and_replace "$script_install_folder/template/$model.sh" "$new_name" 167 | chmod +x "$new_name" 168 | echo "$new_name" 169 | ;; 170 | 171 | project) 172 | if [[ -n "${name:-}" ]] && [[ ! "$name" == " " ]]; then 173 | get_author_data "$name" 174 | else 175 | random_name="$(random_word)_$(random_word)" 176 | get_author_data "./$random_name" 177 | fi 178 | if [[ ! -d "$new_name" ]]; then 179 | IO:announce "Creating project $new_name ..." 180 | mkdir "$new_name" 181 | template_folder="$script_install_folder/template" 182 | ## first do all files that can change 183 | for file in "$template_folder"/*.md "$template_folder/LICENSE" "$template_folder"/.gitignore "$template_folder"/.env.example; do 184 | bfile=$(basename "$file") 185 | ((quiet)) || echo -n "$bfile " 186 | new_file="$new_name/$bfile" 187 | copy_and_replace "$file" "$new_file" 188 | done 189 | ((quiet)) || echo -n "$clean_name.sh " 190 | copy_and_replace "$template_folder/$model.sh" "$new_name/$clean_name.sh" 191 | chmod +x "$new_name/$clean_name.sh" 192 | ## now the CI/CD files 193 | if [[ -f "$template_folder/bitbucket-pipelines.yml" ]]; then 194 | ((quiet)) || echo -n "bitbucket-pipelines " 195 | cp "$template_folder/bitbucket-pipelines.yml" "$new_name/" 196 | fi 197 | if [[ -d "$template_folder/.github" ]]; then 198 | ((quiet)) || echo -n ".github " 199 | cp -r "$template_folder/.github" "$new_name/.github" 200 | fi 201 | 202 | ((quiet)) || echo " " 203 | if confirm "Do you want to 'git init' the new project?"; then 204 | (pushd "$new_name" && git init && git add . && popd || return) > /dev/null 2>&1 205 | fi 206 | IO:success "next step: 'cd $new_name' and start scripting!" 207 | else 208 | IO:alert "Folder [$new_name] already exists, cannot make a new project there" 209 | fi 210 | ;; 211 | 212 | init) 213 | repo_name=$(basename "$script_install_folder") 214 | [[ "$repo_name" == "bashew" ]] && IO:die "You can only run the '$script_basename init' of a *new* repo, derived from the bashew template on Github." 215 | [[ ! -d ".git" ]] && IO:die "You can only run '$script_basename init' in the root of your repo" 216 | [[ ! -d "template" ]] && IO:die "The 'template' folder seems to be missing, are you sure this repo is freshly cloned from pforret/bashew?" 217 | [[ ! -f "$script_install_folder/template/$model.sh" ]] && IO:die "$model.sh is not a valid template" 218 | new_name="$repo_name.sh" 219 | get_author_data "./$new_name" 220 | IO:announce "Creating script $new_name ..." 221 | # shellcheck disable=SC2154 222 | for file in template/*.md template/LICENSE template/.gitignore template/.gitignore; do 223 | bfile=$(basename "$file") 224 | ((quiet)) || echo -n "$bfile " 225 | new_file="./$bfile" 226 | rm -f "$new_file" 227 | copy_and_replace "$file" "$new_file" 228 | done 229 | copy_and_replace "$script_install_folder/template/$model.sh" "$new_name" 230 | chmod +x "$new_name" 231 | git add "$new_name" 232 | alt_dir=$(dirname "$new_name") 233 | alt_base=$(basename "$new_name" .sh) 234 | alt_name="$alt_dir/$alt_base" 235 | if [[ ! "$alt_name" == "$new_name" ]]; then 236 | # create a "do_this" alias for "do_this.sh" 237 | ln -s "$new_name" "$alt_name" 238 | git add "$alt_name" 239 | fi 240 | IO:announce "Now cleaning up unnecessary bashew files ..." 241 | delete_stuff template 242 | delete_stuff tests/disabled 243 | delete_stuff tests/test_bashew.sh 244 | delete_stuff tests/test_bashew_init.sh 245 | delete_stuff tests/test_functions.sh 246 | delete_stuff assets 247 | delete_stuff .tmp 248 | delete_stuff log 249 | delete_stuff doc 250 | IO:debug "Delete script [bashew.sh] ..." 251 | ( 252 | sleep 1 253 | rm -f bashew.sh bashew 254 | ) & # delete will happen after the script is finished 255 | IO:success "script $new_name created!" 256 | IO:success "now do: ${col_ylw}git commit -a -m 'after bashew init' && git push${col_reset}" 257 | IO:print "tip: install ${col_ylw}basher${col_reset} and ${col_ylw}pforret/setver${col_reset} for easy bash script version management" 258 | ;; 259 | 260 | update) 261 | pushd "$script_install_folder" || IO:die "No access to folder [$script_install_folder]" 262 | git pull || IO:die "Cannot update with git" 263 | # shellcheck disable=SC2164 264 | popd 265 | ;; 266 | 267 | check | env) 268 | ## leave this default action, it will make it easier to test your script 269 | #TIP: use «$script_prefix check» to check if this script is ready to execute and what values the options/flags are 270 | #TIP:> $script_prefix check 271 | #TIP: use «$script_prefix env» to generate an example .env file 272 | #TIP:> $script_prefix env > .env 273 | check_script_settings 274 | ;; 275 | 276 | debug) 277 | IO:print "print_with_out=yes" 278 | IO:debug "print_with_log=yes" 279 | IO:announce "print_with_IO:announce=yes" 280 | IO:success "print_with_success=yes" 281 | IO:progress "print_with_progress=yes" 282 | echo "" 283 | IO:alert "print_with_alert=yes" 284 | 285 | local hash3 hash6 286 | hash3=$(echo "1234567890" | hash 3) 287 | hash6=$(echo "1234567890" | hash) 288 | IO:print "hash3=$hash3" 289 | IO:print "hash6=$hash6" 290 | IO:print "script_basename=$script_basename" 291 | IO:print "script_author=$script_author" 292 | IO:print "escape1 = $(escape "/forward/slash")" 293 | IO:print "escape2 = $(escape '\backward\slash')" 294 | IO:print "lowercase = $(lower_case 'AbCdEfGhIjKlMnÔû')" 295 | IO:print "uppercase = $(upper_case 'AbCdEfGhIjKlMnÔû')" 296 | IO:print "slugify = $(slugify 'AbCdEfGhIjKlMnÔû')" 297 | # shellcheck disable=SC2015 298 | is_set "$force" && IO:print "force=$force (true)" || IO:print "force=$force (false)" 299 | ;; 300 | 301 | *) 302 | IO:die "param [$action] not recognized" 303 | ;; 304 | esac 305 | } 306 | 307 | ##################################################################### 308 | ################### DO NOT MODIFY BELOW THIS LINE ################### 309 | ##################################################################### 310 | 311 | # set strict mode - via http://redsymbol.net/articles/unofficial-bash-strict-mode/ 312 | # removed -e because it made basic [[ testing ]] difficult 313 | set -uo pipefail 314 | IFS=$'\n\t' 315 | hash() { 316 | length=${1:-6} 317 | if [[ -n $(command -v md5sum) ]]; then 318 | # regular linux 319 | md5sum | cut -c1-"$length" 320 | else 321 | # macos 322 | md5 | cut -c1-"$length" 323 | fi 324 | } 325 | 326 | force=0 327 | help=0 328 | verbose=0 329 | #to enable verbose even before option parsing 330 | [[ $# -gt 0 ]] && [[ $1 == "-v" ]] && verbose=1 331 | quiet=0 332 | #to enable quiet even before option parsing 333 | [[ $# -gt 0 ]] && [[ $1 == "-q" ]] && quiet=1 334 | 335 | ### stdout/stderr output 336 | initialise_output() { 337 | [[ "${BASH_SOURCE[0]:-}" != "${0}" ]] && sourced=1 || sourced=0 338 | [[ -t 1 ]] && piped=0 || piped=1 # detect if output is piped 339 | if [[ $piped -eq 0 ]]; then 340 | col_reset="\033[0m" 341 | col_red="\033[1;31m" 342 | col_grn="\033[1;32m" 343 | col_ylw="\033[1;33m" 344 | else 345 | col_reset="" 346 | col_red="" 347 | col_grn="" 348 | col_ylw="" 349 | fi 350 | 351 | [[ $(echo -e '\xe2\x82\xac') == '€' ]] && unicode=1 || unicode=0 # detect if unicode is supported 352 | if [[ $unicode -gt 0 ]]; then 353 | char_succ="✅" 354 | char_fail="⛔" 355 | char_alrt="✴️" 356 | char_wait="⏳" 357 | info_icon="🌼" 358 | config_icon="🌱" 359 | clean_icon="🧽" 360 | require_icon="🔌" 361 | else 362 | char_succ="OK " 363 | char_fail="!! " 364 | char_alrt="?? " 365 | char_wait="..." 366 | info_icon="(i)" 367 | config_icon="[c]" 368 | clean_icon="[c]" 369 | require_icon="[r]" 370 | fi 371 | error_prefix="${col_red}>${col_reset}" 372 | } 373 | 374 | IO:print() { ((quiet)) && true || printf '%b\n' "$*"; } 375 | IO:debug() { ((verbose)) && IO:print "${col_ylw}# $* ${col_reset}" >&2; return 0;} 376 | IO:die() { IO:print "${col_red}${char_fail} $script_basename${col_reset}: $*" >&2 && tput bel && safe_exit; } 377 | IO:alert() { IO:print "${col_red}${char_alrt}${col_reset}: $*" >&2; } 378 | IO:success() { IO:print "${col_grn}${char_succ}${col_reset} $*"; } 379 | IO:announce() { 380 | IO:print "${col_grn}${char_wait}${col_reset} $*" 381 | sleep 1 382 | } 383 | IO:progress() { 384 | ((quiet)) || ( 385 | local screen_width 386 | screen_width=$(tput cols 2> /dev/null || echo 80) 387 | local rest_of_line 388 | rest_of_line=$((screen_width - 5)) 389 | 390 | if ((piped)); then 391 | IO:print "$*" >&2 392 | else 393 | printf "... %-${rest_of_line}b\r" "$* " >&2 394 | fi 395 | ) 396 | } 397 | 398 | log_to_file() { [[ -n ${log_file:-} ]] && echo "$(date '+%H:%M:%S') | $*" >> "$log_file"; } 399 | 400 | ### string processing 401 | lower_case() { echo "$*" | tr '[:upper:]' '[:lower:]'; } 402 | upper_case() { echo "$*" | tr '[:lower:]' '[:upper:]'; } 403 | escape() { echo "$*" | sed 's/\//\\\//g'; } 404 | is_set() { [[ "$1" -gt 0 ]]; } 405 | 406 | slugify() { 407 | # slugify 408 | # slugify "Jack, Jill & Clémence LTD" => jack-jill-clemence-ltd 409 | # slugify "Jack, Jill & Clémence LTD" "_" => jack_jill_clemence_ltd 410 | separator="${2:-}" 411 | [[ -z "$separator" ]] && separator="-" 412 | # shellcheck disable=SC2020 413 | echo "$1" | 414 | tr '[:upper:]' '[:lower:]' | 415 | tr 'àáâäæãåāçćčèéêëēėęîïííīįìłñńôöòóœøōõßśšûüùúūÿžźż' 'aaaaaaaaccceeeeeeeiiiiiiilnnoooooooosssuuuuuyzzz' | 416 | awk '{ 417 | gsub(/[\[\]@#$%^&*;,.:()<>!?\/+=_]/," ",$0); 418 | gsub(/^ */,"",$0); 419 | gsub(/ *$/,"",$0); 420 | gsub(/ */,"-",$0); 421 | gsub(/[^a-z0-9\-]/,""); 422 | print; 423 | }' | 424 | sed "s/-/$separator/g" 425 | } 426 | 427 | title_case() { 428 | # title_case 429 | # title_case "Jack, Jill & Clémence LTD" => JackJillClemenceLtd 430 | # title_case "Jack, Jill & Clémence LTD" "_" => Jack_Jill_Clemence_Ltd 431 | separator="${2:-}" 432 | # shellcheck disable=SC2020 433 | echo "$1" | 434 | tr '[:upper:]' '[:lower:]' | 435 | tr 'àáâäæãåāçćčèéêëēėęîïííīįìłñńôöòóœøōõßśšûüùúūÿžźż' 'aaaaaaaaccceeeeeeeiiiiiiilnnoooooooosssuuuuuyzzz' | 436 | awk '{ gsub(/[\[\]@#$%^&*;,.:()<>!?\/+=_-]/," ",$0); print $0; }' | 437 | awk '{ 438 | for (i=1; i<=NF; ++i) { 439 | $i = toupper(substr($i,1,1)) tolower(substr($i,2)) 440 | }; 441 | print $0; 442 | }' | 443 | sed "s/ /$separator/g" | 444 | cut -c1-50 445 | } 446 | 447 | ### interactive 448 | confirm() { 449 | # $1 = question 450 | flag_set $force && return 0 451 | read -r -p "$1 [y/N] " -n 1 452 | echo " " 453 | [[ $REPLY =~ ^[Yy]$ ]] 454 | } 455 | 456 | ask() { 457 | # $1 = question 458 | # $2 = default value 459 | local ANSWER 460 | if [[ -n "${2:-}" ]]; then 461 | read -r -p "$1 ($2) > " ANSWER 462 | else 463 | read -r -p "$1 > " ANSWER 464 | fi 465 | [[ -n "$ANSWER" ]] && echo "$ANSWER" || echo "${2:-}" 466 | } 467 | 468 | trap "IO:die \"ERROR \$? after \$SECONDS seconds \n\ 469 | \${error_prefix} last command : '\$BASH_COMMAND' \" \ 470 | \$(< \$script_install_path awk -v lineno=\$LINENO \ 471 | 'NR == lineno {print \"\${error_prefix} from line \" lineno \" : \" \$0}')" INT TERM EXIT 472 | # cf https://askubuntu.com/questions/513932/what-is-the-bash-command-variable-good-for 473 | 474 | safe_exit() { 475 | [[ -n "${tmp_file:-}" ]] && [[ -f "$tmp_file" ]] && rm "$tmp_file" 476 | trap - INT TERM EXIT 477 | IO:debug "$script_basename finished after $SECONDS seconds" 478 | exit 0 479 | } 480 | 481 | flag_set() { [[ "$1" -gt 0 ]]; } 482 | 483 | show_usage() { 484 | IO:print "Program: ${col_grn}$script_basename $script_version${col_reset} by ${col_ylw}$script_author${col_reset}" 485 | IO:print "Updated: ${col_grn}$script_modified${col_reset}" 486 | IO:print "Description: package_description" 487 | echo -n "Usage: $script_basename" 488 | list_options | 489 | awk ' 490 | BEGIN { FS="|"; OFS=" "; oneline="" ; fulltext="Flags, options and parameters:"} 491 | $1 ~ /flag/ { 492 | fulltext = fulltext sprintf("\n -%1s|--%-12s: [flag] %s [default: off]",$2,$3,$4) ; 493 | oneline = oneline " [-" $2 "]" 494 | } 495 | $1 ~ /option/ { 496 | fulltext = fulltext sprintf("\n -%1s|--%-12s: [option] %s",$2,$3 " ",$4) ; 497 | if($5!=""){fulltext = fulltext " [default: " $5 "]"; } 498 | oneline = oneline " [-" $2 " <" $3 ">]" 499 | } 500 | $1 ~ /list/ { 501 | fulltext = fulltext sprintf("\n -%1s|--%-12s: [list] %s (array)",$2,$3 " ",$4) ; 502 | fulltext = fulltext " [default empty]"; 503 | oneline = oneline " [-" $2 " <" $3 ">]" 504 | } 505 | $1 ~ /secret/ { 506 | fulltext = fulltext sprintf("\n -%1s|--%s <%s>: [secret] %s",$2,$3,"?",$4) ; 507 | oneline = oneline " [-" $2 " <" $3 ">]" 508 | } 509 | $1 ~ /param/ { 510 | if($2 == "1"){ 511 | fulltext = fulltext sprintf("\n %-17s: [parameter] %s","<"$3">",$4); 512 | oneline = oneline " <" $3 ">" 513 | } 514 | if($2 == "?"){ 515 | fulltext = fulltext sprintf("\n %-17s: [parameter] %s (optional)","<"$3">",$4); 516 | oneline = oneline " <" $3 "?>" 517 | } 518 | if($2 == "n"){ 519 | fulltext = fulltext sprintf("\n %-17s: [parameters] %s (1 or more)","<"$3">",$4); 520 | oneline = oneline " <" $3 " …>" 521 | } 522 | } 523 | END {print oneline; print fulltext} 524 | ' 525 | } 526 | 527 | check_last_version() { 528 | ( 529 | # shellcheck disable=SC2164 530 | pushd "$script_install_folder" &> /dev/null 531 | if [[ -d .git ]]; then 532 | local remote 533 | remote="$(git remote -v | grep fetch | awk 'NR == 1 {print $2}')" 534 | IO:progress "Check for latest version - $remote" 535 | git remote update &> /dev/null 536 | if [[ $(git rev-list --count "HEAD...HEAD@{upstream}" 2> /dev/null) -gt 0 ]]; then 537 | IO:print "There is a more recent update of this script - run <<$script_prefix update>> to update" 538 | fi 539 | fi 540 | # shellcheck disable=SC2164 541 | popd &> /dev/null 542 | ) 543 | } 544 | 545 | update_script_to_latest() { 546 | # run in background to avoid problems with modifying a running interpreted script 547 | ( 548 | sleep 1 549 | cd "$script_install_folder" && git pull 550 | ) & 551 | } 552 | 553 | show_tips() { 554 | ((sourced)) && return 0 555 | # shellcheck disable=SC2016 556 | grep < "${BASH_SOURCE[0]}" -v '$0' | 557 | awk \ 558 | -v green="$col_grn" \ 559 | -v yellow="$col_ylw" \ 560 | -v reset="$col_reset" \ 561 | ' 562 | /TIP: / {$1=""; gsub(/«/,green); gsub(/»/,reset); print "*" $0} 563 | /TIP:> / {$1=""; print " " yellow $0 reset} 564 | ' | 565 | awk \ 566 | -v script_basename="$script_basename" \ 567 | -v script_prefix="$script_prefix" \ 568 | '{ 569 | gsub(/\$script_basename/,script_basename); 570 | gsub(/\$script_prefix/,script_prefix); 571 | print ; 572 | }' 573 | } 574 | 575 | check_script_settings() { 576 | if [[ -n $(filter_option_type flag) ]]; then 577 | local name 578 | IO:print "## ${col_grn}boolean flags${col_reset}:" 579 | filter_option_type flag | 580 | while read -r name; do 581 | declare -p "$name" | cut -d' ' -f3- 582 | done 583 | fi 584 | 585 | if [[ -n $(filter_option_type option) ]]; then 586 | IO:print "## ${col_grn}option defaults${col_reset}:" 587 | filter_option_type option | 588 | while read -r name; do 589 | declare -p "$name" | cut -d' ' -f3- 590 | done 591 | fi 592 | 593 | if [[ -n $(filter_option_type list) ]]; then 594 | IO:print "## ${col_grn}list options${col_reset}:" 595 | filter_option_type list | 596 | while read -r name; do 597 | declare -p "$name" | cut -d' ' -f3- 598 | done 599 | fi 600 | 601 | if [[ -n $(filter_option_type param) ]]; then 602 | if ((piped)); then 603 | IO:debug "Skip parameters for .env files" 604 | else 605 | IO:print "## ${col_grn}parameters${col_reset}:" 606 | filter_option_type param | 607 | while read -r name; do 608 | declare -p "$name" | cut -d' ' -f3- 609 | done 610 | fi 611 | fi 612 | } 613 | 614 | filter_option_type() { 615 | list_options | grep "$1|" | cut -d'|' -f3 | sort | grep -v '^\s*$' 616 | } 617 | 618 | init_options() { 619 | local init_command 620 | init_command=$(list_options | 621 | grep -v "verbose|" | 622 | awk ' 623 | BEGIN { FS="|"; OFS=" ";} 624 | $1 ~ /flag/ && $5 == "" {print $3 "=0; "} 625 | $1 ~ /flag/ && $5 != "" {print $3 "=\"" $5 "\"; "} 626 | $1 ~ /option/ && $5 == "" {print $3 "=\"\"; "} 627 | $1 ~ /option/ && $5 != "" {print $3 "=\"" $5 "\"; "} 628 | $1 ~ /list/ {print $3 "=(); "} 629 | $1 ~ /secret/ {print $3 "=\"\"; "} 630 | ') 631 | if [[ -n "$init_command" ]]; then 632 | eval "$init_command" 633 | fi 634 | } 635 | 636 | expects_single_params() { list_options | grep 'param|1|' > /dev/null; } 637 | expects_optional_params() { list_options | grep 'param|?|' > /dev/null; } 638 | expects_multi_param() { list_options | grep 'param|n|' > /dev/null; } 639 | 640 | parse_options() { 641 | if [[ $# -eq 0 ]]; then 642 | show_usage >&2 643 | safe_exit 644 | fi 645 | 646 | ## first process all the -x --xxxx flags and options 647 | while true; do 648 | # flag is saved as $flag = 0/1 649 | # option