tot - Check-in [17]
Not logged in
[Browse]  [Home]  [Login]  [Reports]  [Search]  [Timeline
  [Patchset
Check-in Number: 17
Date: 2007-Mar-18 10:59:40 (local)
2007-Mar-18 09:59:40 (UTC)
User:rse
Branch:
Comment: add base and ruby stuff to the repository, too
Tickets:
Inspections:
Files:
javascript/base.html      added-> 17
javascript/base.js      added-> 17
javascript/ruby.html      added-> 17
javascript/ruby.js      added-> 17
Added: javascript/base.html
===================================================================
--- javascript/base.html	                        (rev 0)
+++ javascript/base.html	2007-03-18 09:59:40 UTC (rev 17)
@@ -0,0 +1,41 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+    <head>
+        <title>Base class for JavaScript</title>
+        <script type="text/javascript" src="base.js"></script>
+    </head>
+    <body>
+        <h1>Base class for JavaScript</h1>
+        <script type="text/javascript">
+            //  TEST
+            var object = new Base;
+            object.extend({
+                value: "some data",
+                method: function() {
+                     document.writeln("Hello World!");
+                }
+            });
+            object.method();
+            // ==> Hello World!
+
+            //  TEST
+            var object = new Base;
+            object.method = function() {
+                document.writeln("Hello World!");
+            };
+            object.extend({
+                method: function() {
+                    // call the "super" method
+                    this.base();
+                    // add some code
+                    document.writeln("Hello again!");
+                }
+            });
+            object.method();
+            // ==> Hello World!
+            // ==> Hello again!
+        </script>
+    </body>
+</html>
+

Added: javascript/base.js
===================================================================
--- javascript/base.js	                        (rev 0)
+++ javascript/base.js	2007-03-18 09:59:40 UTC (rev 17)
@@ -0,0 +1,143 @@
+/*
+**  base.js -- base class for better OO inheritance in JavaScript
+**  Copyright (c) 2006 Dean Edwards <http://dean.edwards.name/>
+**  Copyright (c) 2007 Ralf S. Engelschall <rse@engelschall.com>
+**  Licensed under LGPL <http://www.gnu.org/licenses/lgpl.txt>
+**
+**  This is derived from Dean Edwards' "Base" implementation, version
+**  1.0.2 as available on http://dean.edwards.name/base/Base.js.
+**  It was cleaned up, modified and extended with the feedback
+**  under http://dean.edwards.name/weblog/2006/03/base/ by Ralf S.
+**  Engelschall.
+**
+**  $LastChangedDate: $
+**  $LastChangedRevision: $
+*/
+
+/*  class constructor  */
+var Base = function() {
+    if (arguments.length) {
+        if (this == window) {
+            // cast an object to this class
+            Base.prototype.extend.call(arguments[0], arguments.callee.prototype);
+        } else {
+            this.extend(arguments[0]);
+        }
+    }
+};
+
+/*  class version  */
+Base.version = "1.0.2+FIXES+RSE";
+
+/*  class method for extending  */
+Base.extend = function(_instance, _static) {
+    var extend = Base.prototype.extend;
+    if (!_instance)
+         _instance = {};
+
+    /* build the prototype */
+    Base._prototyping = true;
+    var _prototype = new this;
+    extend.call(_prototype, _instance);
+    var constructor = _prototype.constructor;
+    _prototype.constructor = this;
+    delete Base._prototyping;
+
+    /* create the wrapper for the constructor function */
+    var klass = function() {
+        if (!Base._prototyping) {
+            if (this instanceof klass) {
+                constructor.apply(this, arguments);
+            } else {
+                if (arguments.length)
+                    return extend.call(arguments[0], _prototype);
+            }
+        }
+    };
+    klass.prototype = _prototype;
+
+    /* build the class interface */
+    klass.extend = this.extend;
+    klass.implement = this.implement;
+    klass.toString = function() {
+        return String(constructor);
+    };
+    extend.call(klass, _static);
+
+    /* single instance */
+    var object = constructor ? klass : _prototype;
+
+    /* class initialisation */
+    if (object.init instanceof Function)
+        object.init();
+
+    return object;
+};
+
+/*  class method for multipe inheritance support  */
+Base.implement = function() {
+    for (var i = 0; i < arguments.length; i++) {
+        _interface = arguments[i];
+        if (_interface instanceof Function)
+            _interface = _interface.prototype;
+        this.prototype.extend(_interface);
+    }
+};
+
+/*  instance method for extending  */
+Base.prototype.extend = function(source, value) {
+        var extend = Base.prototype.extend;
+        if (arguments.length == 2) {
+            var ancestor = this[source];
+            if (   (ancestor instanceof Function)
+                && (value instanceof Function)
+                && ancestor.valueOf() != value.valueOf()
+                && /\bbase\b/.test(value)               ) {
+                var method = value;
+                value = function() {
+                    var previous = this.base;
+                    this.base = ancestor;
+                    try {
+                        var returnValue = method.apply(this, arguments);
+                    } catch (ex) {
+                        throw ex;
+                    }
+                    finally {
+                        this.base = previous;
+                    }
+                    return returnValue;
+                };
+                // point to the underlying method
+                value.valueOf = function() {
+                    return method;
+                };
+                value.toString = function() {
+                    return String(method);
+                };
+            }
+            return this[source] = value;
+        } else if (source) {
+            var _prototype = {toSource: null};
+            // do the "toString" and other methods manually
+            var _protected = ["toString", "valueOf"];
+            // if we are prototyping then include the constructor
+            if (Base._prototyping)
+                 _protected[2] = "constructor";
+            for (var i = 0; (name = _protected[i]); i++) {
+                if (source[name] != _prototype[name])
+                    extend.call(this, name, source[name]);
+            }
+            // copy each of the source object's properties to this object
+            for (var name in source) {
+                if (!_prototype[name])
+                    extend.call(this, name, source[name]);
+            }
+        }
+        return this;
+};
+
+/*  instance method for invoking ancestor method */
+Base.prototype.base = function() {
+    /* call this method from any other method to invoke that method's ancestor */
+};
+

Added: javascript/ruby.html
===================================================================
--- javascript/ruby.html	                        (rev 0)
+++ javascript/ruby.html	2007-03-18 09:59:40 UTC (rev 17)
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+    <head>
+        <title>Ruby for JavaScript</title>
+        <script type="text/javascript" src="ruby.js"></script>
+    </head>
+    <body>
+        <h1>Ruby for JavaScript</h1>
+        <script type="text/javascript">
+            /* FIXME */
+        </script>
+    </body>
+</html>

Added: javascript/ruby.js
===================================================================
--- javascript/ruby.js	                        (rev 0)
+++ javascript/ruby.js	2007-03-18 09:59:40 UTC (rev 17)
@@ -0,0 +1,892 @@
+/*
+**  ruby.js -- Ruby-style object extensions for JavaScript
+**  Copyright (c) 2006 Florian Gross <flgr@ccan.de>
+**  Copyright (c) 2007 Ralf S. Engelschall <rse@engelschall.com>
+**  Licensed under GPL <http://www.gnu.org/licenses/gpl.txt>
+**
+**  This is derived from the Florian Gross' "ruby.js", version 0.4.0 as
+**  of 2005-09-05 from http://flgr.0x42.net/ruby.js/, which implements
+**  some of Ruby's standard library functions in JavaScript. It was
+**  reformatted and cleaned up (especially semicolons additions to allow
+**  code packing) by Ralf S. Engelschall.
+**
+**  $LastChangedDate: $
+**  $LastChangedRevision: $
+*/
+
+/*
+ *  Notice: This library heavily touches the global JavaScript namespace
+ *  and actually doesn't create a custom namespace at all. It is
+ *  expected that this library will become obsolete when the next
+ *  revision of the ECMA-262 standard is released as there is plans to
+ *  offer more functionality as part of the official language's standard
+ *  library. Note that this implementation does not exactly mimic all
+ *  of Ruby's standard library -- ".each" in general always yields
+ *  value,key where key is the index for sequential containers and the
+ *  Range implementation is known to have bugs.
+ */
+
+(function(){
+    /*
+     *  "Object" extensions
+     */
+
+    Object.prototype.clone = function (deepClone) {
+        var result = new this.constructor();
+        for (var property in this) {
+            if (deepClone && typeof this[property] === 'object')
+                result[property] = this[property].clone(deepClone);
+            else
+                result[property] = this[property];
+        }
+        return result;
+    };
+
+    Object.prototype.extend = function (other) {
+        if (!this.mixins)
+            this.mixins = [];
+        this.mixins.push(other);
+        for (var property in other)
+            if (!this.hasOwnProperty(property))
+                this[property] = other[property];
+        return;
+    };
+
+    Object.prototype.cmp = function (other) {
+        if (this < other)
+            return -1;
+        if (this > other)
+            return +1;
+        return 0;
+    };
+
+    Object.prototype.valuesAt = function () {
+        var obj = this;
+        return(arguments.toArray().map(function(index) {
+            return obj[index];
+        }));
+    };
+
+    Object.prototype.toArray = function () {
+        if (!this.length)
+            throw "Can't convert";
+        var result = [];
+        for (var i = 0; i < this.length; i++)
+            result.push(this[i]);
+        return result;
+    };
+
+    Object.prototype.hash = function () {
+        return this.toSource().hash();
+    };
+
+    Object.prototype.instanceOf = function (klass) {
+        return (this.constructor == klass);
+    };
+
+    Object.prototype.isA =
+    Object.prototype.kindOf = function (klass) {
+        if (this.instanceOf(klass))
+            return true;
+        if (this["mixins"] != undefined && this.mixins.includes(klass))
+            return true;
+        return false;
+    };
+
+    Object.prototype.methods = function () {
+        var result = [];
+        for (var property in this)
+            if (typeof this[property] === "function")
+                result.push(property);
+        return result;
+    };
+
+    Object.prototype.respondTo = function (method) {
+        return this.methods().includes(method);
+    };
+
+    Object.prototype.send = function(method) {
+        var rest = arguments.toArray().last(-1);
+        if (!this.respondTo(method))
+            throw "undefined method";
+        return this[method].apply(this, rest);
+    };
+
+    Object.prototype.instanceEval = function (code) {
+        if (code.isA(Function))
+            return code.apply(this);
+        return eval(code.toString());
+    };
+
+    /*
+     *  "Number" extensions
+     */
+
+    Number.prototype.times = function (block) {
+        for (var i = 0; i < this; i++)
+            block(i);
+    };
+
+    Number.prototype.upto = function (other, block) {
+        for (var i = this; i <= other; i++)
+            block(i);
+    };
+
+    Number.prototype.downto = function (other, block) {
+        for (var i = this; i >= other; i--)
+            block(i);
+    };
+
+    Number.prototype.towards = function (other, block) {
+        var step = this.cmp(other);
+        for (var i = this; i !== other - step; i -= step)
+            block(i);
+    };
+
+    Number.prototype.succ = function () {
+        return this + 1;
+    };
+
+    Number.prototype.pred = function () {
+        return this - 1;
+    };
+
+    Number.prototype.chr = function () {
+        return String.fromCharCode(this);
+    };
+
+    /*
+     *  "Enumerable" addition
+     */
+
+    var Enumerable = new Object();
+
+    Enumerable.eachWindow = function (window, block) {
+        if (!window.isA(Range))
+            window = range(0, window);
+        var elements = [], pushed = 0;
+        this.each(function (item, index) {
+            elements.push(item);
+            pushed += 1;
+            if (pushed % window.rend == 0) {
+                var start = [0, window.start - window.rend + pushed].max();
+                var end   = [0, window.rend + pushed].max();
+                block(elements.fetch(xrange(start, end)), index);
+            }
+        });
+    };
+
+    Enumerable.collect =
+    Enumerable.map = function (block) {
+        var result = [];
+        this.each(function (item, index) {
+            result.push(block(item, index));
+        });
+        return result;
+    };
+
+    Enumerable.toArray = Enumerable.entries = function () {
+        return this.map(function (item) {
+            return(item);
+        });
+    };
+
+    Enumerable.inject = function (firstArg) {
+        var state, block, first = true;
+        if (arguments.length == 1)
+            block = firstArg;
+        else {
+            state = firstArg;
+            block = arguments[1];
+        }
+        this.each(function (item, index) {
+            if (first && typeof state === "undefined")
+                state = item, first = false;
+            else
+                state = block(state, item, index);
+        });
+        return state;
+    };
+
+    Enumerable.find =
+    Enumerable.detect = function (block) {
+        var result, done;
+        this.each(function (item, index) {
+            if (!done && block(item, index)) {
+                result = item;
+                done = true;
+            }
+        });
+        return result;
+    };
+
+    Enumerable.findAll =
+    Enumerable.select = function (block) {
+        return this.inject([], function (result, item, index) {
+            return (block(item, index) ? result.add(item) : result);
+        });
+    };
+
+    Enumerable.grep = function (obj) {
+        return this.findAll(function (item) {
+            return obj.test(item);
+        });
+    };
+
+    Enumerable.reject = function (block) {
+        return this.select(function (item, index) {
+            return !block(item, index);
+        });
+    };
+
+    Enumerable.compact = function () {
+        return this.select(function (item) {
+            return (typeof item !== "undefined")
+        });
+    };
+
+    Enumerable.nitems = function () {
+        return this.compact().length;
+    };
+
+    Enumerable.sortBy = function (block) {
+        return this.map(function(item, index) {
+            return [ block(item, index), item ];
+        }).sort(function (a, b) {
+            return a[0].cmp(b[0]);
+        }).map(function (item) {
+            return item[1];
+        });
+    };
+
+    Enumerable.all = function (block) {
+        return (this.findAll(block).length == this.length);
+    };
+
+    Enumerable.any = function (block) {
+        return (typeof this.find(block) !== "undefined");
+    };
+
+    Enumerable.includes = function (obj) {
+        return this.any(function (item) {
+            return (item === obj);
+        });
+    };
+
+    Enumerable.index = function (obj) {
+        var result;
+        this.find(function (item, index) {
+            if (obj == item) {
+                result = index;
+                return true;
+            } else
+                return false;
+        });
+        return result;
+    };
+
+    Enumerable.uniq = function () {
+        return this.inject([], function (result, item) {
+            return (result.includes(item) ? result : result.add(item));
+        });
+    };
+
+    Enumerable.max = function (block) {
+        if (!block)
+            block = function (a, b) { return a.cmp(b); };
+        return this.sort(block).last();
+    };
+
+    Enumerable.min = function (block) {
+        if (!block) {
+            block = function (a, b) { return a.cmp(b); };
+        return this.sort(block).first();
+    };
+
+    Enumerable.partition = function (block) {
+        var positives = [], negatives = [];
+        this.each(function (item, index) {
+            if (block(item, index))
+                positives.push(item);
+            else
+                negatives.push(item);
+        });
+        return [positives, negatives];
+    };
+
+    Enumerable.zip = function () {
+        var ary = arguments.toArray();
+        ary.unshift(this);
+        return ary.transpose();
+    };
+
+    Enumerable.flatten = function (depth) {
+        if (depth == undefined)
+            depth = -1;
+        if (!depth)
+            return this;
+        return this.inject([], function(result, item) {
+            var flatItem = item.respondTo("flatten") ? item.flatten(depth - 1) : [item];
+            return result.merge(flatItem);
+        });
+    };
+
+    /*
+     *  "Array" extension
+     */
+
+    Array.fromObject = function (obj) {
+        if (!obj.length)
+            throw "Can't convert";
+        var result = [];
+        for (var i = 0; i < obj.length; i++)
+            result.push(obj[i]);
+        return result;
+    };
+
+    Array.prototype.transpose = function () {
+        var result, length = -1;
+        this.each(function (item, index) {
+            if (length < 0) { /* first element */
+                length = item.length;
+                result = Array.withLength(length, function () {
+                    return new Array(this.length);
+                });
+            } else if (length != item.length) {
+                throw "Element sizes differ";
+            }
+            item.each(function (iitem, iindex) {
+                result[iindex][index] = iitem;
+            });
+        });
+        return result;
+    };
+
+    Array.withLength = function (length, fallback) {
+        var result = [null].mul(length);
+        result.fill(fallback);
+        return result;
+    };
+
+    Array.prototype.each = function (block) {
+        for (var index = 0; index < this.length; index++) {
+            var item = this[index];
+            block(item, index);
+        }
+        return this;
+    };
+
+    Array.prototype.extend(Enumerable);
+
+    Array.prototype.isEmpty = function () {
+        return (this.length == 0);
+    };
+
+    Array.prototype.at =
+    Array.prototype.fetch = function (index, length) {
+        if (index.isA(Range)) {
+            var end = index.rend + (index.rend < 0 ? this.length : 0);
+            index = index.start;
+            length = end - index + 1;
+        }
+        if (length == undefined)
+            length = 1;
+        if (index < 0)
+            index += this.length;
+        var result = this.slice(index, index + length);
+        return (result.length == 1 ? result[0] : result);
+    };
+
+    Array.prototype.first = function (amount) {
+        if (amount == undefined)
+            amount = 1;
+        return this.at(xrange(0, amount));
+    };
+
+    Array.prototype.last = function (amount) {
+        if (amount == undefined)
+            amount = 1;
+        return this.at(range(-amount, -1));
+    };
+
+    Array.prototype.store = function (index) {
+        var length = 1, obj;
+        arguments = arguments.toArray();
+        arguments.shift();
+        if (arguments.length == 2)
+            length = arguments.shift();
+        obj = arguments.shift();
+        if (!obj.isA(Array))
+            obj = [obj];
+        if (index.isA(Range)) {
+            var end = index.rend + (index.rend < 0 ? this.length : 0);
+            index = index.start;
+            length = end - index + 1;
+        }
+        if (index < 0)
+            index += this.length;
+        this.replace(this.slice(0, index).merge(obj).merge(this.slice(index + length)));
+        return this;
+    };
+
+    Array.prototype.insert = function (index) {
+        var values = arguments.toArray().last(-1);
+        if (index < 0)
+            index += this.length + 1;
+        return this.store(index, 0, values);
+    };
+
+    Array.prototype.update = function (other) {
+        var obj = this;
+        other.each(function(item) { obj.push(item) });
+        return obj;
+    };
+
+    Array.prototype.merge = Array.prototype.concat;
+
+    Array.prototype.add = function (item) {
+        return this.merge([item]);
+    };
+
+    Array.prototype.clear = function () {
+        var obj = this;
+        this.length.times(function (index) {
+            delete obj[index];
+        });
+        this.length = 0;
+    };
+
+    Array.prototype.replace = function (obj) {
+        this.clear();
+        this.update(obj);
+    };
+
+    Array.prototype.mul = function (count) {
+        var result = [];
+        var obj = this;
+        count.times(function() {
+            result = result.merge(obj);
+        });
+        return result;
+    };
+
+    Array.prototype.fill = function (value) {
+        var old_length = this.length;
+        var obj = this;
+        this.clear();
+        var block;
+        if (typeof value !== "function")
+            block = function() { return(value) };
+        else
+            block = value;
+        old_length.times(function (i) {
+            obj.push(block(i));
+        });
+    };
+
+    Array.prototype.removeAt = function (targetIndex) {
+        var result = this[targetIndex];
+        var newArray = this.reject(function (item, index) {
+            return (index == targetIndex);
+        });
+        this.replace(newArray);
+        return result;
+    };
+
+    Array.prototype.remove = function (obj) {
+        this.removeAt(this.index(obj));
+    };
+
+    Array.prototype.removeIf = function (block) {
+        this.replace(this.reject(block));
+    };
+
+    /*
+     *  "Range" addition
+     */
+
+    function Range (start, end, excludeEnd) {
+        this.begin = this.start = start;
+        this.end = end;
+        this.excludeEnd = excludeEnd;
+        this.rend = excludeEnd ? end.pred() : end;
+        this.length = this.toArray().length;
+    };
+
+    function range (start, end) {
+        return (new Range(start, end));
+    }
+    function xrange(start, end) {
+        return (new Range(start, end, true));
+    }
+
+    Range.prototype.toString = function () {
+        return ("" + this.start + (this.excludeEnd ? "..." : "..") + this.end);
+    }
+
+    Range.prototype.each = function (block) {
+        var index = 0;
+        this.start.towards(this.rend, function (i) {
+            return block(i, index++);
+        });
+    }
+
+    Range.prototype.extend(Enumerable);
+
+    Range.prototype.includes = function (item) {
+        return (this.start.cmp(item) == -1 && this.rend.cmp(item) == +1);
+    };
+
+    /*
+     *  "Hash" addition
+     */
+
+    function Hash (defaultBlock) {
+        this.defaultBlock = defaultBlock;
+        this.keys = [];
+        this.values = [];
+        this.length = 0;
+    }
+
+    Hash.fromArray = function (array) {
+        var result = new Hash();
+        array.each(function (item) {
+            var key = item[0], value = item[1];
+            result.store(key, value);
+        });
+        return result;
+    };
+
+    Hash.prototype.at =
+    Hash.prototype.fetch = function (key, block) {
+        if (this.hasKey(key))
+            return this["item_" + key.hash()];
+        else if (block)
+            return block(key);
+        else
+            return defaultBlock(key);
+    };
+
+    Hash.prototype.store = function (key, value) {
+        this.keys.push(key);
+        this.values.push(value);
+        this.length++;
+        return (this["item_" + key.hash()] = value);
+    };
+
+    Hash.prototype.toA = function () {
+        return this.keys.zip(this.values);
+    };
+
+    Hash.prototype.isEmpty = function () {
+        return (this.length == 0);
+    };
+
+    Hash.prototype.has =
+    Hash.prototype.includes =
+    Hash.prototype.hasKey = function (key) {
+        return hasOwnProperty("item_" + key.hash());
+    };
+
+    Hash.prototype.hasValue = function (value) {
+        return this.values.includes(value);
+    };
+
+    Hash.prototype.each = function (block) {
+        this.toA().each(function (pair) {
+            return block(pair[1], pair[0]);
+        });
+    };
+
+    Hash.prototype.extend(Enumerable);
+
+    Hash.prototype.merge = function (other) {
+        other.each(function (value, key) {
+            this.store(key, value);
+        });
+    };
+
+    Hash.prototype.remove = function (key) {
+        var valueIndex = this.keys.index(key);
+        var value = this.values[valueIndex];
+        this.keys.remove(key);
+        this.values.removeAt(valueIndex);
+        delete(this["item_" + key.hash()]);
+        this.length--;
+        return [key, value];
+    };
+
+    Hash.prototype.removeIf = function (block) {
+        this.each(function (value, key) {
+            if (block(value, key))
+                this.remove(key);
+        });
+    };
+
+    Hash.prototype.shift = function () {
+        return this.remove(this.keys[0]);
+    };
+
+    Hash.prototype.clear = function () {
+        var obj = this;
+        this.length.times(function() {obj.shift()});
+    };
+
+    Hash.prototype.replace = function (obj) {
+        this.clear();
+        this.merge(obj);
+    };
+
+    Hash.prototype.invert = function () {
+        return Hash.fromArray(this.map(function (value, key) {
+            return [value, key];
+        }));
+    };
+
+    Hash.prototype.rehash = function () {
+        var result = new Hash(this.defaultBlock);
+        this.each(function (value, key) {
+             result.store(key, value);
+        });
+        this.replace(result);
+    };
+
+    /*
+     *  "MatchData" addition
+     */
+
+    function MatchData (matches, str, pos) {
+        this.matches = matches, this.string = str;
+        this.begin = this.position = pos;
+        this.match = matches[0];
+        this.captures = matches.slice(1);
+        this.end = pos + this.match.length;
+        this.length = matches.length;
+        this.preMatch = str.substr(0, pos);
+        this.postMatch = str.substr(this.end);
+    }
+
+    MatchData.prototype.toString = function () {
+        return this.match;
+    };
+
+    MatchData.prototype.at = function (index) {
+        return this.matches.at(index);
+    };
+
+    MatchData.prototype.toArray = function () {
+        return this.matches;
+    };
+
+    /*
+     *  "RegExp" extension
+     */
+
+    RegExp.prototype.match = function (str) {
+        var matches = this.exec(str);
+        if (matches) {
+            var pos = str.search(this);
+            return (new MatchData(matches, str, pos));
+        }
+    };
+
+    /*
+     *  "String" extension
+     */
+
+    String.prototype.clone = function () {
+        return (new String(this));
+    };
+
+    String.prototype.each = function (block) {
+        this.split("\n").each(block);
+    };
+
+    String.prototype.extend(Enumerable);
+
+    String.prototype.toArray = function () {
+        return this.split("\n");
+    };
+
+    String.prototype.towards = function (other, block) {
+        var item = this;
+        while (item.cmp(other) <= 0) {
+            block(item);
+            item = item.succ();
+        }
+    };
+
+    String.prototype.hash = function () {
+        var result = 0;
+        this.split("").each(function (item) {
+            result += item.charCodeAt(0);
+            result += (result << 10);
+            result ^= (result >> 6);
+        })
+        result += (result << 3);
+        result ^= (result >> 11);
+        result += (result << 15);
+        return result;
+    }
+
+    String.prototype.chars = function () {
+        return this.split("");
+    };
+
+    String.prototype.at =
+    String.prototype.fetch = function (index, length) {
+        if (index.isA(Range)) {
+            var end = index.rend + (index.rend < 0 ? this.length : 0);
+            index = index.start;
+            length = end - index + 1;
+        }
+        if (length == undefined)
+            length = 1;
+        if (index < 0)
+            index += this.length;
+        return this.substr(index, length);
+    };
+
+    String.prototype.store =
+    String.prototype.change = function (index) {
+        var length = 1, obj;
+        arguments = arguments.toArray();
+        arguments.shift();
+        if (arguments.length == 2)
+            length = arguments.shift();
+        obj = arguments.shift();
+        if (index.isA(Range)) {
+            var end = index.rend + (index.rend < 0 ? this.length : 0);
+            index = index.start;
+            length = end - index + 1;
+        }
+        if (index < 0)
+            index += this.length;
+        return (this.substr(0, index) + obj + this.substr(index + length));
+    };
+
+    String.prototype.reverse = function () {
+        return this.split("").reverse().join("");
+    };
+
+    String.prototype.scan = function (pattern) {
+        var str = this, result = [], oldPos = -1, match, offset = 0;
+        while (match = pattern.match(str)) {
+            if (match.end == match.begin)
+                throw "Can't have null length matches with scan()";
+            var newMatch = new MatchData(match.matches, match.string, match.position + offset);
+            result.push(newMatch);
+            str = match.postMatch;
+            offset += match.toString().length;
+        }
+        return result;
+    };
+
+    String.prototype.sub = function (what, by, global) {
+        var block = (typeof by === "function" ? by : function() { return(by) });
+        var matches = this.scan(what), result = this, offset = 0;
+        if (!global && !by.global)
+            matches = matches.slice(0, 1);
+        matches.each(function (match) {
+            var replacement = block(match);
+            offset += replacement.length - match.toString().length;
+            result = result.change(match.begin + offset, match.toString().length, replacement);
+        });
+        return result;
+    };
+
+    String.prototype.gsub = function (what, by) {
+        return this.sub(what, by, true);
+    };
+
+    String.prototype.tr = function (from, to) {
+        var map = Hash.fromArray(from.chars().zip(to.chars()));
+        return (this.chars().map(function (chr) {
+            return (map.includes(chr) ? map.fetch(chr) : chr);
+        }).join(""));
+    };
+
+    String.prototype.mul = function (other) {
+        var result = "", str = this;
+        other.times(function() {
+            result += str;
+        });
+        return result;
+    };
+
+    String.prototype.isUpcase = function () {
+        return (this == this.upcase());
+    };
+
+    String.prototype.isDowncase = function () {
+        return (this == this.downcase());
+    }
+
+    String.prototype.isCapitalized = function () {
+        return (   this.fetch(0).isUpcase()
+                && this.fetch(range(1, -1)).isDowncase());
+    };
+
+    String.prototype.upcase = String.prototype.toUpperCase;
+
+    String.prototype.downcase = String.prototype.toLowerCase;
+
+    String.prototype.capitalize = function () {
+        return (this.fetch(0).upcase() + this.fetch(range(1, -1)).downcase());
+    };
+
+    String.prototype.swapcase = function () {
+        return (this.chars().map(function (chr) {
+            if (chr.isUpcase())
+                return chr.downcase();
+            if (chr.isDowncase())
+                return chr.upcase();
+            return chr;
+        }).join(""));
+    };
+
+    String.prototype.ord = function () {
+        return this.charCodeAt(0);
+    };
+
+    String.prototype.isEmpty = function () {
+        return (this.length == 0);
+    }
+
+    String.prototype.succ = function () {
+        if (this.isEmpty())
+            return this;
+        /* numerics */
+        if (/^\d+$/.test(this))
+            return ((Number(this) + 1).toString());
+        /* just one character */
+        if (this.length == 1) {
+            /* letters */
+            if (/[A-Za-z]/.test(this)) {
+                var lastLetter  = this.isUpcase() ? 'Z' : 'z';
+                var firstLetter = this.isUpcase() ? 'A' : 'a';
+                return ((this == lastLetter) ? firstLetter.mul(2) : (this.ord() + 1).chr());
+            }
+            return (this == (-1).chr() ? 0.0.chr().mul(2) : (this.ord() + 1).chr());
+        }
+        /* multiple characters */
+        var result = this;
+        for (var index = this.length; --index >= 0; ) {
+            var chr = this.at(index);
+            if (chr.succ().length == 1 || index == 0)
+                return result.change(index, chr.succ());
+            result = result.change(index, chr.succ().at(-1));
+        }
+        return result;
+    };
+
+    String.prototype.ljust = function (length, fill) {
+        if (!fill)
+            fill = " ";
+        if (fill.length > 1)
+            throw "TODO: Make fills with length > 1 work.";
+        return (this + fill.mul(length / fill.length - this.length));
+    };
+
+})();
+