#!/usr/bin/env zsh # Recommended: hg >= 1.0 PDIR="$HOME/packages" PKG_ROOT="ssh://derf.homelinux.org/var/packages_root" VCS_CMD="hg" VCS_OPTIONS="--quiet" VCS_ADD="clone" VCS_INCOMING="incoming" VCS_STATUS="status" VCS_UPDATE="pull" VCS_UPDATE_OPTIONS="--update" CL_OPTIONS="-q" info=$'\e[0;36m' error=$'\e[0;31m' reset=$'\e[0m' ## ## Internal functions for displaying stuff ## info () { echo -ne "${info}$*${reset}" } warn () { echo -ne "${error}$*${reset}" } die () { echo -ne "${error}$*${reset}" exit 100 } clear_line () { echo -ne "\r \r" } # Read local configuration if ([ -f $HOME/.pkg.conf ]) { . $HOME/.pkg.conf } export PDIR export PKG_ROOT ## ## Make sure everything's sane ## Warn otherwise ## # we need sed -r (and sed -i, but not checking for that here) QUUX=$(echo foox | sed -r 's/^fo{2}(.)$/quu\1/' 2> /dev/null) if ([ "$QUUX" != 'quux' ]) { warn "sed is not working properly. This may produce unexpected behaviour.\n" } if ([ ! -d $PDIR ]) { die "$PDIR not found!!" } ## ## Some additional variables related to PKG_ROOT ## # Protocol if (echo "$PKG_ROOT" | grep "^ssh" &> /dev/null) { PKG_PROTO='ssh' } elif (echo "$PKG_ROOT" | grep "^/" &> /dev/null) { PKG_PROTO='file' } else { false } # Host if ([ "$PKG_PROTO" = "ssh" ]) { PKG_HOST=$(echo "$PKG_ROOT" | sed 's!^ssh://!!' | sed -r 's!^([^/]*)/.*$!\1!') } # Remote path if ([ "$PKG_PROTO" = "ssh" ]) { PKG_PATH=$(echo "$PKG_ROOT" | sed 's!^ssh://'"$PKG_HOST"'/!!') } elif ([ "$PKG_PROTO" = "file" ]) { PKG_PATH="$PKG_ROOT" } ## ## Ask the user for confirmation ## # Default reply: Yes confirm_yes () { echo -n "$* [Y/n] " read -k 1 [ $REPLY != $'\n' ] && echo if ([ "$REPLY" = 'y' -o "$REPLY" = 'Y' -o "$REPLY" = $'\n' ]) { true } else { false } } # Default reply: No confirm_no () { echo -n "$* [y/N] " read -q } ## ## Major internal functions ## # Return an understandable priority from the numeric one real_priority () { case "$1" in 6) echo "essential" ;; 5) echo "important" ;; 4) echo "required" ;; 3) echo "standard" ;; 2) echo "optional" ;; 1) echo "extra" ;; *) echo ;; esac } # Check dependencies and offer to install them check_deps () { [ -r $PDIR/$1/dependencies ] || return 0 DEPS=($(cat $PDIR/$1/dependencies)) INSTALL=() for dep in $DEPS; { if ([ "$dep" = "$1" ]) { warn "This package depends on itself. Therefore, I'm considering it borked. Not installing.\n" return 100 } if ([ ! -d $PDIR/$dep ]) { if (confirm_yes "$1 depends on $dep. Install dependency?") { INSTALL+="$dep" } } } if ([ -n "$INSTALL" ]) { for pkg in $INSTALL; { pkg_add "$pkg" } } } # Write a packages' files to .collected # Currently, this is only documentation populate_collected () { cd $PDIR/$1 || return info "Enabling documentation " if ([ -d man ]) { for section in man/*; { section=$(basename $section) for manpage in man/$section/*; { manpage=$(basename $manpage) if (podchecker man/$section/$manpage &> /dev/null) { pod2man -c "$1 package" -r "/home/derf" man/$section/$manpage > $PDIR/.collected/man/man$section/$manpage.$section echo -n "+" } else { echo -n "." } } } } if ([ -d bin ]) { for i in bin/*; { if (podchecker $i &> /dev/null) { pod2man $i > $PDIR/.collected/man/man1/$(basename $i).1 echo -n "+" } else { echo -n "." } } } clear_line } # Remove a packages' files from .collected # Assuming there are no packages with colliding files # TODO: Make sure there are none genocide_collected () { cd $PDIR/$1 || return info "Removing documentation" # if ([ -d man ]) { # for i in man/*/*; { # rm $PDIR/.collected/$i # } # } if ([ -d bin ]) { for i in bin/*; { rm -f $PDIR/.collected/man/man1/$(basename $i).1 } } clear_line } ## ## Finally - the functions actually doing something ## pkg_add () { if ([ -d $PDIR/$1 ]) { info "Package '$1' is already installed!\n" return 100 } cd $PDIR || return 255 info "Retrieving package $1...\n" $VCS_CMD $VCS_OPTIONS $VCS_ADD $PKG_ROOT/$1 || return 255 if ([ -r $PDIR/$1/Makefile ]) { info "Building binaries\n" cd $PDIR/$1 make } if ([ -r $PDIR/$1/hooks/post-add ]) { info 'Executing post-add hook\n' . $PDIR/$1/hooks/post-add } check_deps "$1" cd $PDIR/$1 checklinks $CL_OPTIONS populate_collected "$1" return 0 } pkg_remove () { [ -d $PDIR/$1 ] || die "Package '$1' is not installed!\n" [ -d $PDIR/$1/.hg ] || die "Not a valid package: '$1'\n" cd $PDIR/$1 if ([ -r priority ]) { if ([ $(cat priority) -gt 3 ]) { confirm_no "Package '$1' is $(real_priority $(cat priority)). Really remove?" || return } } if ([ -f $PDIR/$1/hooks/pre-remove ]) { info 'Executing pre-remove hook\n' . $PDIR/$1/hooks/pre-remove } genocide_collected "$1" rm -r $PDIR/$1 info "Package removed.\n" } pkg_update () { cd $PDIR/$1 info "Looking for updates: $1" NEW=$($VCS_CMD $VCS_OPTIONS $VCS_INCOMING) if ([ $? = 0 ]) { clear_line info "Updating: $1 to $(echo $NEW | tail -n 1)" $VCS_CMD $VCS_OPTIONS $VCS_UPDATE $VCS_UPDATE_OPTIONS clear_line info "Updated: $1 to $(echo $NEW | tail -n 1) \n" if ([ -r Makefile ]) { info "Building binaries\n" make } if ([ -r hooks/post-update ]) { info 'Executing post-update hook\n' . hooks/post-update } checklinks $CL_OPTIONS populate_collected "$1" } else { clear_line } cd $PDIR } # If no package was specified, update everything pkg_update_wrapper () { if ([ -n "$1" ]) { pkg_update "$1" } else { cd $PDIR for i in *(/); { pkg_update "$i" } } } # Change the 'default' url in every package's .hgrc pkg_changesrc () { cd $PDIR for i in *(/); { if ([ -f $i/.hg/hgrc ]) { sed -i "s!default = [^:]*://.*\$!default = $1/$i!" $i/.hg/hgrc } } } pkg_list_installed () { =ls -1 $PDIR } pkg_list_available () { if ([ "$PKG_PROTO" = 'ssh' ]) { ssh -q "$PKG_HOST" ls -1 "$PKG_PATH" } elif ([ "$PKG_PROTO" = 'file' ]) { ls -1 "$PKG_PATH" } } # Local modifications should not be overseen... pkg_status () { cd $PDIR/$1 info "looking for local modifications: $1" check_deps $1 checklinks $CL_OPTIONS STATUS=$($VCS_CMD $VCS_STATUS) if ([ -n "$STATUS" ]) { info "\rLocally modified in $1:\n" echo "$STATUS" } else { clear_line } } # Same as with update - if no package is specified, check all pkg_status_wrapper () { if ([ -n "$1" ]) { pkg_status "$1" } else { cd $PDIR for i in *(/); { pkg_status "$i" } } } # Various information related to a package pkg_info () { cd $PDIR/$1 || return [ -z "$1" ] && die "Not enough arguments\n" # Fetch the infos NAME="$1" if ([ -r priority ]) { PRIORITY=$(cat priority) PRIORITY_NAME=$(real_priority "$PRIORITY") } LOG=$(hg log) VERSION=$(echo $LOG | grep -m1 'changeset:' | grep -Eo '[0-9]{1,}:[0-9a-f]*') DATE=$(echo $LOG | grep -m1 'date:' | grep -Eo '[A-Z][a-z]{2}.*') if ([ -r dependencies ]) { DEPENDENCIES=$(cat dependencies | tr "\n" " " | sed 's/ /, /g' | sed 's/, $//') } if ([ -r tags ]) { TAGS=$(cat tags | tr "\n" " " | sed 's/ /, /g' | sed 's/, $//') } if ([ -d hooks ]) { HOOKS=$(ls hooks) } if ([ -r Makefile ]) { MAKEFILE=1 } SIZE=$(du -sh .hg | grep -o '.*[KMG]') if ([ -r description ]) { DESCRIPTION=$(cat description) } show_info () { [ -z "$2" ] && return info "$1: " echo "$2" } show_info "Package" "$NAME" show_info "Priority" "$PRIORITY ($PRIORITY_NAME)" show_info "Version" "$VERSION" show_info "Date" "$DATE" show_info "Depends" "$DEPENDENCIES" show_info "Tags" "$TAGS" show_info "Hooks" "$HOOKS" show_info "Repostiry Size" "$SIZE" show_info "Description" "$DESCRIPTION" } pkg_log () { cd $PDIR/$1 || return hg glog | less } pkg_changelog () { [ -r $PDIR/$1/changelog ] && view $PDIR/$1/changelog } # Almost obsoleted by the bin/* -> man/ stuff pkg_doc () { if ([ -r $PDIR/.collected/man/$1 ]) { man $PDIR/.collected/man/$1 } elif ([ -r $PDIR/.collected/doc/$1 ]) { less $PDIR/.collected/doc/$1 } else { echo "No documentation found" } } ## ## Now what shall we do... ## case "$1" in add) pkg_add "$2" ;; changelog) pkg_changelog "$2" ;; changeroot) pkg_changesrc "$2" ;; delete) pkg_remove "$2" ;; doc) pkg_doc "$2" ;; info) pkg_info "$2" ;; install) pkg_add "$2" ;; list) pkg_list_installed ;; list-all) pkg_list_available ;; log) pkg_log "$2" ;; remove) pkg_remove "$2" ;; status) pkg_status_wrapper "$2" ;; update) pkg_update_wrapper "$2" ;; *) die "wait, what?\n" ;; esac