Start PDoc integration.

This commit is contained in:
Samuel Lebeau 2008-12-14 05:36:59 +01:00 committed by Andrew Dupont
parent 24fb692281
commit b2597ece0c
16 changed files with 1001 additions and 16 deletions

View File

@ -10,29 +10,41 @@ PROTOTYPE_TEST_UNIT_DIR = File.join(PROTOTYPE_TEST_DIR, 'unit')
PROTOTYPE_TMP_DIR = File.join(PROTOTYPE_TEST_UNIT_DIR, 'tmp')
PROTOTYPE_VERSION = '1.6.0.3'
$:.unshift File.join(PROTOTYPE_ROOT, 'lib')
task :default => [:dist, :dist_helper, :package, :clean_package_source]
desc "Builds the distribution."
task :dist do
$:.unshift File.join(PROTOTYPE_ROOT, 'lib')
require 'protodoc'
Dir.chdir(PROTOTYPE_SRC_DIR) do
File.open(File.join(PROTOTYPE_DIST_DIR, 'prototype.js'), 'w+') do |dist|
dist << Protodoc::Preprocessor.new('prototype.js')
end
File.open(File.join(PROTOTYPE_DIST_DIR, 'prototype.js'), 'w+') do |dist|
source = File.join(PROTOTYPE_SRC_DIR, 'prototype.js')
dist << Protodoc::Preprocessor.new(source, :strip_documentation => true)
end
end
desc "Builds the documentation."
task :doc do
require 'protodoc'
require 'pdoc'
Tempfile.open("prototype-doc") do |temp|
source = File.join(PROTOTYPE_SRC_DIR, 'prototype.js')
temp << Protodoc::Preprocessor.new(source, :strip_documentation => false)
temp.flush
rm_rf PROTOTYPE_DOC_DIR
PDoc::Runner.new(temp.path, :output => PROTOTYPE_DOC_DIR).run
end
end
desc "Builds the updating helper."
task :dist_helper do
$:.unshift File.join(PROTOTYPE_ROOT, 'lib')
require 'protodoc'
Dir.chdir(File.join(PROTOTYPE_ROOT, 'ext', 'update_helper')) do
File.open(File.join(PROTOTYPE_DIST_DIR, 'prototype_update_helper.js'), 'w+') do |dist|
dist << Protodoc::Preprocessor.new('prototype_update_helper.js')
end
File.open(File.join(PROTOTYPE_DIST_DIR, 'prototype_update_helper.js'), 'w+') do |dist|
source = File.join(PROTOTYPE_ROOT, 'ext', 'update_helper', 'prototype_update_helper.js')
dist << Protodoc::Preprocessor.new(source)
end
end

View File

@ -8,27 +8,43 @@ class String
def strip_whitespace_at_line_ends
lines.map {|line| line.gsub(/\s+$/, '')} * $/
end
def strip_pdoc_comments
gsub %r{\s*/\*\*.*?\*\*/}m, "\n"
end
end
module Protodoc
module Environment
def include(*filenames)
filenames.map {|filename| Preprocessor.new(filename).to_s}.join("\n")
filenames.map do |filename|
Preprocessor.new(expand_path(filename), @options).result
end.join("\n")
end
end
class Preprocessor
include Environment
def initialize(filename)
def initialize(filename, options = { })
filename = File.join(filename.split('/'))
@filename = File.expand_path(filename)
@template = ERB.new(IO.read(@filename), nil, '%')
@options = options
end
def to_s
@template.result(binding).strip_whitespace_at_line_ends
def expand_path(filename)
File.join(File.dirname(@filename), filename)
end
def result
result = @template.result(binding)
result = result.strip_whitespace_at_line_ends
result = result.strip_pdoc_comments if @options[:strip_documentation]
result
end
alias_method :to_s, :result
end
end

View File

