diff --git a/js-classes/Array.js b/js-classes/Array.js index 040d84a2e71f511310d4e8a258d5fd30f60ba7a9..c6afbdfb6b6fd79643d3f97f146d2291153288e5 100644 --- a/js-classes/Array.js +++ b/js-classes/Array.js @@ -558,4 +558,497 @@ * value or when an array is referred to in a string concatenation. * * @return {String} The array as a string. - */ \ No newline at end of file + */ + +// ECMAScript 5 methods + +/** + * @method isArray + * @static + * Returns true if an object is an array, false if it is not. + * + * // all following calls return true + * Array.isArray([]); + * Array.isArray([1]); + * Array.isArray( new Array() ); + * Array.isArray( Array.prototype ); // Little known fact: Array.prototype itself is an array. + * + * // all following calls return false + * Array.isArray(); + * Array.isArray({}); + * Array.isArray(null); + * Array.isArray(undefined); + * Array.isArray(17); + * Array.isArray("Array"); + * Array.isArray(true); + * Array.isArray(false); + * Array.isArray({ __proto__ : Array.prototype }); + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Mixed} obj The object to be checked. + * @return {Boolean} True when Array. + */ + +/** + * @method indexOf + * Returns the first index at which a given element can be found in the array, or -1 if it is not present. + * + * `indexOf` compares `searchElement` to elements of the Array using strict equality (the same method used + * by the `===`, or triple-equals, operator). + * + * var array = [2, 5, 9]; + * var index = array.indexOf(2); + * // index is 0 + * index = array.indexOf(7); + * // index is -1 + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Mixed} searchElement Element to locate in the array. + * @param {Number} [fromIndex] The index at which to begin the search. Defaults to 0, i.e. the whole array + * will be searched. If the index is greater than or equal to the length of the array, -1 is returned, i.e. + * the array will not be searched. If negative, it is taken as the offset from the end of the array. Note + * that even when the index is negative, the array is still searched from front to back. If the calculated + * index is less than 0, the whole array will be searched. + * @return {Number} The index of element found or -1. + */ + +/** + * @method lastIndexOf + * Returns the last index at which a given element can be found in the array, or -1 if it is not present. + * The array is searched backwards, starting at `fromIndex`. + * + * `lastIndexOf` compares `searchElement` to elements of the Array using strict equality (the same method + * used by the `===`, or triple-equals, operator). + * + * var array = [2, 5, 9, 2]; + * var index = array.lastIndexOf(2); + * // index is 3 + * index = array.lastIndexOf(7); + * // index is -1 + * index = array.lastIndexOf(2, 3); + * // index is 3 + * index = array.lastIndexOf(2, 2); + * // index is 0 + * index = array.lastIndexOf(2, -2); + * // index is 0 + * index = array.lastIndexOf(2, -1); + * // index is 3 + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Mixed} searchElement Element to locate in the array. + * @param {Number} [fromIndex] The index at which to start searching backwards. Defaults to the array's + * length, i.e. the whole array will be searched. If the index is greater than or equal to the length of + * the array, the whole array will be searched. If negative, it is taken as the offset from the end of the + * array. Note that even when the index is negative, the array is still searched from back to front. If + * the calculated index is less than 0, -1 is returned, i.e. the array will not be searched. + * @return {Number} The index of element found or -1. + */ + +/** + * @method forEach + * Executes a provided function once per array element. + * + * `forEach` executes the provided function (`callback`) once for each element present in the array. `callback` + * is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which + * have been deleted or which have never been assigned values. + * + * If a `thisArg` parameter is provided to `forEach`, it will be used as the `this` value for each `callback` + * invocation as if `callback.call(thisArg, element, index, array)` was called. If `thisArg` is `undefined` or + * `null`, the `this` value within the function depends on whether the function is in strict mode or not + * (passed value if in strict mode, global object if in non-strict mode). + * + * The `range` of elements processed by `forEach` is set before the first invocation of `callback`. Elements + * which are appended to the array after the call to `forEach` begins will not be visited by `callback`. If + * existing elements of the array are changed, or deleted, their value as passed to callback will be the + * value at the time `forEach` visits them; elements that are deleted are not visited. + * + * The following code logs a line for each element in an array: + * + * function logArrayElements(element, index, array) { + * console.log("a[" + index + "] = " + element); + * } + * [2, 5, 9].forEach(logArrayElements); + * // logs: + * // a[0] = 2 + * // a[1] = 5 + * // a[2] = 9 + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Function} callback Function to execute for each element. + * @param {Mixed} callback.value The element value. + * @param {Number} callback.index The element index. + * @param {Array} callback.array The array being traversed. + * @param {Object} [thisArg] Object to use as `this` when executing `callback`. + */ + +/** + * @method every + * Tests whether all elements in the array pass the test implemented + * by the provided function. + * + * `every` executes the provided `callback` function once for each element + * present in the array until it finds one where `callback` returns a + * false value. If such an element is found, the `every` method + * immediately returns false. Otherwise, if `callback` returned a true + * value for all elements, `every` will return true. `callback` is invoked + * only for indexes of the array which have assigned values; it is not + * invoked for indexes which have been deleted or which have never + * been assigned values. + * + * If a `thisObject` parameter is provided to `every`, it will be used as + * the `this` for each invocation of the callback. If it is not + * provided, or is `null`, the global object associated with callback is + * used instead. + * + * `every` does not mutate the array on which it is called. + * + * The range of elements processed by `every` is set before the first + * invocation of callback. Elements which are appended to the array + * after the call to every begins will not be visited by `callback`. If + * existing elements of the array are changed, their value as passed + * to `callback` will be the value at the time `every` visits them; + * elements that are deleted are not visited. + * + * `every` acts like the "for all" quantifier in mathematics. In + * particular, for an empty array, it returns true. (It is vacuously + * true that all elements of the empty set satisfy any given + * condition.) + * + * The following example tests whether all elements in the array are + * bigger than 10. + * + * function isBigEnough(element, index, array) { + * return (element >= 10); + * } + * var passed = [12, 5, 8, 130, 44].every(isBigEnough); + * // passed is false + * passed = [12, 54, 18, 130, 44].every(isBigEnough); + * // passed is true + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Function} callback Function to test for each element. + * @param {Mixed} callback.value The element value. + * @param {Number} callback.index The element index. + * @param {Array} callback.array The array being traversed. + * @param {Boolean} callback.return Should return true when element passes the test. + * @param {Object} [thisObject] Object to use as `this` when executing `callback`. + * @return {Boolean} True when all elements pass the test. + */ + +/** + * @method some + * Tests whether some element in the array passes the test implemented + * by the provided function. + * + * `some` executes the `callback` function once for each element + * present in the array until it finds one where `callback` returns a + * true value. If such an element is found, some immediately returns + * true. Otherwise, some returns false. `callback` is invoked only for + * indexes of the array which have assigned values; it is not invoked + * for indexes which have been deleted or which have never been + * assigned values. + * + * If a `thisObject` parameter is provided to some, it will be used as + * the `this` for each invocation of the `callback`. If it is not + * provided, or is `null`, the global object associated with callback is + * used instead. + * + * `some` does not mutate the array on which it is called. + * + * The range of elements processed by `some` is set before the first + * invocation of callback. Elements that are appended to the array + * after the call to some begins will not be visited by `callback`. If + * an existing, unvisited element of the array is changed by `callback`, + * its value passed to the visiting callback will be the value at the + * time that `some` visits that element's index; elements that are + * deleted are not visited. + * + * The following example tests whether some element in the array is + * bigger than 10. + * + * function isBigEnough(element, index, array) { + * return (element >= 10); + * } + * var passed = [2, 5, 8, 1, 4].some(isBigEnough); + * // passed is false + * passed = [12, 5, 8, 1, 4].some(isBigEnough); + * // passed is true + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Function} callback Function to test for each element. + * @param {Mixed} callback.value The element value. + * @param {Number} callback.index The element index. + * @param {Array} callback.array The array being traversed. + * @param {Boolean} callback.return Should return true when element passes the test. + * @param {Object} [thisObject] Object to use as `this` when executing `callback`. + * @return {Boolean} True when at least one element passes the test. + */ + +/** + * @method filter + * Creates a new array with all elements that pass the test + * implemented by the provided function. + * + * `filter` calls a provided `callback` function once for each element in + * an array, and constructs a new array of all the values for which + * `callback` returns a true value. `callback` is invoked only for indexes + * of the array which have assigned values; it is not invoked for + * indexes which have been deleted or which have never been assigned + * values. Array elements which do not pass the `callback` test are + * simply skipped, and are not included in the new array. + * + * If a `thisObject` parameter is provided to `filter`, it will be + * used as the `this` for each invocation of the `callback`. If it is not + * provided, or is `null`, the global object associated with callback is + * used instead. + * + * `filter` does not mutate the array on which it is called. + * + * The range of elements processed by `filter` is set before the first + * invocation of `callback`. Elements which are appended to the array + * after the call to `filter` begins will not be visited by `callback`. If + * existing elements of the array are changed, or deleted, their value + * as passed to `callback` will be the value at the time `filter` visits + * them; elements that are deleted are not visited. + * + * The following example uses filter to create a filtered array that + * has all elements with values less than 10 removed. + * + * function isBigEnough(element, index, array) { + * return (element >= 10); + * } + * var filtered = [12, 5, 8, 130, 44].filter(isBigEnough); + * // filtered is [12, 130, 44] + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Function} callback Function to test for each element. + * @param {Mixed} callback.value The element value. + * @param {Number} callback.index The element index. + * @param {Array} callback.array The array being traversed. + * @param {Boolean} callback.return Should return true when element passes the test. + * @param {Object} [thisObject] Object to use as `this` when executing `callback`. + * @return {Array} Array of elements that passed the test. + */ + +/** + * @method map + * Creates a new array with the results of calling a provided function + * on every element in this array. + * + * `map` calls a provided `callback` function once for each element in + * an array, in order, and constructs a new array from the + * results. `callback` is invoked only for indexes of the array which + * have assigned values; it is not invoked for indexes which have been + * deleted or which have never been assigned values. + * + * If a `thisArg` parameter is provided to map, it will be used as the + * `this` for each invocation of the `callback`. If it is not provided, or + * is `null`, the global object associated with callback is used + * instead. + * + * `map` does not mutate the array on which it is called. + * + * The range of elements processed by `map` is set before the first + * invocation of `callback`. Elements which are appended to the array + * after the call to `map` begins will not be visited by `callback`. If + * existing elements of the array are changed, or deleted, their value + * as passed to `callback` will be the value at the time `map` visits + * them; elements that are deleted are not visited. + * + * The following code creates an array of "plural" forms of nouns from + * an array of their singular forms. + * + * function fuzzyPlural(single) { + * var result = single.replace(/o/g, 'e'); + * if( single === 'kangaroo'){ + * result += 'se'; + * } + * return result; + * } + * + * var words = ["foot", "goose", "moose", "kangaroo"]; + * console.log(words.map(fuzzyPlural)); + * + * // ["feet", "geese", "meese", "kangareese"] + * + * The following code takes an array of numbers and creates a new + * array containing the square roots of the numbers in the first + * array. + * + * var numbers = [1, 4, 9]; + * var roots = numbers.map(Math.sqrt); + * // roots is now [1, 2, 3], numbers is still [1, 4, 9] + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Function} callback Function that produces an element of the new Array + * from an element of the current one. + * @param {Mixed} callback.value The element value. + * @param {Number} callback.index The element index. + * @param {Array} callback.array The array being traversed. + * @param {Boolean} callback.return Should return true when element passes the test. + * @param {Object} [thisObject] Object to use as `this` when executing `callback`. + * @return {Array} Array of the return values of `callback` function. + */ + +/** + * @method reduce + * Applies a function against an accumulator and each value of the + * array (from left-to-right) as to reduce it to a single value. + * + * `reduce` executes the `callback` function once for each element + * present in the array, excluding holes in the array. + * + * The first time the `callback` is called, `previousValue` and + * `currentValue` can be one of two values. If `initialValue` is + * provided in the call to `reduce`, then `previousValue` will be equal to + * `initialValue` and `currentValue` will be equal to the first value in + * the array. If no `initialValue` was provided, then `previousValue` will + * be equal to the first value in the array and `currentValue` will be + * equal to the second. + * + * Suppose the following use of reduce occurred: + * + * [0,1,2,3,4].reduce(function(previousValue, currentValue, index, array){ + * return previousValue + currentValue; + * }); + * + * The callback would be invoked four times, with the arguments and + * return values in each call being as follows: + * + * | | previousValue | currentValue | index | array | return value + * |:------------|:--------------|:-------------|:------|:------------|:------------ + * | first call | 0 | 1 | 1 | [0,1,2,3,4] | 1 + * | second call | 1 | 2 | 2 | [0,1,2,3,4] | 3 + * | third call | 3 | 3 | 3 | [0,1,2,3,4] | 6 + * | fourth call | 6 | 4 | 4 | [0,1,2,3,4] | 10 + * + * The value returned by `reduce` would be that of the last callback + * invocation (10). + * + * If you were to provide an initial value as the second argument to + * reduce, the result would look like this: + * + * [0,1,2,3,4].reduce(function(previousValue, currentValue, index, array){ + * return previousValue + currentValue; + * }, 10); + * + * | | previousValue | currentValue | index | array | return value + * |:------------|:--------------|:-------------|:------|:------------|:------------ + * | first call | 10 | 0 | 0 | [0,1,2,3,4] | 10 + * | second call | 10 | 1 | 1 | [0,1,2,3,4] | 11 + * | third call | 11 | 2 | 2 | [0,1,2,3,4] | 13 + * | fourth call | 13 | 3 | 3 | [0,1,2,3,4] | 16 + * | fifth call | 16 | 4 | 4 | [0,1,2,3,4] | 20 + * + * The value returned by `reduce` this time would be, of course, 20. + * + * Example: Sum up all values within an array: + * + * var total = [0, 1, 2, 3].reduce(function(a, b) { + * return a + b; + * }); + * // total == 6 + * + * Example: Flatten an array of arrays: + * + * var flattened = [[0, 1], [2, 3], [4, 5]].reduce(function(a, b) { + * return a.concat(b); + * }); + * // flattened is [0, 1, 2, 3, 4, 5] + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Function} callback Function to execute on each value in the array. + * @param {Mixed} callback.previousValue The value previously returned in the last + * invocation of the `callback`, or `initialValue`, if supplied. + * @param {Mixed} callback.currentValue The current element being processed in the array. + * @param {Number} callback.index The index of the current element being processed in the array. + * @param {Array} callback.array The array `reduce` was called upon. + * @param {Mixed} [initialValue] Object to use as the first argument to the first call + * of the `callback`. + * @return {Mixed} The value returned by final invocation of the `callback`. + */ + +/** + * @method reduceRight + * Applies a function simultaneously against two values of the array + * (from right-to-left) as to reduce it to a single value. + * + * `reduceRight` executes the `callback` function once for each + * element present in the array, excluding holes in the array. + * + * The first time the `callback` is called, `previousValue` and + * `currentValue` can be one of two values. If `initialValue` is + * provided in the call to `reduceRight`, then `previousValue` will be equal to + * `initialValue` and `currentValue` will be equal to the last value in + * the array. If no `initialValue` was provided, then `previousValue` will + * be equal to the last value in the array and `currentValue` will be + * equal to the second-to-last value. + * + * Some example run-throughs of the function would look like this: + * + * [0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) { + * return previousValue + currentValue; + * }); + * + * // First call + * previousValue = 4, currentValue = 3, index = 3 + * + * // Second call + * previousValue = 7, currentValue = 2, index = 2 + * + * // Third call + * previousValue = 9, currentValue = 1, index = 1 + * + * // Fourth call + * previousValue = 10, currentValue = 0, index = 0 + * + * // array is always the object [0,1,2,3,4] upon which reduceRight was called + * + * // Return Value: 10 + * + * And if you were to provide an initialValue, the result would look like this: + * + * [0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) { + * return previousValue + currentValue; + * }, 10); + * + * // First call + * previousValue = 10, currentValue = 4, index = 4 + * + * // Second call + * previousValue = 14, currentValue = 3, index = 3 + * + * // Third call + * previousValue = 17, currentValue = 2, index = 2 + * + * // Fourth call + * previousValue = 19, currentValue = 1, index = 1 + * + * // Fifth call + * previousValue = 20, currentValue = 0, index = 0 + * + * // array is always the object [0,1,2,3,4] upon which reduceRight was called + * + * // Return Value: 20 + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Function} callback Function to execute on each value in the array. + * @param {Mixed} callback.previousValue The value previously returned in the last + * invocation of the `callback`, or `initialValue`, if supplied. + * @param {Mixed} callback.currentValue The current element being processed in the array. + * @param {Number} callback.index The index of the current element being processed in the array. + * @param {Array} callback.array The array `reduceRight` was called upon. + * @param {Mixed} [initialValue] Object to use as the first argument to the first call + * of the `callback`. + * @return {Mixed} The value returned by final invocation of the `callback`. + */ diff --git a/js-classes/Date.js b/js-classes/Date.js index da22606e651cbf2c223faffe0a74a693c37d3683..aa633f572ec7e87fdfeb8a01b05ea50a99203989 100644 --- a/js-classes/Date.js +++ b/js-classes/Date.js @@ -996,4 +996,26 @@ * myVar = x.valueOf(); //assigns -424713600000 to myVar * * @return {Number} Date represented as milliseconds. + */ + +// ECMAScript 5 methods + +/** + * @method toJSON + * Returns a JSON representation of the Date object. + * + * Date instances refer to a specific point in time. Calling `toJSON()` + * returns a JSON formatted string representing the Date object's + * value. This method is generally intended to, by default, usefully + * serialize Date objects during JSON serialization. + * + * var jsonDate = (new Date()).toJSON(); + * var backToDate = new Date(jsonDate); + * + * console.log("Serialized date object: " + jsonDate); + * // Serialized date object: 2013-01-17T12:59:08.449Z + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @return {String} Date value in `YYYY-MM-DDTHH-MM-SS.MMMZ` format. */ \ No newline at end of file diff --git a/js-classes/Function.js b/js-classes/Function.js index bdbf32200c37e00b5b2c5b078500deb20d294639..59823467c6019b3e2c2b847430fb6f740f6e916e 100644 --- a/js-classes/Function.js +++ b/js-classes/Function.js @@ -253,4 +253,84 @@ * argument list, curly braces, and function body. * * @return {String} The function as a string. + */ + +// ECMAScript 5 methods + +/** + * @method bind + * + * Creates a new function that, when called, has its `this` keyword set + * to the provided value, with a given sequence of arguments preceding + * any provided when the new function was called. + * + * The `bind()` function creates a new function (a bound function) with + * the same function body (internal Call attribute in ECMAScript 5 + * terms) as the function it is being called on (the bound function's + * target function) with the `this` value bound to the first argument of + * `bind()`, which cannot be overridden. `bind()` also accepts leading + * default arguments to provide to the target function when the bound + * function is called. A bound function may also be constructed using + * the new operator: doing so acts as though the target function had + * instead been constructed. The provided `this` value is ignored, while + * prepended arguments are provided to the emulated function. + * + * ## Creating a bound function + * + * The simplest use of `bind()` is to make a function that, no matter + * how it is called, is called with a particular `this` value. A common + * mistake for new JavaScript programmers is to extract a method from + * an object, then to later call that function and expect it to use + * the original object as its `this` (e.g. by using that method in + * callback-based code). Without special care, however, the original + * object is usually lost. Creating a bound function from the + * function, using the original object, neatly solves `this` problem: + * + * var x = 9; + * var module = { + * x: 81, + * getX: function() { return this.x; } + * }; + * + * module.getX(); // 81 + * + * var getX = module.getX; + * getX(); // 9, because in this case, "this" refers to the global object + * + * // create a new function with 'this' bound to module + * var boundGetX = getX.bind(module); + * boundGetX(); // 81 + * + * ## Partial functions + * + * The next simplest use of `bind()` is to make a function with + * pre-specified initial arguments. These arguments (if any) follow + * the provided this value and are then inserted at the start of the + * arguments passed to the target function, followed by the arguments + * passed to the bound function, whenever the bound function is + * called. + * + * function list() { + * return Array.prototype.slice.call(arguments); + * } + * + * var list1 = list(1, 2, 3); // [1, 2, 3] + * + * // Create a function with a preset leading argument + * var leadingZeroList = list.bind(undefined, 37); + * + * var list2 = leadingZeroList(); // [37] + * var list3 = leadingZeroList(1, 2, 3); // [37, 1, 2, 3] + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} thisArg The value to be passed as the `this` + * parameter to the target function when the bound function is + * called. The value is ignored if the bound function is constructed + * using the new operator. + * + * @param {Mixed...} [args] Arguments to prepend to arguments provided + * to the bound function when invoking the target function. + * + * @return {Function} The bound function. */ \ No newline at end of file diff --git a/js-classes/Object.js b/js-classes/Object.js index ade164463cc884015107a480a06ff74874d6599e..c4f110b3fbbff7d5b60e8ee28d53aa9810b1ffde 100644 --- a/js-classes/Object.js +++ b/js-classes/Object.js @@ -401,4 +401,642 @@ * types[i] = [types[i].constructor, types[i] instanceof Type, types[i].toString()]; * }; * alert(types.join("\n")); + */ + +// ECMAScript 5 methods + +/** + * @method create + * @static + * Creates a new object with the specified prototype object and properties. + * + * ## Classical inheritance with Object.create + * + * Below is an example of how to use `Object.create` to achieve + * classical inheritance, this is for single inheritance, which is all + * that Javascript supports. + * + * //Shape - superclass + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * Shape.prototype.move = function(x, y) { + * this.x += x; + * this.y += y; + * console.info("Shape moved."); + * }; + * + * // Rectangle - subclass + * function Rectangle() { + * Shape.call(this); //call super constructor. + * } + * + * Rectangle.prototype = Object.create(Shape.prototype); + * + * var rect = new Rectangle(); + * + * rect instanceof Rectangle //true. + * rect instanceof Shape //true. + * + * rect.move(); //Outputs, "Shape moved." + * + * If you wish to inherit from multiple objects, then mixins are a possibility. + * + * function MyClass() { + * SuperClass.call(this); + * OtherSuperClass.call(this); + * } + * + * MyClass.prototype = Object.create(SuperClass.prototype); //inherit + * mixin(MyClass.prototype, OtherSuperClass.prototype); //mixin + * + * MyClass.prototype.myMethod = function() { + * // do a thing + * }; + * + * The mixin function would copy the functions from the superclass + * prototype to the subclass prototype, the mixin function needs to be + * supplied by the user. + * + * ## Using `propertiesObject` argument with Object.create + * + * var o; + * + * // create an object with null as prototype + * o = Object.create(null); + * + * + * o = {}; + * // is equivalent to: + * o = Object.create(Object.prototype); + * + * + * // Example where we create an object with a couple of sample properties. + * // (Note that the second parameter maps keys to *property descriptors*.) + * o = Object.create(Object.prototype, { + * // foo is a regular "value property" + * foo: { writable:true, configurable:true, value: "hello" }, + * // bar is a getter-and-setter (accessor) property + * bar: { + * configurable: false, + * get: function() { return 10 }, + * set: function(value) { console.log("Setting `o.bar` to", value) } + * }}) + * + * + * function Constructor(){} + * o = new Constructor(); + * // is equivalent to: + * o = Object.create(Constructor.prototype); + * // Of course, if there is actual initialization code in the Constructor function, the Object.create cannot reflect it + * + * + * // create a new object whose prototype is a new, empty object + * // and a adding single property 'p', with value 42 + * o = Object.create({}, { p: { value: 42 } }) + * + * // by default properties ARE NOT writable, enumerable or configurable: + * o.p = 24 + * o.p + * //42 + * + * o.q = 12 + * for (var prop in o) { + * console.log(prop) + * } + * //"q" + * + * delete o.p + * //false + * + * //to specify an ES3 property + * o2 = Object.create({}, { p: { value: 42, writable: true, enumerable: true, configurable: true } }); + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} proto The object which should be the prototype of + * the newly-created object. + * + * Throws a `TypeError` exception if the `proto` parameter isn't null or + * an object. + * + * @param {Object} [propertiesObject] If specified and not undefined, + * an object whose enumerable own properties (that is, those + * properties defined upon itself and not enumerable properties along + * its prototype chain) specify property descriptors to be added to + * the newly-created object, with the corresponding property names. + * + * @return {Object} the newly created object. + */ + +/** + * @method defineProperty + * @static + * + * Defines a new property directly on an object, or modifies an + * existing property on an object, and returns the object. + * + * This method allows precise addition to or modification of a + * property on an object. Normal property addition through assignment + * creates properties which show up during property enumeration + * (for...in loop or {@link Object#keys} method), whose values may be + * changed, and which may be deleted. This method allows these extra + * details to be changed from their defaults. + * + * Property descriptors present in objects come in two main flavors: + * data descriptors and accessor descriptors. A data descriptor is a + * property that has a value, which may or may not be writable. An + * accessor descriptor is a property described by a getter-setter pair + * of functions. A descriptor must be one of these two flavors; it + * cannot be both. + * + * Both data and accessor descriptor is an object with the following + * optional keys: + * + * - **configurable** True if and only if the type of this property + * descriptor may be changed and if the property may be deleted from + * the corresponding object. Defaults to false. + * + * - **enumerable** True if and only if this property shows up during + * enumeration of the properties on the corresponding + * object. Defaults to false. + * + * A data descriptor is an object with the following optional keys: + * + * - **value** The value associated with the property. Can be any + * valid JavaScript value (number, object, function, etc) Defaults + * to undefined. + * + * - **writable** True if and only if the value associated with the + * property may be changed with an assignment operator. Defaults to + * false. + * + * An accessor descriptor is an object with the following optional + * keys: + * + * - **get** A function which serves as a getter for the property, or + * undefined if there is no getter. The function return will be used + * as the value of property. Defaults to undefined. + * + * - **set** A function which serves as a setter for the property, or + * undefined if there is no setter. The function will receive as + * only argument the new value being assigned to the + * property. Defaults to undefined. + * + * Bear in mind that these options are not necessarily own properties + * so, if inherited, will be considered too. In order to ensure these + * defaults are preserved you might freeze the Object.prototype + * upfront, specify all options explicitly, or point to null as + * __proto__ property. + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object on which to define the property. + * @param {String} prop The name of the property to be defined or modified. + * @param {Object} descriptor The descriptor for the property being + * defined or modified. + */ + +/** + * @method defineProperties + * @static + * + * Defines new or modifies existing properties directly on an object, + * returning the object. + * + * In essence, it defines all properties corresponding to the + * enumerable own properties of props on the object. + * + * Object.defineProperties(obj, { + * "property1": { + * value: true, + * writable: true + * }, + * "property2": { + * value: "Hello", + * writable: false + * } + * // etc. etc. + * }); + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object on which to define or modify properties. + * @param {Object} props An object whose own enumerable properties + * constitute descriptors for the properties to be defined or + * modified. + */ + +/** + * @method getOwnPropertyDescriptor + * @static + * + * Returns a property descriptor for an own property (that is, one + * directly present on an object, not present by dint of being along + * an object's prototype chain) of a given object. + * + * This method permits examination of the precise description of a + * property. A property in JavaScript consists of a string-valued name + * and a property descriptor. Further information about property + * descriptor types and their attributes can be found in + * {@link Object#defineProperty}. + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object in which to look for the property. + * @param {String} prop The name of the property whose description is + * to be retrieved. + * + * A property descriptor is a record with some of the following + * attributes: + * + * - **value** The value associated with the property (data + * descriptors only). + * + * - **writable** True if and only if the value associated with + * the property may be changed (data descriptors only). + * + * - **get** A function which serves as a getter for the property, + * or undefined if there is no getter (accessor descriptors only). + * + * - **set** A function which serves as a setter for the property, + * or undefined if there is no setter (accessor descriptors only). + * + * - **configurable** true if and only if the type of this property + * descriptor may be changed and if the property may be deleted + * from the corresponding object. + * + * - **enumerable** true if and only if this property shows up + * during enumeration of the properties on the corresponding object. + * + * @return {Mixed} Value of the property descriptor. + */ + +/** + * @method keys + * @static + * + * Returns an array of a given object's own enumerable properties, in + * the same order as that provided by a for-in loop (the difference + * being that a for-in loop enumerates properties in the prototype + * chain as well). + * + * Returns an array whose elements are strings corresponding to the + * enumerable properties found directly upon object. The ordering of + * the properties is the same as that given by looping over the + * properties of the object manually. + * + * var arr = ["a", "b", "c"]; + * alert(Object.keys(arr)); // will alert "0,1,2" + * + * // array like object + * var obj = { 0 : "a", 1 : "b", 2 : "c"}; + * alert(Object.keys(obj)); // will alert "0,1,2" + * + * // getFoo is property which isn't enumerable + * var my_obj = Object.create({}, { getFoo : { value : function () { return this.foo } } }); + * my_obj.foo = 1; + * + * alert(Object.keys(my_obj)); // will alert only foo + * + * If you want all properties, even the not enumerable, see + * {@link Object#getOwnPropertyNames}. + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object whose enumerable own properties are + * to be returned. + * @return {String[]} Array of property names. + */ + +/** + * @method getOwnPropertyNames + * @static + * + * Returns an array of all properties (enumerable or not) found + * directly upon a given object. + * + * Rreturns an array whose elements are strings corresponding to the + * enumerable and non-enumerable properties found directly upon + * obj. The ordering of the enumerable properties in the array is + * consistent with the ordering exposed by a for...in loop (or by + * {@link Object#keys}) over the properties of the object. The + * ordering of the non-enumerable properties in the array, and among + * the enumerable properties, is not defined. + * + * var arr = ["a", "b", "c"]; + * print(Object.getOwnPropertyNames(arr).sort()); // prints "0,1,2,length" + * + * // Array-like object + * var obj = { 0: "a", 1: "b", 2: "c"}; + * print(Object.getOwnPropertyNames(obj).sort()); // prints "0,1,2" + * + * // Printing property names and values using Array.forEach + * Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) { + * print(val + " -> " + obj[val]); + * }); + * // prints + * // 0 -> a + * // 1 -> b + * // 2 -> c + * + * // non-enumerable property + * var my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; }, enumerable: false } }); + * my_obj.foo = 1; + * + * print(Object.getOwnPropertyNames(my_obj).sort()); // prints "foo, getFoo" + * + * If you want only the enumerable properties, see {@link Object#keys} + * or use a for...in loop (although note that this will return + * enumerable properties not found directly upon that object but also + * along the prototype chain for the object unless the latter is + * filtered with {@link #hasOwnProperty}). + * + * Items on the prototype chain are not listed: + * + * function ParentClass () { + * } + * ParentClass.prototype.inheritedMethod = function () { + * }; + * + * function ChildClass () { + * this.prop = 5; + * this.method = function () {}; + * } + * ChildClass.prototype = new ParentClass; + * ChildClass.prototype.prototypeMethod = function () { + * }; + * + * alert( + * Object.getOwnPropertyNames( + * new ChildClass() // ["prop", "method"] + * ) + * ) + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object whose enumerable and non-enumerable + * own properties are to be returned. + * @return {String[]} Array of property names. + */ + +/** + * @method getPrototypeOf + * @static + * + * Returns the prototype (i.e. the internal `[[Prototype]]`) of the + * specified object. + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} object The object whose prototype is to be returned. + * Throws a TypeError exception if this parameter isn't an Object. + * + * @return {Object} the prototype + */ + +/** + * @method preventExtensions + * @static + * + * Prevents new properties from ever being added to an object + * (i.e. prevents future extensions to the object). + * + * An object is extensible if new properties can be added to it. + * `preventExtensions` marks an object as no longer extensible, so that + * it will never have properties beyond the ones it had at the time it + * was marked as non-extensible. Note that the properties of a + * non-extensible object, in general, may still be deleted. Attempting + * to add new properties to a non-extensible object will fail, either + * silently or by throwing a TypeError (most commonly, but not + * exclusively, when in strict mode). + * + * It only prevents addition of own properties. Properties can still + * be added to the object prototype. + * + * If there is a way to turn an extensible object to a non-extensible + * one, there is no way to do the opposite in ECMAScript 5 + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object which should be made non-extensible. + */ + +/** + * @method isExtensible + * @static + * + * Determines if an object is extensible (whether it can have new + * properties added to it). + * + * Objects are extensible by default: they can have new properties + * added to them, and can be modified. An object can be marked as + * non-extensible using {@link Object#preventExtensions}, + * {@link Object#seal}, or {@link Object#freeze}. + * + * // New objects are extensible. + * var empty = {}; + * assert(Object.isExtensible(empty) === true); + * + * // ...but that can be changed. + * Object.preventExtensions(empty); + * assert(Object.isExtensible(empty) === false); + * + * // Sealed objects are by definition non-extensible. + * var sealed = Object.seal({}); + * assert(Object.isExtensible(sealed) === false); + * + * // Frozen objects are also by definition non-extensible. + * var frozen = Object.freeze({}); + * assert(Object.isExtensible(frozen) === false); + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object which should be checked. + * @return {Boolean} True when object is extensible. + */ + +/** + * @method seal + * @static + * + * Seals an object, preventing new properties from being added to it + * and marking all existing properties as non-configurable. Values of + * present properties can still be changed as long as they are + * writable. + * + * By default, objects are extensible (new properties can be added to + * them). Sealing an object prevents new properties from being added + * and marks all existing properties as non-configurable. This has the + * effect of making the set of properties on the object fixed and + * immutable. Making all properties non-configurable also prevents + * them from being converted from data properties to accessor + * properties and vice versa, but it does not prevent the values of + * data properties from being changed. Attempting to delete or add + * properties to a sealed object, or to convert a data property to + * accessor or vice versa, will fail, either silently or by throwing a + * TypeError (most commonly, although not exclusively, when in strict + * mode code). + * + * The prototype chain remains untouched. + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object which should be sealed. + */ + +/** + * @method isSealed + * @static + * + * Determines if an object is sealed. + * + * An object is sealed if it is non-extensible and if all its + * properties are non-configurable and therefore not removable (but + * not necessarily non-writable). + * + * // Objects aren't sealed by default. + * var empty = {}; + * assert(Object.isSealed(empty) === false); + * + * // If you make an empty object non-extensible, it is vacuously sealed. + * Object.preventExtensions(empty); + * assert(Object.isSealed(empty) === true); + * + * // The same is not true of a non-empty object, unless its properties are all non-configurable. + * var hasProp = { fee: "fie foe fum" }; + * Object.preventExtensions(hasProp); + * assert(Object.isSealed(hasProp) === false); + * + * // But make them all non-configurable and the object becomes sealed. + * Object.defineProperty(hasProp, "fee", { configurable: false }); + * assert(Object.isSealed(hasProp) === true); + * + * // The easiest way to seal an object, of course, is Object.seal. + * var sealed = {}; + * Object.seal(sealed); + * assert(Object.isSealed(sealed) === true); + * + * // A sealed object is, by definition, non-extensible. + * assert(Object.isExtensible(sealed) === false); + * + * // A sealed object might be frozen, but it doesn't have to be. + * assert(Object.isFrozen(sealed) === true); // all properties also non-writable + * + * var s2 = Object.seal({ p: 3 }); + * assert(Object.isFrozen(s2) === false); // "p" is still writable + * + * var s3 = Object.seal({ get p() { return 0; } }); + * assert(Object.isFrozen(s3) === true); // only configurability matters for accessor properties + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object which should be checked. + * @return {Boolean} True if the object is sealed, otherwise false. + */ + +/** + * @method freeze + * @static + * + * Freezes an object: that is, prevents new properties from being + * added to it; prevents existing properties from being removed; and + * prevents existing properties, or their enumerability, + * configurability, or writability, from being changed. In essence the + * object is made effectively immutable. The method returns the object + * being frozen. + * + * Nothing can be added to or removed from the properties set of a + * frozen object. Any attempt to do so will fail, either silently or + * by throwing a TypeError exception (most commonly, but not + * exclusively, when in strict mode). + * + * Values cannot be changed for data properties. Accessor properties + * (getters and setters) work the same (and still give the illusion + * that you are changing the value). Note that values that are objects + * can still be modified, unless they are also frozen. + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object to freeze. + */ + +/** + * @method isFrozen + * @static + * + * Determines if an object is frozen. + * + * An object is frozen if and only if it is not extensible, all its + * properties are non-configurable, and all its data properties (that + * is, properties which are not accessor properties with getter or + * setter components) are non-writable. + * + * // A new object is extensible, so it is not frozen. + * assert(Object.isFrozen({}) === false); + * + * // An empty object which is not extensible is vacuously frozen. + * var vacuouslyFrozen = Object.preventExtensions({}); + * assert(Object.isFrozen(vacuouslyFrozen) === true); + * + * // A new object with one property is also extensible, ergo not frozen. + * var oneProp = { p: 42 }; + * assert(Object.isFrozen(oneProp) === false); + * + * // Preventing extensions to the object still doesn't make it frozen, + * // because the property is still configurable (and writable). + * Object.preventExtensions(oneProp); + * assert(Object.isFrozen(oneProp) === false); + * + * // ...but then deleting that property makes the object vacuously frozen. + * delete oneProp.p; + * assert(Object.isFrozen(oneProp) === true); + * + * // A non-extensible object with a non-writable but still configurable property is not frozen. + * var nonWritable = { e: "plep" }; + * Object.preventExtensions(nonWritable); + * Object.defineProperty(nonWritable, "e", { writable: false }); // make non-writable + * assert(Object.isFrozen(nonWritable) === false); + * + * // Changing that property to non-configurable then makes the object frozen. + * Object.defineProperty(nonWritable, "e", { configurable: false }); // make non-configurable + * assert(Object.isFrozen(nonWritable) === true); + * + * // A non-extensible object with a non-configurable but still writable property also isn't frozen. + * var nonConfigurable = { release: "the kraken!" }; + * Object.preventExtensions(nonConfigurable); + * Object.defineProperty(nonConfigurable, "release", { configurable: false }); + * assert(Object.isFrozen(nonConfigurable) === false); + * + * // Changing that property to non-writable then makes the object frozen. + * Object.defineProperty(nonConfigurable, "release", { writable: false }); + * assert(Object.isFrozen(nonConfigurable) === true); + * + * // A non-extensible object with a configurable accessor property isn't frozen. + * var accessor = { get food() { return "yum"; } }; + * Object.preventExtensions(accessor); + * assert(Object.isFrozen(accessor) === false); + * + * // ...but make that property non-configurable and it becomes frozen. + * Object.defineProperty(accessor, "food", { configurable: false }); + * assert(Object.isFrozen(accessor) === true); + * + * // But the easiest way for an object to be frozen is if Object.freeze has been called on it. + * var frozen = { 1: 81 }; + * assert(Object.isFrozen(frozen) === false); + * Object.freeze(frozen); + * assert(Object.isFrozen(frozen) === true); + * + * // By definition, a frozen object is non-extensible. + * assert(Object.isExtensible(frozen) === false); + * + * // Also by definition, a frozen object is sealed. + * assert(Object.isSealed(frozen) === true); + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @param {Object} obj The object which should be checked. + * @return {Boolean} True if the object is frozen, otherwise false. */ \ No newline at end of file diff --git a/js-classes/String.js b/js-classes/String.js index 8b8479d649f8a191139063fc9315eb2a96a428d0..520bc7506206382cadec54ddad82bb66e3e93eff 100644 --- a/js-classes/String.js +++ b/js-classes/String.js @@ -1033,4 +1033,22 @@ * alert(x.valueOf()) // Displays "Hello world" * * @return {String} Returns value of string. - */ \ No newline at end of file + */ + +// ECMAScript 5 methods + +/** + * @method trim + * Removes whitespace from both ends of the string. + * + * Does not affect the value of the string itself. + * + * The following example displays the lowercase string `"foo"`: + * + * var orig = " foo "; + * alert(orig.trim()); + * + * **NOTE:** This method is part of the ECMAScript 5 standard. + * + * @return {String} A string stripped of whitespace on both ends. + */ diff --git a/jsduck.gemspec b/jsduck.gemspec index b2dd794bd2c1ba95cac25b6ebd7eaaf5d7aa353e..7e992dd8b2938dd0eb443426664d7c549a6538ff 100644 --- a/jsduck.gemspec +++ b/jsduck.gemspec @@ -2,8 +2,8 @@ Gem::Specification.new do |s| s.required_rubygems_version = ">= 1.3.5" s.name = 'jsduck' - s.version = '4.6.0' - s.date = '2013-01-09' + s.version = '4.6.1' + s.date = '2013-01-17' s.summary = "Simple JavaScript Duckumentation generator" s.description = "Documentation generator for Sencha JS frameworks" s.homepage = "https://github.com/senchalabs/jsduck" @@ -23,6 +23,7 @@ Gem::Specification.new do |s| s.add_dependency 'json' s.add_dependency 'parallel' s.add_dependency 'therubyracer', '>= 0.10.0', '< 0.11.0' + s.add_dependency 'dimensions' s.add_development_dependency 'rspec' s.add_development_dependency 'rake' diff --git a/lib/jsduck/assets.rb b/lib/jsduck/assets.rb index 69276651c6b4c99388954a9e338340fa04ad3017..02085a51f13dc9543ea64ca2ceb3bba3cbe14a96 100644 --- a/lib/jsduck/assets.rb +++ b/lib/jsduck/assets.rb @@ -1,4 +1,5 @@ -require 'jsduck/images' +require 'jsduck/img/dir_set' +require 'jsduck/img/writer' require 'jsduck/welcome' require 'jsduck/guides' require 'jsduck/videos' @@ -29,8 +30,8 @@ module JsDuck doc_formatter = DocFormatter.new(@opts) doc_formatter.relations = @relations - @images = Images.new(@opts.images) - @welcome = Welcome.create(@opts.welcome) + @images = Img::DirSet.new(@opts.images, "images") + @welcome = Welcome.create(@opts.welcome, doc_formatter) @guides = Guides.create(@opts.guides, doc_formatter, @opts) @videos = Videos.create(@opts.videos) @examples = Examples.create(@opts.examples, @opts) @@ -43,7 +44,7 @@ module JsDuck # Welcome page and categories are written in JsDuck::IndexHtml def write @guides.write(@opts.output_dir+"/guides") - @images.copy(@opts.output_dir+"/images") + Img::Writer.copy(@images.all_used, @opts.output_dir+"/images") end end diff --git a/lib/jsduck/batch_formatter.rb b/lib/jsduck/batch_formatter.rb index 9aa57e607127e7332bfaf9af2aec595db81710d7..402083a131b0482a019e19bbdc7662df35ff835b 100644 --- a/lib/jsduck/batch_formatter.rb +++ b/lib/jsduck/batch_formatter.rb @@ -1,6 +1,7 @@ require 'jsduck/util/parallel' require 'jsduck/class_formatter' require 'jsduck/doc_formatter' +require 'jsduck/img/dir_set' require 'jsduck/logger' module JsDuck @@ -21,7 +22,7 @@ module JsDuck begin { :doc => formatter.format(cls.internal_doc), - :images => formatter.images + :images => formatter.images.all_used } rescue Logger.fatal_backtrace("Error while formatting #{cls[:name]} #{files}", $!) @@ -32,15 +33,21 @@ module JsDuck # Then merge the data back to classes sequentially formatted_classes.each do |cls| relations[cls[:doc][:name]].internal_doc = cls[:doc] - cls[:images].each {|img| assets.images.add(img) } + # Perform lookup of all the images again. We're really doing + # this work twice now, but as we usually don't have excessive + # amounts of images, the performance penalty should be minimal. + cls[:images].each {|img| assets.images.get(img[:filename]) } end + + # Print warnings for unused images + assets.images.report_unused end # Factory method to create new ClassFormatter instances. def self.create_class_formatter(relations, opts) doc_formatter = DocFormatter.new(opts) doc_formatter.relations = relations - doc_formatter.img_path = "images" + doc_formatter.images = Img::DirSet.new(opts.images, "images") class_formatter = ClassFormatter.new(relations, doc_formatter) # Don't format types when exporting diff --git a/lib/jsduck/class_formatter.rb b/lib/jsduck/class_formatter.rb index b66130f70c2cb9592cdf87bf9217308e6be3b486..db1f3ef65d1a3aaeeceab0f20139197c20dd293f 100644 --- a/lib/jsduck/class_formatter.rb +++ b/lib/jsduck/class_formatter.rb @@ -39,7 +39,7 @@ module JsDuck cls end - # Returns the images detected by doc-formatter + # Access to the Img::DirSet object inside doc-formatter def images @formatter.images end diff --git a/lib/jsduck/doc_formatter.rb b/lib/jsduck/doc_formatter.rb index aaa0cc41f53054214601007e6f4de8296ebb54d9..433d14347d1f2df4d7f6209b80a707950562e39a 100644 --- a/lib/jsduck/doc_formatter.rb +++ b/lib/jsduck/doc_formatter.rb @@ -24,12 +24,10 @@ module JsDuck @doc_context = {} end - # Sets base path to prefix images from {@img} tags. - def img_path=(path) - @inline_img.base_path = path + # Accessors to the images attribute of Inline::Img + def images=(images) + @inline_img.images = images end - - # Returns list of all image paths gathered from {@img} tags. def images @inline_img.images end @@ -47,6 +45,7 @@ module JsDuck @doc_context = doc @inline_video.doc_context = doc @inline_link.doc_context = doc + @inline_img.doc_context = doc end # Returns the current documentation context diff --git a/lib/jsduck/guides.rb b/lib/jsduck/guides.rb index e483518b6fbb8ff2ee2b917cfa9f56f6f0e78150..47d0a42424b393286a9701029cbddaf593a6a6fc 100644 --- a/lib/jsduck/guides.rb +++ b/lib/jsduck/guides.rb @@ -5,6 +5,7 @@ require 'jsduck/util/null_object' require 'jsduck/logger' require 'jsduck/grouped_asset' require 'jsduck/util/html' +require 'jsduck/img/dir' require 'fileutils' module JsDuck @@ -39,6 +40,7 @@ module JsDuck def load_all_guides each_item do |guide| guide["url"] = resolve_url(guide) + guide[:filename] = guide["url"] + "/README.md" guide[:html] = load_guide(guide) end end @@ -52,22 +54,29 @@ module JsDuck def load_guide(guide) return Logger.warn(:guide, "Guide not found", guide["url"]) unless File.exists?(guide["url"]) - - guide_file = guide["url"] + "/README.md" - - return Logger.warn(:guide, "Guide not found", guide_file) unless File.exists?(guide_file) + return Logger.warn(:guide, "Guide not found", guide[:filename]) unless File.exists?(guide[:filename]) begin - @formatter.doc_context = {:filename => guide_file, :linenr => 0} - @formatter.img_path = "guides/#{guide["name"]}" - - return add_toc(guide, @formatter.format(Util::IO.read(guide_file))) + return format_guide(guide) rescue Logger.fatal_backtrace("Error while reading/formatting guide #{guide['url']}", $!) exit(1) end end + def format_guide(guide) + @formatter.doc_context = {:filename => guide[:filename], :linenr => 0} + @formatter.images = Img::Dir.new(guide["url"], "guides/#{guide["name"]}") + html = add_toc(guide, @formatter.format(Util::IO.read(guide[:filename]))) + + # Report unused images (but ignore the icon files) + @formatter.images.get("icon.png") + @formatter.images.get("icon-lg.png") + @formatter.images.report_unused + + return html + end + def write_guide(guide, dir) return unless guide[:html] diff --git a/lib/jsduck/images.rb b/lib/jsduck/images.rb deleted file mode 100644 index 60ccdbf5a6aef93ea181008c00d26e82f773522f..0000000000000000000000000000000000000000 --- a/lib/jsduck/images.rb +++ /dev/null @@ -1,72 +0,0 @@ -require "jsduck/logger" -require "fileutils" - -module JsDuck - - # Looks up images from directories specified through --images option. - class Images - def initialize(paths) - @paths = scan_for_images(paths) - @images = {} - end - - # Scans each path for image files, building a hash of paths where - # each path points to a hash of image files found in that path. - def scan_for_images(paths) - map = {} - paths.each do |path| - # Scans directory for image files - map[path] = {} - Dir[path+"/**/*.{png,jpg,jpeg,gif}"].each do |img| - map[path][img] = false - end - end - map - end - - # Adds relative image path of an image - def add(filename) - unless @images[filename] - @images[filename] = true - end - end - - # Copys over images to given output dir - def copy(output_dir) - @images.each_key do |img| - unless copy_img(img, output_dir) - Logger.warn(:image, "Image not found.", img) - end - end - report_unused - end - - # Attempts to copy one image, returns true on success - def copy_img(img, output_dir) - @paths.each_pair do |path, map| - filename = path + "/" + img - if map.has_key?(filename) - dest = output_dir + "/" + img - Logger.log("Copying image", dest) - FileUtils.makedirs(File.dirname(dest)) - FileUtils.cp(filename, dest) - # mark file as used. - map[filename] = true - return true - end - end - return false - end - - # Report unused images - def report_unused - @paths.each_pair do |path, map| - map.each_pair do |img, used| - Logger.warn(:image_unused, "Image not used.", img) unless used - end - end - end - - end - -end diff --git a/lib/jsduck/img/dir.rb b/lib/jsduck/img/dir.rb new file mode 100644 index 0000000000000000000000000000000000000000..dd066e5722f4af409d56b91ebc4b667a385e0d41 --- /dev/null +++ b/lib/jsduck/img/dir.rb @@ -0,0 +1,94 @@ +require 'dimensions' +require 'jsduck/logger' + +module JsDuck + module Img + + # Looks up images from a directory. + class Dir + def initialize(full_path, relative_path) + @full_path = full_path + @relative_path = relative_path + @images = {} + end + + # Retrieves hash of information for a given relative image + # filename. It will have the fields: + # + # - :filename - the same as the parameter of this method + # - :full_path - actual path in the filesystem. + # - :relative_path - relative path to be used inside tag. + # - :width - Image width + # - :height - Image height + # + # When the image is not found, returns nil. + def get(filename) + img = scan_img(filename) + if img + @images[filename] = img + end + img + end + + # Returns all used images. + def all_used + @images.values + end + + # Print warnings about all unused images. + def report_unused + scan_for_unused_images.each {|img| warn_unused(img) } + end + + private + + def scan_img(filename) + full_path = File.join(@full_path, filename) + if File.exists?(File.join(@full_path, filename)) + img_record(filename) + else + nil + end + end + + # Scans directory for image files, building a hash of image files + # found in that directory. + def scan_for_unused_images + unused = [] + ::Dir[@full_path+"/**/*.{png,jpg,jpeg,gif}"].each do |path| + filename = relative_path(@full_path, path) + unused << img_record(filename) unless @images[filename] + end + unused + end + + def warn_unused(img) + Logger.warn(:image_unused, "Image not used.", img[:full_path]) + end + + def img_record(filename) + full_path = File.join(@full_path, filename) + width, height = Dimensions.dimensions(full_path) + + return { + :filename => filename, + :relative_path => File.join(@relative_path, filename), + :full_path => full_path, + :width => width, + :height => height, + } + end + + # Given a path to directory and a path to file, returns the path + # to this file relative to the given dir. For example: + # + # base_path("/foo/bar", "/foo/bar/baz/img.jpg") --> "baz/img.jpg" + # + def relative_path(dir_path, file_path) + file_path.slice(dir_path.length+1, file_path.length) + end + + end + + end +end diff --git a/lib/jsduck/img/dir_set.rb b/lib/jsduck/img/dir_set.rb new file mode 100644 index 0000000000000000000000000000000000000000..993ca3bc17f74c8f0e981cbd7adba477b59bada1 --- /dev/null +++ b/lib/jsduck/img/dir_set.rb @@ -0,0 +1,39 @@ +require "jsduck/img/dir" +require "jsduck/logger" +require "fileutils" + +module JsDuck + module Img + + # A collection if Img::Dir objects. + # + # Looks up images from directories specified through --images + # option. + # + # This class provides the same interface as Img::Dir, except that + # the constructor takes array of full_paths not just one. + class DirSet + def initialize(full_paths, relative_path) + @dirs = full_paths.map {|path| Img::Dir.new(path, relative_path) } + end + + def get(filename) + @dirs.each do |dir| + if img = dir.get(filename) + return img + end + end + return nil + end + + def all_used + @dirs.map {|dir| dir.all_used }.flatten + end + + def report_unused + @dirs.each {|dir| dir.report_unused } + end + end + + end +end diff --git a/lib/jsduck/img/writer.rb b/lib/jsduck/img/writer.rb new file mode 100644 index 0000000000000000000000000000000000000000..c054a3f3fbf36fa9b8e290e41dc02f885835dd9e --- /dev/null +++ b/lib/jsduck/img/writer.rb @@ -0,0 +1,23 @@ +require "jsduck/logger" +require "fileutils" + +module JsDuck + module Img + + # Copies images to destination directory. + class Writer + # Takes an array of image records retrieved from + # Img::Dir#all_used or Img::DirSet#all_used and copies all of + # them to given output directory. + def self.copy(images, output_dir) + images.each do |img| + dest = File.join(output_dir, img[:filename]) + Logger.log("Copying image", dest) + FileUtils.makedirs(File.dirname(dest)) + FileUtils.cp(img[:full_path], dest) + end + end + end + + end +end diff --git a/lib/jsduck/inline/img.rb b/lib/jsduck/inline/img.rb index 09bed97b077ed5f3b44c3f5ed6e607310532bc5b..a5b6eae75ae438fd637f29e1cad958762098c882 100644 --- a/lib/jsduck/inline/img.rb +++ b/lib/jsduck/inline/img.rb @@ -1,25 +1,24 @@ require 'jsduck/util/html' require 'jsduck/logger' +require 'pp' module JsDuck module Inline # Implementation of inline tag {@img} class Img - # Base path to prefix images from {@img} tags. - # Defaults to no prefix. - attr_accessor :base_path - - # This will hold list of all image paths gathered from {@img} tags. + # Instance of Img::Dir or Img::DirSet that's used for looking up + # image information. attr_accessor :images + # Sets up instance to work in context of particular doc object. + # Used for error reporting. + attr_accessor :doc_context + def initialize(opts={}) - @tpl = opts[:img_tpl] || '%a' + @tpl = opts[:img_tpl] || '%a' @re = /\{@img\s+(\S*?)(?:\s+(.+?))?\}/m - - @base_path = nil - @images = [] end # Takes StringScanner instance. @@ -37,13 +36,22 @@ module JsDuck # applies the image template def apply_tpl(url, alt_text) - @images << url + img = @images.get(url) + if !img + Logger.warn(:image, "Image #{url} not found.", @doc_context[:filename], @doc_context[:linenr]) + img = {} + end + @tpl.gsub(/(%\w)/) do case $1 when '%u' - @base_path ? (@base_path + "/" + url) : url + img[:relative_path] when '%a' Util::HTML.escape(alt_text||"") + when '%w' + img[:width] + when '%h' + img[:height] else $1 end diff --git a/lib/jsduck/options.rb b/lib/jsduck/options.rb index 95141d34a75694584c1157a19edcff93e1a13b0c..d51e8db8141f809f7188dbb3f60ae07947016d58 100644 --- a/lib/jsduck/options.rb +++ b/lib/jsduck/options.rb @@ -91,7 +91,7 @@ module JsDuck ] @ext4_events = nil - @version = "4.6.0" + @version = "4.6.1" # Customizing output @title = "Documentation - JSDuck" @@ -110,7 +110,7 @@ module JsDuck @link_tpl = '%a' # Note that we wrap image template inside

