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: |
|
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));
+ };
+
+})();
+