Add a yes/no prompt type. (#13)
parent
6fb8b6c516
commit
250f58bcc2
|
@ -4,5 +4,5 @@ BinPackArguments: false
|
|||
BinPackParameters: false
|
||||
ColumnLimit: 80
|
||||
ContinuationIndentWidth: 2
|
||||
IndentCaseLabels: false
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 2
|
||||
|
|
|
@ -10,7 +10,7 @@ STAGED=$(
|
|||
TARGETS=()
|
||||
while IFS= read -r FILENAME
|
||||
do
|
||||
if [[ "$FILENAME" =~ .*\.c ]] || [[ "$FILENAME" == .*\.h ]]; then
|
||||
if [[ "$FILENAME" =~ .*\.c$ ]] || [[ "$FILENAME" == .*\.h$ ]]; then
|
||||
TARGETS+=("${FILENAME}")
|
||||
fi
|
||||
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:
|
||||
|
||||
* `line`
|
||||
* The simplest prompt type. Takes in a free-form response submitted after
|
||||
encountering a newline (`\n`). The resulting environment variable has
|
||||
leading and trailing whitespace trimmed.
|
||||
* Takes in a free-form response submitted after encountering a newline (`\n`).
|
||||
The resulting environment variable has leading and trailing whitespace
|
||||
trimmed.
|
||||
* 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
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
enum FieldType {
|
||||
FT_LINE = 1,
|
||||
FT_YES = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,7 +6,7 @@ set -e
|
|||
# If set, Bash includes filenames beginning with a `.` in the results of
|
||||
# filename expansion. The filenames `.` and `..` must always be matched
|
||||
# explicitly, even if dotglob is set.
|
||||
shopt -s dotglob
|
||||
shopt -s dotglob extglob
|
||||
|
||||
# ============================================================
|
||||
# PROLOGUE
|
||||
|
@ -45,12 +45,18 @@ else
|
|||
MODULE_ARG="--module '$MODULE'"
|
||||
fi
|
||||
|
||||
if [ -n "$ECTO" ]; then
|
||||
ECTO_ARG=
|
||||
else
|
||||
ECTO_ARG="--no-ecto"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# BUILD
|
||||
# ============================================================
|
||||
|
||||
# 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.
|
||||
find "$BUILD" -type f -execdir chmod 644 {} +
|
||||
|
@ -61,14 +67,18 @@ chmod 755 "$BUILD"/.githooks/pre-commit
|
|||
# subdirectory to avoid interactive conflict resolution.
|
||||
nix develop "$BUILD" \
|
||||
--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.
|
||||
mv -f "$BUILD"/bs.project/* "$BUILD"
|
||||
rmdir "$BUILD"/bs.project
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
@ -98,14 +110,15 @@ done
|
|||
# Typically when building a Phoenix application, Mix will download
|
||||
# esbuild/tailwind binaries on demand. Within nix environments this is not
|
||||
# possible. Instead, specify them directly with these environment variables.
|
||||
sed -i \
|
||||
'42 s/$/,/;
|
||||
43 i \ \ path: System.get_env("MIX_ESBUILD_PATH")
|
||||
s/version: \"0.17.11\"/version: \"0.19.7\"/
|
||||
54 s/$/,/;
|
||||
55 i \ \ path: System.get_env("MIX_TAILWIND_PATH")
|
||||
s/version: \"3.3.2\"/version: \"3.3.5\"/' \
|
||||
"$BUILD/config/config.exs"
|
||||
sed -i '/config :esbuild/, /^$/ {
|
||||
s/0.17.11/0.19.7/; s/]$/],/;
|
||||
s/^$/\ \ path: System.get_env("MIX_ESBUILD_PATH")\n/}' \
|
||||
"$BUILD"/config/config.exs
|
||||
|
||||
sed -i '/config :tailwind/, /^$/ {
|
||||
s/3.3.2/3.3.5/; s/]$/],/;
|
||||
s/^$/\ \ path: System.get_env("MIX_TAILWIND_PATH")\n/}' \
|
||||
"$BUILD"/config/config.exs
|
||||
|
||||
# By default Phoenix generates a postgres configuration with assumed 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.
|
||||
cat <<EOF >> "$BUILD"/.gitignore
|
||||
# The default location of the generated database cluster.
|
||||
/db/
|
||||
|
||||
# Directory used by \`direnv\` to hold \`use flake\`-generated profiles.
|
||||
/.direnv/
|
||||
|
||||
|
@ -124,6 +134,14 @@ cat <<EOF >> "$BUILD"/.gitignore
|
|||
/result
|
||||
EOF
|
||||
|
||||
if [ -n "$ECTO" ]; then
|
||||
cat <<EOF >> "$BUILD"/.gitignore
|
||||
|
||||
# The default location of the generated database cluster.
|
||||
/db/
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# EPILOGUE
|
||||
# ============================================================
|
||||
|
|
|
@ -7,5 +7,9 @@
|
|||
"type": "line",
|
||||
"required": false,
|
||||
"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:
|
||||
```bash
|
||||
$ mix deps.get
|
||||
$ mix ecto.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);
|
||||
|
||||
// TODO: Dynamically size this value.
|
||||
|
@ -103,6 +103,38 @@ static const char *query_line(const struct Field *const field) {
|
|||
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(
|
||||
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) {
|
||||
struct Field *field = fields->buf[i];
|
||||
const char *response = 0;
|
||||
switch (field->type) {
|
||||
case FT_LINE:
|
||||
response = query_line(field);
|
||||
break;
|
||||
case FT_LINE: {
|
||||
char *response = query_line(field);
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -70,6 +70,8 @@ static struct Error *read_field(
|
|||
|
||||
if (strcmp_ci(type->valuestring, "line") == 0) {
|
||||
(*out)->type = FT_LINE;
|
||||
} else if (strcmp_ci(type->valuestring, "yes") == 0) {
|
||||
(*out)->type = FT_YES;
|
||||
} else {
|
||||
error = ERROR_NEW(
|
||||
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_prompt_invalid);
|
||||
sput_run_test(test_validator_valid_no_required);
|
||||
sput_run_test(test_validator_field_type_yes);
|
||||
|
||||
sput_finish_testing();
|
||||
|
||||
|
|
|
@ -240,4 +240,21 @@ static void test_validator_valid_no_required() {
|
|||
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 */
|
||||
|
|
Loading…
Reference in New Issue