From 952920dfc2d048a9782dac9d3549b50779d26fe4 Mon Sep 17 00:00:00 2001 From: Lynda Wang Date: Sun, 9 Aug 2020 15:25:35 -0400 Subject: [PATCH] Add a linux installer --- aliases | 27 +- bash/bashrc | 51 ++- common_env => env.common | 15 + install | 100 +++-- linux/README.md | 2 + linux/bin/dos2unix-recursive | 3 + linux/bin/figt | 3 - linux/bin/fix-vm-resolution | 5 + linux/bin/packer | 807 ---------------------------------- linux/bin/selecta | 818 ----------------------------------- linux/env.platform | 1 + linux/install | 30 ++ osx/install | 8 - script_helpers/file_ops.sh | 13 +- windows/env.platform | 2 + windows/install | 12 + zsh/zshenv | 34 -- zsh/zshrc | 40 +- 18 files changed, 213 insertions(+), 1758 deletions(-) rename common_env => env.common (51%) create mode 100644 linux/README.md create mode 100644 linux/bin/dos2unix-recursive delete mode 100755 linux/bin/figt create mode 100644 linux/bin/fix-vm-resolution delete mode 100755 linux/bin/packer delete mode 100755 linux/bin/selecta create mode 100644 linux/env.platform create mode 100755 linux/install create mode 100644 windows/env.platform create mode 100755 windows/install delete mode 100644 zsh/zshenv diff --git a/aliases b/aliases index b4a13e8..df49e9a 100644 --- a/aliases +++ b/aliases @@ -56,16 +56,6 @@ update-shell() { fi } -# See top 10 bash commands -hist() { - if [[ '${platform,,}' == *'ming'* ]]; then - hist_file=~/.bash_history - else - hist_file=~/.history - fi - cat $hist_file|cut -d ';' -f 2- 2>/dev/null| awk '{a[$1]++ } END{for(i in a){print a[i] " " i}}'|sort -rn|head -} - remove_windows_file() { if [ -f "$1" ]; then recycle-bin.exe "$1" @@ -364,11 +354,13 @@ git_nuke() { #################################################################################################### + +alias ls='ls --color' +alias l='ls -lh' +alias ll='ls -lha' + # Handle the fact that this file will be used with multiple OSs if [[ $platform == 'Linux' ]]; then - alias l='ls -lhg --color' - alias ll='ls -lahg --color' - # Arch alias flux='redshift' alias ipconfig='ip addr' @@ -382,17 +374,12 @@ if [[ $platform == 'Linux' ]]; then # TODO add a trash alias for trash-cli? elif [[ $platform == 'Darwin' ]]; then - alias l='ls -laG' - alias ll='ls -lG' alias trash='rmtrash' alias tt='rmtrash' elif [[ "${platform,,}" == *'ming'* ]]; then # convert to lowercase then compare with wildcard alias python='winpty python.exe' alias python3='winpty python.exe' - - alias l='ls -ahg --color' - alias ls='ls -ahg --color' #alias rm='echo "use trash command instead!"' #alias rmr='echo "use trash command instead!"' alias trash='remove_windows_file' @@ -476,8 +463,8 @@ alias pdot='cd ~/.private-dotfiles' alias duh='du -csh' alias exp='explorer .' alias f='fg' -alias grep='grep -n --color --exclude=tags ' -alias history='fc -l 1' +alias grep='grep --color=auto --exclude=tags ' +alias hist='history' alias histroy='history' alias irb='irb --readline -r irb/completion' alias lcc='lein clean' diff --git a/bash/bashrc b/bash/bashrc index 85a544e..5b2756c 100644 --- a/bash/bashrc +++ b/bash/bashrc @@ -1,22 +1,15 @@ #set -x # Print out all of the commands that are executing. +# If not running interactively, don't do anything +case $- in + *i*) ;; + *) return;; +esac + # Unbreak broken, non-colored terminal export TERM=xterm-256color -test -f ~/.common_env && . ~/.common_env -test -f ~/.private-dotfiles.common/env && . ~/.private-dotfiles.common/env -test -f ~/.private-dotfiles/env && . ~/.private-dotfiles/env - -test -f ~/.aliases && . ~/.aliases -test -f ~/.aliases.private && . ~/.aliases.private - -if [[ -d "$HOME/bin" ]]; then - export PATH=$HOME/bin/:$PATH -fi - -if [[ -d "$HOME/.dotfiles/bin" ]]; then - export PATH=$HOME/.dotfiles/bin/:$PATH -fi +test -f ~/.env.common && . ~/.env.common # TMP and TEMP are defined in the Windows environment. Leaving them set to the default # Windows temporary directory can have unexpected consequences. @@ -64,6 +57,8 @@ export NVM_DIR="$HOME/.nvm" # Don't use ^D to exit set -o ignoreeof +# don't put duplicate lines or lines starting with space in the history. +# See bash(1) for more options # don't put duplicate lines or lines starting with space in the history. # See bash(1) for more options HISTCONTROL=ignoreboth @@ -71,9 +66,10 @@ 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 +HISTSIZE=20000 +HISTFILESIZE=20000 +HISTIGNORE="&:ls:l:ll:[bf]g:less:clear:cls:exit:history:hist" +HISTTIMEFORMAT='%F %T ' # check the window size after each command and, if necessary, # update the values of LINES and COLUMNS. @@ -82,5 +78,26 @@ shopt -s checkwinsize # make less more friendly for non-text input files, see lesspipe(1) [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" +# set a fancy prompt (non-color, unless we know we "want" color) +case "$TERM" in + xterm-color) color_prompt=yes;; +esac + # colored GCC warnings and errors export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' + +# 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)" +fi + +# enable programmable completion features (you don't need to enable +# this, if it's already enabled in /etc/bash.bashrc and /etc/profile +# sources /etc/bash.bashrc). +if ! shopt -oq posix; then + if [ -f /usr/share/bash-completion/bash_completion ]; then + . /usr/share/bash-completion/bash_completion + elif [ -f /etc/bash_completion ]; then + . /etc/bash_completion + fi +fi diff --git a/common_env b/env.common similarity index 51% rename from common_env rename to env.common index 1e42a98..283edfa 100644 --- a/common_env +++ b/env.common @@ -13,3 +13,18 @@ esac export PLATFORM=$platform export PLATFORM_OS=$platform_os +test -f $HOME/.env.platform && . $HOME/.env.platform +test -f $HOME/.private-dotfiles.common/env && . $HOME/.private-dotfiles.common/env +test -f $HOME/.private-dotfiles/env && . $HOME/.private-dotfiles/env + +test -f $HOME/.aliases && . $HOME/.aliases +test -f $HOME/.aliases.private && . $HOME/.aliases.private + +if [[ -d "$HOME/bin" ]]; then + export PATH=$HOME/bin/:$PATH +fi + +if [[ -d "$HOME/.dotfiles/bin" ]]; then + export PATH=$HOME/.dotfiles/bin/:$PATH +fi + diff --git a/install b/install index baf2f15..78cc401 100755 --- a/install +++ b/install @@ -35,29 +35,27 @@ set -e # Helpers #################################################################################################### +use_shell() { + shell=$1 + if hash chsh >/dev/null 2>&1; then + printf "\n${BLUE}Changing the default shell to $shell${NORMAL}\n" + chsh -s $(which $shell) + else + error "\nUnable to change the shell because this system does not have chsh.\n" + fi +} + setup_zsh() { - printf "Setting up zsh...\n" + printf "${MAGENTA}==> ${NORMAL}Setting up zsh...\n" if [[ $is_linux -eq 1 ]]; then sudo apt install zsh fi - TEST_CURRENT_SHELL=$(expr "$SHELL" : '.*/\(.*\)') - if [ "$TEST_CURRENT_SHELL" != "zsh" ]; then - if hash chsh >/dev/null 2>&1; then - printf "\n${BLUE}Changing the default shell to zsh${NORMAL}\n" - chsh -s $(grep /zsh$ /etc/shells | tail -1) - else - printf "\n${RED}Unable to change the shell because this system does not have chsh.\n" - printf "${BLUE}If this is Windows then you probably want to run the bash installer.${NORMAL}\n" - fi - fi - setup_dir .dotfiles/zsh/core .zsh FILES=() FILES+=('zshrc') - FILES+=('zshenv') FILES+=('zlogin') for file in "${FILES[@]}" @@ -66,23 +64,37 @@ setup_zsh() { done } +setup_bash() { + printf "${MAGENTA}==> ${NORMAL}Setting up bash...\n" + + FILES=() + FILES+=('bashrc') + FILES+=('bash_profile') + FILES+=('inputrc') + + for file in "${FILES[@]}" + do + setup_file .dotfiles/bash/$file .$file + done +} + #################################################################################################### # Setup #################################################################################################### pushd "$HOME" &>/dev/null -######################### -# Setup root dirs -######################### setup_dir $cwd .dotfiles -setup_dir .dotfiles/vim .vim -######################### -# Setup root files -######################### +if [[ $is_windows -eq 1 ]]; then + printf "${MAGENTA}==> ${NORMAL}Copy ${YELLOW}.dotfiles/vim${NORMAL} to ${YELLOW}$PWD/.vim${NORMAL}\n" + cp -r .dotfiles/vim .vim +else + setup_dir .dotfiles/vim .vim +fi + FILES=() -FILES+=('common_env') +FILES+=('env.common') FILES+=('aliases') FILES+=('gitconfig') FILES+=('vimrc') @@ -93,25 +105,45 @@ do setup_file .dotfiles/$file .$file done +set +e +git_comp_filename=".git-completions.bash" +printf "${MAGENTA}==> ${NORMAL}Download git completions list to ${YELLOW}$PWD/$git_comp_filename${NORMAL}\n" +curl https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash -o $git_comp_filename +set -e + ######################### -# Setup non-root files +# Setup platform files ######################### + if [[ $is_windows -eq 1 ]]; then - setup_file .dotfiles/bash/bashrc .bashrc - setup_file .dotfiles/bash/bash_profile .bash_profile - setup_file .dotfiles/bash/inputrc .inputrc - setup_file .dotfiles/windows/gitconfig.platform .gitconfig.platform -fi - -curl https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash -o .git-completion.bash - -if [[ $is_windows -eq 0 ]]; then + printf "\n${BOLD}Setting up Windows${NORMAL}\n\n" + os_name="windows" + # Already using bash if running msys2. + setup_bash +elif [[ $is_macos -eq 1 ]]; then + printf "\n${BOLD}Setting up MacOS${NORMAL}\n\n" + os_name="osx" setup_zsh + setup_bash + use_shell zsh +elif [[ $is_linux -eq 1 ]]; then + printf "\n${BOLD}Setting up Linux${NORMAL}\n\n" + os_name="linux" + setup_zsh + setup_bash + use_shell bash fi -if [[ $is_macos -eq 1 ]]; then - printf "\n${BOLD}Running the MacOS installer${NORMAL}\n" - $cwd/osx/install +if [[ $os_name != "" ]]; then + if [ -f .dotfiles/$os_name/env.platform ]; then + setup_file .dotfiles/$os_name/env.platform .env.platform + fi + + if [ -f .dotfiles/$os_name/gitconfig.platform ]; then + setup_file .dotfiles/$os_name/gitconfig.platform .gitconfig.platform + fi + + $cwd/$os_name/install fi popd "$HOME" &>/dev/null diff --git a/linux/README.md b/linux/README.md new file mode 100644 index 0000000..66bcdaf --- /dev/null +++ b/linux/README.md @@ -0,0 +1,2 @@ +You may need to run `dos2unix` on this repo because of Windows line endings. There's a script in +this directory's bin folder called `dos2unix-recursive` which you can use to do the conversion. diff --git a/linux/bin/dos2unix-recursive b/linux/bin/dos2unix-recursive new file mode 100644 index 0000000..c27e311 --- /dev/null +++ b/linux/bin/dos2unix-recursive @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +find . -type f -print0 | xargs -0 dos2unix -f *.txt *.vim diff --git a/linux/bin/figt b/linux/bin/figt deleted file mode 100755 index a22b7b2..0000000 --- a/linux/bin/figt +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -LEIN_FAST_TRAMPOLINE=rlwrap lein trampoline figwheel "$@" diff --git a/linux/bin/fix-vm-resolution b/linux/bin/fix-vm-resolution new file mode 100644 index 0000000..df02b3c --- /dev/null +++ b/linux/bin/fix-vm-resolution @@ -0,0 +1,5 @@ +#!/bin/bash + +xrandr --newmode "1920x1080_60.00" 172.80 1920 2040 2248 2576 1080 1081 1084 1118 -HSync +Vsync +xrandr --addmode Virtual1 "1920x1080_60.00" +xrandr --output Virtual1 --mode "1920x1080_60.00" diff --git a/linux/bin/packer b/linux/bin/packer deleted file mode 100755 index d8d3291..0000000 --- a/linux/bin/packer +++ /dev/null @@ -1,807 +0,0 @@ -#!/bin/bash -# From https://github.com/keenerd/packer/blob/master/packer -# Use: package installer for Arch Linux -# -# Copyright Matthew Bruenig -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -[[ $PACMAN ]] || PACMAN="pacman" - -# set tmpfile stuff, clean tmpdir -tmpdir="${TMPDIR:-/tmp}/packertmp-$UID" -rm -rf "$tmpdir" &>/dev/null -mkdir -p "$tmpdir" - -makepkgconf='/etc/makepkg.conf' -usermakepkgconf="$HOME/.makepkg.conf" -pacmanconf='/etc/pacman.conf' - -RPCURL="https://aur.archlinux.org/rpc.php?type" -PKGURL="https://aur.archlinux.org" -PKGBURL="https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h" - -if [[ -t 1 && ! $COLOR = "NO" ]]; then - COLOR1='\e[1;39m' - COLOR2='\e[1;32m' - COLOR3='\e[1;35m' - COLOR4='\e[1;36m' - COLOR5='\e[1;34m' - COLOR6='\e[1;33m' - COLOR7='\e[1;31m' - ENDCOLOR='\e[0m' - S='\\' -fi -_WIDTH="$(stty size | cut -d ' ' -f 2)" - -trap ctrlc INT -ctrlc() { - echo - exit -} - -err() { - echo -e "$1" - exit 1 -} - -usage() { - echo 'usage: packer [option] [package] [package] [...]' - echo - echo ' -S - installs package' - echo ' -Syu|-Su - updates all packages, also takes -uu and -yy options' - echo ' -Ss|-Ssq - searches for package' - echo ' -Si - outputs info for package' - echo ' -G - download and extract aur tarball only' - echo - echo ' --quiet - only output package name for searches' - echo ' --ignore - takes a comma-separated list of packages to ignore' - echo ' --noconfirm - do not prompt for any confirmation' - echo ' --noedit - do not prompt to edit files' - echo ' --quickcheck - check for updates and exit' - echo ' --auronly - only do actions for aur' - echo ' --devel - update devel packages during -Su' - echo ' --skipinteg - when using makepkg, do not check md5s' - echo ' --preview - edit pkgbuild before sourcing' - echo ' -h - outputs this message' - exit -} - -# Called whenever anything needs to be run as root ($@ is the command) -runasroot() { - if [[ $UID -eq 0 ]]; then - "$@" - elif sudo -v &>/dev/null && sudo -l "$@" &>/dev/null; then - sudo -E "$@" - else - echo -n "root " - su -c "$(printf '%q ' "$@")" - fi -} - -# Source makepkg.conf file -sourcemakepkgconf() { - . "$makepkgconf" - [[ -r "$usermakepkgconf" ]] && . "$usermakepkgconf" -} - -# Parse IgnorePkg and --ignore, put in globally accessible ignoredpackages array -getignoredpackages() { - IFS=',' read -ra ignoredpackages <<< "$ignorearg" - ignoredpackages+=( $(grep '^ *IgnorePkg' "$pacmanconf" | cut -d '=' -f 2-) ) -} - -# Checks to see if $1 is an ignored package -isignored() { - [[ " ${ignoredpackages[@]} " =~ " $1 " ]] -} - -# Tests whether $1 exists on the aur -existsinaur() { - rpcinfo "$1" - [[ "$(jshon -Qe resultcount -u < "$tmpdir/$1.info")" != "0" ]] -} - -# Tests whether $1 exists in pacman -existsinpacman() { - pacman -Si -- "$1" &>/dev/null -} - -# Tests whether $1 is provided in pacman, sets globally accessibly providepkg var -providedinpacman() { - unset providepkg - IFS=$'\n' read -rd '' -a providepkg < <(pacman -Ssq -- "^$1$") - [[ -n $providepkg ]] -} - -# Tests whether $1 exists in a pacman group -existsinpacmangroup() { - [[ $(pacman -Sgq "$1") ]] -} - -# Tests whether $1 exists locally -existsinlocal() { - pacman -Qq -- "$1" &>/dev/null -} - -# Scrapes the aur deps from PKGBUILDS and puts in globally available dependencies array -scrapeaurdeps() { - pkginfo "$1" "$preview" - . "$tmpdir/$1.PKGBUILD" - IFS=$'\n' - dependencies=( $(echo -e "${depends[*]}\n${makedepends[*]}" | sed -e 's/=.*//' -e 's/>.*//' -e 's/<.*//'| sort -u) ) - unset IFS -} - -# Finds dependencies of package $1 -# Sets pacmandeps and aurdeps array, which can be accessed globally after function runs -finddeps() { - # loop through dependencies, if not installed, determine if pacman or aur deps - pacmandeps=() - aurdeps=() - scrapeaurdeps "$1" - missingdeps=( $(pacman -T "${dependencies[@]}") ) - while [[ $missingdeps ]]; do - checkdeps=() - for dep in "${missingdeps[@]}"; do - if [[ " $1 ${aurdeps[@]} ${pacmandeps[@]} " =~ " $dep " ]]; then - continue - fi - if existsinpacman "$dep"; then - pacmandeps+=("$dep") - elif existsinaur "$dep"; then - if [[ $aurdeps ]]; then - aurdeps=("$dep" "${aurdeps[@]}") - else - aurdeps=("$dep") - fi - checkdeps+=("$dep") - elif providedinpacman "$dep"; then - pacmandeps+=("$providepkg") - else - [[ $option = "install" ]] && err "Dependency \`$dep' of \`$1' does not exist." - echo "Dependency \`$dep' of \`$1' does not exist." - return 1 - fi - done - missingdeps=() - for dep in "${checkdeps[@]}"; do - scrapeaurdeps "$dep" - for depdep in "${dependencies[@]}"; do - [[ $(pacman -T "$depdep") ]] && missingdeps+=("$depdep") - done - done - done - return 0 -} - -# Displays a progress bar ($1 is numerator, $2 is denominator, $3 is candy/normal) -aurbar() { - # Delete line - printf "\033[0G" - - # Get vars for output - beginline=" aur" - beginbar="[" - endbar="] " - perc="$(($1*100/$2))" - width="$(stty size)" - width="${width##* }" - charsbefore="$((${#beginline}+${#1}+${#2}+${#beginbar}+3))" - spaces="$((51-$charsbefore))" - barchars="$(($width-51-7))" - hashes="$(($barchars*$perc/100))" - dashes="$(($barchars-$hashes))" - - # Print output - printf "$beginline %${spaces}s$1 $2 ${beginbar}" "" - - # ILoveCandy - if [[ $3 = candy ]]; then - for ((n=1; n<$hashes; n++)); do - if (( (n==($hashes-1)) && ($dashes!=0) )); then - (($n%2==0)) && printf "\e[1;33mc\e[0m" || printf "\e[1;33mC\e[0m" - else - printf "-" - fi - done - for ((n=1; n<$dashes; n++)); do - N=$(( $n+$hashes )) - (($hashes>0)) && N=$(($N-1)) - (($N%3==0)) && printf "o" || printf " " - done - else - for ((n=0; n<$hashes; n++)); do - printf "#" - done - for ((n=0; n<$dashes; n++)); do - printf "-" - done - fi - printf "%s%3s%%\r" ${endbar} ${perc} -} - -rpcinfo() { - if ! [[ -f "$tmpdir/$1.info" ]]; then - curl -LfGs --data-urlencode "arg=$1" "$RPCURL=info" > "$tmpdir/$1.info" - fi -} - -pkglink() { - rpcinfo $1 - echo "${PKGURL}$(jshon -Q -e results -e URLPath -u < "$tmpdir/$1.info")" -} - -# downloads pkgbuild ($1), edits if $2 is set -pkginfo() { - if ! [[ -f "$tmpdir/$1.PKGBUILD" ]]; then - curl -Lfs "${PKGBURL}=${1}" > "$tmpdir/$1.PKGBUILD" - if [[ $2 ]]; then - # rename for syntax highlighting - cp "$tmpdir/$1.PKGBUILD" "$tmpdir/PKGBUILD" - confirm_edit "${COLOR6}Edit $1 PKGBUILD with \$EDITOR? [Y/n]${ENDCOLOR} " "$tmpdir/PKGBUILD" - mv "$tmpdir/PKGBUILD" "$tmpdir/$1.PKGBUILD" - fi - fi -} - -# Checks if package is newer on aur ($1 is package name, $2 is local version) -aurversionisnewer() { - rpcinfo "$1" - unset aurversion - if existsinaur "$1"; then - aurversion="$(jshon -Q -e results -e Version -u < "$tmpdir/$1.info")" - if [[ "$(LC_ALL=C vercmp "$aurversion" "$2")" -gt 0 ]]; then - return 0 - fi - fi - return 1 -} - -isoutofdate() { - rpcinfo "$1" - [[ "$(jshon -Q -e results -e OutOfDate -u < "$tmpdir/$1.info")" = "1" ]] -} - -# $1 is prompt, $2 is file -confirm_edit() { - if [[ (! -f "$2") || "$noconfirm" || "$noedit" ]]; then - return - fi - echo -en "$1" - if proceed; then - ${EDITOR:-vi} "$2" - fi -} - -# Installs packages from aur ($1 is package, $2 is dependency or explicit) -aurinstall() { - dir="${TMPDIR:-/tmp}/packerbuild-$UID/$1" - sourcemakepkgconf - - # Prepare the installation directory - # If there is an old directory and aurversion is not newer, use old directory - if . "$dir/$1/PKGBUILD" &>/dev/null && ! aurversionisnewer "$1" "$pkgver-$pkgrel"; then - cd "$dir/$1" - else - [[ -d "$dir" ]] && rm -rf "$dir" - mkdir -p "$dir" - cd "$dir" - curl -Lfs "$(pkglink $1)" > "$1.tar.gz" - mkdir "$1" - tar xf "$1.tar.gz" -C "$1" --strip-components=1 - cd "$1" - if [[ $preview && -s "$tmpdir/$1.PKGBUILD" ]]; then - cp "$tmpdir/$1.PKGBUILD" PKGBUILD - fi - # customizepkg - if [[ -f "/etc/customizepkg.d/$1" ]] && type -t customizepkg &>/dev/null; then - echo "Applying customizepkg instructions..." - customizepkg --modify - fi - fi - - # check for missing arch - if [[ ! " ${arch[@]} " =~ " any " ]]; then - if [[ ! " ${arch[@]} " =~ " ${CARCH} " ]]; then - echo -e "${COLOR6}warning:$ENDCOLOR $CARCH missing from arch array" - fi - fi - - # Allow user to edit PKGBUILD - confirm_edit "${COLOR6}Edit $1 PKGBUILD with \$EDITOR? [Y/n]${ENDCOLOR} " PKGBUILD - if ! [[ -f PKGBUILD ]]; then - err "No PKGBUILD found in directory." - fi - - # Allow user to edit .install - unset install - . PKGBUILD - confirm_edit "${COLOR6}Edit $install with \$EDITOR? [Y/n]${ENDCOLOR} " "$install" - - # Installation (makepkg and pacman) - rm -f *$PKGEXT - if [[ $UID -eq 0 ]]; then - makepkg $MAKEPKGOPTS --asroot -f - else - makepkg $MAKEPKGOPTS -f - fi - - [[ $? -ne 0 ]] && echo "The build failed." && return 1 - pkgtarfiles="" - for i in "${pkgname[@]}"; do - pkgtarfiles="$pkgtarfiles $i-*$PKGEXT" - done - if [[ $2 = dependency ]]; then - runasroot $PACMAN ${PACOPTS[@]} --asdeps -U $pkgtarfiles - elif [[ $2 = explicit ]]; then - runasroot $PACMAN ${PACOPTS[@]} -U $pkgtarfiles - fi -} - -# Goes through all of the install tests and execution ($@ is packages to be installed) -installhandling() { - packageargs=("$@") - getignoredpackages - sourcemakepkgconf - # Figure out all of the packages that need to be installed - for package in "${packageargs[@]}"; do - # Determine whether package is in pacman repos - if ! [[ $auronly ]] && existsinpacman "$package"; then - pacmanpackages+=("$package") - elif ! [[ $auronly ]] && existsinpacmangroup "$package"; then - pacmanpackages+=("$package") - elif existsinaur "$package"; then - if finddeps "$package"; then - # here is where dep dupes are created - aurpackages+=("$package") - aurdepends=("${aurdeps[@]}" "${aurdepends[@]}") - pacmandepends+=("${pacmandeps[@]}") - fi - else - err "Package \`$package' does not exist." - fi - done - - # Check if any aur target packages are ignored - for package in "${aurpackages[@]}"; do - if isignored "$package"; then - echo -ne "${COLOR5}:: ${COLOR1}$package is in IgnorePkg/IgnoreGroup. Install anyway?${ENDCOLOR} [Y/n] " - if ! [[ $noconfirm ]]; then - proceed || continue - else - echo - fi - fi - aurtargets+=("$package") - done - - # Check if any aur dependencies are ignored - for package in "${aurdepends[@]}"; do - if isignored "$package"; then - echo -ne "${COLOR5}:: ${COLOR1}$package is in IgnorePkg/IgnoreGroup. Install anyway?${ENDCOLOR} [Y/n] " - if ! [[ $noconfirm ]]; then - if ! proceed; then - echo "Unresolved dependency \`$package'" - unset aurtargets - break - fi - else - echo - fi - fi - done - - # First install the explicit pacman packages, let pacman prompt - if [[ $pacmanpackages ]]; then - runasroot $PACMAN "${PACOPTS[@]}" -S -- "${pacmanpackages[@]}" - fi - if [[ -z $aurtargets ]]; then - exit - fi - # Test if aurpackages are already installed; echo warning if so - for pkg in "${aurtargets[@]}"; do - if existsinlocal "$pkg"; then - localversion="$(pacman -Qs "$pkg" | grep -F "local/$pkg" | cut -d ' ' -f 2)" - if ! aurversionisnewer "$pkg" "$localversion"; then - echo -e "${COLOR6}warning:$ENDCOLOR $pkg-$localversion is up to date -- reinstalling" - fi - fi - done - - # Echo warning if packages are out of date - for pkg in "${aurtargets[@]}" "${aurdepends[@]}"; do - if isoutofdate "$pkg"; then - echo -e "${COLOR6}warning:$ENDCOLOR $pkg is flagged out of date" - fi - done - - # Prompt for aur packages and their dependencies - echo - if [[ $aurdepends ]]; then - num="$((${#aurdepends[@]}+${#aurtargets[@]}))" - echo -e "${COLOR6}Aur Targets ($num):${ENDCOLOR} ${aurdepends[@]} ${aurtargets[@]}" - else - echo -e "${COLOR6}Aur Targets ($((${#aurtargets[@]}))):${ENDCOLOR} ${aurtargets[@]}" - fi - if [[ $pacmandepends ]]; then - IFS=$'\n' read -rd '' -a pacmandepends < \ - <(printf "%s\n" "${pacmandepends[@]}" | sort -u) - echo -e "${COLOR6}Pacman Targets (${#pacmandepends[@]}):${ENDCOLOR} ${pacmandepends[@]}" - fi - - # Prompt to proceed - echo -en "\nProceed with installation? [Y/n] " - if ! [[ $noconfirm ]]; then - proceed || exit - else - echo - fi - - # Install pacman dependencies - if [[ $pacmandepends ]]; then - runasroot $PACMAN --noconfirm --asdeps -S -- "${pacmandepends[@]}" || err "Installation failed." - fi - - # Install aur dependencies - if [[ $aurdepends ]]; then - for dep in "${aurdepends[@]}"; do - aurinstall "$dep" "dependency" - done - fi - - # Install the aur packages - for package in "${aurtargets[@]}"; do - scrapeaurdeps "$package" - if pacman -T "${dependencies[@]}" &>/dev/null; then - aurinstall "$package" "explicit" - else - echo "Dependencies for \`$package' are not met, not building..." - fi - done -} - -run_quick_check() { - bigurl="https://aur.archlinux.org/rpc.php?type=multiinfo" - for p in $(pacman -Qqm); do - bigurl="$bigurl&arg\[\]=$p" - done - parsed_aur="$(curl -s "$bigurl" | \ - jshon -e results -a -e Name -u -p -e Version -u | \ - sed 's/^$/-/' | paste -s -d '\t\n' | sort)" - packages="$(expac -Q '%n\t%v' | sort)" - comm -23 <(echo "$parsed_aur") <(echo "$packages") | cut -f 1 - if [[ $auronly == 1 ]]; then - return - fi - # see https://mailman.archlinux.org/pipermail/pacman-dev/2011-October/014673.html - # (note to self, get that merged already...) - if [[ -z $CHECKUPDATE_DB ]]; then - CHECKUPDATE_DB="${TMPDIR:-/tmp}/checkup-db-${USER}/" - fi - eval $(awk '/DBPath/ {print $1$2$3}' $pacmanconf) - DBPath="${DBPath:-/var/lib/pacman/}" - mkdir -p "$CHECKUPDATE_DB" - ln -s "${DBPath}/local" "$CHECKUPDATE_DB" &> /dev/null - fakeroot pacman -Sqy --dbpath "$CHECKUPDATE_DB" &> /dev/null - pacman -Qqu --dbpath "$CHECKUPDATE_DB" 2> /dev/null -} - -# proceed with installation prompt -proceed() { - read -n 1 - echo - case "$REPLY" in - 'Y'|'y'|'') return 0 ;; - *) return 1 ;; - esac -} - -# process busy loop -nap() { - while (( $(jobs | wc -l) >= 8 )); do - jobs > /dev/null - done -} - -# Argument parsing -[[ $1 ]] || usage -packageargs=() -while [[ $1 ]]; do - case "$1" in - '-S') option=install ;; - '-Ss') option=search ;; - '-Ssq'|'-Sqs') option=search ; quiet='1' ;; - '-Si') option=info ;; - -S*u*) option=update ; pacmanarg="$1" ;; - '-G') option=download ;; - '-h'|'--help') usage ;; - '--quiet') quiet='1' ;; - '--ignore') ignorearg="$2" ; PACOPTS+=("--ignore" "$2") ; shift ;; - '--noconfirm') noconfirm='1' PACOPTS+=("--noconfirm");; - '--noedit') noedit='1' ;; - '--auronly') auronly='1' ;; - '--quickcheck') quickcheck='1' ;; - '--devel') devel='1' ;; - '--skipinteg') MAKEPKGOPTS="--skipinteg" ;; - '--preview') preview='1' ;; - '--') shift ; packageargs+=("$@") ; break ;; - -*) echo "packer: Option \`$1' is not valid." ; exit 5 ;; - *) packageargs+=("$1") ;; - esac - shift -done - -# check for new packages -if [[ $quickcheck == 1 ]]; then - run_quick_check - exit -fi - -# Sanity checks -[[ $option ]] || option="searchinstall" -[[ $option != "update" && -z $packageargs ]] && err "Must specify a package." - -# Install (-S) handling -if [[ $option = install ]]; then - installhandling "${packageargs[@]}" - exit -fi - -# Update (-Su) handling -if [[ $option = update ]]; then - getignoredpackages - sourcemakepkgconf - # Pacman update - if ! [[ $auronly ]]; then - runasroot $PACMAN "${PACOPTS[@]}" "$pacmanarg" - fi - - # Aur update - echo -e "${COLOR5}:: ${COLOR1}Synchronizing aur database...${ENDCOLOR}" - IFS=$'\n' read -rd '' -a packages < <(pacman -Qm) - newpackages=() - checkignores=() - total="${#packages[@]}" - grep -q '^ *ILoveCandy' "$pacmanconf" && bartype='candy' || bartype='normal' - - if [[ $devel ]]; then - for ((i=0; i<$total; i++)); do - aurbar "$((i+1))" "$total" "$bartype" - pkg="${packages[i]%% *}" - if isignored "$pkg"; then - checkignores+=("${packages[i]}") - continue - fi - pkginfo "$pkg" & - nap - done - wait - for ((i=0; i<$total; i++)); do - pkg="${packages[i]%% *}" - ver="${packages[i]##* }" - if [[ ! -s "$tmpdir/$pkg.PKGBUILD" ]]; then - continue - fi - if isignored "$pkg"; then - continue - fi - unset _darcstrunk _cvsroot _gitroot _svntrunk _bzrtrunk _hgroot - . "$tmpdir/$pkg.PKGBUILD" - if [[ "$(LC_ALL=C vercmp "$pkgver-$pkgrel" "$ver")" -gt 0 ]]; then - newpackages+=("$pkg") - elif [[ ${_darcstrunk} || ${_cvsroot} || ${_gitroot} || ${_svntrunk} || ${_bzrtrunk} || ${_hgroot} ]]; then - newpackages+=("$pkg") - fi - done - else - for ((i=0; i<$total; i++)); do - aurbar "$((i+1))" "$total" "$bartype" - pkg="${packages[i]%% *}" - rpcinfo "$pkg" & - nap - done - wait - for ((i=0; i<$total; i++)); do - pkg="${packages[i]%% *}" - ver="${packages[i]##* }" - if isignored "$pkg"; then - checkignores+=("${packages[i]}") - elif aurversionisnewer "$pkg" "$ver"; then - newpackages+=("$pkg") - fi - done - fi - echo - - echo -e "${COLOR5}:: ${COLOR1}Starting full aur upgrade...${ENDCOLOR}" - - # Check and output ignored package update info - for package in "${checkignores[@]}"; do - if aurversionisnewer "${package%% *}" "${package##* }"; then - echo -e "${COLOR6}warning:${ENDCOLOR} ${package%% *}: ignoring package upgrade (${package##* } => $aurversion)" - fi - done - - # Now for the installation part - if [[ $newpackages ]]; then - auronly='1' - installhandling "${newpackages[@]}" - fi - echo " local database is up to date" -fi - -# Download (-G) handling -if [[ $option = download ]]; then - for package in "${packageargs[@]}"; do - if existsinaur "$package"; then - pkglist+=("$package") - else - err "Package \`$package' does not exist on aur." - fi - done - - for package in "${pkglist[@]}"; do - curl -Lfs "$(pkglink $package)" > "$package.tar.gz" - mkdir "$package" - tar xf "$package.tar.gz" -C "$package" --strip-components=1 - done -fi - -# Search (-Ss) handling -if [[ $option = search || $option = searchinstall ]]; then - # Pacman searching - if ! [[ $auronly ]]; then - if [[ $quiet ]]; then - results="$(pacman -Ssq -- "${packageargs[@]}")" - else - results="$(pacman -Ss -- "${packageargs[@]}")" - results="$(sed -r "s|^[^ ][^/]*/|$S${COLOR3}&$S${COLOR1}|" <<< "$results")" - results="$(sed -r "s|^([^ ]+) ([^ ]+)(.*)$|\1 $S${COLOR2}\2$S${ENDCOLOR}\3|" <<< "$results")" - fi - if [[ $option = search ]]; then - echo -e "$results" | fmt -"$_WIDTH" -s - else # interactive - echo -e "$results" | fmt -"$_WIDTH" -s | nl -v 0 -w 1 -s ' ' -b 'p^[^ ]' - fi | sed '/^$/d' - pacname=( $(pacman -Ssq -- "${packageargs[@]}") ) - pactotal="${#pacname[@]}" - else - pactotal=0 - fi - - # Aur searching and tmpfile preparation - for package in "${packageargs[@]}"; do - curl -LfGs --data-urlencode "arg=$package" "$RPCURL=search" | \ - jshon -Q -e results -a -e Name -u -p -e Version -u -p -e NumVotes -u -p -e Description -u | \ - sed 's/^$/-/' | paste -s -d "\t\t\t\n" | sort -nr -k 3 > "$tmpdir/$package.search" & - done - wait - cp "$tmpdir/${packageargs[0]}.search" "$tmpdir/search.results" - for ((i=1 ; i<${#packageargs[@]} ; i++)); do - grep -xFf "$tmpdir/search.results" "$tmpdir/${packageargs[$i]}.search" > "$tmpdir/search.results-2" - mv "$tmpdir/search.results-2" "$tmpdir/search.results" - done - sed -i '/^$/d' "$tmpdir/search.results" - - # Prepare tmp file and arrays - IFS=$'\n' read -rd '' -a aurname < <(cut -f 1 "$tmpdir/search.results") - aurtotal="${#aurname[@]}" - alltotal="$(($pactotal+$aurtotal))" - # Echo out the -Ss formatted package information - - IFS=$'\t\n' - if [[ $option = search ]]; then - if [[ $quiet ]]; then - printf "%s\n" ${aurname[@]} - elif [[ -s "$tmpdir/search.results" ]]; then - printf "${COLOR3}aur/${COLOR1}%s ${COLOR2}%s${ENDCOLOR} (%s)\n %s\n" $(cat "$tmpdir/search.results") - fi - else - # interactive - if [[ $quiet ]]; then - nl -v ${pactotal:-0} -w 1 -s ' ' <(cut -f 1 "$tmpdir/search.results") - elif [[ -s "$tmpdir/search.results" ]]; then - printf "%d ${COLOR3}aur/${COLOR1}%s ${COLOR2}%s${ENDCOLOR} (%s)\n %s\n" $(nl -v ${pactotal:-0} -w 1 < "$tmpdir/search.results") - fi - fi | fmt -"$_WIDTH" -s - unset IFS - - # Prompt and install selected numbers - if [[ $option = searchinstall ]]; then - pkglist=() - allpackages=( "${pacname[@]}" "${aurname[@]}" ) - - # Exit if there are no matches - [[ $allpackages ]] || exit - - # Prompt for numbers - echo - echo -e "${COLOR2}Type numbers to install. Separate each number with a space.${ENDCOLOR}" - echo -ne "${COLOR2}Numbers: ${ENDCOLOR}" - read -r - - # Parse answer - if [[ $REPLY ]]; then - for num in $REPLY; do - if [[ $num -lt $alltotal ]]; then - pkglist+=("${allpackages[$num]}") - else - err "Number \`$num' is not assigned to any of the packages." - fi - done - fi - - # Call installhandling to take care of the packages chosen - installhandling "${pkglist[@]}" - fi - - # Remove the tmpfiles - rm -f "$tmpdir/*search" &>/dev/null - rm -f "$tmpdir/search.result" &>/dev/null - exit -fi - -# Info (-Si) handling -if [[ $option = info ]]; then - # Pacman info check - sourcemakepkgconf - for package in "${packageargs[@]}"; do - if ! [[ $auronly ]] && existsinpacman "$package"; then - results="$(pacman -Si -- "$package")" - results="$(sed -r "s|^(Repository[^:]*:)(.*)$|\1$S${COLOR3}\2$S${ENDCOLOR}|" <<< "$results")" - results="$(sed -r "s|^(Name[^:]*:)(.*)$|\1$S${COLOR1}\2$S${ENDCOLOR}|" <<< "$results")" - results="$(sed -r "s|^(Version[^:]*:)(.*)$|\1$S${COLOR2}\2$S${ENDCOLOR}|" <<< "$results")" - results="$(sed -r "s|^(URL[^:]*:)(.*)$|\1$S${COLOR4}\2$S${ENDCOLOR}|" <<< "$results")" - results="$(sed -r "s|^[^ ][^:]*:|$S${COLOR1}&$S${ENDCOLOR}|" <<< "$results")" - echo -e "$results" - exit - else # Check to see if it is in the aur - pkginfo "$package" "$preview" - [[ -s "$tmpdir/$package.PKGBUILD" ]] || err "${COLOR7}error:${ENDCOLOR} package '$package' was not found" - . "$tmpdir/$package.PKGBUILD" - - # Echo out the -Si formatted package information - # Retrieve each element in order and echo them immediately - echo -e "${COLOR1}Repository : ${COLOR3}aur" - echo -e "${COLOR1}Name : $pkgname" - echo -e "${COLOR1}Version : ${COLOR2}$pkgver-$pkgrel" - echo -e "${COLOR1}URL : ${COLOR4}$url" - echo -e "${COLOR1}Licenses : ${ENDCOLOR}${license[@]}" - echo -e "${COLOR1}Groups : ${ENDCOLOR}${groups[@]:-None}" - echo -e "${COLOR1}Provides : ${ENDCOLOR}${provides[@]:-None}" - echo -e "${COLOR1}Depends On : ${ENDCOLOR}${depends[@]}" - echo -e "${COLOR1}Make Depends : ${ENDCOLOR}${makedepends[@]}" - echo -e -n "${COLOR1}Optional Deps : ${ENDCOLOR}" - - len="${#optdepends[@]}" - if [[ $len -eq 0 ]]; then - echo "None" - else - for ((i=0 ; i<$len ; i++)); do - if [[ $i = 0 ]]; then - echo "${optdepends[$i]}" - else - echo -e " ${optdepends[$i]}" - fi - done - fi - - echo -e "${COLOR1}Conflicts With : ${ENDCOLOR}${conflicts[@]:-None}" - echo -e "${COLOR1}Replaces : ${ENDCOLOR}${replaces[@]:-None}" - echo -e "${COLOR1}Architecture : ${ENDCOLOR}${arch[@]}" - echo -e "${COLOR1}Description : ${ENDCOLOR}$pkgdesc" - echo - fi - done -fi diff --git a/linux/bin/selecta b/linux/bin/selecta deleted file mode 100755 index 7ff7096..0000000 --- a/linux/bin/selecta +++ /dev/null @@ -1,818 +0,0 @@ -#!/usr/bin/env bash -# vim: set ft=ruby: - -# This file executes as a bash script, which turns around and executes Ruby via -# the line below. The -x argument to Ruby makes it discard everything before -# the second "!ruby" shebang. This allows us to work on Linux, where the -# shebang can only have one argument so we can't directly say -# "#!/usr/bin/env ruby --disable-gems". Thanks for that, Linux. -# -# If this seems confusing, don't worry. You can treat it as a normal Ruby file -# starting with the "!ruby" shebang below. - -exec /usr/bin/env ruby --disable-gems -x "$0" $* -#!ruby - -if RUBY_VERSION < '1.9.3' - abort "error: Selecta requires Ruby 1.9.3 or higher." -end - -require "optparse" -require "io/console" -require "io/wait" -require "set" - -KEY_CTRL_C = ?\C-c -KEY_CTRL_N = ?\C-n -KEY_CTRL_P = ?\C-p -KEY_CTRL_U = ?\C-u -KEY_CTRL_H = ?\C-h -KEY_CTRL_W = ?\C-w -KEY_CTRL_J = ?\C-j -KEY_CTRL_M = ?\C-m -KEY_DELETE = 127.chr # Equivalent to ?\C-? - -class Selecta - VERSION = [0, 0, 6] - - def main - # We have to parse options before setting up the screen or trying to read - # the input in case the user did '-h', an invalid option, etc. and we need - # to terminate. - options = Configuration.parse_options(ARGV) - input_lines = $stdin.readlines - - search = Screen.with_screen do |screen, tty| - config = Configuration.from_inputs(input_lines, options, screen.height) - run_in_screen(config, screen, tty) - end - - unless search.selection == Search::NoSelection - puts search.selection - end - rescue Screen::NotATTY - $stderr.puts( - "Can't get a working TTY. Selecta requires an ANSI-compatible terminal.") - exit(1) - rescue Abort - # We were aborted via ^C. - # - # If we didn't mess with the TTY configuration at all, then ^C would send - # SIGINT to the entire process group. That would terminate both Selecta and - # anything piped into or out of it. Because Selecta puts the terminal in - # raw mode, that doesn't happen; instead, we detect the ^C as normal input - # and raise Abort, which leads here. - # - # To make pipelines involving Selecta behave as people expect, we send - # SIGINT to our own process group, which should exactly match what termios - # would do to us if the terminal weren't in raw mode. "Should!" <- Remove - # those scare quotes if ten years pass without this breaking! - # - # The SIGINT will cause Ruby to raise Interrupt, so we also have to handle - # that here. - begin - Process.kill("INT", -Process.getpgrp) - rescue Interrupt - exit(1) - end - end - - def run_in_screen(config, screen, tty) - search = Search.from_config(config) - - # We emit the number of lines we'll use later so we don't clobber whatever - # was already on the screen. - config.visible_choices.times { tty.puts } - begin - search = ui_event_loop(search, screen, tty) - ensure - # Always move the cursor to the bottom so the next program doesn't draw - # over whatever we left on the screen. - screen.move_cursor(screen.height - 1, 0) - end - search - end - - # Use the search and screen to process user actions until they quit. - def ui_event_loop(search, screen, tty) - while not search.done? - Renderer.render!(search, screen) - search = handle_keys(search, tty) - end - search - end - - def handle_keys(search, tty) - new_query_chars = "" - - # Read through all of the buffered input characters. Process control - # characters immediately. Save any query characters to be processed - # together at the end, since there's no reason to process intermediate - # results when there are more characters already buffered. - tty.get_available_input.chars.each do |char| - is_query_char = !!(char =~ /[[:print:]]/) - if is_query_char - new_query_chars << char - else - search = handle_control_character(search, char) - end - end - - if new_query_chars.empty? - search - else - search.append_search_string(new_query_chars) - end - end - - # On each keystroke, generate a new search object - def handle_control_character(search, key) - case key - - when KEY_CTRL_N then search.down - when KEY_CTRL_P then search.up - - when KEY_CTRL_U then search.clear_query - when KEY_CTRL_W then search.delete_word - when KEY_CTRL_H, KEY_DELETE then search.backspace - - when ?\r, KEY_CTRL_J, KEY_CTRL_M then search.done - - when KEY_CTRL_C then raise Abort - - else search - end - end - - class Abort < RuntimeError; end -end - -class Configuration < Struct.new(:visible_choices, :initial_search, :choices) - def initialize(visible_choices, initialize, choices) - # Constructor is defined to force argument presence; otherwise Struct - # defaults missing arguments to nil - super - end - - def self.from_inputs(choices, options, screen_height=21) - # Shrink the number of visible choices if the screen is too small - visible_choices = [20, screen_height - 1].min - - choices = massage_choices(choices) - Configuration.new(visible_choices, options.fetch(:search), choices) - end - - def self.default_options - parse_options([]) - end - - def self.parse_options(argv) - options = {} - - parser = OptionParser.new do |opts| - opts.banner = "Usage: #{$PROGRAM_NAME} [options]" - - opts.on_tail("-h", "--help", "Show this message") do |v| - puts opts - exit - end - - opts.on_tail("--version", "Show version") do - puts Selecta::VERSION.join('.') - exit - end - - options[:search] = "" - opts.on("-s", "--search SEARCH", "Specify an initial search string") do |search| - options[:search] = search - end - end - - begin - parser.parse!(argv) - rescue OptionParser::InvalidOption => e - $stderr.puts e - $stderr.puts parser - exit 1 - end - - options - end - - def self.massage_choices(choices) - choices.map do |choice| - # Encoding to UTF-8 with `:invalid => :replace` isn't good enough; it - # still leaves some invalid characters. For example, this string will fail: - # - # echo "девуш\xD0:" | selecta - # - # Round-tripping through UTF-16, with `:invalid => :replace` as well, - # fixes this. I don't understand why. I found it via: - # - # http://stackoverflow.com/questions/2982677/ruby-1-9-invalid-byte-sequence-in-utf-8 - if choice.valid_encoding? - choice - else - utf16 = choice.encode('UTF-16', 'UTF-8', :invalid => :replace, :replace => '') - utf16.encode('UTF-8', 'UTF-16') - end.strip - end - end -end - -class Search - attr_reader :index, :query, :config, :original_matches, :all_matches, :best_matches - - def initialize(vars) - @config = vars.fetch(:config) - @index = vars.fetch(:index) - @query = vars.fetch(:query) - @done = vars.fetch(:done) - @original_matches = vars.fetch(:original_matches) - @all_matches = vars.fetch(:all_matches) - @best_matches = vars.fetch(:best_matches) - @vars = vars - end - - def self.from_config(config) - trivial_matches = config.choices.reject(&:empty?).map do |choice| - Match.trivial(choice) - end - - search = new(:config => config, - :index => 0, - :query => "", - :done => false, - :original_matches => trivial_matches, - :all_matches => trivial_matches, - :best_matches => trivial_matches) - - if config.initial_search.empty? - search - else - search.append_search_string(config.initial_search) - end - end - - # Construct a new Search by merging in a hash of changes. - def merge(changes) - vars = @vars.merge(changes) - - # If the query changed, throw away the old matches so that new ones will be - # computed. - matches_are_stale = vars.fetch(:query) != @query - if matches_are_stale - vars = vars.reject { |key| key == :matches } - end - - Search.new(vars) - end - - def done? - @done - end - - def selection - if @aborted - NoSelection - else - match = best_matches.fetch(@index) { NoSelection } - if match == NoSelection - match - else - match.original_choice - end - end - end - - def down - move_cursor(1) - end - - def up - move_cursor(-1) - end - - def max_visible_choices - [@config.visible_choices, all_matches.count].min - end - - def append_search_string(string) - merge(:index => 0, - :query => @query + string) - .recompute_matches(all_matches) - end - - def backspace - merge(:index => 0, - :query => @query[0...-1]) - .recompute_matches - end - - def clear_query - merge(:index => 0, - :query => "") - .recompute_matches - end - - def delete_word - merge(:index => 0, - :query => @query.sub(/[^ ]* *$/, "")) - .recompute_matches - end - - def done - merge(:done => true) - end - - def abort - merge(:aborted => true) - end - - def recompute_matches(previous_matches=self.original_matches) - if self.query.empty? - merge(:all_matches => original_matches, - :best_matches => original_matches) - else - all_matches = recompute_all_matches(previous_matches) - best_matches = recompute_best_matches(all_matches) - merge(:all_matches => all_matches, :best_matches => best_matches) - end - end - - private - - def recompute_all_matches(previous_matches) - query = self.query.downcase - query_chars = query.chars.to_a - - matches = previous_matches.map do |match| - choice = match.choice - score, range = Score.score(choice, query_chars) - range ? match.refine(score, range) : nil - end.compact - end - - def recompute_best_matches(all_matches) - return [] if all_matches.empty? - - count = [@config.visible_choices, all_matches.count].min - matches = [] - - best_score = all_matches.min_by(&:score).score - - # Consider matches, beginning with the best-scoring. A match always ranks - # higher than other matches with worse scores. However, the ranking between - # matches of the same score depends on other factors, so we always have to - # consider all matches of a given score. - (best_score..Float::INFINITY).each do |score| - matches += all_matches.select { |match| match.score == score } - # Stop if we have enough matches. - return sub_sort_matches(matches)[0, count] if matches.length >= count - end - end - - def sub_sort_matches(matches) - matches.sort_by do |match| - [match.score, match.matching_range.count, match.choice.length] - end - end - - def move_cursor(direction) - if max_visible_choices > 0 - index = (@index + direction) % max_visible_choices - merge(:index => index) - else - self - end - end - - class NoSelection; end -end - -class Match < Struct.new(:original_choice, :choice, :score, :matching_range) - def self.trivial(choice) - empty_range = (0...0) - new(choice, choice.downcase, 0, empty_range) - end - - def to_text - if matching_range.none? - Text[original_choice] - else - before = original_choice[0...matching_range.begin] - matching = original_choice[matching_range.begin..matching_range.end] - after = original_choice[(matching_range.end + 1)..-1] - Text[before, :red, matching, :default, after] - end - end - - def refine(score, range) - Match.new(original_choice, choice, score, range) - end -end - -class Score - class << self - # A word boundary character is any ASCII character that's not alphanumeric. - # This isn't strictly correct: characters like ZERO WIDTH NON-JOINER, - # non-Latin punctuation, etc. will be incorrectly treated as non-boundary - # characters. This is necessary for performance: even building a Set of - # boundary characters based only on the input text is prohibitively slow (2-3 - # seconds for 80,000 input paths on a 2014 MacBook Pro). - BOUNDARY_CHARS = (0..127).map(&:chr).select do |char| - char !~ /[A-Za-z0-9_]/ - end.to_set - - def score(string, query_chars) - first_char, *rest = query_chars - - # Keep track of the best match that we've seen. This is uglier than - # building a list of matches and then sorting them, but it's faster. - best_score = Float::INFINITY - best_range = nil - - # Iterate over each instance of the first query character. E.g., if we're - # querying the string "axbx" for "x", we'll start at index 1 and index 3. - each_index_of_char_in_string(string, first_char) do |first_index| - score = 1 - - # Find the best score starting at this index. - score, last_index = find_end_of_match(string, rest, score, first_index) - - # Did we do better than we have for the best starting point so far? - if last_index && score < best_score - best_score = score - best_range = (first_index..last_index) - end - end - - [best_score, best_range] - end - - # Find all occurrences of the character in the string, returning their indexes. - def each_index_of_char_in_string(string, char) - index = 0 - while index - index = string.index(char, index) - if index - yield index - index += 1 - end - end - end - - # Find each of the characters in the string, moving strictly left to right. - def find_end_of_match(string, chars, score, first_index) - last_index = first_index - - # Remember the type of the last character match for special scoring. - last_type = nil - - chars.each do |this_char| - # Where's the next occurrence of this character? The optimal algorithm - # would consider all instances of query character, but that's slower - # than this eager method. - index = string.index(this_char, last_index + 1) - - # This character doesn't occur in the string, so this can't be a match. - return [nil, nil] unless index - - if index == last_index + 1 - # This matching character immediately follows the last matching - # character. The first two sequential characters score; subsequent - # ones don't. - if last_type != :sequential - last_type = :sequential - score += 1 - end - # This character follows a boundary character. - elsif BOUNDARY_CHARS.include?(string[index - 1]) - if last_type != :boundary - last_type = :boundary - score += 1 - end - # This character isn't special. - else - last_type = :normal - score += index - last_index - end - - last_index = index - end - - [score, last_index] - end - end -end - -class Renderer < Struct.new(:search) - def self.render!(search, screen) - rendered = Renderer.new(search).render - start_line = screen.height - search.config.visible_choices - 1 - screen.with_cursor_hidden do - screen.write_lines(start_line, rendered.choices) - screen.move_cursor(start_line, 0) - screen.write_line(start_line, rendered.search_line) - end - end - - def render - search_line = "#{match_count_label} > " + search.query - - matches = search.best_matches - matches = matches.each_with_index.map do |match, index| - if index == search.index - Text[:inverse] + match.to_text + Text[:reset] - else - match.to_text - end - end - matches = correct_match_count(matches) - lines = [search_line] + matches - Rendered.new(lines, search_line) - end - - def match_count_label - choice_count = search.original_matches.length - max_label_width = choice_count.to_s.length - match_count = search.all_matches.count - match_count.to_s.rjust(max_label_width) - end - - def correct_match_count(matches) - limited = matches[0, search.config.visible_choices] - padded = limited + [""] * (search.config.visible_choices - limited.length) - padded - end - - class Rendered < Struct.new(:choices, :search_line) - end - - private - - def replace_array_element(array, index, new_value) - array = array.dup - array[index] = new_value - array - end -end - -class Screen - def self.with_screen - TTY.with_tty do |tty| - screen = self.new(tty) - screen.configure_tty - begin - raise NotATTY if screen.height == 0 - yield screen, tty - ensure - screen.restore_tty - tty.puts - end - end - end - - class NotATTY < RuntimeError; end - - attr_reader :tty - - def initialize(tty) - @tty = tty - @original_stty_state = tty.stty("-g") - end - - def configure_tty - # -echo: terminal doesn't echo typed characters back to the terminal - # -icanon: terminal doesn't interpret special characters (like backspace) - tty.stty("raw -echo -icanon") - end - - def restore_tty - tty.stty("#{@original_stty_state}") - end - - def suspend - restore_tty - begin - yield - configure_tty - rescue - restore_tty - end - end - - def with_cursor_hidden(&block) - write_bytes(ANSI.hide_cursor) - begin - block.call - ensure - write_bytes(ANSI.show_cursor) - end - end - - def height - tty.winsize[0] - end - - def width - tty.winsize[1] - end - - def move_cursor(line, column) - write_bytes(ANSI.setpos(line, column)) - end - - def write_line(line, text) - write(line, 0, text) - end - - def write_lines(line, texts) - texts.each_with_index do |text, index| - write(line + index, 0, text) - end - end - - def write(line, column, text) - # Discard writes outside the main screen area - write_unrestricted(line, column, text) if line < height - end - - def write_unrestricted(line, column, text) - text = Text[:default, text] unless text.is_a? Text - write_text_object(line, column, text) - end - - def write_text_object(line, column, text) - # Blank the line before drawing to it - write_bytes(ANSI.setpos(line, 0)) - write_bytes(" " * width) - - text.components.each do |component| - if component.is_a? String - write_bytes(ANSI.setpos(line, column)) - # Don't draw off the edge of the screen. - # - width - 1 is the last column we have (zero-indexed) - # - subtract the current column from that to get the number of - # columns we have left. - chars_to_draw = [0, width - 1 - column].max - component = expand_tabs(component)[0..chars_to_draw] - write_bytes(component) - column += component.length - elsif component == :inverse - write_bytes(ANSI.inverse) - elsif component == :reset - write_bytes(ANSI.reset) - else - if component =~ /_/ - fg, bg = component.to_s.split(/_/).map(&:to_sym) - else - fg, bg = component, :default - end - write_bytes(ANSI.color(fg, bg)) - end - end - end - - def expand_tabs(string) - # Modified from http://markmail.org/message/avdjw34ahxi447qk - tab_width = 8 - string.gsub(/([^\t\n]*)\t/) do - $1 + " " * (tab_width - ($1.size % tab_width)) - end - end - - def write_bytes(bytes) - tty.console_file.write(bytes) - end -end - -class Text - attr_reader :components - - def self.[](*args) - new(args) - end - - def initialize(components) - @components = components - end - - def ==(other) - components == other.components - end - - def +(other) - Text[*(components + other.components)] - end -end - -class ANSI - ESC = 27.chr - - class << self - def escape(sequence) - ESC + "[" + sequence - end - - def clear - escape "2J" - end - - def hide_cursor - escape "?25l" - end - - def show_cursor - escape "?25h" - end - - def setpos(line, column) - escape "#{line + 1};#{column + 1}H" - end - - def color(fg, bg=:default) - fg_codes = { - :black => 30, - :red => 31, - :green => 32, - :yellow => 33, - :blue => 34, - :magenta => 35, - :cyan => 36, - :white => 37, - :default => 39, - } - bg_codes = { - :black => 40, - :red => 41, - :green => 42, - :yellow => 43, - :blue => 44, - :magenta => 45, - :cyan => 46, - :white => 47, - :default => 49, - } - fg_code = fg_codes.fetch(fg) - bg_code = bg_codes.fetch(bg) - escape "#{fg_code};#{bg_code}m" - end - - def inverse - escape("7m") - end - - def reset - escape("0m") - end - end -end - -class TTY < Struct.new(:console_file) - def self.with_tty(&block) - # Selecta reads data from stdin and writes it to stdout, so we can't draw - # UI and receive keystrokes through them. Fortunately, all modern - # Unix-likes provide /dev/tty, which IO.console gives us. - console_file = IO.console - tty = TTY.new(console_file) - block.call(tty) - end - - def get_available_input - input = console_file.getc - while console_file.ready? - input += console_file.getc - end - input - end - - def puts - console_file.puts - end - - def winsize - console_file.winsize - end - - def stty(args) - command("stty #{args}").strip - end - - private - - # Run a command with the TTY as stdin, capturing the output via a pipe - def command(command) - IO.pipe do |read_io, write_io| - pid = Process.spawn(command, :in => "/dev/tty", :out => write_io) - Process.wait(pid) - raise "Command failed: #{command.inspect}" unless $?.success? - write_io.close - read_io.read - end - end -end - -if $0 == __FILE__ - Selecta.new.main -end diff --git a/linux/env.platform b/linux/env.platform new file mode 100644 index 0000000..4c48230 --- /dev/null +++ b/linux/env.platform @@ -0,0 +1 @@ +export PATH=$HOME/.dotfiles/linux/bin:$PATH diff --git a/linux/install b/linux/install new file mode 100755 index 0000000..f0e16f3 --- /dev/null +++ b/linux/install @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -e + +source "$HOME/.dotfiles/script_helpers/printing.sh" +source "$HOME/.dotfiles/script_helpers/core.sh" +source "$HOME/.dotfiles/script_helpers/platform.sh" +source "$HOME/.dotfiles/script_helpers/file_ops.sh" + +#----------------------------------- +# Apt packages +#----------------------------------- + +printf "\n${BOLD}Installing apt packages...${NORMAL}\n\n" + +apt_packages=( + 'tree' + 'powershell' +) + +sudo apt update + +for package in "${apt_packages[@]}" +do + printf "${BOLD}$package${NORMAL}\n" + eval "sudo apt install $package" + printf "\n" +done + +printf "\n${BOLD}Finished setting up Linux${NORMAL}\n" diff --git a/osx/install b/osx/install index cdcd98b..4b12eb2 100755 --- a/osx/install +++ b/osx/install @@ -7,12 +7,6 @@ source "$HOME/.dotfiles/script_helpers/core.sh" source "$HOME/.dotfiles/script_helpers/platform.sh" source "$HOME/.dotfiles/script_helpers/file_ops.sh" -printf "${BOLD}Installing env...${NORMAL}\n" -setup_file $HOME/.dotfiles/osx/env.platform $HOME/.env.platform - -printf "\n${BOLD}Installing Git customizations...${NORMAL}\n" -setup_file $HOME/.dotfiles/osx/gitconfig.platform $HOME/.gitconfig.platform - printf "\n${BOLD}Installing key remap config...${NORMAL}\n" key_dest=$HOME/.config/karabiner/assets/complex_modifications/ mkdir -p $key_dest @@ -24,8 +18,6 @@ setup_file $HOME/.dotfiles/osx/capslock-to-ctrl-for-karabiner.json ${key_dest}/c printf "\n${BOLD}Installing Homebrew packages...${NORMAL}\n\n" -#brew tap homebrew/core - brew_packages=( 'openssl' 'wget' diff --git a/script_helpers/file_ops.sh b/script_helpers/file_ops.sh index 6eca90e..c63866b 100644 --- a/script_helpers/file_ops.sh +++ b/script_helpers/file_ops.sh @@ -211,8 +211,8 @@ link_file() { echo Link cmd:: $link_cmd fi - printf "${BOLD}${GREEN}==> ${NORMAL}Linking ${BOLD}${YELLOW}'$source_path'${NORMAL} to ${BOLD}${YELLOW}'$dest_path'${NORMAL}\n" - eval $link_cmd + printf "${BOLD}${GREEN}==> ${NORMAL}Linking ${BOLD}${YELLOW}'$source_path'${NORMAL} to ${BOLD}${YELLOW}'$dest_path'${NORMAL}\n" 2>/dev/null + eval $link_cmd 1>/dev/null } function setup_file() { @@ -228,6 +228,15 @@ function setup_file() { function setup_dir() { src=$1 dest=$2 + ignore_missing=$3 + if [ ! -d $src ]; then + error "Source path '$src' doesn't exist!\n" + if [[ $ignore_missing != "1" ]]; then + abort + else + return + fi + fi if [ ! -d $dest ]; then link_file $src $dest $confirm_link else diff --git a/windows/env.platform b/windows/env.platform new file mode 100644 index 0000000..94ebfde --- /dev/null +++ b/windows/env.platform @@ -0,0 +1,2 @@ +export CMAKE_CXX_COMPILER=/Library/Developer/CommandLineTools/usr/bin/c++ +export CMAKE_C_COMPILER=/Library/Developer/CommandLineTools/usr/bin/cc diff --git a/windows/install b/windows/install new file mode 100755 index 0000000..69320e6 --- /dev/null +++ b/windows/install @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e + +source "$HOME/.dotfiles/script_helpers/printing.sh" +source "$HOME/.dotfiles/script_helpers/core.sh" +source "$HOME/.dotfiles/script_helpers/platform.sh" +source "$HOME/.dotfiles/script_helpers/file_ops.sh" + +# Nothing to do at the moment. + +printf "\n${BOLD}Finished setting up Windows${NORMAL}\n" diff --git a/zsh/zshenv b/zsh/zshenv deleted file mode 100644 index 518a754..0000000 --- a/zsh/zshenv +++ /dev/null @@ -1,34 +0,0 @@ -platform=`uname -s` - -# Unbreak broken, non-colored terminal -export TERM=xterm-256color - -# Use vim as the editor -export EDITOR=vim - -# Homebrew setup -export HOMEBREW_NO_ANALYTICS=1 -export HOMEBREW_NO_INSECURE_REDIRECT=1 -export HOMEBREW_CASK_OPTS=--require-sha - -# Ruby -export RBENV_PATH="$HOME/.rbenv" - -# Clojure -export LEIN_FAST_TRAMPOLINE=y - -if [[ $platform == 'Linux' ]]; then - export LD_LIBRARY_PATH="/usr/lib/jvm/java-8-openjdk/jre/lib/amd64" - export LOLCOMMITS_ANIMATE=4 - export LOLCOMMITS_FORK=true - export LOLCOMMITS_STEALTH=true - export LOLCOMMITS_DIR="/shared/Dev/lolcommits" -fi - -if [ -d "$HOME/.rbenv" ]; then - # Start rbenv - eval "$(rbenv init -)" -fi - -# Start the SSH agent -eval "$(ssh-agent -s)" > /dev/null diff --git a/zsh/zshrc b/zsh/zshrc index 3dd1587..87e6ba2 100644 --- a/zsh/zshrc +++ b/zsh/zshrc @@ -47,7 +47,7 @@ fi # Autoload things before calling compinit autoload -Uz compinit -compinit +compinit -i # Never know when you're gonna need to popd! setopt AUTO_PUSHD @@ -67,10 +67,33 @@ chpwd() { # Save a ton of history export HISTSIZE=20000 export HISTFILE="$HOME/.history" +export HISTCONTROL=ignoredups export SAVEHIST=$HISTSIZE setopt appendhistory autocd bindkey -e +# Unbreak broken, non-colored terminal +export TERM=xterm-256color + +# Use vim as the editor +export EDITOR=vim + +# Homebrew setup +export HOMEBREW_NO_ANALYTICS=1 +export HOMEBREW_NO_INSECURE_REDIRECT=1 +export HOMEBREW_CASK_OPTS=--require-sha + +# Ruby +export RBENV_PATH="$HOME/.rbenv" + +# Clojure +export LEIN_FAST_TRAMPOLINE=y + +if [ -d "$HOME/.rbenv" ]; then + # Start rbenv + eval "$(rbenv init -)" +fi + # Set to this to use case-sensitive completion # CASE_SENSITIVE="true" @@ -90,20 +113,7 @@ bindkey -e source $ZSH/lib/*.zsh # Source my custom files after oh-my-zsh so I can override things. -test -f $HOME/.common_env && . $HOME/.common_env -test -f $HOME/.private-dotfiles.common/env && . $HOME/.private-dotfiles.common/env -test -f $HOME/.private-dotfiles/env && . $HOME/.private-dotfiles/env - -test -f $HOME/.aliases && . $HOME/.aliases -test -f $HOME/.aliases.private && . $HOME/.aliases.private - -if [[ -d "$HOME/bin" ]]; then - export PATH=$HOME/bin/:$PATH -fi - -if [[ -d "$HOME/.dotfiles/bin" ]]; then - export PATH=$HOME/.dotfiles/bin/:$PATH -fi +test -f $HOME/.env.common && . $HOME/.env.common # Fix in neovim infocmp $TERM | sed 's/kbs=^[hH]/kbs=\\177/' > $HOME/.$TERM.ti