Acme-MUDLike

 view release on metacpan or  search on metacpan

lib/Acme/MUDLike.pm  view on Meta::CPAN

package Acme::MUDLike;

use 5.008000;
use strict;
use warnings;

use Continuity;
use Carp;
use Devel::Pointer;

our $VERSION = '0.04';

# Todo:
# 
# *  what would be *really* cool is doing on the fly image generation to draw an overhead map of the program based on a 
#    graph of which objects reference which other objects and let people go walk around inside of their program
#    and then they could fight methods and use global variables as weapons!
#
# * http://zvtm.sourceforge.net/zgrviewer.html or something similar for showing the user the "map" of
#   nodes/rooms/whatever made of has-a references or something.
# 
# * /goto should put you inside an arbitrary object, /look should list as exits and/or items the object references contained by that object
#   in other words, break away from our rigid API for inventory/room/etc.
# 
# * need a black list black list, so we can re-add ourself to things that get serialized by Acme::State even though we're in %INC
# 
# * need an error log viewabe by all.
# 
# * eval and its output should be sent to the whole room.
# 
# * Better account management.
# 
# * There's code around to parse LPC and convert it to Perl.  It would be neat to offer a full blown 2.4.5
#   lib for people to play around in.
# 
# * Acme::IRCLike would probably be more popular -- bolt an IRC server onto your app.
# 
# * Also, a telnet interface beyond just an HTTP interface would be nice.  Should be easy to do.
# 
# * Let "players" wander between apps.  Offer RPC to support this.
# 
# * Optionally take an existing Continuity instance with path_session set and optionally parameters
#   for the paths to use for chat pull and commands.
#   Not sure how to work this; each path gets its own coroutine, but there is still only one main().
#   Continuity doesn't have a registry of which paths go to which callbacks.
# 
# Done:
# 
# * mark/call commands should have a current object register, so you can do /call thingie whatever /next and then be calling 
#   into the object returned by thingie->whatever
# 
# * /list (like look, but with stringified object references)
# 
# * /mark <n>  ... or... /mark <stringified obj ref>
# 
# * messages still in duplicate when the same player logs in twice; make room's tell_object operate uniquely.
# 
# * messages in triplicate because each player has three routines and is inserted into the floor three times.  oops.
# 
# * build the ajax.chat.js into source. -- okay, test.
# 
# * eval, call
# 
# * inventory's insert() method should set the insertee's environment to itself.  that way, all objects have an environment.
# 
# * Commands need to do $floor->tell_object or $self->tell_object rather than output directly.
# 
# * Put @messages into the room ($floor).  Get the chat action out of the main loop.  Dispatch all
#   actions.  Maybe.
# 

our $password; # Acme::State friendly
our $floor;    # holds all other objects
our $players;  # holds all players; kind of like $floor except in the future, inactive players might get removed from the floor, or there might be multiple rooms

my $continuity;
my $got_message;   # diddled to wake the chat event watchers

$SIG{PIPE} = 'IGNORE';

sub new {
    my $package = shift;
    my %args = @_;

    die "We've already got one" if $continuity;

    $password = delete $args{password} if exists $args{password};
    $password ||= join('', map { $_->[int rand scalar @$_] } (['a'..'z', 'A'..'Z', '0'..'9']) x 8),

    my $staticp = sub { 
        # warn "staticp: url->path: ``@{[ $_[0]->url->path ]}''"; 
        return 0 if $_[0]->url->path =~  m/\.js$/; 
        # warn "staticp: dynamic js handling override not engaged";
        return $_[0]->url->path =~ m/\.(jpg|jpeg|gif|png|css|ico|js)$/ 
    };

    $continuity = $args{continuity} || Continuity->new(
        staticp => sub { $staticp->(@_); },
        callback => sub { login(@_) },
        path_session => 1,
        port => 2000,
        %args,
    );

    print "Admin:\n", $continuity->adapter->daemon->url, '?admin=', $password, '&nick=', (getpwuid $<)[0], "\n";

    $floor ||= Acme::MUDLike::room->new();
    $players ||= Acme::MUDLike::inventory->new();

    bless { }, $package;
}

