diff --git a/.editorconfig b/.editorconfig index 361cd4145..d308d9465 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,6 +2,14 @@ indent_style = space indent_size = 2 +[migration/*] +indent_style = space +indent_size = 2 + +[*.sh] +indent_style = space +indent_size = 2 + [.zshrc] indent_style = space indent_size = 2 diff --git a/bin/dotly b/bin/dotly index e35a6e3d2..bd53f68f4 100755 --- a/bin/dotly +++ b/bin/dotly @@ -6,6 +6,7 @@ source "$DOTLY_PATH/scripts/core/_main.sh" ##? Usage: ##? dotly self-update +##? dotly autoupdate docs::parse "$@" case $1 in diff --git a/bin/open b/bin/open new file mode 100755 index 000000000..8bf814d83 --- /dev/null +++ b/bin/open @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +# Open command if open exists in system +SCRIPT_PATH="$(cd -- "$(dirname "$0")" && pwd -P)" +FULL_SCRIPT_PATH="$SCRIPT_PATH/$(basename "$0")" + +mapfile -c 1 -t < <( which -a open | grep -v "$FULL_SCRIPT_PATH" ) +OPEN_BIN="" +if [[ "${#MAPFILE[@]}" -gt 0 ]]; then + OPEN_BIN="${MAPFILE[0]}" +fi + +os=$(uname | tr '[:upper:]' '[:lower:]') + +case "$os" in + *darwin*) + if [[ -n "$OPEN_BIN" && -x "$OPEN_BIN" ]]; then + "$OPEN_BIN" "$@" + else + echo -e "\033[0;31mNot possible to use \`open\` command in this system\033[0m" + exit 4 + fi + ;; + *linux*) + if grep -q Microsoft /proc/version; then + cmd.exe /C start "$@" + elif [[ -n "$OPEN_BIN" && -x "$OPEN_BIN" ]]; then + "$OPEN_BIN" "$@" + elif ! which xdg-open | grep 'not found'; then + xdg-open "$@" + elif ! which gnome-open | grep 'not found'; then + gnome-open "$@" + else + echo -e "\033[0;31mNot possible to use \`open\` command in this system\033[0m" + exit 4 + fi + ;; + *cygwin*) + if command -v realpath &>/dev/null; then + cygstart "$@" + else + echo -e "\033[0;31mNot possible to use \`open\` command in this system\033[0m" + exit 4 + fi + ;; + *) + echo -e "\033[0;31m\`open\` command or any other known alternative does not exists in this system\033[0m" + exit 1 + ;; +esac diff --git a/dotfiles_template/.gitignore b/dotfiles_template/.gitignore new file mode 100644 index 000000000..49a7065f6 --- /dev/null +++ b/dotfiles_template/.gitignore @@ -0,0 +1,7 @@ +# START OF DOTLY GITIGNORE +.dotly_force_current_version +.dotly_update_available +.dotly_update_available_is_major +.dotly_updated +.cached_github_api_calls +# END OF DOTLY GITIGNORE diff --git a/dotfiles_template/shell/bash/.bash_profile b/dotfiles_template/shell/bash/.bash_profile index 86795d706..3ee6b293e 100644 --- a/dotfiles_template/shell/bash/.bash_profile +++ b/dotfiles_template/shell/bash/.bash_profile @@ -1 +1 @@ -source ~/.bashrc +. "$HOME/.bashrc" diff --git a/dotfiles_template/shell/bash/.bashrc b/dotfiles_template/shell/bash/.bashrc index 8bc9a70a2..a17ea8d26 100644 --- a/dotfiles_template/shell/bash/.bashrc +++ b/dotfiles_template/shell/bash/.bashrc @@ -2,42 +2,9 @@ export DOTFILES_PATH="XXX_DOTFILES_PATH_XXX" export DOTLY_PATH="$DOTFILES_PATH/modules/dotly" export DOTLY_THEME="codely" -if [[ "$(ps -p $$ -ocomm=)" =~ (bash$) ]]; then - __right_prompt() { - RIGHT_PROMPT="" - [[ -n $RPS1 ]] && RIGHT_PROMPT=$RPS1 || RIGHT_PROMPT=$RPROMPT - if [[ -n $RIGHT_PROMPT ]]; then - n=$(($COLUMNS - ${#RIGHT_PROMPT})) - printf "%${n}s$RIGHT_PROMPT\\r" - fi - } - export PROMPT_COMMAND="__right_prompt" -fi - -source "$DOTFILES_PATH/shell/init.sh" - -PATH=$( - IFS=":" - echo "${path[*]}" -) -export PATH - -themes_paths=( - "$DOTFILES_PATH/shell/bash/themes" - "$DOTLY_PATH/shell/bash/themes" -) - -for THEME_PATH in ${themes_paths[@]}; do - THEME_PATH="${THEME_PATH}/$DOTLY_THEME.sh" - [ -f "$THEME_PATH" ] && source "$THEME_PATH" && break -done - -for bash_file in "$DOTLY_PATH"/shell/bash/completions/_*; do - source "$bash_file" -done - -if [ -n "$(ls -A "$DOTFILES_PATH/shell/bash/completions/")" ]; then - for bash_file in "$DOTFILES_PATH"/shell/bash/completions/_*; do - source "$bash_file" - done +if [[ -f "$DOTLY_PATH/shell/init-dotly.sh" ]] +then + . "$DOTLY_PATH/shell/init-dotly.sh" +else + echo "\033[0;31m\033[1mDOTLY Loader could not be found, check \$DOTFILES_PATH variable\033[0m" fi diff --git a/dotfiles_template/shell/exports.sh b/dotfiles_template/shell/exports.sh index cd5e49d1d..51e1dbefc 100644 --- a/dotfiles_template/shell/exports.sh +++ b/dotfiles_template/shell/exports.sh @@ -1,3 +1,17 @@ +# ------------------------------------------------------------------------------ +# GENERAL INFORMATION ABOUT THIS FILE +# The variables here are loaded previously PATH is defined. Use full path if you +# need to do something like JAVA_HOME here or consider to add a init-script +# ------------------------------------------------------------------------------ + + +# ------------------------------------------------------------------------------ +# Dotly config +# ------------------------------------------------------------------------------ +export DOTLY_AUTO_UPDATE_PERIOD_IN_DAYS=7 +export DOTLY_AUTO_UPDATE_MODE="auto" # silent, auto, info, prompt +export DOTLY_UPDATE_VERSION="stable" # latest, stable, minor + # ------------------------------------------------------------------------------ # Codely theme config # ------------------------------------------------------------------------------ @@ -8,9 +22,10 @@ export CODELY_THEME_PROMPT_IN_NEW_LINE=false # ------------------------------------------------------------------------------ # Languages # ------------------------------------------------------------------------------ -export JAVA_HOME='/Library/Java/JavaVirtualMachines/amazon-corretto-15.jdk/Contents/Home' -export GEM_HOME="$HOME/.gem" -export GOPATH="$HOME/.go" +JAVA_HOME="$(/usr/libexec/java_home 2>&1 /dev/null)" +GEM_HOME="$HOME/.gem" +GOPATH="$HOME/.go" +export JAVA_HOME GEM_HOME GOPATH # ------------------------------------------------------------------------------ # Apps @@ -22,25 +37,3 @@ else fi export FZF_DEFAULT_OPTS="--color=$fzf_colors --reverse" - -# ------------------------------------------------------------------------------ -# Path - The higher it is, the more priority it has -# ------------------------------------------------------------------------------ -export path=( - "$HOME/bin" - "$DOTLY_PATH/bin" - "$DOTFILES_PATH/bin" - "$JAVA_HOME/bin" - "$GEM_HOME/bin" - "$GOPATH/bin" - "$HOME/.cargo/bin" - "/usr/local/opt/ruby/bin" - "/usr/local/opt/python/libexec/bin" - "/opt/homebrew/bin" - "/usr/local/bin" - "/usr/local/sbin" - "/bin" - "/usr/bin" - "/usr/sbin" - "/sbin" -) diff --git a/dotfiles_template/shell/functions.sh b/dotfiles_template/shell/functions.sh index 0db2a5f27..8b1378917 100644 --- a/dotfiles_template/shell/functions.sh +++ b/dotfiles_template/shell/functions.sh @@ -1,19 +1 @@ -function cdd() { - cd "$(ls -d -- */ | fzf)" || echo "Invalid directory" -} -function j() { - fname=$(declare -f -F _z) - - [ -n "$fname" ] || source "$DOTLY_PATH/modules/z/z.sh" - - _z "$1" -} - -function recent_dirs() { - # This script depends on pushd. It works better with autopush enabled in ZSH - escaped_home=$(echo $HOME | sed 's/\//\\\//g') - selected=$(dirs -p | sort -u | fzf) - - cd "$(echo "$selected" | sed "s/\~/$escaped_home/")" || echo "Invalid directory" -} diff --git a/dotfiles_template/shell/init-scripts.enabled/.gitkeep b/dotfiles_template/shell/init-scripts.enabled/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dotfiles_template/shell/init-scripts/.gitkeep b/dotfiles_template/shell/init-scripts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dotfiles_template/shell/init.sh b/dotfiles_template/shell/init.sh deleted file mode 100644 index a6a0f4b39..000000000 --- a/dotfiles_template/shell/init.sh +++ /dev/null @@ -1,5 +0,0 @@ -# This is a useful file to have the same aliases/functions in bash and zsh - -source "$DOTFILES_PATH/shell/aliases.sh" -source "$DOTFILES_PATH/shell/exports.sh" -source "$DOTFILES_PATH/shell/functions.sh" diff --git a/dotfiles_template/shell/paths.sh b/dotfiles_template/shell/paths.sh new file mode 100644 index 000000000..397c7580a --- /dev/null +++ b/dotfiles_template/shell/paths.sh @@ -0,0 +1,14 @@ +# ------------------------------------------------------------------------------ +# Path - The higher it is, the more priority it has +# ------------------------------------------------------------------------------ +# JAVA_HOME, GEM_HOME, GOHOME, deno($HOME/.deno/bin), cargo are now loaded +# in init-dotly.sh +# Mandatory paths: /usr/local/{bin,sbin}, /bin, /usr/{bin,sbin} /sbin +# are also loaded in init-dotly +# paths defined here are loaded first +# +export path=( + "$HOME/bin" + "$DOTLY_PATH/bin" + "$DOTFILES_PATH/bin" +) diff --git a/dotfiles_template/shell/zsh/.zshrc b/dotfiles_template/shell/zsh/.zshrc index 514b62698..f53e2955d 100644 --- a/dotfiles_template/shell/zsh/.zshrc +++ b/dotfiles_template/shell/zsh/.zshrc @@ -1,26 +1,9 @@ # Uncomment for debuf with `zprof` # zmodload zsh/zprof -# ZSH Ops -setopt HIST_IGNORE_ALL_DUPS -setopt HIST_FCNTL_LOCK -setopt +o nomatch -# setopt autopushd - -# Start zim -source "$ZIM_HOME/init.zsh" - -# Async mode for autocompletion -ZSH_AUTOSUGGEST_USE_ASYNC=true -ZSH_HIGHLIGHT_MAXLENGTH=300 - -source "$DOTFILES_PATH/shell/init.sh" - -fpath=("$DOTFILES_PATH/shell/zsh/themes" "$DOTFILES_PATH/shell/zsh/autocompletions" "$DOTLY_PATH/shell/zsh/themes" "$DOTLY_PATH/shell/zsh/completions" $fpath) - -autoload -Uz promptinit && promptinit -prompt ${DOTLY_THEME:-codely} - -source "$DOTLY_PATH/shell/zsh/bindings/dot.zsh" -source "$DOTLY_PATH/shell/zsh/bindings/reverse_search.zsh" -source "$DOTFILES_PATH/shell/zsh/key-bindings.zsh" +if [[ -f "$DOTLY_PATH/shell/init-dotly.sh" ]] +then + . "$DOTLY_PATH/shell/init-dotly.sh" +else + echo "\033[0;31m\033[1mDOTLY Loader could not be found, check \$DOTFILES_PATH variable\033[0m" +fi diff --git a/dotfiles_template/symlinks/conf.yaml b/dotfiles_template/symlinks/conf.yaml index 3fa33d884..159364545 100644 --- a/dotfiles_template/symlinks/conf.yaml +++ b/dotfiles_template/symlinks/conf.yaml @@ -8,6 +8,9 @@ - create: - $DOTFILES_PATH/shell/bash/completions - $DOTFILES_PATH/shell/bash/themes + - $DOTLY_PATH/shell/init-scripts + - $DOTFILES_PATH/shell/init-scripts + - $DOTFILES_PATH/shell/init-scripts.enabled - link: ~/.bash_profile: shell/bash/.bash_profile diff --git a/migration/v0.1 b/migration/v0.1 new file mode 100755 index 000000000..0bcd15f41 --- /dev/null +++ b/migration/v0.1 @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "No migration actions needed for v0.1" diff --git a/migration/v2.0.0 b/migration/v2.0.0 new file mode 100755 index 000000000..c1f1965e6 --- /dev/null +++ b/migration/v2.0.0 @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +DOTFILES_TEMPLATE_PATH="$DOTLY_PATH/dotfiles_template" + +files_to_backup=( + "$DOTFILES_PATH/shell/bash/.bashrc" + "$DOTFILES_PATH/shell/zsh/.zshrc" + "$DOTFILES_PATH/shell/paths.sh" +) + +for item in "${files_to_backup[@]}"; do + bk_path="" + + if [ -f "$item" ] && [ ! -L "$HOME/bin/script" ]; then + bk_path="$(files::backup_if_file_exists "$item")" + [[ -z "$bk_path" ]] && continue + output::write "File '$item' exits and was moved as backup to:" + output::write " $bk_path" + output::empty_line + fi +done + +# Always copy using interactive mode: cp -i +output::answer "Copying .bashrc and .zshrc files" +rm -f "$DOTFILES_PATH/shell/bash/.bashrc" +cp -i "$DOTFILES_TEMPLATE_PATH/shell/bash/.bashrc" "$DOTFILES_PATH/shell/bash/" +cp -i "$DOTFILES_TEMPLATE_PATH/shell/zsh/.zshrc" "$DOTFILES_PATH/shell/zsh/" +output::solution ".bashrc and .zshrc copied" +output::empty_line + +# Edit .bashrc file templating +templating::replace "$DOTFILES_PATH/shell/bash/.bashrc" --dotfiles-path="${DOTFILES_PATH//$HOME/\$HOME}" + +# Create new paths.sh file +output::answer "Creating paths.sh file with your current values" +if [ -n "${path[*]:-}" ]; then + { + printf "path=(" + printf " \"%s\"\n" "${path[@]}" | sort | uniq + printf ")\n" + printf "export path\n" + } >| "$DOTFILES_PATH/shell/paths.sh" +else + # Copy paths from dotfiles_template + cp -i "$DOTFILES_TEMPLATE_PATH/shell/paths.sh" "$DOTFILES_PATH/shell/" +fi +output::solution "paths.sh file created" +output::answer "Remember to delete your current paths if you have it in your exports.sh" +output::empty_line + +# Gitignore +if [ ! -f "$DOTFILES_PATH/.gitignore" ]; then + cp -i "$DOTFILES_TEMPLATE_PATH/.gitignore" "$DOTFILES_PATH/" +else + cat "$DOTFILES_TEMPLATE_PATH/.gitignore" >> "$DOTFILES_PATH/.gitignore" +fi 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/_main.sh b/scripts/core/_main.sh index 3434ad854..0a87eb3b0 100755 --- a/scripts/core/_main.sh +++ b/scripts/core/_main.sh @@ -1,6 +1,7 @@ if ! ${DOT_MAIN_SOURCED:-false}; then - for file in $DOTLY_PATH/scripts/core/{args,collections,documentation,dot,git,log,platform,output,script,str}.sh; do - source "$file" + for file in "$DOTLY_PATH"/scripts/core/{args,array,async,collections,documentation,dot,files,git,log,platform,output,script,str}.sh; do + #shellcheck source=/dev/null + . "$file" || exit 5 done unset file diff --git a/scripts/core/array.sh b/scripts/core/array.sh new file mode 100644 index 000000000..ef2a0494f --- /dev/null +++ b/scripts/core/array.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# Usage: array::* "${arr1[@]}" "${arr2[@]}" +array::union() { echo "${@}" | tr ' ' '\n' | sort | uniq; } +array::disjunction() { echo "${@}" | tr ' ' '\n' | sort | uniq -u; } +array::difference() { echo "${@}" | tr ' ' '\n' | sort | uniq -d; } +array::exists_value() { + local value array_value + value="${1:-}"; shift + + for array_value in "$@"; do + [[ "$array_value" == "$value" ]] && return 0 + done + + return 1 +} + diff --git a/scripts/core/async.sh b/scripts/core/async.sh new file mode 100644 index 000000000..18e71e3ff --- /dev/null +++ b/scripts/core/async.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +# This was literally copied from: https://github.com/zombieleet/async-bash +# check the README.md for information on how to use this script + +# set +eu + +declare -a JOB_IDS +declare -i JOBS=1; + +killJob() { + local jobToKill signal __al__signals isSig + + jobToKill="$1" + signal="$2" + signal=${signal^^} + + [[ ! $jobToKill =~ ^[[:digit:]]+$ ]] && { + printf "%s\n" "\"$jobToKill\" should be an integer "; + return 1; + } + + + { + [[ -z "$signal" ]] && { + signal="SIGTERM" + } + } || { + # for loop worked better than read line in this case + __al__signals=$(kill -l); + isSig=0; + for sig in ${__al__signals}; do + [[ ! $sig =~ ^[[:digit:]]+\)$ ]] && { + [[ $signal == $sig ]] && { + isSig=1; + break; + } + } + done + + (( isSig != 1 )) && { + signal="SIGTERM" + } + } + + + + for job in ${JOB_IDS[@]};do + # increment job to 1 since array index starts from 0 + read -r -d " " -a __kunk__ <<< "${JOB_IDS[$job]}" + (( __kunk__ == jobToKill )) && { + read -r -d " " -a __kunk__ <<< "${JOB_IDS[$job]}" + + kill -${signal} %${__kunk__} + + status=$? + + (( status != 0 )) && { + printf "cannot kill %s %d\n" "${JOB_IDS[$job]}" "${__kunk__}" + return 1; + } + + printf "%d killed with %s\n" "${__kunk__}" "${signal}" + + return 0; + } + done +} + +async() { + local cmdToExec resolve reject _c __temp status + set +e # Avoid crash if any function fail + + cmdToExec="$1" + resolve="$2" + reject="$3" + + [[ -z "$cmdToExec" ]] || [[ -z "$reject" ]] || [[ -z "$resolve" ]] && { + printf "%s\n" "Insufficient number of arguments"; + return 1; + } + + + + __temp=( "$cmdToExec" "$reject" "$resolve" ) + + + for _c in "${__temp[@]}"; do + read -r -d " " comm <<<"${_c}" + type "${comm}" &>/dev/null + + status=$? + + (( status != 0 )) && { + printf "\"%s\" is neither a function nor a recognized cmd\n" "${_c}"; + unset _c + return 1; + } + done + + unset __temp _c + + { + __result=$($cmdToExec) + status=$? + + if (( status == 0 )) + then + $resolve "${__result}" + else + $reject "${status}" + fi + unset __result + } & + + JOB_IDS+=( "${JOBS} ${cmd}" ) + + read -r -d " " -a __kunk__ <<< "${JOB_IDS[$(( ${#JOB_IDS[@]} - 1))]}" + + #echo ${__kunk__} + + + : $(( JOBS++ )) + +} \ No newline at end of file diff --git a/scripts/core/dot.sh b/scripts/core/dot.sh index 0c1c48696..9266f1dd9 100644 --- a/scripts/core/dot.sh +++ b/scripts/core/dot.sh @@ -1,3 +1,7 @@ +#!/usr/bin/env bash + +[[ -z "${SCRIPT_LOADED_LIBS[*]:-}" ]] && SCRIPT_LOADED_LIBS=() + dot::list_contexts() { dotly_contexts=$(ls "$DOTLY_PATH/scripts") dotfiles_contexts=$(ls "$DOTFILES_PATH/scripts") @@ -30,3 +34,65 @@ dot::list_scripts_path() { printf "%s\n%s" "$dotly_contexts" "$dotfiles_contexts" | sort -u } + +dot::get_script_path() { + echo "$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +} + +dot::get_full_script_path() { + echo "$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )/$(basename "$0")" +} + +# Old name: dot::get_script_src_path +# If you find any old name replace by +# new one: +dot::load_library() { + local lib lib_path lib_paths lib_full_path + lib="${1:-}" + lib_full_path="" + + if [[ -n "${lib:-}" ]]; then + lib_paths=() + if [[ -n "${2:-}" ]]; then + lib_paths+=("$DOTFILES_PATH/scripts/$2/src" "$DOTLY_PATH/scripts/$2/src" "$2") + else + lib_paths+=( + "$(dot::get_script_path)/src" + ) + fi + + lib_paths+=( + "$DOTLY_PATH/scripts/core" + "." + ) + + for lib_path in "${lib_paths[@]}"; do + [[ -f "$lib_path/$lib" ]] &&\ + lib_full_path="$lib_path/$lib" &&\ + break + + [[ -f "$lib_path/$lib.sh" ]] &&\ + lib_full_path="$lib_path/$lib.sh" &&\ + break + done + + # Library loading + if [[ -n "${lib_full_path:-}" ]] && [[ -r "${lib_full_path:-}" ]]; then + if ! array::exists_value "${lib_full_path:-}" "${SCRIPT_LOADED_LIBS[@]:-}"; then + #shellcheck disable=SC1090 + . "$lib_full_path" + SCRIPT_LOADED_LIBS+=( + "$lib_full_path" + ) + fi + + return 0 + else + output::error "🚨 Library loading error with: \"${lib_full_path:-No lib path found}\"" + exit 1 + fi + fi + + # No arguments + return 1 +} diff --git a/scripts/core/files.sh b/scripts/core/files.sh new file mode 100644 index 000000000..fdb36f8bc --- /dev/null +++ b/scripts/core/files.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +files::check_if_path_is_older() { + local path_to_check number_of period + path_to_check="$1" + number_of="${2:-0}" + period="${3:-days}" + [[ -e "$path_to_check" ]] && [[ $(date -r "$path_to_check" +%s) -lt $(date -d "now - $number_of $period" +%s) ]] +} + +files::backup_if_file_exists() { + local file_path bk_suffix bk_file_path + file_path="$(eval realpath -q -m "${1:-}")" + bk_suffix="${2:-$(date +%s)}" + bk_file_path="$file_path.${bk_suffix}" + + if [[ -n "$file_path" ]] &&\ + { [[ -f "$file_path" ]] || [[ -d "$file_path" ]]; } + then + eval mv "$file_path" "$bk_file_path" && echo "$bk_file_path" && return 1 + fi + + return 0 +} diff --git a/scripts/core/git.sh b/scripts/core/git.sh index f2f419b38..a7cba7c55 100755 --- a/scripts/core/git.sh +++ b/scripts/core/git.sh @@ -2,6 +2,180 @@ git::is_in_repo() { git rev-parse HEAD >/dev/null 2>&1 } +# shellcheck disable=SC2120 git::current_branch() { - git branch + git branch --show-current "$@" +} + +git::get_local_HEAD_hash() { + local branch + branch="${1:-HEAD}" + git::is_in_repo && git rev-parse "$branch" +} + +git::get_remote_branch_HEAD_hash() { + local remote branch + remote="${1:-origin}" + branch="${2:-master}" + git::is_in_repo && git ls-remote --heads "$remote" "$branch" +} + +git::local_current_branch_commit_exists_remote() { + local remote branch local_commit + remote="${1:-origin}" + branch="${2:-master}" + local_commit="$(git::get_local_HEAD_hash "$branch")" + + [ -n "$local_commit" ] &&\ + git::is_in_repo && + git ls-remote --symref "$remote" | tail -n +2 | grep -q "$local_commit" +} + +git::remote_branch_by_hash() { + [[ -n "${1:-}" ]] && git ls-remote --symref origin | tail -n +2 | grep "${1:-}" | awk '{print $2}' | grep "refs/heads" | sed 's#refs/heads/##' +} + +# shellcheck disable=SC2120 +git::current_commit_hash() { + git rev-parse HEAD "$@" +} + +# shellcheck disable=SC2120 +git::get_commit_tag() { + local commit + if git::is_in_repo; then + commit="${1:-}" + { [[ -n "$commit" ]] && shift; } || commit="$(git::current_commit_hash)" + + git show-ref --tags "$@" | grep "$commit" | awk '{print $2}' | sed 's#refs/tags/##' + fi +} + +git::get_current_latest_tag() { + git::get_all_local_tags | head -n1 +} + +# shellcheck disable=SC2120 +git::get_all_local_tags() { + #git tag -l --sort="-version:refname" "$@" + git show-ref --tags | sort --reverse | awk '{print $2}' | sed 's#refs/tags/##' +} + +git::get_all_remote_tags() { + local repository + repository="${1:-origin}" + git ls-remote --tags --sort "-version:refname" "$repository" "$@" +} + +git::get_all_remote_tags_version_only() { + local repository + repository="${1:-}" + { [[ -n "$repository" ]] && shift; } || repository="origin" + git::get_all_remote_tags "$repository" "${@:-*.*.*}" 2>/dev/null | sed 's/.*\///; s/\^{}//' | uniq +} + +git::check_local_tag_exists() { + local repository tag_version + repository="${1:-}" + tag_version="${2:-}" + + { [[ -z "$repository" ]] || [[ -z "$tag_version" ]]; } && return 1 + + git::get_all_local_tags | grep -q "$tag_version" +} + +git::check_remote_tag_exists() { + local repository tag_version + repository="${1:-}" + tag_version="${2:-}" + + { [[ -z "$repository" ]] || [[ -z "$tag_version" ]]; } && return 1 + + [[ -n "$(git::get_all_remote_tags_version_only "$repository" "$tag_version")" ]] +} + +git::get_submodule_property() { + local gitmodules_path submodule_directory property + + if [ $# -gt 2 ]; then + gitmodules_path="$1"; shift + submodule_directory="$1" + fi + + gitmodules_path="${gitmodules_path:-$DOTFILES_PATH/.gitmodules}" + submodule_directory="${submodule_directory:-modules/${1:-}}" + property="${2:-}" + + [[ -f "$gitmodules_path" ]] && [[ -n "$submodule_directory" ]] && [[ -n "$property" ]] && git config -f "$gitmodules_path" submodule."$submodule_directory"."$property" +} + +git::check_file_exists_in_previous_commit() { + [[ -n "${1:-}" ]] && ! git rev-parse @~:"${1:-}" > /dev/null 2>&1 +} + +git::get_file_last_commit_timestamp() { + [[ -n "${1:-}" ]] && git rev-list --all --date-order --timestamp -1 "${1:-}" 2>/dev/null | awk '{print $1}' +} + +git::get_commit_timestamp() { + [[ -n "${1:-}" ]] && git rev-list --all --date-order --timestamp | grep "${1:-}" | awk '{print $1}' +} + +git::check_file_is_modified_after_commit() { + local file_path file_commit_date commit_to_check commit_to_check_date + file_path="${1:-}" + commit_to_check="${2:-}" + { [[ -z "$file_path" ]] || [[ -z "${commit_to_check:-}" ]] || [[ ! -e "$file_path" ]]; } && return 1 + + file_commit_date="$(git::get_file_last_commit_timestamp "${file_path:-}" 2>/dev/null)" + + [[ -z "$file_commit_date" ]] && return 0 # File path did not exists previously then + # it is more recent than any commit 😅 + + commit_to_check_date="$(git::get_commit_timestamp "$commit_to_check")" + [[ "$file_commit_date" -gt "$commit_to_check_date" ]] +} + +# PR Note to reviewer: This function could be replaced by the next one, the +# function name that use update is this one +# git::check_local_repo_is_updated() { +# local repo_path remote return_code current_dir remote_head_hash remote_head_branch local_head_remote_branch_hash +# remote="${1:-origin}" +# repo_path="${2:-.}" + +# current_dir="$(pwd)" +# return_code=1 + +# cd "$repo_path" || return 1 + +# if git::is_in_repo; then +# remote_head_hash="$(git ls-remote --symref "$remote" | tail -n +2 | head -n 1 | awk '{print $1}')" # remote: HEAD +# remote_head_branch="$(git::remote_branch_by_hash "$remote_head_hash")" +# local_head_remote_branch_hash="$(git rev-parse "$remote_head_branch")" + +# git::local_current_branch_commit_exists_remote "$remote" "$local_head_remote_branch_hash" && [ "$remote_head_hash" == "$local_head_remote_branch_hash" ] +# return_code=$? +# fi + +# cd "$current_dir" || return $return_code + +# return $return_code +# } + +git::check_local_repo_is_updated() { + git::is_in_repo && ! git status -sb 2>/dev/null | grep -q 'behind' +} + +git::dotly_repository_exec() { + local return_code + return_code=0 + cd "$DOTLY_PATH" || return 1 + + if git::is_in_repo; then + eval "$@" + else + return_code=1 + fi + + return "$return_code" } diff --git a/scripts/core/output.sh b/scripts/core/output.sh index a19d1b1a8..3ad5fea58 100644 --- a/scripts/core/output.sh +++ b/scripts/core/output.sh @@ -12,25 +12,54 @@ _output::parse_code() { } output::write() { - local -r text="${1:-}" - + local with_code_parsed + local -r text="${*:-}" with_code_parsed=$(_output::parse_code "$text") - echo -e "$with_code_parsed" } -output::answer() { output::write " > $1"; } -output::error() { output::answer "${red}$1${normal}"; } -output::solution() { output::answer "${green}$1${normal}"; } +output::answer() { output::write " > ${*:-}"; } +output::error() { output::answer "${red}${*:-}${normal}"; } +output::solution() { output::answer "${green}${*:-}${normal}"; } output::question() { - with_code_parsed=$(_output::parse_code "$1") + [[ $# -ne 2 ]] && return 1 if [ "${DOTLY_ENV:-PROD}" == "CI" ] || [ "${DOTLY_INSTALLER:-false}" = true ]; then - answer="y" + echo "y" + elif declare -F platform::is_macos &>/dev/null && platform::is_macos; then + echo -n " > 🤔 $1: "; + read -r "$2"; + else + read -rp "🤔 $1: " "$2" + fi +} +output::question_default() { + local question default_value var_name + + [[ $# -ne 3 ]] && return 1 + + question="${1:-}" + default_value="${2:-}" + var_name="${3:-}" + + output::question "$question? [$default_value]" "$var_name" + eval "$var_name=\"\${$var_name:-$default_value}\"" +} +output::yesno() { + local question default PROMPT_REPLY values + + [[ $# -eq 0 ]] && return 1 + + question="$1" + default="${2:-Y}" + + if [[ "$default" =~ ^[Yy] ]]; then + values="Y/n" else - read -rp "🤔 $with_code_parsed: " "answer" + values="y/N" fi - echo "$answer" + output::question "$question? [$values]" "PROMPT_REPLY" + [[ "${PROMPT_REPLY:-$default}" =~ ^[Yy] ]] } output::answer_is_yes() { @@ -45,18 +74,18 @@ output::empty_line() { echo ''; } output::header() { output::empty_line - output::write "${bold_blue}---- $1 ----${normal}" + output::write "${bold_blue}---- ${*:-} ----${normal}" } -output::h1_without_margin() { output::write "${bold_blue}# $1${normal}"; } +output::h1_without_margin() { output::write "${bold_blue}# ${*:-}${normal}"; } output::h1() { output::empty_line - output::h1_without_margin "$1" + output::h1_without_margin "${*:-}" } output::h2() { output::empty_line - output::write "${bold_blue}## $1${normal}" + output::write "${bold_blue}## ${*:-}${normal}" } output::h3() { output::empty_line - output::write "${bold_blue}### $1${normal}" + output::write "${bold_blue}### ${*:-}${normal}" } diff --git a/scripts/core/platform.sh b/scripts/core/platform.sh index 019244693..352d9acc8 100644 --- a/scripts/core/platform.sh +++ b/scripts/core/platform.sh @@ -21,3 +21,74 @@ platform::is_wsl() { platform::wsl_home_path() { wslpath "$(wslvar USERPROFILE 2>/dev/null)" } + +platform::normalize_ver() { + local version + version="${1//./ }" + echo "${version//v/}" +} + +platform::compare_ver() { + [[ $1 -lt $2 ]] && echo -1 && return + [[ $1 -gt $2 ]] && echo 1 && return + + echo 0 +} + +# It does not support beta, rc and similar suffix +platform::semver_compare() { + if [ -z "${1:-}" ] || [ -z "${2:-}" ]; then + return 1 + fi + + v1="$(platform::normalize_ver "${1:-}")" + v2="$(platform::normalize_ver "${2:-}")" + + major1="$(echo "$v1" | awk '{print $1}')" + major2="$(echo "$v2" | awk '{print $1}')" + + minor1="$(echo "$v1" | awk '{print $2}')" + minor2="$(echo "$v2" | awk '{print $2}')" + + patch1="$(echo "$v1" | awk '{print $3}')" + patch2="$(echo "$v2" | awk '{print $3}')" + + compare_major="$(platform::compare_ver "$major1" "$major2")" + compare_minor="$(platform::compare_ver "$minor1" "$minor2")" + compare_patch="$(platform::compare_ver "$patch1" "$patch2")" + + if [[ $compare_major -ne 0 ]]; then + echo "$compare_major" + elif [[ $compare_minor -ne 0 ]]; then + echo "$compare_minor" + else + echo "$compare_patch" + fi +} + +# It does not support beta, rc and similar suffix +# First argument is the current version to say if second argument is +# a version update that is no a major update +platform::semver_is_minor_or_patch_update() { + v1="$(platform::normalize_ver "$1")" + v2="$(platform::normalize_ver "$2")" + + major1="$(echo "$v1" | awk '{print $1}')" + major2="$(echo "$v2" | awk '{print $1}')" + + minor1="$(echo "$v1" | awk '{print $2}')" + minor2="$(echo "$v2" | awk '{print $2}')" + + patch1="$(echo "$v1" | awk '{print $3}')" + patch2="$(echo "$v2" | awk '{print $3}')" + + compare_major="$(platform::compare_ver "$major1" "$major2")" + compare_minor="$(platform::compare_ver "$minor1" "$minor2")" + compare_patch="$(platform::compare_ver "$patch1" "$patch2")" + + [[ $compare_major -eq 0 ]] && { # Only equals major are minor or patch updates + [[ $compare_minor -eq -1 ]] || { # If minor is over current minor is and update + [[ $compare_minor -eq 0 ]] && [[ $compare_patch -eq -1 ]] # If minor is equal and patch is greater + } + } +} diff --git a/scripts/core/str.sh b/scripts/core/str.sh index 31b52aed9..cad602725 100755 --- a/scripts/core/str.sh +++ b/scripts/core/str.sh @@ -8,3 +8,9 @@ str::split() { str::contains() { [[ $2 == *$1* ]] } + +str::to_upper() { echo "${@:-$(|] [...] +# echo "template string" | templating::replace_var [...] +# +# echo "Those are my family names: XXX_FAMILY_NAMES_XXX" |\ +# templating::replace_var family-names Miguel Manuel +# +# This will print: +# "Those are my family names: Miguel Manuel" +templating::replace_var () { + local file_path string var_name value + + [[ $# -lt 2 ]] && return 1 + + # Replacer + if [[ -t 0 ]] && [[ -f "$1" ]]; then + file_path="$1"; shift + var_name="XXX_$(str::to_upper "$1" | tr '-' '_')_XXX"; shift + value="${*:-}" + sed -i -e "s|${var_name}|${value}|g" "$file_path" + elif [[ -t 0 ]]; then + string="$1"; shift + var_name="XXX_$(str::to_upper "$1" | tr '-' '_')_XXX"; shift + value="${*:-}" + echo "${string//$var_name/$value}" + else + var_name="XXX_$(str::to_upper "$1" | tr '-' '_')_XXX"; shift + value="${*:-}" + sed -e "s|${var_name}|${value}|g" [...] +# +# echo "Those are common names in spain: XXX_NAMES_XXX" |\ +# templating::replace_var_join names ', ' Manuel Jorge David Luis Pedro +# +# This will print: +# "Those are common names in spain: Manuel, Jorge, David, Luis, Pedro" +# +templating::replace_var_join() { + local string var_name glue joined_str + + { [[ -t 0 && $# -lt 3 ]] || [[ $# -lt 2 ]]; } && return 1 + + if [[ -t 0 ]]; then + string="$1"; + var_name="$1"; shift + glue="$1"; shift + joined_str="$(str::join "$glue" "$@")" + templating::replace_var "$string" "$var_name" "$joined_str" + else + var_name="$1"; shift + glue="$1"; shift + joined_str="$(str::join "$glue" "$@")" + templating::replace_var "$var_name" "$joined_str" " --name=Gabriel --email-address=no-email@example.com +# templating::replace "XXX_NAME_XXX " --name Gabriel --email-address no-email@example.com +# templating::replace "XXX_NAME_XXX " name Gabriel email-address no-email@example.com +# echo "XXX_NAME_XXX " |\ +# templating::replace name Gabriel email-address no-email@example.com +# templating::replace /path/to/file --name=Gabriel --email-address=no-email@example.com +# templating::replace /path/to/file --name Gabriel --email-address no-email@example.com +# templating::replace /path/to/file name Gabriel email-address no-email@example.com +# +# This will print +# "Gabriel " +# +templating::replace() { + local var_name var_value output + case "${1:-}" in + --*=*|--*) + output=$( +docs::parse "$@" + +SCRIPT_NAME="dot core wrong" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if ${version:-}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + +"$DOTLY_PATH/bin/open" "https://www.youtube.com/watch?v=t3otBjVZzT0" diff --git a/scripts/dotfiles/create b/scripts/dotfiles/create index f7033018d..6ffcb3332 100755 --- a/scripts/dotfiles/create +++ b/scripts/dotfiles/create @@ -2,7 +2,8 @@ set -euo pipefail -source "$DOTLY_PATH/scripts/core/_main.sh" +. "$DOTLY_PATH/scripts/core/_main.sh" +. "$DOTLY_PATH/scripts/core/templating.sh" ##? Create the dotfiles structure ##? @@ -12,9 +13,15 @@ source "$DOTLY_PATH/scripts/core/_main.sh" docs::parse "$@" dotfiles::apply_templating() { - sed -i -e "s|XXX_DOTFILES_PATH_XXX|$DOTFILES_PATH|g" "$DOTFILES_PATH/bin/sdot" - sed -i -e "s|XXX_DOTFILES_PATH_XXX|$DOTFILES_PATH|g" "$DOTFILES_PATH/shell/bash/.bashrc" - sed -i -e "s|XXX_DOTFILES_PATH_XXX|$DOTFILES_PATH|g" "$DOTFILES_PATH/shell/zsh/.zshenv" + local tpl_files=( + "$DOTFILES_PATH/bin/sdot" + "$DOTFILES_PATH/shell/bash/.bashrc" + "$DOTFILES_PATH/shell/zsh/.zshenv" + ) + + for file in "${tpl_files[@]}"; do + templating::replace "$file" --dotfiles-path="${DOTFILES_PATH//$HOME/\$HOME}" + done } if [ ! -d "$DOTFILES_PATH/shell" ]; then diff --git a/scripts/init/disable b/scripts/init/disable new file mode 100755 index 000000000..a8ddf2362 --- /dev/null +++ b/scripts/init/disable @@ -0,0 +1,84 @@ +#!/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 "init.sh" + +##? Disable init scripts +##? +##? +##? Usage: +##? disable [-h | --help] +##? disable [-v | --version] +##? disable [] +##? +##? Options: +##? -h --help Show this help +##? -v --version Show the program version +##? +##? Author: +##? Gabriel Trabanco Llano +docs::parse "$@" + +SCRIPT_NAME="dot init disable" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if ${version:-}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + +if [[ ${DOTLY_INIT_SCRIPTS:-true} != true ]]; then + output::error "Init scripts are disabled" + exit 1 +fi + +# Get the scripts +#shellcheck disable=SC2207 +enabled_scripts=($(init::get_enabled)) + +# If there is script_name +if [[ -n "${script_name:-}" ]]; then + status=0 + if init::exists_script "$script_name"; then + init::disable "$script_name" + if ! init::status "$script_name"; then + output::solution "Disabled '$script_name'" + fi + + init::status "$script_name" && output::error "Could not be disabled." && status=1 + else + output::error "$script_name does not exists." + status=1 + fi + + exit $status +fi + +# If there is no script_name +# If there are no enabled scripts or nothing to be disabled, exit +if [[ -n "${enabled_scripts[*]:-}" ]]; then + #shellcheck disable=SC2207 + to_disable=($(printf "%s\n" "${enabled_scripts[@]}" | init::fzf "Choose one or more (Shift + Tab) scripts to disable from init terminal")) +else + output::answer "Nothing to be disabled" +fi + +[[ -z "${to_disable[*]:-}" ]] && exit 0 + +for item in "${to_disable[@]}"; do + + init::disable "$item" + + if ! init::status "$item"; then + output::solution "Init script '$item'... Disabled" + else + output::error "Init script '$item'... Could not be disabled." + fi + +done diff --git a/scripts/init/enable b/scripts/init/enable new file mode 100755 index 000000000..4e064612b --- /dev/null +++ b/scripts/init/enable @@ -0,0 +1,80 @@ +#!/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 "init.sh" + +##? Enable init scripts +##? +##? +##? Usage: +##? enable [-h | --help] +##? enable [-v | --version] +##? enable [] +##? +##? Options: +##? -h --help Show this help +##? -v --version Show the program version +##? +##? Author: +##? Gabriel Trabanco Llano +docs::parse "$@" + +SCRIPT_NAME="dot init enable" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if ${version:-}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + +if [[ ${DOTLY_INIT_SCRIPTS:-true} != true ]]; then + output::error "Init scripts are disabled" + exit 1 +fi + +# Get the scripts +init_scripts=("$(init::get_scripts)") +enabled_scripts=("$(init::get_enabled)") + +# If the user gives the script_name +if [[ -n "${script_name:-}" ]]; then + status=0 + if init::exists_script "$script_name"; then + init::enable "$script_name" + init::status "$script_name" && output::solution "Enabled" + ! init::status "$script_name" && output::error "Could not be enabled." && status=1 + else + output::error "$script_name does not exists." + status=1 + fi + exit $status +fi + +# If there is no script_name +# If there is nothing that can be enabled or not select scripts to +# be enabled, exit +not_enabled_scripts=("$(array::disjunction "${init_scripts[@]}" "${enabled_scripts[@]}")") +if [[ -n "${not_enabled_scripts[*]:-}" ]]; then + #shellcheck disable=SC2207 + to_enable=($(array::disjunction "${init_scripts[@]}" "${enabled_scripts[@]}" | init::fzf "Choose one or more (Shift + Tab) scripts to enable when init terminal")) +else + output::answer "Nothing can be enabled" +fi +[[ -z "$to_enable" ]] && exit 0 + +for item in "${to_enable[@]}"; do + init::enable "$item" + + if init::status "$item"; then + output::solution "Init script '$item'... Enabled" + else + output::error "Init script '$item' error... It could not be enabled." + exit 1 + fi +done diff --git a/scripts/init/src/init.sh b/scripts/init/src/init.sh new file mode 100644 index 000000000..54140c16a --- /dev/null +++ b/scripts/init/src/init.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash + +# PR annotation +# If you change this folders you should also change them in init-dotly.sh +DOTLY_INIT_SCRIPTS_PATH="$DOTLY_PATH/shell/init-scripts" +DOTFILES_INIT_SCRIPTS_PATH="$DOTFILES_PATH/shell/init-scripts" +ENABLED_INIT_SCRIPTS_PATH="$DOTFILES_PATH/shell/init-scripts.enabled" + +[[ ! -d "$ENABLED_INIT_SCRIPTS_PATH" ]] &&\ + output::error "The folder path to enable scripts does not exists." &&\ + output::write "If you want to disble init script add in your exports \`export DOTLY_INIT_SCRIPTS=false\` " &&\ + output::write "If you want to enable. Execute \`dot self migration v2.0.0\` first." &&\ + exit 1 + +[[ ! -d "$DOTLY_INIT_SCRIPTS_PATH" ]] &&\ + output::error "The init scripts of DOTLY does not exists." &&\ + output::write "Try with \`dot self migration v2.0.0\` first." &&\ + exit 1 + +init::exists_script() { + [[ -e "$DOTLY_INIT_SCRIPTS_PATH/$1" ]] || [[ -e "$DOTFILES_INIT_SCRIPTS_PATH" ]] +} + +init::status() { + init::exists_script "$1" && [[ -f "$ENABLED_INIT_SCRIPTS_PATH/$1" ]] +} + +init::get_scripts() { + [[ -d "$DOTLY_INIT_SCRIPTS_PATH" ]] &&\ + [[ -d "$DOTFILES_INIT_SCRIPTS_PATH" ]] &&\ + find "$DOTLY_INIT_SCRIPTS_PATH" \ + "$DOTFILES_INIT_SCRIPTS_PATH" -name "*" -type f,l -print0 -exec echo {} \; |\ + xargs -0 -I _ basename _ | sort | uniq +} + +init::get_enabled() { + [[ -d "$ENABLED_INIT_SCRIPTS_PATH" ]] &&\ + find "$ENABLED_INIT_SCRIPTS_PATH" -name "*" -type l -print0 -exec echo {} \; |\ + xargs -0 -I _ basename _ | sort | uniq +} + +init::fzf() { + local piped_values preview_cmd + piped_values="$(] +##? +##? Options: +##? -h --help Show this help +##? -v --version Show the program version +##? +##? Author: +##? Gabriel Trabanco Llano +docs::parse "$@" + +SCRIPT_NAME="dot init status" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if ${version:-}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + +if [[ ${DOTLY_INIT_SCRIPTS:-true} != true ]]; then + output::error "Init scripts are disabled" + exit 1 +fi + +# Get the scripts +#shellcheck disable=SC2207 +init_scripts=($(init::get_scripts)) + +# Check status, if user gives a script_name +if [ -n "${script_name:-}" ]; then + + if init::status "$script_name"; then + output::solution "'$script_name' is enabled" + else + output::error "'$script_name' is disabled" + fi + +else + + # If there is no script_name, gives the status of all + for item in "${init_scripts[@]}"; do + { + + init::status "$item" &&\ + output::solution "'$item'... Enabled." + + } || output::error "'$item'... Disabled." + done + +fi diff --git a/scripts/self/async-update b/scripts/self/async-update new file mode 100755 index 000000000..4387a2751 --- /dev/null +++ b/scripts/self/async-update @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail + +. "$DOTLY_PATH/scripts/core/_main.sh" +dot::load_library "dotly_autoupdate.sh" + +##? Async autoupdate dotly to avoid load all core functions in default bash +##? +##? Usage: +##? autoupdate +docs::parse "$@" + +set +eu # Needed to use async +async autoupdate::dotly_updater autoupdate::dotly_success autoupdate::dotly_reject \ No newline at end of file diff --git a/scripts/self/install b/scripts/self/install index cb3e8eec4..5608ab799 100755 --- a/scripts/self/install +++ b/scripts/self/install @@ -1,7 +1,9 @@ #!/usr/bin/env bash -source "$DOTLY_PATH/scripts/core/_main.sh" -source "$DOTLY_PATH/scripts/self/utils/install.sh" +set -euo pipefail + +. "$DOTLY_PATH/scripts/core/_main.sh" +dot::load_library "install.sh" export ZIM_HOME="$DOTLY_PATH/modules/zimfw" export PATH="$HOME/.cargo/bin:$PATH" diff --git a/scripts/self/migration b/scripts/self/migration new file mode 100755 index 000000000..50b002282 --- /dev/null +++ b/scripts/self/migration @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +set -uo pipefail +#set +e # Avoid crash if any function return false + +[[ -z "$DOTLY_PATH" ]] && exit 1 + +. "$DOTLY_PATH/scripts/core/_main.sh" +. "$DOTLY_PATH/scripts/core/templating.sh" + +##? Executes migration scripts for dotfiles. If your dotly is updated and no +##? script version is provided then try to guess the latest necessary migration +##? script. +##? +##? Usage: +##? migration [-h | --help] +##? migration [-v | --version] +##? migration [] +##? +##? Options: +##? -h --help Show this help +##? -v --version Show the program version +##? +##? Author: +##? Gabriel Trabanco Llano +docs::parse "$@" + +SCRIPT_NAME="dot self version" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if ${version:-}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + +output::header "DOTLY migration wizard" + +[[ -z "${to_version:-}" ]] && [[ -f "$DOTFILES_PATH/.dotly_updated" ]] && to_version="$(uptate::migration_script_exits)" + +if [[ ${to_version:-false} == false ]]; then + to_version="$(find "$DOTLY_PATH/migration/" -name "*" -type f,l -executable -print0 -exec echo {} \; | xargs -I _ basename _ | sort --reverse | fzf --header "Select migration script version")" +fi + +if [[ -n "$to_version" ]] && [[ -x "$DOTLY_PATH/migration/$to_version" ]]; then + output::write "You will execute migration script for '$to_version' this could" + output::write "result in a damage of your current dotfiles if they are not" + output::write "organized as expected." + output::write "PLEASE PERFORM A BACKUP OF YOUR DOTFILES BEFORE CONTINUE" + output::empty_line + + ! output::yesno "Sure you want to continue" && exit 1 + output::empty_line + + #shellcheck source=/dev/null + . "$DOTLY_PATH/migration/$to_version" || output::error "Migration script '$to_version' could not be executed" +else + output::error "There is no migration script for version '$to_version' or is not a executable file" +fi + +if [[ -n "$to_version" ]] && { [[ -f "$DOTLY_PATH/symlinks/$to_version.yaml" ]] || [[ -f "$DOTLY_PATH/symlinks/$to_version.yml" ]]; }; then + output::header "Applying symlinks for '$to_version'" + "$DOTLY_PATH/bin/dot" symlinks update "$to_version" +fi diff --git a/scripts/self/src/dotly_autoupdate.sh b/scripts/self/src/dotly_autoupdate.sh new file mode 100644 index 000000000..df8cba549 --- /dev/null +++ b/scripts/self/src/dotly_autoupdate.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +. "$DOTLY_PATH/scripts/self/src/update.sh" + +autoupdate::dotly_updater() { + local CURRENT_DIR remote_dotly_minor + set +e # Avoid crash if any function return an error + + # Other needed variables + CURRENT_DIR="$(pwd)" + + # Change to dotly path + cd "$DOTLY_PATH" || return 1 + + [[ -f "$DOTFILES_PATH/.dotly_updated" ]] &&\ + [[ "${DOTLY_AUTO_UPDATE_MODE:-auto}" != "silent" ]] && { + output::empty_line + output::write " 🥳 🎉 🍾 DOTLY UPDATED 🥳 🎉 🍾 " + output::empty_line + migration_script="$(uptate::migration_script_exits)" + if [[ -n "$migration_script" ]]; then + output::write "Migration script is neccesary to be executed and must be done syncronously by executing:" + output::answer "dot self migration $migration_script" + output::empty_line + fi + + [[ -z "$migration_script" ]] && rm "$DOTFILES_PATH/.dotly_updated" + } + + [[ -f "$DOTFILES_PATH/.dotly_update_available" ]] && return 0 + + if files::check_if_path_is_older "$DOTLY_PATH" "${DOTLY_AUTO_UPDATE_PERIOD_IN_DAYS:-7}" "days" &&\ + ! git::check_local_repo_is_updated "origin" "$DOTLY_PATH" + then + touch "$DOTFILES_PATH/.dotly_update_available" + + remote_dotly_minor="$(update::check_minor_update)" + if [[ -z "$remote_dotly_minor" ]]; then + touch "$DOTFILES_PATH/.dotly_update_available_is_major" + fi + fi + + cd "$CURRENT_DIR" || return 1 +} + +autoupdate::dotly_success() { + if [[ -f "$DOTFILES_PATH/.dotly_update_available" ]] && [[ ! -f "$DOTFILES_PATH/.dotly_force_current_version" ]]; then + if [[ -f "$DOTFILES_PATH/.dotly_update_available_is_major" ]] && [[ "$(str::to_lower "$DOTLY_UPDATE_VERSION")" =~ minor$ ]]; then + return 0 + fi + + case "$(str::to_lower "${DOTLY_AUTO_UPDATE_MODE:-auto}")" in + "silent") + update::update_local_dotly_module + rm -f "$DOTFILES_PATH/.dotly_update_available" + ;; + "info") + output::empty_line + output::write " ---------------------------------------------" + output::write "| 🥳🎉🍾 NEW DOTLY VERSION AVAILABLE 🥳🎉🍾 |" + output::write " ---------------------------------------------" + output::empty_line + ;; + "prompt") + # Nothing to do here + ;; + *) # auto + output::answer "🚀 Updating DOTLY Automatically" + update::update_local_dotly_module + output::solution "Updated, restart your terminal." + rm -f "$DOTFILES_PATH/.dotly_update_available" + ;; + esac + fi +} + +autoupdate::dotly_reject() { + # Nothing to be updated + return 0 +} diff --git a/scripts/self/utils/install.sh b/scripts/self/src/install.sh similarity index 96% rename from scripts/self/utils/install.sh rename to scripts/self/src/install.sh index 74530292a..1c4565f49 100755 --- a/scripts/self/utils/install.sh +++ b/scripts/self/src/install.sh @@ -4,7 +4,7 @@ install_macos_custom() { if ! platform::command_exists brew; then output::error "brew not installed, installing" - if [ "$DOTLY_ENV" == "CI" ]; then + if [ "${DOTLY_ENV:-}" == "CI" ]; then export CI=1 fi diff --git a/scripts/self/src/update.sh b/scripts/self/src/update.sh new file mode 100644 index 000000000..398da8baa --- /dev/null +++ b/scripts/self/src/update.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2120 +update::check_minor_update() { + local local_dotly_version remote_dotly_versions tags_number tag_version + tags_number="${1:-10}" + local_dotly_version="$(git::dotly_repository_exec git::get_current_latest_tag)" + remote_dotly_versions=($(git::get_all_remote_tags_version_only $(git::get_submodule_property dotly url) | head -n${tags_number})) + + [ -n "$local_dotly_version" ] && for tag_version in "${remote_dotly_versions[@]}"; do + [ -z "$tag_version" ] && continue # I am not sure if this can happen + [ "$(platform::semver_compare "$local_dotly_version" "$tag_version")" -le 0 ] && break # Older version no check + + if platform::semver_is_minor_or_patch_update "$local_dotly_version" "$tag_version"; then + echo "$tag_version" + return 0 + fi + done + + return 1 +} + +# Get the latest minor using the HEAD as it could be +update::get_latest_minor_local_head() { + local current_tag_version latest_local_tag latest_tags_version tag_version return_code + current_tag_version="$(git::dotly_repository_exec git::get_commit_tag)" # Current HEAD tag + latest_local_tag="$(git::get_current_latest_tag)" + return_code=1 + + if [[ -z "$current_tag_version" ]] && [[ -n "$latest_local_tag" ]]; then + echo "$latest_local_tag" + return_code=0 + + elif [[ -n "$current_tag_version" ]]; then + latest_tags_version=($(git::get_all_local_tags)) + + # Select latest local minor tag taking the current HEAD tag as main + for tag_version in "${latest_tags_version[@]}"; do + [[ "$(platform::semver_compare "$current_tag_version" "$tag_version")" -le 0 ]] && break + if "$(platform::semver_is_minor_or_patch_update "$current_tag_version" "$tag_version")"; then + current_tag_version="$tag_version" + return_code=0 + break + fi + done + fi + + [[ -n "$current_tag_version" ]] && echo "$current_tag_version" + + return "$return_code" +} + +update::check_if_is_stable_update() { + local local_dotly_version remote_dotly_versions tags_number + set +e + + local_dotly_version="$(git::dotly_repository_exec git::get_current_latest_tag)" + remote_dotly_version="$(git::get_all_remote_tags_version_only $(git::get_submodule_property dotly url) | head -n1)" + + [[ "$(platform::semver_compare "$local_dotly_version" "$remote_dotly_version")" -eq -1 ]] && echo "$remote_dotly_version" +} + +# shellcheck disable=SC2120 +update::update_dotly_repository() { + local current_directory current_branch update_submodules return_code + set +e + + # Defaults values that are needed here + current_directory="$(pwd)" + return_code=0 + current_branch="$(git::get_submodule_property dotly branch)" + + # Arguments + update_submodules="${1:-}" + + { [[ -d "$DOTLY_PATH" ]] && cd "$DOTLY_PATH"; } || return 1 + + if git::is_in_repo; then + git discard >/dev/null 2>&1 + git checkout "$current_branch" >/dev/null 2>&1 + git pull >/dev/null 2>&1 + return_code=$? + + if [[ -n "$update_submodules" ]] && [[ $return_code -eq 0 ]]; then + git submodule update --init --recursive "$@" > /dev/null 2>&1 # $@ because maybe you want to update specific submodule only + fi + fi + + cd "$current_directory" || return $return_code + + return $return_code +} + +update::check_consistency_with_dotly_version() { + local local_commit_tag + local_commit_tag="$(git::dotly_repository_exec git::get_commit_tag)" + + case "$(str::to_lower "$DOTLY_UPDATE_VERSION")" in + "stable"|"minor") + if [ -z "$local_commit_tag" ] && [ ! -f "$DOTFILES_PATH/.dotly_force_current_version" ]; then + output::error "Error in your Dotly configuration, 'DOTLY_UPDATE_VERSION'" + output::empty_line + output::answer "You have selected to update to $DOTLY_UPDATE_VERSION but you are not" + output::write "\tusing any stable version. Modify DOTLY_UPDATE_VERSION variable or use" + output::write "\tthe script:" + output::write "\t\tdot self version" + output::empty_line + output::write "You can also disable updates by using: 'dot self update --disable'" + output::empty_line + return 1 + fi + ;; + *) + return 0 + ;; + esac +} + +update::update_local_dotly_module() { + local current_dotly_hash local_dotly_version remote_dotly_minor remote_dotly_tag + set +e # Avoid crash if any function fail + + current_dotly_hash="$(git::get_local_HEAD_hash)" + local_dotly_version="$(git::dotly_repository_exec git::get_commit_tag)" + remote_dotly_minor="$(update::check_minor_update)" + remote_dotly_tag="$(update::check_if_is_stable_update)" + + # No update + if [ ! -f "$DOTFILES_PATH/.dotly_force_current_version" ]; then + return 1 + fi + + # Version consistency + if ! update::check_consistency_with_dotly_version >/dev/null; then + return 1 + fi + + # Update local repository + if ! git::check_local_repo_is_updated "origin" "$DOTLY_PATH"; then + update::update_dotly_repository + [[ -n "$local_dotly_version" ]] && git checkout "$local_dotly_version" # Keep current tag + fi + + case "$(str::to_lower "${DOTLY_AUTO_UPDATE_VERSION:-stable}")" in + "latest"|"beta") + ;; + "minor"|"only_minor") + if [ -n "$remote_dotly_minor" ]; then + git::dotly_repository_exec git checkout "$remote_dotly_minor" + fi + ;; + *) #Stable + git::dotly_repository_exec git checkout -q "$remote_dotly_tag" + ;; + esac + + rm -f "$DOTFILES_PATH/.dotly_force_current_version" + rm -f "$DOTFILES_PATH/.dotly_update_available" + rm -f "$DOTFILES_PATH/.dotly_update_available_is_major" + echo "$current_dotly_hash" >| "$DOTFILES_PATH/.dotly_updated" +} + +uptate::migration_script_exits() { + local latest_migration_script update_previous_commit + latest_migration_script="$(find "$DOTLY_PATH/migration/" -name "*" -type f,l -executable -print0 -exec echo {} \; | sort --reverse | head -n 1 | xargs)" + + # No update no migration necessary + if [[ ! -f "$DOTFILES_PATH/.dotly_updated" ]] || [[ -z "$latest_migration_script" ]]; then + return 1 + fi + + # If was added in previous commit + if ! git::check_file_exists_in_previous_commit "$latest_migration_script"; then + echo "$latest_migration_script" + return 0 + fi + + # Get previous commit and check if was added after + update_previous_commit="$(cat "$DOTFILES_PATH/.dotly_updated")" + [[ -z "$update_previous_commit" ]] && return 1 # Could not be checked if migration script should be executed + + git::check_file_is_modified_after_commit "$latest_migration_script" "$update_previous_commit" && echo "$latest_migration_script" +} diff --git a/scripts/self/update b/scripts/self/update index 9d0ca7df3..d1e339fb6 100755 --- a/scripts/self/update +++ b/scripts/self/update @@ -2,18 +2,33 @@ set -euo pipefail -source "$DOTLY_PATH/scripts/core/_main.sh" +#shellcheck source=/dev/null +. "$DOTLY_PATH/scripts/core/_main.sh" +dot::load_library "update.sh" ##? Update dotly to the latest stable release ##? ##? Usage: -##? update +##? update [--disable | --enable] +##? +##? Options: +##? --disable Deactivate the dotly update command +##? --enable Activate the dotly update command +##? docs::parse "$@" -cd "$DOTLY_PATH" -git discard >/dev/null 2>&1 -git checkout master >/dev/null 2>&1 -git pull >/dev/null 2>&1 -git submodule update --init --recursive >/dev/null 2>&1 +if ${disable:-enable}; then + touch "$DOTFILES_PATH/.dotly_force_current_version" + exit 0 +elif ${enable:-false}; then + rm -f "$DOTFILES_PATH/.dotly_force_current_version" + exit 0 +fi + +update::update_local_dotly_module -output::answer '✅ dotly updated to the latest version' +if [[ -f "$DOTFILES_PATH/.dotly_updated" ]]; then + output::answer '✅ dotly updated to the latest version' +else + output::answer '👌 You already have latest dotly version' +fi diff --git a/scripts/self/utils/init.sh b/scripts/self/utils/init.sh new file mode 100644 index 000000000..50f51fc9e --- /dev/null +++ b/scripts/self/utils/init.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +# Needed variables +DOTLY_INIT_SCRIPTS_PATH=${DOTLY_INIT_SCRIPTS_PATH:-$DOTLY_PATH/shell/init-scripts} +DOTFILES_INIT_SCRIPTS_PATH=${DOTFILES_INIT_SCRIPTS_PATH:-$DOTFILES_PATH/shell/init-scripts} +ENABLED_INIT_SCRIPTS_PATH=${ENABLED_INIT_SCRIPTS_PATH:-$DOTFILES_PATH/shell/init-scripts.enabled} + +# Check if init script exists in dotly or dotfiles +init::exists_script() { + [[ -f "$DOTLY_INIT_SCRIPTS_PATH/$1" ]] || [[ -f "$DOTFILES_INIT_SCRIPTS_PATH" ]] +} + +# check if init script is enabled or disabled +init::status() { + init::exists_script "$1" && [[ -f "$ENABLED_INIT_SCRIPTS_PATH/$1" ]] +} + +# Stored init scripts +init::get_scripts() { + [[ -d "$DOTLY_INIT_SCRIPTS_PATH" ]] &&\ + [[ -d "$DOTFILES_INIT_SCRIPTS_PATH" ]] &&\ + find "$DOTLY_INIT_SCRIPTS_PATH" \ + "$DOTFILES_INIT_SCRIPTS_PATH" -name "*" -type f |\ + xargs -I _ basename _ | sort | uniq +} + +# Enabled init scripts +init::get_enabled() { + [[ -d "$ENABLED_INIT_SCRIPTS_PATH" ]] &&\ + find "$ENABLED_INIT_SCRIPTS_PATH" -name "*" -type l |\ + xargs -I _ basename _ | sort | uniq +} + +init::fzf() { + local piped_values="$( +docs::parse "$@" + +SCRIPT_NAME="dot self version" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if ${version:-}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + +if ${view_remote:-false}; then + git::get_all_remote_tags_version_only + exit 0 +fi + +local_tags=($(git::get_all_local_tags) "minor" "stable" "latest") +selected_tag="$(printf "%s\n" "${local_tags[@]}" | fzf --header "Select a dotly version" --preview "")" + +case "$selected_tag" in + "minor") + current_tag_version="$(update::get_latest_minor_local_head)" # Current HEAD tag or stable + if [[ -n "$current_tag_version" ]]; then + git::dotly_repository_exec git checkout -q "$current_tag_version" + output::solution "Switched to latest DOTLY stable version $current_tag_version" + modify_bash_file_variable "$DOTFILES_PATH/shell/exports.sh" "DOTLY_UPDATE_VERSION" "minor" + else + output::error "No releases locally yet" + fi + ;; + "stable") + latest_stable="$(git::get_current_latest_tag)" + if [[ -n "$latest_stable" ]]; then + git::dotly_repository_exec git checkout -q "$latest_stable" + output::solution "Switched to latest DOTLY stable version $latest_stable" + modify_bash_file_variable "$DOTFILES_PATH/shell/exports.sh" "DOTLY_UPDATE_VERSION" "stable" + else + output::error "No releases locally yet" + fi + ;; + "latest") + git::dotly_repository_exec git checkout -q "master" + ;; + *) + if [[ -n "$selected_tag" ]]; then + git::dotly_repository_exec git checkout -q "$selected_tag" + output::solution "Switched to DOTLY version $selected_tag" + fi + ;; +esac + +if [[ -n "$selected_tag" ]]; then + output::answer "DOTLY is update locked. To unlock updates use: dot self update --enable" + touch "$DOTFILES_PATH/.dotly_force_current_version" +fi diff --git a/scripts/shell/zsh b/scripts/shell/zsh index a52e54d8e..427a08566 100755 --- a/scripts/shell/zsh +++ b/scripts/shell/zsh @@ -9,6 +9,7 @@ source "$DOTLY_PATH/scripts/core/_main.sh" ##? zsh test_performance ##? zsh reload_completions ##? zsh clean_cache +##? zsh fix_permissions docs::parse "$@" case $1 in @@ -48,6 +49,11 @@ case $1 in output::empty_line output::answer 'Now restart your terminal' ;; +"fix_permissions") + sudo -v + sudo chown -R $(whoami) /usr/local/share/zsh /usr/local/share/zsh/site-functions + chmod u+w /usr/local/share/zsh /usr/local/share/zsh/site-functions + ;; *) exit 1 ;; diff --git a/scripts/symlinks/apply b/scripts/symlinks/apply index 2731cac40..7c1a93892 100755 --- a/scripts/symlinks/apply +++ b/scripts/symlinks/apply @@ -18,8 +18,21 @@ symlinks::apply() { ##? Usage: ##? apply ##? +##? Options: +##? -h --help Show this help +##? -v --version Show the program version +##? docs::parse "$@" +SCRIPT_NAME="dot symlinks apply" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if $version; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + symlinks::apply "conf.yaml" if platform::is_macos; then diff --git a/scripts/symlinks/update b/scripts/symlinks/update new file mode 100755 index 000000000..7612b19ea --- /dev/null +++ b/scripts/symlinks/update @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +set -euo pipefail + +. "$DOTLY_PATH/scripts/core/_main.sh" + +symlinks::get_files() { + [[ -d "$DOTLY_PATH/symlinks" ]] &&\ + find "$DOTLY_PATH/symlinks" -name "*.yaml" -type f,l -print0 -exec echo {} \; |\ + xargs -I _ basename _ | sort +} + +symlinks::fzf() { + local piped_values + piped_values="$(] +##? +##? Options: +##? -h --help Show this help +##? -v --version Show the program version +##? -p --no-prompt Avoid warning the user about the consecuences of apply +##? a dotbot file. +##? +docs::parse "$@" + +SCRIPT_NAME="dot symlinks apply" +SCRIPT_VERSION="1.0.0" + +# Print name and version +if ${version:-false}; then + output::write "$SCRIPT_NAME v$SCRIPT_VERSION" + exit +fi + + +if [[ -z "$symlinks_file" ]]; then + symlinks_file="$(symlinks::get_files | symlinks::fzf)" + [[ -z "$symlinks_file" ]] && exit 0 + symlinks_file="$DOTLY_PATH/symlinks/$symlinks_file" +else + for f in "$symlinks_file" "$symlinks_file.yaml" "$symlinks_file.yml"; do + [[ -e "$f" ]] && symlinks_file="$f" && break + done + + if [[ ! -e "$symlinks_file" ]]; then + output::error "The file does not exists" + exit 1 + fi +fi + +if ! ${no_prompt:-false} && ! output::yesno "This could be danger your current dotfiles. Do you still want to continue"; then + exit 1 +fi + +output::header "Apply dotbot update to your dotfiles" +output::write "This will apply a selected symlinks to apply any dotly update" + +output::empty_line +"$DOTLY_PATH/modules/dotbot/bin/dotbot" -d "$DOTFILES_PATH" -c "$symlinks_file" +output::empty_line + +output::write "Remember to merge this symlinks file to yours in:" +output::answer "$DOTFILES_PATH/symlinks/conf.yaml" + \ No newline at end of file diff --git a/shell/bash/init.sh b/shell/bash/init.sh new file mode 100755 index 000000000..470017c87 --- /dev/null +++ b/shell/bash/init.sh @@ -0,0 +1,59 @@ +if [[ "$(ps -p $$ -ocomm=)" =~ (bash$) ]]; then + __right_prompt() { + RIGHT_PROMPT="" + [[ -n $RPS1 ]] && RIGHT_PROMPT=$RPS1 || RIGHT_PROMPT=$RPROMPT + if [[ -n $RIGHT_PROMPT ]]; then + n=$(($COLUMNS - ${#RIGHT_PROMPT})) + printf "%${n}s$RIGHT_PROMPT\\r" + fi + } + export PROMPT_COMMAND="__right_prompt" +fi + +PATH=$( + IFS=":" + echo "${path[*]:-}" +) +export PATH + +themes_paths=( + "$DOTFILES_PATH/shell/bash/themes" + "$DOTLY_PATH/shell/bash/themes" +) + +# bash completion +export BASH_COMPLETION_COMPAT_DIR="/usr/local/etc/bash_completion.d" +if [[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]]; then + #shellcheck source=/dev/null + . "/usr/local/etc/profile.d/bash_completion.sh" +fi + +# brew Bash completion +if type brew &>/dev/null; then + HOMEBREW_PREFIX="$(brew --prefix)" + if [[ -r "${HOMEBREW_PREFIX}/etc/profile.d/bash_completion.sh" ]]; then + #shellcheck source=/dev/null + . "${HOMEBREW_PREFIX}/etc/profile.d/bash_completion.sh" + else + for COMPLETION in "${HOMEBREW_PREFIX}/etc/bash_completion.d/"*; do + #shellcheck source=/dev/null + [[ -r "$COMPLETION" ]] && . "$COMPLETION" + done + fi +fi +unset COMPLETION + +#shellcheck disable=SC2068 +for THEME_PATH in ${themes_paths[@]}; do + THEME_PATH="${THEME_PATH}/${DOTLY_THEME:-codely}.sh" + #shellcheck source=/dev/null + [ -f "$THEME_PATH" ] && . "$THEME_PATH" && break +done +unset THEME_PATH + +find {"$DOTLY_PATH","$DOTFILES_PATH"}"/shell/bash/completions/" -name "_*" -print0 -exec echo {} \; 2>/dev/null | xargs -0 -I _ echo _ | while read -r completion; do + [[ -z "$completion" ]] && continue + #shellcheck source=/dev/null + . "$completion" || echo -e "\033[0;31mBASH completion '$completion' could not be loaded\033[0m" +done +unset completion diff --git a/shell/bash/themes/codely.sh b/shell/bash/themes/codely.sh index 919c00b2e..68bdb2e20 100644 --- a/shell/bash/themes/codely.sh +++ b/shell/bash/themes/codely.sh @@ -4,6 +4,20 @@ MIDDLE_CHARACTER="◂" GREEN_COLOR="32" RED_COLOR="31" +prompt_dotly_autoupdate() { + if [ -f "$DOTFILES_PATH/.dotly_update_available" ] &&\ + { + [ "$(echo "$DOTLY_AUTO_UPDATE_MODE" | tr '[:upper:]' '[:lower:]')" != "minor" ] ||\ + { + [ "$(echo "$DOTLY_AUTO_UPDATE_MODE" | tr '[:upper:]' '[:lower:]')" == "minor" ] &&\ + [ ! -f "$DOTFILES_PATH/.dotly_update_available_is_major" ] + } + } + then + print -n "📥 | " + fi +} + codely_theme() { LAST_CODE="$?" current_dir=$(dot core short_pwd) @@ -15,8 +29,8 @@ codely_theme() { fi if [ -z "$CODELY_THEME_MINIMAL" ]; then - export PS1="\[\e[${STATUS_COLOR}m\]{\[\e[m\]${MIDDLE_CHARACTER}\[\e[${STATUS_COLOR}m\]}\[\e[m\] \[\e[33m\]${current_dir}\[\e[m\] " + export PS1="\$(prompt_dotly_autoupdate)\[\e[${STATUS_COLOR}m\]{\[\e[m\]${MIDDLE_CHARACTER}\[\e[${STATUS_COLOR}m\]}\[\e[m\] \[\e[33m\]${current_dir}\[\e[m\] " else - export PS1="\[\e[${STATUS_COLOR}m\]{\[\e[m\]${MIDDLE_CHARACTER}\[\e[${STATUS_COLOR}m\]}\[\e[m\] " + export PS1="\$(prompt_dotly_autoupdate)\[\e[${STATUS_COLOR}m\]{\[\e[m\]${MIDDLE_CHARACTER}\[\e[${STATUS_COLOR}m\]}\[\e[m\] " fi } diff --git a/shell/init-dotly.sh b/shell/init-dotly.sh new file mode 100644 index 000000000..ee13022b6 --- /dev/null +++ b/shell/init-dotly.sh @@ -0,0 +1,108 @@ +# Needed dotly functions +#shellcheck disable=SC2148 +function cdd() { + #shellcheck disable=SC2012 + cd "$(ls -d -- */ | fzf)" || echo "Invalid directory" +} + +function j() { + fname=$(declare -f -F _z) + + #shellcheck source=/dev/null + [ -n "$fname" ] || . "$DOTLY_PATH/modules/z/z.sh" + + _z "$1" +} + +function recent_dirs() { + # This script depends on pushd. It works better with autopush enabled in ZSH + escaped_home=$(echo "$HOME" | sed 's/\//\\\//g') + selected=$(dirs -p | sort -u | fzf) + + # shellcheck disable=SC2001 + cd "$(echo "$selected" | sed "s/\~/$escaped_home/")" || echo "Invalid directory" +} + +# Envs +# GPG TTY +GPG_TTY="$(tty)" +export GPG_TTY + +# shellcheck source=/dev/null +[[ -f "$DOTFILES_PATH/shell/exports.sh" ]] && . "$DOTFILES_PATH/shell/exports.sh" + +# Paths +# shellcheck source=/dev/null +[[ -f "$DOTFILES_PATH/shell/paths.sh" ]] && . "$DOTFILES_PATH/shell/paths.sh" + +# Add openssl if it exists +[[ -d "/usr/local/opt/openssl/bin" ]] && path+=("/usr/local/opt/openssl/bin") + +# Conditional paths +[[ -d "${JAVA_HOME:-}" ]] && path+=("$JAVA_HOME/bin") +[[ -d "${GEM_HOME:-}" ]] && path+=("$GEM_HOME/bin") +[[ -d "${GOHOME:-}" ]] && path+=("$GOHOME/bin") +[[ -d "$HOME/.deno/bin" ]] && path+=("$HOME/.deno/bin") +[[ -d "/usr/local/opt/ruby/bin" ]] && path+=("/usr/local/opt/ruby/bin") +[[ -d "/usr/local/opt/python/libexec/bin" ]] && path+=("/usr/local/opt/python/libexec/bin") +[[ -d "/usr/local/bin" ]] && path+=("/usr/local/bin") +[[ -d "/usr/local/sbin" ]] && path+=("/usr/local/sbin") +[[ -d "/bin" ]] && path+=("/bin") +[[ -d "/usr/bin" ]] && path+=("/usr/bin") +[[ -d "/usr/sbin" ]] && path+=("/usr/sbin") +[[ -d "/sbin" ]] && path+=("/sbin") + +# Brew add gnutools in macos only +# UNAME_BIN and BREW_BIN are necessary because paths are not yet loaded +UNAME_BIN="${UNAME_BIN:-/usr/bin/uname}" +if [[ -x "$UNAME_BIN" && "$("$UNAME_BIN" -s)" == "Darwin" ]]; then + BREW_BIN="${BREW_BIN:-$(which brew)}" + [[ ! -x "$BREW_BIN" && -x "/usr/local/bin/brew" ]] && BREW_BIN="/usr/local/bin/brew" + + if [[ -d "$("$BREW_BIN" --prefix)" ]]; then + export path=( + "$("$BREW_BIN" --prefix)/opt/coreutils/libexec/gnubin" + "$("$BREW_BIN" --prefix)/opt/findutils/libexec/gnubin" + "${path[@]}" + ) + fi +fi + +# Load dotly core for your current BASH +# PR Note about this: $SHELL sometimes see zsh under certain circumstances in macOS +CURRENT_SHELL="unknown" +if [[ -n "${BASH_VERSION:-}" ]]; then + CURRENT_SHELL="bash" +elif [[ -n "${ZSH_VERSION:-}" ]]; then + CURRENT_SHELL="zsh" +fi + +if [[ "$CURRENT_SHELL" != "unknown" && -f "$DOTLY_PATH/shell/${CURRENT_SHELL}/init.sh" ]]; then + #shellcheck source=/dev/null + . "$DOTLY_PATH/shell/${CURRENT_SHELL}/init.sh" +else + echo -e "\033[0;31m\033[1mDOTLY Could not be loaded: Initializer not found for \`${CURRENT_SHELL}\`\033[0m" +fi + +# Aliases +#shellcheck source=/dev/null +{ [[ -f "$DOTFILES_PATH/shell/aliases.sh" ]] && . "$DOTFILES_PATH/shell/aliases.sh"; } || true + +# Functions +#shellcheck source=/dev/null +{ [[ -f "$DOTFILES_PATH/shell/functions.sh" ]] && . "$DOTFILES_PATH/shell/functions.sh"; } || true + +#shellcheck source=/dev/null +[[ -f "$HOME/.cargo/env" ]] && . "$HOME/.cargo/env" + + +# Auto Init scripts at the end +init_scripts_path="$DOTFILES_PATH/shell/init-scripts.enabled" +if [[ ${DOTLY_INIT_SCRIPTS:-true} == true ]] && [[ -d "$init_scripts_path" ]]; then + find "$DOTFILES_PATH/shell/init-scripts.enabled" -mindepth 1 -maxdepth 1 -type f,l -print0 2>/dev/null | xargs -0 -I _ realpath --quiet --logical _ | while read -r init_script; do + [[ -z "$init_script" ]] && continue + #shellcheck source=/dev/null + { [[ -f "$init_script" ]] && . "$init_script"; } || echo -e "\033[0;31m$init_script could not be loaded\033[0m" + done +fi +unset init_script init_scripts_path diff --git a/shell/init-scripts/autoupdate b/shell/init-scripts/autoupdate new file mode 100755 index 000000000..ae80f3307 --- /dev/null +++ b/shell/init-scripts/autoupdate @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +"$DOTLY_PATH/bin/dot" self async-update \ No newline at end of file diff --git a/shell/zsh/init.sh b/shell/zsh/init.sh new file mode 100755 index 000000000..6bd6e73fb --- /dev/null +++ b/shell/zsh/init.sh @@ -0,0 +1,58 @@ +#shellcheck disable=SC2148 +reverse-search() { + local selected num + setopt localoptions noglobsubst noposixbuiltins pipefail HIST_FIND_NO_DUPS 2> /dev/null + + #shellcheck disable=SC2207 + selected=( $(fc -rl 1 | + FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" fzf) ) + local ret=$? + if [ -n "${selected[*]:-}" ]; then + num=${selected[1]} + if [ -n "$num" ]; then + zle vi-fetch-history -n $num + fi + fi + zle redisplay + typeset -f zle-line-init >/dev/null && zle zle-line-init + return $ret +} + +# ZSH Ops +setopt HIST_IGNORE_ALL_DUPS +setopt HIST_FCNTL_LOCK +setopt +o nomatch +# setopt autopushd + +# Start zim +#shellcheck source=/dev/null +. "$ZIM_HOME/init.zsh" + +# Async mode for autocompletion +# shellcheck disable=SC2034 +ZSH_AUTOSUGGEST_USE_ASYNC=true +# shellcheck disable=SC2034 +ZSH_HIGHLIGHT_MAXLENGTH=300 + +fpath=( + "$DOTFILES_PATH/shell/zsh/themes" + "$DOTFILES_PATH/shell/zsh/autocompletions" + "$DOTLY_PATH/shell/zsh/themes" + "$DOTLY_PATH/shell/zsh/completions" + "${fpath[@]}" +) + +# Brew ZSH Completions +if type brew &>/dev/null; then + fpath+=("$(brew --prefix)/share/zsh/site-functions") +fi + +autoload -Uz promptinit && promptinit +prompt "${DOTLY_THEME:-codely}" + +#shellcheck source=/dev/null +. "$DOTLY_PATH/shell/zsh/bindings/dot.zsh" +#shellcheck source=/dev/null +. "$DOTLY_PATH/shell/zsh/bindings/reverse_search.zsh" +#shellcheck source=/dev/null +. "$DOTFILES_PATH/shell/zsh/key-bindings.zsh" diff --git a/shell/zsh/themes/prompt_codely_setup b/shell/zsh/themes/prompt_codely_setup index 0bb0410c9..7220fd2c5 100644 --- a/shell/zsh/themes/prompt_codely_setup +++ b/shell/zsh/themes/prompt_codely_setup @@ -42,8 +42,16 @@ prompt_codely_precmd() { (( ${+functions[git-info]} )) && git-info } +prompt_dotly_autoupdate() { + if [ -f "$DOTFILES_PATH/.dotly_update_available" ]; then + print -n "📥 | " + fi +} + prompt_codely_setup() { - local prompt_codely_status="%(?:%F{green}{%F{$status_icon_color}$status_icon_ok%F{green}}:%F{red}{%F{$status_icon_color}$status_icon_ko%F{red}})" + local prompt_codely_status + + prompt_codely_status="%(?:%F{green}{%F{$status_icon_color}$status_icon_ok%F{green}}:%F{red}{%F{$status_icon_color}$status_icon_ko%F{red}})" autoload -Uz add-zsh-hook && add-zsh-hook precmd prompt_codely_precmd @@ -56,9 +64,9 @@ prompt_codely_setup() { zstyle ':zim:git-info:keys' format "prompt" " %F{cyan}%b%c %C%D" if [ "$CODELY_THEME_MINIMAL" = true ]; then - PS1="${prompt_codely_status} " + PS1="\$(prompt_dotly_autoupdate)${prompt_codely_status} " else - PS1="${prompt_codely_status} \$(prompt_codely_pwd)\$(prompt_codely_git)%f " + PS1="\$(prompt_dotly_autoupdate)${prompt_codely_status} \$(prompt_codely_pwd)\$(prompt_codely_git)%f " fi if [ "$CODELY_THEME_PROMPT_IN_NEW_LINE" = true ]; then diff --git a/symlinks/core-feature.yaml b/symlinks/core-feature.yaml new file mode 100644 index 000000000..7d66113e8 --- /dev/null +++ b/symlinks/core-feature.yaml @@ -0,0 +1,10 @@ +- defaults: + link: + create: true + force: true + +- create: + - $DOTLY_PATH/shell/init-scripts + - $DOTFILES_PATH/shell/init-scripts + - $DOTFILES_PATH/shell/init-scripts.enabled + diff --git a/symlinks/v2.0.0.yaml b/symlinks/v2.0.0.yaml new file mode 100644 index 000000000..2d05eab82 --- /dev/null +++ b/symlinks/v2.0.0.yaml @@ -0,0 +1,8 @@ +- defaults: + link: + create: true + +- create: + - $DOTLY_PATH/shell/init-scripts + - $DOTFILES_PATH/shell/init-scripts + - $DOTFILES_PATH/shell/init-scripts.enabled