Commit 901b8520 authored by fxa's avatar fxa

Restructured the complete project

parent 966f7847
/*jshint */
/*global complete, desc, fail, file, task*/
(function () {
"use strict";
var
async = require('async'),
path = require('path'),
fs = require('fs'),
hinter = require('./build/hinter'),
jake = require('jake'),
nodeunit = require('nodeunit'),
fileConcatter = require('./build/fileConcatter'),
UglifyJS = require('uglify-js');
var
NODEUNIT_OPTIONS = {
"error_prefix": "",
"error_suffix": "",
"ok_prefix": "",
"ok_suffix": "",
"bold_prefix": "",
"bold_suffix": "",
"assertion_prefix": "",
"assertion_suffix": ""
};
var
SRC_HOME = 'src',
SRC_FILES = [
// cannot use the fileList, because the order matters
path.join(SRC_HOME, 'objectHelper.js'),
path.join(SRC_HOME, 'charHelper.js'),
path.join(SRC_HOME, 'pctEncoder.js'),
path.join(SRC_HOME, 'rfcCharHelper.js'),
path.join(SRC_HOME, 'encodingHelper.js'),
path.join(SRC_HOME, 'operators.js'),
path.join(SRC_HOME, 'isDefined.js'),
path.join(SRC_HOME, 'LiteralExpression.js'),
path.join(SRC_HOME, 'parse.js'),
path.join(SRC_HOME, 'VariableExpression.js'),
path.join(SRC_HOME, 'UriTemplate_.js')
],
UNIT_TESTS = new jake.FileList('test/unit/test*.js').toArray(),
INTEGRATION_TESTS = [
path.join('test', 'integration', 'simpleTest.js'),
path.join('test', 'integration', 'testRfcSamples.js')
],
TMP_UNTESTED_UNCOMPRESSED = 'tmp-untested-uritemplate.js',
TMP_UNTESTED_COMPRESSED = 'tmp-unested-uritemplate-min.js',
TARGET_HOME = 'bin',
TARGET_UNCOMPRESSED = path.join(TARGET_HOME, 'uritemplate.js'),
TARGET_COMPRESSED = path.join(TARGET_HOME, 'uritemplate-min.js'),
ASYNC = {async: true};
var all = new jake.FileList();
all.include('./*');
all.include('src/**');
all.include('test/**');
all.exclude(TARGET_COMPRESSED);
var TARGET_UNCOMPRESSED_DEPENDENCIES = all.toArray();
function closeTask(err) {
if (err) {
fail(err);
}
complete();
}
desc('clean');
task('clean', [], function () {
function unlinkWhenExists(filename, callback) {
fs.unlink(filename, function (err) {
callback(err && err.code !== 'ENOENT' ? err : undefined);
});
}
async.forEach([TMP_UNTESTED_UNCOMPRESSED, TMP_UNTESTED_COMPRESSED, TARGET_UNCOMPRESSED, TARGET_COMPRESSED], unlinkWhenExists, closeTask);
}, ASYNC);
file(TARGET_UNCOMPRESSED, TARGET_UNCOMPRESSED_DEPENDENCIES, function () {
global.URI_TEMPLATE_FILE = TMP_UNTESTED_UNCOMPRESSED;
async.series([
function (callback) {
jake.logger.log('looking for jshint warnings ...');
hinter.hint(SRC_FILES, callback);
},
function (callback) {
jake.logger.log('unit testing ...');
nodeunit.reporters['default'].run(UNIT_TESTS, NODEUNIT_OPTIONS, callback);
},
function (callback) {
jake.logger.log('build concatenated version ...');
fileConcatter.concat(SRC_FILES, TMP_UNTESTED_UNCOMPRESSED, callback, {
mapper: [fileConcatter.removeJshintOptions, fileConcatter.removeUseStrict],
preAll: fs.readFileSync('src/pre.txt', 'utf-8'),
postAll: fs.readFileSync('src/post.txt', 'utf-8')
});
},
function (callback) {
jake.logger.log('hinting the concatenated version ... ');
hinter.hint([TMP_UNTESTED_UNCOMPRESSED], callback);
},
function (callback) {
jake.logger.log('integration tests ...');
nodeunit.reporters['default'].run(INTEGRATION_TESTS, NODEUNIT_OPTIONS, callback);
},
function (callback) {
jake.logger.log('move uncompressed version to target directory');
fs.rename(TMP_UNTESTED_UNCOMPRESSED, TARGET_UNCOMPRESSED, callback);
}
], closeTask);
}, ASYNC);
file(TARGET_COMPRESSED, [TARGET_UNCOMPRESSED], function () {
global.URI_TEMPLATE_FILE = TMP_UNTESTED_COMPRESSED;
async.series([
function (callback) {
jake.logger.log('compress to temporary file ... ');
var compressed = UglifyJS.minify(TARGET_UNCOMPRESSED);
fs.writeFile(TMP_UNTESTED_COMPRESSED, compressed.code, 'utf8', callback);
},
function (callback) {
jake.logger.log('integration tests with minified version ... ');
nodeunit.reporters['default'].run(INTEGRATION_TESTS, NODEUNIT_OPTIONS, callback);
},
function (callback) {
jake.logger.log('move compressed version to target ... ');
fs.rename(TMP_UNTESTED_COMPRESSED, TARGET_COMPRESSED, callback);
}
], closeTask);
}, ASYNC);
desc('release');
task('release', [TARGET_COMPRESSED], function () {
jake.logger.log('done.');
});
task('default', ['clean', 'release']);
}());
\ No newline at end of file
...@@ -22,7 +22,7 @@ and then: ...@@ -22,7 +22,7 @@ and then:
var var
UriTemplate = require('uritemplate'), UriTemplate = require('uritemplate'),
template; template;
template = UriTemplate.parse('{?query*}'); template = UriTemplate.parse('{?query*})';
template.expand({query: {first: 1, second: 2}}); template.expand({query: {first: 1, second: 2}});
--> "?first=1&second=2" --> "?first=1&second=2"
...@@ -33,22 +33,29 @@ So you have to to: ...@@ -33,22 +33,29 @@ So you have to to:
git submodule init git submodule init
git submodule update git submodule update
Tests Build
----- -----
jake clean release
The tests are taken from https://github.com/uri-templates/uritemplate-test as a submodule. Tests
Run the tests with -----
node test.js
Comming soon
------------
* A new method extract(uri), which tries to extract the variables from a given uri The integration tests are taken from https://github.com/uri-templates/uritemplate-test as a submodule.
* Support of javascript's Date class The tests are integrated in the Jakefile.
License License
------- -------
Copyright 2012 Franz Antesberger Copyright 2013 Franz Antesberger
MIT License, see http://mit-license.org/ MIT License, see http://mit-license.org/
Release Notes
-------------
0.2.0 heavy project refactoring, splitting source files, introducing jshint (preparation of next steps)
Next Steps
----------
* Implementing unit tests (now only dummy test implemented)
* Updating uritemplate-test (mnot added some new tests and removed some wrong. At the moment I cannot update, because the new tests will not pass)
* A new method extract(uri), which tries to extract the variables from a given uri.
This is harder, than you might think
(function(e){"use strict";function t(e){var n,o;if(null===e||void 0===e)return!1;if(r.isArray(e)){for(n=0;e.length>n;n+=1)if(t(e[n]))return!0;return!1}if("string"==typeof e||"number"==typeof e||"boolean"==typeof e)return!0;for(o in e)if(e.hasOwnProperty(o)&&t(e[o]))return!0;return!1}var r=function(){function e(e){return"[object Array]"===Object.prototype.toString.apply(e)}function t(e,t,r){var n,o=r;for(n in e)e.hasOwnProperty(n)&&(o=t(o,e[n],n,e));return o}function r(e,t,r){var n,o=r;for(n=0;e.length>n;n+=1)o=t(o,e[n],n,e);return o}function n(n,o,i){return e(n)?r(n,o,i):t(n,o,i)}return{isArray:e,reduce:n}}(),n=function(){function e(e){return e>="a"&&"z">=e||e>="A"&&"Z">=e}function t(e){return e>="0"&&"9">=e}function r(e){return t(e)||e>="a"&&"f">=e||e>="A"&&"F">=e}return{isAlpha:e,isDigit:t,isHexDigit:r}}(),o=function(){function e(e){return unescape(encodeURIComponent(e))}function t(t){var r,n,o="",i=e(t);for(n=0;i.length>n;n+=1)r=i.charCodeAt(n),o+="%"+r.toString(16).toUpperCase();return o}function r(e){if(3>e.length)return!1;for(var t=0;e.length>t;t+=3)if("%"!==e.charAt(t)||!n.isHexDigit(e.charAt(t+1)||!n.isHexDigit(e.charAt(t+2))))return!1;return!0}function o(e,t){var n=e.charAt(t);return"%"!==n?n:(n=e.substr(t,3),r(n)?n:"%")}return{encodeCharacter:t,decodeCharacter:decodeURIComponent,isPctEncoded:r,pctCharAt:o}}(),i=function(){function e(e){return n.isAlpha(e)||n.isDigit(e)||"_"===e||o.isPctEncoded(e)}function t(e){return n.isAlpha(e)||n.isDigit(e)||"-"===e||"."===e||"_"===e||"~"===e}function r(e){return":"===e||"/"===e||"?"===e||"#"===e||"["===e||"]"===e||"@"===e||"!"===e||"$"===e||"&"===e||"("===e||")"===e||"*"===e||"+"===e||","===e||";"===e||"="===e||"'"===e}return{isVarchar:e,isUnreserved:t,isReserved:r}}(),a=function(){function e(e,t){var r,n="",a="";for(("number"==typeof e||"boolean"==typeof e)&&(e=""+e),r=0;e.length>r;r+=a.length)a=o.pctCharAt(e,r),n+=a.length>1?a:i.isUnreserved(a)||t&&i.isReserved(a)?a:o.encodeCharacter(a);return n}function t(t){return e(t,!0)}return{encode:e,encodePassReserved:t}}(),u=function(){function e(e){t[e]={symbol:e,separator:"?"===e?"&":""===e||"+"===e||"#"===e?",":e,named:";"===e||"&"===e||"?"===e,ifEmpty:"&"===e||"?"===e?"=":"",first:"+"===e?"":e,encode:"+"===e||"#"===e?a.encodePassReserved:a.encode,toString:function(){return this.symbol}}}var t={};return e(""),e("+"),e("#"),e("."),e("/"),e(";"),e("?"),e("&"),{valueOf:function(e){if(t[e])return t[e];if("=,!@|".indexOf(e)>=0)throw Error('Illegal use of reserved operator "'+e+'"');return t[""]}}}(),s=function(){function e(e){var t,r="",n="";for(t=0;e.length>t;t+=n.length)n=o.pctCharAt(e,t),r+=n.length>0?n:i.isReserved(n)||i.isUnreserved(n)?n:o.encodeCharacter(n);return r}function t(e){this.literal=t.encodeLiteral(e)}return t.encodeLiteral=e,t.prototype.expand=function(){return this.literal},t.prototype.toString=t.prototype.expand,t}(),f=function(){function e(e){function t(){h={varname:a.substring(d,f),exploded:!1,maxLength:null},d=null}function r(){if(g===f)throw Error("after a ':' you have to specify the length. position = "+f);h.maxLength=parseInt(a.substring(g,f),10),g=null}var a,s,f,l,p=[],h=null,d=null,g=null;for(a=e.substr(1,e.length-2),s=u.valueOf(a.charAt(0)),f=""===s.symbol?0:1,d=f;a.length>f;f+=l.length){if(l=o.pctCharAt(a,f),null!==d){if("."===l){if(d===f)throw Error("a varname MUST NOT start with a dot -- see position "+f);continue}if(i.isVarchar(l))continue;t()}if(null!==g){if(n.isDigit(l))continue;r()}if(":"!==l)if("*"!==l){if(","!==l)throw Error("illegal character '"+l+"' at position "+f);p.push(h),h=null,d=f+1}else{if(null===h)throw Error("explode exploded at position "+f);if(h.exploded)throw Error("explode exploded twice at position "+f);if(h.maxLength)throw Error("an explode (*) MUST NOT follow to a prefix, see position "+f);h.exploded=!0}else{if(null!==h.maxLength)throw Error("only one :maxLength is allowed per varspec at position "+f);g=f+1}}return null!==d&&t(),null!==g&&r(),p.push(h),new c(e,s,p)}function t(t){var r,n,o=[],i=null,a=0;for(r=0;t.length>r;r+=1)if(n=t.charAt(r),null===a){if(null===i)throw Error("reached unreachable code");if("{"===n)throw Error("brace was opened in position "+i+" and cannot be reopened in position "+r);if("}"===n){if(i+1===r)throw Error("empty braces on position "+i);o.push(e(t.substring(i,r+1))),i=null,a=r+1}}else{if("}"===n)throw Error("brace was closed in position "+r+" but never opened");"{"===n&&(r>a&&o.push(new s(t.substring(a,r))),a=null,i=r)}if(null!==i)throw Error("brace was opened on position "+i+", but never closed");return t.length>a&&o.push(new s(t.substr(a))),new l(t,o)}return t}(),c=function(){function e(e){return JSON?JSON.stringify(e):e}function n(e,t,r){this.templateText=e,this.operator=t,this.varspecs=r}return n.prototype.toString=function(){return this.templateText},n.prototype.expand=function(n){function o(e,r,n){return t(r)&&(e.length>0&&(e+=","),l||(e+=d.encode(n)+","),e+=d.encode(r)),e}function i(e,r,n){return t(r)&&(e.length>0&&(e+=d.separator),e+=l?s.encodeLiteral(f.varname):d.encode(n),e+="="+d.encode(r)),e}function a(e,r,n){return t(r)&&(e.length>0&&(e+=d.separator),l||(e+=d.encode(n)+"="),e+=d.encode(r)),e}var u,f,c,l,p="",h=!0,d=this.operator;for(u=0;this.varspecs.length>u;u+=1)if(f=this.varspecs[u],c=n[f.varname],h?(p+=this.operator.first,h=!1):p+=this.operator.separator,l=r.isArray(c),"string"==typeof c||"number"==typeof c||"boolean"==typeof c){if(c=""+c,this.operator.named){if(p+=s.encodeLiteral(f.varname),""===c){p+=this.operator.ifEmpty;continue}p+="="}f.maxLength&&c.length>f.maxLength&&(c=c.substr(0,f.maxLength)),p+=this.operator.encode(c)}else{if(f.maxLength)throw Error("Prefix modifiers are not applicable to variables that have composite values. You tried to expand "+this+" with "+e(c));if(f.exploded)p+=r.reduce(c,d.named?i:a,"");else{if(d.named){if(p+=s.encodeLiteral(f.varname),!t(c)){p+=this.operator.ifEmpty;continue}p+="="}p+=r.reduce(c,o,"")}}return p},n}(),l=function(){function e(e,t){this.templateText=e,this.expressions=t}return e.prototype.toString=function(){return this.templateText},e.prototype.expand=function(e){var t,r="";for(t=0;this.expressions.length>t;t+=1)r+=this.expressions[t].expand(e);return r},e.parse=f,e}();e(l)})(function(e){"use strict";"undefined"!=typeof module?module.exports=e:"undefined"!=typeof define?define([],function(){return e}):"undefined"!=typeof window?window.UriTemplate=e:global.UriTemplate=e});
\ No newline at end of file
This diff is collapsed.
module.exports = (function () {
"use strict";
var
fs = require('fs'),
async = require('async');
function readFileUtf8(filename, callback) {
// if you call readFile with encoding, the result is the file content as string.
// without encoding it would be a stream, which can be converted to a string with its toString() method
fs.readFile(filename, 'utf-8', callback);
}
function concat(inputFileArr, outputfile, callback, options) {
async.map(inputFileArr, readFileUtf8, function (err, contents) {
if (err) {
throw new Error('could not read files: ' + err);
}
if (options && options.mapper) {
if (Array.isArray(options.mapper)) {
options.mapper.forEach(function (map) {
contents = contents.map(map);
});
}
else {
contents = contents.map(options.mapper);
}
}
var concatenatedContent = contents.reduce(function (previousValue, currentValue) {
return previousValue + '\n' + currentValue;
});
if (options && options.preAll) {
var pre = typeof options.preAll === 'string' ? options.preAll : options.preAll(concatenatedContent);
concatenatedContent = pre + concatenatedContent;
}
if (options && options.postAll) {
var post = typeof options.postAll === 'string' ? options.postAll : options.postAll(concatenatedContent);
concatenatedContent = concatenatedContent + post;
}
fs.writeFile(outputfile, concatenatedContent, 'utf-8', callback);
});
}
function startsWith(text, beginning) {
return text.substr(0, beginning.length) === beginning;
}
function removeFirstLine(text) {
var indexOfLinebreak = text.indexOf("\n");
if (indexOfLinebreak < 0) {
return "";
}
return text.substr(indexOfLinebreak + 1);
}
function removeUseStrict(text) {
var lines = text.split('\n');
var filteredLines = lines.filter(function (line) {
return line.indexOf('"use strict"') < 0;
});
return filteredLines.join('\n');
}
function removeJshintOptions(source) {
if (startsWith(source, '/*jshint')) {
source = removeFirstLine(source);
}
if (startsWith(source, '/*global')) {
source = removeFirstLine(source);
}
return source;
}
return {
concat: concat,
removeJshintOptions: removeJshintOptions,
removeUseStrict: removeUseStrict
};
}());
\ No newline at end of file
module.exports = (function () {
"use strict";
var
fs = require('fs'),
jshint = require("jshint").JSHINT;
var
// for a full list and description see http://www.jshint.com/options/
JSHINT_OPTIONS = {
bitwise: true, // bitwise is forbidden
curly: true, // must put oneliners in curly braces
eqeqeq: true, // must use ===
forin: true,
immed: true, // wrap immediately called functions
newcap: true, // CAP constructors
noarg: true, // arguments.caller is evil
noempty: true, // empty blocks are forbidden
nonew: true, // forbids new X(); without assignment
regexp: true, // warns with wrong dots -- I would prefer to forbid regexps
strict: true, // must use "use strict"
undef: true, // forbids use of undefined variables
unused: true, // forbids unused variables
maxcomplexity: 17 // much too high. should be max. 10
},
JSHINT_GLOBALS = {
"module": true
};
function hint(jsFiles, callback) {
var
failed = false,
numCheckedFiles = 0;
jsFiles.forEach(function (jsFile) {
fs.readFile(jsFile, 'utf-8', function (err, content) {
if (failed) {
return;
}
if (err) {
throw new Error('could not read file ' + jsFile + ': ' + err);
}
numCheckedFiles += 1;
if (!jshint.jshint(content, JSHINT_OPTIONS, JSHINT_GLOBALS)) {
failed = true;
console.log(jshint.errors);
callback(jshint.errors, jsFile);
}
if (numCheckedFiles === jsFiles.length) {
callback();
}
});
});
}
return {
hint: hint
};
}());
\ No newline at end of file
...@@ -2,27 +2,27 @@ ...@@ -2,27 +2,27 @@
<html> <html>
<head> <head>
<title>Demo of Usage</title> <title>Demo of Usage</title>
<script type="text/javascript" src="src/uritemplate.js"></script> <script type="text/javascript" src="bin/uritemplate.js"></script>
</head> </head>
<body> <body>
<div id="id"></div> <div id="id"></div>
<script type="text/javascript"> <script type="text/javascript">
(function () { (function () {
"use strict"; "use strict";
var var
templateText = "{?query*}", templateText = "{?query*}",
variables = { variables = {
query: { query: {
first: "1", first: "1",
second: 2 second: 2
} }
}; };
document.getElementById('id').innerHTML = document.getElementById('id').innerHTML =
"<p>When you have a template of the form</p><p><code>var templateText = \"" + templateText "<p>When you have a template of the form</p><p><code>var templateText = \"" + templateText
+ "\";</code></p><p>and params of the form </p><p><code>var variables = " + JSON.stringify(variables) + "\";</code></p><p>and params of the form </p><p><code>var variables = " + JSON.stringify(variables)
+ ";</code></p><p>, you can use </p><p><code>UriTemplate.parse(templateText).expand(variables); </code></p><p>to produce </p><p><code>" + ";</code></p><p>, you can use </p><p><code>UriTemplate.parse(templateText).expand(variables); </code></p><p>to produce </p><p><code>"
+ UriTemplate.parse(templateText).expand(variables) + UriTemplate.parse(templateText).expand(variables)
+ "</code></p><p> Look at the source code of this page!</p>"; + "</code></p><p> Look at the source code of this page!</p>";
}()); }());
</script> </script>
......
...@@ -5,24 +5,24 @@ ...@@ -5,24 +5,24 @@
<script type="text/javascript" src="require.js"></script> <script type="text/javascript" src="require.js"></script>
</head> </head>
<body> <body>
<div id="id"></div> <div id="id"></div>
<script type="text/javascript"> <script type="text/javascript">
require(['src/uritemplate.js'],function(UriTemplate) { require(['bin/uritemplate.js'], function (UriTemplate) {
"use strict"; "use strict";
var var
templateText = "{?query*}", templateText = "{?query*}",
variables = { variables = {
query: { query: {
first: "1", first: "1",
second: 2 second: 2
} }
}; };
document.getElementById('id').innerHTML = document.getElementById('id').innerHTML =
"<p>When you have a template of the form</p><p><code>var templateText = \"" + templateText "<p>When you have a template of the form</p><p><code>var templateText = \"" + templateText
+ "\";</code></p><p>and params of the form </p><p><code>var variables = " + JSON.stringify(variables) + "\";</code></p><p>and params of the form </p><p><code>var variables = " + JSON.stringify(variables)
+ ";</code></p><p>, you can use </p><p><code>UriTemplate.parse(templateText).expand(variables); </code></p><p>to produce </p><p><code>" + ";</code></p><p>, you can use </p><p><code>UriTemplate.parse(templateText).expand(variables); </code></p><p>to produce </p><p><code>"
+ UriTemplate.parse(templateText).expand(variables) + UriTemplate.parse(templateText).expand(variables)
+ "</code></p><p> Look at the source code of this page!</p>"; + "</code></p><p> Look at the source code of this page!</p>";
}); });
</script> </script>
......
{ {
"name" : "uritemplate", "name": "uritemplate",
"description" : "An UriTemplate implementation of rfc 6570", "description": "An UriTemplate implementation of rfc 6570",
"homepage" : "https://www.github.com/fxa/uritemplate-js", "homepage": "https://www.github.com/fxa/uritemplate-js",
"keywords" : ["util", "uri", "template", "rfc6570"], "keywords": ["util", "uri", "template", "rfc6570"],
"author" : "Franz Antesberger", "author": "Franz Antesberger",
"contributors" : [], "contributors": [],
"dependencies" : [], "dependencies": [],
"main" : "src/uritemplate.js", "main": "bin/uritemplate.js",
"licenses": [{ "licenses": [
"type": "MIT", {
"url": "http://www.opensource.org/licenses/mit-license.php" "type": "MIT",
}], "url": "http://www.opensource.org/licenses/mit-license.php"
"files": [ }
"src/uritemplate.js", ],
"demo.html", "files": [
"test.js", "src",
"README.md", "test",
".gitmodules", "bin",
"uritemplate-test/extended-tests.json", "Jakefile.js",
"uritemplate-test/negative-tests.json", "own-testcases.json",
"uritemplate-test/README.md", "demo.html",
"uritemplate-test/spec-examples-by-sections.json", "demo_AMD.html",
"uritemplate-test/spec-examples.json" "README.md",
], "reqire.js",
"version" : "0.1.4" ".gitmodules",
"uritemplate-test/extended-tests.json",
"uritemplate-test/negative-tests.json",
"uritemplate-test/README.md",
"uritemplate-test/spec-examples-by-sections.json",
"uritemplate-test/spec-examples.json"
],
"version": "0.2.0"
} }
\ No newline at end of file
/*jshint unused:false */
/*global pctEncoder, rfcCharHelper*/
var LiteralExpression = (function () {
"use strict";
function encodeLiteral(literal) {
var
result = '',
index,
chr = '';
for (index = 0; index < literal.length; index += chr.length) {
chr = pctEncoder.pctCharAt(literal, index);
if (chr.length > 0) {
result += chr;
}
else {
result += rfcCharHelper.isReserved(chr) || rfcCharHelper.isUnreserved(chr) ? chr : pctEncoder.encodeCharacter(chr);
}
}
return result;
}
function LiteralExpression(literal) {
this.literal = LiteralExpression.encodeLiteral(literal);
}
LiteralExpression.encodeLiteral = encodeLiteral;
LiteralExpression.prototype.expand = function () {
return this.literal;
};
LiteralExpression.prototype.toString = LiteralExpression.prototype.expand;
return LiteralExpression;
}());
/*jshint unused:false */
/*global parse*/
var UriTemplate = (function () {
"use strict";
function UriTemplate(templateText, expressions) {
this.templateText = templateText;
this.expressions = expressions;
}
UriTemplate.prototype.toString = function () {
return this.templateText;
};
UriTemplate.prototype.expand = function (variables) {
var
index,
result = '';
for (index = 0; index < this.expressions.length; index += 1) {
result += this.expressions[index].expand(variables);
}
return result;
};
UriTemplate.parse = parse;
return UriTemplate;
}());
/*jshint unused:false */
/*global pctEncoder, rfcCharHelper, isDefined, LiteralExpression, objectHelper*/
var VariableExpression = (function () {
"use strict";
// helper function if JSON is not available
function prettyPrint(value) {
return JSON ? JSON.stringify(value) : value;
}
function VariableExpression(templateText, operator, varspecs) {
this.templateText = templateText;
this.operator = operator;
this.varspecs = varspecs;
}
VariableExpression.prototype.toString = function () {
return this.templateText;
};
VariableExpression.prototype.expand = function expandExpression(variables) {
var
result = '',
index,
varspec,
value,
valueIsArr,
isFirstVarspec = true,
operator = this.operator;
// callback to be used within array.reduce
function reduceUnexploded(result, currentValue, currentKey) {
if (isDefined(currentValue)) {
if (result.length > 0) {
result += ',';
}
if (!valueIsArr) {
result += operator.encode(currentKey) + ',';
}
result += operator.encode(currentValue);
}
return result;
}
function reduceNamedExploded(result, currentValue, currentKey) {
if (isDefined(currentValue)) {
if (result.length > 0) {
result += operator.separator;
}
result += (valueIsArr) ? LiteralExpression.encodeLiteral(varspec.varname) : operator.encode(currentKey);
result += '=' + operator.encode(currentValue);
}
return result;
}
function reduceUnnamedExploded(result, currentValue, currentKey) {
if (isDefined(currentValue)) {
if (result.length > 0) {
result += operator.separator;
}
if (!valueIsArr) {
result += operator.encode(currentKey) + '=';
}
result += operator.encode(currentValue);
}
return result;
}
// expand each varspec and join with operator's separator
for (index = 0; index < this.varspecs.length; index += 1) {
varspec = this.varspecs[index];
value = variables[varspec.varname];
// if (!isDefined(value)) {
// continue;
// }
if (isFirstVarspec) {
result += this.operator.first;
isFirstVarspec = false;
}
else {
result += this.operator.separator;
}
valueIsArr = objectHelper.isArray(value);
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
value = value.toString();
if (this.operator.named) {
result += LiteralExpression.encodeLiteral(varspec.varname);
if (value === '') {
result += this.operator.ifEmpty;
continue;
}
result += '=';
}
if (varspec.maxLength && value.length > varspec.maxLength) {
value = value.substr(0, varspec.maxLength);
}
result += this.operator.encode(value);
}
else if (varspec.maxLength) {
// 2.4.1 of the spec says: "Prefix modifiers are not applicable to variables that have composite values."
throw new Error('Prefix modifiers are not applicable to variables that have composite values. You tried to expand ' + this + " with " + prettyPrint(value));
}
else if (!varspec.exploded) {
if (operator.named) {
result += LiteralExpression.encodeLiteral(varspec.varname);
if (!isDefined(value)) {
result += this.operator.ifEmpty;
continue;
}
result += '=';
}
result += objectHelper.reduce(value, reduceUnexploded, '');
}
else {
// exploded and not string
result += objectHelper.reduce(value, operator.named ? reduceNamedExploded : reduceUnnamedExploded, '');
}
}
return result;
};
return VariableExpression;
}());
/*jshint unused: false */
/*global */
var charHelper = (function () {
"use strict";
function isAlpha(chr) {
return (chr >= 'a' && chr <= 'z') || ((chr >= 'A' && chr <= 'Z'));
}
function isDigit(chr) {
return chr >= '0' && chr <= '9';
}
function isHexDigit(chr) {
return isDigit(chr) || (chr >= 'a' && chr <= 'f') || (chr >= 'A' && chr <= 'F');
}
return {
isAlpha: isAlpha,
isDigit: isDigit,
isHexDigit: isHexDigit
};
}());
/*jshint unused: false */
/*global rfcCharHelper, pctEncoder*/
var encodingHelper = (function () {
"use strict";
function encode(text, passReserved) {
var
result = '',
index,
chr = '';
if (typeof text === "number" || typeof text === "boolean") {
text = text.toString();
}
for (index = 0; index < text.length; index += chr.length) {
chr = pctEncoder.pctCharAt(text, index);
if (chr.length > 1) {
result += chr;
}
else {
result += rfcCharHelper.isUnreserved(chr) || (passReserved && rfcCharHelper.isReserved(chr)) ? chr : pctEncoder.encodeCharacter(chr);
}
}
return result;
}
function encodePassReserved(text) {
return encode(text, true);
}
return {
encode: encode,
encodePassReserved: encodePassReserved
};
}());
/*jshint unused:false */
/*global objectHelper*/
/**
* Detects, whether a given element is defined in the sense of rfc 6570
* Section 2.3 of the RFC makes clear defintions:
* * undefined and null are not defined.
* * the empty string is defined
* * an array ("list") is defined, if it contains at least one defined element
* * an object ("map") is defined, if it contains at least one defined property
* @param object
* @return {Boolean}
*/
function isDefined(object) {
"use strict";
var
index,
propertyName;
if (object === null || object === undefined) {
return false;
}
if (objectHelper.isArray(object)) {
for (index = 0; index < object.length; index += 1) {
if (isDefined(object[index])) {
return true;
}
}
return false;
}
if (typeof object === "string" || typeof object === "number" || typeof object === "boolean") {
// falsy values like empty strings, false or 0 are "defined"
return true;
}
// else Object
for (propertyName in object) {
if (object.hasOwnProperty(propertyName) && isDefined(object[propertyName])) {
return true;
}
}
return false;
}
/*jshint unused: false */
var objectHelper = (function () {
"use strict";
function isArray(value) {
return Object.prototype.toString.apply(value) === '[object Array]';
}
// performs an array.reduce for objects
// TODO handling if initialValue is undefined
function objectReduce(object, callback, initialValue) {
var
propertyName,
currentValue = initialValue;
for (propertyName in object) {
if (object.hasOwnProperty(propertyName)) {
currentValue = callback(currentValue, object[propertyName], propertyName, object);
}
}
return currentValue;
}
// performs an array.reduce, if reduce is not present (older browser...)
// TODO handling if initialValue is undefined
function arrayReduce(array, callback, initialValue) {
var
index,
currentValue = initialValue;
for (index = 0; index < array.length; index += 1) {
currentValue = callback(currentValue, array[index], index, array);
}
return currentValue;
}
function reduce(arrayOrObject, callback, initialValue) {
return isArray(arrayOrObject) ? arrayReduce(arrayOrObject, callback, initialValue) : objectReduce(arrayOrObject, callback, initialValue);
}
return {
isArray: isArray,
reduce: reduce
};
}());
/*jshint unused: false */
/*global encodingHelper*/
// the operators defined by rfc 6570
var operators = (function () {
"use strict";
var
bySymbol = {};
function create(symbol) {
bySymbol[symbol] = {
symbol: symbol,
separator: (symbol === '?') ? '&' : (symbol === '' || symbol === '+' || symbol === '#') ? ',' : symbol,
named: symbol === ';' || symbol === '&' || symbol === '?',
ifEmpty: (symbol === '&' || symbol === '?') ? '=' : '',
first: (symbol === '+' ) ? '' : symbol,
encode: (symbol === '+' || symbol === '#') ? encodingHelper.encodePassReserved : encodingHelper.encode,
toString: function () {
return this.symbol;
}
};
}
create('');
create('+');
create('#');
create('.');
create('/');
create(';');
create('?');
create('&');
return {valueOf: function (chr) {
if (bySymbol[chr]) {
return bySymbol[chr];
}
if ("=,!@|".indexOf(chr) >= 0) {
throw new Error('Illegal use of reserved operator "' + chr + '"');
}
return bySymbol[''];
}};
}());
/*jshint unused:false */
/*global pctEncoder, operators, charHelper, rfcCharHelper, LiteralExpression, UriTemplate, VariableExpression */
var parse = (function () {
"use strict";
function parseExpression(outerText) {
var
text,
operator,
varspecs = [],
varspec = null,
varnameStart = null,
maxLengthStart = null,
index,
chr;
function closeVarname() {
varspec = {varname: text.substring(varnameStart, index), exploded: false, maxLength: null};
varnameStart = null;
}
function closeMaxLength() {
if (maxLengthStart === index) {
throw new Error("after a ':' you have to specify the length. position = " + index);
}
varspec.maxLength = parseInt(text.substring(maxLengthStart, index), 10);
maxLengthStart = null;
}
// remove outer {}
text = outerText.substr(1, outerText.length - 2);
// determine operator
operator = operators.valueOf(text.charAt(0));
index = (operator.symbol === '') ? 0 : 1;
varnameStart = index;
for (; index < text.length; index += chr.length) {
chr = pctEncoder.pctCharAt(text, index);
if (varnameStart !== null) {
// the spec says: varname = varchar *( ["."] varchar )
// so a dot is allowed except for the first char
if (chr === '.') {
if (varnameStart === index) {
throw new Error('a varname MUST NOT start with a dot -- see position ' + index);
}
continue;
}
if (rfcCharHelper.isVarchar(chr)) {
continue;
}
closeVarname();
}
if (maxLengthStart !== null) {
if (charHelper.isDigit(chr)) {
continue;
}
closeMaxLength();
}
if (chr === ':') {
if (varspec.maxLength !== null) {
throw new Error('only one :maxLength is allowed per varspec at position ' + index);
}
maxLengthStart = index + 1;
continue;
}
if (chr === '*') {
if (varspec === null) {
throw new Error('explode exploded at position ' + index);
}
if (varspec.exploded) {
throw new Error('explode exploded twice at position ' + index);
}
if (varspec.maxLength) {
throw new Error('an explode (*) MUST NOT follow to a prefix, see position ' + index);
}
varspec.exploded = true;
continue;
}
// the only legal character now is the comma
if (chr === ',') {
varspecs.push(varspec);
varspec = null;
varnameStart = index + 1;
continue;
}
throw new Error("illegal character '" + chr + "' at position " + index);
} // for chr
if (varnameStart !== null) {
closeVarname();
}
if (maxLengthStart !== null) {
closeMaxLength();
}
varspecs.push(varspec);
return new VariableExpression(outerText, operator, varspecs);
}
function parseTemplate(uriTemplateText) {
// assert filled string
var
index,
chr,
expressions = [],
braceOpenIndex = null,
literalStart = 0;
for (index = 0; index < uriTemplateText.length; index += 1) {
chr = uriTemplateText.charAt(index);
if (literalStart !== null) {
if (chr === '}') {
throw new Error('brace was closed in position ' + index + " but never opened");
}
if (chr === '{') {
if (literalStart < index) {
expressions.push(new LiteralExpression(uriTemplateText.substring(literalStart, index)));
}
literalStart = null;
braceOpenIndex = index;
}
continue;
}
if (braceOpenIndex !== null) {
// here just { is forbidden
if (chr === '{') {
throw new Error('brace was opened in position ' + braceOpenIndex + " and cannot be reopened in position " + index);
}
if (chr === '}') {
if (braceOpenIndex + 1 === index) {
throw new Error("empty braces on position " + braceOpenIndex);
}
expressions.push(parseExpression(uriTemplateText.substring(braceOpenIndex, index + 1)));
braceOpenIndex = null;
literalStart = index + 1;
}
continue;
}
throw new Error('reached unreachable code');
}
if (braceOpenIndex !== null) {
throw new Error("brace was opened on position " + braceOpenIndex + ", but never closed");
}
if (literalStart < uriTemplateText.length) {
expressions.push(new LiteralExpression(uriTemplateText.substr(literalStart)));
}
return new UriTemplate(uriTemplateText, expressions);
}
return parseTemplate;
}());
/*jshint unused:false */
/*global unescape, charHelper*/
var pctEncoder = (function () {
"use strict";
// see http://ecmanaut.blogspot.de/2006/07/encoding-decoding-utf8-in-javascript.html
function toUtf8(s) {
return unescape(encodeURIComponent(s));
}
function encode(chr) {
var
result = '',
octets = toUtf8(chr),
octet,
index;
for (index = 0; index < octets.length; index += 1) {
octet = octets.charCodeAt(index);
result += '%' + octet.toString(16).toUpperCase();
}
return result;
}
function isPctEncoded(chr) {
if (chr.length < 3) {
return false;
}
for (var index = 0; index < chr.length; index += 3) {
if (chr.charAt(index) !== '%' || !charHelper.isHexDigit(chr.charAt(index + 1) || !charHelper.isHexDigit(chr.charAt(index + 2)))) {
return false;
}
}
return true;
}
function pctCharAt(text, startIndex) {
var chr = text.charAt(startIndex);
if (chr !== '%') {
return chr;
}
chr = text.substr(startIndex, 3);
if (!isPctEncoded(chr)) {
return '%';
}
return chr;
}
return {
encodeCharacter: encode,
decodeCharacter: decodeURIComponent,
isPctEncoded: isPctEncoded,
pctCharAt: pctCharAt
};
}());
exportCallback(UriTemplate);
}(function (UriTemplate) {
"use strict";
// export UriTemplate, when module is present, or pass it to window or global
if (typeof module !== "undefined") {
module.exports = UriTemplate;
}
else if (typeof define !== "undefined") {
define([],function() {
return UriTemplate;
});
}
else if (typeof window !== "undefined") {
window.UriTemplate = UriTemplate;
}
else {
global.UriTemplate = UriTemplate;
}
}
));
/*jshint */
/*global unescape, module, define, window, global*/
/*
UriTemplate Copyright (c) 2012-2013 Franz Antesberger. All Rights Reserved.
Available via the MIT license.
*/
(function (exportCallback) {
"use strict";
/*jshint unused: false */
/*global charHelper, pctEncoder*/
var rfcCharHelper = (function () {
"use strict";
/**
* Returns if an character is an varchar character according 2.3 of rfc 6570
* @param chr
* @return (Boolean)
*/
function isVarchar(chr) {
return charHelper.isAlpha(chr) || charHelper.isDigit(chr) || chr === '_' || pctEncoder.isPctEncoded(chr);
}
/**
* Returns if chr is an unreserved character according 1.5 of rfc 6570
* @param chr
* @return {Boolean}
*/
function isUnreserved(chr) {
return charHelper.isAlpha(chr) || charHelper.isDigit(chr) || chr === '-' || chr === '.' || chr === '_' || chr === '~';
}
/**
* Returns if chr is an reserved character according 1.5 of rfc 6570
* @param chr
* @return {Boolean}
*/
function isReserved(chr) {
return chr === ':' || chr === '/' || chr === '?' || chr === '#' || chr === '[' || chr === ']' || chr === '@' || chr === '!' || chr === '$' || chr === '&' || chr === '(' ||
chr === ')' || chr === '*' || chr === '+' || chr === ',' || chr === ';' || chr === '=' || chr === "'";
}
return {
isVarchar: isVarchar,
isUnreserved: isUnreserved,
isReserved: isReserved
};
}());
module.exports = (function () {
"use strict";
var
UriTemplate = require('../../' + global.URI_TEMPLATE_FILE);
return {
'UriTemplate has a parse function': function (test) {
test.equal(typeof UriTemplate.parse, 'function');
test.done();
}
};
}());
module.exports = (function () {
(function () {
"use strict"; "use strict";
var var
assert = require('assert'),
fs = require('fs'), fs = require('fs'),
UriTemplate = require('./src/uritemplate.js'), sandbox = require('nodeunit').utils.sandbox;
numTestsPassed = 0;
function loadUriTemplate() {
var context = {module: {}};
sandbox(global.URI_TEMPLATE_FILE, context);
return context.module.exports;
}
function assertMatches(template, variables, expected, chapterName) { function loadTestFile(testFileName) {
return JSON.parse(fs.readFileSync(testFileName));
}
function assertMatches(test, template, variables, expected, chapterName, UriTemplate) {
var var
uriTemplate, uriTemplate,
actual, actual,
index; index;
try { try {
uriTemplate = UriTemplate.parse(template); uriTemplate = UriTemplate.parse(template);
// console.log('uritemplate parsed:' + uriTemplate);
} }
catch (error) { catch (error) {
// console.log('error', error);
// console.log('expected', expected.toString());
// if expected === false, the error was expected! // if expected === false, the error was expected!
if (expected === false) { if (expected === false) {
return; return;
} }
assert.fail('chapter "' + chapterName + '", template "' + template + '" threw error: ' + error); console.log('error', error);
console.log('expected', expected.toString());
test.notEqual('chapter ' + chapterName + ', template ' + template + ' threw error: ' + error);
return; return;
} }
assert.ok(!!uriTemplate, 'uri template could not be parsed'); test.ok(!!uriTemplate, 'uri template could not be parsed');
try { try {
actual = uriTemplate.expand(variables); actual = uriTemplate.expand(variables);
if (expected === false) { if (expected === false) {
assert.fail('chapter "' + chapterName + '", template "' + template + '" expected to fail, but returned \'' + actual + '\'!'); test.fail('chapter ' + chapterName + ', template ' + template + ' expected to fail, but returned \'' + actual + '\'!');
return; return;
} }
} }
...@@ -36,7 +49,7 @@ ...@@ -36,7 +49,7 @@
if (expected === false) { if (expected === false) {
return; return;
} }
assert.fail('chapter "' + chapterName + '", template "' + template + '" threw error: ' + exception); test.notEqual('chapter ' + chapterName + ', template ' + template + ' threw error: ' + JSON.stringify(exception, null, 4));
return; return;
} }
if (expected.constructor === Array) { if (expected.constructor === Array) {
...@@ -46,14 +59,14 @@ ...@@ -46,14 +59,14 @@
return; return;
} }
} }
assert.fail('actual: ' + actual + ', expected: one of ' + JSON.stringify(expected) + ", " + 'chapter "' + chapterName + '", template "' + template + '"'); test.notEqual('actual: ' + actual + ', expected: one of ' + JSON.stringify(expected) + 'chapter ' + chapterName + ', template ' + template);
} }
else { else {
assert.equal(actual, expected, 'actual: "' + actual + '", expected: "' + expected + '", template: "' + template + '"'); test.equal(actual, expected, 'actual: ' + actual + ', expected: ' + expected + ', template: ' + template);
} }
} }
function runTestFile(filename) { function runTestFile(test, filename) {
var var
tests, tests,
chapterName, chapterName,
...@@ -61,8 +74,10 @@ ...@@ -61,8 +74,10 @@
variables, variables,
index, index,
template, template,
expexted; expexted,
tests = JSON.parse(fs.readFileSync(filename)); UriTemplate;
UriTemplate = loadUriTemplate();
tests = loadTestFile(filename);
for (chapterName in tests) { for (chapterName in tests) {
if (tests.hasOwnProperty(chapterName)) { if (tests.hasOwnProperty(chapterName)) {
chapter = tests[chapterName]; chapter = tests[chapterName];
...@@ -70,19 +85,26 @@ ...@@ -70,19 +85,26 @@
for (index = 0; index < chapter.testcases.length; index += 1) { for (index = 0; index < chapter.testcases.length; index += 1) {
template = chapter.testcases[index][0]; template = chapter.testcases[index][0];
expexted = chapter.testcases[index][1]; expexted = chapter.testcases[index][1];
assertMatches(template, variables, expexted, chapterName); assertMatches(test, template, variables, expexted, chapterName, UriTemplate);
numTestsPassed += 1;
} }
console.log(chapterName); console.log(chapterName);
} }
} }
test.done();
} }
runTestFile('uritemplate-test/spec-examples.json'); return {
runTestFile('uritemplate-test/extended-tests.json'); 'spec examples': function (test) {
runTestFile('uritemplate-test/negative-tests.json'); runTestFile(test, 'uritemplate-test/spec-examples.json');
},
runTestFile('own-testcases.json'); 'extended tests': function (test) {
console.log('passed all ' + numTestsPassed + ' tests!'); runTestFile(test, 'uritemplate-test/extended-tests.json');
},
}()); 'negative tests': function (test) {
\ No newline at end of file runTestFile(test, 'uritemplate-test/negative-tests.json');
},
'own tests': function (test) {
runTestFile(test, 'own-testcases.json');
}
};
}());
module.exports = (function () {
"use strict";
var
sandbox = require('nodeunit').utils.sandbox;
var context = {};
sandbox('src/objectHelper.js', context);
var objectHelper = context.objectHelper;
return {
'reduce works with initial value': function (test) {
var callNum = 0;
var result = objectHelper.reduce({a: 1, b: 2, c: 17}, function (current, value, name) {
if (callNum === 0) {
test.equal(current, -1);
test.equal(value, 1);
test.equal(name, 'a');
}
else if (callNum === 1) {
test.equal(current, 1);
test.equal(value, 2);
test.equal(name, 'b');
}
else {
test.equal(current, 2);
test.equal(value, 17);
test.equal(name, 'c');
}
callNum += 1;
return Math.max(current, value);
}, -1);
test.equal(result, 17);
test.done();
}
};
}());
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment