From 324568044af4287dd25a90dc143ea26df565d1e3 Mon Sep 17 00:00:00 2001 From: Daniel Friesel Date: Sat, 11 Jul 2009 01:17:18 +0200 Subject: Renamed pkg to caretaker --- .gitignore | 2 +- README | 4 +- bin/checklinks | 6 +- bin/ct | 860 ++++++++++++++++++++++++++++++++++++++++++ bin/pkg | 854 ----------------------------------------- description | 4 +- include/bootstrap | 28 +- include/newpackage | 2 +- include/pkglist | 2 +- man/1/ct.pod | 162 ++++++++ man/1/pkg.pod | 162 -------- man/5/caretaker.conf.pod | 114 ++++++ man/5/pkg.conf.pod | 114 ------ man/7/caretaker-setup.pod | 17 + man/7/caretaker.pod | 256 +++++++++++++ man/7/pkg-setup.pod | 17 - man/7/pkg.pod | 256 ------------- provides/zsh/alias | 1 - provides/zsh/completions/_ct | 89 +++++ provides/zsh/completions/_pkg | 92 ----- test/add | 18 +- test/main | 44 +-- test/pull | 4 +- test/push | 2 +- test/remove | 20 +- test/setup | 4 +- 26 files changed, 1568 insertions(+), 1566 deletions(-) create mode 100755 bin/ct delete mode 100755 bin/pkg create mode 100644 man/1/ct.pod delete mode 100644 man/1/pkg.pod create mode 100644 man/5/caretaker.conf.pod delete mode 100644 man/5/pkg.conf.pod create mode 100644 man/7/caretaker-setup.pod create mode 100644 man/7/caretaker.pod delete mode 100644 man/7/pkg-setup.pod delete mode 100644 man/7/pkg.pod delete mode 100644 provides/zsh/alias create mode 100644 provides/zsh/completions/_ct delete mode 100644 provides/zsh/completions/_pkg diff --git a/.gitignore b/.gitignore index 3919581..ed40d0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/pkg.conf +/caretaker.conf diff --git a/README b/README index ce91108..8152539 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ -pkg - distributed dotfile and script manager +caretaker - distributed dotfile and script manager -For setup: perldoc -F man/7/pkg-setup +For setup: perldoc -F man/7/caretaker-setup.pod Requires: - zsh diff --git a/bin/checklinks b/bin/checklinks index 765e5c7..307e246 100755 --- a/bin/checklinks +++ b/bin/checklinks @@ -164,9 +164,9 @@ which have a status of "ok" or "absolute", level 2 will filter anything except =item B<-p>, B<--parameter> I=I While reading the links file, replace $I with I. -When used in conjuction with pkg(1), $package will be set to the current -package's relative path (as seen from $HOME, like C), -and $etc will be set to $package/etc (like C) +When used in conjuction with ct(1), $package will be set to the current +package's relative path (as seen from $HOME, like C), +and $etc will be set to $package/etc (like C) =item B<-q>, B<--quiet> diff --git a/bin/ct b/bin/ct new file mode 100755 index 0000000..ab216ad --- /dev/null +++ b/bin/ct @@ -0,0 +1,860 @@ +#!/usr/bin/env zsh +## caretaker - /home package manager and zsh playground +## Copyright © 2008-2009 by Daniel Friesel +## +## Permission to use, copy, modify, and/or distribute this software for any +## purpose with or without fee is hereby granted, provided that the above +## copyright notice and this permission notice appear in all copies. +## +## THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +## WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +## MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +## ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +## WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +## ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +## OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +setopt extended_glob +typeset -a -U triggers +typeset -a CL_OPTIONS + +c_info=$'\e[0;36m' +c_error=$'\e[0;31m' +c_reset=$'\e[0m' + +function info { + echo -ne "${c_info}$*${c_reset}" +} + +function warn { + echo -ne "${c_error}$*${c_reset}" > /dev/stderr +} + +function die { + echo -ne "${c_error}$*${c_reset}" > /dev/stderr + exit 100 +} + +function say { + echo $* +} + +function clear_line { + echo -ne "\r\e[2K" +} + +# Read local configuration +: ${XDG_CONFIG_HOME=$HOME/.config} +if [[ -r $XDG_CONFIG_HOME/caretaker/caretaker.conf ]] { + source $XDG_CONFIG_HOME/caretaker/caretaker.conf +} elif [[ -r $HOME/.caretaker.conf ]] { + source $HOME/.caretaker.conf +} elif [[ -r $XDG_CONFIG_HOME/pkg/pkg.conf ]] { + warn "pkg was renamed to caretaker, so move your config to $XDG_CONFIG_HOME/caretaker/caretaker.conf" + source $XDG_CONFIG_HOME/pkg/pkg.conf +} elif [[ -r $HOME/.pkg.conf ]] { + warn "pkg was renamed to caretaker, so move your config to $HOME/.caretaker.conf" + source $HOME/.pkg.conf +} + +# Parse commandline options +while [[ $1 == --* ]] { + case $1 in + --coluors) COLOURS=1 ;; + --no-colours) COLOURS=0 ;; + --quiet) SILENT=1 ;; + --no-quiiet) SILENT=0 ;; + --debug) DEBUG=1 ;; + --no-debug) DEBUG=0 ;; + --auto-update) AUTOUPDATE=1 ;; + --no-auto-update) AUTOUPDATE=0 ;; + --checklinks-options) CL_OPTIONS+=$2; shift ;; + --packagedir) PKG_DIR=$2; shift ;; + --packageroot) PKG_ROOT=$2; shift ;; + --progress) PROGRESS=1 ;; + --no-progress) PROGRESS=0 ;; + *) die "Unknown argument: '$1'\n" ;; + esac + shift +} + +action=$1 +((#)) && shift + +[[ -n $PKG_ROOT ]] || die "No PKG_ROOT specified. Please edit your caretaker.conf\n" +: ${PKG_DIR:="$HOME/packages"} +: ${PKGLIST_LOCAL=0} +: ${CL_OPTIONS:=--quiet} +: ${SILENT=0} +: ${DEBUG=0} +: ${AUTOUPDATE=1} +: ${GIT_USE_ORIGIN=1} +: ${COLOURS=1} +: ${PROGRESS=1} +export PKG_DIR +export PKG_ROOT + +function debug { + typeset func line + if (( $# >= 3 )) { + func=$1 + line=$2 + shift 2 + } + echo "(debug) $func:$line: $*" >&2 +} + +# I need function name and line number of the function _calling_ debug, +# so I can't get them from inside the debug function. +alias debug='debug ${(z%):-%N %i}' + +# Avoid calling debug without debug mode... just in case one needs more speed +if (( !DEBUG )) { + unalias debug + function debug {} +} + +if (( SILENT )) { + # The goal is not to override anything set by the user... + # So, an alias should be safer than fiddling with $MAKEFLAGS + alias make='make -s' + function info {} + function say {} + function clear_line {} + PROGRESS=0 +} + +if (( !COLOURS )) { + c_info='' + c_reset='' + c_error='' +} + +if [[ ! -d $PKG_DIR ]] { + die "Error: Package directory '$PKG_DIR' not found\n" +} + + +## +## Setup some additional variables related to PKG_ROOT +## + +# Protocol +if [[ $PKG_ROOT == ssh://* ]] { + PKG_PROTO='ssh' +} elif [[ $PKG_ROOT == git://* ]] { + PKG_PROTO='git' +} elif [[ $PKG_ROOT == /* ]] { + PKG_PROTO='file' +} else { + die "Error: Unknown protocol in PKG_ROOT '$PKG_ROOT'\n" +} + +# user, host, path +if [[ $PKG_PROTO == (git|ssh) ]] { + PKG_HOST=${${PKG_ROOT#"${PKG_PROTO}://"}%%/*} + PKG_PATH=${PKG_ROOT#"${PKG_PROTO}://$PKG_HOST"} + if [[ $PKG_HOST == *@* ]] { + PKG_USER=${PKG_HOST%%@*} + PKG_HOST=${PKG_HOST#*@} + } else { + PKG_USER=$USERNAME + } +} elif [[ $PKG_PROTO == 'file' ]] { + PKG_PATH=$PKG_ROOT +} + +: ${PKGLIST_PATH:=$PKG_PATH/pkglist} + +if ((DEBUG)) { + info "caretaker: running in debug mode. Infos follow:\n" + echo "--- running ---" + echo " zsh $ZSH_VERSION" + echo " git "${$(git --version)[3]} + echo " caretaker "${$(git --git-dir=$PKG_DIR/${${(s:/:)$(readlink $0)}[-3]}/.git/ log -n 1)[2]} + echo "--- settings ---" + echo " PKG_ROOT $PKG_ROOT" + echo " PKG_PROTO $PKG_PROTO" + echo " PKG_USER $PKG_USER" + echo " PKG_HOST $PKG_HOST" + echo " PKGLIST_PATH $PKGLIST_PATH" + echo " PKGLIST_LOCAL $PKGLIST_LOCAL" + echo " PKG_DIR $PKG_DIR" + echo " CL_OPTIONS $CL_OPTIONS" + echo " SILENT $SILENT" + echo " COLOURS $COLOURS" + echo " PROGRESS $PROGRESS" + echo " AUTOUPDATE $AUTOUPDATE" + echo " GIT_USE_ORIGIN $GIT_USE_ORIGIN" +} + +function check_installed { + [[ -n $1 && -d $PKG_DIR/$1 ]] || die "Package not installed: '$1'\n" +} + +function check_valid { + list_exists $1 || die "Package does not exist: '$1'\n" +} + +# Default reply: Yes +function confirm_yes { + echo -n "$* [Y/n] " + read -k 1 + [[ $REPLY != $'\n' ]] && echo + [[ $REPLY == (y|Y|$'\n') ]] +} + +# Default reply: No +function confirm_no { + echo -n "$* [y/N] " + read -q +} + + +## +## Major internal functions +## + +# this function only has content when wrap is used. +# but since it's always called, it will be empty by default +function wrap_info {} + +function progress { + ((PROGRESS)) || return + typeset -i current=$1 + typeset -i max=$2 + typeset desc=$3 + typeset desc2=$4 + typeset output='' + typeset -i currentper=$(( (current*100)/max )) + typeset item j c a + function item { + for j in {0..$1}; { + (( j > 0 )) && output+=$2 + } + } + c=$(( currentper/5 )) + a=$(( 20-c )) + output+="${c_info}$desc${c_reset} [" + item $c '=' + item $a ' ' + output+="] $currentper% $desc2" + clear_line + echo -ne $output +} + +## VCS Wrappers + +function vcs_setup { + cd $PKG_DIR/$1 +} + +function vcs_to_list ( + vcs_setup $1 + if [[ -d $PKG_DIR/$1/.git ]] { + echo -n "$1 git " + echo ${$(git log -n 1 master)[2]} + } else { + warn "No git repository found: $1\n" + } +) + +function vcs_add ( + if [[ $(list_type $1) == git ]] { + git clone "$PKG_ROOT/$1" + vcs_setup $1 + git config push.default matching + } else { + die "$1: Cannot handle repository format '$(list_type $1)'\n" + } +) + +function vcs_log ( + vcs_setup $1 + git log +) + +function vcs_branch_is_master ( + vcs_setup $1 + typeset IFS=$'\n' branch line + for line in $(git branch); { + [[ $line == \*\ * ]] && branch=${line#* } + } + if [[ $branch != master ]] { + warn "$1: The currently checked out branch is not master, but '$branch'\n" \ + "Currently, with GIT_USE_ORIGIN=0, caretaker can only operate on the branch master\n" \ + " -> skipping repo, please fix manually\n" + return 1 + } +) + +function vcs_pull ( + vcs_setup $1 + if ((GIT_USE_ORIGIN)) { + # the package might be newly created and not have an origin yet + vcs_fix_origin $1 + git pull + } else { + vcs_branch_is_master $1 && git pull $PKG_ROOT/${PWD:t} master + } +) + +function vcs_push ( + vcs_setup $1 + if ((GIT_USE_ORIGIN)) { + # see above + vcs_fix_origin $1 + git push + } else { + vcs_branch_is_master $1 && git push $PKG_ROOT/${PWD:t} master + } +) + +function vcs_status ( + vcs_setup $1 + git status +) + +function vcs_fix_origin ( + vcs_setup $1 + if [[ ! -r .git/remotes/origin && ! -r .git/refs/remotes/origin/HEAD ]] { + fgrep -q '[remote "origin"]' .git/config || + git remote add origin $PKG_ROOT/$1 + } +) + + +## List stuff + +function list_is_installed { + grep -q "^$1 " $PKG_DIR/.list +} + +function list_exists { + grep -q "^$1 " $PKG_DIR/.list-remote +} + +function list_packages_local { + cut -d ' ' -f 1 $PKG_DIR/.list +} + +function list_packages_remote { + cut -d ' ' -f 1 $PKG_DIR/.list-remote +} + +function list_incoming { + [[ $(list_version_local $1) != $(list_version_remote $1) ]] +} + +function list_type { + echo ${$(grep "^$1 " $PKG_DIR/.list-remote)[2]} +} + +function list_type_local { + echo ${$(grep "^$1 " $PKG_DIR/.list)[2]} +} + +function list_update_remote { + typeset tmpfile=$(mktemp -t pkglist.XXXXXX) + typeset -i ret=0 + export PKG_DIR + if [[ $PKGLIST_LOCAL == 1 || $PKG_PROTO == 'file' ]] { + $PKGLIST_PATH $PKG_PATH > $tmpfile + } elif [[ $PKG_PROTO == 'ssh' ]] { + ssh $PKG_USER@$PKG_HOST "PKG_DIR='$PKG_DIR' $PKGLIST_PATH $PKG_PATH" > $tmpfile + } + if [[ -n $(cat $tmpfile) ]] { + cp $tmpfile .list-remote + } else { + die "remote list update failed\n" + } + rm $tmpfile +} + +function list_update_local { + typeset all=${#$(echo $PKG_DIR/*(/))} + typeset -i current=0 + typeset i + rm -f $PKG_DIR/.list + for package in *(-/); { + (( current++ )) + progress $current $all 'Updating package list' $package + vcs_to_list $package >> $PKG_DIR/.list + } +} + +function list_package_update { + typeset list + list=$(grep -v "^$1 " $PKG_DIR/.list) + echo $list > $PKG_DIR/.list + vcs_to_list $1 >> $PKG_DIR/.list +} + +function list_package_remove { + typeset list + list=$(grep -v "^$1 " $PKG_DIR/.list) + echo $list > $PKG_DIR/.list +} + +function list_version_local { + echo ${$(grep "^$1 " $PKG_DIR/.list)[3]} +} + +function list_version_remote { + echo ${$(grep "^$1 " $PKG_DIR/.list-remote)[3]} +} + + +function priority_name { + case $1 in + 6) echo 'essential' ;; + 5) echo 'important' ;; + 4) echo 'required' ;; + 3) echo 'standard' ;; + 2) echo 'optional' ;; + 1) echo 'extra' ;; + *) warn "invalid priority: $!" ;; + esac +} + +function exec_hook { + typeset package=$1 + typeset hook=$2 + if [[ -r $PKG_DIR/$package/hooks/$hook ]] { + info "$package: executing hook $hook\n" + cd $PKG_DIR/$package + (source hooks/$hook) + } +} + +function global_hook { + cd $PKG_DIR/$1 + case $2 in + post-add) + exec_hook $1 post-add + global_hook $1 post-update + ;; + pre-update) + ;; + post-update) + triggers+=$1 + check_prereqs $1 + if [[ -r Makefile ]] { + wrap_info $1 + info "Running make\n" + make + } + checklinks $CL_OPTIONS \ + --parameter package=${${PWD#$HOME}#/##} \ + --parameter etc=${${PWD#$HOME}#/##}/etc + populate_collected $1 + update_provides $1 + list_package_update $1 + ;; + pre-remove) + exec_hook $1 pre-remove + genocide_collected $1 + checklinks $CL_OPTIONS --remove + list_package_remove $1 + update_provides $1 + ;; + esac + (( $+functions[pkg_hook_$2] )) && pkg_hook_$2 $1 +} + +function check_prereqs { + typeset -a -U install maybe_install + typeset warn info i + typeset package=$1 + [[ -r $PKG_DIR/$package/prereqs ]] || return 0 + cd $PKG_DIR/$package + wrap_info $1 + info "checking prerequisites\n" + + # function scope → typeset and localoptions are possible + function source_prereqs { + { + source prereqs + } always { + if (( TRY_BLOCK_ERROR )) { + warn "Error in prereqs script\n" + TRY_BLOCK_ERROR=0 + } + } + } + + function is_installed { + [[ -d $PKG_DIR/$1 ]] + } + function perlmodule { + perl -M$1 < /dev/null 2> /dev/null + } + function executable file_in_path { + if [[ $0 == file_in_path ]] { + warn "'file_in_path' is deprecated, use 'executable' instead\n" + } + which $1 > /dev/null + } + function offer_install { + install+=$1 + } + function depend require { + if [[ $0 == require ]] { + warn "'require' is depracated, use 'depend' instead\n" + } + if [[ $1 == 'package' ]] { + is_installed $2 || offer_install $2 + } else { + $* || warn+="Requirement failed: $*\n" + } + } + + function suggest { + if [[ $1 == 'package' ]] { + is_installed $2 || info "$package suggests package $2\n" + } else { + $* || echo "Suggest failed: $*\n" + } + } + + function recommend { + if [[ $1 == 'package' ]] { + is_installed $2 || maybe_install+=$2 + } else { + $* || info+="Recommend failed: $*\n" + } + } + + source_prereqs + + if [[ -n $warn || -n $info ]] { + [[ -n $warn ]] && warn $warn + [[ -n $info ]] && echo -n $info + read -q '?continue [] ' + } + + if [[ -n $install ]] { + info "$1 requires the following packages: ${(j:, :)install}\n" + if confirm_yes "Install them?"; then + for package in $install; { + pkg_add $package + } + fi + } + if [[ -n $maybe_install ]] { + info "$1 recommends the following packages: ${(j:, :)maybe_install}\n" + if confirm_no "Install them?"; then + for package in $maybe_install; { + pkg_add $package + } + fi + } +} + +# Write a package's documentation to .collected +# and symlink its binaries from ~/bin +function populate_collected { + cd $PKG_DIR/$1 || return + typeset -i bins=0 bino=0 mans=0 mano=0 + typeset man section manpage file + + wrap_info $1 + info "Enabling documentation\n" + for man in man/*/*(N); { + section=${man:h:t} + manpage=${man:t} + if podchecker man/$section/$manpage &> /dev/null; then + pod2man -u -s $section -c "$1 package" -r $HOME man/$section/$manpage > $PKG_DIR/.collected/man/man$section/${manpage%.pod}.$section + fi + } + for file in bin/*(N); { + if podchecker $file &> /dev/null; then + pod2man -u $file > $PKG_DIR/.collected/man/man1/${file:t}.1 + fi + } + for file in bin/*(-*N); { + if [[ -L $HOME/$file || ! -e $HOME/$file ]] { + if [[ $(readlink $HOME/$file) != (../${PKG_DIR//$HOME\/}|$PKG_DIR)/$1/$file ]] { + rm -f $HOME/$file + if [[ $PKG_DIR == ${HOME}* ]] { + ln -s ../${PKG_DIR//$HOME\/}/$1/$file $HOME/$file + } else { + ln -s $PKG_DIR/$1/$file $HOME/$file + } + } + } else { + warn "populate_collected: Not updating ~/$file since it's not a symlink\n" + } + } +} + +# Remove a package's files from .collected +# Assuming there are no packages with colliding files +function genocide_collected { + typeset i file man manual section + cd $PKG_DIR/$1 || return + wrap_info $1 + info "Removing documentation" + for man in man/*/*(N); { + section=${man:h:t} + manual=${man:t} + if [[ -e $PKG_DIR/.collected/man/man$section/$manual.$section ]] { + rm $PKG_DIR/.collected/man/man$section/$manual.$section + } + } + for file in bin/*(N); { + rm -f $PKG_DIR/.collected/man/man1/${file:t}.1 + } + clear_line + for file in bin/*(-*N); { + if [[ $(readlink $HOME/$file) == (../${PKG_DIR//$HOME\/}|$PKG_DIR)/$1/$file ]] { + rm -f $HOME/$file + } + } +} + +function update_provides { + typeset package + for package in $PKG_DIR/$1/provides/*(N:t); { + if [[ -d $PKG_DIR/$package ]] { + triggers+=$package + } + } +} + +function apply_triggers { + typeset package + for package in $triggers; { + exec_hook $package 'post-update' + } +} + +# Iterate a function over every installed package +function wrap { + typeset function=$1 + typeset arg=$2 + typeset progress=$3 + typeset i + typeset -i all current + + if [[ -n $2 ]] { + $function $2 + } else { + function wrap_info { + clear_line + info "$1: " + } + [[ -n $progress ]] && all=$(list_packages_local | wc -l) + [[ -n $progress ]] && current=0 + for package in *(-/); { + cd $PKG_DIR + (( current++ )) + [[ -n $progress ]] && progress $current $all $progress $package + $function $package + } + [[ -n $progress ]] && clear_line + } +} + +## +## The "frontend" functions +## + +function pkg_add { + if [[ -d $PKG_DIR/$1 ]] { + info "Package '$1' is already installed!\n" + exit 1 + } + check_valid $1 + info "Retrieving package $1...\n" + vcs_add $1 || return 255 + global_hook $1 post-add +} + +function pkg_push { + check_installed $1 + check_valid $1 + if list_incoming $1; then + clear_line + info "Pushing $1\n" + global_hook $1 pre-update + vcs_push $1 + global_hook $1 post-update + fi +} + +function pkg_remove { + check_installed $1 + if [[ -r $PKG_DIR/$1/priority ]] { + if (( $(cat $PKG_DIR/$1/priority) > 3 )) { + confirm_no "Package '$1' is $(priority_name $(cat $PKG_DIR/$1/priority)). Really remove?" || return + } + } + global_hook $1 pre-remove + rm -rf $PKG_DIR/$1 + info "Package removed.\n" +} + +function pkg_upgrade { + check_installed $1 + check_valid $1 + if [[ $(list_type $1) != $(list_type_local $1) ]] { + clear_line + warn "Incompatible systems. Please reinstall: $1\n" + warn " remote '$(list_type $1)' <-> local '$(list_type_local)'\n" + return 9 + } + if list_incoming $1; then + clear_line + info "Updating $1 to $(list_version_remote $1)\n" + global_hook $1 pre-update + vcs_pull $1 + global_hook $1 post-update + fi +} + +function pkg_list { + typeset package crap + case $1 in + ''|local) + list_packages_local + ;; + all|remote) + list_packages_remote + ;; + not-installed) + for package in $(list_packages_remote); { + list_is_installed $package || echo $package + } + ;; + esac +} + +function pkg_status { + typeset vcs_status + check_installed $1 + vcs_status=$(PAGER='' vcs_status $1) + if [[ -n $vcs_status && $vcs_status != *'nothing to commit (working directory clean)' ]] { + if ((SILENT)) { + echo $1 + } else { + clear_line + info "$1:\n" + echo $vcs_status + } + } +} + +function pkg_refresh { + check_installed $1 + global_hook $1 pre-update + global_hook $1 post-update +} + +function pkg_update { + if [[ -z $1 || $1 == local ]] { + info "Updating local package list\n" + list_update_local + clear_line + } + if [[ -z $1 || $1 == remote ]] { + info "Updating remote package list\n" + list_update_remote + } +} + +function pkg_info { + check_valid $1 + + typeset name=$1 + typeset repo_type=$(list_type $1) + typeset priority priority_name + typeset hooks makefile discription state + if [[ -d $1 ]] { + cd $1 + if [[ -r priority ]] { + priority=$(cat priority) + priority_name=$(priority_name $priority) + } + if [[ -d hooks ]] { + hooks=$(ls -m hooks) + } + if [[ -r Makefile ]] { + makefile=1 + } + size=$(du -sh .$(list_type $1) | grep -o '.*[KMG]') + if [[ -r description ]] { + description=$(cat description) + } + state='installed' + if list_incoming $1; then + state+=', needs update' + else + state+=', up-to-date' + fi + } else { + state='not installed' + } + + function show_info { + [[ -z $2 ]] && return + info "$1: " + echo $2 + } + + show_info 'Package' $name + show_info 'State' $state + [[ -n $priority ]] && show_info 'Priority' "$priority ($priority_name)" + show_info 'Local Version' $(list_version_local $1) + show_info 'Remote Version' $(list_version_remote $1) + show_info 'Repository Type' $repo_type + show_info 'Repository Size' $size + show_info 'Hooks' $hooks + show_info 'Description' $description +} + +function pkg_log { + check_installed $1 + vcs_log $1 +} + + +cd $PKG_DIR || die "Cannot cd $PKG_DIR" + +# Note: +# wrap foobar "$1" <- the "" are neccessary here, since $1 is optional (and therefore may be empty) +case $action in + a|add|install) + if [[ $action == install ]] { + warn "ct install is deprecated, use ct add instead\n" + } + pkg_add $* + ;; + rm|delete|remove) + if [[ $action == delete ]] { + warn "ct delete is deprecated, use ct remove instead\n" + } + pkg_remove $* + ;; + i|info) pkg_info $* ;; + ls|list) pkg_list $* ;; + l|log) pkg_log $* ;; + p|push) + (( AUTOUPDATE )) && pkg_update + wrap pkg_push "$1" 'Pushing' + ;; + r|refresh) wrap pkg_refresh "$1" 'Refreshing' ;; + s|status) wrap pkg_status "$1" 'Checking package status' ;; + u|update) pkg_update $* ;; + f|upgrade|pull) + if [[ $action == upgrade ]] { + warn "ct updgrade is deprecated, use ct pull instead\n" + } + (( AUTOUPDATE )) && pkg_update remote + wrap pkg_upgrade "$1" 'Looking for updates' + ;; + e|eval) eval $* ;; + *) die "wait, what?\nct: unknown action: '$action'\n" ;; +esac + +apply_triggers diff --git a/bin/pkg b/bin/pkg deleted file mode 100755 index 3441c0a..0000000 --- a/bin/pkg +++ /dev/null @@ -1,854 +0,0 @@ -#!/usr/bin/env zsh -## pkg - /home package manager and zsh playground -## Copyright © 2008-2009 by Daniel Friesel -## -## Permission to use, copy, modify, and/or distribute this software for any -## purpose with or without fee is hereby granted, provided that the above -## copyright notice and this permission notice appear in all copies. -## -## THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -## WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -## MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -## ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -## WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -## ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -## OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -setopt extended_glob -typeset -a -U triggers -typeset -a CL_OPTIONS - -c_info=$'\e[0;36m' -c_error=$'\e[0;31m' -c_reset=$'\e[0m' - -function info { - echo -ne "${c_info}$*${c_reset}" -} - -function warn { - echo -ne "${c_error}$*${c_reset}" > /dev/stderr -} - -function die { - echo -ne "${c_error}$*${c_reset}" > /dev/stderr - exit 100 -} - -function say { - echo $* -} - -function clear_line { - echo -ne "\r\e[2K" -} - -# Read local configuration -: ${XDG_CONFIG_HOME=$HOME/.config} -if [[ -r $XDG_CONFIG_HOME/pkg/pkg.conf ]] { - source $XDG_CONFIG_HOME/pkg/pkg.conf -} elif [[ -r $HOME/.pkg.conf ]] { - source $HOME/.pkg.conf -} - -# Parse commandline options -while [[ $1 == --* ]] { - case $1 in - --coluors) COLOURS=1 ;; - --no-colours) COLOURS=0 ;; - --quiet) SILENT=1 ;; - --no-quiiet) SILENT=0 ;; - --debug) DEBUG=1 ;; - --no-debug) DEBUG=0 ;; - --auto-update) AUTOUPDATE=1 ;; - --no-auto-update) AUTOUPDATE=0 ;; - --checklinks-options) CL_OPTIONS+=$2; shift ;; - --packagedir) PKG_DIR=$2; shift ;; - --packageroot) PKG_ROOT=$2; shift ;; - --progress) PROGRESS=1 ;; - --no-progress) PROGRESS=0 ;; - *) die "Unknown argument: '$1'\n" ;; - esac - shift -} - -action=$1 -((#)) && shift - -[[ -n $PKG_ROOT ]] || die "No PKG_ROOT specified. Please edit your pkg.conf\n" -: ${PKG_DIR:="$HOME/packages"} -: ${PKGLIST_LOCAL=0} -: ${CL_OPTIONS:=--quiet} -: ${SILENT=0} -: ${DEBUG=0} -: ${AUTOUPDATE=1} -: ${GIT_USE_ORIGIN=1} -: ${COLOURS=1} -: ${PROGRESS=1} -export PKG_DIR -export PKG_ROOT - -function debug { - typeset func line - if (( $# >= 3 )) { - func=$1 - line=$2 - shift 2 - } - echo "(debug) $func:$line: $*" >&2 -} - -# I need function name and line number of the function _calling_ debug, -# so I can't get them from inside the debug function. -alias debug='debug ${(z%):-%N %i}' - -# Avoid calling debug without debug mode... just in case one needs more speed -if (( !DEBUG )) { - unalias debug - function debug {} -} - -if (( SILENT )) { - # The goal is not to override anything set by the user... - # So, an alias should be safer than fiddling with $MAKEFLAGS - alias make='make -s' - function info {} - function say {} - function clear_line {} - PROGRESS=0 -} - -if (( !COLOURS )) { - c_info='' - c_reset='' - c_error='' -} - -if [[ ! -d $PKG_DIR ]] { - die "Error: Package directory '$PKG_DIR' not found\n" -} - - -## -## Setup some additional variables related to PKG_ROOT -## - -# Protocol -if [[ $PKG_ROOT == ssh://* ]] { - PKG_PROTO='ssh' -} elif [[ $PKG_ROOT == git://* ]] { - PKG_PROTO='git' -} elif [[ $PKG_ROOT == /* ]] { - PKG_PROTO='file' -} else { - die "Error: Unknown protocol in PKG_ROOT '$PKG_ROOT'\n" -} - -# user, host, path -if [[ $PKG_PROTO == (git|ssh) ]] { - PKG_HOST=${${PKG_ROOT#"${PKG_PROTO}://"}%%/*} - PKG_PATH=${PKG_ROOT#"${PKG_PROTO}://$PKG_HOST"} - if [[ $PKG_HOST == *@* ]] { - PKG_USER=${PKG_HOST%%@*} - PKG_HOST=${PKG_HOST#*@} - } else { - PKG_USER=$USERNAME - } -} elif [[ $PKG_PROTO == 'file' ]] { - PKG_PATH=$PKG_ROOT -} - -: ${PKGLIST_PATH:=$PKG_PATH/pkglist} - -if ((DEBUG)) { - info "pkg: running in debug mode. Infos follow:\n" - echo "--- running ---" - echo " zsh $ZSH_VERSION" - echo " git "${$(git --version)[3]} - echo " pkg "${$(git --git-dir=$PKG_DIR/${${(s:/:)$(readlink $0)}[-3]}/.git/ log -n 1)[2]} - echo "--- settings ---" - echo " PKG_ROOT $PKG_ROOT" - echo " PKG_PROTO $PKG_PROTO" - echo " PKG_USER $PKG_USER" - echo " PKG_HOST $PKG_HOST" - echo " PKGLIST_PATH $PKGLIST_PATH" - echo " PKGLIST_LOCAL $PKGLIST_LOCAL" - echo " PKG_DIR $PKG_DIR" - echo " CL_OPTIONS $CL_OPTIONS" - echo " SILENT $SILENT" - echo " COLOURS $COLOURS" - echo " PROGRESS $PROGRESS" - echo " AUTOUPDATE $AUTOUPDATE" - echo " GIT_USE_ORIGIN $GIT_USE_ORIGIN" -} - -function check_installed { - [[ -n $1 && -d $PKG_DIR/$1 ]] || die "Package not installed: '$1'\n" -} - -function check_valid { - list_exists $1 || die "Package does not exist: '$1'\n" -} - -# Default reply: Yes -function confirm_yes { - echo -n "$* [Y/n] " - read -k 1 - [[ $REPLY != $'\n' ]] && echo - [[ $REPLY == (y|Y|$'\n') ]] -} - -# Default reply: No -function confirm_no { - echo -n "$* [y/N] " - read -q -} - - -## -## Major internal functions -## - -# this function only has content when wrap is used. -# but since it's always called, it will be empty by default -function wrap_info {} - -function progress { - ((PROGRESS)) || return - typeset -i current=$1 - typeset -i max=$2 - typeset desc=$3 - typeset desc2=$4 - typeset output='' - typeset -i currentper=$(( (current*100)/max )) - typeset item j c a - function item { - for j in {0..$1}; { - (( j > 0 )) && output+=$2 - } - } - c=$(( currentper/5 )) - a=$(( 20-c )) - output+="${c_info}$desc${c_reset} [" - item $c '=' - item $a ' ' - output+="] $currentper% $desc2" - clear_line - echo -ne $output -} - -## VCS Wrappers - -function vcs_setup { - cd $PKG_DIR/$1 -} - -function vcs_to_list ( - vcs_setup $1 - if [[ -d $PKG_DIR/$1/.git ]] { - echo -n "$1 git " - echo ${$(git log -n 1 master)[2]} - } else { - warn "No git repository found: $1\n" - } -) - -function vcs_add ( - if [[ $(list_type $1) == git ]] { - git clone "$PKG_ROOT/$1" - vcs_setup $1 - git config push.default matching - } else { - die "$1: Cannot handle repository format '$(list_type $1)'\n" - } -) - -function vcs_log ( - vcs_setup $1 - git log -) - -function vcs_branch_is_master ( - vcs_setup $1 - typeset IFS=$'\n' branch line - for line in $(git branch); { - [[ $line == \*\ * ]] && branch=${line#* } - } - if [[ $branch != master ]] { - warn "$1: The currently checked out branch is not master, but '$branch'\n" \ - "Currently, with GIT_USE_ORIGIN=0, pkg can only operate on the branch master\n" \ - " -> skipping repo, please fix manually\n" - return 1 - } -) - -function vcs_pull ( - vcs_setup $1 - if ((GIT_USE_ORIGIN)) { - # the package might be newly created and not have an origin yet - vcs_fix_origin $1 - git pull - } else { - vcs_branch_is_master $1 && git pull $PKG_ROOT/${PWD:t} master - } -) - -function vcs_push ( - vcs_setup $1 - if ((GIT_USE_ORIGIN)) { - # see above - vcs_fix_origin $1 - git push - } else { - vcs_branch_is_master $1 && git push $PKG_ROOT/${PWD:t} master - } -) - -function vcs_status ( - vcs_setup $1 - git status -) - -function vcs_fix_origin ( - vcs_setup $1 - if [[ ! -r .git/remotes/origin && ! -r .git/refs/remotes/origin/HEAD ]] { - fgrep -q '[remote "origin"]' .git/config || - git remote add origin $PKG_ROOT/$1 - } -) - - -## List stuff - -function list_is_installed { - grep -q "^$1 " $PKG_DIR/.list -} - -function list_exists { - grep -q "^$1 " $PKG_DIR/.list-remote -} - -function list_packages_local { - cut -d ' ' -f 1 $PKG_DIR/.list -} - -function list_packages_remote { - cut -d ' ' -f 1 $PKG_DIR/.list-remote -} - -function list_incoming { - [[ $(list_version_local $1) != $(list_version_remote $1) ]] -} - -function list_type { - echo ${$(grep "^$1 " $PKG_DIR/.list-remote)[2]} -} - -function list_type_local { - echo ${$(grep "^$1 " $PKG_DIR/.list)[2]} -} - -function list_update_remote { - typeset tmpfile=$(mktemp -t pkglist.XXXXXX) - typeset -i ret=0 - export PKG_DIR - if [[ $PKGLIST_LOCAL == 1 || $PKG_PROTO == 'file' ]] { - $PKGLIST_PATH $PKG_PATH > $tmpfile - } elif [[ $PKG_PROTO == 'ssh' ]] { - ssh $PKG_USER@$PKG_HOST "PKG_DIR='$PKG_DIR' $PKGLIST_PATH $PKG_PATH" > $tmpfile - } - if [[ -n $(cat $tmpfile) ]] { - cp $tmpfile .list-remote - } else { - die "remote list update failed\n" - } - rm $tmpfile -} - -function list_update_local { - typeset all=${#$(echo $PKG_DIR/*(/))} - typeset -i current=0 - typeset i - rm -f $PKG_DIR/.list - for package in *(-/); { - (( current++ )) - progress $current $all 'Updating package list' $package - vcs_to_list $package >> $PKG_DIR/.list - } -} - -function list_package_update { - typeset list - list=$(grep -v "^$1 " $PKG_DIR/.list) - echo $list > $PKG_DIR/.list - vcs_to_list $1 >> $PKG_DIR/.list -} - -function list_package_remove { - typeset list - list=$(grep -v "^$1 " $PKG_DIR/.list) - echo $list > $PKG_DIR/.list -} - -function list_version_local { - echo ${$(grep "^$1 " $PKG_DIR/.list)[3]} -} - -function list_version_remote { - echo ${$(grep "^$1 " $PKG_DIR/.list-remote)[3]} -} - - -function priority_name { - case $1 in - 6) echo 'essential' ;; - 5) echo 'important' ;; - 4) echo 'required' ;; - 3) echo 'standard' ;; - 2) echo 'optional' ;; - 1) echo 'extra' ;; - *) warn "invalid priority: $!" ;; - esac -} - -function exec_hook { - typeset package=$1 - typeset hook=$2 - if [[ -r $PKG_DIR/$package/hooks/$hook ]] { - info "$package: executing hook $hook\n" - cd $PKG_DIR/$package - (source hooks/$hook) - } -} - -function global_hook { - cd $PKG_DIR/$1 - case $2 in - post-add) - exec_hook $1 post-add - global_hook $1 post-update - ;; - pre-update) - ;; - post-update) - triggers+=$1 - check_prereqs $1 - if [[ -r Makefile ]] { - wrap_info $1 - info "Running make\n" - make - } - checklinks $CL_OPTIONS \ - --parameter package=${${PWD#$HOME}#/##} \ - --parameter etc=${${PWD#$HOME}#/##}/etc - populate_collected $1 - update_provides $1 - list_package_update $1 - ;; - pre-remove) - exec_hook $1 pre-remove - genocide_collected $1 - checklinks $CL_OPTIONS --remove - list_package_remove $1 - update_provides $1 - ;; - esac - (( $+functions[pkg_hook_$2] )) && pkg_hook_$2 $1 -} - -function check_prereqs { - typeset -a -U install maybe_install - typeset warn info i - typeset package=$1 - [[ -r $PKG_DIR/$package/prereqs ]] || return 0 - cd $PKG_DIR/$package - wrap_info $1 - info "checking prerequisites\n" - - # function scope → typeset and localoptions are possible - function source_prereqs { - { - source prereqs - } always { - if (( TRY_BLOCK_ERROR )) { - warn "Error in prereqs script\n" - TRY_BLOCK_ERROR=0 - } - } - } - - function is_installed { - [[ -d $PKG_DIR/$1 ]] - } - function perlmodule { - perl -M$1 < /dev/null 2> /dev/null - } - function executable file_in_path { - if [[ $0 == file_in_path ]] { - warn "'file_in_path' is deprecated, use 'executable' instead\n" - } - which $1 > /dev/null - } - function offer_install { - install+=$1 - } - function depend require { - if [[ $0 == require ]] { - warn "'require' is depracated, use 'depend' instead\n" - } - if [[ $1 == 'package' ]] { - is_installed $2 || offer_install $2 - } else { - $* || warn+="Requirement failed: $*\n" - } - } - - function suggest { - if [[ $1 == 'package' ]] { - is_installed $2 || info "$package suggests package $2\n" - } else { - $* || echo "Suggest failed: $*\n" - } - } - - function recommend { - if [[ $1 == 'package' ]] { - is_installed $2 || maybe_install+=$2 - } else { - $* || info+="Recommend failed: $*\n" - } - } - - source_prereqs - - if [[ -n $warn || -n $info ]] { - [[ -n $warn ]] && warn $warn - [[ -n $info ]] && echo -n $info - read -q '?continue [] ' - } - - if [[ -n $install ]] { - info "$1 requires the following packages: ${(j:, :)install}\n" - if confirm_yes "Install them?"; then - for package in $install; { - pkg_add $package - } - fi - } - if [[ -n $maybe_install ]] { - info "$1 recommends the following packages: ${(j:, :)maybe_install}\n" - if confirm_no "Install them?"; then - for package in $maybe_install; { - pkg_add $package - } - fi - } -} - -# Write a package's documentation to .collected -# and symlink its binaries from ~/bin -function populate_collected { - cd $PKG_DIR/$1 || return - typeset -i bins=0 bino=0 mans=0 mano=0 - typeset man section manpage file - - wrap_info $1 - info "Enabling documentation\n" - for man in man/*/*(N); { - section=${man:h:t} - manpage=${man:t} - if podchecker man/$section/$manpage &> /dev/null; then - pod2man -u -s $section -c "$1 package" -r $HOME man/$section/$manpage > $PKG_DIR/.collected/man/man$section/${manpage%.pod}.$section - fi - } - for file in bin/*(N); { - if podchecker $file &> /dev/null; then - pod2man -u $file > $PKG_DIR/.collected/man/man1/${file:t}.1 - fi - } - for file in bin/*(-*N); { - if [[ -L $HOME/$file || ! -e $HOME/$file ]] { - if [[ $(readlink $HOME/$file) != (../${PKG_DIR//$HOME\/}|$PKG_DIR)/$1/$file ]] { - rm -f $HOME/$file - if [[ $PKG_DIR == ${HOME}* ]] { - ln -s ../${PKG_DIR//$HOME\/}/$1/$file $HOME/$file - } else { - ln -s $PKG_DIR/$1/$file $HOME/$file - } - } - } else { - warn "populate_collected: Not updating ~/$file since it's not a symlink\n" - } - } -} - -# Remove a package's files from .collected -# Assuming there are no packages with colliding files -function genocide_collected { - typeset i file man manual section - cd $PKG_DIR/$1 || return - wrap_info $1 - info "Removing documentation" - for man in man/*/*(N); { - section=${man:h:t} - manual=${man:t} - if [[ -e $PKG_DIR/.collected/man/man$section/$manual.$section ]] { - rm $PKG_DIR/.collected/man/man$section/$manual.$section - } - } - for file in bin/*(N); { - rm -f $PKG_DIR/.collected/man/man1/${file:t}.1 - } - clear_line - for file in bin/*(-*N); { - if [[ $(readlink $HOME/$file) == (../${PKG_DIR//$HOME\/}|$PKG_DIR)/$1/$file ]] { - rm -f $HOME/$file - } - } -} - -function update_provides { - typeset package - for package in $PKG_DIR/$1/provides/*(N:t); { - if [[ -d $PKG_DIR/$package ]] { - triggers+=$package - } - } -} - -function apply_triggers { - typeset package - for package in $triggers; { - exec_hook $package 'post-update' - } -} - -# Iterate a function over every installed package -function wrap { - typeset function=$1 - typeset arg=$2 - typeset progress=$3 - typeset i - typeset -i all current - - if [[ -n $2 ]] { - $function $2 - } else { - function wrap_info { - clear_line - info "$1: " - } - [[ -n $progress ]] && all=$(list_packages_local | wc -l) - [[ -n $progress ]] && current=0 - for package in *(-/); { - cd $PKG_DIR - (( current++ )) - [[ -n $progress ]] && progress $current $all $progress $package - $function $package - } - [[ -n $progress ]] && clear_line - } -} - -## -## The "frontend" functions -## - -function pkg_add { - if [[ -d $PKG_DIR/$1 ]] { - info "Package '$1' is already installed!\n" - exit 1 - } - check_valid $1 - info "Retrieving package $1...\n" - vcs_add $1 || return 255 - global_hook $1 post-add -} - -function pkg_push { - check_installed $1 - check_valid $1 - if list_incoming $1; then - clear_line - info "Pushing $1\n" - global_hook $1 pre-update - vcs_push $1 - global_hook $1 post-update - fi -} - -function pkg_remove { - check_installed $1 - if [[ -r $PKG_DIR/$1/priority ]] { - if (( $(cat $PKG_DIR/$1/priority) > 3 )) { - confirm_no "Package '$1' is $(priority_name $(cat $PKG_DIR/$1/priority)). Really remove?" || return - } - } - global_hook $1 pre-remove - rm -rf $PKG_DIR/$1 - info "Package removed.\n" -} - -function pkg_upgrade { - check_installed $1 - check_valid $1 - if [[ $(list_type $1) != $(list_type_local $1) ]] { - clear_line - warn "Incompatible systems. Please reinstall: $1\n" - warn " remote '$(list_type $1)' <-> local '$(list_type_local)'\n" - return 9 - } - if list_incoming $1; then - clear_line - info "Updating $1 to $(list_version_remote $1)\n" - global_hook $1 pre-update - vcs_pull $1 - global_hook $1 post-update - fi -} - -function pkg_list { - typeset package crap - case $1 in - ''|local) - list_packages_local - ;; - all|remote) - list_packages_remote - ;; - not-installed) - for package in $(list_packages_remote); { - list_is_installed $package || echo $package - } - ;; - esac -} - -function pkg_status { - typeset vcs_status - check_installed $1 - vcs_status=$(PAGER='' vcs_status $1) - if [[ -n $vcs_status && $vcs_status != *'nothing to commit (working directory clean)' ]] { - if ((SILENT)) { - echo $1 - } else { - clear_line - info "$1:\n" - echo $vcs_status - } - } -} - -function pkg_refresh { - check_installed $1 - global_hook $1 pre-update - global_hook $1 post-update -} - -function pkg_update { - if [[ -z $1 || $1 == local ]] { - info "Updating local package list\n" - list_update_local - clear_line - } - if [[ -z $1 || $1 == remote ]] { - info "Updating remote package list\n" - list_update_remote - } -} - -function pkg_info { - check_valid $1 - - typeset name=$1 - typeset repo_type=$(list_type $1) - typeset priority priority_name - typeset hooks makefile discription state - if [[ -d $1 ]] { - cd $1 - if [[ -r priority ]] { - priority=$(cat priority) - priority_name=$(priority_name $priority) - } - if [[ -d hooks ]] { - hooks=$(ls -m hooks) - } - if [[ -r Makefile ]] { - makefile=1 - } - size=$(du -sh .$(list_type $1) | grep -o '.*[KMG]') - if [[ -r description ]] { - description=$(cat description) - } - state='installed' - if list_incoming $1; then - state+=', needs update' - else - state+=', up-to-date' - fi - } else { - state='not installed' - } - - function show_info { - [[ -z $2 ]] && return - info "$1: " - echo $2 - } - - show_info 'Package' $name - show_info 'State' $state - [[ -n $priority ]] && show_info 'Priority' "$priority ($priority_name)" - show_info 'Local Version' $(list_version_local $1) - show_info 'Remote Version' $(list_version_remote $1) - show_info 'Repository Type' $repo_type - show_info 'Repository Size' $size - show_info 'Hooks' $hooks - show_info 'Description' $description -} - -function pkg_log { - check_installed $1 - vcs_log $1 -} - - -cd $PKG_DIR || die "Cannot cd $PKG_DIR" - -# Note: -# wrap foobar "$1" <- the "" are neccessary here, since $1 is optional (and therefore may be empty) -case $action in - a|add|install) - if [[ $action == install ]] { - warn "pkg install is deprecated, use pkg add instead\n" - } - pkg_add $* - ;; - rm|delete|remove) - if [[ $action == delete ]] { - warn "pkg delete is deprecated, use pkg remove instead\n" - } - pkg_remove $* - ;; - i|info) pkg_info $* ;; - ls|list) pkg_list $* ;; - l|log) pkg_log $* ;; - p|push) - (( AUTOUPDATE )) && pkg_update - wrap pkg_push "$1" 'Pushing' - ;; - r|refresh) wrap pkg_refresh "$1" 'Refreshing' ;; - s|status) wrap pkg_status "$1" 'Checking package status' ;; - u|update) pkg_update $* ;; - f|upgrade|pull) - if [[ $action == upgrade ]] { - warn "pkg updgrade is deprecated, use pkg pull instead\n" - } - (( AUTOUPDATE )) && pkg_update remote - wrap pkg_upgrade "$1" 'Looking for updates' - ;; - e|eval) eval $* ;; - *) die "wait, what?\npkg: unknown action: '$action'\n" ;; -esac - -apply_triggers diff --git a/description b/description index 89bdd85..3a2db36 100644 --- a/description +++ b/description @@ -1,2 +1,2 @@ -The pkg package -Contains the 'pkg' utility itself, 'checklinks' and the 'bootstrap' script. +The caretaker package +Contains the 'ct' utility itself, 'checklinks' and the 'bootstrap' script. diff --git a/include/bootstrap b/include/bootstrap index 07d8c4f..880fa13 100755 --- a/include/bootstrap +++ b/include/bootstrap @@ -1,7 +1,7 @@ #!/usr/bin/env zsh # bootstrap - populate a home with the most necessary scripts -# After running this, other packages can be installed using 'pkg' -# Note: Since pkg is written in zsh, this script also is. +# After running this, other packages can be installed using 'ct' +# Note: Since caretaker is written in zsh, this script also is. # This way, I don't have to check for zsh somewhere in the script, # and also have an excuse for using zsh here :P @@ -19,7 +19,7 @@ if [[ -n $1 ]] { Usage: ./bootstrap PKG_ROOT [PKG_DIR] PKG_ROOT is an URI, either of the form proto://host/path, or just /path Note: The path must be absolute, it may not contain a literal ~ - PKG_DIR is the path where pkg and all further packages will be installed, + PKG_DIR is the path where caretaker and all further packages will be installed, by default $default_path meow exit 100 @@ -45,25 +45,25 @@ if ! which git &> /dev/null; then exit 200 fi -echo 'Fetching the pkg package...' +echo 'Fetching the caretaker package...' cd $PKG_DIR -git clone $PKG_ROOT/pkg -cd pkg +git clone $PKG_ROOT/caretaker caretaker +cd caretaker -echo 'Writing pkg.conf' -mkdir -p $XDG_CONFIG_HOME/pkg -cat > $XDG_CONFIG_HOME/pkg/pkg.conf <<- flurbl +echo 'Writing caretaker.conf' +mkdir -p $XDG_CONFIG_HOME/caretaker +cat > $XDG_CONFIG_HOME/caretaker/caretaker.conf <<- flurbl PKG_ROOT='$PKG_ROOT' PKG_DIR="${PKG_DIR/$HOME/\$HOME}" flurbl -echo 'Installing pkg package' +echo 'Installing caretaker package' rehash bin/checklinks -bin/pkg eval populate_collected pkg -bin/pkg eval exec_hook pkg post-add +bin/ct eval populate_collected caretaker +bin/ct eval exec_hook caretaker post-add -bin/pkg update +bin/ct update if (( rcempty )) { echo 'Removing empty zshrc' @@ -73,6 +73,6 @@ if (( rcempty )) { if [[ $PATH != *$HOME/bin* ]] { cat <<- tac Note: You may need to change your PATH to include ~/bin, - otherwise certain parts of pkg will not work. + otherwise certain parts of caretaker will not work. tac } diff --git a/include/newpackage b/include/newpackage index 924da44..889adfd 100755 --- a/include/newpackage +++ b/include/newpackage @@ -11,7 +11,7 @@ while [[ $1 == --* ]] { shift } -[[ -r ~/.pkg.conf ]] && source ~/.pkg.conf +[[ -r ~/.caretaker.conf ]] && source ~/.caretaker.conf [[ -d $PKG_ROOT ]] || exit 1 cd $PKG_ROOT diff --git a/include/pkglist b/include/pkglist index 0986150..660036b 100755 --- a/include/pkglist +++ b/include/pkglist @@ -1,6 +1,6 @@ #!/usr/bin/env zsh ## on the PKG_HOST: list available packages -## used by pkg remote-update +## used by ct remote-update # the PKG_PATH (package root path) is given as first argument ($1) if [[ ! -d $1 ]] { diff --git a/man/1/ct.pod b/man/1/ct.pod new file mode 100644 index 0000000..4bdba98 --- /dev/null +++ b/man/1/ct.pod @@ -0,0 +1,162 @@ +=head1 NAME + +caretaker - distributed dotfile and script manager + +=head1 SYNOPSIS + +B [I] I [I] + +=head1 DESCRIPTION + +B is the basic script for maintaining packages + +I may be one of: + +=over + +=item B I + +fetch given I from the package_root and install it + +=item B I + +Remove I from the local packages tree + +=item B I + +Evauluate I. See caretaker(7) + +=item B I + +Show information on I, like dependencies, version, etc + +=item B [I] + +List packages depending on I: + +=over + +=item * B + +show all packages + +=item * B + +show currently installed packages (default) + +=item * B + +show remote packages which are not installed + +=back + +=item B I + +Show commit history for package + +=item B [ I ] + +push new local versions to PKG_ROOT. +If no argument is given, pushes all installed packages + +=item B [ I ] + +Check prereqs and update symlinks of I. +If no argument is given, refreshs all installed packages + +=item B [ I ] + +Check for local changes to the I's files. +If no arguments is given, checks all installed packages + +=item B [ B|B ] + +Update local/remote package list (both if no argument is given) + +=item B [ I ] + +retrieve and install newest available version of I. +If no argument is given, updates all installed packages + +=back + +=head2 SHORT ACTIONS + +Instead of the Is described above, you may also use their short +forms: + + short long form + ----- --------- + a add + e eval + f pull (think "fetch") + i info + l log + ls list + p push + r refresh + rm remove + s status + u update + +=head1 OPTIONS + +Options marked as [boolean] may be negated +by prepending a 'no', like '--no-auto-update'. + +Options always override the configuration file. +Also, every option refers to a configuration parameter, +so please see caretaker.conf(5) for more information. + +=head1 FILES + +=over + +=item F<~/.caretaker.conf> + +Contains configuration vars + +=back + +=head1 EXAMPLES + +=over + +=item * B + +Get new stuff from the packages_root. +Note: If you haven't disabled AUTOUPDATE, B will suffice here + +=item * B + +Push local changes to the packages_root. +Note: If you haven't disabled AUTOUPDATE, B will suffice here + +=item * B + +'Fake-Upgrade'. Execute the appropiate commands as if the caretaker package had been +upgraded (useful when you change something but don't want to commit/push yet) + +=back + +=head1 AUTHOR + +Daniel Friesel Ederf@derf.homelinux.orgE + +=head1 CREDITS + +=over + +=item * Lars Stoltenow (penma) + +for pointing out various bugs and design mistakes + +=item * Maximilian GaE (mxey) + +many ideas and suggestions + +=back + +=head1 SEE ALSO + +L(5), caretaker(7) diff --git a/man/1/pkg.pod b/man/1/pkg.pod deleted file mode 100644 index 7f207ba..0000000 --- a/man/1/pkg.pod +++ /dev/null @@ -1,162 +0,0 @@ -=head1 NAME - -pkg - distributed dotfile and script manager - -=head1 SYNOPSIS - -B [I] I [I] - -=head1 DESCRIPTION - -B is the basic script for maintaining packages - -I may be one of: - -=over - -=item B I - -fetch given I from the package_root and install it - -=item B I - -Remove I from the local packages tree - -=item B I - -Evauluate I. See pkg(7) - -=item B I - -Show information on I, like dependencies, version, etc - -=item B [I] - -List packages depending on I: - -=over - -=item * B - -show all packages - -=item * B - -show currently installed packages (default) - -=item * B - -show remote packages which are not installed - -=back - -=item B I - -Show commit history for package - -=item B [ I ] - -push new local versions to PKG_ROOT. -If no argument is given, pushes all installed packages - -=item B [ I ] - -Check prereqs and update symlinks of I. -If no argument is given, refreshs all installed packages - -=item B [ I ] - -Check for local changes to the I's files. -If no arguments is given, checks all installed packages - -=item B [ B|B ] - -Update local/remote package list (both if no argument is given) - -=item B [ I ] - -retrieve and install newest available version of I. -If no argument is given, updates all installed packages - -=back - -=head2 SHORT ACTIONS - -Instead of the Is described above, you may also use their short -forms: - - short long form - ----- --------- - a add - e eval - f pull (think "fetch") - i info - l log - ls list - p push - r refresh - rm remove - s status - u update - -=head1 OPTIONS - -Options marked as [boolean] may be negated -by prepending a 'no', like '--no-auto-update'. - -Options always override the configuration file. -Also, every option refers to a configuration parameter, -so please see pkg.conf(5) for more information. - -=head1 FILES - -=over - -=item F<~/.pkg.conf> - -Contains configuration vars - -=back - -=head1 EXAMPLES - -=over - -=item * B - -Get new stuff from the packages_root. -Note: If you haven't disabled AUTOUPDATE, B will suffice here - -=item * B - -Push local changes to the packages_root. -Note: If you haven't disabled AUTOUPDATE, B will suffice here - -=item * B - -'Fake-Upgrade'. Execute the appropiate commands as if the pkg package had been -upgraded (useful when you change something but don't want to commit/push yet) - -=back - -=head1 AUTHOR - -Daniel Friesel Ederf@derf.homelinux.orgE - -=head1 CREDITS - -=over - -=item * Lars Stoltenow (penma) - -for pointing out various bugs and design mistakes - -=item * Maximilian GaE (mxey) - -many ideas and suggestions - -=back - -=head1 SEE ALSO - -L(5), pkg(7) diff --git a/man/5/caretaker.conf.pod b/man/5/caretaker.conf.pod new file mode 100644 index 0000000..c083a3f --- /dev/null +++ b/man/5/caretaker.conf.pod @@ -0,0 +1,114 @@ +=head1 NAME + +~/.config/caretaker/caretaker.conf - L(1) configuration + +=head1 DESCRIPTION + +Contains configuration parameters for L(1) + +The file consists of multiple lines of the form I=I. + +Normal zsh syntax is allowed, which means everything after '#' will be treated +as comment and that there must be no space between the parameter and the content. + +With the exception of B, all parameters are optional as they have +reasonable (so I hope) defaults. + +=head1 OPTIONS + +The text in (braces) refers to the ct commandline option with which the config +setting may be overridden (if present). +The text in [these braces] is the default value. + +=over + +=item B=I (--packageroot I) + +the package root path. +I may either be of the form C or C. + +=item B=I [$PKG_PATH/pkglist] + +path to pkglist on the package root, to generate the package list. +In the default, $PKG_PATH means the path component of $PKG_ROOT. + +=item B=I [0] + +If true, B will always be executed on the local machine, regardless +of where the B lies. For possible uses of this setting, see +L + +=item B=I (--packagedir I) [$HOME/packages] + +path for the local package tree + +=item B=(I) (--checklinks-options I) [-q] + +Options to invoke L(1) with + +=item B=I (--colours) [1] + +Colorize the output (cyan for info messages, red for errors/warnings) + +=item B=I (--progress) [1] + +Show a progress bar when performing tasks on all packages + +=item B=I (--quiet) [0] + +Operate in silent mode. If B<1>, it also sets PROGRESS=0 + +=item B=I (--debug) [0] + +Operate in debug mode if 1 + +=item B=I (--auto-update) [1] + +If 1, automatically execute 'ct update' before 'ct push' +and 'ct remote-update' before 'ct upgrade' + +=item B=I [1] + +By default, caretaker will simply issue a git push/pull, so that git will use the +repository's origin to determine where to push/pull. + +If you regularly change your PKG_ROOT, are too lazy to properly configure your +git repos or whatever, set this to 0. Then, caretaker will always call git pull/push +with both the remote repo and the branch as arguments. + +=item BI {I} + +Define the global hook I, its I will be executed +together with caretaker's global hooks; the name of the package for which the hook is +being executed will be given as first parameter and is accessible throug B<$1>. +The hook is just a zsh function, so you can use any valid syntax you want, +including newlines. +See zsh(1) for more. + +Valid I names are: post-add, pre-update, post-update, pre-remove. +Note that post-add automatically executes post-update. + +Example: function pkg_hook_post-update {clear_line; echo "Hello from package $1!"} + +=back + +=head2 COLOURS + +Colours are defined in the same way as options. They take an ANSI escape code +as argument. + +=over + +=item B=I (C<$'\e[0;36m'>) + +Colour for informational messages (default: cyan) + +=item B=I (C<$'\e[0;31m'>) + +Colour for warning and error messages (default: red) + +=back + +=head1 SEE ALSO + +L(1), L(1) diff --git a/man/5/pkg.conf.pod b/man/5/pkg.conf.pod deleted file mode 100644 index c66ae2d..0000000 --- a/man/5/pkg.conf.pod +++ /dev/null @@ -1,114 +0,0 @@ -=head1 NAME - -~/.config/pkg/pkg.conf - L(1) configuration - -=head1 DESCRIPTION - -Contains configuration parameters for L(1) - -The file consists of multiple lines of the form I=I. - -Normal zsh syntax is allowed, which means everything after '#' will be treated -as comment and that there must be no space between the parameter and the content. - -With the exception of B, all parameters are optional as they have -reasonable (so I hope) defaults. - -=head1 OPTIONS - -The text in (braces) refers to the pkg commandline option with which the config -setting may be overridden (if present). -The text in [these braces] is the default value. - -=over - -=item B=I (--packageroot I) - -the package root path. -I may either be of the form C or C. - -=item B=I [$PKG_PATH/pkglist] - -path to pkglist on the package root, to generate the package list. -In the default, $PKG_PATH means the path component of $PKG_ROOT. - -=item B=I [0] - -If true, B will always be executed on the local machine, regardless -of where the B lies. For possible uses of this setting, see -L - -=item B=I (--packagedir I) [$HOME/packages] - -path for the local package tree - -=item B=(I) (--checklinks-options I) [-q] - -Options to invoke L(1) with - -=item B=I (--colours) [1] - -Colorize the output (cyan for info messages, red for errors/warnings) - -=item B=I (--progress) [1] - -Show a progress bar when performing tasks on all packages - -=item B=I (--quiet) [0] - -Operate in silent mode. If B<1>, it also sets PROGRESS=0 - -=item B=I (--debug) [0] - -Operate in debug mode if 1 - -=item B=I (--auto-update) [1] - -If 1, automatically execute 'pkg update' before 'pkg push' -and 'pkg remote-update' before 'pkg upgrade' - -=item B=I [1] - -By default, pkg will simply issue a git push/pull, so that git will use the -repository's origin to determine where to push/pull. - -If you regularly change your PKG_ROOT, are too lazy to properly configure your -git repos or whatever, set this to 0. Then, pkg will always call git pull/push -with both the remote repo and the branch as arguments. - -=item BI {I} - -Define the global hook I, its I will be executed -together with pkg's global hooks; the name of the package for which the hook is -being executed will be given as first parameter and is accessible throug B<$1>. -The hook is just a zsh function, so you can use any valid syntax you want, -including newlines. -See zsh(1) for more. - -Valid I names are: post-add, pre-update, post-update, pre-remove. -Note that post-add automatically executes post-update. - -Example: function pkg_hook_post-update {clear_line; echo "Hello from package $1!"} - -=back - -=head2 COLOURS - -Colours are defined in the same way as options. They take an ANSI escape code -as argument. - -=over - -=item B=I (C<$'\e[0;36m'>) - -Colour for informational messages (default: cyan) - -=item B=I (C<$'\e[0;31m'>) - -Colour for warning and error messages (default: red) - -=back - -=head1 SEE ALSO - -L(1), L(1) diff --git a/man/7/caretaker-setup.pod b/man/7/caretaker-setup.pod new file mode 100644 index 0000000..d59a0a2 --- /dev/null +++ b/man/7/caretaker-setup.pod @@ -0,0 +1,17 @@ +=head1 NAME + +caretaker - Package root setup + +=head1 PACKAGE ROOT + +First, you'll need to create a root directory (from now on PKG_ROOT) on the +server which shall from now on host all your packages. +Then you need to put the caretaker git repository into F - it's recommended +to do this via git clone --bare. + +Copy the pkglist script (include/pkglist) to F. + +Now you can add your own packages as git repos in PKG_ROOT. + +To use caretaker with your packages on a machine, download and execute +the bootstrap script (include/bootstrap). diff --git a/man/7/caretaker.pod b/man/7/caretaker.pod new file mode 100644 index 0000000..bbef02d --- /dev/null +++ b/man/7/caretaker.pod @@ -0,0 +1,256 @@ +=head1 NAME + +caretaker - distributed dotfile and script manager, package format + +=head1 INTRO + +(if you prefer technical infos over historical blah-blah, skip this section) + +Actually, caretaker is just a pimped dotfile manager, which just happens to support +a sort of packages, version control, automatic sym- and hardlinking, and which +can also handle scripts and binaries. Oh, and it can cause serious brain damage. + +It evolved from two hg repos for ~/bin and ~/etc and some management scripts, +and now it can handle as many git repos as you want, which may contain +basically anything you can think of - you can even store movies in them. +(Of course that would be completely braindead, but hey - +you could, if you wanted to) + +=head1 THE BASICS + +caretaker requires two directories in your home directory. B<~/bin> contains +symlinks to the executables shipped with your packages, and $PKG_DIR +(B<~/packages> by default) contains the +packages themselves. B<~/bin> may also contain normal executables; caretaker will +not overwrite existing files. + +=head1 THE PACKAGE DIRECTORY + +$PKG_DIR is the core of all this stuff. Its main use is storing the packages. +There is one directory for each installed package, as created by B. +$PKG_DIR holds two special files: B<.list> and B<.list-remote>. For +an explanation about these files, see L below. +It also contains a special directory, F<.collected> - see L. + +=head2 NOTE + +All directories in $PKG_DIR must be valid git repositories which are not in the +state of 'initial commit'. Dotfiles (directories starting with a .) are exempt +from this, they will be ignored by caretaker. + +=head1 THE PACKAGE ROOT + +The packages_root, in caretaker referred to as $PKG_ROOT, is structured just like +the packages directory $PKG_DIR, except that it neither contains .list nor +.list-remote. The packages root is the central point where caretaker fetches +packages from and pushes packages to. + +The package root should contain the pkglist script shipped in include/. +If it doesn't, PKGLIST_PATH in .caretaker.conf must be set to the appropiate +location on the package root host. + +=head1 THE PACKAGE LIST + +The package list lives in the files B<.list> and B<.list-remote> mentioned +above. It's used to decide whether a package needs to be pulled / pushed. +Also, the 'caretaker add' completion relies on .list-remote, and back in the days when +caretaker supported more than one DVCS, it was used to determine which DVCS to use +for which package. + +It consists of one line per package, each line containing three items separated +by a single whitespace. The first item is the package name, the second one the +repository type (DVCS), the third the current revision. Example: + + caretaker git 82d716d01dee0329af7df5e67b55558fe3ff1466 + +The package list is generated by the script set in the config var $PKGLIST_PATH, +by default F. Depending on $PKGLIST_LOCAL and $PKG_ROOT, it +is either executed on the local host or on the remote host containing the package +root. The script is always called with $PKG_PATH as the first argument. +Its output must only contain valid pkglist lines (see the example above). + +With $PKGLIST_LOCAL set to 1, there are some interesting possibilities. +For instance, your pkglist script could contain a line like +C<< curl -s http://example.org/cgi-bin/pkglist.cgi >> - so you can update your +remote package list without having to use ssh. + +=head1 WHAT IS A PACKAGE? + +Anything tracked with git can be used as package. However, as the purpose of caretaker +is not to do your version control, you probably want to have at least one of the +files and directories described below in it. + +=head1 PACKAGE STRUCTURE + +Special (as in, mostly handled by caretaker) directories and files in a package. + +Note that all files and directories mentioned here are optional. + +=over + +=item bin/ + +The place for executables to be in the user's PATH. +caretaker will automatically create symlinks in F<~/bin> pointing to the files +in the package's F. Also, if a file in F contains valid POD, +a manual will be generated out of it (see L) + +=item etc/ + +Configuration files, not treated specially though + +=item hooks/ + +Package hooks, see L + +=item include/ + +Scripts used by the package that don't belong into B. Not treated specially + +=item man/ + +Manual files in POD format, separated by section (like man/7/caretaker.pod). +To be prepared for possible future support of other manual formats, it is +recommended to postfix each file with .pod + +=item provides/ + +Files for inclusion into other packages. See L + +=item description + +Package description for B + +=item links + +Sym- and hardlink descriptions. See checklinks(1) + +=item Makefile + +If a Makefile is available, C will be executed every time the package +is updated (ct add/push/pull/refresh) + +=item prereqs + +The package's prerequisites, mainly dependencies. See L + +=item priority + +Package priority as an integer between 1 and 6. +Packages with a priority above 3 require user confirmation to be removed + +=back + +=head1 PREREQUISITES + +The prerequisites are stored in a package in the file F. +It as an ordinary shell script which is sourced by caretaker's global post-update +hook; so it will be sourced after pulling, pushing or refreshing a package. + +Note that the file will be sourced in function scope. It is recommended to +introduce parameters and options local to the prereqs script with +C<< typeset >> and C<< setopt localoptions >>, respectively. + +It's main use is to check for dependencies. To help with this, the following +functions are available: + +=over + +=item B I + +Returns true if I is installed, otherwise false + +=item B I + +Returns true if I can be used by perl, otherwise false + +=item B I + +Returns true if I was found in the users PATH, otherwise false + +=item B I + +Mark I for installation + +=item B I | B I + +Execute expression and automatically warn if it fails. +In case of B, automatically mark B for installation +if it isn't installed. +If a B fails, caretaker will inform the user about it and wait for confirmation + +=item B I<...>, B I<...> + +Take the same arguments as B, but are of lower priority. +recommend only causes "info" messages, and suggest does not interrupt caretaker +to make sure it's read by the user + +=back + +Additionally, the string parameters B and B can be used to store +messages. + +After executing the prereqs script, caretaker will print the content of +these parameters and wait for confirmation. +It will also offer to install packages marked by B or +B. + +=head1 PROVIDES + +The F directory contains subdirectories with the names of the package +for which stuff is provided. Every time a package with a provides directory is +added, updated or removed, for each of the directories in F, the +respective package's post-update hook is exectued. It is the responsibility +of that hook to do something useful with the data in F. + +=head1 HOOKS + +Hooks are little zsh snippets residing in $PKG_DIR/hooks +which are sourced from within caretaker whenever needed. + +Currently, the following hooks exist: + +=over + +=item post-add + +Sourced after a package was installed (e.g. with ct add/ct install) + +=item post-update + +Sourced after a package was updated (ct pull). +It is also sourced when adding a package (after post-add) and +when calling ct refresh. + +=item pre-remove + +Sourced before a package is removed (ct remove) + +=back + +=head1 COLLECTED PACKAGE FILES + +These files reside in F<$PKG_DIR/.collected> (subject to change). + +The directory is somewhat similar to F<~/bin> - it is automatically populated +by caretaker. However, this one does not contain symlinks. + +Currently, it only contains the directory F, which holds the "compiled" +manual pages from the packages (extracted from F and F). +This way, you can put F<.../.collected/man> into you MANPATH to access manuals +provided by packages. + +=head1 GIT + +B uses git(1) as backend for storing and syncing package information. +It is not recommended to use branches other than "master". +While they should work if GIT_USE_ORIGIN is set to 1 (the default), they will +most likely confuse caretaker. + +=head1 AUTHOR + +Daniel Friesel Ederf@derf.homelinux.orgE + +=head1 SEE ALSO + +checklinks(1), ct(1) diff --git a/man/7/pkg-setup.pod b/man/7/pkg-setup.pod deleted file mode 100644 index 1c0d7bf..0000000 --- a/man/7/pkg-setup.pod +++ /dev/null @@ -1,17 +0,0 @@ -=head1 NAME - -pkg - Package root setup - -=head1 PACKAGE ROOT - -First, you'll need to create a root directory (from now on PKG_ROOT) on the -server which shall from now on host all your packages. -Then you need to put the pkg git repository into F - it's recommended -to do this via git clone --bare. - -Copy the pkglist script (include/pkglist) to F. - -Now you can add your own packages as git repos in PKG_ROOT. - -To use pkg with your packages on a machine, download and execute -the bootstrap script (include/bootstrap). diff --git a/man/7/pkg.pod b/man/7/pkg.pod deleted file mode 100644 index 8288b54..0000000 --- a/man/7/pkg.pod +++ /dev/null @@ -1,256 +0,0 @@ -=head1 NAME - -pkg - distributed dotfile and script manager, package format - -=head1 INTRO - -(if you prefer technical infos over historical blah-blah, skip this section) - -Actually, pkg is just a pimped dotfile manager, which just happens to support -a sort of packages, version control, automatic sym- and hardlinking, and which -can also handle scripts and binaries. Oh, and it can cause serious brain damage. - -It evolved from two hg repos for ~/bin and ~/etc and some management scripts, -and now it can handle as many git repos as you want, which may contain -basically anything you can think of - you can even store movies in them. -(Of course that would be completely braindead, but hey - -you could, if you wanted to) - -=head1 THE BASICS - -pkg requires two directories in your home directory. B<~/bin> contains -symlinks to the executables shipped with your packages, and $PKG_DIR -(B<~/packages> by default) contains the -packages themselves. B<~/bin> may also contain normal executables; pkg will -not overwrite existing files. - -=head1 THE PACKAGE DIRECTORY - -$PKG_DIR is the core of all this stuff. Its main use is storing the packages. -There is one directory for each installed package, as created by B. -$PKG_DIR holds two special files: B<.list> and B<.list-remote>. For -an explanation about these files, see L below. -It also contains a special directory, F<.collected> - see L. - -=head2 NOTE - -All directories in $PKG_DIR must be valid git repositories which are not in the -state of 'initial commit'. Dotfiles (directories starting with a .) are exempt -from this, they will be ignored by pkg. - -=head1 THE PACKAGE ROOT - -The packages_root, in pkg referred to as $PKG_ROOT, is structured just like -the packages directory $PKG_DIR, except that it neither contains .list nor -.list-remote. The packages root is the central point where pkg fetches -packages from and pushes packages to. - -The package root should contain the pkglist script shipped in include/. -If it doesn't, PKGLIST_PATH in .pkg.conf must be set to the appropiate -location on the package root host. - -=head1 THE PACKAGE LIST - -The package list lives in the files B<.list> and B<.list-remote> mentioned -above. It's used to decide whether a package needs to be pulled / pushed. -Also, the 'pkg add' completion relies on .list-remote, and back in the days when -pkg supported more than one DVCS, it was used to determine which DVCS to use -for which package. - -It consists of one line per package, each line containing three items separated -by a single whitespace. The first item is the package name, the second one the -repository type (DVCS), the third the current revision. Example: - - pkg git 82d716d01dee0329af7df5e67b55558fe3ff1466 - -The package list is generated by the script set in the config var $PKGLIST_PATH, -by default F. Depending on $PKGLIST_LOCAL and $PKG_ROOT, it -is either executed on the local host or on the remote host containing the package -root. The script is always called with $PKG_PATH as the first argument. -Its output must only contain valid pkglist lines (see the example above). - -With $PKGLIST_LOCAL set to 1, there are some interesting possibilities. -For instance, your pkglist script could contain a line like -C<< curl -s http://example.org/cgi-bin/pkglist.cgi >> - so you can update your -remote package list without having to use ssh. - -=head1 WHAT IS A PACKAGE? - -Anything tracked with git can be used as package. However, as the purpose of pkg -is not to do your version control, you probably want to have at least one of the -files and directories described below in it. - -=head1 PACKAGE STRUCTURE - -Special (as in, mostly handled by pkg) directories and files in a package. - -Note that all files and directories mentioned here are optional. - -=over - -=item bin/ - -The place for executables to be in the user's PATH. -pkg will automatically create symlinks in F<~/bin> pointing to the files -in the package's F. Also, if a file in F contains valid POD, -a manual will be generated out of it (see L) - -=item etc/ - -Configuration files, not treated specially though - -=item hooks/ - -Package hooks, see L - -=item include/ - -Scripts used by the package that don't belong into B. Not treated specially - -=item man/ - -Manual files in POD format, separated by section (like man/7/pkg.pod). -To be prepared for possible future support of other manual formats, it is -recommended to postfix each file with .pod - -=item provides/ - -Files for inclusion into other packages. See L - -=item description - -Package description for B - -=item links - -Sym- and hardlink descriptions. See checklinks(1) - -=item Makefile - -If a Makefile is available, C will be executed every time the package -is updated (pkg add/push/pull/refresh) - -=item prereqs - -The package's prerequisites, mainly dependencies. See L - -=item priority - -Package priority as an integer between 1 and 6. -Packages with a priority above 3 require user confirmation to be removed - -=back - -=head1 PREREQUISITES - -The prerequisites are stored in a package in the file F. -It as an ordinary shell script which is sourced by pkg's global post-update -hook; so it will be sourced after pulling, pushing or refreshing a package. - -Note that the file will be sourced in function scope. It is recommended to -introduce parameters and options local to the prereqs script with -C<< typeset >> and C<< setopt localoptions >>, respectively. - -It's main use is to check for dependencies. To help with this, the following -functions are available: - -=over - -=item B I - -Returns true if I is installed, otherwise false - -=item B I - -Returns true if I can be used by perl, otherwise false - -=item B I - -Returns true if I was found in the users PATH, otherwise false - -=item B I - -Mark I for installation - -=item B I | B I - -Execute expression and automatically warn if it fails. -In case of B, automatically mark B for installation -if it isn't installed. -If a B fails, pkg will inform the user about it and wait for confirmation - -=item B I<...>, B I<...> - -Take the same arguments as B, but are of lower priority. -recommend only causes "info" messages, and suggest does not interrupt pkg -to make sure it's read by the user - -=back - -Additionally, the string parameters B and B can be used to store -messages. - -After executing the prereqs script, pkg will print the content of -these parameters and wait for confirmation. -It will also offer to install packages marked by B or -B. - -=head1 PROVIDES - -The F directory contains subdirectories with the names of the package -for which stuff is provided. Every time a package with a provides directory is -added, updated or removed, for each of the directories in F, the -respective package's post-update hook is exectued. It is the responsibility -of that hook to do something useful with the data in F. - -=head1 HOOKS - -Hooks are little zsh snippets residing in $PKG_DIR/hooks -which are sourced from within pkg whenever needed. - -Currently, the following hooks exist: - -=over - -=item post-add - -Sourced after a package was installed (e.g. with pkg add/pkg install) - -=item post-update - -Sourced after a package was updated (pkg pkg pull). -It is also sourced when adding a package (after post-add) and -when calling pkg refresh. - -=item pre-remove - -Sourced before a package is removed (pkg remove) - -=back - -=head1 COLLECTED PACKAGE FILES - -These files reside in F<$PKG_DIR/.collected> (subject to change). - -The directory is somewhat similar to F<~/bin> - it is automatically populated -by pkg. However, this one does not contain symlinks. - -Currently, it only contains the directory F, which holds the "compiled" -manual pages from the packages (extracted from F and F). -This way, you can put F<.../.collected/man> into you MANPATH to access manuals -provided by packages. - -=head1 GIT - -B uses git(1) as backend for storing and syncing package information. -It is not recommended to use branches other than "master". -While they should work if GIT_USE_ORIGIN is set to 1 (the default), they will -most likely confuse pkg. - -=head1 AUTHOR - -Daniel Friesel Ederf@derf.homelinux.orgE - -=head1 SEE ALSO - -checklinks(1), pkg(1) diff --git a/provides/zsh/alias b/provides/zsh/alias deleted file mode 100644 index b2517bc..0000000 --- a/provides/zsh/alias +++ /dev/null @@ -1 +0,0 @@ -alias ct=pkg diff --git a/provides/zsh/completions/_ct b/provides/zsh/completions/_ct new file mode 100644 index 0000000..c0af61b --- /dev/null +++ b/provides/zsh/completions/_ct @@ -0,0 +1,89 @@ +#compdef ct +## vim:ft=zsh +## caretaker completion + +typeset expl + +function _ct_action () { + _wanted action expl 'action' \ + compadd add eval remove info list \ + log pull push refresh status update +} + +function _ct_installed () { + _wanted package expl 'local package' \ + compadd $(ct list local) +} + +function _ct_all () { + _wanted package expl 'package' \ + compadd $(ct list all) +} + +function _ct_notinstalled () { + _wanted package expl 'remote package' \ + compadd $(ct list not-installed) +} + +function _ct_args { + if (( CURRENT == 2 )) { + case ${words[1]} in + l|log|f|pull|p|push|r|refresh|rm|remove|s|status) + _ct_installed + ;; + i|info) + _ct_all + ;; + a|add) + _ct_notinstalled + ;; + e|eval) + _message 'shell code for evaluation' + _wanted function expl 'internal function' \ + compadd $(grep -E '^\S*\s*\(\)\s*{' =ct | cut -d ' ' -f 1) \ + $(grep -E 'function \S* (\(\) )?{' =ct | cut -d ' ' -f 2) + ;; + ls|list) + _wanted something expl 'list mode' \ + compadd all local not-installed + ;; + u|update) + _wanted mode expl 'update target' \ + compadd local remote + ;; + *) + _message 'no more arguments' + ;; + esac + } elif [[ ${words[1]} == e(val|) ]] { + _message 'shell code for evaluation' + if (( CURRENT == 3 )) { + case ${words[2]} in + exec_hook|check_prereqs|*_collected|) + _ct_installed + ;; + esac + } elif (( CURRENT == 4 )) { + case ${words[2]} in + exec_hook) + _wanted hook expl 'package hook' \ + compadd $(ls -1 ~/packages/${words[3]}/hooks 2> /dev/null) + ;; + esac + } + } +} + +_arguments \ + '--quiet[quiet mode]' \ + '--debug[debugmode]' \ + '--auto-update[automatically update package list]' \ + '*--checklinks-options[options for checklinks]:option' \ + '--packagedir[package directory]:directory:_files -/' \ + '--packageroot[package root]:url' \ + '--colours[use colours]' \ + '--no-colours[No colours]' \ + '--progress[show progress bar]' \ + '--no-progress[No progress bar]' \ + ':action:_ct_action' \ + '*::arguments:_ct_args' diff --git a/provides/zsh/completions/_pkg b/provides/zsh/completions/_pkg deleted file mode 100644 index 36f0081..0000000 --- a/provides/zsh/completions/_pkg +++ /dev/null @@ -1,92 +0,0 @@ -#compdef pkg -## vim:ft=zsh -## pkg completion -## Daniel Friesel -## https://derf.homelinux.org/~derf/dotfiles/zsh/completions/_pkg -## see also: https://derf.homelinux.org/~derf/code/lighty-stats - -typeset expl - -function _pkg_action () { - _wanted action expl 'action' \ - compadd add eval remove info list \ - log pull push refresh status update -} - -function _pkg_installed () { - _wanted package expl 'local package' \ - compadd $(pkg list local) -} - -function _pkg_all () { - _wanted package expl 'package' \ - compadd $(pkg list all) -} - -function _pkg_notinstalled () { - _wanted package expl 'remote package' \ - compadd $(pkg list not-installed) -} - -function _pkg_args { - if (( CURRENT == 2 )) { - case ${words[1]} in - l|log|f|pull|p|push|r|refresh|rm|remove|s|status) - _pkg_installed - ;; - i|info) - _pkg_all - ;; - a|add) - _pkg_notinstalled - ;; - e|eval) - _message 'shell code for evaluation' - _wanted function expl 'internal function' \ - compadd $(grep -E '^\S*\s*\(\)\s*{' =pkg | cut -d ' ' -f 1) \ - $(grep -E 'function \S* (\(\) )?{' =pkg | cut -d ' ' -f 2) - ;; - ls|list) - _wanted something expl 'list mode' \ - compadd all local not-installed - ;; - u|update) - _wanted mode expl 'update target' \ - compadd local remote - ;; - *) - _message 'no more arguments' - ;; - esac - } elif [[ ${words[1]} == e(val|) ]] { - _message 'shell code for evaluation' - if (( CURRENT == 3 )) { - case ${words[2]} in - exec_hook|check_prereqs|*_collected|) - _pkg_installed - ;; - esac - } elif (( CURRENT == 4 )) { - case ${words[2]} in - exec_hook) - _wanted hook expl 'package hook' \ - compadd $(ls -1 ~/packages/${words[3]}/hooks 2> /dev/null) - ;; - esac - } - } -} - -_arguments \ - '--quiet[quiet mode]' \ - '--debug[debugmode]' \ - '--auto-update[automatically update package list]' \ - '*--checklinks-options[options for checklinks]:option' \ - '--packagedir[package directory]:directory:_files -/' \ - '--packageroot[package root]:url' \ - '--colours[use colours]' \ - '--no-colours[No colours]' \ - '--progress[show progress bar]' \ - '--no-progress[No progress bar]' \ - ':action:_pkg_action' \ - '*::arguments:_pkg_args' diff --git a/test/add b/test/add index f319813..8094c2b 100644 --- a/test/add +++ b/test/add @@ -1,11 +1,11 @@ ## vim:ft=zsh -echo "# pkg add (ok)" -pkg add $repo +echo "# ct add (ok)" +ct add $repo [[ -e $test_pdir/$repo/foo ]] [[ -d $test_pdir/$repo/.git ]] -echo "# pkg add (already installed)" -! pkg add $repo +echo "# ct add (already installed)" +! ct add $repo echo "# populate_collected" [[ -L $test_home/bin/$file ]] @@ -13,9 +13,9 @@ echo "# populate_collected" [[ -e $test_pdir/.collected/man/man2/$file.2 ]] repeat 2 { - echo "# pkg list" - stringcmp "pkg\n$repo" "$(pkg list local)" - stringcmp "$complement\nrb\nrb-bare\nrc\nrc-bare" "$(pkg list not-installed)" - stringcmp "pkg\nra\nra-bare\nrb\nrb-bare\nrc\nrc-bare" "$(pkg list remote)" - pkg update + echo "# ct list" + stringcmp "caretaker\n$repo" "$(ct list local)" + stringcmp "$complement\nrb\nrb-bare\nrc\nrc-bare" "$(ct list not-installed)" + stringcmp "caretaker\nra\nra-bare\nrb\nrb-bare\nrc\nrc-bare" "$(ct list remote)" + ct update } diff --git a/test/main b/test/main index ff5f596..3544ed0 100755 --- a/test/main +++ b/test/main @@ -3,7 +3,7 @@ unset -m 'GIT_*' 'XDG_*' setopt err_exit trap "print -P '\n%N:%i: %B%F{red}Test faild!%F{default}%b'" ZERR -alias pkg='pkg --quiet --no-colours' +alias ct='ct --quiet --no-colours' source test/documentation @@ -31,39 +31,39 @@ source $tests/checklinks (source $tests/setup) echo "# bootstrapping PKG_DIR" -$test_proot/pkg/include/bootstrap $test_proot $test_pdir +$test_proot/caretaker/include/bootstrap $test_proot $test_pdir echo "# checking for success" -[[ -e $test_home/.config/pkg/pkg.conf ]] -[[ -d $test_proot/pkg ]] -[[ -d $test_pdir/pkg ]] -[[ -L $test_home/bin/pkg ]] +[[ -e $test_home/.config/caretaker/caretaker.conf ]] +[[ -d $test_proot/caretaker ]] +[[ -d $test_pdir/caretaker ]] +[[ -L $test_home/bin/ct ]] [[ -L $test_home/bin/checklinks ]] -[[ -x $(readlink $test_home/bin/pkg) ]] +[[ -x $(readlink $test_home/bin/ct) ]] [[ -x $(readlink $test_home/bin/checklinks) ]] -[[ -e $test_pdir/.collected/man/man1/pkg.1 ]] -[[ -e $test_pdir/.collected/man/man5/pkg.conf.5 ]] -[[ -e $test_pdir/.collected/man/man7/pkg.7 ]] +[[ -e $test_pdir/.collected/man/man1/ct.1 ]] +[[ -e $test_pdir/.collected/man/man5/caretaker.conf.5 ]] +[[ -e $test_pdir/.collected/man/man7/caretaker.7 ]] -echo "# pkg list local" -stringcmp "pkg" "$(pkg list)" -stringcmp "pkg" "$(pkg list local)" +echo "# ct list local" +stringcmp "caretaker" "$(ct list)" +stringcmp "caretaker" "$(ct list local)" -echo "# pkg list not-installed" -stringcmp "ra\nra-bare\nrb\nrb-bare\nrc\nrc-bare" "$(pkg list not-installed)" +echo "# ct list not-installed" +stringcmp "ra\nra-bare\nrb\nrb-bare\nrc\nrc-bare" "$(ct list not-installed)" -echo "# pkg list all" -stringcmp "pkg\nra\nra-bare\nrb\nrb-bare\nrc\nrc-bare" "$(pkg list all)" +echo "# ct list all" +stringcmp "caretaker\nra\nra-bare\nrb\nrb-bare\nrc\nrc-bare" "$(ct list all)" -echo "# pkg add (no such repo)" -! pkg add weltfrieden +echo "# ct add (no such repo)" +! ct add weltfrieden -echo "# pkg add (already installed)" -! pkg add pkg +echo "# ct add (already installed)" +! ct add caretaker for conf_origin in 0 1; { echo "## GIT_USE_ORIGIN=$conf_origin" - echo "GIT_USE_ORIGIN=$conf_origin" >> $test_home/.config/pkg/pkg.conf + echo "GIT_USE_ORIGIN=$conf_origin" >> $test_home/.config/caretaker/caretaker.conf for repo in ra ra-bare; { file=${repo%-*} if [[ $repo == *-bare ]] { diff --git a/test/pull b/test/pull index c6a410a..9a04867 100644 --- a/test/pull +++ b/test/pull @@ -1,6 +1,6 @@ ## vim:ft=zsh if [[ $repo == $file ]] { - echo "# pkg pull" - pkg pull + echo "# ct pull" + ct pull [[ -f $pkgdir/$repo/baz ]] } diff --git a/test/push b/test/push index 8463e61..d4f1894 100644 --- a/test/push +++ b/test/push @@ -3,7 +3,7 @@ somefile=$RANDOM touch $somefile git add $somefile git commit --quiet -m $somefile -pkg push +ct push if [[ $repo == *-bare ]] { [[ $(git --git-dir=$test_proot/$repo log --pretty=oneline | head -1) == *$somefile ]] } else { diff --git a/test/remove b/test/remove index e850ec0..f895e60 100644 --- a/test/remove +++ b/test/remove @@ -1,19 +1,19 @@ ## vim:ft=zsh -echo "# pkg remove (not installed/nonexistent)" -! pkg remove suckage -! pkg remove rb +echo "# ct remove (not installed/nonexistent)" +! ct remove suckage +! ct remove rb -echo "# pkg remove (ok)" -pkg remove $repo +echo "# ct remove (ok)" +ct remove $repo echo "# genocide_collected (~/bin)" [[ ! -L $test_home/bin/$file ]] [[ ! -e $test_pdir/.collected/man/man2/$file.2 ]] repeat 2 { - echo "# pkg list" - stringcmp "pkg" "$(pkg list local)" - stringcmp "ra\nra-bare\nrb\nrb-bare\nrc\nrc-bare" "$(pkg list not-installed)" - stringcmp "pkg\nra\nra-bare\nrb\nrb-bare\nrc\nrc-bare" "$(pkg list remote)" - pkg update + echo "# ct list" + stringcmp "caretaker" "$(ct list local)" + stringcmp "ra\nra-bare\nrb\nrb-bare\nrc\nrc-bare" "$(ct list not-installed)" + stringcmp "caretaker\nra\nra-bare\nrb\nrb-bare\nrc\nrc-bare" "$(ct list remote)" + ct update } diff --git a/test/setup b/test/setup index 9c4fb66..14cfd7b 100644 --- a/test/setup +++ b/test/setup @@ -1,8 +1,8 @@ ## vim:ft=zsh echo "# setting up PKG_ROOT" cd $test_proot -git clone --quiet ${1-git://git.tabularazor.org/~derf/pkg} pkg -cp pkg/include/pkglist pkglist +git clone --quiet ${1-git://git.tabularazor.org/~derf/caretaker} caretaker +cp caretaker/include/pkglist pkglist for repo in ra rb rc; ( mkdir $repo cd $repo -- cgit v1.2.3