Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ jobs:
run: shellcheck shellkin setup uninstall
- name: Run Bats tests
run: bats --recursive --print-output-on-failure test
- name: Add shellkin to PATH
run: echo "$GITHUB_WORKSPACE" >> "$GITHUB_PATH"
- name: Run Shellkin tests
run: ./shellkin
run: shellkin

ubuntu_setup:
name: Setup on Ubuntu
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ Implemented pieces include:
## Usage

```bash
# Create a starter features directory:
shellkin --init

# Run all repo features:
shellkin

Expand All @@ -114,6 +117,11 @@ and are not executed.
Use `shellkin --validate` to check feature structure and step-definition matching
without running any step bodies.

Use `shellkin --init` to create a starter features directory with an example
feature, step definitions, `support.sh`, and a local README. Pass a target
directory or configure `--default-target` to initialize a directory other than
`features`; use `--stepdefs` to choose the step definition directory name.

## Configuration with `.shellkin`

Shellkin supports configuration from a `.shellkin` argfile in the current
Expand Down Expand Up @@ -180,6 +188,12 @@ features/
- Additional support files are loaded when passed with `--load`.
- `--stepdefs` and `--load` paths are relative to the features directory.

You can create this structure with:

```bash
shellkin --init
```

## Step Definitions

Step definitions are shell snippets declared in files under
Expand Down
10 changes: 10 additions & 0 deletions doc/shellkin.1
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,16 @@ Default: value of \-\-default\-target
.SH OPTIONS
.SS \-\-fail\-fast, \-f
Abort after the first failing scenario
.IP \(bu 2
Conflicts With: \f[B]\-\-init\f[R]
.SS \-\-validate, \-v
Validate feature and step definition files
.IP \(bu 2
Conflicts With: \f[B]\-\-init\f[R]
.SS \-\-init
Initialize a Shellkin features directory
.IP \(bu 2
Conflicts With: \f[B]\-\-validate, \-\-fail\-fast, \-\-load\f[R]
.SS \-\-default\-target, \-t DIR
Path to features directory
.PP
Expand All @@ -51,6 +59,8 @@ Relative to features root
support.sh is loaded automatically
.IP \(bu 2
\f[I]Repeatable\f[R]
.IP \(bu 2
Conflicts With: \f[B]\-\-init\f[R]
.SH SEE ALSO
\f[B]shellkin\-stepdefs\f[R](5), \f[B]shellkin\-feature\f[R](5)
.SH SOURCE CODE
Expand Down
10 changes: 10 additions & 0 deletions doc/shellkin.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,21 @@ OPTIONS

Abort after the first failing scenario

- Conflicts With: **--init**

--validate, -v
--------------------------------------------------

Validate feature and step definition files

- Conflicts With: **--init**

--init
--------------------------------------------------

Initialize a Shellkin features directory

- Conflicts With: **--validate, --fail-fast, --load**

--default-target, -t DIR
--------------------------------------------------
Expand Down Expand Up @@ -86,6 +95,7 @@ support.sh is loaded automatically


- *Repeatable*
- Conflicts With: **--init**

SEE ALSO
==================================================
Expand Down
12 changes: 12 additions & 0 deletions features/init.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Feature: --init
Initialize a runnable features directory

Scenario: Initializing a runnable features directory
Given I am in a temp directory
When I run 'shellkin --init'
Then the output should include 'initialized shellkin features directory: features'
And the exit code should mean success
When I run 'shellkin'
Then the output should include 'Feature: shellkin example'
And the output should include '1 scenario, 0 failing'
And the exit code should mean success
187 changes: 187 additions & 0 deletions shellkin
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ root_command() {
stepdefs_subdir=${args['--stepdefs']}
fail_fast=${args['--fail-fast']:-0}
validate_mode=${args['--validate']:-0}
init_mode=${args['--init']:-0}
load_args=${args['--load']:-}
load_paths=()
feature_status=0
Expand All @@ -31,6 +32,17 @@ root_command() {
eval "load_paths=( $load_args )"
fi

if ((init_mode != 0)); then
init_target=${target_arg:-$default_target}

if init_files_create "$init_target" "$stepdefs_subdir"; then
return 0
fi

printf 'init error:\n%s\n' "$INIT_ERROR" >&2
return 1
fi

if ! parse_test_target "$target_arg" "$default_target"; then
printf 'validation error in TARGET:\n%s\n' "$VALIDATION_ERROR" >&2
return 1
Expand Down Expand Up @@ -187,11 +199,19 @@ shellkin_usage() {
# :flag.usage
printf " %s\n" "$(green "--fail-fast, -f")"
printf " Abort after the first failing scenario\n"
printf " %s\n" "Conflicts: --init"
echo

# :flag.usage
printf " %s\n" "$(green "--validate, -v")"
printf " Validate feature and step definition files\n"
printf " %s\n" "Conflicts: --init"
echo

# :flag.usage
printf " %s\n" "$(green "--init")"
printf " Initialize a Shellkin features directory\n"
printf " %s\n" "Conflicts: --validate, --fail-fast, --load"
echo

# :flag.usage
Expand All @@ -209,6 +229,7 @@ shellkin_usage() {
# :flag.usage
printf " %s\n" "$(green "--load, -l SUPPORT_SCRIPT (repeatable)")"
printf " Path to support script for step definitions \n Relative to features root \n support.sh is loaded automatically\n"
printf " %s\n" "Conflicts: --init"
echo

# :command.usage_fixed_flags
Expand Down Expand Up @@ -890,6 +911,136 @@ feature_step_type_resolve() {
esac
}

# src/lib/init/files.sh
init_files_create() {
local target_dir=$1
local stepdefs_subdir=$2
local stepdefs_dir="$target_dir/$stepdefs_subdir"
local feature_file="$target_dir/example.feature"
local support_file="$target_dir/support.sh"
local stepdefs_file="$stepdefs_dir/core.sh"
local readme_file="$target_dir/README.md"

INIT_ERROR=

init__target_validate "$target_dir" "$stepdefs_dir" || return 1
init__file_available "$feature_file" || return 1
init__file_available "$support_file" || return 1
init__file_available "$stepdefs_file" || return 1
init__file_available "$readme_file" || return 1

mkdir -p "$stepdefs_dir"

init__example_feature_write "$feature_file"
init__support_file_write "$support_file"
init__stepdefs_file_write "$stepdefs_file"
init__readme_file_write "$readme_file" "$target_dir" "$stepdefs_subdir"

printf 'initialized shellkin features directory: %s\n' "$target_dir"
}

init__target_validate() {
local target_dir=$1
local stepdefs_dir=$2

if [[ -z $target_dir ]]; then
INIT_ERROR="init target is required"
return 1
fi

if [[ $target_dir == *:* ]]; then
INIT_ERROR="init target must be a directory, not a scenario selector: $target_dir"
return 1
fi

if [[ $target_dir == *.feature ]]; then
INIT_ERROR="init target must be a directory, not a feature file: $target_dir"
return 1
fi

if [[ -e $target_dir && ! -d $target_dir ]]; then
INIT_ERROR="init target exists and is not a directory: $target_dir"
return 1
fi

if [[ -e $stepdefs_dir && ! -d $stepdefs_dir ]]; then
INIT_ERROR="step definitions path exists and is not a directory: $stepdefs_dir"
return 1
fi
}

init__file_available() {
local path=$1

if [[ -e $path ]]; then
INIT_ERROR="refusing to overwrite existing file: $path"
return 1
fi
}

init__example_feature_write() {
local path=$1

cat >"$path" <<'EOF'
Feature: shellkin example
A small generated example

Scenario: Run a command
When I run 'printf hello'
Then the output should include 'hello'
EOF
}

init__support_file_write() {
local path=$1

cat >"$path" <<'EOF'
# Helper functions for Shellkin step definitions.
EOF
}

init__stepdefs_file_write() {
local path=$1

cat >"$path" <<'EOF'
@When I run '{command}'
run "$command"

@Then the output should include '{text}'
[[ "$LAST_STDOUT" == *"$text"* ]] || fail "expected output to include '$text'"
EOF
}

init__readme_file_write() {
local path=$1
local target_dir=$2
local stepdefs_subdir=$3
local run_command=shellkin
local target_arg
local stepdefs_arg

if [[ $target_dir != features || $stepdefs_subdir != step_definitions ]]; then
printf -v target_arg '%q' "$target_dir"
printf -v stepdefs_arg '%q' "$stepdefs_subdir"
run_command="shellkin --default-target $target_arg --stepdefs $stepdefs_arg"
fi

cat >"$path" <<EOF
# Shellkin Features

- Feature files live in this directory.
- Step definitions live in \`$stepdefs_subdir/\`.
- \`support.sh\` is loaded automatically when present.
- Extra support files can be loaded with repeatable \`--load\` entries.

Run these tests with:

\`\`\`bash
$run_command
\`\`\`
EOF
}

# src/lib/output/test.sh
output_feature_start() {
blue_bold "\nFeature: $1"
Expand Down Expand Up @@ -1612,6 +1763,12 @@ parse_requirements() {

;;
# :flag.argfile_case
--init)
# :flag.argfile_case_no_arg
[[ -n ${args['--init']+x} ]] || args['--init']=1

;;
# :flag.argfile_case
--default-target | -t)
# :flag.argfile_case_arg
if [[ -n "$argfile_value" ]]; then
Expand Down Expand Up @@ -1651,6 +1808,11 @@ parse_requirements() {
case "$key" in
# :flag.case
--fail-fast | -f)
# :flag.conflicts
if [[ -n "${args['--init']:-}" ]]; then
printf "conflicting options: %s cannot be used with %s\n" "$key" "--init" >&2
exit 1
fi

# :flag.case_no_arg
args['--fail-fast']=1
Expand All @@ -1659,12 +1821,32 @@ parse_requirements() {

# :flag.case
--validate | -v)
# :flag.conflicts
if [[ -n "${args['--init']:-}" ]]; then
printf "conflicting options: %s cannot be used with %s\n" "$key" "--init" >&2
exit 1
fi

# :flag.case_no_arg
args['--validate']=1
shift
;;

# :flag.case
--init)
# :flag.conflicts
for conflict in --validate --fail-fast --load; do
if [[ -n "${args[$conflict]:-}" ]]; then
printf "conflicting options: %s cannot be used with %s\n" "$key" "$conflict" >&2
exit 1
fi
done

# :flag.case_no_arg
args['--init']=1
shift
;;

# :flag.case
--default-target | -t)

Expand Down Expand Up @@ -1695,6 +1877,11 @@ parse_requirements() {

# :flag.case
--load | -l)
# :flag.conflicts
if [[ -n "${args['--init']:-}" ]]; then
printf "conflicting options: %s cannot be used with %s\n" "$key" "--init" >&2
exit 1
fi

# :flag.case_arg
if [[ -n ${2+x} ]]; then
Expand Down
6 changes: 6 additions & 0 deletions src/bashly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ flags:
- long: --fail-fast
short: -f
help: Abort after the first failing scenario
conflicts: [--init]
- long: --validate
short: -v
help: Validate feature and step definition files
conflicts: [--init]
- long: --init
help: Initialize a Shellkin features directory
conflicts: [--validate, --fail-fast, --load]
- long: --default-target
short: -t
help: |
Expand All @@ -49,6 +54,7 @@ flags:
Relative to features root
support.sh is loaded automatically
repeatable: true
conflicts: [--init]


# Manpages Extras
Expand Down
Loading
Loading