├── .gitignore
├── lib
├── formatters
│ ├── count.js
│ ├── json.js
│ ├── safe.js
│ ├── url-encode.js
│ ├── htmltag.js
│ ├── slugify.js
│ ├── htmlattr.js
│ ├── json-pretty.js
│ ├── timesince.js
│ ├── comment-link.js
│ ├── smartypants.js
│ ├── activate-twitter-links.js
│ ├── date.js
│ ├── product-status.js
│ ├── social-button.js
│ ├── pluralize.js
│ ├── social-button-inline.js
│ ├── like-button.js
│ ├── comment-count.js
│ ├── image-meta.js
│ ├── product-price.js
│ ├── comments.js
│ ├── image.js
│ ├── product-checkout.js
│ ├── item-classes.js
│ └── index.js
├── predicates
│ ├── odd.js
│ ├── even.js
│ ├── excerpt.js
│ ├── event.js
│ ├── image.js
│ ├── video.js
│ ├── collection.js
│ ├── comments.js
│ ├── passthrough.js
│ ├── external-link.js
│ ├── folder.js
│ ├── index-predicate.js
│ ├── equal.js
│ ├── lessThan.js
│ ├── greaterThan.js
│ ├── disqus.js
│ ├── lessThanOrEqual.js
│ ├── collectionTypeNameEquals.js
│ ├── greaterThanOrEqual.js
│ ├── main-image.js
│ ├── location.js
│ └── index.js
├── util.js
└── jsontemplate.js
├── README.md
├── LICENSE
├── squarespace-jsont.js
├── package.json
└── .jshintrc
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | test*
4 | .notes
5 | npm-debug.log
--------------------------------------------------------------------------------
/lib/formatters/count.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: count
4 | * Returns the length of a collection
5 | *
6 | * @usage: {@|count}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return val.length;
11 | };
--------------------------------------------------------------------------------
/lib/formatters/json.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: json
4 | * Returns object JSON representation
5 | *
6 | * @usage: {@|json}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return JSON.stringify( val );
11 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | node-squarespace-jsont
2 | ======================
3 |
4 | > This project is deprecated and you can now use the [official Squarespace local development server](https://engineering.squarespace.com/blog/2016/the-developer-platform-gets-local).
5 |
--------------------------------------------------------------------------------
/lib/predicates/odd.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: odd
4 | * Evaluates a value as odd
5 | *
6 | * @usage: {.odd?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return !require( "./even" )( data, ctx, args );
11 | };
--------------------------------------------------------------------------------
/lib/predicates/even.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: even
4 | * Evaluates a value as even
5 | *
6 | * @usage: {.even?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return (ctx._LookUpStack( args[ 0 ] ) % 2 == 0);
11 | };
--------------------------------------------------------------------------------
/lib/predicates/excerpt.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: excerpt
4 | * Evaluates collections excerpt
5 | *
6 | * @usage: {.excerpt?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return ( data.excerpt ) ? true : false;
11 | };
--------------------------------------------------------------------------------
/lib/formatters/safe.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: safe
4 | * Returns a string with tags stripped out
5 | *
6 | * @usage: {@|safe}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return require( "phpjs" ).strip_tags( val );
11 | };
--------------------------------------------------------------------------------
/lib/predicates/event.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: event
4 | * Evaluates collections event
5 | *
6 | * @usage: {.event?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return ( data.recordTypeLabel === "event" ) ? true : false;
11 | };
--------------------------------------------------------------------------------
/lib/predicates/image.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: image
4 | * Evaluates collections images
5 | *
6 | * @usage: {.image?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return ( data.recordTypeLabel === "image" ) ? true : false;
11 | };
--------------------------------------------------------------------------------
/lib/predicates/video.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: video
4 | * Evaluates collections video
5 | *
6 | * @usage: {.video?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return ( data.recordTypeLabel === "video" ) ? true : false;
11 | };
--------------------------------------------------------------------------------
/lib/formatters/url-encode.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: url-encode
4 | * Returns an encoded url string
5 | *
6 | * @usage: {@|url-encode}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return encodeURI( encodeURIComponent( val ) );
11 | };
--------------------------------------------------------------------------------
/lib/predicates/collection.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: collection
4 | * Evaluates collections collection
5 | *
6 | * @usage: {.collection?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return ( data.collection ) ? true : false;
11 | };
--------------------------------------------------------------------------------
/lib/predicates/comments.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: comments
4 | * Evaluates collections comments
5 | *
6 | * @usage: {.comments?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return ( data.commentState === 1 ) ? true : false;
11 | };
--------------------------------------------------------------------------------
/lib/formatters/htmltag.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: htmltag
4 | * Returns an html tag escaped string
5 | *
6 | * @usage: {@|htmltag}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return require( "../jsontemplate" ).HtmlTagEscape( val );
11 | };
--------------------------------------------------------------------------------
/lib/formatters/slugify.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: slugify
4 | * Returns a string url in standard slug format
5 | *
6 | * @usage: {@|slugify}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return require( "slug" )( val.toLowerCase() );
11 | };
--------------------------------------------------------------------------------
/lib/predicates/passthrough.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: passthrough
4 | * Evaluates collections passthrough
5 | *
6 | * @usage: {.passthrough?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return ( data.passthrough ) ? true : false;
11 | };
--------------------------------------------------------------------------------
/lib/formatters/htmlattr.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: htmlattr
4 | * Returns an html tag escaped string
5 | *
6 | * @usage: {@|htmlattr}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return require( "../jsontemplate" ).HtmlTagEscape( val );
11 | };
--------------------------------------------------------------------------------
/lib/formatters/json-pretty.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: json-pretty
4 | * Returns object JSON representation formatted
5 | *
6 | * @usage: {@|json-pretty}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return JSON.stringify( val, null, 4 );
11 | };
--------------------------------------------------------------------------------
/lib/predicates/external-link.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: external-link
4 | * Evaluates collections external-link
5 | *
6 | * @usage: {.external-link?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return ( data.externalLink ) ? true : false;
11 | };
--------------------------------------------------------------------------------
/lib/formatters/timesince.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: timesince
4 | * Returns a formatted date string for `time since date`
5 | *
6 | * @usage: {@|timesince}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return require( "moment" )( val ).fromNow();
11 | };
--------------------------------------------------------------------------------
/lib/predicates/folder.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: folder
4 | * Evaluates collections folder
5 | *
6 | * @usage: {.folder?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return ( (data.collection && data.collection.folder) || data.folder ) ? true : false;
11 | };
--------------------------------------------------------------------------------
/lib/predicates/index-predicate.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: index
4 | * Evaluates collections index
5 | *
6 | * @usage: {.index?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | return ( (data.collection && data.collection.typeName === "index") || data.typeName === "index" ) ? true : false;
11 | };
--------------------------------------------------------------------------------
/lib/formatters/comment-link.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: comment-link
4 | * Returns a comment-link block...
5 | *
6 | * @usage: {@|comment-link}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return '';
11 | };
--------------------------------------------------------------------------------
/lib/formatters/smartypants.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: smartypants
4 | * Translates plain ASCII punctuation characters into “smart”
5 | * typographic punctuation HTML entities.
6 | *
7 | * @usage: {@|smartypants}
8 | *
9 | */
10 | module.exports = function ( val, args, ctx ) {
11 | return require( "typogr" ).smartypants( val );
12 | };
--------------------------------------------------------------------------------
/lib/predicates/equal.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: equal
4 | * Evaluates 2 values as equal
5 | *
6 | * @usage: {.equal? foo "bar"}
7 | *
8 | */
9 | var util = require( "../util" );
10 |
11 |
12 | module.exports = function ( data, ctx, args ) {
13 | args = util.getArgsProcessed( args, ctx );
14 |
15 | return ( args[ 0 ] == args[ 1 ] );
16 | };
--------------------------------------------------------------------------------
/lib/formatters/activate-twitter-links.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: activate-twitter-links
4 | * Returns a string with links activated
5 | *
6 | * @usage: {@|activate-twitter-links}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return val.replace( /(^|\s)@(\w+)/g, "$1@$2" );
11 | };
--------------------------------------------------------------------------------
/lib/predicates/lessThan.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: lessThan
4 | * Evaluates a value to be greater than another
5 | *
6 | * @usage: {.lessThan? @index 2}
7 | *
8 | */
9 | var util = require( "../util" );
10 |
11 |
12 | module.exports = function ( data, ctx, args ) {
13 | args = util.getArgsProcessed( args, ctx );
14 |
15 | return ( args[ 0 ] < args[ 1 ] );
16 | };
--------------------------------------------------------------------------------
/lib/predicates/greaterThan.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: greaterThan
4 | * Evaluates a value to be greater than another
5 | *
6 | * @usage: {.greaterThan? @index 2}
7 | *
8 | */
9 | var util = require( "../util" );
10 |
11 |
12 | module.exports = function ( data, ctx, args ) {
13 | args = util.getArgsProcessed( args, ctx );
14 |
15 | return ( args[ 0 ] > args[ 1 ] );
16 | };
--------------------------------------------------------------------------------
/lib/predicates/disqus.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: disqus
4 | * Evaluates collections disqus
5 | *
6 | * @usage: {.disqus?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | // The only place `disqus` appears in data is here:
11 | // websiteSettings.disqusShortname
12 | // So, not really sure how to use this at the item level.
13 | return false;
14 | };
--------------------------------------------------------------------------------
/lib/predicates/lessThanOrEqual.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: lessThanOrEqual
4 | * Evaluates a value to be greater than another
5 | *
6 | * @usage: {.lessThanOrEqual? @index 2}
7 | *
8 | */
9 | var util = require( "../util" );
10 |
11 |
12 | module.exports = function ( data, ctx, args ) {
13 | args = util.getArgsProcessed( args, ctx );
14 |
15 | return ( args[ 0 ] <= args[ 1 ] );
16 | };
--------------------------------------------------------------------------------
/lib/predicates/collectionTypeNameEquals.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: collectionTypeNameEquals
4 | * Evaluates collections typeName
5 | *
6 | * @usage: {.collectionTypeNameEquals? "foo"}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | var typeName = String( args[ 0 ].replace( /"|'/g, "" ) );
11 |
12 | return (data.typeName && data.typeName === typeName || false);
13 | };
--------------------------------------------------------------------------------
/lib/predicates/greaterThanOrEqual.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: greaterThanOrEqual
4 | * Evaluates a value to be greater than another
5 | *
6 | * @usage: {.greaterThanOrEqual? @index 2}
7 | *
8 | */
9 | var util = require( "../util" );
10 |
11 |
12 | module.exports = function ( data, ctx, args ) {
13 | args = util.getArgsProcessed( args, ctx );
14 |
15 | return ( args[ 0 ] >= args[ 1 ] );
16 | };
--------------------------------------------------------------------------------
/lib/formatters/date.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: date
4 | * Returns a formatted date string
5 | *
6 | * @usage: {@|date `format`}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | // Divide by 1000 since phpjs accounts for this:
11 | // PHP API expects UNIX timestamp (auto-convert to int)
12 | return require( "phpjs" ).strftime( args.join( " " ), (parseInt( val, 10 ) / 1000) );
13 | };
--------------------------------------------------------------------------------
/lib/formatters/product-status.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: product-status
4 | * Returns a product status block...
5 | *
6 | * @usage: {@|product-status}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | var ret = "";
11 |
12 | if ( val.variants && val.variants[ 0 ] && val.variants[ 0 ].qtyInStock === 0 ) {
13 | ret = '
sold out
';
14 | }
15 |
16 | return ret;
17 | };
--------------------------------------------------------------------------------
/lib/formatters/social-button.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: social-button
4 | * Returns a social button...
5 | *
6 | * @usage: {@|social-button}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return '';
11 | };
--------------------------------------------------------------------------------
/lib/formatters/pluralize.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: pluralize
4 | * Returns a pluralized string...
5 | *
6 | * @usage: {@|pluralize}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | var pluralizer = "s";
11 |
12 | if ( args && args.length > 1 ) {
13 | pluralizer = ( val > 0 ) ? args[ 1 ] : args[ 0 ];
14 |
15 | } else if ( args && args.length ) {
16 | pluralizer = args[ 0 ];
17 | }
18 |
19 | return ( val > 0 ) ? pluralizer : "";
20 | };
--------------------------------------------------------------------------------
/lib/formatters/social-button-inline.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: social-button-inline
4 | * Returns a social button...
5 | *
6 | * @usage: {@|social-button-inline}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | return '';
11 | };
--------------------------------------------------------------------------------
/lib/predicates/main-image.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: main-image
4 | * Evaluates collections mainImage
5 | *
6 | * @usage: {.main-image?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | var ret = false;
11 |
12 | // 0.1 => A collection
13 | // 0.2 => A collection item
14 | if ( (data.mainImage && data.mainImage.systemDataOrigin === "USER_UPLOAD") || (data.systemDataOrigin === "USER_UPLOAD") ) {
15 | ret = true;
16 | }
17 |
18 | return ret;
19 | };
--------------------------------------------------------------------------------
/lib/formatters/like-button.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: like-button
4 | * Returns a like button...
5 | *
6 | * @usage: {@|like-button}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | var ret = "";
11 |
12 | ret += '';
13 | ret += '';
14 | ret += '';
15 | ret += '';
16 |
17 | return ret;
18 | };
--------------------------------------------------------------------------------
/lib/formatters/comment-count.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: comment-count
4 | * Returns a comment-count block...
5 | *
6 | * @usage: {@|comment-count}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | var ret = "[comment-count?]";
11 |
12 | if ( !val.commentCount ) {
13 | ret = "No Comments";
14 |
15 | } else if ( val.commentCount === 1 ) {
16 | ret = "1 Comment";
17 |
18 | } else {
19 | ret = val.commentCount + " Comments";
20 | }
21 |
22 | return ret;
23 | };
--------------------------------------------------------------------------------
/lib/formatters/image-meta.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: image-meta
4 | * Returns the render for an images meta data
5 | *
6 | * @usage: {@|image-meta}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | var focalPoint;
11 |
12 | if (val.mediaFocalPoint) {
13 | focalPoint = val.mediaFocalPoint.x + ',' + val.mediaFocalPoint.y;
14 | }
15 |
16 | return 'data-src="' + val.assetUrl + '" data-image="' + val.assetUrl + '" data-image-dimensions="' + val.originalSize + '" data-image-focal-point="' + focalPoint + '" alt="' + val.filename + '"';
17 | };
--------------------------------------------------------------------------------
/lib/formatters/product-price.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: product-price
4 | * Returns a product price block...
5 | *
6 | * @usage: {@|product-price}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | var price = ("" + (val.variants[ 0 ].price / 100));
11 | price = price.split( "." );
12 |
13 | if ( !price[ 1 ] ) {
14 | price[ 1 ] = "00";
15 |
16 | } else if ( price[ 1 ].length === 1 ) {
17 | price[ 1 ] = (price[ 1 ] + "0");
18 | }
19 |
20 | price = price.join( "." );
21 |
22 | return '' + price + '
';
23 | };
--------------------------------------------------------------------------------
/lib/formatters/comments.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: comments
4 | * Returns a comments block...
5 | *
6 | * @usage: {@|comments}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | //return '';
11 |
12 | /**
13 | *
14 | * XMLHttpRequest cannot load
15 | * https://XXX.squarespace.com/api/comment/GetComments?targetId=XXX&targetType=1&since=&page=1&sortBy=2
16 | * No 'Access-Control-Allow-Origin' header is present on the requested resource.
17 | * Origin 'http://localhost:XXXX' is therefore not allowed access.
18 | *
19 | */
20 |
21 | return "[comments?]";
22 | };
--------------------------------------------------------------------------------
/lib/predicates/location.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Predicate: location
4 | * Evaluates collections location
5 | *
6 | * @usage: {.location?}
7 | *
8 | */
9 | module.exports = function ( data, ctx, args ) {
10 | // All items have default location info:
11 | // mapLat
12 | // mapLng
13 | // markerLat
14 | // markerLng
15 |
16 | // Custom location info adds extra fields:
17 | // addressTitle
18 | // addressLine1
19 | // addressLine2
20 | // addressCountry
21 |
22 | // Assume that any 1 of these additional fields means item has a location
23 | return ( data.location.addressTitle || data.location.addressLine1 || data.location.addressLine2 || data.location.addressCountry ) ? true : false;
24 | };
--------------------------------------------------------------------------------
/lib/formatters/image.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: image
4 | * Returns the render for an image
5 | *
6 | * @usage: {@|image}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | var focalPoint;
11 |
12 | if (val.mediaFocalPoint) {
13 | focalPoint = val.mediaFocalPoint.x + ',' + val.mediaFocalPoint.y;
14 | }
15 |
16 | return '
';
17 | };
--------------------------------------------------------------------------------
/lib/formatters/product-checkout.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: product-checkout
4 | * Returns a product checkout block...
5 | *
6 | * @usage: {@|product-checkout}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | var ret = "";
11 |
12 | ret += '';
16 | ret += '';
23 |
24 | return ret;
25 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Brandon Lee Kitajchuk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/squarespace-jsont.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Squarespace JSON-T Rendering Engine.
4 | *
5 | */
6 | var jsonTemplate = require( "./lib/jsontemplate" ),
7 | jsontOptions = {
8 | more_formatters: require( "./lib/formatters/index" ),
9 | more_predicates: require( "./lib/predicates/index" ),
10 | undefined_str: ""
11 | },
12 |
13 |
14 | /******************************************************************************
15 | * @Public
16 | *******************************************************************************/
17 |
18 | /**
19 | *
20 | * @method render
21 | * @param {string} render The template string
22 | * @param {object} data The data context
23 | * @returns {string}
24 | * @public
25 | *
26 | */
27 | render = function ( template, data ) {
28 | template = jsonTemplate.Template( template, jsontOptions );
29 | template = template.expand( data );
30 |
31 | return template;
32 | };
33 |
34 |
35 | /******************************************************************************
36 | * @Export
37 | *******************************************************************************/
38 | module.exports = {
39 | render: render
40 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Node Squarespace JSONT",
3 | "name": "node-squarespace-jsont",
4 | "description": "The JSONT template rendering engine for Squarespace in node.js.",
5 | "version": "0.1.23",
6 | "homepage": "https://github.com/NodeSquarespace/node-squarespace-jsont",
7 | "readmeFilename": "README.md",
8 | "contributors": [
9 | {
10 | "name": "Brandon Kitajchuk",
11 | "email": "kitajchuk@gmail.com",
12 | "url": "http://blkpdx.com"
13 | }
14 | ],
15 | "repository": {
16 | "type": "git",
17 | "url": "git://github.com/NodeSquarespace/node-squarespace-jsont.git"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/NodeSquarespace/node-squarespace-jsont/issues"
21 | },
22 | "licenses": [
23 | {
24 | "type": "MIT",
25 | "url": "https://github.com/NodeSquarespace/node-squarespace-jsont/blob/master/LICENSE"
26 | }
27 | ],
28 | "main": "squarespace-jsont",
29 | "dependencies": {
30 | "moment": "~2.8",
31 | "phpjs": "~1.3",
32 | "slug": "~0.7",
33 | "typogr": "^0.6.5"
34 | },
35 | "keywords": [
36 | "squarespace",
37 | "json-template",
38 | "jsont",
39 | "json"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/lib/formatters/item-classes.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Formatter: item-classes
4 | * Returns a formatted className string for an item
5 | *
6 | * @usage: {@|item-classes}
7 | *
8 | */
9 | module.exports = function ( val, args, ctx ) {
10 | var classes = 'hentry';
11 |
12 | if ( val.promotedBlockType ) {
13 | classes += ' promoted promoted-block-' + val.promotedBlockType;
14 | }
15 |
16 | if ( val.categories ) {
17 | classes += val.categories.map(function ( value ) {
18 | return ' category-' + require( "slug" )( value.toLowerCase() );
19 | }).join(' ');
20 | }
21 |
22 | if ( val.tags ) {
23 | classes += val.tags.map(function ( value ) {
24 | return ' tag-' + require( "slug" )( value.toLowerCase() );
25 | }).join(' ');
26 | }
27 |
28 | if ( val.starred ) {
29 | classes += ' featured';
30 | }
31 |
32 | if ( val.structuredContent ) {
33 | if ( val.structuredContent.onSale ) {
34 | classes += ' on-sale';
35 | }
36 | }
37 |
38 | classes += ' author-' + require( "slug" )( val.author.displayName.toLowerCase() )
39 | classes += ' post-type-' + require( "slug" )( val.recordTypeLabel.toLowerCase() );
40 | classes += ' article-index-' + ctx._LookUpStack( '@index' );
41 |
42 | return classes;
43 | };
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "maxerr": 50,
3 | "bitwise": true,
4 | "camelcase": false,
5 | "curly": true,
6 | "eqeqeq": true,
7 | "forin": false,
8 | "immed": true,
9 | "indent": false,
10 | "latedef": false,
11 | "newcap": true,
12 | "noarg": true,
13 | "noempty": true,
14 | "nonew": false,
15 | "plusplus": false,
16 | "quotmark": false,
17 | "undef": false,
18 | "unused": true,
19 | "strict": false,
20 | "maxparams": false,
21 | "maxdepth": false,
22 | "maxstatements": false,
23 | "maxcomplexity": false,
24 | "maxlen": false,
25 | "asi": false,
26 | "boss": false,
27 | "debug": false,
28 | "eqnull": false,
29 | "es5": false,
30 | "esnext": true,
31 | "moz": false,
32 | "evil": false,
33 | "expr": false,
34 | "funcscope": false,
35 | "globalstrict": false,
36 | "iterator": false,
37 | "lastsemic": false,
38 | "laxbreak": false,
39 | "laxcomma": false,
40 | "loopfunc": false,
41 | "multistr": false,
42 | "proto": false,
43 | "scripturl": false,
44 | "shadow": false,
45 | "sub": true,
46 | "supernew": false,
47 | "validthis": false,
48 | "browser": true,
49 | "couch": false,
50 | "devel": true,
51 | "dojo": false,
52 | "jquery": true,
53 | "mootools": false,
54 | "node": true,
55 | "nonstandard": false,
56 | "prototypejs": false,
57 | "rhino": false,
58 | "worker": false,
59 | "wsh": false,
60 | "yui": false,
61 | "globals": {}
62 | }
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | var rQuotes = /"|'/g;
2 |
3 | // Sourced from https://code.jquery.com/
4 | var isNumeric = function ( obj ) {
5 | return !Array.isArray( obj ) && obj - parseFloat( obj ) >= 0;
6 | };
7 |
8 | var getLookupArg = function ( ctx, arg ) {
9 | var get = ctx.get( arg ),
10 | lookup = ctx._LookUpStack( arg ),
11 | lookupIsNumeric = isNumeric( lookup ),
12 | getIsNumeric = isNumeric( get );
13 |
14 | if ( (lookup !== undefined && lookup !== "") || lookupIsNumeric ) {
15 | arg = lookup;
16 |
17 | } else if ( (get !== undefined && get !== "") || getIsNumeric ) {
18 | arg = get;
19 | }
20 |
21 | return arg;
22 | };
23 |
24 | var getProcessedArgs = function ( args, ctx ) {
25 | var arg1 = args[ 0 ],
26 | arg2 = args[ 1 ];
27 |
28 | // One of the args is a string literal
29 | if ( rQuotes.test( arg1 ) ) {
30 | arg1 = String( arg1.replace( rQuotes, "" ) );
31 | arg2 = ( ctx._LookUpStack( arg2 ) || ctx.get( arg2 ) );
32 |
33 | } else if ( rQuotes.test( arg2 ) ) {
34 | arg2 = String( arg2.replace( rQuotes, "" ) );
35 | arg1 = ( ctx._LookUpStack( arg1 ) || ctx.get( arg1 ) );
36 |
37 | // Just make sure we don't mess up numeric values
38 | } else {
39 | if ( !isNumeric( arg1 ) ) {
40 | arg1 = getLookupArg( ctx, arg1 );
41 | }
42 |
43 | if ( !isNumeric( arg2 ) ) {
44 | arg2 = getLookupArg( ctx, arg2 );
45 | }
46 | }
47 |
48 | return [arg1, arg2];
49 | };
50 |
51 | var getArgsProcessed = function ( args, ctx ) {
52 | var arg1 = args[ 0 ],
53 | arg2 = args[ 1 ];
54 |
55 | // Special Case
56 | // {.equal? typeName "collection"}
57 | // {.equal? title "collection"}
58 | // {...and so on...}
59 | // Cannot use `collection` string with the lookup stack.
60 | // It will return the {collection object} rather than a lookup value.
61 | if ( String( arg2.replace( rQuotes, "" ) ) === "collection" ) {
62 | arg1 = getLookupArg( ctx, arg1 );
63 |
64 | // Use Cases
65 | // {.equal? foo.bar "baz"}
66 | // {.equal? foo "bar"}
67 | // {.equal? @index 5}
68 | // {.equal? "foo" foo.bar}
69 | // {... and so on...}
70 | } else {
71 | args = getProcessedArgs( args, ctx );
72 | arg1 = args[ 0 ];
73 | arg2 = args[ 1 ];
74 | }
75 |
76 | return args;
77 | };
78 |
79 | module.exports = {
80 | rQuotes: rQuotes,
81 | isNumeric: isNumeric,
82 | getLookupArg: getLookupArg,
83 | //getProcessedArgs: getProcessedArgs,
84 | getArgsProcessed: getArgsProcessed
85 | };
--------------------------------------------------------------------------------
/lib/formatters/index.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Squarespace formatters.
4 | *
5 | * http://jsont.squarespace.com/
6 | * http://developers.squarespace.com/json-formatters/
7 | *
8 | */
9 | var defaultFormatters = [
10 | "html",
11 | "htmltag",
12 | "html-attr-value",
13 | "str",
14 | "raw",
15 | "AbsUrl",
16 | "plain-url"
17 | ],
18 | formatters = {
19 | "count": require( "./count" ),
20 | "date": require( "./date" ),
21 | "timesince": require( "./timesince" ),
22 | "item-classes": require( "./item-classes" ),
23 | "image": require( "./image" ),
24 | "image-meta": require( "./image-meta" ),
25 | "json": require( "./json" ),
26 | "json-pretty": require( "./json-pretty" ),
27 | "slugify": require( "./slugify" ),
28 | "url-encode": require( "./url-encode" ),
29 | "htmlattr": require( "./htmlattr" ),
30 | "htmltag": require( "./htmltag" ),
31 | "activate-twitter-links": require( "./activate-twitter-links" ),
32 | "safe": require( "./safe" ),
33 | "social-button": require( "./social-button" ),
34 | "social-button-inline": require( "./social-button-inline" ),
35 | "comments": require( "./comments" ),
36 | "comment-link": require( "./comment-link" ),
37 | "comment-count": require( "./comment-count" ),
38 | "like-button": require( "./like-button" ),
39 | "product-price": require( "./product-price" ),
40 | "product-status": require( "./product-status" ),
41 | "product-checkout": require( "./product-checkout" ),
42 | "pluralize": require( "./pluralize" ),
43 | "smartypants": require( "./smartypants" )
44 | };
45 |
46 |
47 |
48 | module.exports = function ( arg ) {
49 | var index = arg.indexOf( " " ),
50 | formatter = null,
51 | args = null;
52 |
53 | if ( index === -1 ) {
54 | formatter = arg;
55 | args = [];
56 |
57 | } else {
58 | formatter = arg.substr( 0, index );
59 | args = arg.substr( index + 1 ).split( " " );
60 | }
61 |
62 | // Test unique case on original arg:
63 | // pluralize/It depends/They depend
64 | // http://jsont.squarespace.com/#Pluralize
65 | if ( /^pluralize\//.test( arg ) ) {
66 | formatter = "pluralize";
67 | args = arg.split( "/" ).slice( 1 );
68 | }
69 |
70 | // Only run if formatter is NOT a JSONT default internal
71 | if ( defaultFormatters.indexOf( formatter ) === -1 ) {
72 | // Wrapper function for passing along to correct formatter
73 | return function ( string, context ) {
74 | var func = formatters[ formatter ],
75 | ret = false;
76 |
77 | if ( typeof func === "function" ) {
78 | // Normalize `args` as an Array excluding `formatter`
79 | ret = func( string, args, context );
80 | }
81 |
82 | return ret;
83 | };
84 | }
85 | };
--------------------------------------------------------------------------------
/lib/predicates/index.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Squarespace predicates.
4 | *
5 | * http://jsont.squarespace.com/
6 | * http://jsont.squarespace.com/scoping-example/
7 | * http://developers.squarespace.com/quick-reference/
8 | *
9 | */
10 | var defaultPredicates = [
11 | "singular",
12 | "plural",
13 | "singular?",
14 | "plural?",
15 | "Debug?"
16 | ],
17 | predicates = {
18 | "main-image?": require( "./main-image" ),
19 | "excerpt?": require( "./excerpt" ),
20 | "comments?": require( "./comments" ),
21 | "even?": require( "./even" ),
22 | "odd?": require( "./odd" ),
23 | "equal?": require( "./equal" ),
24 | "collection?": require( "./collection" ),
25 | "external-link?": require( "./external-link" ),
26 | "folder?": require( "./folder" ),
27 | "location?": require( "./location" ),
28 | "event?": require( "./event" ),
29 | "disqus?": require( "./disqus" ),
30 | "video?": require( "./video" ),
31 | "collectionTypeNameEquals?": require( "./collectionTypeNameEquals" ),
32 | "image?": require( "./image" ),
33 | "passthrough?": require( "./passthrough" ),
34 | "index?": require( "./index-predicate" ),
35 | "greaterThan?": require( "./greaterThan" ),
36 | "greaterThanOrEqual?": require( "./greaterThanOrEqual" ),
37 | "lessThan?": require( "./lessThan" ),
38 | "lessThanOrEqual?": require( "./lessThanOrEqual" )
39 | };
40 |
41 |
42 |
43 | module.exports = function ( arg ) {
44 | // get index for predicate, with handling for equal with non-space delimiters
45 | var index = arg.indexOf(arg.split('?')[1] ? arg.split('?')[1][0] : ' '),
46 | predicate = null,
47 | args = null;
48 |
49 | if ( index === -1 ) {
50 | predicate = arg;
51 | args = [];
52 |
53 | } else {
54 | predicate = arg.substr( 0, index );
55 |
56 | //get delimiter for equal if not a space
57 | var delimiter = arg.split('?')[1] ? arg.split('?')[1][0] : ' ';
58 | var reg1 = new RegExp('^\\s+|\\s+$|' + delimiter, "g");
59 | var reg2 = new RegExp('\\s|' + delimiter, "g");
60 |
61 | // Clean predicate out of args now
62 | arg = arg.replace( predicate, "" ).replace( /^\s+|\s+$/g, "" );
63 |
64 | // Filter and normalize the arguments list
65 | args = arg.split( (/\"/.test( arg ) ? /\"(.*?)\"/g : /\s/g) )
66 | .map(function ( el ) {
67 | return el.replace(reg1, "" );
68 | })
69 | .filter(function ( el ) {
70 | var tmp = el.replace(reg2, "" );
71 |
72 | return ( tmp !== "" );
73 | });
74 | }
75 |
76 | // Only run if predicate is NOT a JSONT default internal
77 | if ( defaultPredicates.indexOf( predicate ) === -1 ) {
78 | // Wrapper function for passing along to correct predicate
79 | return function ( data, context ) {
80 | var func = predicates[ predicate ],
81 | ret = false;
82 |
83 | // Capture {.if} conditions
84 | if ( !/\?$/.test( predicate ) ) {
85 | ret = context.get( arg );
86 |
87 | } else if ( typeof func === "function" ) {
88 | // Normalize `args` as an Array excluding `predicate`
89 | ret = func( data, context, args );
90 | }
91 |
92 | return ret;
93 | };
94 | }
95 | };
96 |
--------------------------------------------------------------------------------
/lib/jsontemplate.js:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2009 Andy Chu
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //
16 | // JavaScript implementation of json-template.
17 | //
18 |
19 | // This is predefined in tests, shouldn't be defined anywhere else. TODO: Do
20 | // something nicer.
21 | var log = log || function() {};
22 | var repr = repr || function() {};
23 |
24 |
25 | // The "module" exported by this script is called "jsontemplate":
26 |
27 | var jsontemplate = function() {
28 |
29 |
30 | // Regex escaping for metacharacters
31 | function EscapeMeta(meta) {
32 | return meta.replace(/([\{\}\(\)\[\]\|\^\$\-\+\?])/g, '\\$1');
33 | }
34 |
35 | var token_re_cache = {};
36 |
37 | function _MakeTokenRegex(meta_left, meta_right) {
38 | var key = meta_left + meta_right;
39 | var regex = token_re_cache[key];
40 | if (regex === undefined) {
41 | // there must at least one character inside {}, and the first one must be a
42 | // non-space, to be accepting of "function() { return {@}; }"
43 | var str = '(' + EscapeMeta(meta_left) + '\\S.*?' + EscapeMeta(meta_right) +
44 | '\n?)';
45 | regex = new RegExp(str, 'g');
46 | }
47 | return regex;
48 | }
49 |
50 | //
51 | // Formatters
52 | //
53 |
54 | function HtmlEscape(s) {
55 | return s.replace(/&/g,'&').
56 | replace(/>/g,'>').
57 | replace(//g,'>').
63 | replace(/ 1) ? p : s;
95 | }
96 |
97 | function _Cycle(value, unused_context, args) {
98 | // Cycle between various values on consecutive integers.
99 | // @index starts from 1, so use 1-based indexing.
100 | return args[(value - 1) % args.length];
101 | }
102 |
103 | var DEFAULT_FORMATTERS = {
104 | 'html': HtmlEscape,
105 | 'htmltag': HtmlTagEscape,
106 | 'html-attr-value': HtmlTagEscape,
107 | 'str': ToString,
108 | 'raw': function(x) { return x; },
109 | 'AbsUrl': function(value, context) {
110 | // TODO: Normalize leading/trailing slashes
111 | return context.get('base-url') + '/' + value;
112 | },
113 | 'plain-url': function(x) {
114 | return '' + HtmlEscape(x) + '' ;
115 | }
116 | };
117 |
118 | var _TestAttribute = function(unused, context, args) {
119 | var name = args[0];
120 | if (name === undefined) {
121 | throw { name: 'EvaluationError',
122 | message: 'The "test" predicate requires an argument.' };
123 | }
124 | try {
125 | return context.get(name);
126 | } catch(err) {
127 | if (err.name == 'UndefinedVariable') {
128 | return false;
129 | } else {
130 | throw err;
131 | }
132 | }
133 | };
134 |
135 | var singular = function(x) { return x == 1; };
136 | var plural = function(x) { return x > 1; };
137 | var DEFAULT_PREDICATES = {
138 | 'singular': singular,
139 | 'plural': plural,
140 |
141 | // OLD: do not use
142 | 'singular?': singular,
143 | 'plural?': plural,
144 | 'Debug?': function(unused, context) {
145 | return _TestAttribute(unused, context, ['debug']);
146 | }
147 | };
148 |
149 | var FunctionRegistry = function() {
150 | return {
151 | lookup: function(user_str) {
152 | return [null, null];
153 | }
154 | };
155 | };
156 |
157 | var SimpleRegistry = function(obj) {
158 | return {
159 | lookup: function(user_str) {
160 | var func = obj[user_str] || null;
161 | return [func, null];
162 | }
163 | };
164 | };
165 |
166 | var CallableRegistry = function(callable) {
167 | return {
168 | lookup: function(user_str) {
169 | var func = callable(user_str);
170 | return [func, null];
171 | }
172 | };
173 | };
174 |
175 | // Default formatters which can't be expressed in DEFAULT_FORMATTERS
176 | var PrefixRegistry = function(functions) {
177 | return {
178 | lookup: function(user_str) {
179 | for (var i = 0; i < functions.length; i++) {
180 | var name = functions[i].name, func = functions[i].func;
181 | if (user_str.slice(0, name.length) == name) {
182 | // Delimiter is usually a space, but could be something else
183 | var args;
184 | var splitchar = user_str.charAt(name.length);
185 | if (splitchar === '') {
186 | args = []; // No arguments
187 | } else {
188 | args = user_str.split(splitchar).slice(1);
189 | }
190 | return [func, args];
191 | }
192 | }
193 | return [null, null]; // No formatter
194 | }
195 | };
196 | };
197 |
198 | var ChainedRegistry = function(registries) {
199 | return {
200 | lookup: function(user_str) {
201 | for (var i=0; i 1) {
236 | name = name_split.shift();
237 | new_context = stack[stack.length-1].context[name];
238 | while (name_split.length) {
239 | name = name_split.shift();
240 | new_context = new_context[name] || null;
241 | }
242 | } else {
243 | new_context = stack[stack.length-1].context[name] || null;
244 | }
245 | }
246 | stack.push({context: new_context, index: -1});
247 | return new_context;
248 | },
249 |
250 | pop: function() {
251 | stack.pop();
252 | },
253 |
254 | next: function() {
255 | var stacktop = stack[stack.length-1];
256 |
257 | // Now we're iterating -- push a new mutable object onto the stack
258 | if (stacktop.index == -1) {
259 | stacktop = {context: null, index: 0};
260 | stack.push(stacktop);
261 | }
262 |
263 | // The thing we're iterating over
264 | var context_array = stack[stack.length-2].context;
265 |
266 | // We're already done
267 | if (stacktop.index == context_array.length) {
268 | stack.pop();
269 | return undefined; // sentinel to say that we're done
270 | }
271 |
272 | stacktop.context = context_array[stacktop.index++];
273 | return true; // OK, we mutated the stack
274 | },
275 |
276 | _Undefined: function() {
277 | return (undefined_str === undefined) ? undefined : undefined_str;
278 | },
279 |
280 | _LookUpStack: function(name) {
281 | var i = stack.length - 1;
282 | while (true) {
283 | var frame = stack[i];
284 | if (name == '@index') {
285 | if (frame.index != -1) { // -1 is undefined
286 | return frame.index;
287 | }
288 | } else {
289 | var context = frame.context;
290 | if (typeof context === 'object') {
291 | var value = context[name];
292 | if (value !== undefined) {
293 | return value;
294 | }
295 | }
296 | }
297 | i--;
298 | if (i <= -1) {
299 | return this._Undefined(name);
300 | }
301 | }
302 | },
303 |
304 | get: function(name) {
305 | if (name == '@') {
306 | return stack[stack.length-1].context;
307 | }
308 | var opts = /\|\|/.test(name) ? name.replace(/\s\|\|\s/g, '||').split('||') : null,
309 | parts = name.split('.'),
310 | value,
311 | ctx;
312 | if (opts) {
313 | ctx = stack[stack.length-1].context;
314 | for (var i=0; i 0) {
503 | // TODO: check that items is an array; apparently this is hard in JavaScript
504 | //if type(items) is not list:
505 | // raise EvaluationError('Expected a list; got %s' % type(items))
506 |
507 | // Execute the statements in the block for every item in the list.
508 | // Execute the alternate block on every iteration except the last. Each
509 | // item could be an atom (string, integer, etc.) or a dictionary.
510 |
511 | var last_index = items.length - 1;
512 | var statements = block.Statements();
513 | var alt_statements = block.Statements('alternate');
514 |
515 | for (var i=0; context.next() !== undefined; i++) {
516 | _Execute(statements, context, callback);
517 | if (i != last_index) {
518 | _Execute(alt_statements, context, callback);
519 | }
520 | }
521 | } else {
522 | _Execute(block.Statements('or'), context, callback);
523 | }
524 |
525 | context.pop();
526 | }
527 |
528 |
529 | var _SECTION_RE = /(repeated)?\s*(section)\s+(\S+)?/;
530 | // Adding carrots - @kitajchuk
531 | var _OR_RE = /^or(?:\s+(.+))?/;
532 | var _IF_RE = /^if(?:\s+(.+))?/;
533 |
534 |
535 | // Turn a object literal, function, or Registry into a Registry
536 | function MakeRegistry(obj) {
537 | if (!obj) {
538 | // if null/undefined, use a totally empty FunctionRegistry
539 | return new FunctionRegistry();
540 | } else if (typeof obj === 'function') {
541 | return new CallableRegistry(obj);
542 | } else if (obj.lookup !== undefined) {
543 | // TODO: Is this a good pattern? There is a namespace conflict where get
544 | // could be either a formatter or a method on a FunctionRegistry.
545 | // instanceof might be more robust.
546 | return obj;
547 | } else if (typeof obj === 'object') {
548 | return new SimpleRegistry(obj);
549 | }
550 | }
551 |
552 | // TODO: The compile function could be in a different module, in case we want to
553 | // compile on the server side.
554 | function _Compile(template_str, options) {
555 | var more_formatters = MakeRegistry(options.more_formatters);
556 |
557 | // default formatters with arguments
558 | var default_formatters = PrefixRegistry([
559 | {name: 'pluralize', func: _Pluralize},
560 | {name: 'cycle', func: _Cycle}
561 | ]);
562 | var all_formatters = new ChainedRegistry([
563 | more_formatters,
564 | SimpleRegistry(DEFAULT_FORMATTERS),
565 | default_formatters
566 | ]);
567 |
568 | var more_predicates = MakeRegistry(options.more_predicates);
569 |
570 | // default predicates with arguments
571 | var default_predicates = PrefixRegistry([
572 | {name: 'test', func: _TestAttribute}
573 | ]);
574 | var all_predicates = new ChainedRegistry([
575 | more_predicates, SimpleRegistry(DEFAULT_PREDICATES), default_predicates
576 | ]);
577 |
578 | // We want to allow an explicit null value for default_formatter, which means
579 | // that an error is raised if no formatter is specified.
580 | var default_formatter;
581 | if (options.default_formatter === undefined) {
582 | default_formatter = 'str';
583 | } else {
584 | default_formatter = options.default_formatter;
585 | }
586 |
587 | function GetFormatter(format_str) {
588 | var pair = all_formatters.lookup(format_str);
589 | if (!pair[0]) {
590 | throw {
591 | name: 'BadFormatter',
592 | message: format_str + ' is not a valid formatter'
593 | };
594 | }
595 | return pair;
596 | }
597 |
598 | function GetPredicate(pred_str, test_attr) {
599 | var pair = all_predicates.lookup(pred_str);
600 | if (!pair[0]) {
601 | if (test_attr) {
602 | pair = [_TestAttribute, [pred_str.slice(null, -1)]];
603 | } else {
604 | throw {
605 | name: 'BadPredicate',
606 | message: pred_str + ' is not a valid predicate'
607 | };
608 | }
609 | }
610 | return pair;
611 | }
612 |
613 | var format_char = options.format_char || '|';
614 | if (format_char != ':' && format_char != '|') {
615 | throw {
616 | name: 'ConfigurationError',
617 | message: 'Only format characters : and | are accepted'
618 | };
619 | }
620 |
621 | var meta = options.meta || '{}';
622 | var n = meta.length;
623 | if (n % 2 == 1) {
624 | throw {
625 | name: 'ConfigurationError',
626 | message: meta + ' has an odd number of metacharacters'
627 | };
628 | }
629 | var meta_left = meta.substring(0, n/2);
630 | var meta_right = meta.substring(n/2, n);
631 |
632 | var token_re = _MakeTokenRegex(meta_left, meta_right);
633 | var current_block = _Section({});
634 | var stack = [current_block];
635 |
636 | var strip_num = meta_left.length; // assume they're the same length
637 |
638 | var token_match;
639 | var last_index = 0;
640 |
641 | while (true) {
642 | token_match = token_re.exec(template_str);
643 | var token;
644 | if (token_match === null) {
645 | break;
646 | } else {
647 | token = token_match[0];
648 | }
649 |
650 | // Add the previous literal to the program
651 | if (token_match.index > last_index) {
652 | var tok = template_str.slice(last_index, token_match.index);
653 | current_block.Append(tok);
654 | }
655 | last_index = token_re.lastIndex;
656 |
657 | var had_newline = false;
658 | if (token.slice(-1) == '\n') {
659 | token = token.slice(null, -1);
660 | had_newline = true;
661 | }
662 |
663 | token = token.slice(strip_num, -strip_num);
664 |
665 | if (token.charAt(0) == '#') {
666 | continue; // comment
667 | }
668 |
669 | if (token.charAt(0) == '.') { // Keyword
670 | token = token.substring(1, token.length);
671 |
672 | var literal = {
673 | 'meta-left': meta_left,
674 | 'meta-right': meta_right,
675 | 'space': ' ',
676 | 'tab': '\t',
677 | 'newline': '\n'
678 | }[token];
679 |
680 | if (literal !== undefined) {
681 | current_block.Append(literal);
682 | continue;
683 | }
684 |
685 | var new_block, func;
686 |
687 | var section_match = token.match(_SECTION_RE);
688 | if (section_match) {
689 | var repeated = section_match[1];
690 | var section_name = section_match[3];
691 |
692 | if (repeated) {
693 | func = _DoRepeatedSection;
694 | new_block = _RepeatedSection({section_name: section_name});
695 | } else {
696 | func = _DoSection;
697 | new_block = _Section({section_name: section_name});
698 | }
699 | current_block.Append([func, new_block]);
700 | stack.push(new_block);
701 | current_block = new_block;
702 | continue;
703 | }
704 |
705 | var pred_str, pred;
706 |
707 | // Check {.or pred?} before {.pred?}
708 | var or_match = token.match(_OR_RE);
709 | if (or_match) {
710 | pred_str = or_match[1];
711 | pred = pred_str ? GetPredicate(pred_str, false) : null;
712 | current_block.NewOrClause(pred);
713 | continue;
714 | }
715 |
716 | // {.if predicate} or {.attr?}
717 | // this is where we need to fix for scope for {.if condition}
718 | // @see http://jsont.squarespace.com/advanced-jsont/ - @kitajchuk
719 | var if_token = false, pred_token = false;
720 | var if_match = token.match(_IF_RE);
721 | if (if_match) {
722 | pred_str = token;
723 | if_token = true;
724 | // also check token split in case of arguments - @kitajchuk
725 | } else {
726 | //get delimiter for equal if not a space
727 | var delimiter = token.split('?')[1] ? token.split('?')[1][0] : ' ';
728 | var token_split = token.split(delimiter).shift(); // split token from arguments - @kitajchuk
729 | if (token.charAt(token.length-1) == '?' || token_split.charAt(token_split.length-1) == '?') {
730 | pred_str = token;
731 | pred_token = true;
732 | }
733 | }
734 | if (if_token || pred_token) {
735 | // test_attr=true if we got the {.attr?} style (pred_token)
736 | pred = pred_str ? GetPredicate(pred_str, pred_token) : null;
737 | new_block = _PredicateSection();
738 | new_block.NewOrClause(pred);
739 | current_block.Append([_DoPredicates, new_block]);
740 | stack.push(new_block);
741 |
742 | current_block = new_block;
743 | continue;
744 | }
745 |
746 | if (token == 'alternates with') {
747 | current_block.AlternatesWith();
748 | continue;
749 | }
750 |
751 | if (token == 'end') {
752 | // End the block
753 | stack.pop();
754 | if (stack.length > 0) {
755 | current_block = stack[stack.length-1];
756 | } else {
757 | throw {
758 | name: 'TemplateSyntaxError',
759 | message: 'Got too many {end} statements'
760 | };
761 | }
762 | continue;
763 | }
764 | }
765 |
766 | // A variable substitution
767 | var parts = token.split(format_char);
768 | var formatters;
769 | var name;
770 | if (parts.length == 1) {
771 | if (default_formatter === null) {
772 | throw {
773 | name: 'MissingFormatter',
774 | message: 'This template requires explicit formatters.'
775 | };
776 | }
777 | // If no formatter is specified, use the default.
778 | formatters = [GetFormatter(default_formatter)];
779 | name = token;
780 | } else {
781 | formatters = [];
782 | for (var j=1; j