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
147 changes: 23 additions & 124 deletions cli/bash/commands/basectl/subcommands/demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,130 +34,29 @@ base_demo_usage_error() {
}

base_demo_subcommand_main() {
local project="" explicit_project="" wrapper resolve_output resolved_name project_root manifest_path demo_script command_runner
local quoted_demo_script command_to_run display_command
local dry_run=0
local args=() extra_args=() project_args=() parser_args=()
local route_venv_dir uses_uv_manager trust_required
local positional_count=0
# shellcheck disable=SC2034 # base_arg_parse receives caller-owned arrays by name.
local -a option_specs=(
"debug|flag|-v"
"workspace|value|--workspace"
"project|value|--project"
"dry_run|flag|--dry-run"
)
local -a positionals=()
local -A parsed_options=()

while (($#)); do
case "$1" in
--)
shift
extra_args=("$@")
break
;;
-h|--help|help)
base_demo_subcommand_usage
return 0
;;
-v)
parser_args+=("$1")
args+=(--debug)
shift
;;
--workspace)
[[ -n "${2:-}" ]] || {
base_demo_usage_error "Option '--workspace' requires an argument."
return $?
}
parser_args+=("--workspace=$2")
args+=(--workspace "$2")
shift 2
;;
--workspace=*)
parser_args+=("$1")
args+=("$1")
shift
;;
--project)
[[ -n "${2:-}" ]] || {
base_demo_usage_error "Option '--project' requires an argument."
return $?
}
[[ -z "$explicit_project" ]] || {
base_demo_usage_error "Option '--project' may be specified only once."
return $?
}
explicit_project="$2"
parser_args+=("--project=$2")
shift 2
;;
--dry-run)
parser_args+=("$1")
shift
;;
-*)
base_demo_usage_error "Unknown demo option '$1'."
return $?
;;
*)
positional_count=$((positional_count + 1))
if ((positional_count > 1)); then
base_demo_usage_error "The 'demo' command accepts one project name."
return $?
fi
parser_args+=("$1")
shift
;;
esac
done

if ! base_arg_parse parsed_options positionals option_specs -- "${parser_args[@]}"; then
base_demo_usage_error "Could not parse demo arguments."
return $?
fi

