Add a yes/no prompt type. (#13)
parent
6fb8b6c516
commit
250f58bcc2
|
@ -4,5 +4,5 @@ BinPackArguments: false
|
||||||
BinPackParameters: false
|
BinPackParameters: false
|
||||||
ColumnLimit: 80
|
ColumnLimit: 80
|
||||||
ContinuationIndentWidth: 2
|
ContinuationIndentWidth: 2
|
||||||
IndentCaseLabels: false
|
IndentCaseLabels: true
|
||||||
IndentWidth: 2
|
IndentWidth: 2
|
||||||
|
|
|
@ -10,7 +10,7 @@ STAGED=$(
|
||||||
TARGETS=()
|
TARGETS=()
|
||||||
while IFS= read -r FILENAME
|
while IFS= read -r FILENAME
|
||||||
do
|
do
|
||||||
if [[ "$FILENAME" =~ .*\.c ]] || [[ "$FILENAME" == .*\.h ]]; then
|
if [[ "$FILENAME" =~ .*\.c$ ]] || [[ "$FILENAME" == .*\.h$ ]]; then
|
||||||
TARGETS+=("${FILENAME}")
|
TARGETS+=("${FILENAME}")
|
||||||
fi
|
fi
|
||||||
done <<< "$STAGED"
|
done <<< "$STAGED"
|
||||||
|
|
14
README.md
14
README.md
|
@ -152,10 +152,18 @@ The value of `type` determines how a field is prompted for. Note the value of
|
||||||
`type` is case insenstive. The currently supported list of types are:
|
`type` is case insenstive. The currently supported list of types are:
|
||||||
|
|
||||||
* `line`
|
* `line`
|
||||||
* The simplest prompt type. Takes in a free-form response submitted after
|
* Takes in a free-form response submitted after encountering a newline (`\n`).
|
||||||
encountering a newline (`\n`). The resulting environment variable has
|
The resulting environment variable has leading and trailing whitespace
|
||||||
leading and trailing whitespace trimmed.
|
trimmed.
|
||||||
* If `required`, whitespace-only strings are re-prompted.
|
* If `required`, whitespace-only strings are re-prompted.
|
||||||
|
* `yes`
|
||||||
|
* Takes in any of `"yes"`, `"y"`, `"no"`, and `"n"`. Answers are case
|
||||||
|
insensitive.
|
||||||
|
* Even if not `required`, any answer that does not match one of these patterns
|
||||||
|
is re-prompted.
|
||||||
|
* A value of `"yes"` has an environment variable with value `1` passed to the
|
||||||
|
runner. A value of `"no"` has an environment variable with a null value
|
||||||
|
(i.e. an empty string) passed to the runner.
|
||||||
|
|
||||||
#### Options
|
#### Options
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
enum FieldType {
|
enum FieldType {
|
||||||
FT_LINE = 1,
|
FT_LINE = 1,
|
||||||
|
FT_YES = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,7 +6,7 @@ set -e
|
||||||
# If set, Bash includes filenames beginning with a `.` in the results of
|
# If set, Bash includes filenames beginning with a `.` in the results of
|
||||||
# filename expansion. The filenames `.` and `..` must always be matched
|
# filename expansion. The filenames `.` and `..` must always be matched
|
||||||
# explicitly, even if dotglob is set.
|
# explicitly, even if dotglob is set.
|
||||||
shopt -s dotglob
|
shopt -s dotglob extglob
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# PROLOGUE
|
# PROLOGUE
|
||||||
|
@ -45,12 +45,18 @@ else
|
||||||
MODULE_ARG="--module '$MODULE'"
|
MODULE_ARG="--module '$MODULE'"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -n "$ECTO" ]; then
|
||||||
|
ECTO_ARG=
|
||||||
|
else
|
||||||
|
ECTO_ARG="--no-ecto"
|
||||||
|
fi
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# BUILD
|
# BUILD
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
# Copy template contents over to the intermediate build directory.
|
# Copy template contents over to the intermediate build directory.
|
||||||
cp -r template/* "$BUILD"
|
cp -r template/!(README*) "$BUILD"
|
||||||
|
|
||||||
# Explicitly set permissions on all copied template files.
|
# Explicitly set permissions on all copied template files.
|
||||||
find "$BUILD" -type f -execdir chmod 644 {} +
|
find "$BUILD" -type f -execdir chmod 644 {} +
|
||||||
|
@ -61,14 +67,18 @@ chmod 755 "$BUILD"/.githooks/pre-commit
|
||||||
# subdirectory to avoid interactive conflict resolution.
|
# subdirectory to avoid interactive conflict resolution.
|
||||||
nix develop "$BUILD" \
|
nix develop "$BUILD" \
|
||||||
--command bash \
|
--command bash \
|
||||||
-c "mix phx.new $BUILD/bs.project --app '$APP' $MODULE_ARG"
|
-c "mix phx.new $BUILD/bs.project --app '$APP' $MODULE_ARG $ECTO_ARG"
|
||||||
|
|
||||||
# Copy the generated files into the intermediate build directory.
|
# Copy the generated files into the intermediate build directory.
|
||||||
mv -f "$BUILD"/bs.project/* "$BUILD"
|
mv -f "$BUILD"/bs.project/* "$BUILD"
|
||||||
rmdir "$BUILD"/bs.project
|
rmdir "$BUILD"/bs.project
|
||||||
|
|
||||||
# Overwrite the generated README in favor of that defined in our template.
|
# Overwrite the generated README in favor of that defined in our template.
|
||||||
cp template/README.md "$BUILD"
|
if [ -n "$ECTO" ]; then
|
||||||
|
cp template/README-ecto.md "$BUILD"/README.md
|
||||||
|
else
|
||||||
|
cp template/README-no-ecto.md "$BUILD"/README.md
|
||||||
|
fi
|
||||||
chmod 644 "$BUILD"/README.md
|
chmod 644 "$BUILD"/README.md
|
||||||
|
|
||||||
# Include additional build files for our assets. Group into subdirectory so
|
# Include additional build files for our assets. Group into subdirectory so
|
||||||
|
@ -80,7 +90,9 @@ mv "$BUILD"/bs.assets/* "$BUILD"/assets
|
||||||
rmdir "$BUILD"/bs.assets
|
rmdir "$BUILD"/bs.assets
|
||||||
|
|
||||||
# Create a new database cluster.
|
# Create a new database cluster.
|
||||||
nix develop "$BUILD" --command bash -c "pg_ctl initdb -D $BUILD/db"
|
if [ -n "$ECTO" ]; then
|
||||||
|
nix develop "$BUILD" --command bash -c "pg_ctl initdb -D $BUILD/db"
|
||||||
|
fi
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# REWRITES
|
# REWRITES
|
||||||
|
@ -98,14 +110,15 @@ done
|
||||||
# Typically when building a Phoenix application, Mix will download
|
# Typically when building a Phoenix application, Mix will download
|
||||||
# esbuild/tailwind binaries on demand. Within nix environments this is not
|
# esbuild/tailwind binaries on demand. Within nix environments this is not
|
||||||
# possible. Instead, specify them directly with these environment variables.
|
# possible. Instead, specify them directly with these environment variables.
|
||||||
sed -i \
|
sed -i '/config :esbuild/, /^$/ {
|
||||||
'42 s/$/,/;
|
s/0.17.11/0.19.7/; s/]$/],/;
|
||||||
43 i \ \ path: System.get_env("MIX_ESBUILD_PATH")
|
s/^$/\ \ path: System.get_env("MIX_ESBUILD_PATH")\n/}' \
|
||||||
s/version: \"0.17.11\"/version: \"0.19.7\"/
|
"$BUILD"/config/config.exs
|
||||||
54 s/$/,/;
|
|
||||||
55 i \ \ path: System.get_env("MIX_TAILWIND_PATH")
|
sed -i '/config :tailwind/, /^$/ {
|
||||||
s/version: \"3.3.2\"/version: \"3.3.5\"/' \
|
s/3.3.2/3.3.5/; s/]$/],/;
|
||||||
"$BUILD/config/config.exs"
|
s/^$/\ \ path: System.get_env("MIX_TAILWIND_PATH")\n/}' \
|
||||||
|
"$BUILD"/config/config.exs
|
||||||
|
|
||||||
# By default Phoenix generates a postgres configuration with assumed username
|
# By default Phoenix generates a postgres configuration with assumed username
|
||||||
# `postgres`. This flake encourages a local postgres database with username
|
# `postgres`. This flake encourages a local postgres database with username
|
||||||
|
@ -114,9 +127,6 @@ sed -i "s/username: \"postgres\"/username: \"$(whoami)\"/g" "$BUILD/config/dev.e
|
||||||
|
|
||||||
# Append an additional rule to `.gitignore` to ignore the database cluster.
|
# Append an additional rule to `.gitignore` to ignore the database cluster.
|
||||||
cat <<EOF >> "$BUILD"/.gitignore
|
cat <<EOF >> "$BUILD"/.gitignore
|
||||||
# The default location of the generated database cluster.
|
|
||||||
/db/
|
|
||||||
|
|
||||||
# Directory used by \`direnv\` to hold \`use flake\`-generated profiles.
|
# Directory used by \`direnv\` to hold \`use flake\`-generated profiles.
|
||||||
/.direnv/
|
/.direnv/
|
||||||
|
|
||||||
|
@ -124,6 +134,14 @@ cat <<EOF >> "$BUILD"/.gitignore
|
||||||
/result
|
/result
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
if [ -n "$ECTO" ]; then
|
||||||
|
cat <<EOF >> "$BUILD"/.gitignore
|
||||||
|
|
||||||
|
# The default location of the generated database cluster.
|
||||||
|
/db/
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# EPILOGUE
|
# EPILOGUE
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
|
@ -7,5 +7,9 @@
|
||||||
"type": "line",
|
"type": "line",
|
||||||
"required": false,
|
"required": false,
|
||||||
"prompt": "Module> "
|
"prompt": "Module> "
|
||||||
|
},
|
||||||
|
"ecto": {
|
||||||
|
"type": "yes",
|
||||||
|
"prompt": "Include Ecto? [Yn] "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ $ pg_ctl -D db stop
|
||||||
|
|
||||||
Afterward, you can run the Phoenix setup commands:
|
Afterward, you can run the Phoenix setup commands:
|
||||||
```bash
|
```bash
|
||||||
|
$ mix deps.get
|
||||||
$ mix ecto.setup
|
$ mix ecto.setup
|
||||||
$ mix assets.setup
|
$ mix assets.setup
|
||||||
```
|
```
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Phoenix Flake Template
|
||||||
|
|
||||||
|
This is a template for constructing a environment for Elixir development
|
||||||
|
(version 1.15.7, Erlang/OTP 25) with the [Phoenix](https://www.phoenixframework.org/)
|
||||||
|
(version 1.7.10) framework. [direnv](https://direnv.net/) can be used to launch
|
||||||
|
a dev shell upon entering this directory (refer to `.envrc`). Otherwise run via:
|
||||||
|
```bash
|
||||||
|
$ nix develop
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
Run the Phoenix setup command and then start the local server:
|
||||||
|
```bash
|
||||||
|
$ mix deps.get
|
||||||
|
$ mix assets.setup
|
||||||
|
$ mix phx.server
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
Mix dependencies are packaged using [mix2nix](https://github.com/ydlr/mix2nix).
|
||||||
|
After updating your `mix.lock` file, make sure to re-run the following:
|
||||||
|
```bash
|
||||||
|
$ mix2nix > deps.nix
|
||||||
|
```
|
||||||
|
As of now, `mix2nix` cannot handle git dependencies found inside the `mix.lock`
|
||||||
|
file. If you have git dependencies, add them manually or use
|
||||||
|
[FODs](https://nixos.org/manual/nixpkgs/stable/#packaging-beam-applications).
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
Frontend dependencies (i.e. assets found in the `/assets` folder) are packaged
|
||||||
|
using [node2nix](https://github.com/svanderburg/node2nix). You can generate the
|
||||||
|
relevant nix files for import using the following sequence of commands:
|
||||||
|
```bash
|
||||||
|
$ cd assets
|
||||||
|
$ rm -r node_modules # If this directory exists.
|
||||||
|
$ node2nix -l
|
||||||
|
```
|
||||||
|
In the above, we must remove `node_modules` (if it exists). Otherwise the
|
||||||
|
node packages will be included in the Nix build, influencing the outcome of
|
||||||
|
`node2nix`. The above generates three files:
|
||||||
|
|
||||||
|
* `node-packages.nix`
|
||||||
|
* Captures the packages that can be deployed (including all its required
|
||||||
|
dependencies)
|
||||||
|
* `node-env.nix`
|
||||||
|
* Contains build logic
|
||||||
|
* `default.nix`
|
||||||
|
* A composition expression allowing users to deploy the package. For an
|
||||||
|
example of this deployment, refer to `flake.nix`
|
||||||
|
|
||||||
|
NOTE: Do not update the lock version used in `assets`. `node2nix` currently only
|
||||||
|
supports lock versions 1 and 2.
|
||||||
|
|
||||||
|
## Language Server
|
||||||
|
|
||||||
|
The [elixir-ls](https://github.com/elixir-lsp/elixir-ls) LSP (version 0.17.10)
|
||||||
|
and [typescript-language-server](https://github.com/typescript-language-server/typescript-language-server)
|
||||||
|
(version 4.1.2) is included in this flake.
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
|
||||||
|
Formatting depends on [prettier](https://prettier.io/) (version 3.1.0) and the
|
||||||
|
`mix format` task. A `pre-commit` hook is included in `.githooks` that can be
|
||||||
|
used to format all `*.exs?`, `*.jsx?`, and `*.tsx?` files prior to commit.
|
||||||
|
Install via:
|
||||||
|
```bash
|
||||||
|
$ git config --local core.hooksPath .githooks/
|
||||||
|
```
|
||||||
|
If running [direnv](https://direnv.net/), this hook is installed automatically
|
||||||
|
when entering the directory.
|
|
@ -79,7 +79,7 @@ static void print_prompt(const struct Field *const field) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *query_line(const struct Field *const field) {
|
static char *query_line(const struct Field *const field) {
|
||||||
assert(field);
|
assert(field);
|
||||||
|
|
||||||
// TODO: Dynamically size this value.
|
// TODO: Dynamically size this value.
|
||||||
|
@ -103,6 +103,38 @@ static const char *query_line(const struct Field *const field) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool query_yes(const struct Field *const field) {
|
||||||
|
assert(field);
|
||||||
|
|
||||||
|
// TODO: Dynamically size this value.
|
||||||
|
char *response = calloc(1, 1024);
|
||||||
|
bool answer = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
print_prompt(field);
|
||||||
|
if (fgets(response, 1024, stdin)) {
|
||||||
|
trim_leading(response);
|
||||||
|
trim_trailing(response);
|
||||||
|
if (strcmp_ci(response, "y") == 0 || strcmp_ci(response, "yes") == 0) {
|
||||||
|
answer = true;
|
||||||
|
break;
|
||||||
|
} else if (
|
||||||
|
strcmp_ci(response, "n") == 0 ||
|
||||||
|
strcmp_ci(response, "no") == 0 ||
|
||||||
|
(response[0] == 0 && !field->required)
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else { // Likely EOF. Force-quit even if required.
|
||||||
|
printf("\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(response);
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
static void push_env(
|
static void push_env(
|
||||||
struct StringBuf *env, const char *key, const char *value
|
struct StringBuf *env, const char *key, const char *value
|
||||||
) {
|
) {
|
||||||
|
@ -129,20 +161,30 @@ static struct Error *push_fields(
|
||||||
) {
|
) {
|
||||||
for (int i = 0; i < fields->size; ++i) {
|
for (int i = 0; i < fields->size; ++i) {
|
||||||
struct Field *field = fields->buf[i];
|
struct Field *field = fields->buf[i];
|
||||||
const char *response = 0;
|
|
||||||
switch (field->type) {
|
switch (field->type) {
|
||||||
case FT_LINE:
|
case FT_LINE: {
|
||||||
response = query_line(field);
|
char *response = query_line(field);
|
||||||
break;
|
if (field->required && !response) {
|
||||||
|
return ERROR_NEW(
|
||||||
|
ERROR_EVALUATOR_RESPONSE_INVALID,
|
||||||
|
ANSI_RED_F("ERROR"),
|
||||||
|
": Could not read response."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
push_env(*env_buf, field->key, response);
|
||||||
|
free(response);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FT_YES: {
|
||||||
|
bool response = query_yes(field);
|
||||||
|
if (response) {
|
||||||
|
push_env(*env_buf, field->key, "1");
|
||||||
|
} else {
|
||||||
|
push_env(*env_buf, field->key, "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (field->required && !response) {
|
|
||||||
return ERROR_NEW(
|
|
||||||
ERROR_EVALUATOR_RESPONSE_INVALID,
|
|
||||||
ANSI_RED_F("ERROR"),
|
|
||||||
": Could not read response."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
push_env(*env_buf, field->key, response);
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@ static struct Error *read_field(
|
||||||
|
|
||||||
if (strcmp_ci(type->valuestring, "line") == 0) {
|
if (strcmp_ci(type->valuestring, "line") == 0) {
|
||||||
(*out)->type = FT_LINE;
|
(*out)->type = FT_LINE;
|
||||||
|
} else if (strcmp_ci(type->valuestring, "yes") == 0) {
|
||||||
|
(*out)->type = FT_YES;
|
||||||
} else {
|
} else {
|
||||||
error = ERROR_NEW(
|
error = ERROR_NEW(
|
||||||
ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN,
|
ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN,
|
||||||
|
|
|
@ -47,6 +47,7 @@ int main(int argc, char *argv[]) {
|
||||||
sput_run_test(test_validator_field_required_valid);
|
sput_run_test(test_validator_field_required_valid);
|
||||||
sput_run_test(test_validator_field_prompt_invalid);
|
sput_run_test(test_validator_field_prompt_invalid);
|
||||||
sput_run_test(test_validator_valid_no_required);
|
sput_run_test(test_validator_valid_no_required);
|
||||||
|
sput_run_test(test_validator_field_type_yes);
|
||||||
|
|
||||||
sput_finish_testing();
|
sput_finish_testing();
|
||||||
|
|
||||||
|
|
|
@ -240,4 +240,21 @@ static void test_validator_valid_no_required() {
|
||||||
test_validator_teardown(fixture);
|
test_validator_teardown(fixture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_validator_field_type_yes() {
|
||||||
|
struct TestValidatorFixture *fixture = test_validator_setup(
|
||||||
|
"{"
|
||||||
|
" \"abc\": {"
|
||||||
|
" \"type\": \"yes\""
|
||||||
|
" \"prompt\": \"What value for key?\""
|
||||||
|
" }"
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
struct Error *error =
|
||||||
|
validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts);
|
||||||
|
sput_fail_unless(error == 0, "yes valid");
|
||||||
|
|
||||||
|
test_validator_teardown(fixture);
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* _BOOTSTRAP_TEST_VALIDATOR */
|
#endif /* _BOOTSTRAP_TEST_VALIDATOR */
|
||||||
|
|
Loading…
Reference in New Issue