@ -1,3 +1,11 @@
/** section: lang, alias of: Array.from
* $A(iterable) -> Array
*
* Accepts an array-like collection (anything with numeric indices) and returns
* its equivalent as an actual Array object.
* This method is a convenience alias of [[Array.from]], but is the preferred way
* of casting to an Array.
**/
function $A(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
@ -21,6 +29,14 @@ if (Prototype.Browser.WebKit) {
};
}
/** section: lang
* $w(string) -> Array
* - string (String): A string with zero or more spaces.
*
* Splits a string into an array, treating all whitespace as delimiters.
*
* Equivalent to Ruby's `%w{foo bar}` or Perl's `qw(foo bar)`.
**/
function $w(string) {
if (!Object.isString(string)) return [];
string = string.strip();
@ -29,6 +45,9 @@ function $w(string) {
Array.from = $A;
/** section: lang
* Array
**/
(function() {
var arrayProto = Array.prototype,
slice = arrayProto.slice,
@ -40,25 +59,49 @@ Array.from = $A;
}
if (!_each) _each = each;
/**
* Array#clear() -> Array
* Empties an array.
**/
function clear() {
this.length = 0;
return this;
}
/**
* Array#first() -> ?
* Returns the array's first item.
**/
function first() {
return this[0];
}
/**
* Array#last() -> ?
* Returns the array's last item.
**/
function last() {
return this[this.length - 1];
}
/**
* Array#compact() -> Array
* Trims the array of `null`, `undefined`, or other "falsy" values.
**/
function compact() {
return this.select(function(value) {
return value != null;
});
}
/**
* Array#flatten() -> Array
* Returns a flat (one-dimensional) version of the array.
*
* Nested arrays are recursively injected inline. This can prove very
* useful when handling the results of a recursive collection algorithm,
* for instance.
**/
function flatten() {
return this.inject([], function(array, value) {
if (Object.isArray(value))
@ -68,6 +111,13 @@ Array.from = $A;
});
}
/**
* Array#without(value...) -> Array
* - value (?): A value to exclude.
*
* Produces a new version of the array that does not contain any of the
* specified values.
**/
function without() {
var values = slice.call(arguments, 0);
return this.select(function(value) {
@ -75,14 +125,34 @@ Array.from = $A;
});
}
/**
* Array#reverse([inline = false]) -> Array
* - inline (Boolean): Whether to modify the array in place. If `false`,
* clones the original array first.
*
* Returns the reversed version of the array.
**/
function reverse(inline) {
return (inline !== false ? this : this.toArray())._reverse();
}
/**
* Array#reduce() -> Array
* Reduces arrays: one-element arrays are turned into their unique item,
* while multiple-element arrays are returned untouched.
**/
function reduce() {
return this.length > 1 ? this : this[0];
}
/**
* Array#uniq([sorted = false]) -> Array
* - sorted (Boolean): Whether the array has already been sorted. If `true`,
* a less-costly algorithm will be used.
*
* Produces a duplicate-free version of an array. If no duplicates are
* found, the original array is returned.
**/
function uniq(sorted) {
return this.inject([], function(array, value, index) {
if (0 == index || (sorted ? array.last() != value : !array.include(value)))
@ -91,24 +161,53 @@ Array.from = $A;
});
}
/**
* Array#intersect(array) -> Array
* - array (Array): A collection of values.
*
* Returns an array containing every item that is shared between the two
* given arrays.
**/
function intersect(array) {
return this.uniq().findAll(function(item) {
return array.detect(function(value) { return item === value });
});
}
/** alias of: Array#toArray
* Array#clone() -> Array
*
* Returns a duplicate of the array, leaving the original array intact.
**/
function clone() {
return slice.call(this, 0);
}
/** related to: Enumerable#size
* Array#size() -> Number
* Returns the size of the array.
*
* This is just a local optimization of the mixed-in [[Enumerable#size]]
* which avoids array cloning and uses the arrays native length property.
**/
function size() {
return this.length;
}
/** related to: Object.inspect
* Array#inspect() -> String
*
* Returns the debug-oriented string representation of an array.
**/
function inspect() {
return '[' + this.map(Object.inspect).join(', ') + ']';
}
/** related to: Object.toJSON
* Array#toJSON() -> String
*
* Returns a JSON string representation of the array.
**/
function toJSON() {
var results = [];
this.each(function(object) {
@ -118,6 +217,15 @@ Array.from = $A;
return '[' + results.join(', ') + ']';
}
/**
* Array#indexOf(item[, offset = 0]) -> Number
* - item (?): A value that may or may not be in the array.
* - offset (Number): The number of initial items to skip before beginning the
* search.
*
* Returns the position of the first occurrence of `item` within the array or
* `-1` if `item` doesnt exist in the array.
**/
function indexOf(item, i) {
i || (i = 0);
var length = this.length;
@ -127,12 +235,26 @@ Array.from = $A;
return -1;
}
/** related to: Array#indexOf
* Array#lastIndexOf(item[, offset]) -> Number
* - item (?): A value that may or may not be in the array.
* - offset (Number): The number of items at the end to skip before beginning
* the search.
*
* Returns the position of the last occurrence of `item` within the array or
* `-1` if `item` doesnt exist in the array.
**/
function lastIndexOf(item, i) {
i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
var n = this.slice(0, i).reverse().indexOf(item);
return (n < 0) ? n : i - n - 1;
}
/**
* Array#concat(args...) -> Array
*
* TODO: Array#concat
**/
function concat() {
var array = slice.call(this, 0), item;
for (var i = 0, length = arguments.length; i < length; i++) {

View File

@ -1,5 +1,35 @@
/* Based on Alex Arnell's inheritance implementation. */
/** section: lang
* Class
**/
var Class = (function() {
/**
* Class.create([superclass][, methods...]) -> Class
* - superclass (Class): The optional superclass to inherit methods from.
* - methods (Object): An object whose properties will be "mixed-in" to the
* new class. Any number of mixins can be added; later mixins take
* precedence.
*
* Creates a class.
*
* Class.create returns a function that, when called, will fire its own
* `initialize` method.
*
* `Class.create` accepts two kinds of arguments. If the first argument is
* a `Class`, it's treated as the new class's superclass, and all its
* methods are inherited. Otherwise, any arguments passed are treated as
* objects, and their methods are copied over as instance methods of the new
* class. Later arguments take precedence over earlier arguments.
*
* If a subclass overrides an instance method declared in a superclass, the
* subclass's method can still access the original method. To do so, declare
* the subclass's method as normal, but insert `$super` as the first
* argument. This makes `$super` available as a method for use within the
* function.
*
* To extend a class after it has been defined, use [[Class#addMethods]].
**/
function create() {
var parent = null, properties = $A(arguments);
if (Object.isFunction(properties[0]))
@ -30,6 +60,23 @@ var Class = (function() {
return klass;
}
/**
* Class#addMethods(methods) -> Class
* - methods (Object): The methods to add to the class.
*
* Adds methods to an existing class.
*
* `Class#addMethods` is a method available on classes that have been
* defined with `Class.create`. It can be used to add new instance methods
* to that class, or overwrite existing methods, after the class has been
* defined.
*
* New methods propagate down the inheritance chain. If the class has
* subclasses, those subclasses will receive the new methods even in the
* context of `$super` calls. The new methods also propagate to instances of
* the class and of all its subclasses, even those that have already been
* instantiated.
**/
function addMethods(source) {
var ancestor = this.superclass && this.superclass.prototype;
var properties = Object.keys(source);

View File

@ -1,3 +1,12 @@
/** section: lang
* Date
**/
/**
* Date#toJSON -> String
*
* TODO: Date#toJSON
**/
Date.prototype.toJSON = function() {
return '"' + this.getUTCFullYear() + '-' +
(this.getUTCMonth() + 1).toPaddedString(2) + '-' +

View File

@ -1,6 +1,19 @@
/** section: lang
* mixin Enumerable
**/
var $break = { };
var Enumerable = (function() {
/**
* Enumerable#each(iterator[, context]) -> Enumerable
* - iterator (Function): A `Function` that expects an item in the
* collection as the first argument and a numerical index as the second.
* - context (Object): The scope in which to call `iterator`. Affects what
* the keyword `this` means inside `iterator`.
*
* Calls `iterator` for each item in the collection.
**/
function each(iterator, context) {
var index = 0;
try {
@ -13,6 +26,12 @@ var Enumerable = (function() {
return this;
}
/**
* Enumerable#eachSlice(number[, iterator = Prototype.K[, context]]) -> Enumerable
*
* TODO: Enumerable#eachSlice
* http://prototypejs.org/api/enumerable#method-eachslice
**/
function eachSlice(number, iterator, context) {
var index = -number, slices = [], array = this.toArray();
if (number < 1) return array;
@ -21,6 +40,12 @@ var Enumerable = (function() {
return slices.collect(iterator, context);
}
/**
* Enumerable#all([iterator = Prototype.K[, context]]) -> Boolean
*
* Determines whether all the elements are boolean-equivalent to `true`,
* either directly or through computation by the provided iterator.
**/
function all(iterator, context) {
iterator = iterator || Prototype.K;
var result = true;
@ -31,6 +56,12 @@ var Enumerable = (function() {
return result;
}
/**
* Enumerable#any([iterator = Prototype.K[, context]]) -> Boolean
*
* Determines whether at least one element is boolean-equivalent to `true`,
* either directly or through computation by the provided iterator.
**/
function any(iterator, context) {
iterator = iterator || Prototype.K;
var result = false;
@ -41,6 +72,12 @@ var Enumerable = (function() {
return result;
}
/** alias of: Enumerable#map
* Enumerable#collect([iterator = Prototype.K[, context]]) -> Array
*
* Returns the results of applying the iterator to each element.
* Aliased as [[Enumerable#map]].
**/
function collect(iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
@ -50,6 +87,12 @@ var Enumerable = (function() {
return results;
}
/** alias of: Enumerable#find
* Enumerable#detect(iterator[, context]) -> firstElement | undefined
*
* Finds the first element for which the iterator returns a truthy value.
* Aliased by the [[Enumerable#find]] method.
**/
function detect(iterator, context) {
var result;
this.each(function(value, index) {
@ -61,6 +104,12 @@ var Enumerable = (function() {
return result;
}
/** alias of: select
* Enumerable#findAll(iterator[, context]) -> Array
*
* Returns all the elements for which the iterator returned truthy value.
* Aliased as [[Enumerable#select]].
**/
function findAll(iterator, context) {
var results = [];
this.each(function(value, index) {
@ -70,6 +119,12 @@ var Enumerable = (function() {
return results;
}
/**
* Enumerable#grep(regex[, iterator = Prototype.K[, context]]) -> Array
*
* Returns all the elements that match the filter. If an iterator is provided,
* it is used to produce the returned value for each selected element.
**/
function grep(filter, iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
@ -84,6 +139,12 @@ var Enumerable = (function() {
return results;
}
/** alias of: Enumerable#member
* Enumerable#include(object) -> Boolean
*
* Determines whether a given object is in the Enumerable or not,
* based on the `==` comparison operator. Aliased as [[Enumerable#member]].
**/
function include(object) {
if (Object.isFunction(this.indexOf))
if (this.indexOf(object) != -1) return true;
@ -98,6 +159,12 @@ var Enumerable = (function() {
return found;
}
/**
* Enumerable#inGroupsOf(size[, filler = null]) -> [group...]
*
* Groups items in fixed-size chunks, using a specific value to fill up
* the last chunk if necessary.
**/
function inGroupsOf(number, fillWith) {
fillWith = Object.isUndefined(fillWith) ? null : fillWith;
return this.eachSlice(number, function(slice) {
@ -106,6 +173,13 @@ var Enumerable = (function() {
});
}
/**
* Enumerable#inject(accumulator, iterator[, context]) -> accumulatedValue
*
* Incrementally builds a result value based on the successive results
* of the iterator.
* This can be used for array construction, numerical sums/averages, etc.
**/
function inject(memo, iterator, context) {
this.each(function(value, index) {
memo = iterator.call(context, memo, value, index);
@ -113,6 +187,12 @@ var Enumerable = (function() {
return memo;
}
/**
* Enumerable#invoke(methodName[, arg...]) -> Array
*
* Invokes the same method, with the same arguments, for all items in a collection.
* Returns the results of the method calls.
**/
function invoke(method) {
var args = $A(arguments).slice(1);
return this.map(function(value) {
@ -120,6 +200,14 @@ var Enumerable = (function() {
});
}
/**
* Enumerable#max([iterator = Prototype.K[, context]]) -> maxValue
*
* Returns the maximum element (or element-based computation), or undefined if
* the enumeration is empty.
* Elements are either compared directly, or by first applying the iterator
* and comparing returned values.
**/
function max(iterator, context) {
iterator = iterator || Prototype.K;
var result;
@ -131,6 +219,14 @@ var Enumerable = (function() {
return result;
}
/**
* Enumerable#min([iterator = Prototype.K[, context]]) -> minValue
*
* Returns the minimum element (or element-based computation), or undefined if
* the enumeration is empty.
* Elements are either compared directly, or by first applying the iterator
* and comparing returned values.
**/
function min(iterator, context) {
iterator = iterator || Prototype.K;
var result;
@ -142,6 +238,14 @@ var Enumerable = (function() {
return result;
}
/**
* Enumerable#partition([iterator = Prototype.K[, context]]) -> [TrueArray, FalseArray]
*
* Partitions the elements in two groups: those regarded as true, and those
* considered false.
* By default, regular JavaScript boolean equivalence is used, but an iterator
* can be provided, that computes a boolean representation of the elements.
**/
function partition(iterator, context) {
iterator = iterator || Prototype.K;
var trues = [], falses = [];
@ -152,6 +256,12 @@ var Enumerable = (function() {
return [trues, falses];
}
/**
* Enumerable#pluck(propertyName) -> Array
*
* Optimization for a common use-case of collect: fetching the same property
* for all the elements. Returns the property values.
**/
function pluck(property) {
var results = [];
this.each(function(value) {
@ -160,6 +270,11 @@ var Enumerable = (function() {
return results;
}
/**
* Enumerable#reject(iterator[, context]) -> Array
*
* Returns all the elements for which the iterator returned a falsy value.
**/
function reject(iterator, context) {
var results = [];
this.each(function(value, index) {
@ -169,6 +284,12 @@ var Enumerable = (function() {
return results;
}
/**
* Enumerable#sortBy(iterator[, context]) -> Array
*
* Provides a custom-sorted view of the elements based on the criteria computed,
* for each element, by the iterator.
**/
function sortBy(iterator, context) {
return this.map(function(value, index) {
return {
@ -181,10 +302,24 @@ var Enumerable = (function() {
}).pluck('value');
}
/** alias of: Enumerable#entries
* Enumerable#toArray() -> Array
*
* Returns an Array representation of the enumeration.
* Aliased as [[Enumerable#entries]].
**/
function toArray() {
return this.map();
}
/**
* Enumerable#zip(sequence...[, iterator = Prototype.K]) -> Array
*
* Zips together (think of the zip on a pair of trousers) 2+ sequences,
* providing an array of tuples.
* Each tuple contains one value per original sequence.
* Tuples can be converted to something else by applying the optional iterator on them.
**/
function zip() {
var iterator = Prototype.K, args = $A(arguments);
if (Object.isFunction(args.last()))
@ -196,10 +331,20 @@ var Enumerable = (function() {
});
}
/**
* Enumerable#size() -> Number
*
* Returns the size of the enumeration.
**/
function size() {
return this.toArray().length;
}
/** related to: Object.inspect
* Enumerable#inspect() -> String
*
* TODO: Enumerable#inspect
**/
function inspect() {
return '#<Enumerable:' + this.toArray().inspect() + '>';
}

View File

@ -1,3 +1,6 @@
/** section: lang
* Function
**/
Object.extend(Function.prototype, (function() {
var slice = Array.prototype.slice;
@ -12,6 +15,13 @@ Object.extend(Function.prototype, (function() {
return update(array, args);
}
/**
* Function#argumentNames() -> Array
*
* Reads the argument names as stated in the function definition and returns
* the values as an array of strings (or an empty array if the function is
* defined without parameters).
**/
function argumentNames() {
var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
.replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
@ -19,6 +29,13 @@ Object.extend(Function.prototype, (function() {
return names.length == 1 && !names[0] ? [] : names;
}
/**
* Function#bind(object[, args...]) -> Function
* - object (Object): The object to bind to.
*
* Wraps the function in another, locking its execution scope to an object
* specified by `object`.
**/
function bind(context) {
if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
var __method = this, args = slice.call(arguments, 1);
@ -28,6 +45,14 @@ Object.extend(Function.prototype, (function() {
}
}
/** related to: Function#bind
* Function#bindAsEventListener(object[, args...]) -> Function
* - object (Object): The object to bind to.
*
* An event-specific variant of [[Function#bind]] which ensures the function
* will recieve the current event object as the first argument when
* executing.
**/
function bindAsEventListener(context) {
var __method = this, args = slice.call(arguments, 1);
return function(event) {
@ -36,6 +61,15 @@ Object.extend(Function.prototype, (function() {
}
}
/**
* Function#curry(args...) -> Function
* Partially applies the function, returning a function with one or more
* arguments already filled in.
*
* Function#curry works just like [[Function#bind]] without the initial
* scope argument. Use the latter if you need to partially apply a function
* _and_ modify its execution scope at the same time.
**/
function curry() {
if (!arguments.length) return this;
var __method = this, args = slice.call(arguments, 0);
@ -45,6 +79,19 @@ Object.extend(Function.prototype, (function() {
}
}
/**
* Function#delay(seconds[, args...]) -> Number
* - seconds (Number): How long to wait before calling the function.
*
* Schedules the function to run after the specified amount of time, passing
* any arguments given.
*
* Behaves much like `window.setTimeout`. Returns an integer ID that can be
* used to clear the timeout with `window.clearTimeout` before it runs.
*
* To schedule a function to run as soon as the interpreter is idle, use
* [[Function#defer]].
**/
function delay(timeout) {
var __method = this, args = slice.call(arguments, 1);
timeout = timeout * 1000
@ -53,11 +100,33 @@ Object.extend(Function.prototype, (function() {
}, timeout);
}
/**
* Function#defer(args...) -> Number
* Schedules the function to run as soon as the interpreter is idle.
*
* A deferred function will not run immediately; rather, it will run as soon
* as the interpreters call stack is empty.
*
* Behaves much like `window.setTimeout` with a delay set to `0`. Returns an
* ID that can be used to clear the timeout with `window.clearTimeout` before
* it runs.
**/
function defer() {
var args = update([0.01], arguments);
return this.delay.apply(this, args);
}
/**
* Function#wrap(wrapperFunction) -> Function
* - wrapperFunction (Function): The function to act as a wrapper.
*
* Returns a function wrapped around the original function.
*
* `Function#wrap` distills the essence of aspect-oriented programming into
* a single method, letting you easily build on existing functions by
* specifying before and after behavior, transforming the return value, or
* even preventing the original function from being called.
**/
function wrap(wrapper) {
var __method = this;
return function() {
@ -66,6 +135,13 @@ Object.extend(Function.prototype, (function() {
}
}
/**
* Function#methodize() -> Function
* Wraps the function inside another function that, at call time, pushes
* `this` to the original function as the first argument.
*
* Used to define both a generic method and an instance method.
**/
function methodize() {
if (this._methodized) return this._methodized;
var __method = this;

View File

@ -1,8 +1,23 @@
/** section: lang
* $H([object]) -> Hash
*
* Creates a Hash (which is synonymous to map or associative array for our purposes).
* A convenience wrapper around the Hash constructor, with a safeguard that lets you pass
* an existing Hash object and get it back untouched (instead of uselessly cloning it).
**/
function $H(object) {
return new Hash(object);
};
/** section: lang
* class Hash
**/
var Hash = Class.create(Enumerable, (function() {
/**
* new Hash([object])
*
* TODO: new Hash
**/
function initialize(object) {
this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
}
@ -16,34 +31,69 @@ var Hash = Class.create(Enumerable, (function() {
}
}
/**
* Hash#set(key, value) -> value
*
* Sets the hashs `key` property to value and returns value.
**/
function set(key, value) {
return this._object[key] = value;
}
/**
* Hash#get(key) -> value
*
* Returns the value of the hashs `key` property.
**/
function get(key) {
// simulating poorly supported hasOwnProperty
if (this._object[key] !== Object.prototype[key])
return this._object[key];
}
/**
* Hash#unset(key) -> value
*
* Deletes the hashs `key` property and returns its value.
**/
function unset(key) {
var value = this._object[key];
delete this._object[key];
return value;
}
/**
* Hash#toObject() -> Object
*
* Returns a cloned, vanilla object.
**/
function toObject() {
return Object.clone(this._object);
}
/**
* Hash#keys() -> [String...]
*
* Provides an Array of keys (that is, property names) for the hash.
**/
function keys() {
return this.pluck('key');
}
/**
* Hash#values() -> Array
*
* Collect the values of a hash and returns them in an array.
**/
function values() {
return this.pluck('value');
}
/**
* Hash#index(value) -> String
*
* TODO: Hash#index
**/
function index(value) {
var match = this.detect(function(pair) {
return pair.value === value;
@ -51,10 +101,23 @@ var Hash = Class.create(Enumerable, (function() {
return match && match.key;
}
/**
* Hash#merge(object) -> Hash
*
* Merges `object` to hash and returns the result of that merge.
* Prior to v1.6.0: This was destructive (object's values were added to hash).
* Since v1.6.0: This is no longer destructive (hash is cloned before the operation).
**/
function merge(object) {
return this.clone().update(object);
}
/**
* Hash#update(object) -> Hash
*
* Updates hash with the key/value pairs of `object`.
* The original hash will be modified.
**/
function update(object) {
return new Hash(object).inject(this, function(result, pair) {
result.set(pair.key, pair.value);
@ -62,11 +125,21 @@ var Hash = Class.create(Enumerable, (function() {
});
}
/**
* Hash#toQueryPair(key, value) -> String
*
* TODO: Hash#toQueryPair
**/
function toQueryPair(key, value) {
if (Object.isUndefined(value)) return key;
return key + '=' + encodeURIComponent(String.interpret(value));
}
/** related to: String#toQueryParams
* Hash#toQueryString() -> String
*
* Turns a hash into its URL-encoded query string representation.
**/
function toQueryString() {
return this.inject([], function(results, pair) {
var key = encodeURIComponent(pair.key), values = pair.value;
@ -79,16 +152,31 @@ var Hash = Class.create(Enumerable, (function() {
}).join('&');
}
/** related to: Object.inspect
* Hash#inspect() -> String
*
* Returns the debug-oriented string representation of the hash.
**/
function inspect() {
return '#<Hash:{' + this.map(function(pair) {
return pair.map(Object.inspect).join(': ');
}).join(', ') + '}>';
}
/** related to: Object.toJSON
* Hash#toJSON() -> String
*
* Returns a JSON string.
**/
function toJSON() {
return Object.toJSON(this.toObject());
}
/**
* Hash#clone() -> newHash
*
* Returns a clone of hash.
**/
function clone() {
return new Hash(this);
}

View File

@ -1,38 +1,91 @@
/** section: lang
* Number
**/
Object.extend(Number.prototype, (function() {
/**
* Number#toColorPart() -> String
*
* Produces a 2-digit hexadecimal representation of the number
* (which is therefore assumed to be in the [0..255] range).
* Useful for composing CSS color strings.
**/
function toColorPart() {
return this.toPaddedString(2, 16);
}
/**
* Number#succ() -> Number
*
* Returns the successor of the current Number, as defined by current + 1.
* Used to make numbers compatible with ObjectRange.
**/
function succ() {
return this + 1;
}
/**
* Number#times(iterator) -> Number
*
* Encapsulates a regular [0..n[ loop, Ruby-style.
**/
function times(iterator, context) {
$R(0, this, true).each(iterator, context);
return this;
}
/**
* Number#toPaddedString(length[, radix]) -> String
*
* Converts the number into a string padded with 0s so that the strings length
* is at least equal to `length`.
* Takes an optional `radix` argument which specifies the base to use for conversion.
**/
function toPaddedString(length, radix) {
var string = this.toString(radix || 10);
return '0'.times(length - string.length) + string;
}
/** related to: Object.toJSON
* Number#toJSON() -> String
*
* Returns a JSON string.
**/
function toJSON() {
return isFinite(this) ? this.toString() : 'null';
}
/**
* Number#abs() -> Number
*
* Returns the absolute value of the number.
**/
function abs() {
return Math.abs(this);
}
/**
* Number#round() -> Number
*
* Rounds the number to the nearest integer.
**/
function round() {
return Math.round(this);
}
/**
* Number#ceil() -> Number
*
* Returns the smallest integer greater than or equal to the number.
**/
function ceil() {
return Math.ceil(this);
}
/**
* Number#floor() -> Number
*
* Returns the largest integer less than or equal to the number.
**/
function floor() {
return Math.floor(this);
}

View File

@ -1,3 +1,6 @@
/** section: lang
* Object
**/
(function() {
function getClass(object) {
@ -5,12 +8,36 @@
.match(/^\[object\s(.*)\]$/)[1];
}
/**
* Object.extend(destination, source) -> Object
* - destination (Object): The object to receive the new properties.
* - source (Object): The object whose properties will be duplicated.
*
* Copies all properties from the source to the destination object. Returns
* the destination object.
**/
function extend(destination, source) {
for (var property in source)
destination[property] = source[property];
return destination;
}
/**
* Object.inspect(object) -> String
* - object (Object): The item to be inspected.
*
* Returns the debug-oriented string representation of the object.
*
* `undefined` and `null` are represented as such.
*
* Other types are checked for a `inspect` method. If there is one, it is
* used; otherwise, it reverts to the `toString` method.
*
* Prototype provides `inspect` methods for many types, both built-in and
* library-defined among them `String`, `Array`, `Enumerable` and `Hash`.
* These attempt to provide useful string representations (from a
* developers standpoint) for their respective types.
**/
function inspect(object) {
try {
if (isUndefined(object)) return 'undefined';
@ -22,6 +49,19 @@
}
}
/**
* Object.toJSON(object) -> String
* - object (Object): The object to be serialized.
*
* Returns a JSON string.
*
* `undefined` and `function` types have no JSON representation. `boolean`
* and `null` are coerced to strings.
*
* For other types, `Object.toJSON` looks for a `toJSON` method on `object`.
* If there is one, it is used; otherwise the object is treated like a
* generic `Object`.
**/
function toJSON(object) {
var type = typeof object;
switch (type) {
@ -45,14 +85,53 @@
return '{' + results.join(', ') + '}';
}
/**
* Object.toQueryString(object) -> String
* object (Object): The object whose property/value pairs will be converted.
*
* Turns an object into its URL-encoded query string representation.
*
* This is a form of serialization, and is mostly useful to provide complex
* parameter sets for stuff such as objects in the Ajax namespace (e.g.
* [[Ajax.Request]]).
*
* Undefined-value pairs will be serialized as if empty-valued. Array-valued
* pairs will get serialized with one name/value pair per array element. All
* values get URI-encoded using JavaScripts native `encodeURIComponent`
* function.
*
* The order of pairs in the serialized form is not guaranteed (and mostly
* irrelevant anyway) except for array-based parts, which are serialized
* in array order.
**/
function toQueryString(object) {
return $H(object).toQueryString();
}
/**
* Object.toHTML(object) -> String
* - object (Object): The object to convert to HTML.
*
* Converts the object to its HTML representation.
*
* Returns the return value of `object`s `toHTML` method if it exists; else
* runs `object` through [[String.interpret]].
**/
function toHTML(object) {
return object && object.toHTML ? object.toHTML() : String.interpret(object);
}
/**
* Object.keys(object) -> Array
* - object (Object): The object to pull keys from.
*
* Returns an array of the object's property names.
*
* Note that the order of the resulting array is browser-dependent it
* relies on the `for&#8230;in` loop, for which the ECMAScript spec does not
* prescribe an enumeration order. Sort the resulting array if you wish to
* normalize the order of the object keys.
**/
function keys(object) {
var results = [];
for (var property in object)
@ -60,6 +139,19 @@
return results;
}
/**
* Object.values(object) -> Array
* - object (Object): The object to pull values from.
*
* Returns an array of the object's values.
*
* Note that the order of the resulting array is browser-dependent it
* relies on the `for&#8230;in` loop, for which the ECMAScript spec does not
* prescribe an enumeration order.
*
* Also, remember that while property _names_ are unique, property _values_
* have no such constraint.
**/
function values(object) {
var results = [];
for (var property in object)
@ -67,34 +159,89 @@
return results;
}
/**
* Object.clone(object) -> Object
* - object (Object): The object to clone.
*
* Duplicates the passed object.
*
* Copies all the original's key/value pairs onto an empty object.
*
* Do note that this is a _shallow_ copy, not a _deep_ copy. Nested objects
* will retain their references.
**/
function clone(object) {
return extend({ }, object);
}
/**
* Object.isElement(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is a DOM node of type 1; `false` otherwise.
**/
function isElement(object) {
return !!(object && object.nodeType == 1);
}
/**
* Object.isArray(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is an array; false otherwise.
**/
function isArray(object) {
return getClass(object) === "Array";
}
/**
* Object.isHash(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is an instance of the [[Hash]] class; `false`
* otherwise.
**/
function isHash(object) {
return object instanceof Hash;
}
/**
* Object.isFunction(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is of type `function`; `false` otherwise.
**/
function isFunction(object) {
return typeof object === "function";
}
/**
* Object.isString(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is of type `string`; `false` otherwise.
**/
function isString(object) {
return getClass(object) === "String";
}
/**
* Object.isNumber(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is of type `number`; `false` otherwise.
**/
function isNumber(object) {
return getClass(object) === "Number";
}
/**
* Object.isUndefined(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is of type `string`; `false` otherwise.
**/
function isUndefined(object) {
return typeof object === "undefined";
}

View File

@ -1,4 +1,26 @@
/** section: lang
* class PeriodicalExecuter
**/
var PeriodicalExecuter = Class.create({
/**
* new PeriodicalExecuter(callback, frequency)
* - callback (Function): the function to be executed at each interval.
* - frequency (Number): the amount of time, in sections, to wait in between
* callbacks.
*
* Creates an object that oversees the calling of a particular function via
* `window.setInterval`.
*
* The only notable advantage provided by `PeriodicalExecuter` is that it
* shields you against multiple parallel executions of the `callback`
* function, should it take longer than the given interval to execute (it
* maintains an internal running flag, which is shielded against
* exceptions in the callback function).
*
* This is especially useful if you use one to interact with the user at
* given intervals (e.g. use a prompt or confirm call): this will avoid
* multiple message boxes all waiting to be actioned.
**/
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
@ -15,6 +37,11 @@ var PeriodicalExecuter = Class.create({
this.callback(this);
},
/**
* PeriodicalExecuter#stop() -> undefined
*
* Stops the periodical executer (there will be no further triggers).
**/
stop: function() {
if (!this.timer) return;
clearInterval(this.timer);

View File

@ -1,8 +1,24 @@
/** section: lang
* class ObjectRange
**/
/**
* $R(start, end[, exclusive = false]) -> ObjectRange
*
* Creates a new ObjectRange object.
* This method is a convenience wrapper around the ObjectRange constructor,
* but $R is the preferred alias.
**/
function $R(start, end, exclusive) {
return new ObjectRange(start, end, exclusive);
}
var ObjectRange = Class.create(Enumerable, (function() {
/**
* new ObjectRange(start, end[, exclusive = false])
*
* TODO: new ObjectRange
**/
function initialize(start, end, exclusive) {
this.start = start;
this.end = end;
@ -17,6 +33,11 @@ var ObjectRange = Class.create(Enumerable, (function() {
}
}
/**
* ObjectRange#include(value) -> Boolean
*
* Determines whether the value is included in the range.
**/
function include(value) {
if (value < this.start)
return false;

View File

@ -1,5 +1,23 @@
/** section: lang
* RegExp
**/
/** alias of: RegExp#test
* RegExp#match(str) -> Boolean
*
* Return true if string matches the regular expression, false otherwise.
**/
RegExp.prototype.match = RegExp.prototype.test;
/**
* RegExp.escape(str) -> String
* - str (String): A string intended to be used in a `RegExp` constructor.
*
* Escapes any characters in the string that have special meaning in a
* regular expression.
*
* Use before passing a string into the `RegExp` constructor.
**/
RegExp.escape = function(str) {
return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

View File

@ -1,4 +1,12 @@
/** section: lang
* String
**/
Object.extend(String, {
/**
* String.interpret(value) -> String
*
* TODO: String.interpret
**/
interpret: function(value) {
return value == null ? '' : String(value);
},
@ -20,6 +28,13 @@ Object.extend(String.prototype, (function() {
return function(match) { return template.evaluate(match) };
}
/**
* String#gsub(pattern, replacement) -> String
*
* Returns the string with every occurence of a given pattern replaced by either
* a regular string, the returned value of a function or a [[Template]] string.
* The pattern can be a string or a regular expression.
**/
function gsub(pattern, replacement) {
var result = '', source = this, match;
replacement = prepareReplacement(replacement);
@ -36,6 +51,13 @@ Object.extend(String.prototype, (function() {
return result;
}
/**
* String#sub(pattern, replacement[, count = 1]) -> String
*
* Returns a string with the first count occurrences of pattern replaced by either
* a regular string, the returned value of a function or a [[Template]] string.
* The pattern can be a string or a regular expression.
**/
function sub(pattern, replacement, count) {
replacement = prepareReplacement(replacement);
count = Object.isUndefined(count) ? 1 : count;
@ -46,11 +68,24 @@ Object.extend(String.prototype, (function() {
});
}
/** related to: String#gsub
* String#scan(pattern, iterator) -> String
*
* Allows iterating over every occurrence of the given pattern (which can be a
* string or a regular expression).
* Returns the original string.
**/
function scan(pattern, iterator) {
this.gsub(pattern, iterator);
return String(this);
}
/**
* String#truncate([length = 30[, suffix = '...']]) -> String
*
* Truncates a string to given `length` and appends `suffix` to it (indicating
* that it is only an excerpt).
**/
function truncate(length, truncation) {
length = length || 30;
truncation = Object.isUndefined(truncation) ? '...' : truncation;
@ -58,18 +93,39 @@ Object.extend(String.prototype, (function() {
this.slice(0, length - truncation.length) + truncation : String(this);
}
/**
* String#strip() -> String
*
* Strips all leading and trailing whitespace from a string.
**/
function strip() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
}
/**
* String#stripTags() -> String
*
* Strips a string of any HTML tag.
**/
function stripTags() {
return this.replace(/<\/?[^>]+>/gi, '');
}
/**
* String#stripScripts() -> String
*
* Strips a string of anything that looks like an HTML script block.
**/
function stripScripts() {
return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
}
/**
* String#extractScripts() -> Array
*
* Exctracts the content of any script block present in the string and returns
* them as an array of strings.
**/
function extractScripts() {
var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
@ -78,15 +134,31 @@ Object.extend(String.prototype, (function() {
});
}
/**
* String#evalScripts() -> Array
*
* Evaluates the content of any script block present in the string.
* Returns an array containing the value returned by each script.
**/
function evalScripts() {
return this.extractScripts().map(function(script) { return eval(script) });
}
/**
* String#escapeHTML() -> String
*
* Converts HTML special characters to their entity equivalents.
**/
function escapeHTML() {
escapeHTML.text.data = this;
return escapeHTML.div.innerHTML;
}
/** related to: String#escapeHTML
* String#unescapeHTML() -> String
*
* Strips tags and converts the entity forms of special HTML characters to their normal form.
**/
function unescapeHTML() {
var div = new Element('div');
div.innerHTML = this.stripTags();
@ -95,6 +167,11 @@ Object.extend(String.prototype, (function() {
div.childNodes[0].nodeValue) : '';
}
/** alias of: String#parseQuery, related to: Hash#toQueryString
* String#toQueryParams([separator = '&']) -> Object
*
* Parses a URI-like query string and returns an object composed of parameter/value pairs.
**/
function toQueryParams(separator) {
var match = this.strip().match(/([^?#]*)(#.*)?$/);
if (!match) return { };
@ -115,19 +192,42 @@ Object.extend(String.prototype, (function() {
});
}
/**
* String#toArray() -> Array
*
* Splits the string character-by-character and returns an array with the result.
**/
function toArray() {
return this.split('');
}
/**
* String#succ() -> String
*
* Used internally by ObjectRange.
* Converts the last character of the string to the following character in the
* Unicode alphabet.
**/
function succ() {
return this.slice(0, this.length - 1) +
String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
}
/**
* String#times(count) -> String
*
* Concatenates the string `count` times.
**/
function times(count) {
return count < 1 ? '' : new Array(count + 1).join(this);
}
/**
* String#camelize() -> String
*
* Converts a string separated by dashes into a camelCase equivalent.
* For instance, 'foo-bar' would be converted to 'fooBar'.
**/
function camelize() {
var parts = this.split('-'), len = parts.length;
if (len == 1) return parts[0];
@ -142,18 +242,39 @@ Object.extend(String.prototype, (function() {
return camelized;
}
/**
* String#capitalize() -> String
*
* Capitalizes the first letter of a string and downcases all the others.
**/
function capitalize() {
return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
}
/**
* String#underscore() -> String
*
* Converts a camelized string into a series of words separated by an underscore ("_").
**/
function underscore() {
return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
}
/**
* String#dasherize() -> String
*
* Replaces every instance of the underscore character ("_") by a dash ("-").
**/
function dasherize() {
return this.gsub(/_/,'-');
}
/** related to: Object.inspect
* String#inspect([useDoubleQuotes = false]) -> String
*
* Returns a debug-oriented version of the string (i.e. wrapped in single or
* double quotes, with backslashes and quotes escaped).
**/
function inspect(useDoubleQuotes) {
var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
var character = String.specialChar[match[0]];
@ -163,14 +284,31 @@ Object.extend(String.prototype, (function() {
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
}
/** related to: Object.toJSON
* String#toJSON() -> String
*
* Returns a JSON string.
**/
function toJSON() {
return this.inspect(true);
}
/**
* String#unfilterJSON([filter = Prototype.JSONFilter]) -> String
*
* Strips comment delimiters around Ajax JSON or JavaScript responses.
* This security method is called internally.
**/
function unfilterJSON(filter) {
return this.sub(filter || Prototype.JSONFilter, '#{1}');
}
/**
* String#isJSON() -> Boolean
*
* Check if the string is valid JSON by the use of regular expressions.
* This security method is called internally.
**/
function isJSON() {
var str = this;
if (str.blank()) return false;
@ -178,6 +316,13 @@ Object.extend(String.prototype, (function() {
return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
}
/**
* String#evalJSON([sanitize = false]) -> object
*
* Evaluates the JSON in the string and returns the resulting object.
* If the optional sanitize parameter is set to true, the string is checked for
* possible malicious attempts and eval is not called if one is detected.
**/
function evalJSON(sanitize) {
var json = this.unfilterJSON();
try {
@ -186,27 +331,57 @@ Object.extend(String.prototype, (function() {
throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
}
/**
* String#include(substring) -> Boolean
*
* Check if the string contains a substring.
**/
function include(pattern) {
return this.indexOf(pattern) > -1;
}
/**
* String#startsWith(substring) -> Boolean
*
* Checks if the string starts with substring.
**/
function startsWith(pattern) {
return this.indexOf(pattern) === 0;
}
/**
* String#endsWith(substring) -> Boolean
*
* Checks if the string ends with substring.
**/
function endsWith(pattern) {
var d = this.length - pattern.length;
return d >= 0 && this.lastIndexOf(pattern) === d;
}
/**
* String#empty() -> Boolean
*
* Checks if the string is empty.
**/
function empty() {
return this == '';
}
/**
* String#blank() -> Boolean
*
* Check if the string is 'blank', meaning either empty or containing only whitespace.
**/
function blank() {
return /^\s*$/.test(this);
}
/**
* String#interpolate(object[, pattern]) -> String
*
* Treats the string as a Template and fills it with objects properties.
**/
function interpolate(object, pattern) {
return new Template(this, pattern).evaluate(object);
}

View File

@ -1,9 +1,23 @@
/** section: lang
* class Template
**/
var Template = Class.create({
/**
* new Template(template[, pattern = Template.Pattern])
*
* TODO: new Template
**/
initialize: function(template, pattern) {
this.template = template.toString();
this.pattern = pattern || Template.Pattern;
},
/**
* Template#evaluate(object) -> String
*
* Applies the template to given `object`s data, producing a formatted string
* with symbols replaced by corresponding objects properties.
**/
evaluate: function(object) {
if (Object.isFunction(object.toTemplateReplacements))
object = object.toTemplateReplacements();

15
src/prototype.js vendored
View File

@ -35,6 +35,21 @@ if (Prototype.Browser.MobileSafari)
var Abstract = { };
/**
* == lang ==
* Language extensions.
**/
/** section: lang
* Try
**/
/**
* Try.these(function...) -> ?
* - function (Function): A function that may throw an exception.
* Accepts an arbitrary number of functions and returns the result of the
* first one that doesn't throw an error.
**/
var Try = {
these: function() {
var returnValue;