├── .editorconfig
├── .gitattributes
├── .gitignore
├── .travis.yml
├── Baskfile.sh
├── LICENSE.md
├── README.md
├── bin
└── bask
├── completion
└── bask.bash
├── package.json
├── src
├── bask.sh
└── cli.sh
└── test
├── Baskfile.sh
└── test.sh
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.json]
13 | indent_size = 2
14 |
15 | [*.md]
16 | max_line_length = 80
17 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.sh text eol=lf
3 | *.md text eol=lf
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # .gitignore
2 |
3 | ## ------------------------------------
4 | ## Project build rubbish
5 | ## ------------------------------------
6 |
7 | /node_modules
8 | /deps
9 | /.tmp
10 | /*.tgz
11 | npm-debug.log
12 |
13 |
14 | ## ------------------------------------
15 | ## Normally ignored rubbish
16 | ## ------------------------------------
17 |
18 | ## temp files
19 | *~
20 | *.tmp
21 | *.swp
22 | *.swo
23 | .swp.*
24 |
25 | ## OS generated files
26 | __MACOSX
27 | .DS_Store
28 | .Spotlight*
29 | .Trash*
30 | ehthumbs.db
31 | Thumbs.db
32 |
33 | ## ctags
34 | tags
35 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: bash
2 |
3 | notifications:
4 | email: false
5 |
6 | # From the shellcheck Wiki on Travis
7 | addons:
8 | apt:
9 | sources:
10 | - debian-sid
11 | packages:
12 | - shellcheck
13 |
14 | script:
15 | - ./bin/bask
16 |
--------------------------------------------------------------------------------
/Baskfile.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | shopt -s globstar
4 |
5 | get_shell_files() {
6 | echo "Finding shell files..." 1>&2
7 |
8 | for file in ./bin/**/* ./src/**/*; do
9 | # check for .sh ending
10 | if [[ "$file" =~ .sh$ ]]; then
11 | # Also report to stderr so we can get a log
12 | echo "$file" | tee /dev/fd/2
13 | # check if has bash in shebang on first line, and print if so
14 | elif [ -f "$file" ]; then
15 | # Also report to stderr so we can get a log
16 | awk '/#!\/.*bash/ && NR < 2 { print FILENAME; }' "$file" | tee /dev/fd/2
17 | fi
18 | done
19 |
20 | echo "Done finding shell files." 1>&2
21 | }
22 |
23 | get_files_and_check() {
24 | get_shell_files | xargs shellcheck
25 | }
26 |
27 | task_default() {
28 | get_files_and_check
29 | }
30 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | GNU Lesser General Public License
2 | =================================
3 |
4 | _Version 3, 29 June 2007_
5 | _Copyright © 2007 Free Software Foundation, Inc. <>_
6 |
7 | Everyone is permitted to copy and distribute verbatim copies
8 | of this license document, but changing it is not allowed.
9 |
10 |
11 | This version of the GNU Lesser General Public License incorporates
12 | the terms and conditions of version 3 of the GNU General Public
13 | License, supplemented by the additional permissions listed below.
14 |
15 | ### 0. Additional Definitions
16 |
17 | As used herein, “this License” refers to version 3 of the GNU Lesser
18 | General Public License, and the “GNU GPL” refers to version 3 of the GNU
19 | General Public License.
20 |
21 | “The Library” refers to a covered work governed by this License,
22 | other than an Application or a Combined Work as defined below.
23 |
24 | An “Application” is any work that makes use of an interface provided
25 | by the Library, but which is not otherwise based on the Library.
26 | Defining a subclass of a class defined by the Library is deemed a mode
27 | of using an interface provided by the Library.
28 |
29 | A “Combined Work” is a work produced by combining or linking an
30 | Application with the Library. The particular version of the Library
31 | with which the Combined Work was made is also called the “Linked
32 | Version”.
33 |
34 | The “Minimal Corresponding Source” for a Combined Work means the
35 | Corresponding Source for the Combined Work, excluding any source code
36 | for portions of the Combined Work that, considered in isolation, are
37 | based on the Application, and not on the Linked Version.
38 |
39 | The “Corresponding Application Code” for a Combined Work means the
40 | object code and/or source code for the Application, including any data
41 | and utility programs needed for reproducing the Combined Work from the
42 | Application, but excluding the System Libraries of the Combined Work.
43 |
44 | ### 1. Exception to Section 3 of the GNU GPL
45 |
46 | You may convey a covered work under sections 3 and 4 of this License
47 | without being bound by section 3 of the GNU GPL.
48 |
49 | ### 2. Conveying Modified Versions
50 |
51 | If you modify a copy of the Library, and, in your modifications, a
52 | facility refers to a function or data to be supplied by an Application
53 | that uses the facility (other than as an argument passed when the
54 | facility is invoked), then you may convey a copy of the modified
55 | version:
56 |
57 | * **a)** under this License, provided that you make a good faith effort to
58 | ensure that, in the event an Application does not supply the
59 | function or data, the facility still operates, and performs
60 | whatever part of its purpose remains meaningful, or
61 |
62 | * **b)** under the GNU GPL, with none of the additional permissions of
63 | this License applicable to that copy.
64 |
65 | ### 3. Object Code Incorporating Material from Library Header Files
66 |
67 | The object code form of an Application may incorporate material from
68 | a header file that is part of the Library. You may convey such object
69 | code under terms of your choice, provided that, if the incorporated
70 | material is not limited to numerical parameters, data structure
71 | layouts and accessors, or small macros, inline functions and templates
72 | (ten or fewer lines in length), you do both of the following:
73 |
74 | * **a)** Give prominent notice with each copy of the object code that the
75 | Library is used in it and that the Library and its use are
76 | covered by this License.
77 | * **b)** Accompany the object code with a copy of the GNU GPL and this license
78 | document.
79 |
80 | ### 4. Combined Works
81 |
82 | You may convey a Combined Work under terms of your choice that,
83 | taken together, effectively do not restrict modification of the
84 | portions of the Library contained in the Combined Work and reverse
85 | engineering for debugging such modifications, if you also do each of
86 | the following:
87 |
88 | * **a)** Give prominent notice with each copy of the Combined Work that
89 | the Library is used in it and that the Library and its use are
90 | covered by this License.
91 |
92 | * **b)** Accompany the Combined Work with a copy of the GNU GPL and this license
93 | document.
94 |
95 | * **c)** For a Combined Work that displays copyright notices during
96 | execution, include the copyright notice for the Library among
97 | these notices, as well as a reference directing the user to the
98 | copies of the GNU GPL and this license document.
99 |
100 | * **d)** Do one of the following:
101 | - **0)** Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 | - **1)** Use a suitable shared library mechanism for linking with the
109 | Library. A suitable mechanism is one that **(a)** uses at run time
110 | a copy of the Library already present on the user's computer
111 | system, and **(b)** will operate properly with a modified version
112 | of the Library that is interface-compatible with the Linked
113 | Version.
114 |
115 | * **e)** Provide Installation Information, but only if you would otherwise
116 | be required to provide such information under section 6 of the
117 | GNU GPL, and only to the extent that such information is
118 | necessary to install and execute a modified version of the
119 | Combined Work produced by recombining or relinking the
120 | Application with a modified version of the Linked Version. (If
121 | you use option **4d0**, the Installation Information must accompany
122 | the Minimal Corresponding Source and Corresponding Application
123 | Code. If you use option **4d1**, you must provide the Installation
124 | Information in the manner specified by section 6 of the GNU GPL
125 | for conveying Corresponding Source.)
126 |
127 | ### 5. Combined Libraries
128 |
129 | You may place library facilities that are a work based on the
130 | Library side by side in a single library together with other library
131 | facilities that are not Applications and are not covered by this
132 | License, and convey such a combined library under terms of your
133 | choice, if you do both of the following:
134 |
135 | * **a)** Accompany the combined library with a copy of the same work based
136 | on the Library, uncombined with any other library facilities,
137 | conveyed under the terms of this License.
138 | * **b)** Give prominent notice with the combined library that part of it
139 | is a work based on the Library, and explaining where to find the
140 | accompanying uncombined form of the same work.
141 |
142 | ### 6. Revised Versions of the GNU Lesser General Public License
143 |
144 | The Free Software Foundation may publish revised and/or new versions
145 | of the GNU Lesser General Public License from time to time. Such new
146 | versions will be similar in spirit to the present version, but may
147 | differ in detail to address new problems or concerns.
148 |
149 | Each version is given a distinguishing version number. If the
150 | Library as you received it specifies that a certain numbered version
151 | of the GNU Lesser General Public License “or any later version”
152 | applies to it, you have the option of following the terms and
153 | conditions either of that published version or of any later version
154 | published by the Free Software Foundation. If the Library as you
155 | received it does not specify a version number of the GNU Lesser
156 | General Public License, you may choose any version of the GNU Lesser
157 | General Public License ever published by the Free Software Foundation.
158 |
159 | If the Library as you received it specifies that a proxy can decide
160 | whether future versions of the GNU Lesser General Public License shall
161 | apply, that proxy's public statement of acceptance of any version is
162 | permanent authorization for you to choose that version for the
163 | Library.
164 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bask
2 |
3 | [](https://travis-ci.org/jez/bask)
4 | [](https://www.npmjs.com/package/bask)
5 | [][gitter]
6 | [](https://github.com/RichardLitt/standard-readme)
7 | [](https://tldrlegal.com/license/gnu-lesser-general-public-license-v3-(lgpl-3))
8 |
9 | > :sunglasses: Bask in the convenience of a task runner for bash
10 |
11 | Bask is a task runner for Bash. It's like Make with a bunch of shell targets,
12 | but without the `.PHONY`'s everywhere, and nicer because there's no more Make
13 | syntax. The main difference is that tasks are always run in Bask, but only run
14 | if targets are out of date in Make.
15 |
16 | If you're writing a Makefile with all `.PHONY` targets, chances are that Bask is
17 | really what you want.
18 |
19 |
20 |
21 | ## Table of Contents
22 |
23 | - [Background](#background)
24 | - [Install](#install)
25 | - [Simple (vendored)](#simple-vendored)
26 | - [Submodule (vendored)](#submodule-vendored)
27 | - [Homebrew](#homebrew)
28 | - [NPM](#npm)
29 | - [Git + PATH](#git--path)
30 | - [Usage](#usage)
31 | - [CLI](#cli)
32 | - [Flags](#flags)
33 | - [Tab Completion](#tab-completion)
34 | - [API](#api)
35 | - [Baskfile](#baskfile)
36 | - [Default Tasks](#default-tasks)
37 | - [Methods](#methods)
38 | - [`bask_depends`](#bask_depends)
39 | - [`bask_fork_join`](#bask_fork_join)
40 | - [`bask_run`](#bask_run)
41 | - [`bask_log`](#bask_log)
42 | - [`bask_colorize`](#bask_colorize)
43 | - [`bask_list_tasks`](#bask_list_tasks)
44 | - [`bask_is_task_defined`](#bask_is_task_defined)
45 | - [Contribute](#contribute)
46 | - [License](#license)
47 |
48 |
49 |
50 | ## Background
51 |
52 | Bask was initially forked from [bash-task-runner]. It was forked for a couple
53 | reasons:
54 |
55 | - I needed to vendor it for an unrelated project.
56 | - I wanted to drop the dependency on GNU coreutils.
57 | - I wanted to improve the name :smirk:
58 |
59 | I actively follow upstream changes. If I'm missing a compatible feature that was
60 | added upstream, feel free to notify me via an issue, pull request, or message on
61 | Gitter.
62 |
63 | Bask's first commit is a squashed version of `bash-task-runner` at the time it
64 | was forked. For a complete list of changes, just use the commit log.
65 |
66 |
67 | ## Install
68 |
69 | Each of the below installation methods is differentiated along two properties:
70 |
71 | - **Local to project**
72 | - Whether Bask will be installed locally to your project or globally on a
73 | system.
74 | - This is good for CI builds and spinning up multiple people on your project
75 | - **CLI-enabled**
76 | - Whether you will be able to use the `bask` command from your prompt.
77 | - Useful for local development, tab completion, and convenience.
78 |
79 | You may want to combine multiple installation methods in order to satisfy both
80 | of these requirements. In particular, we recommend [**Simple
81 | (vendored)**](#simple-vendored) with a method that gives you a CLI and is
82 | compatible with your system.
83 |
84 | | | Local to Project | CLI-enabled |
85 | | --- | --- | --- |
86 | | Simple (vendored) | :white_check_mark: | :no_entry_sign: |
87 | | Submodule (vendored) | :white_check_mark: | :white_check_mark: |
88 | | Homebrew | :no_entry_sign: | :white_check_mark: |
89 | | NPM | :white_check_mark: | :white_check_mark: |
90 | | Git + PATH | :no_entry_sign: | :white_check_mark: |
91 |
92 | ### Dependencies
93 |
94 | Currently Bask requires Bash 4.2+.
95 |
96 | ### Simple (vendored)
97 |
98 | Just drop `src/bask.sh` anywhere in your project folder:
99 |
100 | ```shell
101 | wget https://raw.githubusercontent.com/jez/bask/master/src/bask.sh
102 | ```
103 |
104 | Then skip to [Usage](#usage) for how to use a vendored Bask installation.
105 |
106 | ### Submodule (vendored)
107 |
108 | If you'd like a slightly better story around updating Bask when vendored, you
109 | can use a Git submodule, if you're [familiar with submodules][submodules]:
110 |
111 | ```shell
112 | git submodule add https://github.com/jez/bask
113 | ```
114 |
115 | > Note that if submodules are too heavy-handed, you can get the same effect
116 | > (without the ease of updating) by just unzip'ing Bask's source into your
117 | > project.
118 |
119 | You should now be able to access `bask.sh` within the submodule. Additionally,
120 | you can access the CLI with `./bask/bin/bask`. You can make this more ergonomic
121 | by altering your PATH:
122 |
123 | ```shell
124 | export PATH="$PATH:./bask/bin"
125 | ```
126 |
127 | Then skip to [Usage](#usage) to learn more.
128 |
129 | ### Homebrew
130 |
131 | On OS X, installing Bask globally is simple if you have Homebrew:
132 |
133 | ```shell
134 | brew install jez/formulae/bask
135 | ```
136 |
137 | Then skip to [Usage](#usage) to learn more.
138 |
139 | ### NPM
140 |
141 | If you don't mind the additional dependency on the NPM ecosystem, you can
142 | install Bask with NPM:
143 |
144 | ```shell
145 | # --- Local to Project --- #
146 | npm install --save bask
147 |
148 | # to enable CLI:
149 | export PATH="PATH:./node_modules/.bin"
150 |
151 | # --- Global --- #
152 | npm install -g bask
153 | ```
154 |
155 | Then skip to [Usage](#usage) to learn more.
156 |
157 | ### Git + PATH
158 |
159 | If Bask is not available in a package manager for your system, you can clone
160 | Bask to your computer, and adjust your PATH to contain the installation
161 | location:
162 |
163 | ```
164 | git clone https://github.com/jez/bask
165 |
166 | export PATH="$PATH:$(pwd)/bask/bin"
167 | ```
168 |
169 | Then skip to [Usage](#usage) for how to use the CLI.
170 |
171 |
172 | ## Usage
173 |
174 | You use Bask in conjunction with a `Baskfile`. A basic `Baskfile` looks like
175 | this:
176 |
177 | ```shell
178 | task_foo() {
179 | ## Do something...
180 | }
181 |
182 | task_bar() {
183 | ## Do something...
184 | }
185 | ```
186 |
187 | **Optional**: if you want your `Baskfile` to be a standalone script, add this
188 | to the beginning (works best in conjunction with a vendored installation):
189 |
190 | ```shell
191 | #!/usr/bin/env bash
192 | cd "$(dirname "$0")" || exit
193 | source ./path/to/bask.sh
194 | ```
195 |
196 | You invoke Bask using `bask [task ...]`:
197 |
198 | ```console
199 | $ bask foo bar
200 | [21:37:43.754] Starting 'foo'
201 | [21:37:43.755] Finished 'foo' after 1 ms
202 | [21:37:43.756] Starting 'bar'
203 | [21:37:43.757] Finished 'bar' after 1 ms
204 | ```
205 |
206 | Or, if your `Baskfile` sources `bash.sh`:
207 |
208 | ```console
209 | bash Baskfile foo bar
210 | ```
211 |
212 | ## CLI
213 |
214 | NOTE: Please see `bask -h` for complete, up-to-date CLI usage information.
215 |
216 | ```
217 | Usage: bask [options] [task] [task_options] ...
218 | Options:
219 | -C
, --directory= Change to before doing anything.
220 | --completion= Output code to activate task completions.
221 | Supported shells: 'bash'.
222 | -f , --file= Use as a Baskfile.
223 | -l, --list-tasks List available tasks.
224 | -h, --help Print this message and exit.
225 | ```
226 |
227 | ### Flags
228 |
229 | All flags you pass after the task names are passed to your tasks.
230 |
231 | ```bash
232 | task_foo() {
233 | echo ${@}
234 | }
235 |
236 | $ bask foo --production
237 | --production
238 | ```
239 |
240 | To pass options to the `bask` CLI specifically, you must provide them
241 | before any task names:
242 |
243 | ```bash
244 | $ bask -f scripts/tasks.sh foo
245 | ```
246 |
247 | ### Tab Completion
248 |
249 | The `bask` CLI supports autocompletion for task names (bash only). Simply add
250 | the following line your `~/.bashrc`:
251 |
252 | ```shell
253 | eval $(bask --completion=bash)
254 | ```
255 |
256 |
257 | ## API
258 |
259 | This section covers all the features of Bask.
260 |
261 | ### Baskfile
262 |
263 | Your Baskfile can be named any of the following. Using a `.sh` suffix helps with
264 | things like editor syntax highlighting.
265 |
266 | ```
267 | Baskfile
268 | Baskfile.sh
269 | baskfile
270 | baskfile.sh
271 | ```
272 |
273 |
274 | #### Default Tasks
275 |
276 | You can specify a default task in your Baskfile. It will run when no arguments
277 | are provided. There are two ways to do this:
278 |
279 | ```shell
280 | task_default() {
281 | # do something ...
282 | }
283 | ```
284 |
285 | ```shell
286 | bask_default_task="foo"
287 | task_foo() {
288 | # do something ...
289 | }
290 | ```
291 |
292 |
293 | ### Methods
294 |
295 | Bask exposes a number of functions for manipulating dependencies among tasks,
296 | for logging, and for a few utilities.
297 |
298 | #### `bask_depends`
299 |
300 | > Alias: `bask_sequence`
301 |
302 | This function is for declaring dependencies of a task. It should be invoked
303 | within another task.
304 |
305 | Usage: `bask_depends [task ...]`
306 |
307 | ```shell
308 | task_default() {
309 | bask_depends foo bar
310 | # Output:
311 | # [21:50:33.194] Starting 'foo'
312 | # [21:50:33.195] Finished 'foo' after 1 ms
313 | # [21:50:33.196] Starting 'bar'
314 | # [21:50:33.198] Finished 'bar' after 2 ms
315 | }
316 | ```
317 |
318 | If any task return non-zero, the entire sequence of tasks is aborted with
319 | an error.
320 |
321 | Note that return codes can be bubbled up using `... || return`, so you can
322 | conveniently abort tasks prematurely like this:
323 |
324 | ```
325 | maybe_error() {
326 | return $(($RANDOM % 2))
327 | }
328 |
329 | task_try() {
330 | echo "Bad"
331 | maybe_error || return # <-- bubbles error up if error
332 | # code for when no error
333 | }
334 |
335 | task_finally() {
336 | echo "Good"
337 | }
338 |
339 | task_foo() {
340 | bask_depends try finally
341 | }
342 | ```
343 |
344 | #### `bask_fork_join`
345 |
346 | > Alias: `bask_parallel`
347 |
348 | Bask also allows for spawning independent work in parallel, and resuming the
349 | task when all tasks have completed:
350 |
351 | Usage: `bask_fork_join [task ...]`
352 |
353 | ```shell
354 | task_default() {
355 | bask_fork_join sleep3 sleep5
356 | bask_log "after"
357 | # [21:50:33.194] Starting 'sleep3'
358 | # [21:50:33.194] Starting 'sleep5'
359 | # [21:50:36.396] Finished 'sleep3' after 3.20 s
360 | # [21:50:38.421] Finished 'sleep5' after 5.23 s
361 | # [21:50:38.422] after
362 | }
363 | ```
364 |
365 | Note that all tasks always run to completion, unlike with `bask_depends`.
366 |
367 | #### `bask_run`
368 |
369 | This will log a timestamp plus the command with its arguments, then run it.
370 |
371 | Usage: `bask_run `
372 |
373 | Note that the command must be a simple command--things like pipes, `&&`, `||`,
374 | `{ ... }`, etc. will not work.
375 |
376 |
377 | #### `bask_log`
378 |
379 | You can log information inside Bask tasks using one of the five `bask_log`
380 | helpers:
381 |
382 | | Function | Description |
383 | | --- | --- |
384 | | `bask_log` | Adds a log line (with time), in the foreground color |
385 | | `bask_log_success` | Same as above, but in green |
386 | | `bask_log_error` | Same as above, but in red |
387 | | `bask_log_warning` | Same as above, but in yellow |
388 | | `bask_log_info` | Same as above, but in cyan |
389 | | `bask_log_debug` | Same as above, but in gray |
390 |
391 | Usage: `bask_log message`
392 |
393 | All logging functions have the same usage.
394 |
395 | #### `bask_colorize`
396 |
397 | While the dedicated logging functions are helpful, sometimes you want finer
398 | control over your colors.
399 |
400 | Usage: `bask_colorize message`
401 |
402 | Where `colorname` is one of
403 |
404 | | | | | | | |
405 | | --- | --- | --- | --- | --- | --- |
406 | | black | gray | light_gray | white | | |
407 | | red | green | yellow | blue | purple | cyan |
408 | | light_red | light_green | light_yellow | light_blue | light_purple | light_cyan |
409 |
410 | ```shell
411 | # Simple example
412 | bask_colorize purple This will all be purple
413 |
414 | # Use with `bask_log` to get a timestamp:
415 | bask_log "$(bask_colorize purple This will all be purple)"
416 | ```
417 |
418 | Note that your message will be wrapped with the appropriate color **and** reset
419 | codes. You don't need to worry about manually turning the color back to normal.
420 |
421 | #### `bask_list_tasks`
422 |
423 | The default behavior if no tasks are specified at the command line and no
424 | default tasks are registered is to list all available tasks. You can manually
425 | invoke that with this function.
426 |
427 | Usage: `bask_list_tasks`
428 |
429 | ```shell
430 | task_list() {
431 | bask_list_tasks
432 | }
433 | ```
434 |
435 | #### `bask_is_task_defined`
436 |
437 | Utility function for checking whether a list of tasks are defined. Used
438 | internally, but exposed externally.
439 |
440 | Usage: `bask_is_task_defined [task ...]`
441 |
442 | Note that you can pass in more than one task for checking.
443 |
444 | Alternatively, you can use `bask_is_task_defined_verbose`, which will do the
445 | same checks, but log an error if any task is not defined.
446 |
447 |
448 | ## Contribute
449 |
450 | Bask was forked from [bash-task-runner]. Chances are that if you have an issue
451 | or pull request it can be made against that upstream repo.
452 |
453 | If your issue pertains to functionality specifically only provided here, then
454 | feel free to voice your concerns here. If you're confused where to make the
455 | request, feel free to ask in [Gitter][gitter] first.
456 |
457 | ## License
458 |
459 | GNU Lesser General Public License v3 (LGPL-3.0). See License.md
460 |
461 | | Copyright (c) | Commits |
462 | | --------------- | -------- |
463 | | Aleksej Komarov | 8a28f72 |
464 | | Jake Zimmerman | 5e92344+ |
465 |
466 |
467 |
468 | [bash-task-runner]: https://github.com/stylemistake/bash-task-runner
469 | [gitter]: https://gitter.im/jez/bask
470 | [submodules]: https://git-scm.com/book/en/v2/Git-Tools-Submodules
471 |
--------------------------------------------------------------------------------
/bin/bask:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Get paths where source files are located. http://stackoverflow.com/a/42918
4 | BINDIR="$(dirname "$(perl -MCwd -le 'print Cwd::abs_path(shift)' "${BASH_SOURCE[0]}")")"
5 | ROOTDIR="$(dirname "$BINDIR")"
6 | SRCDIR="$ROOTDIR/src"
7 |
8 | if [[ "${FUNCNAME[0]}" == source ]]; then
9 | echo "Can't source the 'bask' executable. Instead, source 'src/bask.sh' directly."
10 | exit 1
11 | fi
12 |
13 | # Include core files
14 | # shellcheck disable=SC1090
15 | source "$SRCDIR/bask.sh"
16 |
17 | # Include CLI specific files
18 | # shellcheck disable=SC1090
19 | source "$SRCDIR/cli.sh"
20 |
--------------------------------------------------------------------------------
/completion/bask.bash:
--------------------------------------------------------------------------------
1 | _bask_completions() {
2 | local -a options=('-C' '-f' '-l' '-h' '--directory=' '--file='
3 | '--list-tasks' '--help')
4 | local cur="${COMP_WORDS[COMP_CWORD]}"
5 | local prev="${COMP_WORDS[COMP_CWORD-1]}"
6 | local flag tasks
7 | ## Complete the short directory option
8 | if [[ ${prev} == '-C' ]]; then
9 | compopt -o dirnames
10 | COMPREPLY=()
11 | return 0
12 | fi
13 | ## Complete the short file option
14 | if [[ ${prev} == '-f' ]]; then
15 | COMPREPLY=()
16 | return 0
17 | fi
18 | ## Shift compwords to feed into the next condition
19 | if [[ ${prev} == '--'* && ${cur} == '=' ]]; then
20 | flag="${prev}"
21 | prev="${cur}"
22 | cur=""
23 | fi
24 | ## Complete long options
25 | if [[ ${prev} == '=' ]]; then
26 | [[ -z ${flag} ]] && flag="${COMP_WORDS[COMP_CWORD-2]}"
27 | ## Limit selection to directories
28 | if [[ ${flag} == '--directory' ]]; then
29 | compopt -o dirnames
30 | fi
31 | ## Show file completions (using -o default)
32 | COMPREPLY=()
33 | return 0
34 | fi
35 | ## Complete all options
36 | if [[ ${cur} == '-'* ]]; then
37 | COMPREPLY=($(compgen -W "${options[*]}" -- "${cur}"))
38 | [[ ${#COMPREPLY[@]} == 1 && ${COMPREPLY[0]} != "--"*"=" ]] \
39 | && compopt +o nospace || compopt -o nospace
40 | return 0
41 | fi
42 | ## NOTE: ${COMP_WORDS[@]:1} provides the context of currently entered
43 | ## options, so bask would know what Baskfile to use.
44 | tasks="$(bask -l ${COMP_WORDS[@]:1})"
45 | if [[ ${?} -ne 0 ]]; then
46 | ## Show nothing at all
47 | compopt +o default
48 | COMPREPLY=()
49 | return 0
50 | fi
51 | ## Complete task names
52 | COMPREPLY=($(compgen -W "${tasks}" -- "${cur}"))
53 | }
54 |
55 | complete -o default -F _bask_completions bask
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bask",
3 | "version": "0.9.0",
4 | "description": "A task runner for bash",
5 | "directories": {
6 | "test": "test"
7 | },
8 | "bin": {
9 | "bask": "bin/bask"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/jez/bask.git"
14 | },
15 | "keywords": [
16 | "bash",
17 | "task",
18 | "runner",
19 | "bask"
20 | ],
21 | "license": "LGPL-3.0",
22 | "bugs": {
23 | "url": "https://github.com/jez/bask/issues"
24 | },
25 | "homepage": "https://github.com/jez/bask#readme"
26 | }
27 |
--------------------------------------------------------------------------------
/src/bask.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # shellcheck disable=SC2034
4 | VERSION="0.9.0"
5 |
6 | ## Only use milliseconds if available, by detecting GNU coreutils
7 | ## See: http://stackoverflow.com/a/8748344/319952
8 | if date --version > /dev/null 2>&1 ; then
9 | MILLIS="%3N"
10 | else
11 | # Approximate the milliseconds with zeros
12 | MILLIS="000"
13 | fi
14 |
15 | # Suffix for logging purposes
16 | MILLI_SUFFIX=".$MILLIS"
17 |
18 | ## Default task
19 | declare -g bask_default_task="default"
20 |
21 | ## Trap EXIT signal to bootstrap bask.
22 | ## Works like a charm - your script ends, tasks start to run.
23 | ## Trap resets after bootstrapping.
24 | trap '[[ ${?} -eq 0 ]] && _bask_bootstrap' EXIT
25 |
26 | ## Expand aliases
27 | shopt -s expand_aliases
28 |
29 | ## Determine the initial passed arguments to the root script
30 | declare -ga bask_args=("${@}")
31 |
32 | ## Split arguments into tasks and flags.
33 | ## All flags are then passed on to tasks.
34 | ## E.g. --production
35 | ## NOTE: The actual splitting is done in _bask_bootstrap.
36 | declare -ga bask_flags
37 | declare -ga bask_tasks
38 |
39 | declare -gA _bask_colors=(
40 | [black]="$(echo -e '\e[30m')"
41 | [red]="$(echo -e '\e[31m')"
42 | [green]="$(echo -e '\e[32m')"
43 | [yellow]="$(echo -e '\e[33m')"
44 | [blue]="$(echo -e '\e[34m')"
45 | [purple]="$(echo -e '\e[35m')"
46 | [cyan]="$(echo -e '\e[36m')"
47 | [light_gray]="$(echo -e '\e[37m')"
48 | [gray]="$(echo -e '\e[90m')"
49 | [light_red]="$(echo -e '\e[91m')"
50 | [light_green]="$(echo -e '\e[92m')"
51 | [light_yellow]="$(echo -e '\e[93m')"
52 | [light_blue]="$(echo -e '\e[94m')"
53 | [light_purple]="$(echo -e '\e[95m')"
54 | [light_cyan]="$(echo -e '\e[96m')"
55 | [white]="$(echo -e '\e[97m')"
56 | [reset]="$(echo -e '\e[0m')"
57 | )
58 |
59 | ## Logs a message with a timestamp
60 | bask_log() {
61 | local timestamp
62 | timestamp="$(date "+%T$MILLI_SUFFIX")"
63 | echo "[${_bask_colors[gray]}${timestamp}${_bask_colors[reset]}] ${*}"
64 | }
65 |
66 | ## Variations of log with colors
67 | bask_log_error() {
68 | bask_log "${_bask_colors[red]}${*}${_bask_colors[reset]}"
69 | }
70 |
71 | bask_log_warning() {
72 | bask_log "${_bask_colors[yellow]}${*}${_bask_colors[reset]}"
73 | }
74 |
75 | bask_log_success() {
76 | bask_log "${_bask_colors[green]}${*}${_bask_colors[reset]}"
77 | }
78 |
79 | bask_log_info() {
80 | bask_log "${_bask_colors[cyan]}${*}${_bask_colors[reset]}"
81 | }
82 |
83 | bask_log_debug() {
84 | bask_log "${_bask_colors[gray]}${*}${_bask_colors[reset]}"
85 | }
86 | alias bask_log_notice=bask_log_debug
87 |
88 | # Helper colorization function. Usage:
89 | #
90 | # bask_colorize purple This will all be purple
91 | #
92 | # You may want to use in conjunction with bask_log to get a timestamp:
93 | #
94 | # bask_log "$(bask_colorize purple This will all be purple)"
95 | #
96 | bask_colorize() {
97 | echo "${_bask_colors[$1]}${*:2}${_bask_colors[reset]}"
98 | }
99 |
100 | ## List all defined functions beginning with `task_`
101 | _bask_get_defined_tasks() {
102 | local IFS=$'\n'
103 | for task in $(typeset -F); do
104 | [[ ${task} == 'declare -f task_'* ]] && echo "${task:16}"
105 | done
106 | }
107 |
108 | ## Fancy wrapper for `_bask_get_defined_tasks`
109 | bask_list_tasks() {
110 | bask_log "Available tasks:"
111 | local -a tasks=($(_bask_get_defined_tasks))
112 | if [[ ${#tasks[@]} -eq 0 ]]; then
113 | bask_log " ${_bask_colors[light_gray]}${_bask_colors[reset]}"
114 | return
115 | fi
116 | for task in "${tasks[@]}"; do
117 | bask_log " ${_bask_colors[cyan]}${task}${_bask_colors[reset]}"
118 | done
119 | }
120 |
121 | ## Returns a human readable duration in ms
122 | _bask_pretty_ms() {
123 | local -i ms="${1}"
124 | local result
125 | ## If zero or nothing
126 | if [[ -z ${ms} || ${ms} -lt 1 ]]; then
127 | echo "0 ms"
128 | return
129 | ## Only ms
130 | elif [[ ${ms} -lt 1000 ]]; then
131 | echo "${ms} ms"
132 | return
133 | ## Only seconds with trimmed ms point
134 | elif [[ ${ms} -lt 60000 ]]; then
135 | result=$((ms / 1000 % 60)).$((ms % 1000))
136 | echo "${result:0:4} s"
137 | return
138 | fi
139 | local -i parsed
140 | ## Days
141 | parsed=$((ms / 86400000))
142 | [[ ${parsed} -gt 0 ]] && result="${result} ${parsed} d"
143 | ## Hours
144 | parsed=$((ms / 3600000 % 24))
145 | [[ ${parsed} -gt 0 ]] && result="${result} ${parsed} h"
146 | ## Minutes
147 | parsed=$((ms / 60000 % 60))
148 | [[ ${parsed} -gt 0 ]] && result="${result} ${parsed} m"
149 | ## Seconds
150 | parsed=$((ms / 1000 % 60))
151 | [[ ${parsed} -gt 0 ]] && result="${result} ${parsed} s"
152 | ## Output result
153 | echo "${result}"
154 | }
155 |
156 | ## Checks if program is accessible from current $PATH
157 | _bask_is_defined() {
158 | hash "${@}" 2>/dev/null
159 | }
160 |
161 | bask_is_task_defined() {
162 | for task in "${@}"; do
163 | _bask_is_defined "task_${task}" || return
164 | done
165 | }
166 |
167 | bask_is_task_defined_verbose() {
168 | for task in "${@}"; do
169 | if ! _bask_is_defined "task_${task}"; then
170 | bask_log_error "Task '${task}' is not defined!"
171 | return 1
172 | fi
173 | done
174 | }
175 |
176 | _bask_run_task() {
177 | local -i time_start time_end
178 | local time_diff
179 | local task_color="${_bask_colors[cyan]}${1}${_bask_colors[reset]}"
180 | bask_log "Starting '${task_color}'..."
181 | time_start="$(date +%s$MILLIS)"
182 | "task_${1}" "${bask_flags[@]}"
183 | local exit_code=${?}
184 | time_end="$(date +%s$MILLIS)"
185 | time_diff="$(_bask_pretty_ms $((time_end - time_start)))"
186 | if [[ ${exit_code} -ne 0 ]]; then
187 | bask_log_error "Task '${1}'" \
188 | "failed after ${time_diff} (${exit_code})"
189 | return ${exit_code}
190 | fi
191 | bask_log "Finished '${task_color}'" \
192 | "after ${_bask_colors[purple]}${time_diff}${_bask_colors[reset]}"
193 | }
194 |
195 | ## Run tasks sequentially.
196 | bask_sequence() {
197 | bask_is_task_defined_verbose "${@}" || return
198 | for task in "${@}"; do
199 | _bask_run_task "${task}" || return 1
200 | done
201 | }
202 | alias bask_depends=bask_sequence
203 |
204 | ## Run tasks in parallel.
205 | bask_parallel() {
206 | bask_is_task_defined_verbose "${@}" || return 1
207 | local -a pid
208 | local -i exits=0
209 | for task in "${@}"; do
210 | _bask_run_task "${task}" & pid+=(${!})
211 | done
212 | for pid in "${pid[@]}"; do
213 | wait "${pid}" || exits+=1
214 | done
215 | [[ ${exits} -eq 0 ]] && return 0
216 | [[ ${exits} -lt ${#} ]] && return 41 || return 42
217 | }
218 | alias bask_fork_join=bask_parallel
219 |
220 | ## Output command before execution
221 | bask_run() {
222 | bask_log_notice "${@}"
223 | "${@}"
224 | }
225 |
226 | ## Starts the initial task.
227 | _bask_bootstrap() {
228 | ## Clear a trap we set up earlier
229 | trap - EXIT
230 | ## Parse arguments
231 | for arg in "${bask_args[@]}"; do
232 | if [[ ${arg} == -* ]]; then
233 | bask_flags+=("${arg}")
234 | else
235 | bask_tasks+=("${arg//-/_}")
236 | fi
237 | done
238 | ## Run tasks
239 | if [[ ${#bask_tasks[@]} -gt 0 ]]; then
240 | bask_sequence "${bask_tasks[@]}" || exit ${?}
241 | return 0
242 | fi
243 | if bask_is_task_defined "${bask_default_task}"; then
244 | _bask_run_task "${bask_default_task}" || exit ${?}
245 | return 0
246 | fi
247 | ## Nothing to run
248 | bask_list_tasks
249 | }
250 |
--------------------------------------------------------------------------------
/src/cli.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ## NOTE: This script depends on bask.sh to be loaded first.
4 |
5 | ## Globals that come from the entry points
6 | declare -g SRCDIR
7 | declare -g VERSION
8 |
9 | ## Baskfile names that CLI will be looking for in current directory.
10 | declare -ga baskfile_default_names=(
11 | 'baskfile.sh'
12 | 'Baskfile.sh'
13 | 'baskfile'
14 | 'Baskfile'
15 | )
16 |
17 | ## Global variables that hold CLI settings
18 | declare -g bask_file
19 | declare -g bask_directory
20 | declare -g bask_list_tasks
21 |
22 | ## Outputs an error message and exits the script
23 | bask_cli_error() {
24 | trap - EXIT
25 | bask_log_error "${@}"
26 | exit 2
27 | }
28 |
29 | ## Outputs a nice help message
30 | bask_cli_help() {
31 | trap - EXIT
32 | echo "Usage: ${0} [options] [task] [task_options] ..."
33 | echo "Version: $VERSION"
34 | echo "Options:"
35 | echo " -C , --directory= Change to before doing anything."
36 | echo " --completion= Output code to activate task completions."
37 | echo " Supported shells: 'bash'."
38 | echo " -f , --file= Use as a Baskfile."
39 | echo " -l, --list-tasks List available tasks."
40 | echo " -h, --help Print this message and exit."
41 | exit 0
42 | }
43 |
44 | ## Outputs a list of tasks
45 | bask_cli_list_tasks() {
46 | trap - EXIT
47 | _bask_get_defined_tasks
48 | exit 0
49 | }
50 |
51 | ## Outputs code to activate task completions
52 | bask_cli_get_completions_code() {
53 | trap - EXIT
54 | local shell="${1:-bash}"
55 | echo "source $SRCDIR/../completion/bask.${shell}"
56 | exit 0
57 | }
58 |
59 | ## Parses CLI-specific flags.
60 | ## Must take "${bask_args[@]}" as the argument.
61 | bask_cli_parse_args() {
62 | ## Clean up currently defined arguments
63 | bask_args=()
64 | ## Iterate over the provided arguments
65 | while [[ ${#} -gt 0 ]]; do
66 | ## Stop parsing after the first non-flag argument
67 | if [[ ${1} != -* ]]; then
68 | break
69 | fi
70 | ## Help message
71 | if [[ ${1} == '-h' || ${1} == '--help' ]]; then
72 | bask_cli_help
73 | fi
74 | ## List tasks
75 | if [[ ${1} == '-l' || ${1} == '--list-tasks' ]]; then
76 | bask_list_tasks="true"
77 | fi
78 | ## Return the completions code
79 | if [[ ${1} == '--completion='* ]]; then
80 | bask_cli_get_completions_code "${1#*=}"
81 | fi
82 | ## Baskfile override
83 | if [[ ${1} == '-f' ]]; then
84 | [[ -z ${2} ]] && bask_cli_error "Missing an argument after ${1}"
85 | bask_file="${2}"
86 | shift 2
87 | continue
88 | fi
89 | if [[ ${1} == '--file='* ]]; then
90 | bask_file="${1#*=}"
91 | shift 1
92 | continue
93 | fi
94 | ## Current directory override
95 | if [[ ${1} == '-C' ]]; then
96 | [[ -z ${2} ]] && bask_cli_error "Missing an argument after ${1}"
97 | bask_directory="${2}"
98 | shift 2
99 | continue
100 | fi
101 | if [[ ${1} == '--directory='* ]]; then
102 | bask_directory="${1#*=}"
103 | shift 1
104 | continue
105 | fi
106 | ## Append unclassified flags back to bask_args
107 | bask_args+=("${1}")
108 | shift 1
109 | done
110 | ## Append remaining arguments that will be passed to the
111 | ## bootstrap function
112 | bask_args+=("${@}")
113 | }
114 |
115 | ## Parse the actual arguments
116 | bask_cli_parse_args "${bask_args[@]}"
117 |
118 | ## Try to change the current directory
119 | if [[ -n ${bask_directory} ]]; then
120 | if [[ ! -d ${bask_directory} ]]; then
121 | bask_cli_error "'${bask_directory}' is not a directory!"
122 | fi
123 | cd "${bask_directory}" || bask_cli_error "Could not change directory!"
124 | fi
125 |
126 | ## Try to find a Baskfile
127 | if [[ -n ${bask_file} ]]; then
128 | if [[ ! -f ${bask_file} ]]; then
129 | bask_cli_error "'${bask_file}' is not a file!"
130 | fi
131 | else
132 | for file in "${baskfile_default_names[@]}"; do
133 | if [[ -f ${file} ]]; then
134 | bask_file="${file}"
135 | break
136 | fi
137 | done
138 | fi
139 |
140 | ## Baskfile not found
141 | if [[ -z ${bask_file} ]]; then
142 | bask_cli_error 'No Baskfile found.'
143 | fi
144 |
145 | ## Source the Baskfile
146 | # shellcheck disable=SC1090
147 | source "${bask_file}"
148 |
149 | if [ -n "${bask_list_tasks}" ]; then
150 | bask_cli_list_tasks
151 | fi
152 |
--------------------------------------------------------------------------------
/test/Baskfile.sh:
--------------------------------------------------------------------------------
1 | ## Sample Baskfile
2 |
3 | task_one() {
4 | bask_parallel end
5 | }
6 |
7 | task_two() {
8 | bask_parallel one end
9 | }
10 |
11 | task_three() {
12 | bask_parallel one two end
13 | }
14 |
15 | task_end() {
16 | # do nothing
17 | echo -n
18 | }
19 |
20 | task_default() {
21 | bask_parallel one two three end
22 | }
23 |
--------------------------------------------------------------------------------
/test/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd `dirname ${0}`
3 |
4 | source src/bask.sh
5 | source Baskfile.sh
6 |
--------------------------------------------------------------------------------