if ((${#positionals[@]} > 1)); then
base_demo_usage_error "The 'demo' command accepts one project name."
return $?
fi
if ((${#positionals[@]} == 1)); then
project="${positionals[0]}"
fi

explicit_project="${parsed_options[project]:-}"
dry_run="${parsed_options[dry_run]:-0}"
[[ -z "$explicit_project" || -z "$project" ]] || {
base_demo_usage_error "The 'demo' command does not accept a positional project with --project."
return $?
}

wrapper="$BASE_HOME/bin/base-wrapper"
[[ -x "$wrapper" ]] || base_std_fatal_error "Base Python wrapper '$wrapper' is missing or is not executable."

if [[ -n "$explicit_project" ]]; then
project_args+=(--project "$explicit_project")
elif [[ -n "$project" ]]; then
project_args+=("$project")
fi
resolve_output="$("$wrapper" --project base base_projects demo-script "${project_args[@]}" "${args[@]}" --format command-protocol)" || return $?
base_command_protocol_decode_one demo "$resolve_output" || {
base_std_fatal_error "Unable to resolve demo script for project '${project:-current project}'."
}
resolved_name="${BASE_COMMAND_PROTOCOL_FIELDS[project_name]}"
project_root="${BASE_COMMAND_PROTOCOL_FIELDS[project_root]}"
manifest_path="${BASE_COMMAND_PROTOCOL_FIELDS[manifest_path]}"
route_venv_dir="${BASE_COMMAND_PROTOCOL_FIELDS[project_venv_dir]}"
uses_uv_manager="${BASE_COMMAND_PROTOCOL_FIELDS[uses_uv_manager]}"
trust_required="${BASE_COMMAND_PROTOCOL_FIELDS[manifest_command_trust_required]}"
base_project_set_history_context "$resolved_name" "$project_root" "$manifest_path"
demo_script="${BASE_COMMAND_PROTOCOL_FIELDS[demo_script]}"
command_runner="${BASE_COMMAND_PROTOCOL_FIELDS[runner]}"

[[ -n "$resolved_name" && -n "$project_root" && -n "$manifest_path" && -n "$demo_script" ]] || {
base_std_fatal_error "Unable to resolve demo script for project '${project:-current project}'."
}
local project="" demo_script command_runner
local quoted_demo_script command_to_run display_command dry_run
local extra_args=()
local resolved_name project_root manifest_path route_venv_dir uses_uv_manager trust_required

base_project_command_parse_args \
demo base_demo_subcommand_usage base_demo_usage_error \
"The 'demo' command accepts one project name." "$@" || return $?
[[ "$BASE_PROJECT_COMMAND_HELP_SHOWN" == 1 ]] && return 0
project="$BASE_PROJECT_COMMAND_PROJECT"
dry_run="$BASE_PROJECT_COMMAND_DRY_RUN"
extra_args=("${BASE_PROJECT_COMMAND_EXTRA_ARGS[@]}")

base_project_command_resolve_context \
demo-script demo demo_script "demo script" "${project:-current project}" || return $?
resolved_name="$BASE_PROJECT_COMMAND_RESOLVED_NAME"
project_root="$BASE_PROJECT_COMMAND_RESOLVED_ROOT"
manifest_path="$BASE_PROJECT_COMMAND_RESOLVED_MANIFEST"
route_venv_dir="$BASE_PROJECT_COMMAND_ROUTE_VENV"
uses_uv_manager="$BASE_PROJECT_COMMAND_USES_UV"
trust_required="$BASE_PROJECT_COMMAND_TRUST_REQUIRED"
demo_script="$BASE_PROJECT_COMMAND_RESOLVED_ACTION"
command_runner="$BASE_PROJECT_COMMAND_RUNNER"

command_runner="${command_runner:-}"
printf -v quoted_demo_script '%q' "$demo_script"
Expand Down
174 changes: 174 additions & 0 deletions cli/bash/commands/basectl/subcommands/project_command_helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,180 @@ base_project_require_manifest_command_trust() {
"$wrapper" --project base base_trust require "$project" --manifest "$manifest_path"
}

base_project_command_parse_args() {
local command_name="$1"
local usage_function="$2"
local usage_error_function="$3"
local positional_error="$4"
shift 4

local project="" explicit_project="" dry_run=0
local positional_count=0
local args=() extra_args=() parser_args=() positionals=()
# shellcheck disable=SC2034 # base_arg_parse reads the caller-owned option specification by name.
local -a option_specs=(
"debug|flag|-v"
"workspace|value|--workspace"
"project|value|--project"
"dry_run|flag|--dry-run"
)
local -A parsed_options=()

# shellcheck disable=SC2034 # The selected project is consumed by test.sh and demo.sh.
BASE_PROJECT_COMMAND_PROJECT=""
# shellcheck disable=SC2034 # These shared parse outputs are consumed by test.sh and demo.sh.
BASE_PROJECT_COMMAND_DRY_RUN=0
# shellcheck disable=SC2034 # The parser returns help to its caller without resolving a project.
BASE_PROJECT_COMMAND_HELP_SHOWN=0
BASE_PROJECT_COMMAND_ARGUMENTS=()
# shellcheck disable=SC2034 # Passthrough arguments are consumed by test.sh and demo.sh.
BASE_PROJECT_COMMAND_EXTRA_ARGS=()
BASE_PROJECT_COMMAND_SELECTION_ARGS=()

while (($#)); do
case "$1" in
--)
shift
extra_args=("$@")
break
;;
-h|--help|help)
# shellcheck disable=SC2034 # The caller must skip resolution after help is printed.
BASE_PROJECT_COMMAND_HELP_SHOWN=1
"$usage_function"
return 0
;;
-v)
parser_args+=("$1")
args+=(--debug)
shift
;;
--workspace)
[[ -n "${2:-}" ]] || {
"$usage_error_function" "Option '--workspace' requires an argument."
return $?
}
parser_args+=("--workspace=$2")
args+=(--workspace "$2")
shift 2
;;
--workspace=*)
parser_args+=("$1")
args+=("$1")
shift
;;
--project)
[[ -n "${2:-}" ]] || {
"$usage_error_function" "Option '--project' requires an argument."
return $?
}
[[ -z "$explicit_project" ]] || {
"$usage_error_function" "Option '--project' may be specified only once."
return $?
}
explicit_project="$2"
parser_args+=("--project=$2")
shift 2
;;
--dry-run)
parser_args+=("$1")
shift
;;
-*)
"$usage_error_function" "Unknown $command_name option '$1'."
return $?
;;
*)
positional_count=$((positional_count + 1))
if ((positional_count > 1)); then
"$usage_error_function" "$positional_error"
return $?
fi
parser_args+=("$1")
shift
;;
esac
done

if ! base_arg_parse parsed_options positionals option_specs -- "${parser_args[@]}"; then
"$usage_error_function" "Could not parse $command_name arguments."
return $?
fi

