├── .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 | 
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 | [](https://github.com/pforret/bashew/actions/workflows/shellcheck.yml)
2 | [](https://github.com/pforret/bashew/actions/workflows/bash_unit.yml)
3 | [](https://github.com/pforret/bashew/tags)
4 | [](https://github.com/pforret/bashew/releases)
5 |
6 | Part of [](https://blog.forret.com/portfolio/bashful/) network
7 | /
8 | Install with [](https://www.basher.it/package/)
9 |
10 | # bashew.sh
11 |
12 | 
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 | [](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 | [](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 | [](https://basher.gitparade.com/package/) - Basher install script directory
2 |
3 | [](https://github.com/pforret/bashew) - Bash script template
4 |
5 | [](https://www.getrevue.co/profile/bashful) - Bash scripting newsletter
6 |
7 | [](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