Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Zoo: zsh oriented object



Hi all,

At the begining of last summer, Nikolai Weibull posts on zsh-users[1] that he uses autoload to deal with namespaces for his own libraries. Some posts later, Peter Stephenson told me about the ksh-style autoload. I dreamed about object oriented zsh: being able to write something like

use Zoo
use Ldap

# call the ldap constructor
# ( ldap/_new defined in Ldap )
new Ldap srv directory.example.com cn=admin 'giv3m3r00t'

# 
host $( $srv .host )
$srv Search uid=mc 
delete $srv

My dream came true: Zoo is born! Now, i'm really afraid about doing something that cannot be reliable, usable, maintenable, ... . That's why i would appreciate some feedbacks, ideas or advices.

[1] http://www.zsh.org/mla/users//2006/msg00587.html

A. 3 benefits of autoload -Uk (comparing with source)

sourcing files is traditionnaly done with the source command. You have to figure out by yourself where is your library and the fact you already sourced it. 

for example:

if (( ! $+libloaded[ldap] )) {
    source /long/is/the/road/to/my/libs/Ldap
    libloaded[ldap]=1
}

ksh-autoload do it itself. Just be sure that:

* /long/is/the/road/to/my/libs in your fpath (perfect job for your .zshenv)
* /long/is/the/road/to/my/libs/Ldap define Ldap() 

so now, you just have to type: 
autoload -Uk Ldap; Ldap

add this function to your zshenv

use () {
    local lib=$1
    shift
    autoload -Uk $lib
    $lib "$@"
}

you can now write

use Ldap

autoload can also load a file that would not be directly under $fpath So the file /long/is/the/road/to/my/libs/ldif/Entry can be loaded typing

autoload ldif/Entry

So the function name is ... ldif/Entry !!! zsh can do it and it seriously looks like a namespace! Now you can define some functions ldif/entry/Get, ldif/entry/Dump or whatever in this file.

Do you noticed that i use uppercase carrefully? I decided it convention:

* files have a leading upper, directories have lower, so I can have an fpath with the files Ldif and ldif/Entry

* functions begin with upper and i try to have only verbs.

The fact is there is almost nothing to do to have a very beautifull library management: autoload does it for you.

B. about Zoo

The zoo concept is very simple: zsh can execute arrays (if the first element of the array is executable). Examples:

obj=( echo "i'm an object" )
$obj

So the new function is defined like this

new () {
    typeset class=$1 id=$2 self=ZOOBJ$[ZOOCNT++]
    shift 2

    # self is the object itself,
    # it's a global associative array
    # it is exported to be acceded by subshells
    # so
    # echo $( $id .member )
    # is valid 

    typeset -Ag $self

    # you can access to it using eval, (P) ...
    # it's not really readable so I wrote
    # Zoo/Import, Zoo/Echo and Zoo/Set

    eval "$id=( zoo/_object $class $self )"
    export $id $self

    # id is an array that is called as command
    # so the command zoo is called with
    # at least 2 params : the class and the
    # name of the object. 3rd parameter can be
    # the method.
    
    # call the class constructor on the object
    $class:l/_new $self "$@"
}

ldap/_new () {
    shift
    echo construct ldap with "$@"
}

zoo/_object () {
    # not the real! just for demo:
    typeset class=$1 object=$2
    print $object is an associative array
    print storing the values of $class object
}

new ldap boo
$boo

In fact zoo/_object() is a dispatcher that launches functions according to the class name. I joined a complete working example. Just copy all the files in a directory and run zsh example.

C. Conclusion

This is a basic oo implementation. Just supporting constructors, desctructors, properties and methods. I think it can be a base for a lot of features but as i said, i really appreciate to know if i'm in a wrong way. 
source zshenv
use Zoo
use Ldap

# call the ldap constructor
# ( ldap/_new defined in Ldap )
new Ldap srv directory.example.com cn=admin 'giv3m3r00t'

# 
host $( $srv .host )
$srv Search uid=mc 
delete $srv
Ldap () {}

ldap/_new () {
    typeset self=$1
    zoo/Set $self host "$2" binddn "$3" passwd "$4"
}

ldap/_delete () {
    typeset self=$1
    echo i will delete $self 
    unset $self
}

ldap/Search () {
    typeset self=$1  host binddn passwd
    zoo/Import $self host binddn passwd
    shift
    echo ldapsearch -h"$host" -xD"$binddn" -w"$passwd" "$@"
}


Zoo () {}

zoo/_object () {
    typeset class=$1 self=$2 cmd
    (( $# )) &&cmd=$3 ||cmd=Default
    case $cmd[1] {
	(.) zoo/Echo $self $cmd[2,-1] ;;
	(*) $class:l/$cmd $self "$@" ;;
    }
}


zoo/Set () {
    typeset self=$1 k v 
    shift
    for k v {
	eval "${self}[$k]"=${(qqq)v}
    }
}

zoo/Import () {
    typeset self="$1" property
    shift

    for property {
	eval $property='${(q)'${self}\[$property']}'
    }
    
    key="$2" var="$3"
}

zoo/Echo () {
    typeset self="$1" property="$2"
    eval echo \$${self}\[$property]
    # eval echo \$${self}[$property]
}


new () {
    typeset class=$1 id=$2 self=ZOOBJ$[ZOOCNT++]
    shift 2

    # self is the object itself,
    # it's a global associative array
    # it is exported to be acceded by subshells
    # so
    # echo $( $id .member )
    # is valid 

    typeset -Ag $self

    # you can access to it using eval, (P) ...
    # it's not really readable so I wrote
    # Zoo/Import, Zoo/Echo and Zoo/Set
    
    eval "$id=( zoo/_object $class $self )"
    export $id $self

    # id is an array that is called as command
    # so the command zoo is called with
    # at least 2 params : the class and the
    # name of the object. 3rd parameter can be
    # the method.

    
    # call the class constructor on the object
    $class:l/_new $self "$@"
}

delete () {
    typeset class=$2 self=$3
    shift 3
    $class:l/_delete $self 
    unset $self
}

# the content of this file
# will normaly be a part of your

fpath+=$PWD

use () {
    local lib=$1
    shift
    autoload -k $lib
    $lib "$@"
}




Messages sorted by: Reverse Date, Date, Thread, Author