From 53fb803740d962e8c617a67f71cd1dc6a85a1177 Mon Sep 17 00:00:00 2001 From: Toan Nguyen Date: Wed, 23 Jan 2019 03:05:32 -0800 Subject: [PATCH] OMB - Major Refactor (#39) * OMB - Major Refactor - Aliases and completions now works like plugins (need to enabled in .bashrc) - Removed the compatible check in spectrum.sh, OMB now works with Bash v3.x like the old days. - Removed core plugin, added those bash functions into base.sh and load during startup. - Updated OSH template for new installations - Added history config and few other stuff from #17 @TODO: Added a shell script to update old version of .bashrc to new one. * Fixed ShellCheck issues * Fixed ShellCheck issues --- aliases/chmod.aliases.sh | 11 + aliases/general.aliases.sh | 135 +-- aliases/ls.aliases.sh | 25 + aliases/misc.aliases.sh | 112 +++ {completion => completions}/apm.completion.sh | 0 .../awscli.completion.sh | 0 .../brew.completion.sh | 0 .../bundler.completion.sh | 0 .../capistrano.completion.sh | 0 .../composer.completion.sh | 0 .../conda.completion.sh | 0 .../defaults.completion.sh | 0 .../dirs.completion.sh | 0 .../django.completion.sh | 0 .../docker-compose.completion.sh | 0 .../docker-machine.completion.sh | 0 .../docker.completion.sh | 0 .../drush.completion.sh | 0 .../fabric-completion.sh | 0 {completion => completions}/gem.completion.sh | 0 {completion => completions}/gh.completion.sh | 0 {completion => completions}/git.completion.sh | 0 .../git_flow.completion.sh | 0 .../git_flow_avh.completion.sh | 0 {completion => completions}/go.completion.sh | 0 .../gradle.completion.sh | 0 .../grunt.completion.sh | 0 .../gulp.completion.sh | 0 .../homesick.completion.sh | 0 {completion => completions}/hub.completion.sh | 0 .../jboss7.completion.sh | 0 .../jungle.completion.sh | 0 .../kontena.completion.sh | 0 .../kubectl.completion.sh | 0 .../makefile.completion.sh | 0 .../maven.completion.sh | 0 {completion => completions}/npm.completion.sh | 0 {completion => completions}/nvm.completion.sh | 0 .../packer.completion.sh | 0 {completion => completions}/pip.completion.sh | 0 .../pip3.completion.sh | 0 .../projects.completion.sh | 0 .../rake.completion.sh | 0 .../salt.completion.sh | 0 .../sdkman.completion.sh | 0 {completion => completions}/ssh.completion.sh | 0 {completion => completions}/svn.completion.sh | 0 .../system.completion.sh | 0 .../terraform.completion.sh | 0 .../test_kitchen.completion.sh | 0 .../tmux.completion.sh | 0 .../todo.completion.sh | 0 .../vagrant.completion.sh | 0 .../vault.completion.sh | 0 .../virtualbox.completion.sh | 0 plugins/core/core.plugin.sh => lib/base.sh | 235 +++-- lib/bourne-shell.sh | 64 +- lib/directories.sh | 30 + lib/functions.sh | 4 +- lib/history.sh | 57 +- lib/misc.sh | 2 +- lib/mo.sh | 917 ++++++++++++++++++ lib/shopt.sh | 57 ++ lib/spectrum.sh | 76 +- lib/spinner.sh | 46 + lib/theme-and-appearance.sh | 1 + lib/utils.sh | 6 +- oh-my-bash.sh | 79 +- plugins/bu/bu.plugin.sh | 44 +- templates/bashrc.osh-template | 31 +- 70 files changed, 1548 insertions(+), 384 deletions(-) create mode 100644 aliases/chmod.aliases.sh create mode 100644 aliases/ls.aliases.sh create mode 100644 aliases/misc.aliases.sh rename {completion => completions}/apm.completion.sh (100%) rename {completion => completions}/awscli.completion.sh (100%) rename {completion => completions}/brew.completion.sh (100%) rename {completion => completions}/bundler.completion.sh (100%) rename {completion => completions}/capistrano.completion.sh (100%) rename {completion => completions}/composer.completion.sh (100%) rename {completion => completions}/conda.completion.sh (100%) rename {completion => completions}/defaults.completion.sh (100%) rename {completion => completions}/dirs.completion.sh (100%) rename {completion => completions}/django.completion.sh (100%) rename {completion => completions}/docker-compose.completion.sh (100%) rename {completion => completions}/docker-machine.completion.sh (100%) rename {completion => completions}/docker.completion.sh (100%) rename {completion => completions}/drush.completion.sh (100%) rename {completion => completions}/fabric-completion.sh (100%) rename {completion => completions}/gem.completion.sh (100%) rename {completion => completions}/gh.completion.sh (100%) rename {completion => completions}/git.completion.sh (100%) rename {completion => completions}/git_flow.completion.sh (100%) rename {completion => completions}/git_flow_avh.completion.sh (100%) rename {completion => completions}/go.completion.sh (100%) rename {completion => completions}/gradle.completion.sh (100%) rename {completion => completions}/grunt.completion.sh (100%) rename {completion => completions}/gulp.completion.sh (100%) rename {completion => completions}/homesick.completion.sh (100%) rename {completion => completions}/hub.completion.sh (100%) rename {completion => completions}/jboss7.completion.sh (100%) rename {completion => completions}/jungle.completion.sh (100%) rename {completion => completions}/kontena.completion.sh (100%) rename {completion => completions}/kubectl.completion.sh (100%) rename {completion => completions}/makefile.completion.sh (100%) rename {completion => completions}/maven.completion.sh (100%) rename {completion => completions}/npm.completion.sh (100%) rename {completion => completions}/nvm.completion.sh (100%) rename {completion => completions}/packer.completion.sh (100%) rename {completion => completions}/pip.completion.sh (100%) rename {completion => completions}/pip3.completion.sh (100%) rename {completion => completions}/projects.completion.sh (100%) rename {completion => completions}/rake.completion.sh (100%) rename {completion => completions}/salt.completion.sh (100%) rename {completion => completions}/sdkman.completion.sh (100%) rename {completion => completions}/ssh.completion.sh (100%) rename {completion => completions}/svn.completion.sh (100%) rename {completion => completions}/system.completion.sh (100%) rename {completion => completions}/terraform.completion.sh (100%) rename {completion => completions}/test_kitchen.completion.sh (100%) rename {completion => completions}/tmux.completion.sh (100%) rename {completion => completions}/todo.completion.sh (100%) rename {completion => completions}/vagrant.completion.sh (100%) rename {completion => completions}/vault.completion.sh (100%) rename {completion => completions}/virtualbox.completion.sh (100%) rename plugins/core/core.plugin.sh => lib/base.sh (56%) create mode 100644 lib/directories.sh create mode 100644 lib/mo.sh create mode 100644 lib/shopt.sh create mode 100644 lib/spinner.sh diff --git a/aliases/chmod.aliases.sh b/aliases/chmod.aliases.sh new file mode 100644 index 0000000..7b7a023 --- /dev/null +++ b/aliases/chmod.aliases.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# --------------------------------------------------------------------------- + +alias perm='stat --printf "%a %n \n "' # perm: Show permission of target in number +alias 000='chmod 000' # ---------- (nobody) +alias 640='chmod 640' # -rw-r----- (user: rw, group: r, other: -) +alias 644='chmod 644' # -rw-r--r-- (user: rw, group: r, other: -) +alias 755='chmod 755' # -rwxr-xr-x (user: rwx, group: rx, other: x) +alias 775='chmod 775' # -rwxrwxr-x (user: rwx, group: rwx, other: rx) +alias mx='chmod a+x' # ---x--x--x (user: --x, group: --x, other: --x) +alias ux='chmod u+x' # ---x------ (user: --x, group: -, other: -) diff --git a/aliases/general.aliases.sh b/aliases/general.aliases.sh index f17cfb7..d3acc63 100644 --- a/aliases/general.aliases.sh +++ b/aliases/general.aliases.sh @@ -3,18 +3,11 @@ # # Description: This file holds all general BASH aliases # -# Sections: -# 1. Make Terminal Better (remapping defaults and adding functionality) -# 2. File and Folder Management -# 3. Searching -# 4. Process Management -# 5. Networking -# 6. System Operations & Information -# 7. Date & Time Management -# 8. Web Development -# 9. +# For your own benefit, we won't load all aliases in the general, we will +# keep the very generic command here and enough for daily basis tasks. # -# X. Reminders & Notes +# If you are looking for the more sexier aliases, we suggest you take a look +# into other core alias files which installed by default. # # --------------------------------------------------------------------------- @@ -27,129 +20,11 @@ alias mv='mv -iv' # Preferred 'mv' implementation alias mkdir='mkdir -pv' # Preferred 'mkdir' implementation alias ll='ls -lAFh' # Preferred 'ls' implementation alias less='less -FSRXc' # Preferred 'less' implementation -alias nano='nano -W -$' # Preferred 'nano' implementation +alias nano='nano -W' # Preferred 'nano' implementation alias wget='wget -c' # Preferred 'wget' implementation (resume download) -alias cd..='cd ../' # Go back 1 directory level (for fast typers) -alias ..='cd ../' # Go back 1 directory level -alias ...='cd ../../' # Go back 2 directory levels -alias .3='cd ../../../' # Go back 3 directory levels -alias .4='cd ../../../../' # Go back 4 directory levels -alias .5='cd ../../../../../' # Go back 5 directory levels -alias .6='cd ../../../../../../' # Go back 6 directory levels -alias dud='du -d 1 -h' # Short and human-readable file listing -alias duf='du -sh *' # Short and human-readable directory listing -alias ~="cd ~" # ~: Go Home alias c='clear' # c: Clear terminal display alias path='echo -e ${PATH//:/\\n}' # path: Echo all executable Paths alias show_options='shopt' # Show_options: display bash options settings alias fix_stty='stty sane' # fix_stty: Restore terminal settings when screwed up alias cic='set completion-ignore-case On' # cic: Make tab-completion case-insensitive -alias h='fc -l 1 | grep $1' # h: Find an executed command in .bash_history alias src='source ~/.bashrc' # src: Reload .bashrc file - -# lr: Full Recursive Directory Listing -# ------------------------------------------ - alias lr='ls -R | grep ":$" | sed -e '\''s/:$//'\'' -e '\''s/[^-][^\/]*\//--/g'\'' -e '\''s/^/ /'\'' -e '\''s/-/|/'\'' | less' - - -# ------------------------------- -# 2. FILE AND FOLDER MANAGEMENT -# ------------------------------- - -alias numFiles='echo $(ls -1 | wc -l)' # numFiles: Count of non-hidden files in current dir -alias make1mb='truncate -s 1m ./1MB.dat' # make1mb: Creates a file of 1mb size (all zeros) -alias make5mb='truncate -s 5m ./5MB.dat' # make5mb: Creates a file of 5mb size (all zeros) -alias make10mb='truncate -s 10m ./10MB.dat' # make10mb: Creates a file of 10mb size (all zeros) - - -# --------------------------- -# 3. SEARCHING -# --------------------------- - -alias qfind="find . -name " # qfind: Quickly search for file - - -# --------------------------- -# 4. PROCESS MANAGEMENT -# --------------------------- - -# memHogsTop, memHogsPs: Find memory hogs -# ----------------------------------------------------- - alias memHogsTop='top -l 1 -o rsize | head -20' - alias memHogsPs='ps wwaxm -o pid,stat,vsize,rss,time,command | head -10' - -# cpuHogs: Find CPU hogs -# ----------------------------------------------------- - alias cpu_hogs='ps wwaxr -o pid,stat,%cpu,time,command | head -10' - -# topForever: Continual 'top' listing (every 10 seconds) -# ----------------------------------------------------- - alias topForever='top -l 9999999 -s 10 -o cpu' - -# ttop: Recommended 'top' invocation to minimize resources -# ------------------------------------------------------------ -# Taken from this macosxhints article -# http://www.macosxhints.com/article.php?story=20060816123853639 -# ------------------------------------------------------------ - alias ttop="top -R -F -s 10 -o rsize" - - -# --------------------------- -# 5. NETWORKING -# --------------------------- - -alias netCons='lsof -i' # netCons: Show all open TCP/IP sockets -alias lsock='sudo /usr/sbin/lsof -i -P' # lsock: Display open sockets -alias lsockU='sudo /usr/sbin/lsof -nP | grep UDP' # lsockU: Display only open UDP sockets -alias lsockT='sudo /usr/sbin/lsof -nP | grep TCP' # lsockT: Display only open TCP sockets -alias ipInfo0='ifconfig getpacket en0' # ipInfo0: Get info on connections for en0 -alias ipInfo1='ifconfig getpacket en1' # ipInfo1: Get info on connections for en1 -alias openPorts='sudo lsof -i | grep LISTEN' # openPorts: All listening connections -alias showBlocked='sudo ipfw list' # showBlocked: All ipfw rules inc/ blocked IPs - - -# --------------------------------------- -# 6. SYSTEMS OPERATIONS & INFORMATION -# --------------------------------------- - -alias mountReadWrite='/sbin/mount -uw /' # mountReadWrite: For use when booted into single-user -alias perm='stat --printf "%a %n \n "' # perm: Show permission of target in number -alias 000='chmod 000' # ---------- (no fucking permissions) -alias 640='chmod 640' # -rw-r----- (user: rw, group: r, other: -) -alias 644='chmod 644' # -rw-r--r-- (user: rw, group: r, other: -) -alias 755='chmod 755' # -rwxr-xr-x (user: rwx, group: rx, other: x) -alias 775='chmod 775' # -rwxrwxr-x (user: rwx, group: rwx, other: rx) -alias mx='chmod a+x' # ---x--x--x (user: --x, group: --x, other: --x) -alias ux='chmod u+x' # ---x------ (user: --x, group: -, other: -) - - -# --------------------------------------- -# 7. DATE & TIME MANAGEMENT -# --------------------------------------- - -alias bdate="date '+%a, %b %d %Y %T %Z'" -alias cal='cal -3' -alias da='date "+%Y-%m-%d %A %T %Z"' -alias daysleft='echo "There are $(($(date +%j -d"Dec 31, $(date +%Y)")-$(date +%j))) left in year $(date +%Y)."' -alias epochtime='date +%s' -alias mytime='date +%H:%M:%S' -alias secconvert='date -d@1234567890' -alias stamp='date "+%Y%m%d%a%H%M"' -alias timestamp='date "+%Y%m%dT%H%M%S"' -alias today='date +"%A, %B %-d, %Y"' -alias weeknum='date +%V' - - -# --------------------------------------- -# 8. WEB DEVELOPMENT -# --------------------------------------- - -alias apacheEdit='sudo edit /etc/httpd/httpd.conf' # apacheEdit: Edit httpd.conf -alias apacheRestart='sudo apachectl graceful' # apacheRestart: Restart Apache -alias editHosts='sudo edit /etc/hosts' # editHosts: Edit /etc/hosts file -alias herr='tail /var/log/httpd/error_log' # herr: Tails HTTP error logs -alias apacheLogs="less +F /var/log/apache2/error_log" # Apachelogs: Shows apache error logs - -# --------------------------------------- -# 9. REMINDERS & NOTES -# --------------------------------------- diff --git a/aliases/ls.aliases.sh b/aliases/ls.aliases.sh new file mode 100644 index 0000000..7523e8e --- /dev/null +++ b/aliases/ls.aliases.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# --------------------------------------------------------------------------- + +# Directory Listing aliases +alias dir='ls -hFx' +alias l.='ls -d .* --color=tty' # short listing, only hidden files - .* +alias l='ls -lathF' # long, sort by newest to oldest +alias L='ls -latrhF' # long, sort by oldest to newest +alias la='ls -Al' # show hidden files +alias lc='ls -lcr' # sort by change time +alias lk='ls -lSr' # sort by size +alias lh='ls -lSrh' # sort by size human readable +alias lm='ls -al | more' # pipe through 'more' +alias lo='ls -laSFh' # sort by size largest to smallest +alias lr='ls -lR' # recursive ls +alias lt='ls -ltr' # sort by date +alias lu='ls -lur' # sort by access time + + +# lr: Full Recursive Directory Listing +# ------------------------------------------ +alias lr='ls -R | grep ":$" | sed -e '\''s/:$//'\'' -e '\''s/[^-][^\/]*\//--/g'\'' -e '\''s/^/ /'\'' -e '\''s/-/|/'\'' | less' + +alias dud='du -d 1 -h' # Short and human-readable file listing +alias duf='du -sh *' # Short and human-readable directory listing diff --git a/aliases/misc.aliases.sh b/aliases/misc.aliases.sh new file mode 100644 index 0000000..208bd3f --- /dev/null +++ b/aliases/misc.aliases.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# --------------------------------------------------------------------------- +# +# Description: This file holds many useful BASH aliases and save our lives! +# +# Sections: +# 1. File and Folder Management +# 2. Searching +# 3. Process Management +# 4. Networking +# 5. System Operations & Information +# 6. Date & Time Management +# 7. Web Development +# 8. +# +# X. Reminders & Notes +# +# --------------------------------------------------------------------------- + +# ------------------------------- +# 1. FILE AND FOLDER MANAGEMENT +# ------------------------------- + +alias numFiles='echo $(ls -1 | wc -l)' # numFiles: Count of non-hidden files in current dir +alias make1mb='truncate -s 1m ./1MB.dat' # make1mb: Creates a file of 1mb size (all zeros) +alias make5mb='truncate -s 5m ./5MB.dat' # make5mb: Creates a file of 5mb size (all zeros) +alias make10mb='truncate -s 10m ./10MB.dat' # make10mb: Creates a file of 10mb size (all zeros) + + +# --------------------------- +# 2. SEARCHING +# --------------------------- + +alias qfind="find . -name " # qfind: Quickly search for file + + +# --------------------------- +# 3. PROCESS MANAGEMENT +# --------------------------- + +# memHogsTop, memHogsPs: Find memory hogs +# ----------------------------------------------------- + alias memHogsTop='top -l 1 -o rsize | head -20' + alias memHogsPs='ps wwaxm -o pid,stat,vsize,rss,time,command | head -10' + +# cpuHogs: Find CPU hogs +# ----------------------------------------------------- + alias cpu_hogs='ps wwaxr -o pid,stat,%cpu,time,command | head -10' + +# topForever: Continual 'top' listing (every 10 seconds) +# ----------------------------------------------------- + alias topForever='top -l 9999999 -s 10 -o cpu' + +# ttop: Recommended 'top' invocation to minimize resources +# ------------------------------------------------------------ +# Taken from this macosxhints article +# http://www.macosxhints.com/article.php?story=20060816123853639 +# ------------------------------------------------------------ + alias ttop="top -R -F -s 10 -o rsize" + + +# --------------------------- +# 4. NETWORKING +# --------------------------- + +alias netCons='lsof -i' # netCons: Show all open TCP/IP sockets +alias lsock='sudo /usr/sbin/lsof -i -P' # lsock: Display open sockets +alias lsockU='sudo /usr/sbin/lsof -nP | grep UDP' # lsockU: Display only open UDP sockets +alias lsockT='sudo /usr/sbin/lsof -nP | grep TCP' # lsockT: Display only open TCP sockets +alias ipInfo0='ifconfig getpacket en0' # ipInfo0: Get info on connections for en0 +alias ipInfo1='ifconfig getpacket en1' # ipInfo1: Get info on connections for en1 +alias openPorts='sudo lsof -i | grep LISTEN' # openPorts: All listening connections +alias showBlocked='sudo ipfw list' # showBlocked: All ipfw rules inc/ blocked IPs + + +# --------------------------------------- +# 5. SYSTEMS OPERATIONS & INFORMATION +# --------------------------------------- + +alias mountReadWrite='/sbin/mount -uw /' # mountReadWrite: For use when booted into single-user + + +# --------------------------------------- +# 6. DATE & TIME MANAGEMENT +# --------------------------------------- + +alias bdate="date '+%a, %b %d %Y %T %Z'" +alias cal3='cal -3' +alias da='date "+%Y-%m-%d %A %T %Z"' +alias daysleft='echo "There are $(($(date +%j -d"Dec 31, $(date +%Y)")-$(date +%j))) left in year $(date +%Y)."' +alias epochtime='date +%s' +alias mytime='date +%H:%M:%S' +alias secconvert='date -d@1234567890' +alias stamp='date "+%Y%m%d%a%H%M"' +alias timestamp='date "+%Y%m%dT%H%M%S"' +alias today='date +"%A, %B %-d, %Y"' +alias weeknum='date +%V' + + +# --------------------------------------- +# 8. WEB DEVELOPMENT +# --------------------------------------- + +alias apacheEdit='sudo edit /etc/httpd/httpd.conf' # apacheEdit: Edit httpd.conf +alias apacheRestart='sudo apachectl graceful' # apacheRestart: Restart Apache +alias editHosts='sudo edit /etc/hosts' # editHosts: Edit /etc/hosts file +alias herr='tail /var/log/httpd/error_log' # herr: Tails HTTP error logs +alias apacheLogs="less +F /var/log/apache2/error_log" # Apachelogs: Shows apache error logs + +# --------------------------------------- +# 9. REMINDERS & NOTES +# --------------------------------------- diff --git a/completion/apm.completion.sh b/completions/apm.completion.sh similarity index 100% rename from completion/apm.completion.sh rename to completions/apm.completion.sh diff --git a/completion/awscli.completion.sh b/completions/awscli.completion.sh similarity index 100% rename from completion/awscli.completion.sh rename to completions/awscli.completion.sh diff --git a/completion/brew.completion.sh b/completions/brew.completion.sh similarity index 100% rename from completion/brew.completion.sh rename to completions/brew.completion.sh diff --git a/completion/bundler.completion.sh b/completions/bundler.completion.sh similarity index 100% rename from completion/bundler.completion.sh rename to completions/bundler.completion.sh diff --git a/completion/capistrano.completion.sh b/completions/capistrano.completion.sh similarity index 100% rename from completion/capistrano.completion.sh rename to completions/capistrano.completion.sh diff --git a/completion/composer.completion.sh b/completions/composer.completion.sh similarity index 100% rename from completion/composer.completion.sh rename to completions/composer.completion.sh diff --git a/completion/conda.completion.sh b/completions/conda.completion.sh similarity index 100% rename from completion/conda.completion.sh rename to completions/conda.completion.sh diff --git a/completion/defaults.completion.sh b/completions/defaults.completion.sh similarity index 100% rename from completion/defaults.completion.sh rename to completions/defaults.completion.sh diff --git a/completion/dirs.completion.sh b/completions/dirs.completion.sh similarity index 100% rename from completion/dirs.completion.sh rename to completions/dirs.completion.sh diff --git a/completion/django.completion.sh b/completions/django.completion.sh similarity index 100% rename from completion/django.completion.sh rename to completions/django.completion.sh diff --git a/completion/docker-compose.completion.sh b/completions/docker-compose.completion.sh similarity index 100% rename from completion/docker-compose.completion.sh rename to completions/docker-compose.completion.sh diff --git a/completion/docker-machine.completion.sh b/completions/docker-machine.completion.sh similarity index 100% rename from completion/docker-machine.completion.sh rename to completions/docker-machine.completion.sh diff --git a/completion/docker.completion.sh b/completions/docker.completion.sh similarity index 100% rename from completion/docker.completion.sh rename to completions/docker.completion.sh diff --git a/completion/drush.completion.sh b/completions/drush.completion.sh similarity index 100% rename from completion/drush.completion.sh rename to completions/drush.completion.sh diff --git a/completion/fabric-completion.sh b/completions/fabric-completion.sh similarity index 100% rename from completion/fabric-completion.sh rename to completions/fabric-completion.sh diff --git a/completion/gem.completion.sh b/completions/gem.completion.sh similarity index 100% rename from completion/gem.completion.sh rename to completions/gem.completion.sh diff --git a/completion/gh.completion.sh b/completions/gh.completion.sh similarity index 100% rename from completion/gh.completion.sh rename to completions/gh.completion.sh diff --git a/completion/git.completion.sh b/completions/git.completion.sh similarity index 100% rename from completion/git.completion.sh rename to completions/git.completion.sh diff --git a/completion/git_flow.completion.sh b/completions/git_flow.completion.sh similarity index 100% rename from completion/git_flow.completion.sh rename to completions/git_flow.completion.sh diff --git a/completion/git_flow_avh.completion.sh b/completions/git_flow_avh.completion.sh similarity index 100% rename from completion/git_flow_avh.completion.sh rename to completions/git_flow_avh.completion.sh diff --git a/completion/go.completion.sh b/completions/go.completion.sh similarity index 100% rename from completion/go.completion.sh rename to completions/go.completion.sh diff --git a/completion/gradle.completion.sh b/completions/gradle.completion.sh similarity index 100% rename from completion/gradle.completion.sh rename to completions/gradle.completion.sh diff --git a/completion/grunt.completion.sh b/completions/grunt.completion.sh similarity index 100% rename from completion/grunt.completion.sh rename to completions/grunt.completion.sh diff --git a/completion/gulp.completion.sh b/completions/gulp.completion.sh similarity index 100% rename from completion/gulp.completion.sh rename to completions/gulp.completion.sh diff --git a/completion/homesick.completion.sh b/completions/homesick.completion.sh similarity index 100% rename from completion/homesick.completion.sh rename to completions/homesick.completion.sh diff --git a/completion/hub.completion.sh b/completions/hub.completion.sh similarity index 100% rename from completion/hub.completion.sh rename to completions/hub.completion.sh diff --git a/completion/jboss7.completion.sh b/completions/jboss7.completion.sh similarity index 100% rename from completion/jboss7.completion.sh rename to completions/jboss7.completion.sh diff --git a/completion/jungle.completion.sh b/completions/jungle.completion.sh similarity index 100% rename from completion/jungle.completion.sh rename to completions/jungle.completion.sh diff --git a/completion/kontena.completion.sh b/completions/kontena.completion.sh similarity index 100% rename from completion/kontena.completion.sh rename to completions/kontena.completion.sh diff --git a/completion/kubectl.completion.sh b/completions/kubectl.completion.sh similarity index 100% rename from completion/kubectl.completion.sh rename to completions/kubectl.completion.sh diff --git a/completion/makefile.completion.sh b/completions/makefile.completion.sh similarity index 100% rename from completion/makefile.completion.sh rename to completions/makefile.completion.sh diff --git a/completion/maven.completion.sh b/completions/maven.completion.sh similarity index 100% rename from completion/maven.completion.sh rename to completions/maven.completion.sh diff --git a/completion/npm.completion.sh b/completions/npm.completion.sh similarity index 100% rename from completion/npm.completion.sh rename to completions/npm.completion.sh diff --git a/completion/nvm.completion.sh b/completions/nvm.completion.sh similarity index 100% rename from completion/nvm.completion.sh rename to completions/nvm.completion.sh diff --git a/completion/packer.completion.sh b/completions/packer.completion.sh similarity index 100% rename from completion/packer.completion.sh rename to completions/packer.completion.sh diff --git a/completion/pip.completion.sh b/completions/pip.completion.sh similarity index 100% rename from completion/pip.completion.sh rename to completions/pip.completion.sh diff --git a/completion/pip3.completion.sh b/completions/pip3.completion.sh similarity index 100% rename from completion/pip3.completion.sh rename to completions/pip3.completion.sh diff --git a/completion/projects.completion.sh b/completions/projects.completion.sh similarity index 100% rename from completion/projects.completion.sh rename to completions/projects.completion.sh diff --git a/completion/rake.completion.sh b/completions/rake.completion.sh similarity index 100% rename from completion/rake.completion.sh rename to completions/rake.completion.sh diff --git a/completion/salt.completion.sh b/completions/salt.completion.sh similarity index 100% rename from completion/salt.completion.sh rename to completions/salt.completion.sh diff --git a/completion/sdkman.completion.sh b/completions/sdkman.completion.sh similarity index 100% rename from completion/sdkman.completion.sh rename to completions/sdkman.completion.sh diff --git a/completion/ssh.completion.sh b/completions/ssh.completion.sh similarity index 100% rename from completion/ssh.completion.sh rename to completions/ssh.completion.sh diff --git a/completion/svn.completion.sh b/completions/svn.completion.sh similarity index 100% rename from completion/svn.completion.sh rename to completions/svn.completion.sh diff --git a/completion/system.completion.sh b/completions/system.completion.sh similarity index 100% rename from completion/system.completion.sh rename to completions/system.completion.sh diff --git a/completion/terraform.completion.sh b/completions/terraform.completion.sh similarity index 100% rename from completion/terraform.completion.sh rename to completions/terraform.completion.sh diff --git a/completion/test_kitchen.completion.sh b/completions/test_kitchen.completion.sh similarity index 100% rename from completion/test_kitchen.completion.sh rename to completions/test_kitchen.completion.sh diff --git a/completion/tmux.completion.sh b/completions/tmux.completion.sh similarity index 100% rename from completion/tmux.completion.sh rename to completions/tmux.completion.sh diff --git a/completion/todo.completion.sh b/completions/todo.completion.sh similarity index 100% rename from completion/todo.completion.sh rename to completions/todo.completion.sh diff --git a/completion/vagrant.completion.sh b/completions/vagrant.completion.sh similarity index 100% rename from completion/vagrant.completion.sh rename to completions/vagrant.completion.sh diff --git a/completion/vault.completion.sh b/completions/vault.completion.sh similarity index 100% rename from completion/vault.completion.sh rename to completions/vault.completion.sh diff --git a/completion/virtualbox.completion.sh b/completions/virtualbox.completion.sh similarity index 100% rename from completion/virtualbox.completion.sh rename to completions/virtualbox.completion.sh diff --git a/plugins/core/core.plugin.sh b/lib/base.sh similarity index 56% rename from plugins/core/core.plugin.sh rename to lib/base.sh index 3a100d4..9403233 100644 --- a/plugins/core/core.plugin.sh +++ b/lib/base.sh @@ -24,35 +24,46 @@ # mcd: Makes new Dir and jumps inside # -------------------------------------------------------------------- - mcd () { mkdir -p -- "$*" ; builtin cd -- "$*" ; } + mcd () { mkdir -p -- "$*" ; cd -- "$*" || exit ; } # mans: Search manpage given in agument '1' for term given in argument '2' (case insensitive) # displays paginated result with colored search terms and two lines surrounding each hit. # Example: mans mplayer codec # -------------------------------------------------------------------- - mans () { man $1 | grep -iC2 --color=always $2 | less ; } + mans () { man "$1" | grep -iC2 --color=always "$2" | less ; } # showa: to remind yourself of an alias (given some part of it) # ------------------------------------------------------------ - showa () { /usr/bin/grep --color=always -i -a1 $@ ~/Library/init/bash/aliases.bash | grep -v '^\s*$' | less -FSRXc ; } + showa () { /usr/bin/grep --color=always -i -a1 "$@" ~/Library/init/bash/aliases.bash | grep -v '^\s*$' | less -FSRXc ; } # quiet: mute output of a command # ------------------------------------------------------------ quiet () { - $* &> /dev/null & + "$*" &> /dev/null & } # lsgrep: search through directory contents with grep # ------------------------------------------------------------ +# shellcheck disable=SC2010 lsgrep () { ls | grep "$*" ; } # banish-cookies: redirect .adobe and .macromedia files to /dev/null # ------------------------------------------------------------ - banish-cookies () - { - rm -r ~/.macromedia ~/.adobe - ln -s /dev/null ~/.adobe - ln -s /dev/null ~/.macromedia + banish-cookies () { + rm -r ~/.macromedia ~/.adobe + ln -s /dev/null ~/.adobe + ln -s /dev/null ~/.macromedia + } + +# show the n most used commands. defaults to 10 +# ------------------------------------------------------------ + hstats() { + if [[ $# -lt 1 ]]; then + NUM=10 + else + NUM=${1} + fi + history | awk '{print $2}' | sort | uniq -c | sort -rn | head -"$NUM" } @@ -65,57 +76,72 @@ zipf () { zip -r "$1".zip "$1" ; } # zipf: To create a ZIP arc # extract: Extract most know archives with one command # --------------------------------------------------------- extract () { - if [ -f $1 ] ; then - case $1 in - *.tar.bz2) tar xjf $1 ;; - *.tar.gz) tar xzf $1 ;; - *.bz2) bunzip2 $1 ;; - *.rar) unrar e $1 ;; - *.gz) gunzip $1 ;; - *.tar) tar xf $1 ;; - *.tbz2) tar xjf $1 ;; - *.tgz) tar xzf $1 ;; - *.zip) unzip $1 ;; - *.Z) uncompress $1 ;; - *.7z) 7z x $1 ;; - *) echo "'$1' cannot be extracted via extract()" ;; - esac - else - echo "'$1' is not a valid file" - fi + if [ -f "$1" ] ; then + case "$1" in + *.tar.bz2) tar xjf "$1" ;; + *.tar.gz) tar xzf "$1" ;; + *.bz2) bunzip2 "$1" ;; + *.rar) unrar e "$1" ;; + *.gz) gunzip "$1" ;; + *.tar) tar xf "$1" ;; + *.tbz2) tar xjf "$1" ;; + *.tgz) tar xzf "$1" ;; + *.zip) unzip "$1" ;; + *.Z) uncompress "$1" ;; + *.7z) 7z x "$1" ;; + *) echo "'$1' cannot be extracted via extract()" ;; + esac + else + echo "'$1' is not a valid file" + fi } # buf: back up file with timestamp # --------------------------------------------------------- buf () { - local filename=$1 - local filetime=$(date +%Y%m%d_%H%M%S) - cp -a "${filename}" "${filename}_${filetime}" + local filename filetime + filename=$1 + filetime=$(date +%Y%m%d_%H%M%S) + cp -a "${filename}" "${filename}_${filetime}" } # del: move files to hidden folder in tmp, that gets cleared on each reboot # --------------------------------------------------------- del() { - mkdir -p /tmp/.trash && mv "$@" /tmp/.trash; + mkdir -p /tmp/.trash && mv "$@" /tmp/.trash; } # mkiso: creates iso from current dir in the parent dir (unless defined) # --------------------------------------------------------- mkiso () { - if type "mkisofs" > /dev/null; then - [ -z ${1+x} ] && local isoname=${PWD##*/} || local isoname=$1 - [ -z ${2+x} ] && local destpath=../ || local destpath=$2 - [ -z ${3+x} ] && local srcpath=${PWD} || local srcpath=$3 - - if [ ! -f "${destpath}${isoname}.iso" ]; then - echo "writing ${isoname}.iso to ${destpath} from ${srcpath}" - mkisofs -V ${isoname} -iso-level 3 -r -o "${destpath}${isoname}.iso" "${srcpath}" - else - echo "${destpath}${isoname}.iso already exists" - fi + if type "mkisofs" > /dev/null; then + if [ -z ${1+x} ]; then + local isoname=${PWD##*/} else - echo "mkisofs cmd does not exist, please install cdrtools" + local isoname=$1 fi + + if [ -z ${2+x} ]; then + local destpath=../ + else + local destpath=$2 + fi + + if [ -z ${3+x} ]; then + local srcpath=${PWD} + else + local srcpath=$3 + fi + + if [ ! -f "${destpath}${isoname}.iso" ]; then + echo "writing ${isoname}.iso to ${destpath} from ${srcpath}" + mkisofs -V "${isoname}" -iso-level 3 -r -o "${destpath}${isoname}.iso" "${srcpath}" + else + echo "${destpath}${isoname}.iso already exists" + fi + else + echo "mkisofs cmd does not exist, please install cdrtools" + fi } @@ -124,8 +150,17 @@ zipf () { zip -r "$1".zip "$1" ; } # zipf: To create a ZIP arc # --------------------------- ff () { /usr/bin/find . -name "$@" ; } # ff: Find file under the current directory +# shellcheck disable=SC2145 ffs () { /usr/bin/find . -name "$@"'*' ; } # ffs: Find file whose name starts with a given string +# shellcheck disable=SC2145 ffe () { /usr/bin/find . -name '*'"$@" ; } # ffe: Find file whose name ends with a given string +bigfind() { + if [[ $# -lt 1 ]]; then + echo_warn "Usage: bigfind DIRECTORY" + return + fi + du -a "$1" | sort -n -r | head -n 10 +} # --------------------------- @@ -142,7 +177,7 @@ ffe () { /usr/bin/find . -name '*'"$@" ; } # ffe: Find file whose name end # my_ps: List processes owned by my user: # ------------------------------------------------------------ - my_ps() { ps $@ -u $USER -o pid,%cpu,%mem,start,time,bsdtime,command ; } + my_ps() { ps "$@" -u "$USER" -o pid,%cpu,%mem,start,time,bsdtime,command ; } # --------------------------- @@ -152,42 +187,42 @@ ffe () { /usr/bin/find . -name '*'"$@" ; } # ffe: Find file whose name end # ips: display all ip addresses for this host # ------------------------------------------------------------------- ips () { - if command -v ifconfig &>/dev/null - then - ifconfig | awk '/inet /{ print $2 }' - elif command -v ip &>/dev/null - then - ip addr | grep -oP 'inet \K[\d.]+' - else - echo "You don't have ifconfig or ip command installed!" - fi + if command -v ifconfig &>/dev/null + then + ifconfig | awk '/inet /{ print $2 }' + elif command -v ip &>/dev/null + then + ip addr | grep -oP 'inet \K[\d.]+' + else + echo "You don't have ifconfig or ip command installed!" + fi } # down4me: checks whether a website is down for you, or everybody # ------------------------------------------------------------------- down4me () { - curl -s "http://www.downforeveryoneorjustme.com/$1" | sed '/just you/!d;s/<[^>]*>//g' + curl -s "http://www.downforeveryoneorjustme.com/$1" | sed '/just you/!d;s/<[^>]*>//g' } # myip: displays your ip address, as seen by the Internet # ------------------------------------------------------------------- myip () { - res=$(curl -s checkip.dyndns.org | grep -Eo '[0-9\.]+') - echo -e "Your public IP is: ${echo_bold_green} $res ${echo_normal}" + res=$(curl -s checkip.dyndns.org | grep -Eo '[0-9\.]+') + echo -e "Your public IP is: ${echo_bold_green} $res ${echo_normal}" } # ii: display useful host related informaton # ------------------------------------------------------------------- ii() { - echo -e "\\nYou are logged on ${red}$HOST" - echo -e "\\nAdditionnal information:$NC " ; uname -a - echo -e "\\n${red}Users logged on:$NC " ; w -h - echo -e "\\n${red}Current date :$NC " ; date - echo -e "\\n${red}Machine stats :$NC " ; uptime - [[ "$OSTYPE" == darwin* ]] && echo -e "\\n${red}Current network location :$NC " ; scselect - echo -e "\\n${red}Public facing IP Address :$NC " ;myip - [[ "$OSTYPE" == darwin* ]] && echo -e "\\n${red}DNS Configuration:$NC " ; scutil --dns - echo + echo -e "\\nYou are logged on ${red}$HOST" + echo -e "\\nAdditionnal information:$NC " ; uname -a + echo -e "\\n${red}Users logged on:$NC " ; w -h + echo -e "\\n${red}Current date :$NC " ; date + echo -e "\\n${red}Machine stats :$NC " ; uptime + [[ "$OSTYPE" == darwin* ]] && echo -e "\\n${red}Current network location :$NC " ; scselect + echo -e "\\n${red}Public facing IP Address :$NC " ;myip + [[ "$OSTYPE" == darwin* ]] && echo -e "\\n${red}DNS Configuration:$NC " ; scutil --dns + echo } @@ -197,59 +232,65 @@ ffe () { /usr/bin/find . -name '*'"$@" ; } # ffe: Find file whose name end # batch_chmod: Batch chmod for all files & sub-directories in the current one # ------------------------------------------------------------------- - batch_chmod() - { - e_header "Restore all files and sub-directories permissions..." - progress 10 "Re-index file list..." - find . -type d -print0 | xargs -0 chmod 0755 && progress 40 "[1/2]" - find . -type f -print0 | xargs -0 chmod 0644 && progress 90 "[2/2]" - progress 100 "Done!" - echo "$(tput sgr0)" + batch_chmod() { + echo -ne "${echo_bold_blue}Applying 0755 permission for all directories..." + (find . -type d -print0 | xargs -0 chmod 0755) & + spinner + echo -ne "${echo_normal}" + + echo -ne "${echo_bold_blue}Applying 0644 permission for all files..." + (find . -type f -print0 | xargs -0 chmod 0644) & + spinner + echo -ne "${echo_normal}" } # usage: disk usage per directory, in Mac OS X and Linux # ------------------------------------------------------------------- usage () { - if [ $(uname) = "Darwin" ]; then - if [ -n "$1" ]; then - du -hd 1 "$1" - else - du -hd 1 - fi - elif [ $(uname) = "Linux" ]; then - if [ -n "$1" ]; then - du -h --max-depth=1 "$1" - else - du -h --max-depth=1 - fi + if [ "$(uname)" = "Darwin" ]; then + if [ -n "$1" ]; then + du -hd 1 "$1" + else + du -hd 1 fi + elif [ "$(uname)" = "Linux" ]; then + if [ -n "$1" ]; then + du -h --max-depth=1 "$1" + else + du -h --max-depth=1 + fi + fi } # command_exists: checks for existence of a command (0 = true, 1 = false) # ------------------------------------------------------------------- command_exists () { - type "$1" &> /dev/null ; + type "$1" &> /dev/null ; } # pickfrom: picks random line from file # ------------------------------------------------------------------- pickfrom () { - local file=$1 - [ -z "$file" ] && reference $FUNCNAME && return - length=$(cat $file | wc -l) - n=$(expr $RANDOM \* $length \/ 32768 + 1) - head -n $n $file | tail -1 + local file=$1 + [ -z "$file" ] && reference "$FUNCNAME" && return + length=$(wc -l < "$file") + n=$( ($RANDOM \* "$length" / 32768 + 1)) + head -n "$n" "$file" | tail -1 } # passgen: generates random password from dictionary words # Note default length of generated password is 4, you can pass it to the command # E.g. passgen 15 # ------------------------------------------------------------------- +# shellcheck disable=SC2046 +# shellcheck disable=SC2005 +# shellcheck disable=SC2034 +# shellcheck disable=SC2086 passgen () { - local i pass length=${1:-4} - pass=$(echo $(for i in $(eval echo "{1..$length}"); do pickfrom /usr/share/dict/words; done)) - echo "With spaces (easier to memorize): $pass" - echo "Without (use this as the password): $(echo $pass | tr -d ' ')" + local i pass length=${1:-4} + pass=$(echo $(for i in $(eval echo "{1..$length}"); do pickfrom /usr/share/dict/words; done)) + echo "With spaces (easier to memorize): $pass" + echo "Without (use this as the password): $(echo $pass | tr -d ' ')" } @@ -262,11 +303,11 @@ ffe () { /usr/bin/find . -name '*'"$@" ; } # ffe: Find file whose name end # 8. WEB DEVELOPMENT # --------------------------------------- -httpHeaders () { /usr/bin/curl -I -L $@ ; } # httpHeaders: Grabs headers from web page +httpHeaders () { /usr/bin/curl -I -L "$@" ; } # httpHeaders: Grabs headers from web page # httpDebug: Download a web page and show info on what took time # ------------------------------------------------------------------- - httpDebug () { /usr/bin/curl $@ -o /dev/null -w "dns: %{time_namelookup} connect: %{time_connect} pretransfer: %{time_pretransfer} starttransfer: %{time_starttransfer} total: %{time_total}\n" ; } + httpDebug () { /usr/bin/curl "$@" -o /dev/null -w "dns: %{time_namelookup} connect: %{time_connect} pretransfer: %{time_pretransfer} starttransfer: %{time_starttransfer} total: %{time_total}\\n" ; } diff --git a/lib/bourne-shell.sh b/lib/bourne-shell.sh index 096f427..03ecc56 100644 --- a/lib/bourne-shell.sh +++ b/lib/bourne-shell.sh @@ -1,32 +1,6 @@ #!/usr/bin/env bash # ~/.bashrc: executed by bash(1) for non-login shells. # see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) -# for examples - -# If not running interactively, don't do anything -case $- in - *i*) ;; - *) return;; -esac - -# don't put duplicate lines or lines starting with space in the history. -# See bash(1) for more options -HISTCONTROL=ignoreboth - -# append to the history file, don't overwrite it -shopt -s histappend - -# for setting history length see HISTSIZE and HISTFILESIZE in bash(1) -HISTSIZE=1000 -HISTFILESIZE=2000 - -# check the window size after each command and, if necessary, -# update the values of LINES and COLUMNS. -shopt -s checkwinsize - -# If set, the pattern "**" used in a pathname expansion context will -# match all files and zero or more directories and subdirectories. -#shopt -s globstar # make less more friendly for non-text input files, see lesspipe(1) [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" @@ -47,42 +21,42 @@ esac #force_color_prompt=yes if [ -n "$force_color_prompt" ]; then - if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then - # We have color support; assume it's compliant with Ecma-48 - # (ISO/IEC-6429). (Lack of such support is extremely rare, and such - # a case would tend to support setf rather than setaf.) - color_prompt=yes - else - color_prompt= - fi + if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then + # We have color support; assume it's compliant with Ecma-48 + # (ISO/IEC-6429). (Lack of such support is extremely rare, and such + # a case would tend to support setf rather than setaf.) + color_prompt=yes + else + color_prompt= + fi fi if [ "$color_prompt" = yes ]; then - PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' + PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' else - PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' + PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' fi unset color_prompt force_color_prompt # If this is an xterm set the title to user@host:dir case "$TERM" in -xterm*|rxvt*) + xterm*|rxvt*) PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" ;; -*) + *) ;; esac # enable color support of ls and also add handy aliases if [ -x /usr/bin/dircolors ]; then - test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" - alias ls='ls --color=auto' - #alias dir='dir --color=auto' - #alias vdir='vdir --color=auto' + test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" + alias ls='ls --color=auto' + #alias dir='dir --color=auto' + #alias vdir='vdir --color=auto' - alias grep='grep --color=auto' - alias fgrep='fgrep --color=auto' - alias egrep='egrep --color=auto' + alias grep='grep --color=auto' + alias fgrep='fgrep --color=auto' + alias egrep='egrep --color=auto' fi # colored GCC warnings and errors diff --git a/lib/directories.sh b/lib/directories.sh new file mode 100644 index 0000000..c1a18f0 --- /dev/null +++ b/lib/directories.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Common directories functions +alias cd..='cd ../' # Go back 1 directory level (for fast typers) +alias ..='cd ../' # Go back 1 directory level +alias ...='cd ../../' # Go back 2 directory levels +alias .3='cd ../../../' # Go back 3 directory levels +alias .4='cd ../../../../' # Go back 4 directory levels +alias .5='cd ../../../../../' # Go back 5 directory levels +alias .6='cd ../../../../../../' # Go back 6 directory levels + +alias -- -='cd -' +alias 1='cd -' +alias 2='cd -2' +alias 3='cd -3' +alias 4='cd -4' +alias 5='cd -5' +alias 6='cd -6' +alias 7='cd -7' +alias 8='cd -8' +alias 9='cd -9' + +alias md='mkdir -p' +alias rd='rmdir' +alias d='dirs -v | head -10' + +# List directory contents +alias lsa='ls -lah' +alias l='ls -lah' +alias ll='ls -lh' +alias la='ls -lAh' diff --git a/lib/functions.sh b/lib/functions.sh index 61627f4..7203295 100644 --- a/lib/functions.sh +++ b/lib/functions.sh @@ -12,8 +12,8 @@ function upgrade_oh_my_bash() { } function take() { - mkdir -p $1 - cd $1 + mkdir -p "$1" + cd "$1" || exit } function open_command() { diff --git a/lib/history.sh b/lib/history.sh index 0bc2f23..848b438 100644 --- a/lib/history.sh +++ b/lib/history.sh @@ -3,11 +3,53 @@ shopt -s histappend # append to bash_history if Terminal.app quits ## Command history configuration if [ -z "$HISTFILE" ]; then - HISTFILE=$HOME/.bash_history + HISTFILE=$HOME/.bash_history fi -HISTSIZE=10000 -SAVEHIST=10000 + +# some moderate history controls taken from sensible.bash +## SANE HISTORY DEFAULTS ## + +# Append to the history file, don't overwrite it +shopt -s histappend + +# Save multi-line commands as one command +shopt -s cmdhist + +# use readline on history +shopt -s histreedit + +# load history line onto readline buffer for editing +shopt -s histverify + +# save history with newlines instead of ; where possible +shopt -s lithist + +# Record each line as it gets issued +PROMPT_COMMAND='history -a' + +# Huge history. Doesn't appear to slow things down, so why not? +HISTSIZE=500000 +HISTFILESIZE=100000 + +# Avoid duplicate entries +HISTCONTROL="erasedups:ignoreboth" + +# Don't record some commands +export HISTIGNORE="&:[ ]*:exit:ls:bg:fg:history:clear" + +# Use standard ISO 8601 timestamp +# %F equivalent to %Y-%m-%d +# %T equivalent to %H:%M:%S (24-hours format) +HISTTIMEFORMAT='%F %T ' + +# Enable incremental history search with up/down arrows (also Readline goodness) +# Learn more about this here: http://codeinthehole.com/writing/the-most-important-command-line-tip-incremental-hi +# bash4 specific ?? +bind '"\e[A": history-search-backward' +bind '"\e[B": history-search-forward' +bind '"\e[C": forward-char' +bind '"\e[D": backward-char' # Show history #case $HIST_STAMPS in @@ -16,12 +58,3 @@ SAVEHIST=10000 #"yyyy-mm-dd") alias history='fc -il 1' ;; #*) alias history='fc -l 1' ;; #esac - -#shopt append_history -#shopt extended_history -#shopt hist_expire_dups_first -#shopt hist_ignore_dups # ignore duplication command history list -#shopt hist_ignore_space -#shopt hist_verify -#shopt inc_append_history -#shopt share_history # share command history data diff --git a/lib/misc.sh b/lib/misc.sh index b5de98a..7ff28f2 100644 --- a/lib/misc.sh +++ b/lib/misc.sh @@ -19,7 +19,7 @@ fi # only define LC_CTYPE if undefined if [[ -z "$LC_CTYPE" && -z "$LC_ALL" ]]; then - export LC_CTYPE=${LANG%%:*} # pick the first entry from LANG + export LC_CTYPE=${LANG%%:*} # pick the first entry from LANG fi # recognize comments diff --git a/lib/mo.sh b/lib/mo.sh new file mode 100644 index 0000000..4d986ef --- /dev/null +++ b/lib/mo.sh @@ -0,0 +1,917 @@ +#!/usr/bin/env bash +# +#/ Mo is a mustache template rendering software written in bash. It inserts +#/ environment variables into templates. +#/ +#/ Simply put, mo will change {{VARIABLE}} into the value of that +#/ environment variable. You can use {{#VARIABLE}}content{{/VARIABLE}} to +#/ conditionally display content or iterate over the values of an array. +#/ +#/ Learn more about mustache templates at https://mustache.github.io/ +#/ +#/ Simple usage: +#/ +#/ mo [--false] [--help] [--source=FILE] filenames... +#/ +#/ --false - Treat the string "false" as empty for conditionals. +#/ --help - This message. +#/ --source=FILE - Load FILE into the environment before processing templates. +# +# Mo is under a MIT style licence with an additional non-advertising clause. +# See LICENSE.md for the full text. +# +# This is open source! Please feel free to contribute. +# +# https://github.com/tests-always-included/mo + + +# Public: Template parser function. Writes templates to stdout. +# +# $0 - Name of the mo file, used for getting the help message. +# --false - Treat "false" as an empty value. You may set the +# MO_FALSE_IS_EMPTY environment variable instead to a non-empty +# value to enable this behavior. +# --help - Display a help message. +# --source=FILE - Source a file into the environment before processint +# template files. +# -- - Used to indicate the end of options. You may optionally +# use this when filenames may start with two hyphens. +# $@ - Filenames to parse. +# +# Mo uses the following environment variables: +# +# MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" +# will be treated as an empty value for the purposes +# of conditionals. +# MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate +# a help message. +# +# Returns nothing. +mo() { + # This function executes in a subshell so IFS is reset. + # Namespace this variable so we don't conflict with desired values. + local moContent f2source files doubleHyphens + + IFS=$' \n\t' + files=() + doubleHyphens=false + + if [[ $# -gt 0 ]]; then + for arg in "$@"; do + if $doubleHyphens; then + # After we encounter two hyphens together, all the rest + # of the arguments are files. + files=("${files[@]}" "$arg") + else + case "$arg" in + -h|--h|--he|--hel|--help|-\?) + _moUsage "$0" + exit 0 + ;; + + --false) + # shellcheck disable=SC2030 + MO_FALSE_IS_EMPTY=true + ;; + + --source=*) + f2source="${arg#--source=}" + + if [[ -f "$f2source" ]]; then + # shellcheck disable=SC1090 + . "$f2source" + else + echo "No such file: $f2source" >&2 + exit 1 + fi + ;; + + --) + # Set a flag indicating we've encountered double hyphens + doubleHyphens=true + ;; + + *) + # Every arg that is not a flag or a option should be a file + files=("${files[@]}" "$arg") + ;; + esac + fi + done + fi + + _moGetContent moContent "${files[@]}" + _moParse "$moContent" "" true +} + + +# Internal: Scan content until the right end tag is found. Creates an array +# with the following members: +# +# [0] = Content before end tag +# [1] = End tag (complete tag) +# [2] = Content after end tag +# +# Everything using this function uses the "standalone tags" logic. +# +# $1 - Name of variable for the array +# $2 - Content +# $3 - Name of end tag +# $4 - If -z, do standalone tag processing before finishing +# +# Returns nothing. +_moFindEndTag() { + local content remaining scanned standaloneBytes tag + + #: Find open tags + scanned="" + _moSplit content "$2" '{{' '}}' + + while [[ "${#content[@]}" -gt 1 ]]; do + _moTrimWhitespace tag "${content[1]}" + + #: Restore content[1] before we start using it + content[1]='{{'"${content[1]}"'}}' + + case $tag in + '#'* | '^'*) + #: Start another block + scanned="${scanned}${content[0]}${content[1]}" + _moTrimWhitespace tag "${tag:1}" + _moFindEndTag content "${content[2]}" "$tag" "loop" + scanned="${scanned}${content[0]}${content[1]}" + remaining=${content[2]} + ;; + + '/'*) + #: End a block - could be ours + _moTrimWhitespace tag "${tag:1}" + scanned="$scanned${content[0]}" + + if [[ "$tag" == "$3" ]]; then + #: Found our end tag + if [[ -z "${4-}" ]] && _moIsStandalone standaloneBytes "$scanned" "${content[2]}" true; then + #: This is also a standalone tag - clean up whitespace + #: and move those whitespace bytes to the "tag" element + standaloneBytes=( $standaloneBytes ) + content[1]="${scanned:${standaloneBytes[0]}}${content[1]}${content[2]:0:${standaloneBytes[1]}}" + scanned="${scanned:0:${standaloneBytes[0]}}" + content[2]="${content[2]:${standaloneBytes[1]}}" + fi + + local "$1" && _moIndirectArray "$1" "$scanned" "${content[1]}" "${content[2]}" + return 0 + fi + + scanned="$scanned${content[1]}" + remaining=${content[2]} + ;; + + *) + #: Ignore all other tags + scanned="${scanned}${content[0]}${content[1]}" + remaining=${content[2]} + ;; + esac + + _moSplit content "$remaining" '{{' '}}' + done + + #: Did not find our closing tag + scanned="$scanned${content[0]}" + local "$1" && _moIndirectArray "$1" "${scanned}" "" "" +} + + +# Internal: Find the first index of a substring. If not found, sets the +# index to -1. +# +# $1 - Destination variable for the index +# $2 - Haystack +# $3 - Needle +# +# Returns nothing. +_moFindString() { + local pos string + + string=${2%%$3*} + [[ "$string" == "$2" ]] && pos=-1 || pos=${#string} + local "$1" && _moIndirect "$1" "$pos" +} + + +# Internal: Generate a dotted name based on current context and target name. +# +# $1 - Target variable to store results +# $2 - Context name +# $3 - Desired variable name +# +# Returns nothing. +_moFullTagName() { + if [[ -z "${2-}" ]] || [[ "$2" == *.* ]]; then + local "$1" && _moIndirect "$1" "$3" + else + local "$1" && _moIndirect "$1" "${2}.${3}" + fi +} + + +# Internal: Fetches the content to parse into a variable. Can be a list of +# partials for files or the content from stdin. +# +# $1 - Variable name to assign this content back as +# $2-@ - File names (optional) +# +# Returns nothing. +_moGetContent() { + local content filename target + + target=$1 + shift + if [[ "${#@}" -gt 0 ]]; then + content="" + + for filename in "$@"; do + #: This is so relative paths work from inside template files + content="$content"'{{>'"$filename"'}}' + done + else + _moLoadFile content /dev/stdin + fi + + local "$target" && _moIndirect "$target" "$content" +} + + +# Internal: Indent a string, placing the indent at the beginning of every +# line that has any content. +# +# $1 - Name of destination variable to get an array of lines +# $2 - The indent string +# $3 - The string to reindent +# +# Returns nothing. +_moIndentLines() { + local content fragment len posN posR result trimmed + + result="" + len=$((${#3} - 1)) + + #: This removes newline and dot from the workaround in _moPartial + content="${3:0:$len}" + + if [[ -z "${2-}" ]]; then + local "$1" && _moIndirect "$1" "$content" + + return 0 + fi + + _moFindString posN "$content" $'\n' + _moFindString posR "$content" $'\r' + + while [[ "$posN" -gt -1 ]] || [[ "$posR" -gt -1 ]]; do + if [[ "$posN" -gt -1 ]]; then + fragment="${content:0:$posN + 1}" + content=${content:$posN + 1} + else + fragment="${content:0:$posR + 1}" + content=${content:$posR + 1} + fi + + _moTrimChars trimmed "$fragment" false true " " $'\t' $'\n' $'\r' + + if [[ -n "$trimmed" ]]; then + fragment="$2$fragment" + fi + + result="$result$fragment" + _moFindString posN "$content" $'\n' + _moFindString posR "$content" $'\r' + done + + _moTrimChars trimmed "$content" false true " " $'\t' + + if [[ -n "$trimmed" ]]; then + content="$2$content" + fi + + result="$result$content" + + local "$1" && _moIndirect "$1" "$result" +} + + +# Internal: Send a variable up to the parent of the caller of this function. +# +# $1 - Variable name +# $2 - Value +# +# Examples +# +# callFunc () { +# local "$1" && _moIndirect "$1" "the value" +# } +# callFunc dest +# echo "$dest" # writes "the value" +# +# Returns nothing. +_moIndirect() { + unset -v "$1" + printf -v "$1" '%s' "$2" +} + + +# Internal: Send an array as a variable up to caller of a function +# +# $1 - Variable name +# $2-@ - Array elements +# +# Examples +# +# callFunc () { +# local myArray=(one two three) +# local "$1" && _moIndirectArray "$1" "${myArray[@]}" +# } +# callFunc dest +# echo "${dest[@]}" # writes "one two three" +# +# Returns nothing. +_moIndirectArray() { + unset -v "$1" + + # IFS must be set to a string containing space or unset in order for + # the array slicing to work regardless of the current IFS setting on + # bash 3. This is detailed further at + # https://github.com/fidian/gg-core/pull/7 + eval "$(printf "IFS= %s=(\"\${@:2}\") IFS=%q" "$1" "$IFS")" +} + + +# Internal: Determine if a given environment variable exists and if it is +# an array. +# +# $1 - Name of environment variable +# +# Be extremely careful. Even if strict mode is enabled, it is not honored +# in newer versions of Bash. Any errors that crop up here will not be +# caught automatically. +# +# Examples +# +# var=(abc) +# if _moIsArray var; then +# echo "This is an array" +# echo "Make sure you don't accidentally use \$var" +# fi +# +# Returns 0 if the name is not empty, 1 otherwise. +_moIsArray() { + # Namespace this variable so we don't conflict with what we're testing. + local moTestResult + + moTestResult=$(declare -p "$1" 2>/dev/null) || return 1 + [[ "${moTestResult:0:10}" == "declare -a" ]] && return 0 + [[ "${moTestResult:0:10}" == "declare -A" ]] && return 0 + + return 1 +} + + +# Internal: Determine if the given name is a defined function. +# +# $1 - Function name to check +# +# Be extremely careful. Even if strict mode is enabled, it is not honored +# in newer versions of Bash. Any errors that crop up here will not be +# caught automatically. +# +# Examples +# +# moo () { +# echo "This is a function" +# } +# if _moIsFunction moo; then +# echo "moo is a defined function" +# fi +# +# Returns 0 if the name is a function, 1 otherwise. +_moIsFunction() { + local functionList functionName + + functionList=$(declare -F) + functionList=( ${functionList//declare -f /} ) + + for functionName in "${functionList[@]}"; do + if [[ "$functionName" == "$1" ]]; then + return 0 + fi + done + + return 1 +} + + +# Internal: Determine if the tag is a standalone tag based on whitespace +# before and after the tag. +# +# Passes back a string containing two numbers in the format "BEFORE AFTER" +# like "27 10". It indicates the number of bytes remaining in the "before" +# string (27) and the number of bytes to trim in the "after" string (10). +# Useful for string manipulation: +# +# $1 - Variable to set for passing data back +# $2 - Content before the tag +# $3 - Content after the tag +# $4 - true/false: is this the beginning of the content? +# +# Examples +# +# _moIsStandalone RESULT "$before" "$after" false || return 0 +# RESULT_ARRAY=( $RESULT ) +# echo "${before:0:${RESULT_ARRAY[0]}}...${after:${RESULT_ARRAY[1]}}" +# +# Returns nothing. +_moIsStandalone() { + local afterTrimmed beforeTrimmed char + + _moTrimChars beforeTrimmed "$2" false true " " $'\t' + _moTrimChars afterTrimmed "$3" true false " " $'\t' + char=$((${#beforeTrimmed} - 1)) + char=${beforeTrimmed:$char} + + if [[ "$char" != $'\n' ]] && [[ "$char" != $'\r' ]]; then + if [[ -n "$char" ]] || ! $4; then + return 1 + fi + fi + + char=${afterTrimmed:0:1} + + if [[ "$char" != $'\n' ]] && [[ "$char" != $'\r' ]] && [[ -n "$char" ]]; then + return 2 + fi + + if [[ "$char" == $'\r' ]] && [[ "${afterTrimmed:1:1}" == $'\n' ]]; then + char="$char"$'\n' + fi + + local "$1" && _moIndirect "$1" "$((${#beforeTrimmed})) $((${#3} + ${#char} - ${#afterTrimmed}))" +} + + +# Internal: Join / implode an array +# +# $1 - Variable name to receive the joined content +# $2 - Joiner +# $3-$* - Elements to join +# +# Returns nothing. +_moJoin() { + local joiner part result target + + target=$1 + joiner=$2 + result=$3 + shift 3 + + for part in "$@"; do + result="$result$joiner$part" + done + + local "$target" && _moIndirect "$target" "$result" +} + + +# Internal: Read a file into a variable. +# +# $1 - Variable name to receive the file's content +# $2 - Filename to load +# +# Returns nothing. +_moLoadFile() { + local content len + + # The subshell removes any trailing newlines. We forcibly add + # a dot to the content to preserve all newlines. + # TODO: remove cat and replace with read loop? + + content=$(cat -- "$2"; echo '.') + len=$((${#content} - 1)) + content=${content:0:$len} # Remove last dot + + local "$1" && _moIndirect "$1" "$content" +} + + +# Internal: Process a chunk of content some number of times. Writes output +# to stdout. +# +# $1 - Content to parse repeatedly +# $2 - Tag prefix (context name) +# $3-@ - Names to insert into the parsed content +# +# Returns nothing. +_moLoop() { + local content context contextBase + + content=$1 + contextBase=$2 + shift 2 + + while [[ "${#@}" -gt 0 ]]; do + _moFullTagName context "$contextBase" "$1" + _moParse "$content" "$context" false + shift + done +} + + +# Internal: Parse a block of text, writing the result to stdout. +# +# $1 - Block of text to change +# $2 - Current name (the variable NAME for what {{.}} means) +# $3 - true when no content before this, false otherwise +# +# Returns nothing. +_moParse() { + # Keep naming variables mo* here to not overwrite needed variables + # used in the string replacements + local moBlock moContent moCurrent moIsBeginning moTag + + moCurrent=$2 + moIsBeginning=$3 + + # Find open tags + _moSplit moContent "$1" '{{' '}}' + + while [[ "${#moContent[@]}" -gt 1 ]]; do + _moTrimWhitespace moTag "${moContent[1]}" + + case $moTag in + '#'*) + # Loop, if/then, or pass content through function + # Sets context + _moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" + _moTrimWhitespace moTag "${moTag:1}" + _moFindEndTag moBlock "$moContent" "$moTag" + _moFullTagName moTag "$moCurrent" "$moTag" + + if _moTest "$moTag"; then + # Show / loop / pass through function + if _moIsFunction "$moTag"; then + #: TODO: Consider piping the output to _moGetContent + #: so the lambda does not execute in a subshell? + moContent=$($moTag "${moBlock[0]}") + _moParse "$moContent" "$moCurrent" false + moContent="${moBlock[2]}" + elif _moIsArray "$moTag"; then + eval "_moLoop \"\${moBlock[0]}\" \"$moTag\" \"\${!${moTag}[@]}\"" + else + _moParse "${moBlock[0]}" "$moCurrent" false + fi + fi + + moContent="${moBlock[2]}" + ;; + + '>'*) + # Load partial - get name of file relative to cwd + _moPartial moContent "${moContent[@]}" "$moIsBeginning" "$moCurrent" + ;; + + '/'*) + # Closing tag - If hit in this loop, we simply ignore + # Matching tags are found in _moFindEndTag + _moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" + ;; + + '^'*) + # Display section if named thing does not exist + _moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" + _moTrimWhitespace moTag "${moTag:1}" + _moFindEndTag moBlock "$moContent" "$moTag" + _moFullTagName moTag "$moCurrent" "$moTag" + + if ! _moTest "$moTag"; then + _moParse "${moBlock[0]}" "$moCurrent" false "$moCurrent" + fi + + moContent="${moBlock[2]}" + ;; + + '!'*) + # Comment - ignore the tag content entirely + # Trim spaces/tabs before the comment + _moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" + ;; + + .) + # Current content (environment variable or function) + _moStandaloneDenied moContent "${moContent[@]}" + _moShow "$moCurrent" "$moCurrent" + ;; + + '=') + # Change delimiters + # Any two non-whitespace sequences separated by whitespace. + # TODO + _moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" + ;; + + '{'*) + # Unescaped - split on }}} not }} + _moStandaloneDenied moContent "${moContent[@]}" + moContent="${moTag:1}"'}}'"$moContent" + _moSplit moContent "$moContent" '}}}' + _moTrimWhitespace moTag "${moContent[0]}" + _moFullTagName moTag "$moCurrent" "$moTag" + moContent=${moContent[1]} + + # Now show the value + _moShow "$moTag" "$moCurrent" + ;; + + '&'*) + # Unescaped + _moStandaloneDenied moContent "${moContent[@]}" + _moTrimWhitespace moTag "${moTag:1}" + _moFullTagName moTag "$moCurrent" "$moTag" + _moShow "$moTag" "$moCurrent" + ;; + + *) + # Normal environment variable or function call + _moStandaloneDenied moContent "${moContent[@]}" + _moFullTagName moTag "$moCurrent" "$moTag" + _moShow "$moTag" "$moCurrent" + ;; + esac + + moIsBeginning=false + _moSplit moContent "$moContent" '{{' '}}' + done + + echo -n "${moContent[0]}" +} + + +# Internal: Process a partial. +# +# Indentation should be applied to the entire partial +# +# Prefix all variables. +# +# $1 - Name of destination "content" variable. +# $2 - Content before the tag that was not yet written +# $3 - Tag content +# $4 - Content after the tag +# $5 - true/false: is this the beginning of the content? +# $6 - Current context name +# +# Returns nothing. +_moPartial() { + # Namespace variables here to prevent conflicts. + local moContent moFilename moIndent moPartial moStandalone + + if _moIsStandalone moStandalone "$2" "$4" "$5"; then + moStandalone=( $moStandalone ) + echo -n "${2:0:${moStandalone[0]}}" + moIndent=${2:${moStandalone[0]}} + moContent=${4:${moStandalone[1]}} + else + moIndent="" + echo -n "$2" + moContent=$4 + fi + + _moTrimWhitespace moFilename "${3:1}" + + # Execute in subshell to preserve current cwd and environment + ( + # TODO: Remove dirname and use a function instead + cd "$(dirname -- "$moFilename")" || exit 1 + _moIndentLines moPartial "$moIndent" "$( + _moLoadFile moPartial "${moFilename##*/}" + + # Fix bash handling of subshells + # The extra dot is removed in _moIndentLines + echo -n "${moPartial}." + )" + _moParse "$moPartial" "$6" true + ) + + local "$1" && _moIndirect "$1" "$moContent" +} + + +# Internal: Show an environment variable or the output of a function to +# stdout. +# +# Limit/prefix any variables used. +# +# $1 - Name of environment variable or function +# $2 - Current context +# +# Returns nothing. +_moShow() { + # Namespace these variables + local moJoined moNameParts + + if _moIsFunction "$1"; then + CONTENT=$($1 "") + _moParse "$CONTENT" "$2" false + return 0 + fi + + _moSplit moNameParts "$1" "." + + if [[ -z "${moNameParts[1]}" ]]; then + if _moIsArray "$1"; then + eval _moJoin moJoined "," "\${$1[@]}" + echo -n "$moJoined" + else + echo -n "${!1}" + fi + else + # Further subindexes are disallowed + eval "echo -n \"\${${moNameParts[0]}[${moNameParts[1]%%.*}]}\"" + fi +} + + +# Internal: Split a larger string into an array. +# +# $1 - Destination variable +# $2 - String to split +# $3 - Starting delimiter +# $4 - Ending delimiter (optional) +# +# Returns nothing. +_moSplit() { + local pos result + + result=( "$2" ) + _moFindString pos "${result[0]}" "$3" + + if [[ "$pos" -ne -1 ]]; then + # The first delimiter was found + result[1]=${result[0]:$pos + ${#3}} + result[0]=${result[0]:0:$pos} + + if [[ -n "${4-}" ]]; then + _moFindString pos "${result[1]}" "$4" + + if [[ "$pos" -ne -1 ]]; then + # The second delimiter was found + result[2]="${result[1]:$pos + ${#4}}" + result[1]="${result[1]:0:$pos}" + fi + fi + fi + + local "$1" && _moIndirectArray "$1" "${result[@]}" +} + + +# Internal: Handle the content for a standalone tag. This means removing +# whitespace (not newlines) before a tag and whitespace and a newline after +# a tag. That is, assuming, that the line is otherwise empty. +# +# $1 - Name of destination "content" variable. +# $2 - Content before the tag that was not yet written +# $3 - Tag content (not used) +# $4 - Content after the tag +# $5 - true/false: is this the beginning of the content? +# +# Returns nothing. +_moStandaloneAllowed() { + local bytes + + if _moIsStandalone bytes "$2" "$4" "$5"; then + bytes=( $bytes ) + echo -n "${2:0:${bytes[0]}}" + local "$1" && _moIndirect "$1" "${4:${bytes[1]}}" + else + echo -n "$2" + local "$1" && _moIndirect "$1" "$4" + fi +} + + +# Internal: Handle the content for a tag that is never "standalone". No +# adjustments are made for newlines and whitespace. +# +# $1 - Name of destination "content" variable. +# $2 - Content before the tag that was not yet written +# $3 - Tag content (not used) +# $4 - Content after the tag +# +# Returns nothing. +_moStandaloneDenied() { + echo -n "$2" + local "$1" && _moIndirect "$1" "$4" +} + + +# Internal: Determines if the named thing is a function or if it is a +# non-empty environment variable. When MO_FALSE_IS_EMPTY is set to a +# non-empty value, then "false" is also treated is an empty value. +# +# Do not use variables without prefixes here if possible as this needs to +# check if any name exists in the environment +# +# $1 - Name of environment variable or function +# $2 - Current value (our context) +# MO_FALSE_IS_EMPTY - When set to a non-empty value, this will say the +# string value "false" is empty. +# +# Returns 0 if the name is not empty, 1 otherwise. When MO_FALSE_IS_EMPTY +# is set, this returns 1 if the name is "false". +_moTest() { + # Test for functions + _moIsFunction "$1" && return 0 + + if _moIsArray "$1"; then + # Arrays must have at least 1 element + eval "[[ \"\${#${1}[@]}\" -gt 0 ]]" && return 0 + else + # If MO_FALSE_IS_EMPTY is set, then return 1 if the value of + # the variable is "false". + # shellcheck disable=SC2031 + [[ -n "${MO_FALSE_IS_EMPTY-}" ]] && [[ "${!1-}" == "false" ]] && return 1 + + # Environment variables must not be empty + [[ -n "${!1}" ]] && return 0 + fi + + return 1 +} + + +# Internal: Trim the leading whitespace only. +# +# $1 - Name of destination variable +# $2 - The string +# $3 - true/false - trim front? +# $4 - true/false - trim end? +# $5-@ - Characters to trim +# +# Returns nothing. +_moTrimChars() { + local back current front last target varName + + target=$1 + current=$2 + front=$3 + back=$4 + last="" + shift 4 # Remove target, string, trim front flag, trim end flag + + while [[ "$current" != "$last" ]]; do + last=$current + + for varName in "$@"; do + $front && current="${current/#$varName}" + $back && current="${current/%$varName}" + done + done + + local "$target" && _moIndirect "$target" "$current" +} + + +# Internal: Trim leading and trailing whitespace from a string. +# +# $1 - Name of variable to store trimmed string +# $2 - The string +# +# Returns nothing. +_moTrimWhitespace() { + local result + + _moTrimChars result "$2" true true $'\r' $'\n' $'\t' " " + local "$1" && _moIndirect "$1" "$result" +} + + +# Internal: Displays the usage for mo. Pulls this from the file that +# contained the `mo` function. Can only work when the right filename +# comes is the one argument, and that only happens when `mo` is called +# with `$0` set to this file. +# +# $1 - Filename that has the help message +# +# Returns nothing. +_moUsage() { + grep '^#/' "${MO_ORIGINAL_COMMAND}" | cut -c 4- +} + + +# Save the original command's path for usage later +MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}" + +# If sourced, load all functions. +# If executed, perform the actions as expected. +if [[ "$0" == "${BASH_SOURCE[0]}" ]] || [[ -z "${BASH_SOURCE[0]}" ]]; then + mo "$@" +fi diff --git a/lib/shopt.sh b/lib/shopt.sh new file mode 100644 index 0000000..1e32645 --- /dev/null +++ b/lib/shopt.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +# Various shell options collected in single file +# taken from bash-sensible and other sources +## GENERAL OPTIONS ## + +# Prevent file overwrite on stdout redirection +# Use `>|` to force redirection to an existing file +set -o noclobber + +# Update window size after every command +shopt -s checkwinsize + +# Automatically trim long paths in the prompt (requires Bash 4.x) +PROMPT_DIRTRIM=2 + +# Enable history expansion with space +# E.g. typing !! will replace the !! with your last command +bind Space:magic-space + +# Turn on recursive globbing (enables ** to recurse all directories) +shopt -s globstar 2> /dev/null + +# Case-insensitive globbing (used in pathname expansion) +shopt -s nocaseglob; + +## SMARTER TAB-COMPLETION (Readline bindings) ## + +# Perform file completion in a case insensitive fashion +bind "set completion-ignore-case on" + +# Treat hyphens and underscores as equivalent +bind "set completion-map-case on" + +# Display matches for ambiguous patterns at first tab press +bind "set show-all-if-ambiguous on" + +# Immediately add a trailing slash when autocompleting symlinks to directories +bind "set mark-symlinked-directories on" + +## BETTER DIRECTORY NAVIGATION ## + +# Prepend cd to directory names automatically +shopt -s autocd 2> /dev/null +# Correct spelling errors during tab-completion +shopt -s dirspell 2> /dev/null +# Correct spelling errors in arguments supplied to cd +shopt -s cdspell 2> /dev/null + +# This defines where cd looks for targets +# Add the directories you want to have fast access to, separated by colon +# Ex: CDPATH=".:~:~/projects" will look for targets in the current working directory, in home and in the ~/projec +CDPATH="." + +# This allows you to bookmark your favorite places across the file system +# Define a variable containing a path and you will be able to cd into it regardless of the directory you're in +shopt -s cdable_vars diff --git a/lib/spectrum.sh b/lib/spectrum.sh index ee81f00..42f3db1 100644 --- a/lib/spectrum.sh +++ b/lib/spectrum.sh @@ -7,44 +7,42 @@ # typeset in bash does not have associative arrays, declare does in bash 4.0+ # https://stackoverflow.com/a/6047948 -_RED='\033[0;31m' # Red Color (For error) -_NC='\033[0m' # No Color (To reset the terminal color) +# This library only works for BASH 4.x to keep the minimum compatible for macOS. +# shellcheck disable=SC2034 +if [ "${BASH_VERSINFO[0]}" -gt 4 ]; then + _RED='\033[0;31m' # Red Color (For error) + _NC='\033[0m' # No Color (To reset the terminal color) -if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then - echo -e "${_RED}ERROR${_NC}: Sorry, you need at least bash-4.0 to run this script." >&2; - exit 1; + declare -Ag FX FG BG + + FX=( + [reset]="%{^[[00m%}" + [bold]="%{^[[01m%}" [no-bold]="%{^[[22m%}" + [italic]="%{^[[03m%}" [no-italic]="%{^[[23m%}" + [underline]="%{^[[04m%}" [no-underline]="%{^[[24m%}" + [blink]="%{^[[05m%}" [no-blink]="%{^[[25m%}" + [reverse]="%{^[[07m%}" [no-reverse]="%{^[[27m%}" + ) + + for color in {000..255}; do + FG[$color]="%{^[[38;5;${color}m%}" + BG[$color]="%{^[[48;5;${color}m%}" + done + + + OSH_SPECTRUM_TEXT=${OSH_SPECTRUM_TEXT:-Arma virumque cano Troiae qui primus ab oris} + + # Show all 256 colors with color number + function spectrum_ls() { + for code in {000..255}; do + print -P -- "$code: %{$FG[$code]%}$OSH_SPECTRUM_TEXT%{$reset_color%}" + done + } + + # Show all 256 colors where the background is set to specific color + function spectrum_bls() { + for code in {000..255}; do + print -P -- "$code: %{$BG[$code]%}$OSH_SPECTRUM_TEXT%{$reset_color%}" + done + } fi - - -declare -Ag FX FG BG - -FX=( - [reset]="%{^[[00m%}" - [bold]="%{^[[01m%}" [no-bold]="%{^[[22m%}" - [italic]="%{^[[03m%}" [no-italic]="%{^[[23m%}" - [underline]="%{^[[04m%}" [no-underline]="%{^[[24m%}" - [blink]="%{^[[05m%}" [no-blink]="%{^[[25m%}" - [reverse]="%{^[[07m%}" [no-reverse]="%{^[[27m%}" -) - -for color in {000..255}; do - FG[$color]="%{^[[38;5;${color}m%}" - BG[$color]="%{^[[48;5;${color}m%}" -done - - -OSH_SPECTRUM_TEXT=${OSH_SPECTRUM_TEXT:-Arma virumque cano Troiae qui primus ab oris} - -# Show all 256 colors with color number -function spectrum_ls() { - for code in {000..255}; do - print -P -- "$code: %{$FG[$code]%}$OSH_SPECTRUM_TEXT%{$reset_color%}" - done -} - -# Show all 256 colors where the background is set to specific color -function spectrum_bls() { - for code in {000..255}; do - print -P -- "$code: %{$BG[$code]%}$OSH_SPECTRUM_TEXT%{$reset_color%}" - done -} diff --git a/lib/spinner.sh b/lib/spinner.sh new file mode 100644 index 0000000..1c2e3a2 --- /dev/null +++ b/lib/spinner.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# +# Move a process to background and track its progress in a smoothier way. +# Could be use if $TERM not set. +# +# Examples +# +# echo -ne "${fg[red]}I am running..." +# ( my_long_task_running ) & +# spinner +# echo -ne "...${reset_color} ${fg[green]}DONE${reset_color}" +# + +# This spinner is used when there is a terminal. +term_spinner() { + local pid=$! + local delay=0.1 + local spinstr='|/-\' + while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do + local temp=${spinstr#?} + printf " [%c] " "$spinstr" + local spinstr=$temp${spinstr%"$temp"} + sleep $delay + printf "\b\b\b\b\b\b" + done + printf " \b\b\b\b" +} + +no_term_spinner() { + local pid=$! + local delay=0.1 + local spinstr='|/-\' + while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do + printf "." + sleep 2 + done + echo " ✓ " +} + +spinner() { + if [[ -z "$TERM" ]]; then + no_term_spinner + else + term_spinner + fi +} diff --git a/lib/theme-and-appearance.sh b/lib/theme-and-appearance.sh index c8cee0e..bc4a33f 100644 --- a/lib/theme-and-appearance.sh +++ b/lib/theme-and-appearance.sh @@ -2,3 +2,4 @@ # colored ls export LSCOLORS='Gxfxcxdxdxegedabagacad' +alias ls='ls -G' diff --git a/lib/utils.sh b/lib/utils.sh index 7f58b56..d8c7d42 100644 --- a/lib/utils.sh +++ b/lib/utils.sh @@ -119,7 +119,7 @@ e_underline() { printf "${underline}${bold}%s${reset}\n" "$@" } e_bold() { printf "${bold}%s${reset}\n" "$@" } -e_note() { printf "${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n" "$@" +e_note() { printf "${underline}${bold}${blue}Note:${reset} ${yellow}%s${reset}\n" "$@" } # @@ -134,9 +134,9 @@ e_note() { printf "${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n" # fi # seek_confirmation() { - printf "\n${bold}%s${reset}" "$@" + printf "\\n${bold}%s${reset}" "$@" read -p " (y/n) " -n 1 - printf "\n" + printf "\\n" } # Test whether the result of an 'ask' is a confirmation diff --git a/oh-my-bash.sh b/oh-my-bash.sh index 78062e1..97f3b6b 100644 --- a/oh-my-bash.sh +++ b/oh-my-bash.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +# Bail out early if non-interactive +case $- in + *i*) ;; + *) return;; +esac + # Check for updates on initial load... if [ "$DISABLE_AUTO_UPDATE" != "true" ]; then env OSH=$OSH DISABLE_UPDATE_PROMPT=$DISABLE_UPDATE_PROMPT bash -f $OSH/tools/check_for_upgrade.sh @@ -8,7 +14,7 @@ fi # Initializes Oh My Bash # add a function path -fpath=($OSH/functions $OSH/completions $fpath) +fpath=($OSH/functions $fpath) # Set OSH_CUSTOM to the path where your custom config files # and plugins exists, or else we will use the default custom/ @@ -22,7 +28,6 @@ if [[ -z "$OSH_CACHE_DIR" ]]; then OSH_CACHE_DIR="$OSH/cache" fi - # Load all of the config files in ~/.oh-my-bash/lib that end in .sh # TIP: Add files you don't want in git to .gitignore for config_file in $OSH/lib/*.sh; do @@ -48,6 +53,36 @@ for plugin in ${plugins[@]}; do fi done +is_completion() { + local base_dir=$1 + local name=$2 + test -f $base_dir/completions/$name/$name.completion.sh +} +# Add all defined completions to fpath. This must be done +# before running compinit. +for completion in ${completions[@]}; do + if is_completion $OSH_CUSTOM $completion; then + fpath=($OSH_CUSTOM/completions/$completion $fpath) + elif is_completion $OSH $completion; then + fpath=($OSH/completions/$completion $fpath) + fi +done + +is_alias() { + local base_dir=$1 + local name=$2 + test -f $base_dir/aliases/$name/$name.aliases.sh +} +# Add all defined completions to fpath. This must be done +# before running compinit. +for alias in ${aliases[@]}; do + if is_alias $OSH_CUSTOM $alias; then + fpath=($OSH_CUSTOM/aliases/$alias $fpath) + elif is_alias $OSH $alias; then + fpath=($OSH/aliases/$alias $fpath) + fi +done + # Figure out the SHORT hostname if [[ "$OSTYPE" = darwin* ]]; then # macOS's $HOST changes with dhcp, etc. Use ComputerName if possible. @@ -65,18 +100,22 @@ for plugin in ${plugins[@]}; do fi done -# Load all of the completion & aliases files in ~/.oh-my-bash/{completion,aliases} -# that end in .completion.sh & .aliases.sh -# TIP: Add files you don't want in git to .gitignore -for alias_file in $OSH/aliases/*.sh; do - custom_alias_file="${OSH_CUSTOM}/aliases/${alias_file:t}" - [ -f "${custom_alias_file}" ] && alias_file=${custom_alias_file} - source $alias_file +# Load all of the aliases that were defined in ~/.bashrc +for alias in ${aliases[@]}; do + if [ -f $OSH_CUSTOM/aliases/$alias.aliases.sh ]; then + source $OSH_CUSTOM/aliases/$alias.aliases.sh + elif [ -f $OSH/aliases/$alias.aliases.sh ]; then + source $OSH/aliases/$alias.aliases.sh + fi done -for completion_file in $OSH/completion/*.sh; do - custom_completion_file="${OSH_CUSTOM}/completion/${completion_file:t}" - [ -f "${custom_completion_file}" ] && completion_file=${custom_completion_file} - source $completion_file + +# Load all of the completions that were defined in ~/.bashrc +for completion in ${completions[@]}; do + if [ -f $OSH_CUSTOM/completions/$completion.completion.sh ]; then + source $OSH_CUSTOM/completions/$completion.completion.sh + elif [ -f $OSH/completions/$completion.completion.sh ]; then + source $OSH/completions/$completion.completion.sh + fi done # Load all of your custom configurations from custom/ @@ -86,20 +125,6 @@ for config_file in $OSH_CUSTOM/*.sh; do fi done unset config_file -# Load all of your custom aliases from custom/ -for alias_file in $OSH_CUSTOM/aliases/*.sh; do - if [ -f $alias_file ]; then - source $alias_file - fi -done -unset alias_file -# Load all of your custom completions from custom/ -for completion_file in $OSH_CUSTOM/completion/*.sh; do - if [ -f $completion_file ]; then - source $completion_file - fi -done -unset completion_file # Load colors first so they can be use in base theme source "${OSH}/themes/colours.theme.sh" diff --git a/plugins/bu/bu.plugin.sh b/plugins/bu/bu.plugin.sh index 0326934..2566e7c 100644 --- a/plugins/bu/bu.plugin.sh +++ b/plugins/bu/bu.plugin.sh @@ -1,3 +1,4 @@ +#!/usr/bin/env bash # bu.plugin.sh # Author: Taleeb Midi # Based on oh-my-zsh AWS plugin @@ -6,25 +7,26 @@ # # Faster Change Directory up +# shellcheck disable=SC2034 function bu () { - function usage () { - cat <<-EOF - Usage: bu [N] - N N is the number of level to move back up to, this argument must be a positive integer. - h help displays this basic help menu. - EOF - } - # reset variables - STRARGMNT="" - FUNCTIONARG=$1 - # Make sure the provided argument is a positive integer: - if [[ ! -z "${FUNCTIONARG##*[!0-9]*}" ]]; then - for i in $(seq 1 $FUNCTIONARG); do - STRARGMNT+="../" - done - CMD="cd ${STRARGMNT}" - eval $CMD - else - usage - fi -} \ No newline at end of file + function usage () { + cat <<-EOF + Usage: bu [N] + N N is the number of level to move back up to, this argument must be a positive integer. + h help displays this basic help menu. + EOF + } + # reset variables + STRARGMNT="" + FUNCTIONARG=$1 + # Make sure the provided argument is a positive integer: + if [[ ! -z "${FUNCTIONARG##*[!0-9]*}" ]]; then + for i in $(seq 1 $FUNCTIONARG); do + STRARGMNT+="../" + done + CMD="cd ${STRARGMNT}" + eval $CMD + else + usage + fi +} diff --git a/templates/bashrc.osh-template b/templates/bashrc.osh-template index 15f916f..21f510b 100644 --- a/templates/bashrc.osh-template +++ b/templates/bashrc.osh-template @@ -43,19 +43,36 @@ OSH_THEME="font" # Would you like to use another custom folder than $OSH/custom? # OSH_CUSTOM=/path/to/new-custom-folder +# Which completions would you like to load? (completions can be found in ~/.oh-my-bash/completions/*) +# Custom completions may be added to ~/.oh-my-bash/custom/completions/ +# Example format: completions=(ssh git bundler gem pip pip3) +# Add wisely, as too many completions slow down shell startup. +completions=( + git + composer + ssh +) + +# Which aliases would you like to load? (aliases can be found in ~/.oh-my-bash/aliases/*) +# Custom aliases may be added to ~/.oh-my-bash/custom/aliases/ +# Example format: aliases=(vagrant composer git-avh) +# Add wisely, as too many aliases slow down shell startup. +aliases=( + general +) + # Which plugins would you like to load? (plugins can be found in ~/.oh-my-bash/plugins/*) # Custom plugins may be added to ~/.oh-my-bash/custom/plugins/ -# Example format: plugins=(core rails git textmate ruby lighthouse) +# Example format: plugins=(rails git textmate ruby lighthouse) # Add wisely, as too many plugins slow down shell startup. -plugins=(core git bashmarks progress) +plugins=( + git + bashmarks +) -if tty -s -then - source $OSH/oh-my-bash.sh -fi +source $OSH/oh-my-bash.sh # User configuration - # export MANPATH="/usr/local/man:$MANPATH" # You may need to manually set your language environment