From 61c3ed0380fb21ac4c895b5cdc4c2e560968876c Mon Sep 17 00:00:00 2001 From: Anders Hellerup Madsen Date: Mon, 28 Dec 2009 19:05:19 +0100 Subject: [PATCH] More filters --- template/template_defaults.js | 85 ++++++++++--------- template/template_defaults.test.js | 37 ++++++++- utils/string.js | 126 ++++++++++++++++++++++++++--- utils/string.test.js | 7 ++ 4 files changed, 206 insertions(+), 49 deletions(-) diff --git a/template/template_defaults.js b/template/template_defaults.js index 022ad75..f856de2 100644 --- a/template/template_defaults.js +++ b/template/template_defaults.js @@ -1,8 +1,8 @@ "use strict"; -/*jslint laxbreak: true, eqeqeq: true, undef: true, regexp: false */ -/*global require, process, exports */ +/*jslint eqeqeq: true, undef: true, regexp: false */ +/*global require, process, exports, escape */ -var sys = require('sys') +var sys = require('sys'); var template = require('./template'); var utils = require('../utils/utils'); @@ -18,29 +18,27 @@ var utils = require('../utils/utils'); safeseq Not implemented (yet): - slugify - stringformat - striptags time timesince timeuntil - title - truncatewords truncatewords_html unordered_list - upper urlencode urlize urlizetrunc wordcount wordwrap yesno - + +NOTE: + date() filter is not lozalized and has a few gotchas... + stringformat() filter is regular sprintf compliant and doesn't have real python syntax */ var filters = exports.filters = { add: function (value, arg) { - value = value - 0, arg = arg - 0; + value = value - 0; + arg = arg - 0; return (isNaN(value) || isNaN(arg)) ? '' : (value + arg); }, addslashes: function (value, arg) { return utils.string.add_slashes("" + value); }, @@ -105,30 +103,25 @@ var filters = exports.filters = { // TODO: implement iriencode filter throw "iri encoding is not implemented"; }, - join: function (value, arg) { return (value instanceof Array) ? value.join(arg) : '' }, - last: function (value, arg) { return (value instanceof Array && value.length) ? value[value.length - 1] : ''; }, + join: function (value, arg) { return (value instanceof Array) ? value.join(arg) : ''; }, + last: function (value, arg) { return ((value instanceof Array) && value.length) ? value[value.length - 1] : ''; }, length: function (value, arg) { return value.hasOwnProperty('length') ? value.length : 0; }, length_is: function (value, arg) { return value.hasOwnProperty('length') && value.length === arg; }, linebreaks: function (value, arg) { return utils.html.linebreaks("" + value); }, linebreaksbr: function (value, arg) { return "" + value.replace(/\n/g, '
'); }, linenumbers: function (value, arg) { - var lines = ("" + value).split('\n'); - var zeroes = "", len = ("" + lines.length).length; - while (len--) { zeroes += "0"; } - - lines = lines.map( function (s, idx) { - var num = "" + (idx + 1); - return zeroes.slice(0, zeroes.length - num.length) + num + '. ' + s; - }); - return lines.join('\n'); + var lines = String(value).split('\n'); + var len = String(lines.length).length; + return lines + .map(function (s, idx) { return utils.string.sprintf('%0' + len + 'd. %s', idx + 1, s); }) + .join('\n'); }, ljust: function (value, arg) { - if (typeof arg !== 'number') { return ''; } - if (arg <= value.length) { return value.slice(0, arg); } - - var spaces = "", len = arg - value.length; - while (len--) { spaces += ' '; } - return value + spaces; + try { + return utils.string.sprintf('%-' + arg + 's', value).substr(0, arg); + } catch (e) { + return ''; + } }, lower: function (value, arg) { return typeof value === 'string' ? value.toLowerCase() : ''; }, make_list: function (value, arg) { return String(value).split(''); }, @@ -152,15 +145,16 @@ var filters = exports.filters = { return (value instanceof Array) ? value[ Math.floor( Math.random() * 4 ) ] : ''; }, removetags: function (value, arg) { - return String(value).replace(/<(.|\n)*?>/g, ''); + arg = String(arg).replace(/\s+/g, '|'); + var re = new RegExp( ']*/?>', 'ig'); + return String(value).replace(re, ''); }, rjust: function (value, arg) { - if (typeof arg !== 'number') { return ''; } - if (arg <= value.length) { return value.slice(0, arg); } - - var spaces = "", len = arg - value.length; - while (len--) { spaces += ' '; } - return spaces + value; + try { + return utils.string.sprintf('%' + arg + 's', value).substr(0, arg); + } catch (e) { + return ''; + } }, safe: function (value, arg) { // TODO: implement autoescaping @@ -189,10 +183,27 @@ var filters = exports.filters = { return out; }, + slugify: function (value, arg) { + return String(value).toLowerCase().replace(/[^\w\s]/g, '').replace(/\s+/g, '-'); + }, + stringformat: function (value, arg) { + try { return utils.string.sprintf('%' + arg, value); } catch (e) { return ''; } + }, + striptags: function (value, arg) { + return String(value).replace(/<(.|\n)*?>/g, ''); + }, title: function (value, arg) { - throw "Not implemented"; /* http://ejohn.org/blog/title-capitalization-in-javascript/ */ + return utils.string.titleCaps( String(value) ); + }, + truncatewords: function (value, arg) { + return String(value).split(/\s+/g).slice(0, arg).join(' ') + ' ...'; + }, + upper: function (value, arg) { + return (value + '').toUpperCase(); + }, + urlencode: function (value, arg) { + return escape(value); } - }; diff --git a/template/template_defaults.test.js b/template/template_defaults.test.js index 9e517ab..a04e8b0 100644 --- a/template/template_defaults.test.js +++ b/template/template_defaults.test.js @@ -248,7 +248,8 @@ testcase('random'); }); testcase('removetags'); test('should remove tags', function () { - assertEquals('jeg har en dejlig hest.', filters.removetags('