sub loop { my $self = shift; $continuity->loop(@_); }

sub header {
    qq{
        <html><head>
            <script src="/jquery.js" type="text/javascript"></script>
            <script src="/chat.js" type="text/javascript"></script>
        </head><body>
    }; 
}

sub footer { qq{</body></html>\n}; }

sub login {

    my $request = shift;

    #
    # per-user variables
    #

    my $player;

    # STDERR->print("debug: " . $request->request->url->path . "\n"); # XXX
    # STDERR->print("debug: " . $request->request->as_string . "\n"); # XXX
    $SIG{PIPE} = 'IGNORE'; # XXX not helping at all.  grr.

    #
    # static files
    #

    if($request->request->url->path eq '/chat.js') {
        # warn "handling chat.js XXX: ". $request->request->url->path;
        $request->print(Acme::MUDLike::data->chat_js());
        return;
    } elsif($request->request->url->path eq '/jquery.js') {

lib/Acme/MUDLike.pm  view on Meta::CPAN

# room
#

package Acme::MUDLike::room;
push our @ISA, 'Acme::MUDLike::inventory';

sub tell_object {
    my $self = shift;
    my $message = shift;
    # rather than buffering messages, room objects recurse and distribute the message to everyone and everything in it
    # $self->apply('tell_object', $message);
    my %already_told;
    $self->apply(sub { return if $already_told{$_[0]}++; $_[0]->tell_object($message); }, );
}

#
# players
#

package Acme::MUDLike::players;
push our @ISA, 'Acme::MUDLike::inventory'; # use base 'Acme::MUDLike::inventory';

#
# player
#

package Acme::MUDLike::player;
push our @ISA, 'Acme::MUDLike::object';

sub player { 1 }
sub new {
    my $pack = shift;
    bless {
        inventory => Acme::MUDLike::inventory->new,
        messages => [ ],
        @_,
    }, $pack;
}
sub request :lvalue { $_[0]->{request} }
sub id { $_[0]->{name} eq $_[1] or $_[0] eq $_[1] }
sub name { $_[0]->{name} }
sub password { $_[0]->{password} }
sub x :lvalue { $_[0]->{x} }
sub y :lvalue { $_[0]->{y} }
sub xy { $_[0]->{x}, $_[0]->{y} }
sub get { 0; } # can't be picked up
sub inventory { $_[0]->{inventory} }
sub evalcode :lvalue { $_[0]->{evalcode } }
sub current_item :lvalue { $_[0]->{current_item} }

sub tell_object {
    my $self = shift;
    my $msg = shift;
    push @{$self->{messages}}, $msg;
    shift @{$self->{messages}} if @{$self->{messages}} > 100;
    $got_message = 1; # XXX wish this didn't happen for each player but only once after all players got their message
}

sub get_html_messages {
    my $self = shift;
    return join "<br>\n", map { s{<}{\&lt;}gs; s{\n}{<br>\n}g; $_ } $self->get_messages;
}

sub get_messages {
    my $self = shift;
    my @ret;
    # this is written out long because I keep changing it around
    for my $i (1..20) {
        exists $self->{messages}->[-$i] or last;
        my $msg = $self->{messages}->[-$i];
        push @ret, $msg;
    }
    return reverse @ret;
}

sub header () { Acme::MUDLike::header() }
sub footer () { Acme::MUDLike::footer() }

sub command {

    my $self = shift;
    my $request = shift;

    # this is called by login() immediately after verifying credientials

    if($request->request->url->path =~ m/pushstream/) {
        # warn "pushstream path_session handling XXX";
        my $w = Coro::Event->var(var => \$got_message, poll => 'w');
        while(1) {
            $w->next;
            # warn "got_message diddled XXX";
            # on submitting the form without a JS background post, the poll HTTP connection gets broken
            $SIG{PIPE} = 'IGNORE';
            $request->print( join "<br>\n", map { s{<}{\&lt;}gs; s{\n}{<br>\n}g; $_ } $self->get_messages );
            $request->next;
        }
    }

    if($request->request->url->path =~ m/sendmessage/) {
        while(1) {
            # warn "sendmessage path_session handling XXX";
            my $msg = $request->param('message');  
            $self->parse_command($msg);
            # $request->print("Got message.\n");
            $request->print($self->get_html_messages());
            $request->next;
        }
    }

    #
    # players get three execution contexts:
    # * one for AJAX message posts without header/footer in the reply
    # * one for COMET message pulls
    # * the main HTML one below (which might only run once); arbitrarily selected as being the main one cuz its longest
    #

    $floor->insert($self);

    while(1) {

        $request->print(header);
    
        #
        # chat/commands
        #
 
        if($request->param('action') and $request->param('action') eq 'chat') {
            # chat messages first so they appear in the log below
            # there's only one action defined right now -- chat.  everything else hangs off of that. 
            my $msg = $request->param('message');  
            $self->parse_command($msg);
        };

        do {

            $request->print(qq{
                <b>Chat/Command:</b>
                <form method="post" id="f" action="/">
                    <input type="hidden" name="action" value="chat">
                    <input type="hidden" id="nick" name="nick" value="@{[ $self->name ]}"> 
                    <input type="hidden" id="admin" name="admin" value="$password">
                    <input type="text" id="message" name="message" size="50">
                    <!-- <input type="submit" name="sendbutton" value="Send" id="sendbutton"> -->
                    <input type="submit" name="sendbutton" value="Send" id="sendbutton">
                    <span id="status"></span>
                </form>
                <br>
                <div id="log">@{[ $self->get_html_messages ]}</div>
            });
        };

    } continue {
        $request->print(footer);
        $request->next();

lib/Acme/MUDLike.pm  view on Meta::CPAN

    } elsif($msg) {
        $floor->tell_object($self->name . ': ' . $msg); # XXX should be $self->environment->tell_object
        # $request->print("Got it!\n");
    }
}

sub item_by_arg {
    my $self = shift;
    my $item = shift;
    my $ob;
    return $self->current_item if $item eq 'current';
    if($item =~ m/^\d+$/) {
        my @stuff = $self->environment->contents;
        $ob = $stuff[$item] if $item < @stuff;
    }
    $ob or $ob = $self->inventory->named($item);      # thing in our inventory with that name
    $ob or $ob = $self->environment->named($item);     # thing in our environment with that name
    $ob or $ob = $item if exists &{$item.'::new'}; # raw package name
    $ob or do {
      # Foo::Bar=HASH(0x812ea54)
      my $hex;
      ($hex) = $item =~ m{^[a-z][a-z_:]+\((0x[0-9a-z]+)\)}i;
      $hex or ($hex) = $item =~ m{^0x([0-9a-z]+)}i;
      if($hex) {
          $ob = Devel::Pointer::deref(hex($hex));
      }
    };
    return $ob;
}

# actions

sub _call {
    my $self = shift;
    # XXX call a method an in object
    # XXX call sword name 
    my $item = shift;
    my $func = shift;
    my @args = @_; # XXX for each arg, go through the item finding code below, except keep identify if not found
    my $ob = $self->item_by_arg($item) or do {
        $self->tell_object("call: no item by that name/number/package name here");
        return;
    };
    for my $i (0..$#args) {
        my $x = $self->item_by_arg($args[$i]);
        $args[$i] = $x if $x;
    }
    $ob->can($func) or do {
        $self->tell_object("call:  item ``$item'' has no ``$func'' method");
        return;
    };
    $self->tell_object(join '', "Call: ", eval { $ob->can($func)->($ob, @args); } || "Error: ``$@''.");
    1;
}

sub _list {
    my $self = shift;
    my $i = 0;
    $self->tell_object(join '', 
        "Here, you see:\n", 
        map qq{$_\n}, 
        map { $i . ': ' . $_ }
        $self->environment->contents, $self->inventory->contents,
    ); 
}

sub _mark {
    my $self = shift;
    my $item = shift;
    my $ob = $self->item_by_arg($item) or do {
        $self->tell_object("mark: no item by that name/number/package name here");
        return;
    };
    $self->current_item = $ob;
}

sub _eval {
    my $self = shift;
    my $cmd = join ' ', @_;
    no warnings 'redefine';
    # *print = sub { $self->tell_object(@_); };  # this doesn't work reliablely due to possible context changes but worth a shot
    # *say = sub { $self->tell_object("@_\n"); }; # ack... doesn't work at all.
    select $self->request->{request}->{conn};  # would rather it went into their message buffer but comprimising for now
    my $res = eval($cmd) || "Error: ``$@''.";
    $self->tell_object("eval:\n$res");
}

sub _who {
    my $self = shift;
    $self->_look(@_); # for now
}

sub _look {
    my $self = shift;
    my @args = @_;
    # $self->tell_object(join '', "Here, you see:\n", map qq{$_\n}, map $_->name, $floor->contents); 
    $self->tell_object(join '', 
        "Here, you see:\n", 
        map qq{$_\n}, 
        map { $_->can('name') ? $_->name : ref($_) }
        $self->environment->contents
    ); 
}

sub _inv {
    my $self = shift;
    $self->_inventory(@_);
}

sub _i {
    my $self = shift;
    $self->_inventory(@_);
}

sub _inventory {
    my $self = shift;
    my @args = @_;
    $self->tell_object(join '', 
        "You are holding:\n", 
        map qq{$_\n}, 
        map { $_->can('name') ? $_->name : ''.$_ } 
        $self->inventory->contents
    ); 
}

sub _take {
    my $self = shift;
    my @args = @_;
    if(@args == 1) {
        # take thingie
        my $item = $floor->delete($args[0]) or do { $self->tell_object("No ``$args[0]'' here."); return; };
        $self->inventory->insert($item);
        $self->tell_object(qq{Taken.});
    } elsif(@args == 2) {
        $self->tell_object("I don't understand ``$args[0] $args[1]''.");
    } elsif(@args == 3) {
        if($args[1] ne 'from') {
            $self->tell_object("I don't understand ``$args[1] $args[2]''.");
            return;
        }
        my $container = $floor->named($args[2]) or do { $self->tell_object("No ``$args[2]'' here."); return; };
        my $item = $container->inventory->delete($args[0]) or do { $self->tell_object("No ``$args[0]'' here."); return; };
        $self->inventory->insert($item);
        $self->tell_object(qq{Taken.});
    } elsif(! @args or @args > 3) {
        $self->tell_object("Take what?");
    }
}

sub _drop {
    my $self = shift;
    my @args = @_;
    if(@args != 1) {
        $self->tell_object("Drop what?");
        return;
    }
    my $item = $self->delete($args[0]) or do { $self->tell_object("You have no ``$args[0]''."); return; }; 
    $floor->insert($item);
    $self->tell_object(qq{Dropped.});
}

sub _give {
    my $self = shift;
    my @args = @_;
    if(@args != 3 or $args[1] ne 'to') {
        $self->tell_object(qq{Give what to whom?});
        return;
    }
    my $item = $self->inventory->named($args[0]) or do { $self->tell_object("You have no ``$args[0]''."); return; };
    my $container = $floor->named($args[2]) or do { $self->tell_object("There is no ``$args[2]'' here."); return; };
    $self->inventory->delete($args[0]);
    $container->inventory->insert($item);
    $self->tell_object("Ok.");
}

sub _clone {
    my $self = shift;
    my $ob = shift;
    if(! $ob) {
        $self->tell_object(qq{Clone what?});
        return;
    }
    my $item = eval { $ob->new() };
    if(! $item ) {
        $self->tell_object("Failed to load object: ``$@''.");
        return;
    }
    # XXX force an inheritance of object onto it if it doesn't already have one?
    $self->inventory->insert($item);
    $self->tell_object("Ok.");
}

sub _dest {
    my $self = shift;
    my @args = @_;
    if(@args != 1) {
        $self->tell_object(qq{Dest what?});
        return;
    }
    $self->inventory->delete($args[0]) or do { $self->tell_object("You don't have a ``$args[0]''."); return; };
    $self->tell_object("Dest: Ok.");
}


=head1 NAME

Acme::MUDLike - Hang out inside of your application

=head1 SYNOPSIS

    use Acme::MUDLike; 
    my $server = Acme::MUDLike->new;

    # ... your code here

    $server->loop;  # or call the Event or AnyEvent event loop

Connect to the URL provided and cut and paste into the text box:

    /eval package sword; our @ISA = qw/Acme::MUDLike::object/; sub new { my $pack = shift; $pack->SUPER::new(name=>"sword", @_); }
    /clone sword
    /i
    /call sword name
    wee, fun!  oh, hai everyone!
    /eval no strict "refs"; join '', map "$_\n", keys %{"main::"};
    /call Acme::MUDLike::player=HASH(0x8985e10) name

=head1 DESCRIPTION

Multi user chat, general purpose object tracer, eval, and give/drop/take/clone/dest/look.

Adds a social element to software development and develop it from within.
Chat within the application, eval code inside of it (sort of like a simple Read-Eval-Parse Loop).
Call methods in objects from the command line.
Create instances of objects, give them to people, drop them on the floor.

The idea is take the simple command line interface and extend it with more commands,
and to create tools and helper objects that inspect and modify the running program from within.

It fires up a Continuity/HTTP::Daemon based Web server on port 2000 and prints out a login
URL on the command line.
Paste the URL into your browser.
Chat with other users logged into the app.
Messages beginning with a slash, C</>, are interpreted as commands:

=over 2

=item C<< /look >> 

See who else and what else is in the room.

=item C<< /mark >>

  /mark 1

  /mark torch

  /mark foo::bar

  /mark 0x812ea54

Select an object as the logical current object by name, package name, number (as a position in your
inventory list, which is useful for when you've cloned an object that does not define an C<id> or C<name> function),
or by memory address (as in C<< Foo::Bar=HASH(0x812ea54) >>).

=item C<< /call >> 

Call a function in an object; eg, if you're holding a C<toaster>, you can write:

  /call toaster add_bread 1

The special name "current" refers to the current object, as marked with mark.

=item C<< /eval >>

Executes Perl.
C<< $self >> is your own player object.
C<< $self->inventory >> is an C<< Acme::MUDLike::inventory >> object with C<delete>, C<insert>, C<named>,
C<apply>, and C<contents> methods.
C<< $self->environment >> is also an C<< Acme::MUDLike::inventory >> object holding you and other players 
and objects in the room.
The environment and players in it all have C<tell_object> methods that takes a string to add to their
message buffer.
Calling C<tell_object> in the environment sends the message to all players.
Objects define various other methods.

lib/Acme/MUDLike.pm  view on Meta::CPAN


	text: function(e) {
		if ( typeof e == "string" )
			return this.empty().append( document.createTextNode( e ) );

		var t = "";
		jQuery.each( e || this, function(){
			jQuery.each( this.childNodes, function(){
				if ( this.nodeType != 8 )
					t += this.nodeType != 1 ?
						this.nodeValue : jQuery.fn.text([ this ]);
			});
		});
		return t;
	},

	wrap: function() {
		// The elements to wrap the target around
		var a = jQuery.clean(arguments);

		// Wrap each of the matched elements individually
		return this.each(function(){
			// Clone the structure that we're using to wrap
			var b = a[0].cloneNode(true);

			// Insert it before the element to be wrapped
			this.parentNode.insertBefore( b, this );

			// Find the deepest point in the wrap structure
			while ( b.firstChild )
				b = b.firstChild;

			// Move the matched element to within the wrap structure
			b.appendChild( this );
		});
	},
	append: function() {
		return this.domManip(arguments, true, 1, function(a){
			this.appendChild( a );
		});
	},
	prepend: function() {
		return this.domManip(arguments, true, -1, function(a){
			this.insertBefore( a, this.firstChild );
		});
	},
	before: function() {
		return this.domManip(arguments, false, 1, function(a){
			this.parentNode.insertBefore( a, this );
		});
	},
	after: function() {
		return this.domManip(arguments, false, -1, function(a){
			this.parentNode.insertBefore( a, this.nextSibling );
		});
	},
	end: function() {
		return this.prevObject || jQuery([]);
	},
	find: function(t) {
		return this.pushStack( jQuery.map( this, function(a){
			return jQuery.find(t,a);
		}), t );
	},
	clone: function(deep) {
		return this.pushStack( jQuery.map( this, function(a){
			var a = a.cloneNode( deep != undefined ? deep : true );
			a.$events = null; // drop $events expando to avoid firing incorrect events
			return a;
		}) );
	},

	filter: function(t) {
		return this.pushStack(
			jQuery.isFunction( t ) &&
			jQuery.grep(this, function(el, index){
				return t.apply(el, [index])
			}) ||

			jQuery.multiFilter(t,this) );
	},

	not: function(t) {
		return this.pushStack(
			t.constructor == String &&
			jQuery.multiFilter(t, this, true) ||

			jQuery.grep(this, function(a) {
				return ( t.constructor == Array || t.jquery )
					? jQuery.inArray( a, t ) < 0
					: a != t;
			})
		);
	},

	add: function(t) {
		return this.pushStack( jQuery.merge(
			this.get(),
			t.constructor == String ?
				jQuery(t).get() :
				t.length != undefined && (!t.nodeName || t.nodeName == "FORM") ?
					t : [t] )
		);
	},
	is: function(expr) {
		return expr ? jQuery.filter(expr,this).r.length > 0 : false;
	},

	val: function( val ) {
		return val == undefined ?
			( this.length ? this[0].value : null ) :
			this.attr( "value", val );
	},

	html: function( val ) {
		return val == undefined ?
			( this.length ? this[0].innerHTML : null ) :
			this.empty().append( val );
	},
	domManip: function(args, table, dir, fn){
		var clone = this.length > 1; 
		var a = jQuery.clean(args);
		if ( dir < 0 )
			a.reverse();

		return this.each(function(){

lib/Acme/MUDLike.pm  view on Meta::CPAN

			return elem.getAttribute( name );

		// elem is actually elem.style ... set the style
		} else {
			name = name.replace(/-([a-z])/ig,function(z,b){return b.toUpperCase();});
			if ( value != undefined ) elem[name] = value;
			return elem[name];
		}
	},
	trim: function(t){
		return t.replace(/^\s+|\s+$/g, "");
	},

	makeArray: function( a ) {
		var r = [];

		if ( a.constructor != Array )
			for ( var i = 0, al = a.length; i < al; i++ )
				r.push( a[i] );
		else
			r = a.slice( 0 );

		return r;
	},

	inArray: function( b, a ) {
		for ( var i = 0, al = a.length; i < al; i++ )
			if ( a[i] == b )
				return i;
		return -1;
	},
	merge: function(first, second) {
		var r = [].slice.call( first, 0 );

		// Now check for duplicates between the two arrays
		// and only add the unique items
		for ( var i = 0, sl = second.length; i < sl; i++ )
			// Check for duplicates
			if ( jQuery.inArray( second[i], r ) == -1 )
				// The item is unique, add it
				first.push( second[i] );

		return first;
	},
	grep: function(elems, fn, inv) {
		// If a string is passed in for the function, make a function
		// for it (a handy shortcut)
		if ( typeof fn == "string" )
			fn = new Function("a","i","return " + fn);

		var result = [];

		// Go through the array, only saving the items
		// that pass the validator function
		for ( var i = 0, el = elems.length; i < el; i++ )
			if ( !inv && fn(elems[i],i) || inv && !fn(elems[i],i) )
				result.push( elems[i] );

		return result;
	},
	map: function(elems, fn) {
		// If a string is passed in for the function, make a function
		// for it (a handy shortcut)
		if ( typeof fn == "string" )
			fn = new Function("a","return " + fn);

		var result = [], r = [];

		// Go through the array, translating each of the items to their
		// new value (or values).
		for ( var i = 0, el = elems.length; i < el; i++ ) {
			var val = fn(elems[i],i);

			if ( val !== null && val != undefined ) {
				if ( val.constructor != Array ) val = [val];
				result = result.concat( val );
			}
		}

		var r = result.length ? [ result[0] ] : [];

		check: for ( var i = 1, rl = result.length; i < rl; i++ ) {
			for ( var j = 0; j < i; j++ )
				if ( result[i] == r[j] )
					continue check;

			r.push( result[i] );
		}

		return r;
	}
});
 
/*
 * Whether the W3C compliant box model is being used.
 *
 * @property
 * @name $.boxModel
 * @type Boolean
 * @cat JavaScript
 */
new function() {
	var b = navigator.userAgent.toLowerCase();

	// Figure out what browser is being used
	jQuery.browser = {
		safari: /webkit/.test(b),
		opera: /opera/.test(b),
		msie: /msie/.test(b) && !/opera/.test(b),
		mozilla: /mozilla/.test(b) && !/(compatible|webkit)/.test(b)
	};

	// Check to see if the W3C box model is being used
	jQuery.boxModel = !jQuery.browser.msie || document.compatMode == "CSS1Compat";
};

jQuery.each({
	parent: "a.parentNode",
	parents: "jQuery.parents(a)",
	next: "jQuery.nth(a,2,'nextSibling')",
	prev: "jQuery.nth(a,2,'previousSibling')",
	siblings: "jQuery.sibling(a.parentNode.firstChild,a)",
	children: "jQuery.sibling(a.firstChild)"
}, function(i,n){
	jQuery.fn[ i ] = function(a) {
		var ret = jQuery.map(this,n);
		if ( a && typeof a == "string" )
			ret = jQuery.multiFilter(a,ret);
		return this.pushStack( ret );
	};
});

jQuery.each({
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after"
}, function(i,n){
	jQuery.fn[ i ] = function(){
		var a = arguments;
		return this.each(function(){
			for ( var j = 0, al = a.length; j < al; j++ )
				jQuery(a[j])[n]( this );
		});
	};
});

jQuery.each( {
	removeAttr: function( key ) {
		jQuery.attr( this, key, "" );
		this.removeAttribute( key );
	},
	addClass: function(c){
		jQuery.className.add(this,c);
	},
	removeClass: function(c){
		jQuery.className.remove(this,c);
	},
	toggleClass: function( c ){
		jQuery.className[ jQuery.className.has(this,c) ? "remove" : "add" ](this, c);
	},
	remove: function(a){
		if ( !a || jQuery.filter( a, [this] ).r.length )
			this.parentNode.removeChild( this );
	},
	empty: function() {
		while ( this.firstChild )
			this.removeChild( this.firstChild );
	}
}, function(i,n){
	jQuery.fn[ i ] = function() {
		return this.each( n, arguments );
	};
});

jQuery.each( [ "eq", "lt", "gt", "contains" ], function(i,n){
	jQuery.fn[ n ] = function(num,fn) {
		return this.filter( ":" + n + "(" + num + ")", fn );
	};
});

jQuery.each( [ "height", "width" ], function(i,n){
	jQuery.fn[ n ] = function(h) {
		return h == undefined ?
			( this.length ? jQuery.css( this[0], n ) : null ) :
			this.css( n, h.constructor == String ? h : h + "px" );

lib/Acme/MUDLike.pm  view on Meta::CPAN

		if ( context && !context.nodeType )
			context = null;

		// Set the correct context (if none is provided)
		context = context || document;

		// Handle the common XPath // expression
		if ( !t.indexOf("//") ) {
			context = context.documentElement;
			t = t.substr(2,t.length);

		// And the / root expression
		} else if ( !t.indexOf("/") ) {
			context = context.documentElement;
			t = t.substr(1,t.length);
			if ( t.indexOf("/") >= 1 )
				t = t.substr(t.indexOf("/"),t.length);
		}

		// Initialize the search
		var ret = [context], done = [], last = null;

		// Continue while a selector expression exists, and while
		// we're no longer looping upon ourselves
		while ( t && last != t ) {
			var r = [];
			last = t;

			t = jQuery.trim(t).replace( /^\/\//i, "" );

			var foundToken = false;

			// An attempt at speeding up child selectors that
			// point to a specific element tag
			var re = /^[\/>]\s*([a-z0-9*-]+)/i;
			var m = re.exec(t);

			if ( m ) {
				// Perform our own iteration and filter
				jQuery.each( ret, function(){
					for ( var c = this.firstChild; c; c = c.nextSibling )
						if ( c.nodeType == 1 && ( jQuery.nodeName(c, m[1]) || m[1] == "*" ) )
							r.push( c );
				});

				ret = r;
				t = t.replace( re, "" );
				if ( t.indexOf(" ") == 0 ) continue;
				foundToken = true;
			} else {
				// Look for pre-defined expression tokens
				for ( var i = 0; i < jQuery.token.length; i += 2 ) {
					// Attempt to match each, individual, token in
					// the specified order
					var re = jQuery.token[i];
					var m = re.exec(t);

					// If the token match was found
					if ( m ) {
						// Map it against the token's handler
						r = ret = jQuery.map( ret, jQuery.isFunction( jQuery.token[i+1] ) ?
							jQuery.token[i+1] :
							function(a){ return eval(jQuery.token[i+1]); });

						// And remove the token
						t = jQuery.trim( t.replace( re, "" ) );
						foundToken = true;
						break;
					}
				}
			}

			// See if there's still an expression, and that we haven't already
			// matched a token
			if ( t && !foundToken ) {
				// Handle multiple expressions
				if ( !t.indexOf(",") ) {
					// Clean the result set
					if ( ret[0] == context ) ret.shift();

					// Merge the result sets
					jQuery.merge( done, ret );

					// Reset the context
					r = ret = [context];

					// Touch up the selector string
					t = " " + t.substr(1,t.length);

				} else {
					// Optomize for the case nodeName#idName
					var re2 = /^([a-z0-9_-]+)(#)([a-z0-9\\*_-]*)/i;
					var m = re2.exec(t);
					
					// Re-organize the results, so that they're consistent
					if ( m ) {
					   m = [ 0, m[2], m[3], m[1] ];

					} else {
						// Otherwise, do a traditional filter check for
						// ID, class, and element selectors
						re2 = /^([#.]?)([a-z0-9\\*_-]*)/i;
						m = re2.exec(t);
					}

					// Try to do a global search by ID, where we can
					if ( m[1] == "#" && ret[ret.length-1].getElementById ) {
						// Optimization for HTML document case
						var oid = ret[ret.length-1].getElementById(m[2]);
						
						// Do a quick check for the existence of the actual ID attribute
						// to avoid selecting by the name attribute in IE
						if ( jQuery.browser.msie && oid && oid.id != m[2] )
							oid = jQuery('[@id="'+m[2]+'"]', ret[ret.length-1])[0];

						// Do a quick check for node name (where applicable) so
						// that div#foo searches will be really fast
						ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];

					} else {
						// Pre-compile a regular expression to handle class searches



( run in 0.755 second using v1.01-cache-2.11-cpan-39bf76dae61 )