diff --git a/modules/dotbot b/modules/dotbot index cf366bbf6..aa9335089 160000 --- a/modules/dotbot +++ b/modules/dotbot @@ -1 +1 @@ -Subproject commit cf366bbf6676d1c95f412eb514509f16322b5c9c +Subproject commit aa9335089b54475940bb41e2a4eed38affeb5916 diff --git a/modules/zimfw b/modules/zimfw index dfbe53543..7d533fcec 160000 --- a/modules/zimfw +++ b/modules/zimfw @@ -1 +1 @@ -Subproject commit dfbe535430271c5ee0bbd7cfac6df42222e8cdf0 +Subproject commit 7d533fcecd7fbf410ccf71e188dcc9af06c0d5d8 diff --git a/scripts/core/output.sh b/scripts/core/output.sh index 4509bfa32..952087058 100644 --- a/scripts/core/output.sh +++ b/scripts/core/output.sh @@ -1,24 +1,145 @@ -#!/usr/bin/env bash - red='\033[0;31m' green='\033[0;32m' bold_blue='\033[1m\033[34m' normal='\033[0m' +_output::parse_code() { + local color="$normal" + case "${1:-}" in + --color) + color="$2" + shift 2 + ;; + esac + + echo "color: $color" + + local -r text="${*:-}" + + with_code_parsed=$(echo "$text" | awk "{ORS=(NR+1)%2==0?\"${green}\":RS}1" RS="\`" | awk "{ORS=NR%1==0?\"${color}\":RS}1" RS="\`" | tr -d '\n') + + echo -e "$with_code_parsed" +} + output::write() { - local -r text="${1:-}" - echo -e "$text" + local with_code_parsed color + color="$normal" + case "${1:-}" in + --color) + color="$2" + shift 2 + ;; + esac + + local -r text="${*:-}" + with_code_parsed="$(_output::parse_code --color "${color}" "$text")" + echo -e "$with_code_parsed" +} +output::answer() { + local color + color="$normal" + case "${1:-}" in + --color) + color="$2" + shift 2 + ;; + esac + output::write --color "${color}" " > ${*:-}"; } -output::answer() { output::write " > $1"; } -output::error() { output::answer "${red}$1${normal}"; } -output::solution() { output::answer "${green}$1${normal}"; } +output::error() { output::answer --color "${red}" "${red}${*:-}${normal}"; } +output::solution() { output::answer --color "${green}" "${green}${*:-}${normal}"; } output::question() { - if [ platform::is_macos ]; then - output::answer "🤔 $1: "; - read -r "$2"; + local with_code_parsed color + color="$normal" + case "${1:-}" in + --color) + color="$2" + shift 2 + ;; + esac + with_code_parsed="$(_output::parse_code --color "${color}" "${1:-}")" + + if [ "${DOTLY_ENV:-PROD}" == "CI" ] || [ "${DOTLY_INSTALLER:-false}" = true ]; then + answer="y" + else + read -rp "🤔 $with_code_parsed: " "answer" + fi + + echo "$answer" +} +output::answer_is_yes() { + if [[ "${1:-Y}" =~ ^[Yy] ]]; then + return 0 + fi + + return 1 +} + +output::question_default() { + local with_code_parsed color question default_value var_name + color="$normal" + case "${1:-}" in + --color) + color="$2" + shift 2 + ;; + esac + + [[ $# -lt 3 ]] && return 1 + + question="$1" + default_value="$2" + var_name="$3" + + with_code_parsed="$(_output::parse_code --color "${color}" "$question")" + + read -rp "🤔 $with_code_parsed ? [$default_value]: " PROMPT_REPLY + eval "$var_name=\"${PROMPT_REPLY:-$default_value}\"" +} + +output::yesno() { + local with_code_parsed color question default PROMPT_REPLY values + color="$normal" + case "${1:-}" in + --color) + color="$2" + shift 2 + ;; + esac + + [[ $# -eq 0 ]] && return 1 + + question="$1" + default="${2:-Y}" + with_code_parsed="$(_output::parse_code --color "${color}" "$question")" + + if [[ "$default" =~ ^[Yy] ]]; then + values="Y/n" else - read -rp " $1: " "$2" + values="y/N" fi + + output::question_default "$with_code_parsed" "$values" "PROMPT_REPLY" + [[ "$PROMPT_REPLY" == "$values" ]] && PROMPT_REPLY="" + [[ "${PROMPT_REPLY:-$default}" =~ ^[Yy] ]] } + output::empty_line() { echo ''; } -output::header() { output::empty_line; output::write "${bold_blue}---- $1 ----${normal}"; } + +output::header() { + output::empty_line + output::write --color "${bold_blue}" "${bold_blue}---- ${*:-} ----${normal}" +} +output::h1_without_margin() { output::write --color "${bold_blue}" "${bold_blue}# ${*:-}${normal}"; } +output::h1() { + output::empty_line + output::h1_without_margin "${*:-}" +} +output::h2() { + output::empty_line + output::write --color "${bold_blue}" "${bold_blue}## ${*:-}${normal}" +} +output::h3() { + output::empty_line + output::write --color "${bold_blue}" "${bold_blue}### ${*:-}${normal}" +} diff --git a/scripts/script/create b/scripts/script/create new file mode 100755 index 000000000..2e3512689 --- /dev/null +++ b/scripts/script/create @@ -0,0 +1,152 @@ +#!/usr/bin/env bash + +set -euo pipefail + +[[ -z "$DOTLY_PATH" ]] && exit 1 + +#shellcheck source=/dev/null +. "$DOTLY_PATH/scripts/core/_main.sh" +dot::load_library "templating.sh" + +##? Dotly script creator +##? +##? +##? Usage: +##? create [-h | --help] +##? create [-v | --version] +##? create bin +##? create [-c | --core] [-s | --sample] [--author ] [--email ] [...] +##? +##? Options: +##? bin Create a command in $DOTFILES_PATH/bin +##? -h --help Show this help +##? -v --version Show the program version +##? -s --sample Create a script using more complete example with some comments. +##? useful if it is your first script. Anyway you can see more help +##? in the docopt website: http://docopt.org +##? -c --core Create the context and script in "$DOTLY_PATH/scripts" instead of +##? your "$DOTFILES_PATH/scripts" folder +##? --author Provide who is the author, if none the default git author will +##? be used +##? --email Provide author email, if none the default git author email will +##? be used +##? +##? Author: +##? Gabriel Trabanco +##? +docs::parse "$@" + +SCRIPT_NAME="lazy script create" +SCRIPT_VERSION="1.1.0" + +# Print name and version +if ${version:-}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + +case "${1:-}" in + "bin") + new_script_path="$DOTFILES_PATH/bin/${name:-}" + if [[ -z "${name:-}" ]]; then + output::error "🚨 No name for the script provided" + exit 1 + fi + + if [[ ! -d "$DOTFILES_PATH" ]]; then + output::error "❌ Your dotfiles could not be found" + exit 1 + fi + mkdir -p "$DOTFILES_PATH/bin" + + if [[ -f "$new_script_path" ]] &&\ + ! output::yesno " ⚠️ Script \`${name:-}\` exists in \`\$DOTFILES_PATH/bin\`! Do you want to overwrite it (this will delete current file)" + then + output::error "User aborted" + exit 1 + fi + + rm -f "$new_script_path" + touch "$new_script_path" + printf "#!/usr/bin/env bash\n\n" >> "$new_script_path" + printf "set -euo pipefail\n" >> "$new_script_path" + #shellcheck disable=SC2016 + printf '. "$DOTLY_PATH/scripts/core/_main.sh"\n' >> "$new_script_path" + + chmod u+x "$new_script_path" + + output::empty_line + output::solution "The script \`$name\` where successfully created." + output::write "" + output::write "You can access the scipt with your favorite editor by executing:" + output::write "\$EDITOR \"\$DOTFILES_PATH/bin/${name:-}\"" + output::empty_line + + ;; + *) + if [[ -z "${context:-}" ]] || [[ -z "${script_name:-}" ]]; then + output::error "You should provide a context and script name" + output::write "Use: $0 -h" + exit 1 + fi + + SCRIPT_TEMPLATE_PATH="$(dot::get_script_path)/src/templates/script" + + # Path variables + if ${core:-}; then + output::empty_line + output::write "\033[0;31m\033[1m ALERT!!!!!" + output::write "Create this script in your DOTLY scripts folder could be very dangerous." + output::write "This is an option thinked only for DOTLY devs.\033[0m" + output::empty_line + output::yesno "Are you sure you still want to coninue" "N" || { output::answer "User aborted" && exit 1; } + + script_path="$DOTLY_PATH/scripts/$context" + else + script_path="$DOTFILES_PATH/scripts/$context" + fi + + script_filepath="$script_path/$script_name" + + # Which template to use + if ${sample:-}; then + SCRIPT_TEMPLATE_PATH="${SCRIPT_TEMPLATE_PATH}-more" + fi + + # Create the script context (folder) + mkdir -p "$script_path" + + if [[ -d "$DOTFILES_PATH/scripts/$context" ]]; then + output::solution "$script_path were created or exists previously" + fi + + # If script exits ask user to overwrite + if [[ -f $script_filepath ]]; then + output::yesno "The script exists, do you want to delete it and create a empty script" "N" ||\ + { + output::error "The script name \"$script_name\" exists in context \"$context\" and user refuse to recreate the script" &&\ + output::write "Provide a different name for the script or context." &&\ + exit 1 + } + fi + + # Variables for the script + author="${author:-$(git config --global --get user.name)}" + email="${email:-$(git config --global --get user.email)}" + description="${script_description[*]:-}" + + # Description can not be empty + [[ -z "$description" ]] && output::question "Description can not be empty, describe your script" "description" + + cp "${SCRIPT_TEMPLATE_PATH}" "$script_filepath" + templating::replace "$script_filepath" --script-name="$script_name" --script-context="$context" --script-author="$author" --script-author-email="$email" --script-description="$description" > /dev/null + chmod u+x "$script_filepath" + + output::empty_line + output::solution "The script '$script_name' where successfully created." + output::write "" + output::write "You can access the scipt with your favorite editor by executing:" + output::write "\$EDITOR \"\$DOTFILES_PATH/scripts/$context/$script_name\"" + output::empty_line + ;; +esac \ No newline at end of file diff --git a/scripts/script/install-remote b/scripts/script/install-remote new file mode 100755 index 000000000..60c5d22a8 --- /dev/null +++ b/scripts/script/install-remote @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +set -euo pipefail + +[[ -z "$DOTLY_PATH" ]] && exit 1 + +#shellcheck source=/dev/null +. "$DOTLY_PATH/scripts/core/_main.sh" + +##? Install remote context or script in your $DOTFILES_PATH from url. It +##? must be the url to the raw file. +##? If you fill the last param , the remote file name will be +##? omited and renamed in your $DOTFILES_PATH/scripts/ folder +##? +##? Usage: +##? install-remote [ -h | --help ] +##? install-remote [ -v | --version ] +##? install-remote [] +##? +##? Options: +##? -h --help Show this help +##? -v --version Show the program version +##? +##? +##? Author: +##? Gabriel Trabanco Llano +docs::parse "$@" + +SCRIPT_NAME="lazy dotly install-remote" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if ${version:-}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + +# Save current working directory to return user there +STARTING_DIRECTORY="$(pwd)" +CURL_BIN="$(which curl)" + +# Script name if provided if not with downloaded name +[[ -n $script_name ]] && script_name_args="-o $script_name" || script_name_args=${script_name:-"-O"} + +# Download command +download_command="$CURL_BIN -k -L -f -q $script_name_args" + +# Scripts context directory +{ [[ -z "${context:-}" ]] || [[ -z "${script_raw_url:-}" ]]; } && output::error "Error no context or script name." && exit 1 +dotfiles_context="$DOTFILES_PATH/scripts/${context:-}" + +# Create context directory and move to it +mkdir -p "$dotfiles_context" +cd "$dotfiles_context" || exit 1 + +# Download the script +output::write "Downloading the script ⚡️" +eval "$download_command $script_raw_url" +if [ ! $? ] ; then + if [ -z "$(ls -A "$dotfiles_context" 2>/dev/null)" ]; then + output::error "The context '$dotfiles_context' is empty. File could not be downloaded." + exit 1 + fi +fi + +# Getting the name +script_name="$(ls -Art | tail -n 1)" + +# Applying execution rights +chmod u+x "$dotfiles_context/$script_name" + +# How to use it :) +echo +output::solution "The script was successfully added 😀" +output::write "You can execute the script now with:" +output::write "dot $context $script_name" + +cd "$STARTING_DIRECTORY" || exit 1 diff --git a/scripts/script/src/templates/script b/scripts/script/src/templates/script new file mode 100644 index 000000000..06c86b58b --- /dev/null +++ b/scripts/script/src/templates/script @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +set -euo pipefail + +[[ -z "$DOTLY_PATH" ]] && exit 1 + +#shellcheck source=/dev/null +. "$DOTLY_PATH/scripts/core/_main.sh" + +##? XXX_SCRIPT_DESCRIPTION_XXX +##? +##? +##? Usage: +##? XXX_SCRIPT_NAME_XXX [-h | --help] +##? XXX_SCRIPT_NAME_XXX [-v | --version] +##? +##? Options: +##? -h --help Show this help +##? -v --version Show the program version +##? +##? Author: +##? XXX_SCRIPT_AUTHOR_XXX +docs::parse "$@" + +SCRIPT_NAME="sloth XXX_SCRIPT_CONTEXT_XXX XXX_SCRIPT_NAME_XXX" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if ${version:-}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + +# Here begin your script + +case ${1-} in +# Any subcommand should be here +*) + exit 1 + ;; +esac diff --git a/scripts/script/src/templates/script-more b/scripts/script/src/templates/script-more new file mode 100644 index 000000000..63f732676 --- /dev/null +++ b/scripts/script/src/templates/script-more @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +set -euo pipefail + +[[ -z "$DOTLY_PATH" ]] && exit 1 + +#shellcheck source=/dev/null +. "$DOTLY_PATH/scripts/core/_main.sh" + +##? XXX_SCRIPT_DESCRIPTION_XXX +##? +##? +##? Usage: +##? XXX_SCRIPT_NAME_XXX [-h | --help] +##? XXX_SCRIPT_NAME_XXX [-v | --version] +##? XXX_SCRIPT_NAME_XXX -c | --custom-option +##? XXX_SCRIPT_NAME_XXX subcommand [...] +##? +##? Options: +##? -h --help Show this help +##? -v --version Show the program version +##? -c --custom-option With two or more spaces you can add some help +##? subcomand [...] Also provide a help with subcommand +##? This text will be printed as well when you call -h or --help option +##? +##? Author: +##? XXX_SCRIPT_AUTHOR_XXX + +# Options part its important because assign short and long version of the params +docs::parse "$@" + +SCRIPT_NAME="sloth XXX_SCRIPT_CONTEXT_XXX XXX_SCRIPT_NAME_XXX" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if ${version:-}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + +# Here begin your script + +case $1 in +"subcommand") + echo "This case is optional and only useful if you want to implement subcommands" + echo "The example repeteable variable value is \"${example_variable[*]:-No value provided}\"" + echo "The optional variable value is \"${example_optional_variable:-No value provided}\"" + ;; +*) + exit 1 + ;; +esac