if ((${#positionals[@]} > 1)); then
"$usage_error_function" "$positional_error"
return $?
fi
if ((${#positionals[@]} == 1)); then
project="${positionals[0]}"
fi

explicit_project="${parsed_options[project]:-}"
dry_run="${parsed_options[dry_run]:-0}"
[[ -z "$explicit_project" || -z "$project" ]] || {
"$usage_error_function" "The '$command_name' command does not accept a positional project with --project."
return $?
}

# shellcheck disable=SC2034 # The selected project is consumed by test.sh and demo.sh.
BASE_PROJECT_COMMAND_PROJECT="$project"
# shellcheck disable=SC2034 # Dry-run mode is consumed by test.sh and demo.sh.
BASE_PROJECT_COMMAND_DRY_RUN="$dry_run"
BASE_PROJECT_COMMAND_ARGUMENTS=("${args[@]}")
# shellcheck disable=SC2034 # Passthrough arguments are consumed by test.sh and demo.sh.
BASE_PROJECT_COMMAND_EXTRA_ARGS=("${extra_args[@]}")
if [[ -n "$explicit_project" ]]; then
BASE_PROJECT_COMMAND_SELECTION_ARGS=(--project "$explicit_project")
elif [[ -n "$project" ]]; then
BASE_PROJECT_COMMAND_SELECTION_ARGS=("$project")
fi
return 0
}

base_project_command_resolve_context() {
local resolver_command="$1"
local protocol_type="$2"
local command_field="$3"
local command_description="$4"
local display_project="$5"
local wrapper="$BASE_HOME/bin/base-wrapper"
local resolve_output

[[ -x "$wrapper" ]] || base_std_fatal_error "Base Python wrapper '$wrapper' is missing or is not executable."

resolve_output="$("$wrapper" --project base base_projects "$resolver_command" \
"${BASE_PROJECT_COMMAND_SELECTION_ARGS[@]}" "${BASE_PROJECT_COMMAND_ARGUMENTS[@]}" \
--format command-protocol)" || return $?
base_command_protocol_decode_one "$protocol_type" "$resolve_output" || {
base_std_fatal_error "Unable to resolve $command_description for project '$display_project'."
}

BASE_PROJECT_COMMAND_RESOLVED_NAME="${BASE_COMMAND_PROTOCOL_FIELDS[project_name]}"
BASE_PROJECT_COMMAND_RESOLVED_ROOT="${BASE_COMMAND_PROTOCOL_FIELDS[project_root]}"
BASE_PROJECT_COMMAND_RESOLVED_MANIFEST="${BASE_COMMAND_PROTOCOL_FIELDS[manifest_path]}"
# shellcheck disable=SC2034 # Venv route metadata is consumed by test.sh and demo.sh.
BASE_PROJECT_COMMAND_ROUTE_VENV="${BASE_COMMAND_PROTOCOL_FIELDS[project_venv_dir]}"
# shellcheck disable=SC2034 # Manager route metadata is consumed by test.sh and demo.sh.
BASE_PROJECT_COMMAND_USES_UV="${BASE_COMMAND_PROTOCOL_FIELDS[uses_uv_manager]}"
# shellcheck disable=SC2034 # Trust metadata is consumed by test.sh and demo.sh.
BASE_PROJECT_COMMAND_TRUST_REQUIRED="${BASE_COMMAND_PROTOCOL_FIELDS[manifest_command_trust_required]}"
BASE_PROJECT_COMMAND_RESOLVED_ACTION="${BASE_COMMAND_PROTOCOL_FIELDS[$command_field]}"
# shellcheck disable=SC2034 # The runner is consumed by test.sh and demo.sh.
BASE_PROJECT_COMMAND_RUNNER="${BASE_COMMAND_PROTOCOL_FIELDS[runner]}"

[[ -n "$BASE_PROJECT_COMMAND_RESOLVED_NAME" && \
-n "$BASE_PROJECT_COMMAND_RESOLVED_ROOT" && \
-n "$BASE_PROJECT_COMMAND_RESOLVED_MANIFEST" && \
-n "$BASE_PROJECT_COMMAND_RESOLVED_ACTION" ]] || {
base_std_fatal_error "Unable to resolve $command_description for project '$display_project'."
}

base_project_set_history_context \
"$BASE_PROJECT_COMMAND_RESOLVED_NAME" \
"$BASE_PROJECT_COMMAND_RESOLVED_ROOT" \
"$BASE_PROJECT_COMMAND_RESOLVED_MANIFEST"
}

base_project_activate_environment() {
local project="$1"
local project_root="$2"
Expand Down
Loading
Loading