#!/usr/bin/env node /** * Module dependencies. */ try { var stylus = require('stylus'); } catch (err) { var stylus = require('../lib/stylus'); } /** * Banner. */ var banner = '\n\ \n\ _ _ \n\ ___| |_ _ _| |_ _ ___ \n\ / __| __| | | | | | | / __| \n\ \\__ \\ |_| |_| | | |_| \\__ \\ \n\ |___/\\__|\\__, |_|\\__,_|___/ \n\ |___/ \n\ \n\ '; /** * Steps. */ var steps = []; /** * Clear the screen and move the cursor up. */ function clear() { console.log('\x1b[1J\x1b[H'); } /** * Output "press enter" message. */ function enter() { process.stdout.write('\x1b[90mpress enter to continue:\x1b[0m '); } /** * Initialize a `Step` with the given data. * * @param {String} title * @param {String} desc * @param {String} src * @param {Function} fn */ function Step(title, desc, src, fn) { var self = this; this.n = steps.length; this.src = src; desc = desc.replace(/\_([^_]+)\_/g, '\x1b[33m$1\x1b[90m'); this.next = function(){ var step = steps[self.n + 1]; if (step) { step.display(); } else { console.log(' THE END\n'); process.exit(0); } }; this.display = function(retry){ if (!retry) { console.log(' %d) \x1b[1m%s\x1b[0m\n', self.n, title); console.log(' \x1b[90m%s\x1b[0m', desc); } console.log('\n%s\n', src); console.log('\x1b[90m--------------------\x1b[0m \n'); this.input(); }; this.input = function(){ stdin.resume(); var buf = ''; stdin.on('data', function callback(chunk){ if (0 == chunk.indexOf('quit')) process.exit(1); var dot = '.' == chunk[0] && '\n' == chunk[1] , equal = buf.trim() == self.src.trim(); if (dot || equal) { stdin.pause(); stdin.removeListener('data', callback); stylus.render(buf, { filename: 'stdin' }, function(err, css){ if (err) { console.log('\n\x1b[31mError: ' + err.message + '\x1b[0m\n'); self.display(true); return; } clear(); console.log('\x1b[90m-------------------- stylus\x1b[0m \n'); console.log(buf.replace(/^/gm, ' ')); console.log('\x1b[90m-------------------- css\x1b[0m \n'); console.log(css.replace(/^/gm, ' ')); console.log(); enter(); stdin.resume(); stdin.once('data', function(){ clear(); self.next(); }); }); return; } buf += chunk; }); }; } /** * Step definitions. */ steps.push(new Step( 'Selector Properties' , 'The Stylus grammar has indentation-based blocks,\n' + ' meaning one does not surround selector properties\n' + ' using braces _{_ _}_, but uses an indent of two \n' + ' spaces, or a single tab.\n' + '\n' + ' To get started, type the stylus you see below\n' + ' and then press return a few times or type _"."_ to render.' , 'body\n color black')); steps.push(new Step( 'Multiple Selectors' , 'Blocks can be applied to several selectors at once\n' + ' by separating them via comma, or newline.\n' + '\n' + ' Both examples shown below are equivalent.' , 'a.button, input[type=button]\n' + ' border 1px solid #eee\n' + ' padding 5px\n\n' + 'a.button\n' + 'input[type=button]\n' + ' border 1px solid #eee\n' + ' padding 5px' )); steps.push(new Step( 'Nested Selectors' , 'Selectors may also be nested.\n' , 'ul\n' + ' li\n' + ' a\n' + ' color black\n' )); steps.push(new Step( 'Variables' , 'Stylus allows you to define variables by utilizing\n' + ' the _=_ operator, much like you would in any other language.\n' + '\n' + ' These variables may be assigned to _numbers_, _strings_, _booleans_,\n' + ' and virtually everything else you see in css.' , 'size = 14px\n\n' + 'body\n' + ' font size Arial, sans-serif' )); steps.push(new Step( 'Conditional Assignment' , 'The conditional assignment operator _?=_ may be used to\n' + ' define a variable only when previously undefined.' , 'size = 14px\n\n' + 'body\n' + ' size ?= 22px\n' + ' font size Arial, sans-serif' )); steps.push(new Step( 'Arithmetic' , 'A variety of binary arithmetic operators are supported\n' + ' as well as unary operators such as _-_, _+_, and _~_.' , 'size = 14px\n' + 'large-size = size * 2\n' + '\n' + 'body\n' + ' font large-size\n' + '\n' + 'p\n' + ' margin-left -(size - 2)' )); steps.push(new Step( 'Coercion' , 'Arithmetic operations coerce the right-hand operand when\n' + ' applicable, for example converting _2s_ in _1000ms + 2s_\n' + ' into _2000ms_ before evaluating the expression.\n' + '\n' + ' Examples:\n' + ' _15px_ - _5_\n' + ' _#000_ + _#eee_\n' + ' _#fff_ - rgba(_0_,_0_,_0_,_.5_)\n' + ' _#fff_ / _2_\n' + ' _5in_ - _3cm_\n' , 'duration = 2s\n\n' + '.fade\n' + ' -webkit-transition opacity duration - 1500ms linear' )); steps.push(new Step( 'Functions' , 'Stylus functions may act as mixins or have a return value.\n' + 'For example here we use the unit() built-in function to.\n' + 'forcing both unit\'s type to _px_, and then add them.\n' + '\n' + 'Note that we may also use the _return_ keyword, however it\n' + 'is optional in most cases.' , 'add(a, b)\n' + ' a = unit(a, "px")\n' + ' b = unit(b, "px")\n' + ' a + b\n' + '\n' + 'body\n' + ' padding add(5px, 10px)\n' + ' margin add(5, 10)' )); steps.push(new Step( 'Mixins' , 'Mixins are defined in the same manner as functions\n' + 'with return values, however they "mix in" their properties\n' + 'and selectors into the caller.\n' + '\n' + 'Mixin parens are optional in most cases, so we may apply\n' + 'the transparently.' , 'border-radius(n)\n' + ' -webkit-border-radius n\n' + ' -moz-border-radius n\n' + ' border-radius n\n' + '\n' + 'body\n' + ' border-radius(5px)\n' + '\n' + 'form input\n' + ' border-radius 5px' )); /** * Start tutorial. */ clear(); console.log('\x1b[1m' + banner + '\x1b[0m'); console.log('\n Welcome to the stylus interactive tutorial.\n'); console.log(' - To quit at any time type \x1b[1mquit\x1b[0m.'); console.log(' - To force rendering type "." and press enter,'); console.log(' however source will be automatically rendered'); console.log(' when it matches the step\'s source.'); console.log(); enter(); var stdin = process.openStdin(); stdin.setEncoding('utf8'); stdin.once = function(event, fn){ this.on(event, function callback(chunk){ stdin.removeListener(event, callback); fn(chunk); }); }; stdin.once('data', function(chunk){ stdin.pause(); clear(); steps[0].display(); });