because {@img} often # appears inline within text, but that just looks ugly in HTML - @img_tpl = '

%a

' + @img_tpl = '

%a

' @export = nil @seo = false @eg_iframe = nil @@ -290,9 +290,11 @@ module JsDuck end opts.on('--welcome=PATH', - "HTML file with content for welcome page.", + "File with content for welcome page.", "", - "It should only contain the part of a HTML page.", + "Either HTML or Markdown file with content for welcome page.", + "HTML file must only contain the part of the page.", + "Markdown file must have a .md or .markdown extension.", "", "See also: https://github.com/senchalabs/jsduck/wiki/Welcome-page") do |path| @welcome = canonical(path) @@ -500,8 +502,10 @@ module JsDuck "", "%u - URL from @img tag (e.g. 'some/path.png')", "%a - alt text for image", + "%w - width of image", + "%h - height of image", "", - "Defaults to: '

\"%a\"

'") do |tpl| + "Defaults to: '

\"%a\"

'") do |tpl| @img_tpl = tpl end diff --git a/lib/jsduck/welcome.rb b/lib/jsduck/welcome.rb index d04b3e8d434e593d656af8fc46a4f9c286fd0614..fe582d5b2a391682654c5210d2dded3c7853e044 100644 --- a/lib/jsduck/welcome.rb +++ b/lib/jsduck/welcome.rb @@ -5,17 +5,20 @@ module JsDuck class Welcome # Creates Welcome object from filename. - def self.create(filename) + def self.create(filename, doc_formatter) if filename - Welcome.new(filename) + Welcome.new(filename, doc_formatter) else Util::NullObject.new(:to_html => "") end end - # Parses welcome HTML file with content for welcome page. - def initialize(filename) + # Parses welcome HTML or Markdown file with content for welcome page. + def initialize(filename, doc_formatter) @html = Util::IO.read(filename) + if filename =~ /\.(md|markdown)\Z/i + @html = '
' + doc_formatter.format(@html) + '
' + end end # Returns the HTML diff --git a/spec/doc_formatter_spec.rb b/spec/doc_formatter_spec.rb index 14b5b666963547f849c8a32bb50c7c3076a5b07d..7cbe18d1c3653d984bfdfb47f13244b6cfb8869f 100644 --- a/spec/doc_formatter_spec.rb +++ b/spec/doc_formatter_spec.rb @@ -5,9 +5,16 @@ require "jsduck/class" describe JsDuck::DocFormatter do + class ImageDirMock + def get(filename) + {:relative_path => filename} + end + end + before do - @formatter = JsDuck::DocFormatter.new + @formatter = JsDuck::DocFormatter.new(:img_tpl => '%a') @formatter.class_context = "Context" + @formatter.images = ImageDirMock.new @formatter.relations = JsDuck::Relations.new([ JsDuck::Class.new({ :name => "Context", @@ -135,6 +142,7 @@ describe JsDuck::DocFormatter do describe "auto-detect" do before do @formatter.class_context = "Context" + @formatter.images = ImageDirMock.new @formatter.relations = JsDuck::Relations.new([ JsDuck::Class.new({:name => 'Foo.Bar'}), JsDuck::Class.new({:name => 'Foo.Bar.Blah'}), diff --git a/template/app/controller/Search.js b/template/app/controller/Search.js index 0e8d8c804c225daede1ef5a70dd2d51a0260a21d..696eac689e66b844cabe81c5bac01afa08ecba61 100644 --- a/template/app/controller/Search.js +++ b/template/app/controller/Search.js @@ -148,11 +148,8 @@ Ext.define('Docs.controller.Search', { this.getDropdown().getStore().loadData(results.slice(start, end)); // position dropdown below search box this.getDropdown().alignTo('search-field', 'bl', [-12, -2]); - // hide dropdown when nothing found - if (results.length === 0) { - this.getDropdown().hide(); - } - else { + + if (results.length > 0) { // auto-select first result this.getDropdown().getSelectionModel().select(0); } diff --git a/template/app/view/search/Dropdown.js b/template/app/view/search/Dropdown.js index 42a81ddcfd5b9e3665c4d7177d9b4e5b82e58c21..6331255a1ad9813721fcaaf3dff494ae5253a15b 100644 --- a/template/app/view/search/Dropdown.js +++ b/template/app/view/search/Dropdown.js @@ -50,9 +50,13 @@ Ext.define('Docs.view.search.Dropdown', { '', '', '', { getCls: function(meta) { diff --git a/template/resources/sass/viewport.scss b/template/resources/sass/viewport.scss index d34fbc271e1de881f0aafa97f04d74fbdab5fb11..d6dae6c89baa247fc2e814a12c3bfa8b19962319 100644 --- a/template/resources/sass/viewport.scss +++ b/template/resources/sass/viewport.scss @@ -25,6 +25,44 @@ padding: 1em 0 0.4em 0; font-weight: normal; } } +// When welcome text rendered from Markdown. +#welcomeindex .markdown { + margin: 2em; + color: $docs-text-color; + p { + margin-bottom: 1em; } + h1 { + color: $docs-heading-color; + font-family: $docs-heading-font; + font-size: 2em; } + h2 { @include guides-h2-heading; } + h3 { @include guides-h3-heading; } + h3 { font-weight: bold; font-size: 1.1em; } + h4 { font-weight: bold; } + ul { + margin: 0 0 1em 2em; + li { + list-style: disc outside; } } + ol { + margin: 0 0 1em 2em; + li { + list-style: decimal outside; } } + em { + font-style: italic; } + strong { + font-weight: bold; } + pre { + background-color: #f7f7f7; + border: solid 1px #e8e8e8; + @include border-radius(5px); + color: #314e64; + font-family: $docs-monospace-font; + padding: 10px 12px; + margin: 10px 0 14px 0; + overflow-x: auto; + overflow-y: hidden; } } + + .class-overview, .guide-container, .comments-index { // Style only the body area, or the toolbar will get styled too .x-panel-body { @@ -32,7 +70,7 @@ .clr { clear: both; } p, ul, ol { - color: #484848; + color: $docs-text-color; max-width: 900px; } p { padding: 0;