Pastie TextMate integration at your finger tips

written by sam on March 13th, 2008 @ 01:39 PM

Grab the bundle and enjoy, Pastis is included so nothing else is needed.

  • pastes selected text or entire document
  • syntax automatically deduced
  • opens instantly in your favorite browser

advised methods for Javascript with Prototype

written by sam on December 7th, 2007 @ 08:28 AM

an AOP implementation

Well, it's been a long time since including AOP is planned for Prototype (original ticket by Tobie Langel).

This is a working candidate with slight differences and improvements. Let's start with some code, as usual :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PeriodicalExecuter.addMethods(Advisable);

PeriodicalExecuter.prototype.addAroundAdvice('execute', function(proceed) {
  // assuming the existence of some benchmark function
  var duration = benchmark(proceed);
  console.log('Periodical execution took '+duration+'s');
});
// For now every single instance of PeriodicalExecuter will log the execution duration.

var greeter = Object.extend({
  congrat: function() {
    return "Congratulations";
  }
}, Advisable);

greeter.addAfterAdvice('congrat', function(returnValue) {
  return returnValue + " for Prototype 1.6, you guys made a great work."
});

greeter.congrat();
// "Congratulations for Prototype 1.6, you guys made a great work."

Differences and improvements

Among everything, Aspect is not a mixin anymore but a class that represents aspect of a given object and holds its originals methods. Advisable is the mixin containing AOP-methods that you can extend an instance with or mix into a class. What is the advantage ? well, your object won't be "polluted" with private attributes or methods anymore, but only with a single _aspect private attribute referring to the corresponding Aspect instance, created on demand.

Another feature that motived me in first place, is that you can now remove a given advice (well, only if you have a reference to it, like event handlers) and still have your method doing fine with other advices. The resulting method is computed only when advices are added or removed.

Plays nicely with prototypes and instances

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var Example = Class.create(Advisable, {
  example: function() {
    console.log('in original method');
  }
});

var ex = new Example();
ex.addAroundAdvice('example', function(proceed) {
  console.log('around, before (instance advice)');
  proceed();
  console.log('around, after (instance advice)');
});

Example.prototype.addBeforeAdvice('example', function() {
  console.log('before (prototype advice)');
});

ex.example();

// Guess what's in console ?

// around, before (instance advice)
// before (prototype advice)
// in original method
// around, after (instance advice)

Some syntaxic sugar

This prototype word is kind of annoying sometimes, and there are several cases when you just want to invoke a prototype method on directly on the class holding it.

A simple way to do it is to bind all method in Advisable to Example.prototype and then store it in our class Example.

1
2
3
4
5
6
7
8
9
Object.keys(Advisable).each(function(methodName) {
  Example[methodName] = Advisable[methodName].bind(Example.prototype);
});

// now
Example.addBeforeAdvice('example', advice);
// is equivalent to
Example.prototype.addBeforeAdvice('example', advice);
// but maybe sexier :p

This is a pattern we are using in Prototype UI in our class.js file containing extensions to Class.Methods

Download

You can check out from the svn repository to get source and unit tests.

svn co http://svn.gotfresh.info/projects/advisable/

un pastis oui, un pastiche non !

written by sam on September 12th, 2007 @ 11:13 PM

Pastis is a simple but complete Ruby interface to Pastie. It lets you create, search or list pastes. A standalone script helps integration with the shell.

gem install pastis

Use cases

From shell

  • open a new file in $EDITOR and pastes it on exit
    pastis
  • edit something.js and paste it (ruby is the default language)
    pastis -e something.js -l javascript
  • make a private paste with STDIN
    cat something.rb | pastis -p

From Ruby

1
2
3
4
5
6
7
8

  paste = Pastis.paste a_string, :language => :text, :private => true
  puts paste.url, paste.body
  
  results = Pastis.search('mixin')
  puts results.first.body
  
  puts results.next_page.first.preview unless results.last_page?

From TextMate

Open the bundle editor and add this command in Source bundle (Input: Selected Text or Document, Output: discard). It will paste selected text or entire document with correct syntax, and open it in a browser:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#!/usr/bin/env ruby
require 'rubygems'
require 'pastis'

input = ENV['TM_SELECTED_TEXT'] || File.read(ENV['TM_FILEPATH'])

languages = {
  /source\.ruby/ => :ruby,
  /text\.html\.basic/ => :html,
  /text\.html\.ruby/ => :rhtml,
  /source\.js/ => :javascript,
  /source\.(c|c\+\+)/ => :c,
  /source\.sql/ => :sql,
  /source\.diff/ => :diff
}

language = languages[languages.keys.find { |pattern| ENV['TM_SCOPE'] =~ pattern }] || :plaintext

