Add a yes/no prompt type. (#13)

pull/1/head
Joshua Potter 2023-12-13 12:03:42 -07:00 committed by GitHub
parent 6fb8b6c516
commit 250f58bcc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 203 additions and 34 deletions

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -17,6 +17,7 @@
*/ */
enum FieldType { enum FieldType {
FT_LINE = 1, FT_LINE = 1,
FT_YES = 2,
}; };
/** /**

View File

@ -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
# ============================================================ # ============================================================

View File

@ -7,5 +7,9 @@
"type": "line", "type": "line",
"required": false, "required": false,
"prompt": "Module> " "prompt": "Module> "
},
"ecto": {
"type": "yes",
"prompt": "Include Ecto? [Yn] "
} }
} }

View File

@ -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
``` ```

View File

@ -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.

View File

@ -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;
} }

View File

@ -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,

View File

@ -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();

View File

@ -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 */