jeg har en dejlig hest.

')); + assertEquals('Joel a slug', + filters.removetags('Joel a slug', 'b span')); }); testcase('rjust') test('should right justify value in correctly sized field', function () { @@ -270,6 +271,38 @@ testcase('slice') assertEquals([0,1,2,3,4,5,6,7,8,9], filters.slice(arr, 'hest')); assertEquals([0,1,2,3,4,5,6,7,8,9], filters.slice(arr)); }); - +testcase('slugify'); + test('should slugify correctly', function () { + assertEquals('joel-is-a-slug', filters.slugify('Joel is a slug')); + assertEquals('s-str-verden-da-ikke-lngere', filters.slugify('Så står Verden da ikke længere!')); + assertEquals('super_max', filters.slugify('Super_Max')); + }); +testcase('stringformat'); + test('return expected results', function () { + assertEquals('002', filters.stringformat(2, '03d')); + assertEquals('Hest', filters.stringformat('Hest', 's')); + assertEquals('', filters.stringformat('Hest', '')); + assertEquals('Hest ', filters.stringformat('Hest', '-10s')); + }); +testcase('striptags'); + test('should remove tags', function () { + assertEquals('jeg har en dejlig hest.', filters.striptags('

jeg har en dejlig hest.

')); + }); +testcase('title'); + test('should titlecase correctly', function () { + assertEquals('This Is Correct', filters.title('This is correct')); + }); +testcase('truncatewords'); + test('should truncate', function () { + assertEquals('Joel is ...', filters.truncatewords('Joel is a slug', 2)); + }); +testcase('upper'); + test('should uppercase correctly', function () { + assertEquals('JOEL IS A SLUG', filters.upper('Joel is a slug')); + }); +testcase('urlencode'); + test('should encode urls', function () { + assertEquals('%22Aardvarks%20lurk%2C%20OK%3F%22', filters.urlencode('"Aardvarks lurk, OK?"')); + }); run(); diff --git a/utils/string.js b/utils/string.js index fe35a5c..3a8bd97 100644 --- a/utils/string.js +++ b/utils/string.js @@ -40,20 +40,126 @@ function cap_first(s) { } exports.cap_first = cap_first; -function center(s, width) { - if (s.length > width) { return s; } - var right = Math.round((width - s.length) / 2); - var left = width - (s.length + right); - var out = ''; - while (left--) { out += ' '; } - out += s; - while (right--) { out += ' '; } +/************************************************************************* +* sprintf() and str_repeat() from http://code.google.com/p/sprintf/ +*/ - return out; +/** + * sprintf() for JavaScript v.0.4 + * + * Copyright (c) 2007 Alexandru Marasteanu + * Thanks to David Baird (unit test and patch). + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA + */ + +function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); } + +function sprintf () { + var i = 0, a, f = arguments[i++], o = [], m, p, c, x; + while (f) { + if (m = /^[^\x25]+/.exec(f)) o.push(m[0]); + else if (m = /^\x25{2}/.exec(f)) o.push('%'); + else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) { + if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments."); + if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) + throw("Expecting number but found " + typeof(a)); + switch (m[7]) { + case 'b': a = a.toString(2); break; + case 'c': a = String.fromCharCode(a); break; + case 'd': a = parseInt(a); break; + case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break; + case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break; + case 'o': a = a.toString(8); break; + case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break; + case 'u': a = Math.abs(a); break; + case 'x': a = a.toString(16); break; + case 'X': a = a.toString(16).toUpperCase(); break; + } + a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a); + c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' '; + x = m[5] - String(a).length; + p = m[5] ? str_repeat(c, x) : ''; + o.push(m[4] ? a + p : p + a); + } + else throw ("Huh ?!"); + f = f.substring(m[0].length); + } + return o.join(''); } -exports.center = center; +/*************************************************************************/ + +/************************************************************************* +* titleCaps from http://ejohn.org/files/titleCaps.js (by John Resig) +*/ + +var small = "(a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|v[.]?|via|vs[.]?)"; +var punct = "([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]*)"; +var titleCaps = function(title){ + var parts = [], split = /[:.;?!] |(?: |^)["Ò]/g, index = 0; + + while (true) { + var m = split.exec(title); + parts.push( title.substring(index, m ? m.index : title.length) + .replace(/\b([A-Za-z][a-z.'Õ]*)\b/g, function(all){ + return /[A-Za-z]\.[A-Za-z]/.test(all) ? all : upper(all); + }) + .replace(RegExp("\\b" + small + "\\b", "ig"), lower) + .replace(RegExp("^" + punct + small + "\\b", "ig"), function(all, punct, word){ + return punct + upper(word); + }) + .replace(RegExp("\\b" + small + punct + "$", "ig"), upper)); + + index = split.lastIndex; + + if ( m ) parts.push( m[0] ); + else break; + } + + return parts.join("").replace(/ V(s?)\. /ig, " v$1. ") + .replace(/(['Õ])S\b/ig, "$1s") + .replace(/\b(AT&T|Q&A)\b/ig, function(all){ + return all.toUpperCase(); + }); +}; + +function lower(word){ + return word.toLowerCase(); +} + +function upper(word){ + return word.substr(0,1).toUpperCase() + word.substr(1); +} + +exports.titleCaps = titleCaps; + + +/*************************************************************************/ +exports.sprintf = sprintf; +exports.str_repeat = str_repeat; + + +function center(s, width) { + if (s.length > width) { return s; } + var right = Math.round((width - s.length) / 2); + var left = width - (s.length + right); + return str_repeat(' ', left) + s + str_repeat(' ', right); +} +exports.center = center; diff --git a/utils/string.test.js b/utils/string.test.js index cf2d867..fedb055 100644 --- a/utils/string.test.js +++ b/utils/string.test.js @@ -18,5 +18,12 @@ testcase('string utility functions'); assertEquals(' centered ', center('centered', 17)); assertEquals('centered', center('centered', 3)); }); +testcase('titleCaps') + test('should work as expected', function () { + assertEquals("Nothing to Be Afraid Of?", titleCaps("Nothing to Be Afraid of?")); + assertEquals("Q&A With Steve Jobs: 'That's What Happens in Technology'", + titleCaps("Q&A With Steve Jobs: 'That's What Happens In Technology'") + ); + }) run();