paste = Pastis.paste(input, :language => language)

`open #{paste.url}`

For further information, read the documentation.

Have fun !

Prototype and Options

written by sam on August 30th, 2007 @ 07:15 AM

Tired of seeing Object.extend and this.options everywhere in your Prototype code ? Try this Options mixin !

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Widget = Class.create({
  defaultOptions: {
    className: 'widget',
    theme: 'BeOS'
  },
  
  initialize: function(element, options) {
    this.setOptions(options);
    ...
  }
});

// For 1.6
Class.mixin(Widget, Options);

// For 1.5
Object.extend(Widget.prototype, Options);

some fun with ruby lambdas

written by sam on August 26th, 2007 @ 03:44 PM

introducing SourceProc

In Ruby, some objects cannot be dumped with Marshal, including procedure or methods, bindings, continuations, IO instances or singleton objects.

I personnaly needed to serialize some procedures once, so I decided to create a class of procedures that know their source code and can be marshalled.

Here is SourceProc, read the documentation for more information.

YAGNI, just for fun ;)

a simple example

1
2
3
4
5
6
7
8
9
10
11
12
13
14

  add = slambda '|x, y| x + y'

  add.call(10, Math::PI)
  # => 13.1415926535898

  add.source
  # => '|x, y| x + y'
  
  add.to_s      
  # => 'slambda("|x, y| x + y")'

  Marshal.load(Marshal.dump(add)).call(2, 3)
  # => 5

inheritance and prototype.js

written by sam on August 9th, 2007 @ 01:17 PM

I'm reviewing the content of this article to reflect the new implementation I made (slight differences, more dynamic) for Prototype Window Class we are curently rewriting with Sébastien Gruhier. Stay tuned !

Good news !

It seems that inheritance finally went in Prototype trunk.

As I said in my last article, I was myself working on some implementation of inheritance that could be integrated to Prototype so I was really happy to find a lot of people discussing about inheritance on the Prototype core mailing list.

