Skip to content

Instantly share code, notes, and snippets.

@travisbhartwell
Created June 21, 2025 22:03
Show Gist options
  • Save travisbhartwell/2134385e45192eb695369597f93167dd to your computer and use it in GitHub Desktop.
Save travisbhartwell/2134385e45192eb695369597f93167dd to your computer and use it in GitHub Desktop.
Code coverage for MyProject
#: # -*- mode: shell-script; sh-shell: bash; sh-basic-offset: 4; sh-indentation: 4; coding: utf-8 -*-
#: # shellcheck shell=bash
:
#: # MyCmd Project Command Group Library
#: # Library for Project Task Runner Functionality
:
4: set -o nounset -o errexit -o errtrace -o pipefail
:
4: (return 0 >/dev/null 2>&1) || {
-: echo >&2 "$0 is a library only meant to be sourced."
-: exit 1
: }
:
4: mycmd:command_group.register_version "0.1"
4: mycmd:command_group.register_short_description "MyProject Command Group Library"
4: mycmd:command_group.register_help_text "The MyProject Project Task Runner"
:
4: [[ -n "${_MYCMD_SOURCING_FOR_HELP:-}" ]] && return
:
4: [[ -n "${_MYPROJECT_LIB:-}" ]] && return
8: readonly _MYPROJECT_LIB=1
:
4: mycmd:command_group.load_support_lib "task"
4: mycmd:command_group.load_support_lib "user"
:
8: declare -Agx _MYPROJECT_MAPPINGS=()
:
8: readonly _PROJECT_ROOT_DIRECTORY=0
8: readonly _PROJECT_TASK_DEFINITION_DIRECTORY=1
:
8: _MYPROJECT_USER_CONFIG_FILE="$(mycmd:command_group.get_config_file "mapping-config")"
4: readonly _MYPROJECT_USER_CONFIG_FILE
:
#: # --------------------------------------------------------------------------------------------------
#: # Project Locating Public Functions
F: function project.find_up() {
22: project._load_mapping_configuration
:
22: project._find_up "${@}"
: }
:
#: # --------------------------------------------------------------------------------------------------
#: # Project Locating Private Helper Functions
F: function project._load_mapping_configuration() {
28: _MYPROJECT_MAPPINGS=()
:
28: local -r mapping_config_file="${MYPROJECT_MAPPING_CONFIG:-${_MYPROJECT_USER_CONFIG_FILE}}"
28: project._load_mapping_config_file "${mapping_config_file}"
:
#: # Parse MYPROJECT_MAPPING last so it can override values from the configuration file
28: if [[ -n "${MYPROJECT_MAPPING-}" ]]; then
7: local -r project_root_directory="${MYPROJECT_MAPPING%%:*}"
7: local -r task_definition_directory="${MYPROJECT_MAPPING##*:}"
7: mycmd.trace "Checking '${MYPROJECT_MAPPING}' with project root directory '${project_root_directory}' and task definition directory '${task_definition_directory}'."
:
7: project._add_task_definition_directory_mapping_if_valid "${project_root_directory}" "${task_definition_directory}"
: fi
:
28: project._log_myproject_mappings
: }
:
F: function project._load_mapping_config_file() {
28: local -r mapping_config_file="${1}"
:
28: if ! [[ -e "${mapping_config_file}" ]]; then
20: mycmd.debug "MyProject Mapping Configuration file '${mapping_config_file}' does not exist."
20: return
: fi
:
16: local -a config_lines=()
:
8: readarray -t config_lines <"${mapping_config_file}"
:
8: mycmd.trace "Processing ${#config_lines[@]} lines from mapping config file '${mapping_config_file}'."
:
8: local line
8: local project_root_directory
8: local task_definition_directory
:
36: for line in "${config_lines[@]}"; do
36: mycmd.trace "Processing config file line:"
36: mycmd.trace "${line}"
:
36: project_root_directory="${line%%=*}"
36: task_definition_directory="${line##*=}"
:
36: project._add_task_definition_directory_mapping_if_valid "${project_root_directory}" "${task_definition_directory}"
: done
: }
:
F: function project._add_task_definition_directory_mapping_if_valid() {
43: local project_root_directory="${1}"
43: local task_definition_directory="${2}"
:
43: mycmd.trace "Checking mapping with project root directory '${project_root_directory}' and task definition directory '${task_definition_directory}'."
:
86: if ! project_root_directory="$(project._resolve_directory "${project_root_directory}")"; then
8: mycmd.debug "Error canonicalizing project root directory, ignoring mapping."
8: return
: fi
35: readonly project_root_directory
:
70: if ! task_definition_directory="$(project._resolve_directory "${task_definition_directory}")"; then
8: mycmd.debug "Error canonicalizing task definition directory, ignoring mapping."
8: return
: fi
27: readonly task_definition_directory
:
27: if ! project._is_valid_task_definition_directory "${task_definition_directory}"; then
8: mycmd.debug "'${task_definition_directory}' is not a valid task definition directory, ignoring mapping."
8: return
: fi
:
19: mycmd.trace "Configuring mapping for '${project_root_directory}' as '${task_definition_directory}'."
19: _MYPROJECT_MAPPINGS["${project_root_directory}"]="${task_definition_directory}"
: }
:
F: function project._log_myproject_mappings() {
#: # TODO: Use mycmd.print_table when it can use mycmd.trace
28: mycmd.trace "MyProject Mapping is configured with ${#_MYPROJECT_MAPPINGS[@]} entries.\n"
:
28: local project_root_directory
28: local task_definition_directory
18: for project_root_directory in "${!_MYPROJECT_MAPPINGS[@]}"; do
18: task_definition_directory="${_MYPROJECT_MAPPINGS["${project_root_directory}"]}"
18: mycmd.trace "'${project_root_directory}' is mapped to use the task definition directory '${task_definition_directory}'."
: done
: }
:
F: function project._resolve_directory() {
78: local directory="${1}"
78: directory=${directory/#\~/${HOME}}
78: mycmd.canonicalize_path "${directory}"
: }
:
F: function project._is_valid_task_definition_directory() {
98: local -r task_definition_directory="${1}"
:
98: if ! [[ -d "${task_definition_directory}" ]]; then
60: mycmd.debug "'${task_definition_directory}' does not exist."
60: return 1
: fi
:
76: if ! [[ "$(basename "${task_definition_directory}")" == "myproject" ]]; then
10: mycmd.debug "'${task_definition_directory}' is not named 'myproject'."
10: return 1
: fi
:
28: if ! [[ -f "${task_definition_directory}/main" ]]; then
1: mycmd.debug "Required file 'main' missing from '${task_definition_directory}'."
1: return 1
: fi
:
27: return 0
: }
:
F: function project._find_up() {
77: local -n project_root_directories="${1}"
77: local starting_directory="${2}"
:
154: if ! starting_directory="$(mycmd.canonicalize_path "${starting_directory}")"; then
1: mycmd.debug "'${starting_directory}' not found."
1: return 1
: fi
:
76: mycmd.trace "Looking in '${starting_directory}' for MyProject Task Definition directory."
:
76: if [[ -v _MYPROJECT_MAPPINGS[${starting_directory}] ]]; then
8: local -r task_definition_directory="${_MYPROJECT_MAPPINGS["${starting_directory}"]}"
:
8: mycmd.trace "Found MyProject Mapping for '${starting_directory}' as '${task_definition_directory}'."
:
8: project_root_directories["${_PROJECT_ROOT_DIRECTORY}"]="${starting_directory}"
#: # shellcheck disable=SC2034
8: project_root_directories["${_PROJECT_TASK_DEFINITION_DIRECTORY}"]="${task_definition_directory}"
8: return 0
: fi
:
68: local -r possible_path="${starting_directory}/myproject"
:
68: if project._is_valid_task_definition_directory "${possible_path}"; then
7: project_root_directories["${_PROJECT_ROOT_DIRECTORY}"]="${starting_directory}"
#: # shellcheck disable=SC2034
7: project_root_directories["${_PROJECT_TASK_DEFINITION_DIRECTORY}"]="${possible_path}"
7: return 0
: fi
:
61: if [[ "${starting_directory}" = "/" ]]; then
6: mycmd.debug "MyProject task definition directory not found."
6: return 1
: fi
:
55: project._find_up "${!project_root_directories}" "${starting_directory}/.."
: }
:
#: # --------------------------------------------------------------------------------------------------
#: # Project Task Definition File Loading
F: function project.load_closest_task_definition() {
2: local -a project_directories
2: if ! project.find_up project_directories "${PWD}"; then
-: mycmd.log "Not located in a MyProject Project."
-: return 1
: fi
:
2: local -r project_root_directory="${project_directories["${_PROJECT_ROOT_DIRECTORY}"]}"
2: local -r task_definition_directory="${project_directories["${_PROJECT_TASK_DEFINITION_DIRECTORY}"]}"
:
2: mycmd.output "Using project root at '${project_root_directory}'.\n"
2: mycmd.output "Using task definition directory at '${task_definition_directory}'.\n"
:
2: if ! project:task_registry.load_task_definition_file "${project_root_directory}" "${task_definition_directory}" main; then
-: mycmd.log "Error loading project task definition files."
-: return 1
: fi
: }
:
F: function project.load_all_task_definition_files() {
1: local -a project_directories
1: if ! project.find_up project_directories "${PWD}"; then
-: mycmd.log "Not located in a MyProject Project."
-: return 1
: fi
:
1: local -r project_root_directory="${project_directories["${_PROJECT_ROOT_DIRECTORY}"]}"
1: local -r task_definition_directory="${project_directories["${_PROJECT_TASK_DEFINITION_DIRECTORY}"]}"
:
1: if ! project:task_registry.load_all_task_definition_files "${project_root_directory}" "${task_definition_directory}"; then
-: mycmd.log "Error loading project task definition files."
-: return 1
: fi
: }
:
#: # --------------------------------------------------------------------------------------------------
#: # Task Execution
F: function project.execute_task() {
4: local fully_qualified_task_name="${1}"
4: shift
:
4: local return_code=0
:
#: # shellcheck disable=SC2034
4: local -A task
4: if ! project:task_registry.get_task "${fully_qualified_task_name}" task; then
3: mycmd.debug "Task '${fully_qualified_task_name}' is not found"
3: return_code=1
: fi
:
4: if (($# > 0 && "${return_code}" != 0)); then
2: fully_qualified_task_name="${fully_qualified_task_name}/${1}"
2: shift
:
2: if ! project:task_registry.get_task "${fully_qualified_task_name}" task; then
1: mycmd.log "Task '${fully_qualified_task_name}' is not found"
: else
1: return_code=0
: fi
: fi
:
4: if (("${return_code}" != 0)); then
2: mycmd.log "Task not found."
2: return 1
: fi
:
3: project:task.execute task "${@}" || return_code="${?}"
:
2: return "${return_code}"
: }
:
4: mycmd.trace "The MyProject command group library has been sourced."
#: # -*- mode: shell-script; sh-shell: bash; sh-basic-offset: 4; sh-indentation: 4; coding: utf-8 -*-
#: # shellcheck shell=bash
:
#: # MyProject Task Command Group Support Library
#: # Support for Task Definition Data Structures and Related Functions for MyProject
:
4: set -o nounset -o errexit -o errtrace -o pipefail
:
4: (return 0 >/dev/null 2>&1) || {
-: echo >&2 "$0 is a library only meant to be sourced."
-: exit 1
: }
:
4: [[ -n "${_MYPROJECT_TASK_LIB:-}" ]] && return
8: readonly _MYPROJECT_TASK_LIB=1
:
4: declare -Agx _MYPROJECT_TASKS
4: declare -Agx _MYPROJECT_TASKS_TASK_DEFINITION_FILES
4: declare -Agx _MYPROJECT_TASKS_ARGUMENT_COUNTS
4: declare -Agx _MYPROJECT_TASKS_ARGUMENT_INDICES
4: declare -agx _MYPROJECT_TASKS_ARGUMENTS
4: declare -Agx _MYPROJECT_LOADED_TASK_DEFINITION_FILES
:
8: readonly _TASK_FULLY_QUALIFIED_NAME_FIELD='task-fully-qualified-name'
8: readonly _TASK_NAME_FIELD='task-name'
8: readonly _TASK_NAMESPACED_PARTS_STRING_FIELD='task-namespaced-parts-string'
8: readonly _TASK_TASK_DEFINITION_FILE_FIELD='task-definition-file'
8: readonly _TASK_FUNCTION_FIELD='task-function'
8: readonly _TASK_ARGUMENT_COUNT_FIELD='task-argument-count'
8: readonly _TASK_ARGUMENT_INDEX_FIELD='task-argument-index'
:
F: function project:task_registry._reset_registry() {
10: mycmd.trace "Resetting the MyProject Task Registry."
10: _MYPROJECT_TASKS=()
10: _MYPROJECT_TASKS_TASK_DEFINITION_FILES=()
10: _MYPROJECT_TASKS_ARGUMENT_COUNTS=()
10: _MYPROJECT_TASKS_ARGUMENT_INDICES=()
10: _MYPROJECT_TASKS_ARGUMENTS=()
10: _MYPROJECT_LOADED_TASK_DEFINITION_FILES=()
10: unset MYPROJECT_ROOT_DIRECTORY
10: unset MYPROJECT_TASK_DEFINITION_DIRECTORY
: }
:
4: project:task_registry._reset_registry
:
F: function project:task_registry.load_task_definition_file() {
5: local -r project_root_directory="${1}"
5: local -r task_definition_directory="${2}"
5: local -r task_definition_file_name="${3}"
:
5: declare -gx MYPROJECT_ROOT_DIRECTORY="${project_root_directory}"
5: declare -gx MYPROJECT_TASK_DEFINITION_DIRECTORY="${task_definition_directory}"
:
5: project:task_registry._load_task_definition_file "${task_definition_file_name}"
: }
:
F: function project:task_registry.load_all_task_definition_files() {
1: local -r project_root_directory="${1}"
1: local -r task_definition_directory="${2}"
:
#: # First load main
1: if ! project:task_registry.load_task_definition_file "${project_root_directory}" "${task_definition_directory}" main; then
-: mycmd.log "Error loading main task definition file."
-: return 1
: fi
:
2: local -a task_definition_files=("${task_definition_directory}"/*)
:
1: local task_definition_file_path
1: local task_definition_file_name
:
2: for task_definition_file_path in "${task_definition_files[@]}"; do
4: task_definition_file_name="$(basename "${task_definition_file_path}")"
:
2: mycmd.trace "Attempting to load task definition file '${task_definition_file_name}'."
:
2: if ! project:task_registry.load_task_definition_file "${project_root_directory}" "${task_definition_directory}" "${task_definition_file_name}"; then
-: mycmd.log "Error loading task definition file name '${task_definition_file_name}'."
-: return 1
: fi
: done
: }
:
F: function project:task_registry._load_task_definition_file() {
22: if [[ ! -v MYPROJECT_ROOT_DIRECTORY && ! -v MYPROJECT_TASK_DEFINITION_DIRECTORY ]]; then
-: mycmd.debug "Error: MYPROJECT_ROOT DIRECTORY and MYPROJECT_TASK_DEFINITION_DIRECTORY must be set."
-: return 1
: fi
:
22: local -r task_definition_file_name="${1}"
:
22: if [[ -v _MYPROJECT_LOADED_TASK_DEFINITION_FILES[${task_definition_file_name}] ]]; then
18: mycmd.trace "'${task_definition_file_name}' already loaded."
18: return 0
: fi
:
4: mycmd.trace "Loading '${task_definition_file_name}' from task definition directory '${MYPROJECT_TASK_DEFINITION_DIRECTORY}' for project '${MYPROJECT_ROOT_DIRECTORY}'."
:
4: declare -gx MYPROJECT_CURRENT_TASK_FILE="${task_definition_file_name}"
:
4: local -r task_definition_file="${MYPROJECT_TASK_DEFINITION_DIRECTORY}/${task_definition_file_name}"
:
4: local result=0
4: if ! mycmd.source_lib_by_path_if_found "${task_definition_file}"; then
-: mycmd.log "Error loading task definition file '${task_definition_file}'."
-: result=1
: fi
:
4: unset MYPROJECT_CURRENT_TASK_FILE
:
4: if ((result != 0)); then
-: return "${result}"
: fi
:
4: _MYPROJECT_LOADED_TASK_DEFINITION_FILES[${task_definition_file_name}]=1
:
4: return 0
: }
:
F: function project:task_registry.new_task() {
17: local -r task_definition_file_name="${1}"
17: local -r fully_qualified_task_name="${2}"
17: local -r task_function_name="${3}"
17: shift 3
:
17: local -r argc="$#"
:
17: mycmd.trace "Attempting to register task '${fully_qualified_task_name}' with function '${task_function_name}'."
:
17: if [[ -v _MYPROJECT_TASKS["${fully_qualified_task_name}"] ]]; then
2: local existing_task_function_name="${_MYPROJECT_TASKS["${fully_qualified_task_name}"]}"
:
2: if [[ "${existing_task_function_name}" != "${task_function_name}" ]]; then
1: mycmd.debug "'${fully_qualified_task_name}' is already registered with function '${existing_task_function_name}', not '${task_function_name}'."
1: return 1
: else
1: mycmd.debug "'${fully_qualified_task_name}' is already registered."
1: return 0
: fi
: fi
:
15: _MYPROJECT_TASKS["${fully_qualified_task_name}"]="${task_function_name}"
15: _MYPROJECT_TASKS_TASK_DEFINITION_FILES["${fully_qualified_task_name}"]="${task_definition_file_name}"
15: _MYPROJECT_TASKS_ARGUMENT_COUNTS["${fully_qualified_task_name}"]="${argc}"
:
15: if ((argc > 0)); then
3: local index="${#_MYPROJECT_TASKS_ARGUMENTS[@]}"
3: _MYPROJECT_TASKS_ARGUMENTS+=("${@}")
3: _MYPROJECT_TASKS_ARGUMENT_INDICES["${fully_qualified_task_name}"]="${index}"
:
3: mycmd.trace "Created task arguments for task '${fully_qualified_task_name}' at index ${index} with argument count ${argc}."
: else
12: mycmd.trace "Registered 0 arguments for task '${fully_qualified_task_name}'."
: fi
:
15: mycmd.trace "Successfully registered task '${fully_qualified_task_name}' from task definition file '${task_definition_file_name}' with function '${task_function_name}' with ${argc} arguments."
15: return 0
: }
:
F: function project:task_registry.get_task() {
17: local -r fully_qualified_task_name="${1}"
17: local -n task_struct_ref="${2}"
:
17: mycmd.trace "Attempting to load task with fully qualified name '${fully_qualified_task_name}'."
:
17: task_struct_ref=()
:
17: if ! project:task_registry._load_task_definition_file_for_task "${fully_qualified_task_name}"; then
-: mycmd.debug "Error loading task definition file for '${fully_qualified_task_name}'."
-: return 1
: fi
:
17: if ! [[ -v _MYPROJECT_TASKS["${fully_qualified_task_name}"] ]]; then
5: mycmd.debug "Task '${fully_qualified_task_name}' is not found."
5: return 1
: fi
:
12: mycmd.trace "Returning task definition:"
12: task_struct_ref["${_TASK_FULLY_QUALIFIED_NAME_FIELD}"]="${fully_qualified_task_name}"
12: mycmd.trace "- ${_TASK_FULLY_QUALIFIED_NAME_FIELD}: ${task_struct_ref["${_TASK_FULLY_QUALIFIED_NAME_FIELD}"]}"
:
12: local task_name
24: task_name="$(basename "${fully_qualified_task_name}")"
12: readonly task_name
12: task_struct_ref["${_TASK_NAME_FIELD}"]="${task_name}"
12: mycmd.trace "- ${_TASK_NAME_FIELD}: ${task_struct_ref["${_TASK_NAME_FIELD}"]}"
:
12: local -r namespaced_parts_string="${fully_qualified_task_name//\// }"
12: task_struct_ref["${_TASK_NAMESPACED_PARTS_STRING_FIELD}"]="${namespaced_parts_string}"
12: mycmd.trace "- ${_TASK_NAMESPACED_PARTS_STRING_FIELD}: ${task_struct_ref["${_TASK_NAMESPACED_PARTS_STRING_FIELD}"]}"
:
12: task_struct_ref["${_TASK_TASK_DEFINITION_FILE_FIELD}"]="${_MYPROJECT_TASKS_TASK_DEFINITION_FILES["${fully_qualified_task_name}"]}"
12: mycmd.trace "- ${_TASK_TASK_DEFINITION_FILE_FIELD}: ${task_struct_ref["${_TASK_TASK_DEFINITION_FILE_FIELD}"]}"
:
12: task_struct_ref["${_TASK_FUNCTION_FIELD}"]="${_MYPROJECT_TASKS["${fully_qualified_task_name}"]}"
12: mycmd.trace "- ${_TASK_FUNCTION_FIELD}: ${task_struct_ref["${_TASK_FUNCTION_FIELD}"]}"
:
12: local -r argc="${_MYPROJECT_TASKS_ARGUMENT_COUNTS["${fully_qualified_task_name}"]}"
12: task_struct_ref["${_TASK_ARGUMENT_COUNT_FIELD}"]="${argc}"
12: mycmd.trace "- ${_TASK_ARGUMENT_COUNT_FIELD}: ${task_struct_ref["${_TASK_ARGUMENT_COUNT_FIELD}"]}"
:
12: if (("${argc}" > 0)); then
2: if ! [[ -v _MYPROJECT_TASKS_ARGUMENT_INDICES["${fully_qualified_task_name}"] ]]; then
-: mycmd.debug "Required argument indices data missing for '${fully_qualified_task_name}'."
-: return 1
: fi
:
2: task_struct_ref["${_TASK_ARGUMENT_INDEX_FIELD}"]="${_MYPROJECT_TASKS_ARGUMENT_INDICES["${fully_qualified_task_name}"]}"
2: mycmd.trace "- ${_TASK_ARGUMENT_INDEX_FIELD}: ${task_struct_ref["${_TASK_ARGUMENT_INDEX_FIELD}"]}"
: else
10: mycmd.trace "- ${_TASK_ARGUMENT_INDEX_FIELD}: No value set."
: fi
: }
:
F: function project:task_registry._load_task_definition_file_for_task() {
17: local -r fully_qualified_task_name="${1}"
:
17: mycmd.trace "Attempting to load task file, if necessary, for task with fully qualified name '${fully_qualified_task_name}'."
:
17: local task_definition_file_name
17: if [[ "${fully_qualified_task_name}" == *'/'* ]]; then
7: task_definition_file_name="${fully_qualified_task_name%%/*}"
: else
10: task_definition_file_name="main"
: fi
17: readonly task_definition_file_name
:
17: project:task_registry._load_task_definition_file "${task_definition_file_name}"
: }
:
F: function project:task_registry.list_tasks() {
-: if (("${#_MYPROJECT_TASKS[@]}" == 0)); then
-: mycmd.output "There are no registered tasks."
-: return 0
: fi
:
-: mycmd.output "The following tasks are registered:"
:
-: local -a all_tasks_namespaced_parts
-: if ! project:task_registry._get_all_task_namespaced_parts all_tasks_namespaced_parts; then
-: mycmd.log "Error getting all task namespaced parts"
-: return 1
: fi
:
-: local task
-: for task in "${all_tasks_namespaced_parts[@]}"; do
-: mycmd.output "${task}"
: done
: }
:
F: function project:task_registry._get_all_task_namespaced_parts() {
1: local -n all_tasks_namespaced_parts_ref="${1}"
:
2: local -a main_file_tasks=()
2: local -a other_tasks=()
:
1: local fully_qualified_task_name
1: local task_definition_file_name
1: local namespaced_parts_string
1: local -A task
:
6: for fully_qualified_task_name in "${!_MYPROJECT_TASKS[@]}"; do
6: task=()
:
6: if ! project:task_registry.get_task "${fully_qualified_task_name}" task; then
-: mycmd.debug "Unexpected error getting task '${fully_qualified_task_name}'."
-: return 1
: fi
:
12: task_definition_file_name="$(project:task.get_task_definition_file_name task)"
12: namespaced_parts_string="$(project:task.get_namespaced_parts_as_string task)"
:
6: mycmd.trace "Found task with fully qualified name '${fully_qualified_task_name}', with namespaced parts string '${namespaced_parts_string}' with task file '${task_definition_file_name}'."
:
6: if [[ "${task_definition_file_name}" == "main" ]]; then
3: main_file_tasks+=("${namespaced_parts_string}")
: else
3: other_tasks+=("${namespaced_parts_string}")
: fi
: done
:
1: all_tasks_namespaced_parts_ref=()
:
1: if (("${#main_file_tasks[@]}" > 0)); then
#: # shellcheck disable=SC2034
4: readarray -t all_tasks_namespaced_parts_ref < \
-: <(printf '%s\n' "${main_file_tasks[@]}" | LC_ALL=en_US.UTF-8 sort || true)
: fi
:
1: if (("${#other_tasks[@]}" > 0)); then
1: local size_so_far="${#all_tasks_namespaced_parts_ref[@]}"
:
#: # shellcheck disable=SC2034
4: readarray -t -O "${size_so_far}" all_tasks_namespaced_parts_ref < \
-: <(printf '%s\n' "${other_tasks[@]}" | LC_ALL=en_US.UTF-8 sort || true)
: fi
: }
:
F: function project:task.get_fully_qualified_name() {
-: project:task._get_field_from_struct \
10: "${_TASK_FULLY_QUALIFIED_NAME_FIELD}" \
-: "${@}"
: }
:
F: function project:task.get_name() {
-: project:task._get_field_from_struct \
4: "${_TASK_NAME_FIELD}" \
-: "${@}"
: }
:
F: function project:task.get_namespaced_parts_as_string() {
-: project:task._get_field_from_struct \
13: "${_TASK_NAMESPACED_PARTS_STRING_FIELD}" \
-: "${@}"
: }
:
F: function project:task.get_task_definition_file_name() {
-: project:task._get_field_from_struct \
13: "${_TASK_TASK_DEFINITION_FILE_FIELD}" \
-: "${@}"
: }
:
F: function project:task.get_function_name() {
-: project:task._get_field_from_struct \
11: "${_TASK_FUNCTION_FIELD}" \
-: "${@}"
: }
:
F: function project:task.get_argument_count() {
-: project:task._get_field_from_struct \
9: "${_TASK_ARGUMENT_COUNT_FIELD}" \
-: "${@}"
: }
:
F: function project:task.get_argument_index() {
-: project:task._get_field_from_struct \
6: "${_TASK_ARGUMENT_INDEX_FIELD}" \
-: "${@}"
: }
:
F: function project:task._get_field_from_struct() {
66: local -r field_name="${1}"
#: # shellcheck disable=SC2178
66: local -n task_struct_ref="${2}"
:
66: if [[ -v task_struct_ref["${field_name}"] ]]; then
63: echo "${task_struct_ref["${field_name}"]}"
63: return 0
: else
3: mycmd.debug "Missing required task field '${field_name}'."
3: return 1
: fi
: }
:
F: function project:task.get_arguments() {
#: # shellcheck disable=SC2178
5: local -n task_struct_ref="${1}"
5: local -n arguments_ref="${2}"
:
5: local fully_qualified_task_name
10: fully_qualified_task_name="$(project:task.get_fully_qualified_name "${!task_struct_ref}")"
5: readonly fully_qualified_task_name
:
5: local -i argc
10: argc="$(project:task.get_argument_count "${!task_struct_ref}")"
5: readonly argc
:
5: if ((argc == 0)); then
3: mycmd.trace "No arguments defined for task '${fully_qualified_task_name}'."
#: # shellcheck disable=SC2034
3: arguments_ref=()
3: return 0
: fi
:
2: local -i index
4: if ! index="$(project:task.get_argument_index "${!task_struct_ref}")"; then
-: mycmd.log "Unexpected error: index should be set for task '${fully_qualified_task_name}'."
-: return 1
: fi
2: readonly index
:
2: arguments_ref=("${_MYPROJECT_TASKS_ARGUMENTS[@]:index:argc}")
2: mycmd.trace "Set arguments for '${fully_qualified_task_name}' in ${!arguments_ref} as ${arguments_ref[*]}."
: }
:
F: function project:task.function_exists() {
#: # shellcheck disable=SC2178
4: local -n task_struct_ref="${1}"
:
4: local task_function_name
8: task_function_name="$(project:task.get_function_name "${!task_struct_ref}")"
4: readonly task_function_name
:
4: if ! mycmd.function_exists "${task_function_name}"; then
1: local fully_qualified_task_name
2: fully_qualified_task_name="$(project:task.get_fully_qualified_name "${!task_struct_ref}")"
1: readonly fully_qualified_task_name
:
1: mycmd.log "Unknown function '${task_function_name}' for task '${fully_qualified_task_name}'."
1: return 1
: fi
:
3: return 0
: }
:
F: function project:task.execute() {
#: # shellcheck disable=SC2178
4: local -n task_struct_ref="${1}"
4: shift
:
4: if ! project:task.function_exists "${!task_struct_ref}"; then
1: return 1
: fi
:
3: local task_function_name
6: task_function_name="$(project:task.get_function_name "${!task_struct_ref}")"
3: readonly task_function_name
:
3: local namespaced_parts_string
6: namespaced_parts_string="$(project:task.get_namespaced_parts_as_string "${!task_struct_ref}")"
3: readonly namespaced_parts_string
:
3: local task_definition_file_name
6: task_definition_file_name="$(project:task.get_task_definition_file_name "${!task_struct_ref}")"
3: readonly task_definition_file_name
:
6: local -a task_arguments=()
3: project:task.get_arguments "${!task_struct_ref}" task_arguments
3: set -- "${task_arguments[@]}" "${@}"
:
3: declare -gx MYPROJECT_CURRENT_TASK_FILE="${task_definition_file_name}"
:
3: mycmd.output "➡️ Executing task '${namespaced_parts_string}'..."
:
3: local return_code=0
4: "${task_function_name}" "${@}" || return_code="${?}"
:
3: if ((return_code == 0)); then
2: mycmd.output "✅ Task '${namespaced_parts_string}' succeeded."
: else
1: mycmd.output "❌ Task '${namespaced_parts_string}' failed."
: fi
:
3: return "${return_code}"
: }
:
4: mycmd.trace "The MyProject Task command group support library has been sourced."
#: # -*- mode: shell-script; sh-shell: bash; sh-basic-offset: 4; sh-indentation: 4; coding: utf-8 -*-
#: # shellcheck shell=bash
:
#: # MyProject User Command Group Support Library
#: # All of the User-facing Functions for MyProject
:
4: set -o nounset -o errexit -o errtrace -o pipefail
:
4: (return 0 >/dev/null 2>&1) || {
-: echo >&2 "$0 is a library only meant to be sourced."
-: exit 1
: }
:
4: [[ -n "${_MYPROJECT_USER_LIB:-}" ]] && return
8: readonly _MYPROJECT_USER_LIB=1
:
4: mycmd:command_group.load_support_lib "task"
:
F: function myproject.register_task() {
12: local -r task_name="${1}"
12: local -r task_function_name="${2}"
12: shift 2
:
12: if ! [[ -v MYPROJECT_CURRENT_TASK_FILE ]]; then
-: mycmd.debug "Required variable MYPROJECT_CURRENT_TASK_FILE is not set"
-: return 1
: fi
:
12: local fully_qualified_task_name
12: if [[ "${MYPROJECT_CURRENT_TASK_FILE}" == "main" ]]; then
6: fully_qualified_task_name="${task_name}"
: else
6: fully_qualified_task_name="${MYPROJECT_CURRENT_TASK_FILE}/${task_name}"
: fi
12: readonly fully_qualified_task_name
:
-: project:task_registry.new_task \
12: "${MYPROJECT_CURRENT_TASK_FILE}" \
-: "${fully_qualified_task_name}" \
-: "${task_function_name}" \
-: "${@}"
: }
:
4: mycmd.trace "The MyProject User command group support library has been sourced."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment