#!/usr/bin/env bash

# Usage:
#   bash <(curl -L https://visionsystemsinc.github.io/vsi_common/new_just)

temp_file="$(mktemp)"

if [ -z "${VSI_COMMON_DIR+set}" ]; then
  VSI_COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.."; pwd)"
fi

# TODO: Add sphinx by default
# TODO: Add CI by default

# Use a command like true, so that when I comment, it still works
# COMMENT NEXT LINE when developing, uncomment before committing.
cat - << 'true' > "${temp_file}"

#*# just/new_just

#**
# .. default-domain:: bash
#
# ============
# New J.U.S.T.
# ============
#
# .. file:: new_just
#
# Create a new just project
#
# There are many moving pieces to set up a working just project and use
# all the features. Just getting started is hard, without knowing where to
# start. This script will create all the files necessary to have a new
# docker compose based just project up and running.
#
# .. rubric:: Usage
#
# By default, :file:`new_just` will prompt the user for questions on the initial
# setup parameters. Default values are supplied for all questions, and enter
# can be pressed to accept all the defaults. All of the questions can be set
# using environment variables, making them scriptable.
#
# After :file:`new_just` is called, a few git commands need to be called. This is not
# done for you, in case it will break your existing repository. These
# commands are printed out on stdout, while everything else is printed on
# stderr, making them scriptable.
#
# If any of the files written to exist, they are not overwritten, and a warning
# message is printed out.
#
# .. seealso::
#
#   :env:`new_just new_just PROJECT_DIR`, :env:`new_just new_just PROJECT_NAME`, :env:`new_just new_just PROJECT_PREFIX`
#   :env:`new_just new_just REPO_NAME`, :env:`new_just new_just JUSTFILE`, :env:`new_just new_just SETUPFILE`
#   :env:`new_just new_just VSI_DIR`
#**

# These functions are auto replaced by .circleci/config.yml when uploaded to
# https://visionsystemsinc.github.io/vsi_common/new_just using the pattern
# "function function_name(){ :;}"

if [ -f "${VSI_COMMON_DIR}/linux/ask_question" ]; then
  source "${VSI_COMMON_DIR}/linux/ask_question"
else
ask_question () 
{ 
    if [ "${#}" -gt "2" ]; then
        local default_response="${3}";
    else
        local default_response=;
    fi;
    if [ "${!2=}" != "" ]; then
        local override="${!2}";
    fi;
    while true; do
        if [ "${override=}" != "" ]; then
            echo "${1} ${!2}";
            ans="${override}";
            override="";
        else
            if [ "${default_response}" == "" ]; then
                read -r -p "${1} " ans;
            else
                read -r -p "${1} (${default_response}) " ans;
            fi;
        fi;
        if [ "${ans}" == "" ]; then
            ans="${default_response}";
        fi;
        case ${ans} in 
            [Yy]*)
                printf -v "${2}" '%i' 1;
                break
            ;;
            [Nn]*)
                printf -v "${2}" '%i' 0;
                break
            ;;
            '?'*)
                echo "Please answer yes or no.";
                echo "Any answer starting with y/Y/n/N will be accepted";
                echo "You can also export the variable '${2}' to \"y\" or \"n\" to automate the response";
                if [ -n "${default_response+set}" ]; then
                    echo "If you just press enter, the default \"${default_response}\" will be used";
                fi;
                echo
            ;;
            *)
                echo "Please answer yes or no (y/n/?)"
            ;;
        esac;
    done
}
fi

if [ -f "${VSI_COMMON_DIR}/linux/string_tools.bsh" ]; then
  source "${VSI_COMMON_DIR}/linux/string_tools.bsh"
else
quote_escape () 
{ 
    local foo42=\'\\\'\';
    echo "'${1//\'/${foo42}}'"
}
fi

# Use unwrapping echo so that this file is easier to read
if [ -f "${VSI_COMMON_DIR}/linux/uwecho.bsh" ]; then
  source "${VSI_COMMON_DIR}/linux/uwecho.bsh"
else
uwecho () 
{ 
    if [[ ${-} = *i* ]]; then
        echo "WARNING: uwecho does not work when directly called in an interactive bash" 1>&2;
    fi;
    local line_number="$(caller)";
    local file_name="${line_number#* }";
    line_number="${line_number%% *}";
    local lines="$(echo -n ${@+"${@}"} | wc -l)";
    line_number="$((line_number-lines))";
    local source_line="$(sed -n "${line_number}p" "${file_name}")";
    local spaces="$(echo -n "${source_line}" | sed -${sed_flag_rE} 's|^( *uwecho +['"'"'"]?).*|\1|' | tr -d '\n' | wc -c)";
    spaces="${spaces#${spaces%%[![:space:]]*}}";
    local args=();
    local arg;
    while (( ${#} )); do
        IFS= read -rd '' arg < <(sed '2,$s|^ \{'"${spaces}"'\}||' <<< "${1}") || :;
        args+=("${arg}");
        shift 1;
    done;
    echo -n ${args[@]+"${args[@]}"}
}
fi

# Use a bash dir_tools.bsh function
if [ -f "${VSI_COMMON_DIR}/linux/dir_tools.bsh" ]; then
  source "${VSI_COMMON_DIR}/linux/dir_tools.bsh"
else
normpath () 
{ 
    local IFS;
    local x;
    local components;
    local new_components=();
    local absolute=0;
    IFS=/;
    local noglob=0;
    if [[ ${SHELLOPTS} =~ (^|:)noglob(:|$) ]]; then
        noglob=1;
    fi;
    set -o noglob;
    components=(${1});
    if [ "${noglob}" = "0" ]; then
        set +o noglob;
    fi;
    if [ "${1:0:1}" = "/" ]; then
        absolute=1;
    fi;
    for x in "${!components[@]}";
    do
        current="${components[x]}";
        if [ "${current}" = "." ] || [ -z "${current}" ]; then
            continue;
        else
            if [ "${current}" = ".." ]; then
                if [ "${#new_components[@]}" = "0" ]; then
                    if [ "${absolute}" = "1" ]; then
                        continue;
                    else
                        new_components+=("${current}");
                    fi;
                else
                    if [ "${new_components[${#new_components[@]}-1]}" = ".." ]; then
                        new_components+=("${current}");
                    else
                        unset "new_components[${#new_components[@]}-1]";
                    fi;
                fi;
            else
                new_components+=("${current}");
            fi;
        fi;
    done;
    if [ "${absolute}" = "1" ]; then
        echo "/${new_components[*]+${new_components[*]}}";
    else
        if [ "${#new_components[@]}" -gt 0 ] && [ "${new_components[0]}" = ".." ]; then
            echo "${new_components[*]}";
        else
            if [ "${#new_components[@]}" -gt "0" ]; then
                echo "./${new_components[*]+${new_components[*]}}";
            else
                echo ".";
            fi;
        fi;
    fi
}
relative_path () 
{ 
    local path;
    local start;
    if [[ ${1::1} != / ]]; then
        path="${PWD}/${1}";
    else
        path="${1}";
    fi;
    if [[ ${2::1} != / ]]; then
        start="${PWD}/${2}";
    else
        start="${2}";
    fi;
    local IFS='/';
    local noglob=0;
    if [[ ${SHELLOPTS} =~ (^|:)noglob(:|$) ]]; then
        noglob=1;
    fi;
    set -o noglob;
    path=($(normpath "${path}"));
    start=($(normpath "${start}"));
    if [ "${noglob}" = "0" ]; then
        set +o noglob;
    fi;
    unset path[0];
    unset start[0];
    local -i i;
    local s1;
    local s2;
    if [[ ${#path[@]} -lt ${#start[@]} ]]; then
        s1=(${path[@]+"${path[@]}"});
        s2=(${start[@]+"${start[@]}"});
    else
        s1=(${start[@]+"${start[@]}"});
        s2=(${path[@]+"${path[@]}"});
    fi;
    for ((i=0; i<${#s1[@]}; i++ ))
    do
        if [ "${s1[i]}" != "${s2[i]}" ]; then
            break;
        fi;
    done;
    local ans=();
    local -i j;
    for ((j=${#start[@]}-i; j>0; j--))
    do
        ans+=('..');
    done;
    if [ "${bash_bug_ifs_array_slice_expansion}" = "0" ]; then
        for ((j=i+1; j<=${#path[@]}; j++ ))
        do
            ans+=(${path[@]+"${path[@]:j:1}"});
        done;
    else
        ans+=(${path[@]+"${path[@]:i+1}"});
    fi;
    if [ "${#ans[@]}" = "0" ]; then
        echo '.';
    else
        echo "${ans[*]}";
    fi
}
fi

# Use a bash compat.bsh function
if [ -f "${VSI_COMMON_DIR}/linux/compat.bsh" ]; then
  source "${VSI_COMMON_DIR}/linux/compat.bsh"
else
load_vsi_compat () 
{ 
    if [ -z "${VSI_SED_COMPAT-}" ]; then
        local VSI_SED_COMPAT;
        if [[ ${OSTYPE-} = darwin* ]]; then
            VSI_SED_COMPAT=bsd;
        else
            if [ "${OS-}" = "Windows_NT" ] || [[ $(sed --version) = *GNU* ]]; then
                VSI_SED_COMPAT=gnu;
            else
                VSI_SED_COMPAT=bsd;
            fi;
        fi;
    fi;
    if [ "${VSI_SED_COMPAT-}" = "gnu" ]; then
        sed_flag_rE='r';
    else
        sed_flag_rE='E';
    fi;
    if [[ ${OSTYPE-} = darwin* ]]; then
        sed_flags_i=('-i' '');
    else
        sed_flags_i=('-i');
    fi;
    if [ "${OS-}" = "Windows_NT" ] || [[ $(date +%N) =~ ^[0-9]+$ ]]; then
        date_feature_nanoseconds=0;
    else
        date_feature_nanoseconds=1;
    fi;
    local bash_major_version="${BASH_VERSINFO[0]}";
    local bash_minor_version="${bash_major_version}${BASH_VERSINFO[1]}";
    if [ "${bash_minor_version}" -ge "51" ]; then
        bash_feature_posix_process_substitution=0;
    else
        bash_feature_posix_process_substitution=1;
    fi;
    if [ "${bash_minor_version}" -ge "44" ]; then
        bash_behavior_declare_array_quote="";
        bash_feature_parameter_transformation=0;
        bash_feature_declare_array_escape_special_characters=0;
        bash_behavior_empty_array_unbound=1;
        bash_bug_exported_function_corrupt_bash_source=1;
    else
        bash_behavior_declare_array_quote="'";
        bash_feature_parameter_transformation=1;
        bash_feature_declare_array_escape_special_characters=1;
        bash_behavior_empty_array_unbound=0;
        bash_bug_exported_function_corrupt_bash_source=0;
    fi;
    if [ "${bash_minor_version}" -ge "43" ]; then
        bash_feature_declare_name_reffing=0;
        bash_feature_declare_print_function=0;
        bash_bug_local_shadow_exported_variable=1;
        bash_bug_allocate_xtracefd=1;
    else
        bash_feature_declare_name_reffing=1;
        bash_feature_declare_print_function=1;
        bash_bug_local_shadow_exported_variable=0;
        bash_bug_allocate_xtracefd=0;
    fi;
    bash_behavior_pattern_substitution_slash_escape_with_single_quote="o'o";
    if [ "${bash_behavior_pattern_substitution_slash_escape_with_single_quote/\'/\\\'}" = "o\\'o" ]; then
        function bash_behavior_pattern_substitution_slash_escape_with_single_quote () 
        { 
            return 0
        };
    else
        function bash_behavior_pattern_substitution_slash_escape_with_single_quote () 
        { 
            return 1
        };
    fi;
    unset bash_behavior_pattern_substitution_slash_escape_with_single_quote;
    if [ "${bash_minor_version}" -ge "42" ]; then
        bash_feature_declare_global=0;
        bash_behavior_all_traps_display_inherited=0;
        bash_bug_declared_unset_value='';
    else
        bash_feature_declare_global=1;
        bash_behavior_all_traps_display_inherited=1;
        bash_bug_declared_unset_value='=""';
    fi;
    if [ "${bash_minor_version}" -ge "41" ]; then
        bash_feature_printf_array_assignment=0;
        bash_feature_allocate_file_descriptor=0;
        bash_feature_xtracefd=0;
    else
        bash_feature_printf_array_assignment=1;
        bash_feature_allocate_file_descriptor=1;
        bash_feature_xtracefd=1;
    fi;
    if [ "${bash_major_version}" -ge "4" ]; then
        bash_feature_associative_array=0;
        bash_feature_case_modification=0;
        bash_feature_bashpid=0;
        bash_bug_unassigned_variable_set_to_null=1;
        bash_bug_uncaught_unbound_on_exit_trap=1;
    else
        bash_feature_associative_array=1;
        bash_feature_case_modification=1;
        bash_feature_bashpid=1;
        bash_bug_unassigned_variable_set_to_null=0;
        bash_bug_uncaught_unbound_on_exit_trap=0;
    fi;
    function bash_bug_uncaught_error_compound_subshell () 
    { 
        if [ "$(bash -ec '(:; false); echo 0')" = "0" ]; then
            function bash_bug_uncaught_error_compound_subshell () 
            { 
                return 0
            };
            return 0;
        else
            function bash_bug_uncaught_error_compound_subshell () 
            { 
                return 1
            };
            return 1;
        fi
    };
    function bash_bug_bash_env_process_substitution () 
    { 
        if [ -z "${__bash_bug_bash_env_process_substitution+set}" ]; then
            if [ "$(BASH_ENV=<(echo echo 0) bash -c :)" = "0" ]; then
                __bash_bug_bash_env_process_substitution=1;
            else
                __bash_bug_bash_env_process_substitution=0;
            fi;
        fi;
        return "${__bash_bug_bash_env_process_substitution}"
    };
    function bash_feature_rcfile () 
    { 
        local tempfile="$(mktemp)";
        echo "echo 0; rm \"${tempfile}\"" >> "${tempfile}";
        if [ -z "${__bash_feature_rcfile+set}" ]; then
            if [ "$(bash --rcfile "${tempfile}" -ic :)" = "0" ]; then
                __bash_feature_rcfile=0;
            else
                __bash_feature_rcfile=1;
            fi;
        fi;
        rm "${tempfile}";
        return "${__bash_feature_rcfile}"
    };
    function bash_bug_rcfile_process_substitution () 
    { 
        if [ -z "${__bash_bug_rcfile_process_substitution+set}" ]; then
            if [ "$(bash --rcfile <(echo echo 0) -ic :)" = "0" ]; then
                __bash_bug_rcfile_process_substitution=1;
            else
                __bash_bug_rcfile_process_substitution=0;
            fi;
        fi;
        return "${__bash_bug_rcfile_process_substitution}"
    };
    function __bash_bug_at_expansion_null_ifs () 
    { 
        case ${@+"${@}"} in 
            bashbug)
                bash_bug_at_expansion_null_ifs=0
            ;;
            "bash bug")
                bash_bug_at_expansion_null_ifs=1
            ;;
        esac
    };
    IFS="" __bash_bug_at_expansion_null_ifs bash bug;
    if [ "${bash_major_version}" = "4" ]; then
        bash_feature_bashpid_read_only=0;
    else
        bash_feature_bashpid_read_only=1;
    fi;
    local x='foo
bar';
    local re='\n';
    if [[ ${x} =~ ${re} ]]; then
        bash_behavior_regex_special_characters_non_literal=0;
    else
        bash_behavior_regex_special_characters_non_literal=1;
    fi;
    x='';
    re='';
    if [[ ${x} =~ ${re} ]]; then
        bash_bug_regex_empty_string=1;
    else
        bash_bug_regex_empty_string=0;
    fi;
    local y;
    if declare -p y &> /dev/null; then
        bash_bug_declare_fails_local_declared_unset_variable=1;
    else
        bash_bug_declare_fails_local_declared_unset_variable=0;
    fi;
    if [ "${bash_minor_version}" -ge "40" -a "${bash_minor_version}" -le "43" ]; then
        bash_bug_declare_fails_global_declared_unset_variable=0;
    else
        bash_bug_declare_fails_global_declared_unset_variable=1;
    fi;
    if [ "${bash_minor_version}" = "43" ] || [ "${bash_minor_version}" = "44" ]; then
        bash_bug_substitute_empty_funcname=0;
    else
        bash_bug_substitute_empty_funcname=1;
    fi;
    local x=(11 22 33 44);
    local IFS=x;
    local z=("${x[@]:2}");
    if [ "${z[0]}" = "33 44" ]; then
        bash_bug_ifs_array_slice_expansion=0;
    else
        bash_bug_ifs_array_slice_expansion=1;
    fi;
    function git_bug_submodule_path_with_special_characters () 
    { 
        if [ -z "${__git_bug_submodule_path_with_special_characters+set}" ]; then
            if [ "${OS-}" = "Windows_NT" ]; then
                __git_bug_submodule_path_with_special_characters=0;
            else
                source "${VSI_COMMON_DIR}/linux/requirements.bsh";
                source "${VSI_COMMON_DIR}/linux/versions.bsh";
                if meet_requirements "$(git_version)" ">=1.8.3.3"; then
                    __git_bug_submodule_path_with_special_characters=1;
                else
                    __git_bug_submodule_path_with_special_characters=0;
                fi;
            fi;
        fi;
        return "${__git_bug_submodule_path_with_special_characters}"
    };
    function git_feature_support_tls () 
    { 
        if [ -z "${__git_feature_support_tls+set}" ]; then
            __git_feature_support_tls=125;
            if [[ $(GIT_DIR= "${GIT}" ls-remote -h https://tls-v1-2.badssl.com:1012/ 2>&1 > /dev/null) =~ repository.*\ not\ found ]]; then
                __git_feature_support_tls=2;
            else
                if [[ $(GIT_DIR= "${GIT}" ls-remote -h https://tls-v1-1.badssl.com:1011/ 2>&1 > /dev/null) =~ repository.*\ not\ found ]]; then
                    __git_feature_support_tls=1;
                else
                    if [[ $(GIT_DIR= "${GIT}" ls-remote -h https://tls-v1-0.badssl.com:1010/ 2>&1 > /dev/null) =~ repository.*\ not\ found ]]; then
                        __git_feature_support_tls=0;
                    fi;
                fi;
            fi;
        fi;
        return "${__git_feature_support_tls}"
    };
    function git_feature_submodule_update_with_custom_command () 
    { 
        if [ -z "${__git_feature_submodule_update_with_custom_command+set}" ]; then
            source "${VSI_COMMON_DIR}/linux/requirements.bsh";
            source "${VSI_COMMON_DIR}/linux/versions.bsh";
            if meet_requirements "$(git_version)" ">=1.8.4"; then
                __git_feature_submodule_update_with_custom_command=0;
            else
                __git_feature_submodule_update_with_custom_command=1;
            fi;
        fi;
        return "${__git_feature_submodule_update_with_custom_command}"
    };
    function git_feature_cli_config_option () 
    { 
        if [ -z "${__git_feature_cli_config_option+set}" ]; then
            if "${GIT}" -c foo=bar version &> /dev/null; then
                __git_feature_cli_config_option=0;
            else
                __git_feature_cli_config_option=1;
            fi;
        fi;
        return "${__git_feature_cli_config_option}"
    };
    function tar_feature_incremental_backup () 
    { 
        if [ -z "${__tar_feature_incremental_backup+set}" ]; then
            if [ "${OS-}" = "Windows_NT" ]; then
                __tar_feature_incremental_backup=0;
            else
                source "${VSI_COMMON_DIR}/linux/dir_tools.bsh";
                local temp_file;
                make_temp_path temp_file;
                if ${TAR-tar} -cf /dev/null -g "${temp_file}" /dev/null &> /dev/null; then
                    __tar_feature_incremental_backup=0;
                else
                    __tar_feature_incremental_backup=1;
                fi;
            fi;
        fi;
        return "${__tar_feature_incremental_backup}"
    };
    function tar_feature_delete () 
    { 
        if [ -z "${__tar_feature_delete+set}" ]; then
            if ${TAR-tar} -f /dev/null --delete &> /dev/null; then
                __tar_feature_delete=0;
            else
                __tar_feature_delete=1;
            fi;
        fi;
        return "${__tar_feature_delete}"
    };
    function tar_feature_concatenate () 
    { 
        if [ -z "${__tar_feature_concatenate+set}" ]; then
            if ${TAR-tar} -f /dev/null --concatenate /dev/null &> /dev/null; then
                __tar_feature_concatenate=0;
            else
                __tar_feature_concatenate=1;
            fi;
        fi;
        return "${__tar_feature_concatenate}"
    };
    function readlink_behavior_nonexistent_path () 
    { 
        if [ -z "${__readlink_behavior_nonexistent_path+set}" ]; then
            if readlink -f "/dev/null_not_a_file" &> /dev/null; then
                __readlink_behavior_nonexistent_path=0;
            else
                __readlink_behavior_nonexistent_path=1;
            fi;
        fi;
        return "${__readlink_behavior_nonexistent_path}"
    };
    function realpath_behavior_nonexistent_path () 
    { 
        if [ -z "${__realpath_behavior_nonexistent_path+set}" ]; then
            if realpath "/dev/null_not_a_file" &> /dev/null; then
                __realpath_behavior_nonexistent_path=0;
            else
                __realpath_behavior_nonexistent_path=1;
            fi;
        fi;
        return "${__realpath_behavior_nonexistent_path}"
    };
    function singularity_feature_nvccli () 
    { 
        if [ -z "${__singularity_feature_nvccli+set}" ]; then
            source "${VSI_COMMON_DIR}/linux/requirements.bsh";
            if [ -z "${singularity_version+set}" -o -z "${singularity_vendor+set}" ]; then
                local singularity_version;
                local singularity_vendor;
                source "${VSI_COMMON_DIR}/linux/versions.bsh";
                singularity_version_info;
            fi;
            __singularity_feature_nvccli=1;
            case "${singularity_vendor}" in 
                apptainer)
                    if meet_requirements "${singularity_version}" '>=1'; then
                        __singularity_feature_nvccli=0;
                    fi
                ;;
                singularity-ce)
                    if meet_requirements "${singularity_version}" '>=3.9'; then
                        __singularity_feature_nvccli=0;
                    fi
                ;;
            esac;
        fi;
        return "${__singularity_feature_nvccli}"
    };
    function singularity_feature_nvccli_wsl2_gpu () 
    { 
        if [ -z "${__singularity_feature_nvccli_wsl2_gpu+set}" ]; then
            source "${VSI_COMMON_DIR}/linux/requirements.bsh";
            if [ -z "${singularity_version+set}" -o -z "${singularity_vendor+set}" ]; then
                local singularity_version;
                local singularity_vendor;
                source "${VSI_COMMON_DIR}/linux/versions.bsh";
                singularity_version_info;
            fi;
            __singularity_feature_nvccli_wsl2_gpu=1;
            case "${singularity_vendor}" in 
                apptainer)
                    if meet_requirements "${singularity_version}" '>=1.1.0'; then
                        __singularity_feature_nvccli_wsl2_gpu=0;
                    fi
                ;;
                singularity-ce)
                    if meet_requirements "${singularity_version}" '>=3.9.7'; then
                        __singularity_feature_nvccli_wsl2_gpu=0;
                    fi
                ;;
            esac;
        fi;
        return "${__singularity_feature_nvccli_wsl2_gpu}"
    };
    function singularity_bug_writable_tmpfs_without_root () 
    { 
        if [ -z "${__singularity_bug_writable_tmpfs_without_root+set}" ]; then
            source "${VSI_COMMON_DIR}/linux/requirements.bsh";
            if [ -z "${singularity_version+set}" -o -z "${singularity_vendor+set}" ]; then
                local singularity_version;
                local singularity_vendor;
                source "${VSI_COMMON_DIR}/linux/versions.bsh";
                singularity_version_info;
            fi;
            __singularity_bug_writable_tmpfs_without_root=1;
            case "${singularity_vendor}" in 
                apptainer)
                    if meet_requirements "${singularity_version}" '>=1.1.0' '<1.1.3'; then
                        __singularity_bug_writable_tmpfs_without_root=0;
                    fi
                ;;
            esac;
        fi;
        return "${__singularity_bug_writable_tmpfs_without_root}"
    }
}

  # Call this now, to load flags
  load_vsi_compat
fi

#**
# .. function:: docker_add_quote_escape
#
# Escapes arbitrary filename for ``Dockerfile`` ``ADD ["filename", "dest"]`` syntax, not including outer ``"``
#
# :Arguments: ``$1`` - Filename to be escaped
#**
function docker_add_quote_escape()
{
  # Escape \ => \\
  local foo="${1//\\/\\\\}"
  # Escape [ => \\[
  foo="${foo//[/\\\\[}"
  if [ "${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}" -le "42" ]; then
    # Escape ' => \\'
    foo="${foo//\'/\\\'}"
  else
    # Escape ' => \\'
    foo="${foo//\'/\\\\\'}"
  fi
  # Escape " => \\\"
  foo="${foo//\"/\\\\\\\"}"
  echo "${foo}"
}

#**
# .. function:: docker_env_quote_escape
#
# Escapes arbitrary value for ``Dockerfile`` ``ENV var="value"`` syntax, not including outer ``"``
#
# :Arguments: ``$1`` - Value to be escaped
#**
function docker_env_quote_escape()
{
  # Escape \ => \\. While not exactly required, protects certain corner cases
  local foo="${1//\\/\\\\}"
  # Escape " => \"
  echo "${foo//\"/\\\"}"
}

function bash_default_quote_escape()
{
  local foo=${1//\"/\\\"}
  foo=${foo//\'/\\\'}
  echo "${foo}"
}

# $1 - Var name
# $2 - default
# $3 - Prompt message
# USE_DEFAULTS=1 means use default
# Returns 1 if default used, else 0
function set_default()
{
  if [ -n "${!1:+set}" ]; then
    return 0
  fi
  if [ "${USE_DEFAULTS-}" == "1" ]; then
    if [ -z "${!1:+set}" ]; then
      IFS='' read -r -d '' "${1}" < <(printf '%s' "${2-}")
    fi
    # : ${!1:=${2}} # Only works in bash 4.4 or newer
    return 1
  fi

  if [ -z "${!1+set}" ]; then
    read -r -p "${3} (${2}) " "${1}"
  fi
  # : ${!1="$(read -r -p "${3} (${2}) " x; echo "${x}")"} # Only works in bash 4.4 or newer
  if [ "${!1}" == "" ]; then
    IFS='' read -r -d '' "${1}" < <(printf '%s' "${2-}")
    return 1
  fi
  return 0
}

# Check to see if file exists, and makes it exists if it doesn't
# Return 0 if it already exists, else 1
# :Arguments: - ``$1`` - Filename
#             - [``$2``] - Permissions to set to file
# :Return Value: - ``0`` - If file already exists
#                - ``1`` - If file did not exist, and was created
function exists()
{
  if [ -e "${1}" ]; then
    if [ "${#}" -gt "1" ]; then
      chmod "${2}" "${1}"
    fi
    echo "${1} exists, skipping..."
    return 0
  fi

  touch "${1}"
  if [ "${#}" -gt "1" ]; then
    chmod "${2}" "${1}"
  fi
  return 1
}

#################
### setup.env ###
#################
function write_setup_env()
{
  exists "${1}" && return 0

  uwecho "test -f $(quote_escape "${RELATIVE_PATH}/linux/check_shell") && $(quote_escape "${RELATIVE_PATH}/linux/check_shell") bash zsh"'
          export JUST_SETUP_SCRIPT="$(basename "${BASH_SOURCE[0]}")"
          source "$(dirname "${BASH_SOURCE[0]}")/"'"$(quote_escape "${RELATIVE_PATH}/env.bsh")" >> "${1}"
  if [ "${JUSTFILE}" != "Justfile" ]; then
    echo "export JUSTFILE=$(quote_escape "${JUSTFILE}")" >> "${1}"
  else
    echo "unset JUSTFILE" >> "${1}"
  fi
}

###################
### project.env ###
###################
function write_project_env()
{
  exists "${1}" && return 0

  uwecho   '#!/usr/bin/env false bash

            JUST_PROJECT_PREFIX='"${PROJECT_PREFIX}"'
            JUST_VERSION="'"${JUST_VERSION-JUST_VERSION unset in ${1}}"'"
            if [ -z "${'"${PROJECT_PREFIX}"'_CWD+set}" ]; then
              '"${PROJECT_PREFIX}"'_CWD="$(\cd "$(\dirname "${BASH_SOURCE[0]}")"; \pwd)"
            fi
            '  >> "${1}"

  if [ "${USE_DOCKER}" = "1" ]; then
    uwecho '##########
            # Docker #
            # Setup  #
            ##########

            : ${'"${PROJECT_PREFIX}"'_AUTO_ESCAPE='"${PROJECT_PREFIX}"'_.*_DIR_DOCKER}

            # Docker user info
            : ${'"${PROJECT_PREFIX}"'_USERNAME=$(id -u -n)}
            #T# : ${'"${PROJECT_PREFIX}"'_USERNAME_DOCKER=${'"${PROJECT_PREFIX}"'_USERNAME}} to mimic the username in the container
            : ${'"${PROJECT_PREFIX}"'_USERNAME_DOCKER=user}
            : ${'"${PROJECT_PREFIX}"'_UID=$(id -u)}
            : ${'"${PROJECT_PREFIX}"'_GIDS=$(id -G)}
            : ${'"${PROJECT_PREFIX}"'_GID=${'"${PROJECT_PREFIX}"'_GIDS%% *}}
            : ${'"${PROJECT_PREFIX}"'_GROUP_NAMES=$(group_names)}
            : ${'"${PROJECT_PREFIX}"'_HOME=/home/${'"${PROJECT_PREFIX}"'_USERNAME_DOCKER}}

            # Docker image names
            : ${'"${PROJECT_PREFIX}"'_BASE_IMAGE=ubuntu:22.04}
            : ${'"${PROJECT_PREFIX}"'_DOCKER_REPO='"${REPO_NAME}"'}
            : ${VSI_RECIPE_REPO=${'"${PROJECT_PREFIX}"'_DOCKER_REPO}_recipes}
            : ${'"${PROJECT_PREFIX}"'_IMAGE_FOOTER=_${'"${PROJECT_PREFIX}"'_USERNAME}}
            : ${'"${PROJECT_PREFIX}_${APP_NAME_UPPER}"'_IMAGE=${'"${PROJECT_PREFIX}"'_DOCKER_REPO}:'"${APP_NAME}"'${'"${PROJECT_PREFIX}"'_IMAGE_FOOTER}}
            : ${'"${PROJECT_PREFIX}"'_PYTHON_CACHE_IMAGE=${'"${PROJECT_PREFIX}"'_DOCKER_REPO}:python_cache${'"${PROJECT_PREFIX}"'_IMAGE_FOOTER}}
            : ${'"${PROJECT_PREFIX}"'_DEPLOY_IMAGE=${'"${PROJECT_PREFIX}"'_DOCKER_REPO}:deploy${'"${PROJECT_PREFIX}"'_IMAGE_FOOTER}}

            # Primary service runner
            if [ "${JUST_RODEO-}" = "1" ]; then
              : ${'"${PROJECT_PREFIX}"'_COMPOSE_SERVICE_'"${APP_NAME_UPPER}"'=deploy}
            else
              : ${'"${PROJECT_PREFIX}"'_COMPOSE_SERVICE_'"${APP_NAME_UPPER}=${APP_NAME}"'}
            fi

            # Remove development packages from deploy image
            : ${'"${PROJECT_PREFIX}"'_REMOVE_DEV_PACKAGES=0}

            : ${'"${PROJECT_PREFIX}"'_NVIDIA_VISIBLE_DEVICES=all}
            : ${'"${PROJECT_PREFIX}"'_CUDA_VISIBLE_DEVICES=}
            : ${'"${PROJECT_PREFIX}"'_NVIDIA_DRIVER_CAPABILITIES=compute,graphics,utility}

            ###########
            # Docker  #
            # Volumes #
            ###########

            #T# This directory is added to the container using the docker compose file. This mechanism
            #T# should only be used when the directory is guaranteed to exist
            : ${'"${PROJECT_PREFIX}"'_SOURCE_DIR=${'"${PROJECT_PREFIX}"'_CWD}}
            : ${'"${PROJECT_PREFIX}"'_SOURCE_DIR_DOCKER=/src}
            : ${'"${PROJECT_PREFIX}"'_SOURCE_DIR_TYPE=bind}
            #T# vsi_common volume
            : ${'"${PROJECT_PREFIX}"'_VSI_COMMON_DIR=${'"${PROJECT_PREFIX}"'_CWD}/'"$(bash_default_quote_escape "${RELATIVE_PATH}")"'}
            : ${'"${PROJECT_PREFIX}"'_VSI_COMMON_DIR_DOCKER=/vsi}
            : ${'"${PROJECT_PREFIX}"'_VSI_COMMON_DIR_TYPE=bind}

            #T# Add graphical support for Linux containers
            if [ -d "/tmp/.X11-unix" ]; then
              '"${PROJECT_PREFIX}"'_VOLUMES=("/tmp/.X11-unix:/tmp/.X11-unix:ro"
                  ${'"${PROJECT_PREFIX}"'_VOLUMES+"${'"${PROJECT_PREFIX}"'_VOLUMES[@]}"})
            fi
            #T#
            #T# Example of a Dynamic Volume that is created programmatically instead of
            #T# always there. This directory is added to the container using '"${PROJECT_PREFIX}_${APP_NAME_UPPER}"'_VOLUMES.
            #T# This mechanism is better when the directory doesn'"'"'t exist, as the directory
            #T# will be created and owned properly, unlike docker'"'"'s default behavior
            #T# : ${'"${PROJECT_PREFIX}"'_DATA_DIR=${'"${PROJECT_PREFIX}"'_SOURCE_DIR}/new-data}
            #T# : ${'"${PROJECT_PREFIX}"'_DATA_DIR_DOCKER=/data}
            #T# '"${PROJECT_PREFIX}_${APP_NAME_UPPER}"'_VOLUMES=(
            #T#     "${'"${PROJECT_PREFIX}"'_DATA_DIR}:${'"${PROJECT_PREFIX}"'_DATA_DIR_DOCKER}"
            #T#     ${'"${PROJECT_PREFIX}_${APP_NAME_UPPER}"'_VOLUMES+"${'"${PROJECT_PREFIX}_${APP_NAME_UPPER}"'_VOLUMES[@]}"})' >> "${1}"

    if [ "${USE_PIP_TOOLS}" = "1" ]; then
      uwecho '
              ##########
              # Python #
              ##########

              # Python version used
              #T# This version of python is installed using miniconda to acquire an
              #T# arbitrary pre-compiled version of python.
              : ${'"${PROJECT_PREFIX}"'_PYTHON_VERSION="3.10.12"}
              #T# The default type of environment to run pip-tools in
              : ${'"${PROJECT_PREFIX}"'_PIP_TOOLS_ENVIRONMENT=docker}' >> "${1}"
    fi

    uwecho '
            #########
            # Other #
            #########
            #T#
            #T# Start adding your own '"${PROJECT_PREFIX}"'_ variables here

            ###############################################################################
            # Non-'"${PROJECT_PREFIX}"' Settings
            ###############################################################################

            # Put variables that do not begin with '"${PROJECT_PREFIX}"' here.

            if [ -e "/etc/localtime" ]; then
              : ${TZ=$(real_path /etc/localtime)}
            else
              #T# Fallback default zone
              #T# : ${TZ=/usr/share/zoneinfo/UTC}
              : ${TZ=/usr/share/zoneinfo/America/New_York}
            fi

            #T# When volumes get large enough, some docker compose commands will take longer
            #T# than the default 60 seconds timeout, and error out when nothing is wrong.
            #T# 10 minutes is a more reasonable timeline. This only really matters when the
            #T# venv volume is cleaned
            : ${COMPOSE_HTTP_TIMEOUT=600}

            # Use this to add the user name to the docker compose project name. This is
            # important when multiple users are using this docker compose project on a
            # single host. This way all of the docker resources are prefixed with a unique
            # name and do not collide
            source "${VSI_COMMON_DIR}/linux/just_files/docker_functions.bsh" # define docker_compose_sanitize_project_name
            : ${COMPOSE_PROJECT_NAME=$(docker_compose_sanitize_project_name "${'"${PROJECT_PREFIX}"'_CWD}" "${'"${PROJECT_PREFIX}"'_USERNAME}")}' >> "${1}"
    if [ "${USE_PIP_TOOLS}" = "1" ]; then
      uwecho '
              : ${CUSTOM_COMPILE_COMMAND="just pip-compile"}' >> "${1}"
    fi
  fi
}

#################
### README.md ###
#################
function write_readme_md()
{
  exists "${1}" && return 0

  uwecho   '## Getting started

            ```' >> "${1}"

  if [ "${USE_VSI_COMMON}" = "1" ]; then
    echo   "source $(quote_escape "${$}")" >> "${1}"
  fi

  if [ "${USE_DOCKER}" = "1" ]; then
    uwecho "${just_cmd} sync
            ${just_cmd} run ${APP_NAME}" >> "${1}"
  else
    uwecho "${just_cmd} compile
            ${just_cmd} run" >> "${1}"
  fi
  uwecho   '```

            ## Just usage:

            ```' >> "${1}"

  if [ "${USE_VSI_COMMON}" = "1" ]; then
    echo   "source $(quote_escape "${SETUPFILE}")" >> "${1}"
  fi

  uwecho   '${just_cmd} help
            ```' >> "${1}"
}

################
### Justfile ###
################
function write_justfile()
{
  exists "${1}" 775 && return 0

  uwecho     '#!/usr/bin/env bash

              source "${VSI_COMMON_DIR}/linux/just_files/just_env" "$(dirname "${BASH_SOURCE[0]}")"/'"$(quote_escape "${PROJECT_NAME}.env")"'
              ' >> "${1}"

  if [ "${USE_DOCKER}" = "1" ]; then
    uwecho   '# Plugins
              source "${VSI_COMMON_DIR}/linux/ask_question"
              source "${VSI_COMMON_DIR}/linux/just_files/docker_functions.bsh"
              source "${VSI_COMMON_DIR}/linux/just_files/just_docker_functions.bsh"
              source "${VSI_COMMON_DIR}/linux/just_files/just_git_functions.bsh"' >> "${1}"

    if [ "${USE_PIP_TOOLS}" = "1" ]; then
      echo   'source "${VSI_COMMON_DIR}/linux/just_files/just_pip-tools_functions.bsh"' >> "${1}"
    fi

    uwecho   '
              cd "${'"${PROJECT_PREFIX}"'_CWD}"

              # Main function
              function caseify()
              {
                local just_arg="${1}"
                shift 1
                case ${just_arg} in
                  build) # Build Docker image
                    if [ "${#}" -gt "0" ]; then
                      Docker buildx bake "${just_arg}" ${@+"${@}"}
              ' >> "${1}"
    if [ "${USE_PIP_TOOLS}" = "1" ]; then
      uwecho '        if isin '"${APP_NAME}"' ${@+"${@}"}; then
                        justify docker compose clean venv
                      fi' >> "${1}"
    fi
    uwecho   '        extra_args=${#}
                    else
                      justify build recipes-auto "${'"${PROJECT_PREFIX}"'_CWD}/docker"/*.Dockerfile' >> "${1}"
    if [ "${USE_PIP_TOOLS}" = "1" ]; then
      uwecho '        Docker buildx bake '"${APP_NAME}"' python_cache
                      justify docker compose clean venv' >> "${1}"
    else
      uwecho '        Docker buildx bake '"${APP_NAME}"'
                      justify docker compose clean venv' >> "${1}"
    fi
    uwecho   '      fi
                    ;;
                  build_deploy) # Build the deploy docker image
                    Docker buildx bake deploy
                    ;;
              ' >> "${1}"
    uwecho   '    run_'"${APP_NAME}"') # Run '"${APP_NAME}"' 1
                    # These commands will run your app
                    Just-docker-compose run --service-ports '"${APP_NAME}"' ${@+"${@}"}
                    extra_args=${#}
                    ;;

                  shell) # Start a bash shell in your runtime environment
                    Just-docker-compose run '"${APP_NAME}"' shell
                    ;;

                  sync) # Synchronize the many aspects of the project when new code changes \
                        # are applied e.g. after "git checkout"
                    if [ ! -e "${'"${PROJECT_PREFIX}"'_CWD}/.just_synced" ]; then
                      #T# Add any commands here, like initializing a database, etc... that need
                      #T# to be run the first time sync is run.

                      touch "${'"${PROJECT_PREFIX}"'_CWD}/.just_synced"
                    fi
                    # Add any extra steps to run when syncing
                    Docker compose down
                    justify git_submodule-update # For those users who don'"'"'t remember!
                    justify build
                    ;;
              ' >> "${1}"
    if [ "${USE_PIP_TOOLS}" = "1" ]; then
      uwecho '    clean_all) # Delete all local volumes
                    #T# Confirm action to protect accidental deletion. Can be removed if you don'"'"'t want it.
                    ask_question "Are you sure you want to delete all docker volumes?" answer_clean_venv n
                    if [ "${answer_clean_venv}" = "1" ]; then
                      justify docker compose clean --all
                    fi
                    ;;
              ' >> "${1}"
    fi
    uwecho   '    *)
                    #T# Forward targets to all other plugins when they do not match any
                    #T# targets in this caseify.
                    defaultify "${just_arg}" ${@+"${@}"}
                    ;;
                esac
              }

              #T# Add support to have Justfile called directly
              if ! command -v justify &> /dev/null; then caseify ${@+"${@}"};fi' >> "${1}"
  else
    uwecho   'cd "${'"${PROJECT_PREFIX}"'_CWD}"

              function caseify()
              {
                local just_arg="${1}"
                shift 1
                case ${just_arg} in
                  compile) # Build program
                    g++ hi.cpp -o hi
                    ;;

                  run) # Run program
                    ./hi ${@+"${@}"}
                    extra_args=${#}
                    ;;

                  *)
                    defaultify "${just_arg}" ${@+"${@}"}
                    ;;
                esac
              }

              if ! command -v justify &> /dev/null; then caseify ${@+"${@}"};fi' >> "${1}"
  fi
}

#######################
### requirements.in ###
#######################
function write_requirements_in()
{
  exists "${1}" && return 0

  uwecho '# Local packages
          -e /src[docs]' >> "${1}"
}

########################
### requirements.txt ###
########################
function write_requirements_txt()
{
  exists "${1}" && return 0

  uwecho '#
          # This file is autogenerated by pip-compile with Python 3.10
          # by the following command:
          #
          #    just pip-compile
          #
          -e file:///src
              # via -r /src/requirements.in' >> "${1}"
}

######################
### pyproject.toml ###
######################
function write_pyproject_toml()
{
  exists "${1}" && return 0

  uwecho "[build-system]
          requires = [
            \"setuptools >= 61.0\",
            #T# Add other build dependencies here, e.g. numpy
          ]
          build-backend = \"setuptools.build_meta\"

          [project]
          name = \"${PYTHON_PACKAGE}\"
          version = \"0.0.1\"
          dependencies = [
            # Put python dependencies here
          ]
          authors = [
            {name = \"VSI Author\", email = \"vsi@visionsystemsinc.com\"},
          ]
          license = {text = \"MIT License\"}
          readme = \"README.md\"
          description = \"${PROJECT_NAME//\"/\\\"} description\"

          [project.urls]
          Homepage = \"https://github.com/visionsysmtesinc/${PYTHON_PACKAGE}\"
          Documentation = \"https://visionsysmtesinc.github.io/${PYTHON_PACKAGE}\"
          Repository = \"https://github.com/visionsysmtesinc/${PYTHON_PACKAGE}.git\"
          \"Bug Tracker\" = \"https://github.com/visionsysmtesinc/${PYTHON_PACKAGE}/issues\"

          [project.optional-dependencies]
          docs = [\"sphinx\"]

          [tool.setuptools.packages]
          #T# Only directories with __init__.py will be considered part of the package
          find = {namespaces = false}  # Disable implicit namespaces

          #T# [projects.scripts]
          #T# ${PYTHON_PACKAGE}-cli = \"${PYTHON_PACKAGE}:main_cli\"" >> "${1}"
}

function standard_env()
{
  # This has to be handled in a separate function to be DRY, because uwecho can't
  # handle multiline string expansion, it throws off the line counting
  uwecho 'ENV JUSTFILE="/src/docker/'"$(docker_env_quote_escape "${APP_NAME}.Justfile")"'" \
              JUST_SETTINGS="/src/'"$(docker_env_quote_escape "${PROJECT_NAME}.env")"'" \
              #T# Location of the cache used by many linux tools
              XDG_CACHE_HOME=/cache \
              #T# Some tools recommend that these env variables be set to en_US.UTF-8.
              #T# On debian, C.UTF-8 exists by default instead, and seems to work.
              #T# More detail: https://stackoverflow.com/a/38553499
              #T# Note: en_US.UTF-8 exists by default in Centos/Fedora base images
              LC_ALL=C.UTF-8 \
              LANG=C.UTF-8'
}

##################
### Dockerfile ###
##################
function write_dockerfile()
{
  exists "${1}" && return 0

  uwecho   '# syntax=docker/dockerfile:1.4
            ARG VSI_RECIPE_REPO
            ARG BASE_IMAGE

            #T# Dockers use gosu instead of sudo because sudo is a poor fit for docker.
            #T# Note: A function "sudo" is added to call "gosu root" for convenience
            FROM ${VSI_RECIPE_REPO}:gosu AS gosu
            #T# Dockers do not reap zombies by default, tini does this
            FROM ${VSI_RECIPE_REPO}:tini AS tini
            #T# "just" dockers use vsi common
            FROM ${VSI_RECIPE_REPO}:vsi AS vsi' >> "${1}"

  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    uwecho '
            #T# pip-tools manages the python virtual env and installs a consistent set of packages
            FROM ${VSI_RECIPE_REPO}:pip-tools AS pip-tools

            #T# Installs a python for pip-tools to use. pip-tools manages the python virtual
            #T# env and installs a consistent set of packages
            FROM ${VSI_RECIPE_REPO}:conda-python AS python

            ###############################################################################

            #T# The dep_stage stage is the common stage that all other stages can use. You
            #T# can install and setup any programs that are common to all of the other stages
            FROM ${BASE_IMAGE} AS dep_stage

            #T# Unlike normal docker shells, this shell will stop after any error, so you
            #T# no longer need to end command lines with "&& \", you can just use "; \"
            SHELL ["/usr/bin/env", "bash", "-euxvc"]

            # Install your common dependencies here
            RUN apt-get update; \
                DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
                  tzdata ca-certificates readline-common; \
                rm -r /var/lib/apt/lists/*
            ' >> "${1}"
    standard_env >> "${1}"
    uwecho '    # PYTHONPATH="${PYTHONPATH:+${PYTHONPATH}:}/install/lib/python3/site-packages"

            #T# Additional dep stage recipes
            COPY --from=python /usr/local /usr/local/conda-python
            RUN ln -s /usr/local/conda-python/bin/* /usr/local/bin; \
                #T# required for matplotlib TkAgg backend
                ln -s /usr/local/conda-python/lib/{tcl,tk}*/ /usr/local/lib;
            COPY --from=pip-tools /usr/local /usr/local
            COPY --from=tini /usr/local /usr/local
            #T# Do not use gosu in a production environment; it should be removed by then
            COPY --from=gosu /usr/local/bin/gosu /usr/local/bin/gosu
            RUN chmod u+s /usr/local/bin/gosu

            #T# Run post-install scripts for docker recipes (like pip-tools).
            #T# Should be run after all of the "COPY --from" commands.
            #T# See https://git.io/JOcp6 for more details about docker recipes
            RUN shopt -s nullglob; for patch in /usr/local/share/just/container_build_patch/*; do "${patch}"; done

            ###############################################################################

            #T# The python_cache stage is a build stage intended to make the operation of the
            #T# "pip-sync" and "pip-compile" commands more efficient. It also helps to
            #T# minimize the size of the final build stage, which does not require any of the
            #T# build dependencies needed by this stage in order to compile python packages
            #T# during "pip-sync" that, for example, do not have pre-built wheels
            FROM dep_stage AS python_cache

            # Install your build dependencies for python packages here
            #T# This is an example of installing g++ and git as dependencies. Your dependencies
            #T# will depend on what your python packages require
            # RUN apt-get update; \
            #     DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
            #       g++ git; \
            #     rm -r /var/lib/apt/lists/*

            ADD requirements.txt pyproject.toml /src/

            #T# Simple packages can be added as dependencies to your project by:
            #T# 1) Editing your requirements.in file
            #T# 2) just pip-compile
            #T# 3) just sync # (optionally updates the docker image)
            #T#
            #T# Call pip-fake for each editable package
            RUN pip-fake /src '"${PYTHON_PACKAGE}"'; \
                #T# pip-fake /src/external/editable_package package_name; \
                pip-fake '"$(quote_escape "/src/${RELATIVE_PATH}")"' vsi python/vsi


            ARG SKIP_PIP_SYNC
            #T# Sometimes you need to fix the image so that pip-compile can be run, but
            #T# you can'"'"'t because pip-sync won'"'"'t work. This chicken/egg cycle can be
            #T# escaped by setting the build arg SKIP_PIP_SYNC to 1. Then, pip-sync is
            #T# skipped so that the image can be built allowing you to easily relock.
            RUN /usr/local/conda-python/bin/python3 -m venv /venv/src; \
                if [ "${SKIP_PIP_SYNC-}" != "1" ]; then \
                  . /venv/src/bin/activate; \
                  #T# The pip-8210 command is a wrapper for pip that uses the
                  #T# requirements.txt as a constraint to install the correct versions of
                  #T# libraries and their dependencies. Additionally, it handles all
                  #T# incomptibilies covered in pip issue #8210
                  pip-8210 install pip-tools; \

                  #T# # Modern pyproject.toml packages shouldn'"'"'t require this, but older setup.py projects
                  #T# # might have an "build requirment" just to start the build process. Common examples
                  #T# # of this include numpy and torch. E.g. to install the correct version of numpy
                  #T# # before a package builds, add it to the pre-install line above like this
                  #T# # pip-8210 install pip-tools numpy; \

                  pip-sync -v /src/requirements.txt; \
                fi; \
                # Cleanup and make way for the real /src that will be mounted at runtime
                rm -rf /src/* /tmp/pip*

            # These recipes are added here instead of in the dep_stage so that docker
            # is not constantly rerunning pip-sync every time the slightest change to
            # vsi_common is made.
            COPY --from=vsi /vsi /vsi

            ENTRYPOINT ["/usr/bin/env", "bash", "/vsi/linux/just_files/just_entrypoint.sh"]

            CMD ["bash"]

            ###############################################################################

            #T# The final stage is the runtime stage for the application, where everything
            #T# else that is needed is installed and setup
            FROM dep_stage
            ' >> "${1}"
  else
    uwecho '
            ###############################################################################

            FROM debian:stretch

            #T# Unlike normal docker shells, this shell will stop after any error, so you
            #T# no longer need to end command lines with "&& \", you can just use a "; \"
            SHELL ["/usr/bin/env", "bash", "-euxvc"]
            ' >> "${1}"
  fi

  uwecho   '# Install your runtime dependencies here
            RUN apt-get update; \
                DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \' >> "${1}"

  if [ "${USE_PIP_TOOLS}" != "1" ]; then
    echo   '      tzdata \' >> "${1}"
  else
    echo   '      readline-common \' >> "${1}"
  fi

  uwecho   '      ; \
                rm -rf /var/lib/apt/lists/*
            #T#
            #T# Another typical example of installing a package, using it, and then removing
            #T# it by the end of the RUN command to minimize the size of the docker image
            #T# RUN build_deps="wget ca-certificates"; \
            #T#     apt-get update; \
            #T#     DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ${build_deps}; \
            #T#     wget -q https://www.vsi-ri.com/bin/deviceQuery; \
            #T#     DEBIAN_FRONTEND=noninteractive apt-get purge -y --autoremove ${build_deps}; \
            #T#     rm -rf /var/lib/apt/lists/*

            COPY --from=tini /usr/local /usr/local
            COPY --from=gosu /usr/local/bin/gosu /usr/local/bin/gosu
            #T# Allow non-privileged user to run gosu (remove this to take root away from user)
            #T# Do not leave this in for production!
            RUN chmod u+s /usr/local/bin/gosu
            ' >> "${1}"

  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    echo   'COPY --from=python_cache /venv /venv' >> "${1}"
  fi
  uwecho   'COPY --from=vsi /vsi /vsi

            ADD ["'"$(docker_add_quote_escape "${PROJECT_NAME}.env")"'", "/src/"]

            ADD ["'"$(docker_add_quote_escape docker/"${APP_NAME}.Justfile")"'", "/src/docker/"]
            ' >> "${1}"
    standard_env >> "${1}"

    # Does not require execute permissions, unlike:
    # ENTRYPOINT ["/usr/local/bin/tini", "--", "/vsi/linux/just_files/just_entrypoint.sh"]
    uwecho '
            ENTRYPOINT ["/usr/local/bin/tini", "--", "/usr/bin/env", "bash", "/vsi/linux/just_files/just_entrypoint.sh"]

            #T# Update the default '"${APP_NAME}.Justfile"' target
            CMD ["'"${APP_NAME}"'-cmd"]' >> "${1}"
}

#########################
### deploy.Dockerfile ###
#########################
function write_deploy_dockerfile()
{
  exists "${1}" && return 0

  uwecho   '# syntax=docker/dockerfile:1.4
            ARG SOURCE_IMAGE
            ARG '"${APP_NAME_UPPER}"'_IMAGE

            # --------------------
            # Source cache: /src /vsi
            # -- add vsi_common
            # -- add source files & folders
            # -- link vsi, src
            # -- add install volume from tar file
            # --------------------
            FROM ${SOURCE_IMAGE} AS source_cache

            # clear the way
            RUN rm -rf /src /vsi

            # vsi_common
            COPY --from=vsi /vsi /vsi

            # link src, vsi, etc...
            RUN mkdir -p /src/'"$(quote_escape "$(dirname "${RELATIVE_PATH}")")"'; \
                ln -s /vsi /src/'"$(quote_escape "${RELATIVE_PATH}")"';

            # files & folders' >> "${1}"
  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    echo   'COPY '"${PROJECT_NAME}"'.env /src/' >> "${1}"
  fi
  uwecho   'COPY docker/'"$(docker_add_quote_escape docker/"${APP_NAME}.Justfile")"' /src/docker/

            # apps & scripts' >> "${1}"
  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    echo   'COPY '"${PYTHON_PACKAGE}"' /src/'"${PYTHON_PACKAGE}" >> "${1}"
  fi
  uwecho   'COPY scripts /src/scripts

            # entrypoint & command are reused from SOURCE_IMAGE
            ' >> "${1}"

  uwecho   '# --------------------
            # Final deployment
            # --------------------
            FROM ${'"${APP_NAME_UPPER}"'_IMAGE}

            # clear the way' >> "${1}"
  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    echo   'RUN rm -rf /src /cache /vsi' >> "${1}"
  else
    echo   'RUN rm -rf /src /vsi' >> "${1}"
  fi
  uwecho   '
            # copy from source_cache
            COPY --from=source_cache /src /src
            COPY --from=source_cache /vsi /vsi

            # default working directory
            WORKDIR /src

            # optionally remove development packages
            ARG REMOVE_DEV_PACKAGES=0
            RUN if [ "${REMOVE_DEV_PACKAGES}" = "1" ]; then \
                  dnf -y remove \
                      gdb \
                      openssl \
                      python3 \
                      vim-minimal \
                      ; \
                fi

            # entrypoint & command are reused from '"${APP_NAME_UPPER}"'_IMAGE' >> "${1}"
}

####################
### app.Justfile ###
####################
function write_app_justfile()
{
  exists "${1}" 755 && return 0

  uwecho   '#!/usr/bin/env false bash
            #T# Env false because you should never run this file directly.
            #T# bash is added because most code editors will detect this as a shell
            #T# script and use the proper syntax highlighting

            source "${VSI_COMMON_DIR}/linux/just_files/just_default_run_functions.bsh"
            source "${VSI_COMMON_DIR}/linux/just_files/just_pip-tools_functions.bsh"

            cd "${'"${PROJECT_PREFIX}"'_CWD}"

            function caseify()
            {
              local cmd="${1}"
              shift 1
              case "${cmd}" in
                #T# default CMD
                '"${APP_NAME}"'-cmd) # Run '"${APP_NAME}"'
                  echo "Run '"${APP_NAME}"' here: ${cmd} ${@+${@}}"
                  ;;

                shell) # Start interactive shell' >> "${1}"

  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    echo   '      bash --rcfile /venv/src/bin/activate ${@+"${@}"}' >> "${1}"
  else
    echo   '      bash' >> "${1}"
  fi

  uwecho '      ;;
              *)
                #T# When a container runs, its docker "command" is passed to this Justfile.
                #T# This command is matched to the just targets above. When none of those
                #T# targets are matched, this defaultify will run the command exactly as is
                #T# (thanks to the just_default_run_functions plugin)
                defaultify "${cmd}" ${@+"${@}"}
                ;;
            esac
          }' >> "${1}"
}

######################################
 ### deploy.Dockerfile.dockerignore ###
######################################

function deploy_Dockerfile_dockerignore()
{
  exists "${1}" && return 0

  uwecho   '# ignore everything...
            *

            # except for these files & folders...
            !docker
            # !requirements.in
            !requirements.txt
            !'"${PROJECT_NAME}.env"'
            !pyproject.toml

            # apps & scripts
            !scripts' >> "${1}"

  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    echo   "!${PYTHON_PACKAGE}" >> "${1}"
  fi

  uwecho   '
            # vsi_common
            !'"${RELATIVE_PATH}" >> "${1}"
}

##########################
### docker-compose.yml ###
##########################
function write_docker_compose_yml()
{
  exists "${1}" && return 0

  uwecho   '# Common environment
            x-common-environment: &common_environment
              DISPLAY:
              TZ:
              #T# Variables for just_entrypoint_functions
              DOCKER_UID: ${'"${PROJECT_PREFIX}"'_UID}
              DOCKER_GIDS: ${'"${PROJECT_PREFIX}"'_GIDS}
              DOCKER_GROUP_NAMES: ${'"${PROJECT_PREFIX}"'_GROUP_NAMES}
              DOCKER_USERNAME: ${'"${PROJECT_PREFIX}"'_USERNAME_DOCKER}
              #T# Uncomment to add a persistent home directory. This is nice
              #T# for bash history and some applications that store files in home
              # DOCKER_HOME: ${'"${PROJECT_PREFIX}"'_HOME}

            x-volumes:
              - &source_volume
                type: ${'"${PROJECT_PREFIX}"'_SOURCE_DIR_TYPE}
                source: ${'"${PROJECT_PREFIX}"'_SOURCE_DIR}
                target: ${'"${PROJECT_PREFIX}"'_SOURCE_DIR_DOCKER}
              - &vsi_volume
                type: ${'"${PROJECT_PREFIX}"'_VSI_COMMON_DIR_TYPE}
                source: ${'"${PROJECT_PREFIX}"'_VSI_COMMON_DIR}
                target: ${'"${PROJECT_PREFIX}"'_VSI_COMMON_DIR_DOCKER}
              - &cache_volume
                type: volume
                source: cache
                target: /cache' >> "${1}"
  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    uwecho '  - &venv_volume
                type: volume
                source: venv
                target: /venv' >> "${1}"
  fi
  uwecho   '#   - &home_volume
            #     type: volume
            #     source: home-volume
            #     target: ${'"${PROJECT_PREFIX}"'_HOME} # override home-volume

            services:
              '"${APP_NAME}"': &'"${APP_NAME}"'
                build: &'"${APP_NAME}"'_build
                  context: .
                  dockerfile: docker/'"${APP_NAME}"'.Dockerfile
                  args:
                    VSI_RECIPE_REPO:
                    SKIP_PIP_SYNC: ${'"${PROJECT_PREFIX}"'_SKIP_PIP_SYNC-}
                    PYTHON_VERSION: ${'"${PROJECT_PREFIX}"'_PYTHON_VERSION}
                    BASE_IMAGE: "${'"${PROJECT_PREFIX}"'_BASE_IMAGE}"

                #T# prevent different users from clobbering each others images
                image: ${'"${PROJECT_PREFIX}_${APP_NAME_UPPER}"'_IMAGE}
                environment:
                  <<: *common_environment
                  JUSTFILE: /src/docker/'"${APP_NAME}"'.Justfile
                  JUST_SETTINGS: /src/'"${PROJECT_NAME}"'.env
            #     cap_add:
            #       - SYS_PTRACE # Useful for gdb
                volumes:
                  - <<: *source_volume
                  - <<: *vsi_volume
                  - <<: *cache_volume
            #       - <<: *home_volume' >> "${1}"


  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    uwecho '      - <<: *venv_volume

              python_cache:
                <<: *'"${APP_NAME}"'
                build:
                  <<: *'"${APP_NAME}"'_build
                  target: python_cache
                image: ${'"${PROJECT_PREFIX}"'_PYTHON_CACHE_IMAGE}
                volumes:
                  - <<: *source_volume
                    read_only: ${'"${PROJECT_PREFIX}"'_SOURCE_DIR_READONLY-false}
                  - <<: *vsi_volume
                  - <<: *cache_volume
                  - <<: *venv_volume
                    read_only: ${'"${PROJECT_PREFIX}"'_VENV_DIR_READONLY-false}
                  # - *home_volume' >> "${1}"
  fi
  uwecho   '
              # Deploy image
              deploy: &deploy
                <<: *'"${APP_NAME}"'
                build:
                  context: .
                  dockerfile: docker/deploy.Dockerfile
                  args:
                    SOURCE_IMAGE: ${'"${PROJECT_PREFIX}_${APP_NAME_UPPER}"'_IMAGE}
                    '"${APP_NAME_UPPER}"'_IMAGE: ${'"${PROJECT_PREFIX}_${APP_NAME_UPPER}"'_IMAGE}
                    REMOVE_DEV_PACKAGES: ${'"${PROJECT_PREFIX}"'_REMOVE_DEV_PACKAGES}
                image: ${'"${PROJECT_PREFIX}"'_DEPLOY_IMAGE}
                environment:
                  <<: *common_environment
                volumes: []

            volumes:
            #   x-bugfix:
            #   home-volume:' >> "${1}"

  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    uwecho '  venv:
                labels:
                  com.vsi.just.clean_action: ask
              cache:
                labels:
                  com.vsi.just.clean_action: delete' >> "${1}"
  fi
}

############################
### singular-compose.env ###
############################
function write_singular_compose_env()
{
  exists "${1}" && return 0

  uwecho 'instances+=('"${APP_NAME}"')
          '"${APP_NAME}"'_singular_flags=(-c -e)
          : ${'"${APP_NAME}"'_image=${'"${PROJECT_PREFIX}"'_RUN_DIR}/build/'"${APP_NAME}"'_'"${APP_NAME}"'.simg}' >> "${1}"
}

#####################
### .dockerignore ###
#####################
function write_dockerignore()
{
  if ! exists "${1}"; then
    uwecho "#T# Just dockers ignore all files by default, and you must add exceptions
            #T# for each file you want to have added to the docker context. This is for cases
            #T# where large amounts of data exist in the source folder, e.g., for development
            #T# purposes, which are not need for docker build; ignoring these files results
            #T# in faster build time.
            *" > "${1}"
  # https://stackoverflow.com/a/40919/4166604
  elif [ "$(tail -c 1 "${1}" 2>/dev/null)" != "" ]; then
    echo >> "${1}"
  fi

  if ! grep -q '!docker' "${1}" 2>/dev/null; then
    uwecho '#T# Add the entire docker subdir, everything in there should be for building images.
            !docker' >> "${1}"
  fi
  if [ "${USE_VSI_COMMON}" = "1" ] && ! grep -q '!'"${RELATIVE_PATH}" "${1}" 2>/dev/null; then
    uwecho '#T# We need the vsi common dir for the vsi recipes
            !'"${RELATIVE_PATH}" >> "${1}"
  fi

  if ! grep -q '!'"${PROJECT_NAME}.env" "${1}" 2>/dev/null; then
    echo   '!'"${PROJECT_NAME}.env" >> "${1}"
  fi

  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    if ! grep -q '!requirements.txt$' "${1}" 2>/dev/null; then
      echo '!requirements.txt' >> "${1}"
    fi
    if ! grep -q '!pyproject.toml$' "${1}" 2>/dev/null; then
      echo '!pyproject.toml' >> "${1}"
    fi
  fi
}

######################
### .gitattributes ###
######################
function write_gitattributes()
{
  if ! exists "${1}"; then
    uwecho '#T# These file types are being explicitly set to linux line endings for windows.
            #T# This is to allow a windows user to edit and run these files inside a linux docker.
            #T# This list may need additions as time goes on' > "${1}"
  elif [ "$(tail -c 1 "${1}" 2>/dev/null)" != "" ]; then
    echo >> "${1}"
  fi

  if ! grep -q "\*\.sh" "${1}" 2>/dev/null; then
    echo '*.sh eol=lf' >> "${1}"
  fi
  if ! grep -q "\*\.bsh" "${1}" 2>/dev/null; then
    echo '*.bsh eol=lf' >> "${1}"
  fi
  if ! grep -q "\*\.py" "${1}" 2>/dev/null; then
    echo '*.py eol=lf' >> "${1}"
  fi
  if ! grep -q "\*\.env" "${1}" 2>/dev/null; then
    echo '*.env eol=lf' >> "${1}"
  fi
  if ! grep -q "\.justplugins" "${1}" 2>/dev/null; then
    echo '.justplugins eol=lf' >> "${1}"
  fi
  if ! grep -q "Justfile" "${1}" 2>/dev/null; then
    echo 'Justfile eol=lf' >> "${1}"
  fi
  if [ "${USE_DOCKER}" = "1" ] && ! grep -q "\*\.Justfile" "${1}" 2>/dev/null; then
    echo '*.Justfile eol=lf' >> "${1}"
  fi
}

##############
### hi.cpp ###
##############
function write_hi_cpp()
{
  exists "${1}" && return 0
  uwecho '#include <iostream>
          int main()
          {
            std::cout << "hello world!" << std::endl;
            return 0;
          }' >> "${1}"
}

##################
### .gitignore ###
##################
function write_gitignore()
{
  if [ "$(tail -c 1 "${1}" 2>/dev/null)" != "" ]; then
    echo >> "${1}"
  fi

  if ! grep -q local.env "${1}" 2>/dev/null; then
    echo local.env >> "${1}"
  fi
  if ! grep -q local_post.env "${1}" 2>/dev/null; then
    echo local_post.env >> "${1}"
  fi
  if [ "${USE_DOCKER}" = "1" ]; then
    if ! grep -q .just_synced "${1}" 2>/dev/null; then
      echo .just_synced >> "${1}"
    fi
  fi
}

########################
### scripts/test.bsh ###
########################
function test_script()
{
  exists "${1}" && return 0

  uwecho '#!/usr/bin/env python
          import '"${PYTHON_PACKAGE}"'
          print('"${PYTHON_PACKAGE}"'.__file__)' >> "${1}"
}


function format_tutorial()
{
  if [ "${USE_TUTORIAL-}" = "1" ]; then
    # Convert #T# to #
    sed "${sed_flags_i[@]}" '/^ *#T# /{ s/#T#/#/; };
                             # Clear blank lines
                             s/^ *#T#$//' "${1}"
  else
    # Remove #T# lines
    sed "${sed_flags_i[@]}" '/^ *#T#/d' "${1}"
  fi
}

#**
# .. function:: new_just
#
# The main new_just function. The script to be in a function, or else streaming ``curl`` to bash won't work.
#**
function new_just()
{

  # Default values set for you
  if [ "${JUST_FROZEN-}" = "1" ]; then
    : ${USE_VSI_COMMON=n}
  else
    : ${USE_VSI_COMMON=y}
  fi

  : ${SETUPFILE=setup.env}
  : ${JUSTFILE=Justfile}


  while ((${#})); do
    arg="${1}"
    shift 1
    case ${arg} in
      --help)
        echo "New Just Wizard usage"
        echo "====================="
        echo "  --help - prints this help"
        echo "  --project-dir DIR - Use dir as the project directory"
        echo "  --project-name NAME - Affects project env filename and defaults"
        echo "  --prefix PREFIX - Set variable prefix to use"
        echo "  --justfile FILE - Set Justfile name"
        echo "  --setupfile FILE - Set setup.env filename"
        echo "  --vsi-dir DIR - VSI common submodule dir"
        echo "  --[no-]vsi - Enable/[disable] using the vsi submodule"
        echo "  --[no-]docker - Enable/[disable] setting up docker"
        echo "  --[no-]pip-tools - Enable/[disable] using pip-tools in docker"
        echo "  --python-package - Starter python package name"
        echo "  --[no-]tutorial - Enable/[disable] adding tutorial comments"
        echo "  --app APP - Application name for docker compose service"
        echo "  --repo REPO - Docker repo name for compiled images"
        echo "  --defaults - Use all defaults without prompting"
        echo "  --continue - Set to auto continue summary without prompting"
        echo "  --[no-]git - Run the necessary git command to init repo"
        echo
        exit 0
        ;;
      --defaults)
        USE_DEFAULTS=1
        : ${USE_DOCKER=y}
        : ${USE_PIP_TOOLS=y}
        : ${USE_TUTORIAL=n}
        : ${SETUP_GIT=n}
        ;;
      --project-dir)
        PROJECT_DIR="${1}"
        shift 1
        ;;
      --project-name)
        PROJECT_NAME="${1}"
        shift 1
        ;;
      --prefix)
        PROJECT_PREFIX="${1}"
        shift 1
        ;;
      --justfile)
        JUSTFILE="${1}"
        shift 1
        ;;
      --setupfile)
        SETUPFILE="${1}"
        shift 1
        ;;
      --vsi-dir)
        VSI_DIR="${1}"
        shift 1
        ;;
      --vsi)
        USE_VSI_COMMON=y
        ;;
      --no-vsi)
        USE_VSI_COMMON=n
        ;;
      --docker)
        USE_DOCKER=y
        ;;
      --no-docker)
        USE_DOCKER=n
        ;;
      --pip-tools)
        USE_PIP_TOOLS=y
        ;;
      --no-pip-tools)
        USE_PIP_TOOLS=n
        ;;
      --python-package)
        PYTHON_PACKAGE="${1}"
        shift 1
        ;;
      --app)
        APP_NAME="${1}"
        shift 1
        ;;
      --repo)
        REPO_NAME="${1}"
        shift 1
        ;;
      --continue)
        CONTINUE=y
        ;;
      --git)
        SETUP_GIT=y
        ;;
      --no-git)
        SETUP_GIT=n
        ;;
      --tutorial)
        USE_TUTORIAL=y
        ;;
      --no-tutorial)
        USE_TUTORIAL=n
        ;;
      *)
        echo "Unknown argument: ${arg}"
        exit 1
        ;;
    esac
  done

  echo "Setting up a new project to use J.U.S.T."
  echo

  #**
  # .. env:: PROJECT_DIR
  #
  # The project directory is typically the root directory of the main git
  # repository. This is where all the just files will be stored by default.
  #
  # The value can be customized when prompted by :file:`new_just`. The default value
  # is the current working directory. The question can be skipped by setting
  # the environment variable :env:`PROJECT_DIR` to the desired value.
  #**

  if set_default PROJECT_DIR "${PWD}" "Project directory"; then
    mkdir -p "${PROJECT_DIR}"
    PROJECT_DIR="$(cd "${PROJECT_DIR}"; pwd)"
  fi

  #**
  # .. env:: PROJECT_NAME
  #
  # The :env:`PROJECT_NAME` is used to set the project environment file name used by
  # :func:`just_functions.bsh source_environment_files`. Specifically: ``${PROJECT_DIR}/${PROJECT_NAME}.env``
  # :env:`PROJECT_NAME` is also used for for determining other default values.
  #
  # The value can be customized when prompted by :file:`new_just`. The default value
  # is basename of the :env:`PROJECT_DIR`. The question can be skipped by setting the
  # environment variable :env:`PROJECT_NAME` to the desired value.
  #
  # .. seealso::
  #   :func:`just_functions.bsh source_environment_files`
  #**

  set_default PROJECT_NAME "$(basename "${PROJECT_DIR}")" "Project name" || :

  #**
  # .. env:: PROJECT_PREFIX
  #
  # The prefix of environment variables for this project
  #
  # Used to set the value of :envvar:`JUST_PROJECT_PREFIX`. Must contain only valid bash
  # variable-name characters: ``[A-Z0-9_]+``
  #
  # Does not need the trailing _ included, this will always be added when it is
  # used.
  #
  # The value can be customized when prompted by new_just. The default value is
  # uppercase of the :env:`PROJECT_NAME`. The question can be skipped by setting the
  # environment variable :env:`PROJECT_PREFIX` to the desired value.
  #
  # .. seealso::
  #
  #   :envvar:`JUST_PROJECT_PREFIX`
  #**

  if set_default PROJECT_PREFIX \
                 "$(echo "${PROJECT_NAME-}" | tr '[a-z]' '[A-Z]' | sed -${sed_flag_rE} 's|[^A-Z0-9_]+||g')" \
                 "Project variable prefix"; then
    PROJECT_PREFIX="$(echo "${PROJECT_PREFIX-}" | tr '[a-z]' '[A-Z]' | sed -${sed_flag_rE} 's|[^A-Z0-9_]+||g')"
  fi

  #**
  # .. env:: JUSTFILE
  #
  # Name of the justfile used
  #
  # The default just file in just is "Justfile". If this is changed, the only
  # way to inform just of this is by setting it in the :env:`SETUPFILE`. This will be
  # added to the :env:`SETUPFILE` by :file:`new_just` if anything other than the default is
  # used.
  #
  # The value can be customized when prompted by :file:`new_just`. The default value
  # is ``Justfile``. The question can be skipped by setting the environment variable
  # :env:`JUSTFILE` to the desired value.
  #
  # .. seealso::
  #
  #   :env:`SETUPFILE`, just/Justfile
  #**

  if set_default JUSTFILE "Justfile" "Just file name"; then
    JUSTFILE="$(basename "${JUSTFILE}")"
  fi

  #**
  # .. env:: USE_VSI_COMMON
  #
  # Flag to include vsi_common
  #
  # vsi_common can either be included as a submodule, in which case setup.env
  # is needed, or the just executable needs to be installed and vsi_common is
  # not needed.
  #**

  echo "If you do not include vsi_common, then all users will have to have"
  echo "the juste executable installed and on their path to use this project."
  echo "(It also makes updating vsi_common features/bugs harder.)"
  ask_question "Do you want to include the vsi_common submodule?" USE_VSI_COMMON n

  #**
  # .. env:: SETUPFILE
  #
  # Name of the setup file sourced to setup just environment
  #
  # The setup file is used to make the minimal necessary changes to the
  # environment so that just works. This includes adding paths and setting a
  # few environment variables. This is meant to be as unobtrusive as possible.
  #
  # This file needs to be sourced every time a new terminal session is opened.
  # The only time the setup script is not needed is when using :command:`juste`.
  #
  # The value can be customized when prompted by :file:`new_just`. The default value is
  # ``setup.env``. The question can be skipped by setting the environment variable
  # :env:`SETUPFILE` to the desired value.
  #
  # .. seealso::
  #
  #   :command:`juste`
  #**

  if [ "${USE_VSI_COMMON}" = "1" ]; then
    if set_default SETUPFILE "setup.env" "Environment setup script file name"; then
      SETUPFILE="$(basename "${SETUPFILE}")"
    fi
  fi

  #**
  # .. env:: VSI_DIR
  #
  # Location of the vsi_common submodule
  #
  # The vsi_common repository is necessary for virtually all of the just
  # capabilities. The correct way to deal with this is to add vsi_common as a
  # submodule for your main project. This tells just were this submodule is
  # located.
  #
  # The value can be customized when prompted by :file:`new_just`. The default value is
  # external/vsi_common. The question can be skipped by setting the environment
  # variable :env:`VSI_DIR` to the desired value.
  #**

  if [ "${USE_VSI_COMMON}" = "1" ]; then
    if set_default VSI_DIR "${PROJECT_DIR}/external/vsi_common" "VSI common module path"; then
      # Guarantee the path is clean, no .. or . or // in the name
      VSI_DIR="$(cd "${PROJECT_DIR}";
                if [ -e "${VSI_DIR}" ]; then
                  cd "${VSI_DIR}";
                  pwd;
                else
                  mkdir -p "${VSI_DIR}";
                  cd "${VSI_DIR}";
                  pwd;
                  cd "${PROJECT_DIR}";
                  rmdir "${VSI_DIR}";
                fi )"  # <- the space after the fi fixes a VSCode highlighter bug
    fi
  fi

  #**
  # .. rubric:: Docker Stuff
  #
  # .. env:: USE_DOCKER
  #
  # Flag to turn on all the docker features
  #**

  ask_question "Use docker?" USE_DOCKER y

  #**
  # .. env:: USE_PIP_TOOLS
  #
  # Flag to turn on setting up pip-tools
  #**

  ask_question "Use pip-tools?" USE_PIP_TOOLS y
  if [ "${USE_DOCKER}" = "1" ]; then

    #**
    # .. env:: APP_NAME
    #
    # The name of the test app generated. Used for the name of the first service populated for you.
    #**
    if set_default APP_NAME \
                   "$(echo "${APP_NAME-runner}" | tr '[A-Z]' '[a-z]' | sed -${sed_flag_rE} 's|[^a-z0-9_.-]+||g')" \
                   "Name of the first docker compose service"; then
      APP_NAME="$(echo "${APP_NAME}" | tr '[A-Z]' '[a-z]' | sed -${sed_flag_rE} 's|[^a-z0-9_.-]+||g')"
    fi

    APP_NAME_UPPER="$(echo "${APP_NAME}" | tr '[a-z]' '[A-Z]')"

    #**
    # .. env:: REPO_NAME
    #
    # When docker images are built, they need to be named, or else the only way to
    # access them is inconveniently though sha256 checksums.
    #
    # The :env:`REPO_NAME` should be an untagged docker repository name. Tag names will
    # be added for each service. Docker image names must match the regex
    # ``[a-zA-Z0-9][a-zA-Z0-9_.-]*`` or else docker will error
    #
    # The value can be customized when prompted by :file:`new_just`. The default value is
    # lowercase of the :env:`PROJECT_NAME`. The question can be skipped by setting the
    # environment variable :env:`REPO_NAME` to the desired value.
    #**
    if set_default REPO_NAME \
                   "$(echo "${PROJECT_NAME-}" | tr '[A-Z]' '[a-z]' | sed -${sed_flag_rE} 's|[^a-z0-9_./-]+||g')" \
                   "Docker Repo for images"; then
      REPO_NAME="$(echo "${REPO_NAME-}" | tr '[A-Z]' '[a-z]' | sed -${sed_flag_rE} 's|[^a-z0-9_./-]+||g')"
    fi
  fi

  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    #**
    # .. env:: PYTHON_PACKAGE
    #
    # Name of the default python package created
    #**
    if set_default PYTHON_PACKAGE \
                   "$(echo "${PROJECT_NAME-}" | tr '[A-Z]' '[a-z]' | sed -${sed_flag_rE} 's|[^a-z0-9_]+||g')" \
                   "Name of python package"; then
      PYTHON_PACKAGE="$(echo "${PYTHON_PACKAGE-}" | tr '[A-Z]' '[a-z]' | sed -${sed_flag_rE} 's|[^a-z0-9_]+||g')"
    fi
  fi

  #**
  # .. env:: USE_TUTORIAL
  #
  # Flag to turn on tutorial comments
  #**
  ask_question "Add tutorial comments? (good if you are unfamiliar with just)" USE_TUTORIAL n

  echo "Summary"
  echo "======="
  printf "%-40s | %-40s\n" "Project Name" "${PROJECT_NAME}"
  printf "%-40s | %-40s\n" "Project Directory" "${PROJECT_DIR}"
  printf "%-40s | %-40s\n" "Project prefix" "${PROJECT_PREFIX}"
  printf "%-40s | %-40s\n" "Just file" "${JUSTFILE}"
  if [ "${USE_VSI_COMMON}" = "1" ]; then
    printf "%-40s | %-40s\n" "Environment setup script" "${SETUPFILE}"
  fi

  printf "%-40s | %-40s\n" "Use docker" "${USE_DOCKER}"
  printf "%-40s | %-40s\n" "Use pip-tools" "${USE_PIP_TOOLS}"
  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    printf "%-40s | %-40s\n" "Python package name" "${PYTHON_PACKAGE}"
  fi
  printf "%-40s | %-40s\n" "Include vsi_common" "${USE_VSI_COMMON}"
  if [ "${USE_DOCKER}" = "1" ]; then

    printf "%-40s | %-40s\n" "App Name" "${APP_NAME}"
    printf "%-40s | %-40s\n" "Docker Repo" "${REPO_NAME}"
  fi
  if [ "${USE_VSI_COMMON}" = "1" ]; then
    printf "%-40s | %-40s\n" "VSI Common Directory" "${VSI_DIR}"
  fi
  printf "%-40s | %-40s\n" "Add Tutorial" "${USE_TUTORIAL}"
  echo
  # Ask y/n question
  ask_question "Continue?" CONTINUE y

  if [ "${CONTINUE}" != "1" ]; then
    exit 1
  fi

  # Setup done, start making the new environment

  if [ "${USE_VSI_COMMON}" = "1" ]; then
    # RELATIVE_PATH="$(python -c "import os; print(os.path.relpath('${VSI_DIR}', '${PROJECT_DIR}'))")"
    # RELATIVE_PATH="$(perl -e "use File::Spec; print File::Spec->abs2rel('${VSI_DIR}','${PROJECT_DIR}');")"
    RELATIVE_PATH="$(relative_path "${VSI_DIR}" "${PROJECT_DIR}")"
  fi

  if [ "${USE_VSI_COMMON}" = "1" ]; then
    just_cmd="just"
  else
    just_cmd="juste"
  fi

  # Make project dir
  mkdir -p "${PROJECT_DIR}"

  ###################
  # Write out files #
  ###################
  if [ "${USE_VSI_COMMON}" = "1" ]; then
    write_setup_env "${PROJECT_DIR}/${SETUPFILE}"
  fi

  write_project_env "${PROJECT_DIR}/${PROJECT_NAME}.env"
  format_tutorial "${PROJECT_DIR}/${PROJECT_NAME}.env"

  write_readme_md "${PROJECT_DIR}/README.md"

  write_justfile "${PROJECT_DIR}/${JUSTFILE}"
  format_tutorial "${PROJECT_DIR}/${JUSTFILE}"

  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    write_requirements_in "${PROJECT_DIR}/requirements.in"
    format_tutorial "${PROJECT_DIR}/requirements.in"
    write_requirements_txt "${PROJECT_DIR}/requirements.txt"
    format_tutorial "${PROJECT_DIR}/requirements.txt"
    write_pyproject_toml "${PROJECT_DIR}/pyproject.toml"
    format_tutorial "${PROJECT_DIR}/pyproject.toml"

    mkdir -p "${PROJECT_DIR}/${PYTHON_PACKAGE}"
    touch "${PROJECT_DIR}/${PYTHON_PACKAGE}/__init__.py"

    mkdir -p "${PROJECT_DIR}/scripts"
    test_script "${PROJECT_DIR}/scripts/test_script.py"
    chmod 755 "${PROJECT_DIR}/scripts/test_script.py"
  fi

  if [ "${USE_DOCKER}" = "1" ]; then
    mkdir -p "${PROJECT_DIR}/docker"

    write_dockerfile "${PROJECT_DIR}/docker/${APP_NAME}.Dockerfile"
    format_tutorial "${PROJECT_DIR}/docker/${APP_NAME}.Dockerfile"

    write_app_justfile "${PROJECT_DIR}/docker/${APP_NAME}.Justfile"
    format_tutorial "${PROJECT_DIR}/docker/${APP_NAME}.Justfile"

    write_deploy_dockerfile "${PROJECT_DIR}/docker/deploy.Dockerfile"
    format_tutorial "${PROJECT_DIR}/docker/deploy.Dockerfile"

    deploy_Dockerfile_dockerignore "${PROJECT_DIR}/docker/deploy.Dockerfile.dockerignore"
    format_tutorial "${PROJECT_DIR}/docker/deploy.Dockerfile"

    write_docker_compose_yml "${PROJECT_DIR}/docker-compose.yml"
    format_tutorial "${PROJECT_DIR}/docker-compose.yml"

    write_dockerignore "${PROJECT_DIR}/.dockerignore"
    format_tutorial "${PROJECT_DIR}/.dockerignore"
  else
    write_hi_cpp "${PROJECT_DIR}/hi.cpp"
  fi

  # Some final bookkeeping...
  write_gitignore "${PROJECT_DIR}/.gitignore"
  write_gitattributes "${PROJECT_DIR}/.gitattributes"
  format_tutorial "${PROJECT_DIR}/.gitattributes"

  # ****************************************************************************
  # ****DONE****DONE****DONE****DONE****DONE****DONE****DONE****DONE****DONE****
  # ****************************************************************************

  cmds=()

  uwecho "Done!


          To complete your git initialization, the following commands must be run:
          ------------------------------------------------------------------------"
  cmds+=("cd $(quote_escape "${PROJECT_DIR}")")
  if [ ! -e "${PROJECT_DIR}/.git" ]; then
    cmds+=("git init .")
  fi

  if [ "${USE_VSI_COMMON}" = "1" ]; then
    cmds+=("git submodule add https://github.com/visionsystemsinc/vsi_common.git $(quote_escape "${RELATIVE_PATH}")"
          "git submodule update --init --recursive"
          "pushd $(quote_escape "${RELATIVE_PATH}")"
          "git remote set-url origin --push git@github.com:visionsystemsinc/vsi_common.git"
          "popd"
          "git add $(quote_escape "${SETUPFILE}") .gitmodules")
  fi

  cmds+=('sed "${sed_flags_i[@]}" '"'"'s|^JUST_VERSION=.*$|JUST_VERSION='"'"'"$(. '"$(quote_escape "${RELATIVE_PATH}")"'/linux/just_files/just_version.bsh; echo "${JUST_VERSION}")|" '"$(quote_escape "${PROJECT_NAME}")"'.env')

  cmds+=("git add .gitignore README.md $(quote_escape "${JUSTFILE}") $(quote_escape "${PROJECT_NAME}").env")

  if [ "${USE_DOCKER}" = "1" ]; then
    cmds+=("git add .gitattributes .dockerignore docker-compose.yml docker/${APP_NAME}.Dockerfile docker/${APP_NAME}.Justfile docker/deploy.Dockerfile docker/deploy.Dockerfile.dockerignore")
  else
    cmds+=("git add hi.cpp")
  fi
  if [ "${USE_PIP_TOOLS}" = "1" ]; then
    cmds+=("git add '${PYTHON_PACKAGE}' requirements.in requirements.txt pyproject.toml scripts/test_script.py")
  fi

  cmds+=("git commit -m 'Initial commit'")

  for cmd in "${cmds[@]}"; do
    echo "${cmd}" >&${just_stdout-1}
  done

  echo ""
  ask_question "Do you want these git commands to be executed for you now?" SETUP_GIT y

  if [ "${SETUP_GIT}" = "1" ]; then
    pushd "${PROJECT_DIR}" &> /dev/null
      for cmd in "${cmds[@]}"; do
        eval "${cmd}"
      done
    popd &> /dev/null
  fi

  uwecho "


          To use your new just:

          cd $(quote_escape "${PROJECT_DIR}")"

  if [ "${USE_VSI_COMMON}" = "1" ]; then
    echo "source $(quote_escape "${SETUPFILE}")"
  fi

  uwecho "${just_cmd} help

          Get started with:"

  if [ "${USE_DOCKER}" = "1" ]; then
    uwecho "${just_cmd} sync
            ${just_cmd} shell"
  else
    uwecho "${just_cmd} compile
            ${just_cmd} run"
  fi
}

# Don't remove this "true", this is part curl compatibility
true

# If statement in case mktemp is commented out for development purposes
if [ -e "${temp_file-}" ]; then
  source "${temp_file}"
fi

function new_just_cleanup()
{
  local rv="${?}"

  if [ "${new_just_cleanup_called-}" = "0" ]; then
    return "${rv}"
  fi

  # We want to delete the temp file when we are done executing, so that uwecho
  # can use the file, the entire point of making the file
  if [ -e "${temp_file-}" ]; then
    \rm "${temp_file}"
  fi
  echo

  if [ "${1-}" = "term" ]; then
    new_just_cleanup_called=0
    exit_chain 143
  elif [ "${1-}" = "int" ]; then
    new_just_cleanup_called=0
    exit_chain 130
  fi

  return "${rv}"
}

# If new_just is being executed
if [ "${BASH_SOURCE[0]}" = "${0}" ] || [ "$(basename "${BASH_SOURCE[0]}")" = "${0}" ]; then
  set -eu

  # Cleanup
  trap "new_just_cleanup exit" EXIT
  trap "new_just_cleanup int" INT
  trap "new_just_cleanup term" TERM
  # I didn't makes these traps just "exit", becuase I'm not sure that'll work
  # in all OSes and cornercases

  # Redirect stderr to stdout, and stdout to 3; now the only way to write to
  # stdout or stderr is to write to 3
  exec 3>&1 # Copy stdout to 3
  exec 1>&2 # Copy stderr to 1
  just_stdout=3 new_just ${@+"${@}"}

  # # Stop new_just_cleanup from being called twice
  # trap -- EXIT
else
  # uwecho won't work right unless this file is kept around. If being sourced,
  # then VSI_COMMON_DIR is there, and use trap_chain
  source "${VSI_COMMON_DIR}/linux/signal_tools.bsh"

  trap_chain "new_just_cleanup exit" EXIT
  trap_chain "new_just_cleanup int" INT
  trap_chain "new_just_cleanup term" TERM
fi
