Skip to content

Loading…

Added test to highlight View bug #174

Closed
wants to merge 1 commit into from

2 participants

@juzerali

Test and upcoming solution for #173

@nateps

Thanks for submitting, and sorry I never got a chance to review on the previous Derby code. Derby has needed a complete overhaul internally for some time, so I'm closing all pull requests for previous version of Derby.

Master of Derby is now 0.6 and I am cleaning up previous issues so that I can be more responsive to new issues.

@nateps nateps closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 26, 2012
  1. @juzerali
Showing with 301 additions and 0 deletions.
  1. +292 −0 bin/src/derby.js
  2. +9 −0 test/View.mocha.coffee
View
292 bin/src/derby.js
@@ -0,0 +1,292 @@
+// Generated by CoffeeScript 1.3.3
+var ANSI_CODES, APP_COFFEE, APP_HTML, APP_JS, APP_STYL, BASE_STYL, CONNECTION_ALERT_HTML, CONNECTION_ALERT_JS, GITIGNORE_COFFEE, GITIGNORE_JS, MAKEFILE_COFFEE, README, RESET_STYL, SERVER, SERVER_COFFEE, SERVER_ERROR_COFFEE, SERVER_ERROR_JS, SERVER_JS, UI_JS, UI_STYL, abort, basename, createProject, derby, emptyDirectory, exec, fs, join, logWrite, mkdir, mkdirp, newProject, packageJson, printUsage, program, render, resolve, style, styleTag, write, _404_HTML, _404_STYL, _ref;
+
+exec = require('child_process').exec;
+
+program = require('commander');
+
+mkdirp = require('mkdirp');
+
+fs = require('fs');
+
+_ref = require('path'), join = _ref.join, resolve = _ref.resolve, basename = _ref.basename;
+
+derby = require('../../lib/derby');
+
+APP_COFFEE = 'derby = require \'derby\'\n{get, view, ready} = derby.createApp module\nderby.use(require \'../../ui\')\n\n\n## ROUTES ##\n\nstart = +new Date()\n\n# Derby routes can be rendered on the client and the server\nget \'/:roomName?\', (page, model, {roomName}) ->\n roomName ||= \'home\'\n\n # Subscribes the model to any updates on this room\'s object. Calls back\n # with a scoped model equivalent to:\n # room = model.at "rooms.#{roomName}"\n model.subscribe "rooms.#{roomName}", (err, room) ->\n model.ref \'_room\', room\n\n # setNull will set a value if the object is currently null or undefined\n room.setNull \'welcome\', "Welcome to #{roomName}!"\n\n room.incr \'visits\'\n\n # This value is set for when the page initially renders\n model.set \'_timer\', \'0.0\'\n # Reset the counter when visiting a new route client-side\n start = +new Date()\n\n # Render will use the model data as well as an optional context object\n page.render\n roomName: roomName\n randomUrl: parseInt(Math.random() * 1e9).toString(36)\n\n\n## CONTROLLER FUNCTIONS ##\n\nready (model) ->\n timer = null\n\n # Functions on the app can be bound to DOM events using the "x-bind"\n # attribute in a template.\n @stop = ->\n # Any path name that starts with an underscore is private to the current\n # client. Nothing set under a private path is synced back to the server.\n model.set \'_stopped\', true\n clearInterval timer\n\n do @start = ->\n model.set \'_stopped\', false\n timer = setInterval ->\n model.set \'_timer\', (((+new Date()) - start) / 1000).toFixed(1)\n , 100\n';
+
+APP_JS = 'var derby = require(\'derby\')\n , <<app>> = derby.createApp(module)\n , get = <<app>>.get\n , view = <<app>>.view\n , ready = <<app>>.ready\n , start = +new Date()\n\nderby.use(require(\'../../ui\'))\n\n\n// ROUTES //\n\n// Derby routes can be rendered on the client and the server\nget(\'/:roomName?\', function(page, model, params) {\n var roomName = params.roomName || \'home\'\n\n // Subscribes the model to any updates on this room\'s object. Calls back\n // with a scoped model equivalent to:\n // room = model.at(\'rooms.\' + roomName)\n model.subscribe(\'rooms.\' + roomName, function(err, room) {\n model.ref(\'_room\', room)\n\n // setNull will set a value if the object is currently null or undefined\n room.setNull(\'welcome\', \'Welcome to \' + roomName + \'!\')\n\n room.incr(\'visits\')\n\n // This value is set for when the page initially renders\n model.set(\'_timer\', \'0.0\')\n // Reset the counter when visiting a new route client-side\n start = +new Date()\n\n // Render will use the model data as well as an optional context object\n page.render({\n roomName: roomName\n , randomUrl: parseInt(Math.random() * 1e9).toString(36)\n })\n })\n})\n\n\n// CONTROLLER FUNCTIONS //\n\nready(function(model) {\n var timer\n\n // Functions on the app can be bound to DOM events using the "x-bind"\n // attribute in a template.\n this.stop = function() {\n // Any path name that starts with an underscore is private to the current\n // client. Nothing set under a private path is synced back to the server.\n model.set(\'_stopped\', true)\n clearInterval(timer)\n }\n\n this.start = function() {\n model.set(\'_stopped\', false)\n timer = setInterval(function() {\n model.set(\'_timer\', (((+new Date()) - start) / 1000).toFixed(1))\n }, 100)\n }\n this.start()\n\n})\n';
+
+SERVER_COFFEE = 'http = require \'http\'\npath = require \'path\'\nexpress = require \'express\'\ngzippo = require \'gzippo\'\nderby = require \'derby\'\n<<app>> = require \'../<<app>>\'\nserverError = require \'./serverError\'\n\n\n## SERVER CONFIGURATION ##\n\nexpressApp = express()\nserver = module.exports = http.createServer expressApp\n\nderby.use(derby.logPlugin)\nstore = derby.createStore listen: server\n\nONE_YEAR = 1000 * 60 * 60 * 24 * 365\nroot = path.dirname path.dirname __dirname\npublicPath = path.join root, \'public\'\n\nexpressApp\n .use(express.favicon())\n # Gzip static files and serve from memory\n .use(gzippo.staticGzip publicPath, maxAge: ONE_YEAR)\n # Gzip dynamically rendered content\n .use(express.compress())\n\n # Uncomment to add form data parsing support\n .use(express.bodyParser())\n .use(express.methodOverride())\n\n # Uncomment and supply secret to add Derby session handling\n # Derby session middleware creates req.session and socket.io sessions\n # .use(express.cookieParser())\n # .use(store.sessionMiddleware\n # secret: process.env.SESSION_SECRET || \'YOUR SECRET HERE\'\n # cookie: {maxAge: ONE_YEAR}\n # )\n\n # Adds req.getModel method\n .use(store.modelMiddleware())\n # Creates an express middleware from the app\'s routes\n .use(<<app>>.router())\n .use(expressApp.router)\n .use(serverError root)\n\n\n## SERVER ONLY ROUTES ##\n\nexpressApp.all \'*\', (req) ->\n throw "404: #{req.url}"\n';
+
+SERVER_JS = 'var http = require(\'http\')\n , path = require(\'path\')\n , express = require(\'express\')\n , gzippo = require(\'gzippo\')\n , derby = require(\'derby\')\n , <<app>> = require(\'../<<app>>\')\n , serverError = require(\'./serverError\')\n\n\n// SERVER CONFIGURATION //\n\nvar expressApp = express()\n , server = module.exports = http.createServer(expressApp)\n\nderby.use(derby.logPlugin)\nvar store = derby.createStore({listen: server})\n\nvar ONE_YEAR = 1000 * 60 * 60 * 24 * 365\n , root = path.dirname(path.dirname(__dirname))\n , publicPath = path.join(root, \'public\')\n\nexpressApp\n .use(express.favicon())\n // Gzip static files and serve from memory\n .use(gzippo.staticGzip(publicPath, {maxAge: ONE_YEAR}))\n // Gzip dynamically rendered content\n .use(express.compress())\n\n // Uncomment to add form data parsing support\n // .use(express.bodyParser())\n // .use(express.methodOverride())\n\n // Uncomment and supply secret to add Derby session handling\n // Derby session middleware creates req.model and subscribes to _session\n // .use(express.cookieParser())\n // .use(store.sessionMiddleware({\n // secret: process.env.SESSION_SECRET || \'YOUR SECRET HERE\'\n // , cookie: {maxAge: ONE_YEAR}\n // }))\n\n // Adds req.getModel method\n .use(store.modelMiddleware())\n // Creates an express middleware from the app\'s routes\n .use(<<app>>.router())\n .use(expressApp.router)\n .use(serverError(root))\n\n\n// SERVER ONLY ROUTES //\n\nexpressApp.all(\'*\', function(req) {\n throw \'404: \' + req.url\n})\n';
+
+SERVER_ERROR_JS = 'var derby = require(\'derby\')\n , isProduction = derby.util.isProduction\n\nmodule.exports = function(root) {\n var staticPages = derby.createStatic(root)\n\n return function(err, req, res, next) {\n if (err == null) return next()\n\n console.log(err.stack ? err.stack : err)\n\n // Customize error handling here\n var message = err.message || err.toString()\n , status = parseInt(message)\n if (status === 404) {\n staticPages.render(\'404\', res, {url: req.url}, 404)\n } else {\n res.send( ((status >= 400) && (status < 600)) ? status : 500)\n }\n }\n}\n';
+
+SERVER_ERROR_COFFEE = 'derby = require \'derby\'\n{isProduction} = derby.util\n\nmodule.exports = (root) ->\n staticPages = derby.createStatic root\n\n return (err, req, res, next) ->\n return next() unless err?\n\n console.log(if err.stack then err.stack else err)\n\n ## Customize error handling here ##\n message = err.message || err.toString()\n status = parseInt message\n if status is 404\n staticPages.render \'404\', res, {url: req.url}, 404\n else\n res.send if 400 <= status < 600 then status else 500\n';
+
+CONNECTION_ALERT_JS = 'exports.connect = function() {\n model = this.model\n // Hide the reconnect link for a second after clicking it\n model.set(\'hideReconnect\', true)\n setTimeout(function() {\n model.set(\'hideReconnect\', false)\n }, 1000)\n model.socket.socket.connect()\n}\n\nexports.reload = function() {\n window.location.reload()\n}\n';
+
+UI_JS = 'var config = {\n filename: __filename\n, styles: \'../styles/ui\'\n, scripts: {\n connectionAlert: require(\'./connectionAlert\')\n }\n};\n\nmodule.exports = ui\nui.decorate = \'derby\'\n\nfunction ui(derby, options) {\n derby.createLibrary(config, options)\n}\n';
+
+APP_HTML = '<!--\n Derby templates are similar to Handlebars, except that they are first\n parsed as HTML, and there are a few extensions to make them work directly\n with models. A single HTML template defines the HTML output, the event\n handlers that update the model after user interaction, and the event handlers\n that update the DOM when the model changes.\n\n As in Handlebars, double curly braces output a value literally. Derby\n templates add single curly braces, which output a value and set up\n model <- -> view bindings for that object.\n\n Elements that end in colon define template names. Pre-defined templates\n are capitalized by convention, but template names are case-insensitive.\n Pre-defined templates are automatically included when the page is rendered.\n-->\n\n<Title:>\n {{roomName}} - {_room.visits} visits\n\n<Header:>\n <!-- This is a component defined in the /ui directory -->\n <ui:connectionAlert>\n\n<Body:>\n <h1>{_room.welcome}</h1>\n <p><label>Welcome message: <input value="{_room.welcome}"></label></p>\n\n <!-- Other templates are referenced like HTML elements -->\n <p>This page has been visited {_room.visits} times. <app:timer></p>\n\n <p>Let\'s go <a href="/{{randomUrl}}">somewhere random</a>.</p>\n\n<timer:>\n {#if _stopped}\n <a x-bind="click:start">Start timer</a>\n {else}\n You have been here for {_timer} seconds. <a x-bind="click:stop">Stop</a>\n {/}\n';
+
+_404_HTML = '<!--\n This is a static template file, so it doesn\'t have an associated app.\n It is rendered by the server via a staticPages renderer.\n\n Since static pages don\'t include the Derby client library, they can\'t have\n bound variables that automatically update. However, they do support initial\n template tag rendering from a context object and/or model.\n-->\n\n<Title:>\n Not found\n\n<Body:>\n <h1>404</h1>\n <p>Sorry, we can\'t find anything at <b>{{url}}</b>.\n <p>Try heading back to the <a href="/">home page</a>.\n';
+
+CONNECTION_ALERT_HTML = '<connectionAlert:>\n <div class="connection">\n <!--\n connected and canConnect are built-in properties of model. If a variable\n is not defined in the current context, it will be looked up in the model\n data and the model properties\n -->\n {#unless connected}\n <p class="alert">\n {#if canConnect}\n <!-- Leading space is removed, and trailing space is maintained -->\n Offline \n <!-- a :self path alias is automatically created per component -->\n {#unless :self.hideReconnect}\n &ndash; <a x-bind="click:connect">Reconnect</a>\n {/}\n {else}\n Unable to reconnect &ndash; <a x-bind="click:reload">Reload</a>\n {/}\n </p>\n {/}\n </div>\n';
+
+RESET_STYL = 'body,h1,h2,h3,h4,th {\n font: 13px/normal Arial,sans-serif;\n}\nbody {\n background: #fff;\n color: #000;\n}\nbody,fieldset,form,h1,h2,h3,h4,li,ol,p,td,th,ul {\n margin: 0;\n padding: 0;\n}\nul {\n margin: 0 normal;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\nfieldset,img {\n border: 0;\n}\n';
+
+BASE_STYL = '@import "./reset";\n@import "nib/vendor";\n\nbody {\n padding: 2em;\n}\nh1 {\n font-size: 2em;\n margin-bottom: .5em;\n}\np {\n line-height: 2em;\n}\n';
+
+APP_STYL = '@import "../base";\n';
+
+_404_STYL = '@import "./base";\n';
+
+UI_STYL = '.connection {\n position: absolute;\n text-align: center;\n top: 0;\n left: 0;\n width: 100%;\n height: 0;\n z-index: 99;\n}\n.connection > .alert {\n background: #fff1a8;\n border: 1px solid #999;\n border-top: 0;\n border-radius: 0 0 3px 3px;\n display: inline-block;\n line-height: 21px;\n padding: 0 12px;\n}\n';
+
+SERVER = 'require(\'derby\').run(__dirname + \'/lib/server\')\n';
+
+MAKEFILE_COFFEE = 'compile:\n ./node_modules/coffee-script/bin/coffee -bw -o ./lib -c ./src\n';
+
+README = '# <<project>>\n';
+
+GITIGNORE_COFFEE = '.DS_Store\npublic/gen\nlib/\n*.swp\n';
+
+GITIGNORE_JS = '.DS_Store\npublic/gen\n*.swp\n';
+
+packageJson = function(project, useCoffee) {
+ var pkg;
+ pkg = {
+ name: project,
+ description: '',
+ version: '0.0.0',
+ main: './server.js',
+ dependencies: {
+ derby: '*',
+ express: '3.0.0beta4',
+ gzippo: '>=0.1.7'
+ },
+ "private": true
+ };
+ if (useCoffee) {
+ pkg.devDependencies = {
+ 'coffee-script': '>=1.3.3'
+ };
+ }
+ return JSON.stringify(pkg, null, ' ');
+};
+
+printUsage = true;
+
+ANSI_CODES = {
+ 'off': 0,
+ 'bold': 1,
+ 'italic': 3,
+ 'underline': 4,
+ 'blink': 5,
+ 'inverse': 7,
+ 'hidden': 8,
+ 'black': 30,
+ 'red': 31,
+ 'green': 32,
+ 'yellow': 33,
+ 'blue': 34,
+ 'magenta': 35,
+ 'cyan': 36,
+ 'white': 37,
+ 'black_bg': 40,
+ 'red_bg': 41,
+ 'green_bg': 42,
+ 'yellow_bg': 43,
+ 'blue_bg': 44,
+ 'magenta_bg': 45,
+ 'cyan_bg': 46,
+ 'white_bg': 47
+};
+
+styleTag = function(name) {
+ return "\u001b[" + ANSI_CODES[name] + "m";
+};
+
+style = function(styles, text) {
+ var item, out, _i, _len, _ref1;
+ out = '';
+ _ref1 = styles.split(' ');
+ for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
+ item = _ref1[_i];
+ out += styleTag(item);
+ }
+ return out + text + styleTag('off');
+};
+
+emptyDirectory = function(path, callback) {
+ return fs.readdir(path, function(err, files) {
+ if (err && err.code !== 'ENOENT') {
+ throw err;
+ }
+ return callback(!files || !files.length);
+ });
+};
+
+logWrite = function(path) {
+ return console.log(style('green', ' created: ') + path);
+};
+
+mkdir = function(path) {
+ mkdirp.sync(path, '0755');
+ return logWrite(path);
+};
+
+write = function(path, text) {
+ fs.writeFileSync(path, text);
+ return logWrite(path);
+};
+
+render = function(template, ctx) {
+ var key, re, value;
+ for (key in ctx) {
+ value = ctx[key];
+ re = new RegExp('<<' + key + '>>', 'g');
+ template = template.replace(re, value);
+ }
+ return template;
+};
+
+abort = function(message) {
+ message || (message = style('red bold', '\n Aborted \n'));
+ console.error(message);
+ return process.exit(1);
+};
+
+createProject = function(dir, app, useCoffee) {
+ var appScripts, appStyles, appViews, connectionAlert, dirPath, logComplete, project, scripts, serverScripts, styles, ui, views;
+ dirPath = resolve(process.cwd(), dir);
+ if (!(project = basename(dirPath))) {
+ throw new Error('Cannot create project at ' + dirPath);
+ }
+ views = join(dir, 'views');
+ styles = join(dir, 'styles');
+ scripts = useCoffee ? join(dir, 'src') : join(dir, 'lib');
+ appViews = join(views, app);
+ appStyles = join(styles, app);
+ ui = join(dir, 'ui');
+ connectionAlert = join(ui, 'connectionAlert');
+ appScripts = join(scripts, app);
+ serverScripts = join(scripts, 'server');
+ mkdir(dir);
+ mkdir(join(dir, 'public', 'img'));
+ mkdir(appViews);
+ write(join(appViews, 'index.html'), APP_HTML);
+ write(join(views, '404.html'), _404_HTML);
+ mkdir(appStyles);
+ write(join(appStyles, 'index.styl'), APP_STYL);
+ write(join(styles, '404.styl'), _404_STYL);
+ write(join(styles, 'reset.styl'), RESET_STYL);
+ write(join(styles, 'base.styl'), BASE_STYL);
+ write(join(styles, 'ui.styl'), UI_STYL);
+ mkdir(ui);
+ mkdir(connectionAlert);
+ write(join(connectionAlert, 'index.html'), CONNECTION_ALERT_HTML);
+ write(join(connectionAlert, 'index.js'), CONNECTION_ALERT_JS);
+ write(join(ui, 'index.js'), UI_JS);
+ if (useCoffee) {
+ mkdir(appScripts);
+ write(join(appScripts, 'index.coffee'), render(APP_COFFEE, {
+ app: app
+ }));
+ mkdir(serverScripts);
+ write(join(serverScripts, 'index.coffee'), render(SERVER_COFFEE, {
+ app: app
+ }));
+ write(join(serverScripts, 'serverError.coffee'), render(SERVER_ERROR_COFFEE, {
+ app: app
+ }));
+ write(join(dir, 'Makefile'), MAKEFILE_COFFEE);
+ write(join(dir, '.gitignore'), GITIGNORE_COFFEE);
+ } else {
+ mkdir(appScripts);
+ write(join(appScripts, 'index.js'), render(APP_JS, {
+ app: app
+ }));
+ mkdir(serverScripts);
+ write(join(serverScripts, 'index.js'), render(SERVER_JS, {
+ app: app
+ }));
+ write(join(serverScripts, 'serverError.js'), render(SERVER_ERROR_JS, {
+ app: app
+ }));
+ write(join(dir, '.gitignore'), GITIGNORE_JS);
+ }
+ write(join(dir, 'server.js'), SERVER);
+ write(join(dir, 'package.json'), packageJson(project, useCoffee));
+ write(join(dir, 'README.md'), render(README, {
+ project: project
+ }));
+ logComplete = function() {
+ var message;
+ message = style('green bold', '\n Project created!') + '\n\n Try it out:';
+ if (dir !== '.') {
+ message += "\n $ cd " + dir;
+ }
+ if (program.noinstall) {
+ message += '\n $ npm install';
+ }
+ if (useCoffee) {
+ message += "\n $ make\n\n Then in a new terminal:\n $ cd " + dirPath;
+ }
+ message += "\n $ node server.js\n\n More info at: http://derbyjs.com/\n";
+ return console.log(message);
+ };
+ if (program.noinstall) {
+ return logComplete();
+ }
+ process.chdir(dir);
+ console.log('\n Installing dependencies. This may take a little while...');
+ return exec('npm install', function(err, stdout, stderr) {
+ if (err) {
+ return console.error(stderr);
+ }
+ if (stdout) {
+ console.log(stdout.replace(/^|\n/g, '\n '));
+ }
+ return logComplete();
+ });
+};
+
+newProject = function(dir, app) {
+ var directory, type, useCoffee;
+ if (dir == null) {
+ dir = '.';
+ }
+ if (app == null) {
+ app = 'app';
+ }
+ printUsage = false;
+ useCoffee = program.coffee;
+ type = useCoffee ? 'CoffeeScript ' : '';
+ directory = style('bold', dir === '.' ? 'the current directory' : dir);
+ console.log(("\n Creating " + type + "project in " + directory + " with the application ") + style('bold', app) + '\n');
+ return emptyDirectory(dir, function(empty) {
+ if (!empty) {
+ return program.confirm(' Destination is not empty. Continue? ', function(ok) {
+ if (!ok) {
+ abort();
+ }
+ process.stdin.destroy();
+ return createProject(dir, app, useCoffee);
+ });
+ }
+ return createProject(dir, app, useCoffee);
+ });
+};
+
+program.version(derby.version).option('-c, --coffee', 'create files using CoffeeScript').option('-n, --noinstall', "don't run `npm install`");
+
+program.command('new [dir] [app]').description('\nCreate a new Derby project. If no directory name is specified, or the\nname `.` is used, the project will be created in the current directory.\nA name for the default app may be specified optionally.').action(newProject);
+
+program.parse(process.argv);
+
+if (printUsage) {
+ console.log('\n See `derby --help` for usage\n');
+}
View
9 test/View.mocha.coffee
@@ -512,4 +512,13 @@ describe 'View', ->
Some text
And a new line
</script>
+ '''
+ it 'correctly parses an empty HTML node', ->
+ url = 'path/to/a/resource.png'
+ view.make 'test', '''
+ <source src="{{url}}"/>
+ '''
+
+ expect(view.get 'test', {url: url}).to.equal '''
+ <source src="path/to/a/resource.png"/>
'''
Something went wrong with that request. Please try again.