I love the AspectOrientedProgramming-like way of calling the parent method that is, if a method want to call its parent one (the one defined in the superclass' prototype), it simply has to receive it as first argument called $super. So I decided to mix all good ideas of each implementation. Here is the listing of some propositions and the source code (you can download a patch for the revision 7299). Let's give your opinion/critics !

Propositions :

Base class

There is one base class called Base, every class will be a subclass of Base.
It defines a method extend in its prototype. This method simply extend the current object with a given object, allowing to define methods which can call their parent if they include $super in their signature.

Okay so basically, object.extend(properties) is the Object.extend(object, properties) on steroïds.

Classes

Classes have a property constructor set to Class

Classes have methods coming from Class.prototype but are not dynamically linked to this prototype. These are just the usefull methods we want all classes to understand.
Classes methods are not shared with subclasses.

Class methods are :

  • Klass.extend(object) : which extends the current class with the given object (like Object.extend(Klass, object))
  • Klass.include(object) : which extends Klass prototype with the given object.
  • Klass.classEval(declaration) : see classEval

Class creation

Class.create or new Class can be called 3 different ways :
  • with no argument to create a class like within Prototype.
  • with one argument which can be either a class to inherit from (if it's a function) or a declaration body (if it's an object).
  • with two arguments : superclass, declaration body
If no superclass is given, Base is taken. The declaration body will be an argument to the classEval class method

classEval

Klass.classEval(declaration) : declaration is an hash with special properties :
  • self is an hash containing class methods and attributes
  • include is a module or an array of modules to include with Klass.include
Using classEval is like reopening the class, it could be named open.

Source code

The source code has been changed since, you can view the last version.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

  var Class = Object.extend(function(superclass, body) {
    if (typeof superclass != 'function')
      body = superclass, superclass = Base;

    var cls = function() {
      if (arguments[0] !== Class._extending_ && this.initialize)
        this.initialize.apply(this, arguments);
    }
    // Getting properties from Class.prototype
    Object.extend(cls, this);
    Object.extend(cls, {
      superclass: superclass,
      prototype: new superclass(Class._extending_),
      subclasses: []
    });

    cls.constructor = Class;
    cls.prototype.constructor = cls;

    if (body) cls.classEval(body);

    superclass.subclasses.push(cls);
    if (superclass.inherited) superclass.inherited(cls);
    if (cls.afterCreation) cls.afterCreation();

    return cls;
  },
  {
    _extending_: new Object(),

    // backward compatibility
    create: function(superclass, body) {
      return new this(superclass, body);
    }
  });

  Class.prototype = {  
    extend: function(object) {
      Object.extend(this, object);
    },  

    include: function() {
      this.prototype.extend(mixin);
      if (Object.isFunction(mixin.included))
        mixin.included(this);
    },

    classEval: function(body) {
      this.extend(Object.erase(body, 'self') || {});
      if (typeof body.include == 'object')
        [ Object.erase(body, 'include') ].flatten().each(this.include.bind(this));
      this.prototype.extend(body);
    }
  };

  var Base = Object.extend(function() {}, {
    subclasses: [],
    constructor: Class
  });

  Base.prototype = {
    extend: function(object) {
      var ancestor = this.constructor.superclass.prototype;

      for (var property in object) {
        if (Object.isFunction(object[property]) && 
            Object.isFunction(ancestor[property]) &&
            object[property].toString().include('$super')) {

          var method = object[property];

          this[property] = Object.extend(ancestor[property].wrap(method), {
            valueOf: function() { return method; },
            toString: function() { return method.toString(); }
          });

        }
        else
          this[property] = object[property];
      }
    }
  }

a simple URI class

written by sam on May 17th, 2007 @ 11:00 PM

Updated on 2007-08-27 to work with Prototype 1.6 release candidate

what ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

var uri = $U("ftp://billy:bobby@ftp.somewhere.com/path/to/something");
uri.scheme
// => "ftp"
uri.user       
// => "billy"
uri.host     
// => "ftp.somewhere.com"
uri.path      
// => "/path/to/something"
uri == $U(uri) 
// => true
uri.user = 'robert';
uri.path = '/other/place';
uri.scheme = 'http';
uri.toString();
// => "http://robert:bobby@ftp.somewhere.com/other/place"

source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/*
Copyright (c) 2007 Samuel Lebeau

this piece of software is largely inspired by
 http://download.dojotoolkit.org/release-0.4.2/dojo-0.4.2-ajax/src/uri/Uri.js

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the Software), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

var URI = Class.create({
  initialize: function(uri) {
    var r = uri.match(this.constructor.uriPattern);
    if (!r) throw this.constructor._invalidURIError(uri);
    this._uri = uri;
    this.scheme = r[2] || (r[1] ? '' : null);
    this.authority = r[4] || (r[3] ? '' : null);
    this.path = r[5];
    this.query = r[7] || (r[6] ? '' : null);
    this.fragment = r[9] || (r[8] ? '' : null);
    if (this.authority) { 
      r = this.authority.match(this.constructor.authorityPattern);
      this.user = r[3] || null;
      this.password = r[4] || null;
      this.host = r[5];
      this.port = parseInt(r[7]) || null;
    }
  },

  toString: function() {
    return (this.scheme ? this.scheme + '://' : '') +
           (this.user ? this.user + (this.password ? ':' + this.password : '') + '@' : ''  ) +
           (this.host ? this.host + (this.port ? ':' + this.port : '') : '') +
           this.path +
           (this.query ? '?' + this.query : '') +
           (this.fragment ? '#' + this.fragment : '');
  }
});

Object.extend(URI, {
  parse: function(uri) {
    return new this(uri);
  },

  from: function(object) {
    return (object.constructor == URI) ? object : URI.parse(object.toString());
  },

  uriPattern: new RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$"),

  authorityPattern: new RegExp("^(((?:([^:]+):)?([^@]+))@)?([^:]*)(:([0-9]+))?$"),

  _invalidURIError: function(uri) {
    this.name = 'InvalidURIError';
    this.message = 'Invalid URI : ' + uri + '';
  }
});

var $U = URI.from;

yet another javascript inheritance implementation for prototype.js

written by sam on April 12th, 2007 @ 07:00 AM

introduction

As I was writing a complex web application for my school, I feel like I was terribly in need of a nice and useful inheritance mechanism for my javascript code.

I went on the Ruby On Rails Trac and started to look after some interesting patch.

What I found was the famous ticket 4060 submitted by Ben Newman and a link to the paper he wrote about this.

This was really interesting but unfortunately, the source code was too big to be integrated in Prototype. Then I found Base.js on Dean Edward's blog which I liked a lot, but wasn't really the Prototype way (especially the extend semantic).

Another interesting candidate was inheritance.js posted at twologic but I realized it wasn't using a real prototype inheritance (note the new version does, and has inspired Prototype 1.6) but modifying functions (if you decide to redefine a method at runtime, it won't be able to call the parent one).

So I decided to look around and do something by myself with the minimum code possible and looking like Ruby syntax.

usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

var Person = Class.create({
  
  // class declaration
  self: {
    // class method
    find: function(name) {
      return this.population.find(function(p) {
        return p.name == name;
      });
    },

    // class attribute
    population: []
  }

  // instance declaration

  // mixins
  include: [ Comparable, Loggable ],  // imaginary mixins

  initialize: function(name) {
    this.name = name;
    // accessing class from instance
    this.constructor.population.push(this);
  },

  introduce: function() {
    return My name is  + this.name;
  }

});

// We extend Person class
var Employee = Class.create(Person, {
  initialize: function(name, salary) {
    // call the parent initialize method
    this.callSuper('initialize', name);
    this.salary = salary;
  },
 
  introduce: function() {
    return this.callSuper('introduce') +  and I earn  + this.salary;
  }
});

var jack = new Person('Jack');
var billy = new Employee('Billy', 2000);

jack.introduce();
// -> My name is Jack

billy.introduce();
// -> My name is Billy and I earn 2000

Person.find('Jack').name;
// -> 'Jack'

Person.find('Billy');
// -> null

Employee.find('Billy').name;
// -> 'Billy'

important points

  • Class.create is 100% compatible with Prototype and gives an empy initialize method in case no one is given
  • Object.prototype isn't modified
  • Class declaration goes in self
  • initialize special class method is executed at class creation
  • In a subclass, callSuper is available as a class method or an instance method, and calls respectively class or instance method with the given name and arguments in superclass
  • When deriving a class, we copy it's method and attributes in the subclass, and we inherit it's prototype (modified class method won't be modified in subclass, except if it calls callSuper)
  • Every class has 2 two class methods : include and extend, they simply act like their ruby equivalents (see further).
  • A subclass has a superclass attribute
  • Any initialize method can be omitted, default behavior is calling the parent one

let's go further

What about private methods and attributes ? Well, to be prototype compatible, it seems that we need a dirty hook in class constructor (which is not the initialize function but the one returned by Class.create) so maybe we should just use some convention, like this._one, and be conscious of what we're doing (If I'm calling a method which starts with an underscore from something that is not this, I'm certainly doing something wrong...).

How can we include a mixin after declaration ?
The good old Prototype way still works :
Object.extend(MyClass.prototype, MyMixin)
The new funky way :
MyClass.include(MyMixin);
While
MyClass.extend(MyMixin)
litterally extends class with mixin, making MyMixin methods and attributes available in MyClass

Be careful, mixins here are not ruby modules ! They are not part of the inheritance hierarchy. If they redefine a method, the previous one won't be available by calling callSuper.

source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*
Copyright (c) 2007 Samuel Lebeau

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the Software), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

var Class = {
  create: function(superclass, body) {
    if (!body) {
      var body = superclass || {};
      var superclass = null;
    }
    var klass = function() {
      this.constructor = klass;
      if (!Class._prototyping_) this.initialize.apply(this, arguments);
    }
    if (superclass) {
      Object.extend(klass, superclass);
      Object.extend(klass, {
        superclass: superclass,
        callSuper: Class._bindCallSuper(superclass),
        prototype: Class._inheritPrototype(superclass.prototype)
      });
      klass.prototype.callSuper = Class._bindCallSuper(superclass.prototype)
    }
    klass.include = Class._include;
    klass.extend = Class._extend; 
    if (body.self) {
      Object.extend(klass, body.self);
      delete body.self;
    }
    if (body.include) {
      [ body.include ].flatten().each(function(mixin) {
        klass.include(mixin);
      });
      delete body.include;
    }
    Object.extend(klass.prototype, body);
    if (!klass.prototype.initialize)
      klass.prototype.initialize = Prototype.emptyFunction;
    if (klass.initialize) klass.initialize();
    return klass;
  },
  _inheritPrototype: function(proto) {
    var inheritance = function() {};
    inheritance.prototype = proto;
    Class._prototyping_ = true;
    var prototype = new inheritance();
    delete Class._prototyping_;
    return prototype;
  },
  _include: function(mixin) { Object.extend(this.prototype, mixin); },
  _extend: function(mixin) { Object.extend(this, mixin); },
  _bindCallSuper: function(ancestor) {
    return function() {
      var args = $A(arguments), method = args.shift(), ret;
      this.callSuper = ancestor.callSuper;
      try { 
        if (ancestor[method] && ancestor[method] != this[method])
          ret = ancestor[method].apply(this, args);
        else if (!this.callSuper)
          throw new Class._noSuperMethodError(method);
        else
          ret = this.callSuper.apply(this, arguments);
      }
      finally { this.callSuper = arguments.callee }
      if (ret) return ret;
    }
  },
  _noSuperMethodError: function(method) {
    this.name = 'NoSuperMethodError';
    this.message = 'no super method ' + method + ''; 
  }
}

download

installation from trunk

 $ cd /path/to/prototype/trunk
 $ wget http://svn.gotfresh.info/classjs/class-patch-trunk.diff
 $ patch -p0 < class-patch-trunk.diff
 $ rake dist

then check test/unit/class.html or 'rake test' !

Options:

Size

Colors