diff --git a/.env.zsh b/.env.zsh new file mode 100644 index 0000000..be95293 --- /dev/null +++ b/.env.zsh @@ -0,0 +1,29 @@ +##################################################################### +# code-activator artifact # +##################################################################### +# # +# Variables and functions set in this file will be sourced when # +# this project is activated # +# # +# # +# Using the following syntax: # +# ``` # +# __RESTORE__ENV_VAR='new-value' # +# ``` # +# will save the current value of $ENV_VAR then assign ENV_VAR to # +# the 'new-value'. When code-activator calls deactivate, ENV_VAR # +# will be reset to its original value. # +# # +# this only works with VARIABLES (not functions) # +# # +# # +# # +# all other variables / functions will simply be unset whenever: # +# - deactivate is called # +# - another project is activated # +# # +##################################################################### + +export __RESTORE__PATH="$PATH" + +export MY_EXAMPLE_VARIABLE=69 diff --git a/README.md b/README.md index 27e80d1..3aba252 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,44 @@ # Code Activator ##### *A `zsh` utility for terminal-based project navigation* [![Generic badge](https://img.shields.io/badge/junegunn-fzf-blueviolet.svg)](https://github.com/junegunn/fzf) +[![Generic badge](https://img.shields.io/badge/stedolan-jq-blueviolet.svg)](https://github.com/jq/) -The `CODE_ACTIVATOR` function facilitates terminal-based project navigation by providing an interactive, fuzzy-search CLI. - -Generally, the CLI will activate a project's virtual environment then step into the project root. -Once inside a project, the CLI gives the option to deactivate and return to the user's home directory. - -Projects can also be quickly cloned or created from scratch by using the `clone` and `new` operations respectively. - -Use the plugin one of three ways: -1. Use the shortcut combination (`CTRL+SPACE` by default) -1. Use the alias (`lkj` by default) -1. Call `CODE_ACTIVATOR` directly (meant for use as an API) +Code Activator provides fast project navigation in the terminal through both an API and CLI. ## Contributing See our [contributing guide](./docs/CONTRIBUTING.md) and [code of conduct](./docs/CODE_OF_CONDUCT.md). ## Installation -1. install [junegunn/fzf](https://github.com/junegunn/fzf) -1. clone this repo, and source the `*.plugin.zsh` in your `zshrc`: +1. install dependencies [junegunn/fzf](https://github.com/junegunn/fzf) and [stedolan/jq](https://github.com/stedolan/jq) +1. clone this repo, and source the `activator.plugin.zsh` in your `zshrc`: ```shell # replace /code-activator with the appropriate path git clone https://github.com/w0ryn/code-activator-zsh.git /code-activator echo 'source /code-activator' >> $HOME/.zshrc ``` -## Configuration -All configuration options are determined by their respective environment variables. +By default, Code Activator looks for projects in `~/Projects/GitHub` and `~/Projects/BitBucket`, but you can configure\* this in your `~/.config/code-activator-zsh/settings.zsh`. +It is highly recommended that, if nothing else, you configure `CA__DIRS` to group your projects as you please. -### Basic Configuration -Environment Variable | Default | Description ----------------------------------- | ------------------------------- | ----------- -`CODE_ACTIVATOR__SHORTCUT` | `^@` (a.k.a. CTRL+SPACE) | shortcut for running `CODE_ACTIVATOR` as a plugin -`CODE_ACTIVATOR__DISABLE_SHORTCUT` | `0` (a.k.a. false) | whether (or not) the shortcut runner is disabled -`CODE_ACTIVATOR__ALIAS` | `lkj` | easy-to-type alias for running `CODE_ACTIVATOR` -`CODE_ACTIVATOR__DISABLE_ALIAS` | `0` (a.k.a. false) | whether (or not) the alias runner is disabled +\**the configuration file is created the first time Code Activator is sourced* -### Project Directories -- `CODE_ACTIVATOR__DIRS` (list) environment variable -A list of fully-qualified paths to project parent directories. -Provides `($HOME/Code)` as a generic default, but allows for meaningful project grouping: -```shell -export CODE_ACTIVATOR__DIRS=( - "$HOME/Company/Team1" - "$HOME/Company/Team2" - "$HOME/Miscellaneous" - "$HOME/Personal" -) -``` +## Usage +Code Activator commands can be invoked one of three ways: +1. directly (`code-activator`; intended for use as an API) +2. through an alias (`lkj` by default) +3. through a shortcut (`CTRL+SPACE` by default; creates a zsh-plugin) -### Known Targets -- `CODE_ACTIVATOR__KNOWN_TARGETS` (list) environment variable -- expects targets to end in `:` or `/` character -- *appends environment variable list to the default list* +With no argument, Code Activator provides a list of all available projects. +Select one to activate it's environment and jump to the project's root. + +If your first argument to Code Activator is a project name, the specified project will be activated. + +You can also `deactivate` an activated project, `clone` an existing project, or create a `new` project (with the appropriate command). + + +## Custom Environment +Although not exactly a virtual environment, Code Activator creates a `custom-env` for projects where you can set project-specific environment variables or shell functions. +It also provides a `__RESTORE__` syntax to allow safe manipulation of `PATH` or other variables when activating a project. +See [the custom-env template](./.env.zsh) for more details. -A list of default completion targets for setting up or cloning new repositories. -Includes HTTP and GIT protocol targets for GitHub and BitBucket by default, but you may want to add your user to the list: -```shell -export CODE_ACTIVATOR__KNOWN_TARGETS=( - 'git@github.com:/' -) -``` diff --git a/activator.bindings.zsh b/activator.bindings.zsh new file mode 100644 index 0000000..294fcad --- /dev/null +++ b/activator.bindings.zsh @@ -0,0 +1,23 @@ +##################################################################### + +[[ $CA__DISABLE_ALIAS -eq 0 ]] && { + alias $CA__ALIAS='code-activator' +} + +##################################################################### + +[[ $CA__DISABLE_SHORTCUT -eq 0 ]] && command -v zle >/dev/null 2>&1 \ + || return 0 + +_CA__ZSH_SHORTCUT_PLUGIN() { + local OPTIONS=(clone new $(_CA_LIST)) + [ $_CA_ENV ] && OPTIONS=(deactivate $OPTIONS) + + local SELECTION=$(echo $OPTIONS | _CA_MULTILINE | _CA_FZF 'select a project') + [[ $SELECTION =~ . ]] && code-activator $SELECTION + echo + zle reset-prompt +} + +zle -N codeactivator _CA__ZSH_SHORTCUT_PLUGIN +bindkey $CA__SHORTCUT codeactivator diff --git a/activator.completion.zsh b/activator.completion.zsh new file mode 100644 index 0000000..1b13e42 --- /dev/null +++ b/activator.completion.zsh @@ -0,0 +1,40 @@ +##################################################################### +command -v compdef >/dev/null 2>&1 || return 0 +##################################################################### + +_CA__PROJECT_COMPADD() { + local DESCRIPTIONS=() + + [ $_CA_ENV ] && DESCRIPTIONS+='deactivate:deactivate env and jump to $HOME' + DESCRIPTIONS+='clone:clone a project from a known target' + DESCRIPTIONS+='new:create a new project' + + _describe 'commands' DESCRIPTIONS + + compadd -- $(_CA_LIST) +} + +##################################################################### + +_code-activator() { + local state + + _arguments \ + '1: :->project' \ + '2: :->arg1' \ + ; + + case $state in + project ) _CA__PROJECT_COMPADD ;; + arg1 ) + case $words[2] in + clone ) + echo; echo 'where should I clone this?' + compadd -- $CA__DIRS + ;; + esac + ;; + esac +} + +compdef _code-activator code-activator diff --git a/activator.plugin.zsh b/activator.plugin.zsh index f098c5d..05ed654 100644 --- a/activator.plugin.zsh +++ b/activator.plugin.zsh @@ -1,131 +1,40 @@ #!/bin/zsh -##################################################################### + ################################################################### # # -# CODE_ACTIVATOR : a terminal-based navigation utility # +# code-activator() : a terminal-based navigation utility # # # -##################################################################### + ################################################################### -DEPENDENCIES=( - fzf - ) - -IMPORTS=( - "${0:a:h}/zsh/settings.zsh" - "${0:a:h}/zsh/helpers.zsh" - "${0:a:h}/zsh/clone.zsh" - "${0:a:h}/zsh/activate.zsh" - ) +_CA__VERSION=1.0.0 +_CA__DEPENDENCIES=(code-activator fzf jq git) ##################################################################### -for DEPENDENCY in $DEPENDENCIES -do - command -v $DEPENDENCY >/dev/null 2>&1 || { - echo "I require '$DEPENDENCY' but it's not installed :c" - ERROR_CODE=1 - } -done +export _CA=${0:a:h} + +source $_CA/global.zsh || return 1 +source $_CA/config.zsh || return 1 + +for Z in $(ls $_CA/zsh/*); do source $Z || return 1; done ##################################################################### -for IMPORT in $IMPORTS; do source $IMPORT; done +code-activator() { + _CA__CHECK_DEPENDENCIES $_CA__DEPENDENCIES || return 1 -[ ! $_CA__ACTIVATE_LOADED ] && ERROR_CODE=2 -[ ! $_CA__CLONE_LOADED ] && ERROR_CODE=2 - -##################################################################### - -[ $ERROR_CODE ] && return $ERROR_CODE - -##################################################################### -##################################################################### - -CODE_ACTIVATOR() { - local ERROR=0 + local COMMAND ARGS case $1 in - deactivate ) _CA__RESTORE_ENVIRONMENT && cd || ERROR=1 ;; + deactivate ) COMMAND=DEACTIVATE ;; - clone ) _CA__CLONE ${@:2} || ERROR=1 ;; - new ) IS_NEW_PROJECT=1 _CA__CLONE ${@:2} || ERROR=1 ;; - * ) _CA__ACTIVATE ${@:1} || ERROR=42 ;; + clone ) COMMAND=CLONE; ARGS=(${@:2}) ;; + new ) COMMAND=NEW; ARGS=(${@:2}) ;; + + * ) COMMAND=ACTIVATE; ARGS=(${@:1}) ;; esac - [[ $ERROR -ne 0 ]] && _CA__ERROR_CLEANUP $ERROR - - return $ERROR + _CA_$COMMAND $ARGS } -_CODE_ACTIVATOR() { - local state - - _arguments \ - '1: :->project' \ - ':: :->arguments' \ - ; - - case $state in - project ) - compadd $(_CA__GET_COMMANDS_AND_PROJECTS | sed 's/deactivate//') - ;; - arguments ) - case $words[2] in - clone ) __CA__CLONE ;; - esac - ;; - esac -} -compdef _CODE_ACTIVATOR CODE_ACTIVATOR - -##################################################################### - -_CA__GET_COMMANDS_AND_PROJECTS() { - local COMMANDS=(deactivate clone new) - local PROJECTS=() - - for base_dir in $CODE_ACTIVATOR__DIRS - do - for project_dir in $(ls -d -- $base_dir/*) - do - PROJECTS+=("$(basename $base_dir)/$(basename $project_dir)") - done - done - - echo $COMMANDS $PROJECTS -} - -_CA__ERROR_CLEANUP() { - local ERROR="$1" - [ $_CA__SUPPRESS_ERROR ] && return - case $ERROR in - 42 ) - echo 'failed to activate environment; aborting' >&2 - ;; - esac -} - -##################################################################### - -[[ $CODE_ACTIVATOR__DISABLE_SHORTCUT -eq 0 ]] && { - _CA__ZSH_SHORTCUT_PLUGIN() { - local OPTIONS=$(_CA__GET_COMMANDS_AND_PROJECTS | _CA__SED_MULTILINE) - [ ! $__CUSTOM_ENV_ACTIVE ] && OPTIONS=$(echo $OPTIONS | grep -v 'deactivate') - - local ARGUMENT=$(\ - echo $OPTIONS \ - | _CA__SED_MULTILINE \ - | $_CA__FZF --prompt 'select a project: ' \ - ) - - _CA__IN_ZSH_PLUGIN=1 CODE_ACTIVATOR $ARGUMENT - - echo - zle reset-prompt - } - zle -N codeactivator _CA__ZSH_SHORTCUT_PLUGIN - bindkey $CODE_ACTIVATOR__SHORTCUT codeactivator -} - -[[ $CODE_ACTIVATOR__DISABLE_ALIAS -eq 0 ]] && { - alias $CODE_ACTIVATOR__ALIAS='CODE_ACTIVATOR' -} +source $_CA/activator.completion.zsh +source $_CA/activator.bindings.zsh diff --git a/config.zsh b/config.zsh new file mode 100644 index 0000000..983b10d --- /dev/null +++ b/config.zsh @@ -0,0 +1,67 @@ +# cloned projects will be nested under the specified directories, +# allowing for meaningful project grouping. A friendly name is +# derived from the path +# +# e.g. "$HOME/Projects/GitHub" will be called "GitHub" in the CLI +# +# directories must be fully-qualified +CA__DIRS=( + "$HOME/Projects/GitHub" + "$HOME/Projects/BitBucket" + ) + +# additional cloning targets; MUST END IN ':' or '/' +# e.g. 'git@my.githost.com:' or 'git@github.com:w0ryn/' +CA__KNOWN_TARGETS=( + 'https://github.com/' + 'https://bitbucket.org/' + 'git@github.com:' + 'git@bitbucket.org:' + ) + + +# run from the specified hot-key +CA__SHORTCUT='' # CTRL+SPACE + +# run from the specified alias +CA__ALIAS='lkj' + + +# enable / disable the shortcut / alias +# 0 = enabled 1 = disabled +CA__DISABLE_SHORTCUT=0 +CA__DISABLE_ALIAS=0 + + +# structure options +# +# where LOCAL_PROJECT_NAME is provided through the API / CLI, +# the plugin will attempt to create the following structure: +# +# LOCAL_PROJECT_NAME +# | SOURCE_DIR << cloned repository +# | VIRTUAL_ENV << the project's virtual environment +# | CUSTOM_ENV << CODE_ACTIVATOR's custom environment variables file +# | NO_ENV << CODE_ACTIVATOR sentinel to avoid prompting env setup +CA__SOURCE_DIR='code' +CA__VIRTUAL_ENV='env' +CA__CUSTOM_ENV='custom-env' +CA__NO_ENV='no-env' + +##################################################################### + +[ ! -f $_CA__LOCAL_CONFIG ] && { + mkdir -p -- $(dirname $_CA__LOCAL_CONFIG) >/dev/null 2>&1 + awk '/##/{exit};1' $_CA/config.zsh | sed 's/^[^#]/#&/; $d'> $_CA__LOCAL_CONFIG +} + +source $_CA__LOCAL_CONFIG + +export \ + CA__DIRS \ + CA__KNOWN_TARGETS \ + CA__SOURCE_DIR_NAME \ + CA__VIRTUAL_ENV_NAME \ + CA__CUSTOM_ENV_NAME \ + CA__NO_ENV \ + ; diff --git a/global.zsh b/global.zsh new file mode 100644 index 0000000..57d290e --- /dev/null +++ b/global.zsh @@ -0,0 +1,59 @@ +##################################################################### +### Artifacts and Operations ######################################## +##################################################################### + +_CA__LOCAL_CONFIG="$HOME/.config/code-activator-zsh/settings.zsh" + + +_CA_ERROR() { [ $_CA__SUPPRESS_ERROR ] || echo "CA::ERROR : $@" >&2; } + +_CA_FZF() { + local MSG="$@" + [ ! $_NOSEP ] && MSG="$@ : " + fzf -i --height=50% --layout=reverse $FLAGS --prompt $MSG +} +_CA_FZF_PRINT() { _NOSEP=$_NOSEP FLAGS=(--print-query) _CA_FZF $@ | tail -1; } + +_CA_LOAD() { source $_CA/zsh/$1.zsh; } + +_CA_READ() { read -k $1; echo; } + +_CA_MULTILINE() { sed 's/\s\+/\n/g'; } + +_CA_LIST() { + local base_dir PROJECTS=() + { + for base_dir in $CA__DIRS + do + { cd $base_dir; ls -d *; } \ + | awk '{print "'$(basename $base_dir)/'"$1;}' + done + } +} + +##################################################################### +### Dependency Checking ############################################# +##################################################################### + +_CA__CHECK_DEPENDENCIES() { + local D DEPENDENCIES=($@) + for D in $DEPENDENCIES + do + command -v $D >/dev/null 2>&1 || { + local LINK=$(_CA__GET_DEPENDENCY_LINK $D) + echo "I require '$D', but it's not installed. $LINK" >&2 + } + done +} + +_CA__GET_DEPENDENCY_LINK() { + local LINK + + case $1 in + fzf ) LINK='https://github.com/junegunn/fzf' ;; + jq ) LINK='https://github.com/stedolan/jq' ;; + esac + + [ $LINK ] && LINK='('$LINK')' + echo $LINK +} diff --git a/zsh/activate.zsh b/zsh/activate.zsh index 86e7cb2..6ccd8b2 100644 --- a/zsh/activate.zsh +++ b/zsh/activate.zsh @@ -1,58 +1,33 @@ -##################################################################### - -[ ! $_CA__SETTINGS_LOADED ] && source "${0:a:h}/settings.zsh" -[ ! $_CA__HELPERS_LOADED ] && source "${0:a:h}/helpers.zsh" - -##################################################################### - -_CA__ACTIVATE() { - _CA__RESTORE_ENVIRONMENT - +_CA_ACTIVATE() { local PROJECT="$1" - [ ! $PROJECT ] && { - [ ! $_CA__IN_ZSH_PLUGIN ] && cd - return 0 - } + [ ! $PROJECT ] && return 1 local PROJECT_PATH=$(_CA__GET_FULL_PATH $PROJECT) [ ! -d $PROJECT_PATH ] && return 1 - local SOURCE_PATH="$PROJECT_PATH/$_CA__SOURCE_DIR_NAME" - [ -d $SOURCE_PATH ] && { - _CA__ACTIVATE_VIRTUAL_ENV $PROJECT_PATH - _CA__ACTIVATE_CUSTOM_ENV $PROJECT_PATH - cd $SOURCE_PATH - } || { - export __CUSTOM_ENV_ACTIVE=420 - cd $PROJECT_PATH - } - _CA__TMUX_WINDOW_RENAME $PROJECT + _CA_ACTIVATE_ENV $PROJECT_PATH return 0 } -_CA__ACTIVATE_VIRTUAL_ENV() { - local PROJECT_PATH="$1" - local ACTIVATE="$PROJECT_PATH/$_CA__VIRTUAL_ENV_NAME/bin/activate" - local NO_ENV="$PROJECT_PATH/$_CA__NO_ENV_SENTINEL" +_CA__GET_FULL_PATH() { + local PROJECT="$1" - [ -f $ACTIVATE ] && { - source $ACTIVATE - } || { - [ ! -f $NO_ENV ] && { - _CA__INTERACTIVE_ENV_SETUP $PROJECT_PATH && source $ACTIVATE - } - } -} + local PROJECT_ROOT_SHORT=$(dirname $PROJECT) + local PROJECT_NAME=$(basename $PROJECT) -_CA__ACTIVATE_CUSTOM_ENV() { - local PROJECT_PATH="$1" - local CUSTOM_ENV="$PROJECT_PATH/$_CA__CUSTOM_ENV_NAME" + local FULL_BASE_DIR=$(\ + echo $CA__DIRS \ + | _CA_MULTILINE \ + | grep "^.*/$PROJECT_ROOT_SHORT$" \ + ) - [ ! -f $CUSTOM_ENV ] && _CA__INIT_CUSTOM_ENV $PROJECT_PATH - source $CUSTOM_ENV + echo "$FULL_BASE_DIR/$PROJECT_NAME" } ##################################################################### -export _CA__ACTIVATE_LOADED=1 +_CA_DEACTIVATE() { + _CA_RESTORE_ENV + cd +} diff --git a/zsh/clone.zsh b/zsh/clone.zsh index 8d62b2f..e2a34fd 100644 --- a/zsh/clone.zsh +++ b/zsh/clone.zsh @@ -1,12 +1,7 @@ -##################################################################### +_CA_NEW() { IS_NEW=1 _CA_CLONE_OR_NEW $@; } +_CA_CLONE() { IS_NEW=0 _CA_CLONE_OR_NEW $@; } -[ ! $_CA__SETTINGS_LOADED ] && source "${0:a:h}/settings.zsh" -[ ! $_CA__HELPERS_LOADED ] && source "${0:a:h}/helpers.zsh" -[ ! $_CA__ACTIVATE_LOADED ] && source "${0:a:h}/activate.zsh" - -##################################################################### - -_CA__CLONE() { +_CA_CLONE_OR_NEW() { local BASE_DIR="$1" local REMOTE_TARGET="$2" local PROJECT_NAME="$3" @@ -16,7 +11,7 @@ _CA__CLONE() { [ ! $REMOTE_TARGET ] && REMOTE_TARGET=$(_CA__GET_REMOTE_TARGET) [ ! $REMOTE_TARGET ] && return 1 - echo $REMOTE_TARGET + [[ $REMOTE_TARGET =~ .git$ ]] || return 1 [ ! $PROJECT_NAME ] && PROJECT_NAME=$(_CA__GET_PROJECT_NAME $REMOTE_TARGET) @@ -24,80 +19,8 @@ _CA__CLONE() { local PROJECT_PATH="$BASE_DIR/$PROJECT_NAME" - [ -d $PROJECT_PATH ] && { - echo "project '$PROJECT_NAME' already exists in '$(basename $BASE_DIR)'" - return 1 - } - - mkdir $PROJECT_PATH - _CA__CLONE_SOURCE $REMOTE_TARGET $PROJECT_PATH $IS_NEW_PROJECT - _CA__INIT_CUSTOM_ENV $PROJECT_PATH + [[ $IS_NEW -eq 0 ]] && _CA__GIT_CLONE $PROJECT_PATH $REMOTE_TARGET + [[ $IS_NEW -eq 1 ]] && _CA__GIT_INIT $PROJECT_PATH $REMOTE_TARGET - _CA__ACTIVATE "$(basename $BASE_DIR)/$PROJECT_NAME" + _CA_ACTIVATE "$(basename $BASE_DIR)/$PROJECT_NAME" } - - -__CA__CLONE() { - local state - - _arguments \ - '1: :->base_dir' \ - '2: :->project_name' \ - '3: :->remote_target' \ - ':: :->arguments' \ - ; - - case $state in - base_dir ) - for dir in $CODE_ACTIVATOR__DIRS; do compadd $dir; done - ;; - project_name ) - ;; - remote_target ) - ;; - arguments ) ;; - esac -} - -##################################################################### - -_CA__CLONE_SOURCE() { - local REMOTE_TARGET="$1" - local PROJECT_PATH="$2" - local IS_NEW_PROJECT="$3" - local SOURCE_DIR="$PROJECT_PATH/$_CA__SOURCE_DIR_NAME" - - echo "trying to clone '$REMOTE_TARGET' to '$PROJECT_PATH'" - git clone "$REMOTE_TARGET" "$SOURCE_DIR" >/dev/null 2>&1 || { - echo "failed to clone '$REMOTE_TARGET'" - - [ ! $IS_NEW_PROJECT ] && { - printf "is this a new project? [y/N]" - _CA__READ_K yn - [[ $yn =~ ^[yY] ]] && IS_NEW_PROJECT=1 - } - - [ $IS_NEW_PROJECT ] && { - printf 'initializing project...' - { - mkdir $SOURCE_DIR \ - && cd $SOURCE_DIR \ - && git init \ - && touch .gitignore \ - && git add .gitignore \ - && git commit -m 'project init' \ - && git remote add $_CA__GET_GIT_MAIN_BRANCH $REMOTE_TARGET \ - && cd .. \ - ; - } >/dev/null >&1 && echo ' success :)' || echo ' failed :c' - } || { - rm -rf -- $PROJECT_PATH - echo 'exiting' - return 1 - } - } -} - -##################################################################### - -export _CA__CLONE_LOADED=1 diff --git a/zsh/custom-env-template.zsh b/zsh/custom-env-template.zsh deleted file mode 100644 index 4516131..0000000 --- a/zsh/custom-env-template.zsh +++ /dev/null @@ -1,13 +0,0 @@ -##################################################################### - -# Store initial PATH; allows for safe PATH manipulation for a particular project -export _CA__RESTORE_PATH="$PATH" - -# Store a list of project-specific environment variables; unset on env exit -export _CA__RESTORE_ENV=( - __CUSTOM_ENV_ACTIVE - ) - -##################################################################### - -export __CUSTOM_ENV_ACTIVE=69 diff --git a/zsh/env-activate.zsh b/zsh/env-activate.zsh new file mode 100644 index 0000000..5919860 --- /dev/null +++ b/zsh/env-activate.zsh @@ -0,0 +1,46 @@ +_CA_ACTIVATE_ENV() { + local PROJECT_PATH="$1" + local SOURCE_PATH="$PROJECT_PATH/$CA__SOURCE_DIR" + + _CA_RESTORE_ENV + + export _CA_ENV=$PROJECT_PATH/$CA__CUSTOM_ENV + + [ -d $SOURCE_PATH ] && { + _CA__CUSTOM_ENV_INIT + _CA__CUSTOM_ENV_SET_VARIABLES $PROJECT_PATH + _CA__ENV__ACTIVATE $PROJECT_PATH + cd $SOURCE_PATH + } || { + cd $PROJECT_PATH + } + + _CA__TMUX_WINDOW_RENAME $PROJECT +} + +_CA__ENV__ACTIVATE() { + local PROJECT_PATH="$1" + local NO_ENV="$PROJECT_PATH/$CA__NO_ENV" + + local ACTIVATE="$PROJECT_PATH/$CA__VIRTUAL_ENV/bin/activate" + [ -f $ACTIVATE ] && { + source $ACTIVATE + } || { + [ ! -f $NO_ENV ] && { + _CA__INTERACTIVE_ENV_SETUP $PROJECT_PATH && source $ACTIVATE + } + } +} + +##################################################################### + +_CA_RESTORE_ENV() { + deactivate >/dev/null 2>&1 + deactivate_node >/dev/null 2>&1 + + _CA__CUSTOM_ENV_UNSET_VARIABLES + + _CA__TMUX_RESTORE_WINDOW_NAME + + unset _CA_ENV +} diff --git a/zsh/env-custom.zsh b/zsh/env-custom.zsh new file mode 100644 index 0000000..18d2e22 --- /dev/null +++ b/zsh/env-custom.zsh @@ -0,0 +1,65 @@ +_CA__CUSTOM_ENV_SET_VARIABLES() { + [ $_CA_ENV ] && [ -f $_CA_ENV ] || return + local env_var + + for env_var in $(_CA__CUSTOM_ENV_GET_RESTORE_NAMES) + do + eval 'export __RESTORE__'$env_var'=$'$env_var + done + + eval "$(sed 's/__RESTORE__//g' $_CA_ENV)" +} + +_CA__CUSTOM_ENV_UNSET_VARIABLES() { + [ $_CA_ENV ] && [ -f $_CA_ENV ] || return + local var + + for var in $(_CA__CUSTOM_ENV_GET_VARIABLE_NAMES) + do + [[ ! $var =~ ^PATH$ ]] && unset $var + done + + for var in $(_CA__CUSTOM_ENV_GET_FUNCTION_NAMES) + do + unset -f $var + done + + for var in $(_CA__CUSTOM_ENV_GET_RESTORE_NAMES) + do + eval 'export '$var'=$__RESTORE__'$var + unset __RESTORE__$var + done + + for var in $(_CA__CUSTOM_ENV_GET_FUNCTION_NAMES) +} + +_CA__CUSTOM_ENV_INIT() { + [ ! -f $_CA_ENV ] && { + cp "$_CA/.env.zsh" $_CA_ENV + } +} + +##################################################################### + +_CA__CUSTOM_ENV_GET_VARIABLE_NAMES() { + grep -- '^[^#][^=]*=.' $_CA_ENV \ + | _CA_SED__VARIABLE_NAME | sort -u +} + +_CA__CUSTOM_ENV_GET_FUNCTION_NAMES() { + { + grep -- '^function ' $_CA_ENV | sed 's/function //' + grep -- '[^ #]*()' $_CA_ENV + } | sed 's/().*//' | sort -u +} + +_CA__CUSTOM_ENV_GET_RESTORE_NAMES() { + grep -- '^[^#][^=]*=.' $_CA_ENV | grep '__RESTORE__' \ + | _CA_SED__VARIABLE_NAME | sort -u +} + +##################################################################### + +_CA_SED__VARIABLE_NAME() { + sed 's/^export //; s/=.*//; s/__RESTORE__//g' +} diff --git a/zsh/env-setup.zsh b/zsh/env-setup.zsh new file mode 100644 index 0000000..20a56ac --- /dev/null +++ b/zsh/env-setup.zsh @@ -0,0 +1,74 @@ +_CA__INTERACTIVE_ENV_SETUP() { + local PROJECT_PATH="$1" + local NO_ENV="$PROJECT_PATH/$_CA__NO_ENV_SENTINEL" + + printf 'set up a virtual environment now? [(Y)es / (n)o / n(e)ver] ' + _CA_READ yn + + case $yn in + e ) touch $NO_ENV; return 1 ;; + n ) return 1 ;; + esac + + local ENV_VERSION=$(_CA__SELECT_VIRTUAL_ENV) + [ ! $ENV_VERSION ] && return 1 + + _CA__INIT_VIRTUAL_ENV $PROJECT_PATH $ENV_VERSION +} + +##################################################################### + +_CA__SELECT_VIRTUAL_ENV() { + local VERSION=$(_CA__GET_VIRTUALENVS | _CA_FZF 'select an environment') + + [[ $VERSION =~ node ]] && { + FIRST_PICK=$VERSION + + VERSION=$(nodeenv --list 2>&1 \ + | _CA_MULTILINE \ + | sort --reverse --human-numeric-sort \ + | _CA_FZF 'which node version?') + + [[ $VERSION == $FIRST_PICK ]] && return + } + + echo $VERSION +} + +##################################################################### + +_CA__INIT_VIRTUAL_ENV() { + local PROJECT_PATH="$1" + local ENV_VERSION="$2" + local ENV_PATH="$PROJECT_PATH/$_CA__VIRTUAL_ENV_NAME" + + [[ $ENV_VERSION =~ ^[0-9] ]]\ + && _CA__INIT_NODE_ENV $ENV_PATH $ENV_VERSION \ + || _CA__INIT_VIRTUALENV $ENV_PATH $ENV_VERSION \ + ; +} + +_CA__INIT_NODE_ENV() { + local ENV_PATH="$1" + local ENV_VERSION="$2" + + echo "setting up node env ($ENV_VERSION)" + nodeenv --node=$ENV_VERSION $ENV_PATH + echo "done" +} + +_CA__INIT_VIRTUALENV() { + local ENV_PATH="$1" + local ENV_VERSION="$2" + + echo "setting up virtualenv ($ENV_VERSION)" + virtualenv --python=$ENV_VERSION $ENV_PATH + echo "done" +} + +##################################################################### + +_CA__GET_VIRTUALENVS() { + whence -pm '*' | grep python | grep -v -- '-config$\|m$' + which node 2>/dev/null +} diff --git a/zsh/git.zsh b/zsh/git.zsh new file mode 100644 index 0000000..fef3c04 --- /dev/null +++ b/zsh/git.zsh @@ -0,0 +1,52 @@ +_CA__GIT_CLONE() { + local PROJECT_PATH="$1" + local REMOTE_TARGET="$2" + local SOURCE_DIR="$PROJECT_PATH/$CA__SOURCE_DIR" + + [ -d $PROJECT_PATH ] && { + echo "project '$PROJECT_NAME' already exists in '$(basename $BASE_DIR)'" + return 1 + } + + echo "trying to clone '$REMOTE_TARGET' to '$PROJECT_PATH'" + git clone "$REMOTE_TARGET" "$SOURCE_DIR" >/dev/null 2>&1 || { + _CA_ERROR "failed to clone '$REMOTE_TARGET'" + + printf "is this a new project? [y/N]" + _CA_READ yn + [[ $yn =~ ^[yY] ]] && { + _CA__GIT_INIT $PROJECT_PATH $REMOTE_TARGET || return 1 + } || { + printf "cleaning up code-activator artifacts..." + rmdir $PROJECT_PATH + echo 'done' + } + } +} + +_CA__GIT_INIT() { + local PROJECT_PATH="$1" + local REMOTE_TARGET="$2" + local SOURCE_DIR="$PROJECT_PATH/$CA__SOURCE_DIR" + + [ -d $PROJECT_PATH ] && { + echo "project '$PROJECT_NAME' already exists in '$(basename $BASE_DIR)'" + return 1 + } + + printf "initializing '$(basename $PROJECT_PATH)'..." + { + mkdir -p $SOURCE_DIR \ + && cd $SOURCE_DIR \ + && git init \ + && touch .gitignore \ + && git add .gitignore \ + && git commit -m '[INIT] repo initialized!' \ + && git remote add origin $REMOTE_TARGET \ + ; + } >/dev/null 2>&1 && echo 'success!' || { + echo 'failed to initialize repo; cleaning up' + [ ! -d $SOURCE_DIR/.git ] && rm -rf $PROJECT_PATH + return 1 + } +} diff --git a/zsh/helpers.zsh b/zsh/helpers.zsh index f8acb44..6ed9d70 100644 --- a/zsh/helpers.zsh +++ b/zsh/helpers.zsh @@ -1,37 +1,3 @@ -##################################################################### - -[ ! $_CA__SETTINGS_LOADED ] && source "${0:a:h}/settings.zsh" - -##################################################################### - -_CA__RESTORE_ENVIRONMENT() { - deactivate >/dev/null 2>&1 - deactivate_node >/dev/null 2>&1 - - [ $_CA__RESTORE_PATH ] && export PATH="$_CA__RESTORE_PATH" - [[ ${#_CA__RESTORE_ENV[@]} -gt 0 ]] \ - && for var in $_CA__RESTORE_ENV; do unset $var; done - - _CA__TMUX_RESTORE_WINDOW_NAME - - unset _CA__RESTORE_PATH _CA__RESTORE_ENV -} - -_CA__GET_FULL_PATH() { - local PROJECT="$1" - - local PROJECT_ROOT_SHORT=$(dirname $PROJECT) - local PROJECT_NAME=$(basename $PROJECT) - - local FULL_BASE_DIR=$(\ - echo $CODE_ACTIVATOR__DIRS \ - | _CA__SED_MULTILINE \ - | grep "^.*/$PROJECT_ROOT_SHORT$" \ - ) - - echo "$FULL_BASE_DIR/$PROJECT_NAME" -} - _CA__TMUX_WINDOW_RENAME() { local PROJECT="$1" @@ -43,137 +9,3 @@ _CA__TMUX_RESTORE_WINDOW_NAME() { [[ $TERM =~ ^tmux- ]] \ && tmux set automatic-rename on } - -_CA__SELECT_BASE_DIR() { - local BASE_NAMES=() - - for dir in $CODE_ACTIVATOR__DIRS - do - BASE_NAMES=($BASE_NAMES $(basename $dir)) - done - - echo $CODE_ACTIVATOR__DIRS | _CA__SED_MULTILINE \ - | grep $(\ - echo $BASE_NAMES \ - | _CA__SED_MULTILINE \ - | $_CA__FZF --prompt 'select base directory : ') -} - -_CA__GET_REMOTE_TARGET() { - local REMOTE_TARGET=$(\ - echo $CODE_ACTIVATOR__KNOWN_TARGETS \ - | _CA__SED_MULTILINE \ - | $_CA__FZF --print-query --prompt 'set a remote target : ' \ - | tail -1 \ - ) - - local FIRST_SELECTION="$REMOTE_TARGET" - - echo $CODE_ACTIVATOR__KNOWN_TARGETS | grep -q "$REMOTE_TARGET" && { - REMOTE_TARGET="$REMOTE_TARGET$(echo | $_CA__FZF --print-query --prompt "$REMOTE_TARGET" | tail -1)" - [[ $REMOTE_TARGET == $FIRST_SELECTION ]] && return '' - } - - echo $REMOTE_TARGET | grep -q '\.git$' || REMOTE_TARGET="$REMOTE_TARGET.git" - echo $REMOTE_TARGET -} - -_CA__GET_GIT_MAIN_BRANCH() { - { git config --list | grep 'init.defaultbranch' | sed 's/.*=//' \ - || echo 'main' - } 2>/dev/null -} - -_CA__GET_PROJECT_NAME() { - local REMOTE_TARGET="$1" - local DEFAULT_NAME=$(basename $REMOTE_TARGET | sed 's/\.git$//') - - echo $DEFAULT_NAME | $_CA__FZF --print-query --prompt 'set local project name : ' | tail -1 -} - -_CA__INTERACTIVE_ENV_SETUP() { - local PROJECT_PATH="$1" - local NO_ENV="$PROJECT_PATH/$_CA__NO_ENV_SENTINEL" - - printf 'set up a virtual environment now? [(Y)es / (n)o / n(e)ver] ' - _CA__READ_K yn - - case $yn in - e ) touch $NO_ENV; return 1 ;; - n ) return 1 ;; - esac - - local ENV_VERSION=$(_CA__SELECT_VIRTUAL_ENV) - [ ! $ENV_VERSION ] && return 1 - - _CA__INIT_VIRTUAL_ENV $PROJECT_PATH $ENV_VERSION -} - -_CA__SELECT_VIRTUAL_ENV() { - local VERSION=$(\ - echo "$(_CA__GET_PYTHON_IN_PATH)\n$(which node 2>/dev/null)" \ - | $_CA__FZF --prompt 'select a virtual environment : ' \ - ) - - [[ $VERSION =~ node$ ]] && { - FIRST_PICK=$VERSION - - VERSION=$(\ - nodeenv --list 2>&1 \ - | _CA__SED_MULTILINE \ - | $_CA__FZF --prompt 'select a node version : '\ - ) - - [[ $VERSION == $FIRST_PICK ]] && return - } - - echo $VERSION -} - -_CA__GET_PYTHON_IN_PATH() { - whence -pm '*' | grep python | grep -v -- '-config$\|m$' -} - -_CA__INIT_VIRTUAL_ENV() { - local PROJECT_PATH="$1" - local ENV_VERSION="$2" - local ENV_PATH="$PROJECT_PATH/$_CA__VIRTUAL_ENV_NAME" - - [[ $ENV_VERSION =~ ^[0-9] ]]\ - && _CA__INIT_NODE_ENV $ENV_PATH $ENV_VERSION \ - || _CA__INIT_VIRTUALENV $ENV_PATH $ENV_VERSION \ - ; -} - -_CA__INIT_NODE_ENV() { - local ENV_PATH="$1" - local ENV_VERSION="$2" - - echo "setting up node env ($ENV_VERSION)" - nodeenv --node=$ENV_VERSION $ENV_PATH - echo "done" -} - -_CA__INIT_VIRTUALENV() { - local ENV_PATH="$1" - local ENV_VERSION="$2" - - echo "setting up virtualenv ($ENV_VERSION)" - virtualenv --python=$ENV_VERSION $ENV_PATH - echo "done" -} - -_CA__INIT_CUSTOM_ENV() { - local PROJECT_PATH="$1" - - local CUSTOM_ENV_TEMPLATE="$_CA__CUSTOM_ENV_TEMPLATE" - local CUSTOM_ENV="$PROJECT_PATH/$_CA__CUSTOM_ENV_NAME" - - cp $CUSTOM_ENV_TEMPLATE $CUSTOM_ENV -} - -_CA__SED_MULTILINE() { sed 's/\s\+/\n/g'; } - -##################################################################### - -export _CA__HELPERS_LOADED=1 diff --git a/zsh/init.zsh b/zsh/init.zsh new file mode 100644 index 0000000..e69de29 diff --git a/zsh/interactive.zsh b/zsh/interactive.zsh new file mode 100644 index 0000000..9893740 --- /dev/null +++ b/zsh/interactive.zsh @@ -0,0 +1,45 @@ +_CA__SELECT_BASE_DIR() { + local BASE_NAMES=() + + for dir in $CA__DIRS; do BASE_NAMES+=$(basename $dir); done + BASE_NAMES=$(echo $BASE_NAMES | _CA_MULTILINE) + + SELECTION=$(echo $BASE_NAMES | _CA_FZF 'select base directory') + + [ $SELECTION ] && echo $CA__DIRS | _CA_MULTILINE | grep -- $SELECTION +} + +##################################################################### + +_CA__GET_REMOTE_TARGET() { + local REMOTE_TARGET=$(\ + echo $CA__KNOWN_TARGETS \ + | _CA_MULTILINE | _CA_FZF_PRINT 'set a remote target' \ + ) + + local FIRST_SELECTION="$REMOTE_TARGET" + + echo $CA__KNOWN_TARGETS | grep -q "$REMOTE_TARGET" && { + REMOTE_TARGET="$REMOTE_TARGET$(_CA__GET_REPOS $REMOTE_TARGET )" + [[ $REMOTE_TARGET == $FIRST_SELECTION ]] && return '' + } + + echo $REMOTE_TARGET | grep -q '\.git$' || REMOTE_TARGET="$REMOTE_TARGET.git" + echo $REMOTE_TARGET +} + +_CA__GET_REPOS() { + local REMOTE_TARGET="$1" + + # @TODO: github / bitbucket API integration + echo | _NOSEP=1 _CA_FZF_PRINT "$REMOTE_TARGET" +} + +##################################################################### + +_CA__GET_PROJECT_NAME() { + local REMOTE_TARGET="$1" + local DEFAULT_NAME=$(basename $REMOTE_TARGET | sed 's/\.git$//') + + echo $DEFAULT_NAME | _CA_FZF_PRINT 'set local project name' +} diff --git a/zsh/settings.zsh b/zsh/settings.zsh deleted file mode 100644 index d26b102..0000000 --- a/zsh/settings.zsh +++ /dev/null @@ -1,36 +0,0 @@ -##################################################################### - -[[ ${#CODE_ACTIVATOR__DIRS[@]} -eq 0 ]] && export CODE_ACTIVATOR__DIRS=("$HOME/Code") - -export CODE_ACTIVATOR__KNOWN_TARGETS=( - $CODE_ACTIVATOR__KNOWN_TARGETS - 'https://github.com/' - 'https://bitbucket.org/' - 'git@github.com:' - 'git@bitbucket.org:' - ) - -[ ! $CODE_ACTIVATOR__SHORTCUT ] && export CODE_ACTIVATOR__SHORTCUT='' -[ ! $CODE_ACTIVATOR__ALIAS ] && export CODE_ACTIVATOR__ALIAS='lkj' - -[ ! $CODE_ACTIVATOR__DISABLE_SHORTCUT ] && export CODE_ACTIVATOR__DISABLE_SHORTCUT=0 -[ ! $CODE_ACTIVATOR__DISABLE_ALIAS ] && export CODE_ACTIVATOR__DISABLE_ALIAS=0 - -##################################################################### - -export _CA__SOURCE_DIR_NAME='code' -export _CA__VIRTUAL_ENV_NAME='env' -export _CA__CUSTOM_ENV_NAME='custom-env' -export _CA__NO_ENV_SENTINEL='no-env' - -export _CA__CUSTOM_ENV_TEMPLATE="${0:a:h}/custom-env-template.zsh" - - -##################################################################### - -_CA__FZF=(fzf -i --height=50% --layout=reverse) -_CA__READ_K() { read -k $1; echo; } - -##################################################################### - -export _CA__SETTINGS_LOADED=1