├── .gitignore ├── .gitmodules ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── Makefile ├── RAILWAY-CHANGELOG.md ├── README.md ├── bin └── compound.js ├── docs ├── CNAME ├── api │ ├── controller.md │ ├── helpers.md │ ├── index.txt │ ├── roadmap.md │ └── routing.md ├── cli │ ├── compound.md │ └── index.txt ├── docs.html ├── footer.html ├── ga.html ├── index.html ├── migration-guide.md └── sources │ ├── build.js │ ├── images │ ├── logo-dark.png │ ├── logo-dark@2x.png │ ├── logo.png │ ├── logo@2x.png │ ├── menu.png │ └── menu@2x.png │ ├── javascripts │ ├── application.js │ ├── jquery.scrollspy.js │ └── prettyprint.js │ ├── markdown │ ├── about.md │ ├── asset-compiler.md │ ├── code-snippets.md │ ├── coffeescript.md │ ├── controllers.md │ ├── events.md │ ├── extension-api.md │ ├── generators.md │ ├── heroku.md │ ├── localization.md │ ├── orm.md │ ├── repl.md │ ├── routing.md │ └── views.md │ ├── src │ ├── DEPRECATED_index.jade │ └── stylesheets │ │ ├── _content.styl │ │ ├── _headlines.styl │ │ ├── _reset.styl │ │ ├── _sidebar.styl │ │ ├── _styled_objects.styl │ │ ├── _styles.styl │ │ └── application.styl │ ├── stylesheets │ ├── application.css │ └── prettify.css │ ├── template.html │ └── to_md.js ├── lib ├── compound.js ├── controller-bridge.js ├── controller-extensions.js ├── form-for-resource.js ├── helpers.js ├── i18n.js ├── models.js ├── server │ ├── compound.js │ ├── controllers │ │ ├── crud-json.js │ │ ├── crud.js │ │ └── index.js │ ├── extensions.js │ ├── generators.js │ ├── generators │ │ ├── app_generator.js │ │ ├── base_generator.js │ │ └── generator_utils.js │ ├── installer.js │ ├── logger.js │ ├── middleware │ │ ├── domain.js │ │ └── index.js │ ├── structure.js │ └── tools.js └── utils.js ├── package.json ├── scripts └── doc.sh ├── templates ├── Procfile ├── README.md ├── app │ ├── assets │ │ ├── coffeescripts │ │ │ └── application.coffee │ │ └── stylesheets │ │ │ ├── application.less │ │ │ ├── application.sass │ │ │ └── application.styl │ ├── controllers │ │ ├── application_controller.coffee │ │ └── application_controller.js │ ├── helpers │ │ ├── model_helper.coffee │ │ └── model_helper.js │ ├── models │ │ ├── model.coffee │ │ └── model.js │ └── tools │ │ ├── database.coffee │ │ └── database.js ├── blank.js ├── config │ ├── autoload.coffee │ ├── autoload.js │ ├── database_memory.coffee │ ├── database_memory.js │ ├── database_mongodb.coffee │ ├── database_mongodb.js │ ├── database_mongodb.json │ ├── database_mysql.coffee │ ├── database_mysql.js │ ├── database_mysql.json │ ├── database_nano.coffee │ ├── database_nano.js │ ├── database_postgres.js │ ├── database_redis.coffee │ ├── database_redis.js │ ├── database_redis.json │ ├── database_riak.coffee │ ├── database_riak.json │ ├── database_sqlite3.coffee │ ├── database_sqlite3.js │ ├── database_sqlite3.json │ ├── environment.coffee │ ├── environment.js │ ├── environments │ │ ├── development.coffee │ │ ├── development.js │ │ ├── production.coffee │ │ ├── production.js │ │ ├── test.coffee │ │ └── test.js │ ├── routes.coffee │ └── routes.js ├── crud_controller.js ├── db │ ├── schema.coffee │ ├── schema.js │ ├── schema_model.coffee │ └── schema_model.js ├── gitignore-example ├── package.json ├── public │ ├── favicon.ico │ ├── images │ │ ├── compound.png │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ ├── index.html │ ├── javascripts │ │ ├── application.js │ │ ├── bootstrap.js │ │ └── rails.js │ └── stylesheets │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap.css │ │ └── style.css ├── server.coffee ├── server.js └── test │ ├── controllers │ └── crud_controller_test.js │ └── test_helper.js ├── test ├── compound.test.js ├── config.test.js ├── config │ └── initializers │ │ ├── README.md │ │ └── test.js ├── controller-extensions.test.js ├── controller.test.js ├── fixtures │ └── config │ │ ├── database.json │ │ ├── environment.js │ │ └── extra-environment.js ├── generators.test.js ├── helpers.test.js ├── init.js ├── middleware-injects.test.js └── utils.test.js └── vendor └── date_format.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | .DS_Store 4 | dump.rdb 5 | doc 6 | coverage.html 7 | coverage 8 | docs/sources/index.html 9 | log/test.log 10 | analyse-ab.r 11 | log 12 | tags.sh 13 | man 14 | lib/npm-debug.log 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "distr/ejs"] 2 | path = distr/ejs 3 | url = git://github.com/visionmedia/ejs.git 4 | [submodule "distr/jade"] 5 | path = distr/jade 6 | url = git://github.com/visionmedia/jade.git 7 | [submodule "support/nodeunit"] 8 | path = support/nodeunit 9 | url = git://github.com/caolan/nodeunit.git 10 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "before": true, 4 | "beforeAll": true, 5 | "after": true, 6 | "afterAll": true, 7 | "describe": true, 8 | "it": true, 9 | "property": true, 10 | "define": true, 11 | "set": true, 12 | "getApp": true, 13 | "put": true, 14 | "get": true, 15 | "post": true, 16 | "del": true, 17 | "beforeEach": true 18 | }, 19 | "nonstandard": true, 20 | "expr": true, 21 | "node": true, 22 | "browser": true, 23 | "esnext": true, 24 | "bitwise": true, 25 | "camelcase": true, 26 | "curly": true, 27 | "eqeqeq": true, 28 | "immed": true, 29 | "indent": 4, 30 | "latedef": true, 31 | "newcap": true, 32 | "noarg": true, 33 | "quotmark": "single", 34 | "regexp": true, 35 | "undef": true, 36 | "unused": true, 37 | "strict": false, 38 | "latedef": false, 39 | "trailing": true, 40 | "smarttabs": true 41 | } 42 | 43 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | .DS_Store 4 | dump.rdb 5 | doc 6 | log 7 | coverage.html 8 | coverage 9 | docs 10 | analyse-ab.r 11 | tags.sh 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - 0.10 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | compound-changelog(3) -- Changes in CompoundJS 2 | ================================ 3 | 4 | ## HISTORY 5 | 6 | ### 1.1.19 7 | 8 | - Fixed HTTPS support 9 | - Introduce `req.locals` API to early access controller action context (`this`). 10 | - compound.loadConfigs(dir) for loading configs from entire directory into 11 | compound.app.settings 12 | - Added `vhost` route option. 13 | - Added presenters 14 | - Controllers now support promises 15 | 16 | ### 1.1.6 17 | 18 | * **man**: 19 | Docs in roff (man). Change `compound help` command to proxy request to `man`. 20 | Unfortunately compound have optional `ronn` rubygem dependency. 21 | 22 | * **inject middleware**: 23 | New API for middleware injections. 24 | 25 | * **mocha**: 26 | All tests rewritten. Mocha is new default test engine. 27 | 28 | * **cleanup core**: 29 | Generators, assets compiler, clientside moved to separate packages. Refactor 30 | and speedup render. 31 | 32 | * **new helpers**: 33 | icon, imageTag, metaTag, anchor, contentFor, button. 34 | 35 | * **async initializers**: 36 | Initializer may accept second optional param: `Function done`. In that case 37 | next initializer will be called only when `done` callback called. 38 | 39 | * **compound.model() api**: 40 | - compound.model('modelname') - getter (case insensitive) 41 | - compound.model('ModelName', true) - case sensitive getter 42 | - compound.model(NamedFn) - setter for model as named fn (or jugglingdb model) 43 | 44 | ### 1.1.5 45 | 46 | * **generators**: 47 | New generators structure. Fully rewritten by Sascha Gehlich. 48 | 49 | * **noeval controllers**: 50 | Finally we have correct controllers with inheritance, proper require, debug 51 | and meta-programming. Added predefined meta-controllers. 52 | 53 | * **clientside compound**: 54 | A lot of restructuring and rewriting for clienside, separate server and client 55 | loading logic. 56 | 57 | * **miscellaneous**: 58 | Fixes in i18n, helpers, logging, docs, etc.. 59 | 60 | ### 1.1.4 61 | 62 | * **config/autoload**: 63 | No more weird npmfile with `require` problems. 64 | 65 | * **domain**: 66 | Basic middleware to support nodejs domains. 67 | 68 | * **any view engine**: 69 | Now any express-friendly templating engine supported. 70 | 71 | * **block-less helpers**: 72 | View helpers formTag, formFor and fieldsFor doesn require blocks. 73 | 74 | * **bugs**: 75 | A lot of bugfixes after rewriting 76 | 77 | ### 1.1.3 78 | 79 | * **railwayjs renamed**: 80 | Major API changes started at this point. No backwards compatibility with 81 | RailwayJS. 82 | 83 | * **compoundjs.com**: 84 | Static website generated from markdown added to repository 85 | 86 | * **assets compiler**: 87 | Now built-in core. Allows to generate css/js from assets stored in app/assets 88 | directory 89 | 90 | * **express v3**: 91 | Switch to latest express 92 | 93 | * **Events API**: 94 | Improved loading process, now utilizes events API based on nodejs event emitter 95 | 96 | ## SEE ALSO 97 | 98 | [compound-railway-changelog(3)](railway-changelog.3.html) 99 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # TESTS 2 | 3 | TESTER = ./node_modules/.bin/mocha 4 | OPTS = --require ./test/init.js 5 | TESTS = test/*.test.js 6 | JSHINT = ./node_modules/.bin/jshint 7 | 8 | test: 9 | $(TESTER) $(OPTS) $(TESTS) 10 | test-verbose: 11 | $(TESTER) $(OPTS) --reporter spec $(TESTS) 12 | testing: 13 | $(TESTER) $(OPTS) --watch $(TESTS) 14 | 15 | JS_FILES = $(shell find . -type f -name "*.js" \ 16 | -not -path "./node_modules/*" -and \ 17 | -not -path "./coverage/*" -and \ 18 | -not -path "./test/*" -and \ 19 | -not -path "./docs/*" -and \ 20 | -not -path "./vendor/*" -and \ 21 | -not -path "./templates/*" -and \ 22 | -not -path "./db/schema.js") 23 | 24 | check: 25 | @$(JSHINT) $(JS_FILES) 26 | 27 | # MAN DOCS 28 | 29 | CLI_MAN = $(shell find docs/cli -name '*.md' \ 30 | |sed 's|.md|.1|g' \ 31 | |sed 's|docs/cli/|man/|g' ) 32 | 33 | API_MAN = $(shell find docs/api -name '*.md' \ 34 | |sed 's|.md|.3|g' \ 35 | |sed 's|docs/api/|man/|g' ) 36 | 37 | CLI_WEB = $(shell find docs/cli -name '*.md' \ 38 | |sed 's|.md|.1.html|g' \ 39 | |sed 's|docs/cli/|man/html/|g' ) 40 | 41 | API_WEB = $(shell find docs/api -name '*.md' \ 42 | |sed 's|.md|.3.html|g' \ 43 | |sed 's|docs/api/|man/html/|g' ) \ 44 | man/html/railway-changelog.3.html \ 45 | man/html/changelog.3.html \ 46 | 47 | man/%.1: docs/cli/%.md scripts/doc.sh 48 | @[ -d man ] || mkdir man 49 | scripts/doc.sh $< $@ 50 | 51 | man/%.3: docs/api/%.md scripts/doc.sh 52 | @[ -d man ] || mkdir man 53 | scripts/doc.sh $< $@ 54 | 55 | man/html/%.3.html: docs/api/%.md scripts/doc.sh docs/footer.html 56 | @[ -d man/html ] || mkdir -p man/html 57 | scripts/doc.sh $< $@ 58 | 59 | man/html/%.1.html: docs/cli/%.md scripts/doc.sh 60 | @[ -d man/html ] || mkdir -p man/html 61 | scripts/doc.sh $< $@ 62 | 63 | man/html/railway-changelog.3.html: RAILWAY-CHANGELOG.md scripts/doc.sh 64 | scripts/doc.sh $< $@ 65 | 66 | man/html/changelog.3.html: CHANGELOG.md scripts/doc.sh 67 | scripts/doc.sh $< $@ 68 | 69 | MAN = $(API_MAN) $(CLI_MAN) 70 | 71 | build: man 72 | 73 | html: $(API_WEB) $(CLI_WEB) 74 | 75 | web: $(API_WEB) 76 | rsync ./man/html/* compoundjs.com:/var/www/apps/compoundjs.com/public/man 77 | scp ./docs/index.html compoundjs.com:/var/www/apps/compoundjs.com/compound/docs 78 | 79 | all: $(MAN) $(API_WEB) 80 | 81 | man: $(MAN) 82 | 83 | # WEBSITE DOCS 84 | 85 | docs: 86 | node docs/sources/build 87 | apidocs: 88 | makedoc lib/*.js lib/*/*.js -t "RailwayJS API docs" -g "1602/express-on-railway" --assets 89 | 90 | .PHONY: test doc docs 91 | -------------------------------------------------------------------------------- /bin/compound.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var sys = require('util'); 4 | var fs = require('fs'); 5 | 6 | try { 7 | var package = require(process.cwd() + '/package.json'); 8 | if (package && !package.main) { 9 | console.log('Please fill `main` field in your `package.json` file'); 10 | process.exit(); 11 | } 12 | if (package.main.match(/\.coffee$/)) { 13 | require('coffee-script/register'); 14 | } 15 | instantiateApp = require(process.cwd()); 16 | } catch(e) { 17 | instantiateApp = null; 18 | } 19 | 20 | var app, compound; 21 | if (typeof instantiateApp === 'function') { 22 | app = instantiateApp(); 23 | compound = app.compound; 24 | } 25 | if (!compound) { 26 | var Compound = require('../').Compound; 27 | compound = new Compound(); 28 | } 29 | 30 | compound.init(); 31 | 32 | var args = process.argv.slice(2); 33 | var exitAfterAction = true; 34 | var command = args.shift(); 35 | 36 | switch (command) { 37 | default: 38 | case 'h': 39 | case 'help': 40 | if (command && command !== 'help' && command !== 'h') { 41 | var found = false; 42 | Object.keys(compound.tools).forEach(runner(compound.tools)); 43 | function runner(base) { 44 | return function (cmd) { 45 | if (!base) { 46 | return false; 47 | } 48 | var c = base[cmd]; 49 | if (cmd === command || (c && c.help && c.help.shortcut === command)) { 50 | if (cmd !== 'server' && cmd !== 's') { 51 | compound.app.enable('tools'); 52 | } 53 | exitAfterAction = false; 54 | c(compound, args); 55 | found = true; 56 | } 57 | } 58 | } 59 | 60 | if (found) { 61 | break; 62 | } 63 | } 64 | var topic = args.shift(); 65 | if (topic) { 66 | showMan(topic); 67 | return; 68 | } 69 | var help = [ 70 | 'Usage: compound command [argument(s)]\n', 71 | 'Commands:' 72 | ]; 73 | var commands = [ 74 | ['h', 'help [topic]', 'Display compound man page'], 75 | ['i', 'init', 'Initialize compound app'], 76 | ['g', 'generate [smth]', 'Generate something awesome'] 77 | ]; 78 | Object.keys(compound.tools).forEach(function (cmd) { 79 | var h = compound.tools[cmd].help; 80 | if (h) { 81 | commands.push([h.shortcut || '', h.usage || cmd, h.description]); 82 | } 83 | }); 84 | var maxLen = 0, addSpaces = compound.utils.addSpaces; 85 | commands.forEach(function (cmd) { 86 | if (cmd[1].length > maxLen) { 87 | maxLen = cmd[1].length; 88 | } 89 | }); 90 | commands.forEach(function (cmd) { 91 | help.push(' ' + addSpaces(cmd[0] + ',', 4) + addSpaces(cmd[1], maxLen + 1) + cmd[2]); 92 | }); 93 | compound.generators.init(compound, args); 94 | help.push('\nAvailable generators:\n'); 95 | help.push(' ' + compound.generators.list()); 96 | sys.puts(help.join('\n')); 97 | break; 98 | 99 | case 'i': 100 | case 'init': 101 | compound.generators.init(compound); 102 | compound.generators.perform('app', args); 103 | break; 104 | case 'g': 105 | case 'generate': 106 | var what = args.shift(); 107 | compound.generators.init(compound); 108 | if (typeof what == "undefined" || what == null) { 109 | console.log('Generator not specified, available generators: ', compound.generators.list()); 110 | } else { 111 | exitAfterAction = !compound.generators.perform(what, args); 112 | } 113 | break; 114 | case '--version': 115 | console.log(compound.version); 116 | break; 117 | } 118 | 119 | if (exitAfterAction) { 120 | process.exit(0); 121 | } 122 | 123 | function showMan(topic) { 124 | var manDir = require('path').resolve(__dirname + '/../man'); 125 | require('child_process').spawn( 126 | 'man', [manDir + '/' + topic + '.3'], 127 | { 128 | customFds: [0, 1, 2], 129 | env: process.env, 130 | cwd: process.cwd() 131 | } 132 | ); 133 | } 134 | 135 | /*vim ft:javascript*/ 136 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | compoundjs.com -------------------------------------------------------------------------------- /docs/api/helpers.md: -------------------------------------------------------------------------------- 1 | compound-helpers(3) - view helpers 2 | ================================== 3 | 4 | ## DESCRIPTION 5 | 6 | Helpers produce html code. [Built-in][BUILT-IN HELPERS] helpers available in any 7 | view. Custom helpers available in specific controller, see [CUSTOM HELPERS][] 8 | section. 9 | 10 | ## BUILT-IN HELPERS 11 | 12 | ### stylesheetLinkTag(file1[, file2[, ...[, fileN]]]) 13 | 14 | Generate `` tag. 15 | Following ejs: 16 | 17 | <%- stylesheetLinkTag('reset', 'style', 'mobile') %> 18 | 19 | will produce in develompent env: 20 | 21 | 22 | 23 | 24 | 25 | and in production env: 26 | 27 | 28 | 29 | depending on `app.set('merge stylesheets');` option. 30 | 31 | ### javascriptIncludeTag 32 | ### linkTo 33 | 34 | Generate html string `text`. Signature: 35 | 36 | HelperSet.prototype.linkTo = function linkTo(text, url, params) 37 | 38 | Example: 39 | 40 | linkTo('Home', '/', {title: 'Go Home'}); 41 | // Home 42 | 43 | ### contentFor 44 | ### anchor 45 | ### matcher 46 | ### icon 47 | ### imageTag 48 | ### csrfTag 49 | ### csrfMetaTag 50 | ### metaTag 51 | ### formFor 52 | ### fieldsFor 53 | ### errorMessagesFor 54 | ### formTag 55 | ### formTagBegin 56 | ### formTagEnd 57 | ### labelTag 58 | Accepts two optional arguments: label text and set of html params. 59 | ### inputTag 60 | 61 | Accepts single argument - set of html params: 62 | 63 | <%- inputTag({type: 'password', name: 'User[password]'}) %> 64 | 65 | will generate: 66 | 67 | 68 | 69 | ### texteareaTag 70 | 71 | Accepts two optional arguments: value and set of html params. 72 | 73 | <%- texteareaTag('Hello World', {name: 'greeting'}) %> 74 | 75 | will generate: 76 | 77 | 78 | 79 | ### submitTag 80 | ### buttonTag 81 | ### selectTag 82 | ### optionTag 83 | 84 | ## FORM HELPERS 85 | 86 | ### begin 87 | ### end 88 | ### label 89 | ### select 90 | ### input 91 | ### file 92 | ### textarea 93 | ### checkbox 94 | ### submit 95 | 96 | ## CUSTOM HELPERS 97 | 98 | There are two kind of custom helpers: application-wide and controller-wide. 99 | Application-wide helpers defined in `./app/helpers/application_helper.js` file. 100 | Controller-wide helpers available only for specific controller, and should be 101 | defined in `./app/helpers/controllerName_helper.js` file. 102 | 103 | Each controller is a javascript file exports set of functions (helper methods). 104 | These methods available in views and called on controller context, i.e. `this` 105 | keyword inside helper method refers to controller, so that you can access every 106 | member available in controller context: `req`, `res`, `body`, `compound`. To 107 | access view context use `this.viewContext`. 108 | 109 | ## SEE ALSO 110 | 111 | routing(3) 112 | -------------------------------------------------------------------------------- /docs/api/index.txt: -------------------------------------------------------------------------------- 1 | # manuals 2 | compound-routing(3) routing.3 3 | compound-controller(3) controller.3 4 | compound-views(3) views.3 5 | compound-helpers(3) helpers.3 6 | compound-tools(3) tools.3 7 | compound-tools(1) tools.1 8 | -------------------------------------------------------------------------------- /docs/api/roadmap.md: -------------------------------------------------------------------------------- 1 | compound-roadmap(3) -- Major things to do in upcoming releases 2 | ============================================================== 3 | 4 | ## DOCS 5 | 6 | * `man`: 7 | Move to man. Rewrite all docs. Write new sections for events and compound api. 8 | 9 | * `web`: 10 | Publish docs on compoundjs.com. 11 | 12 | * `guides`: 13 | Transform current docs into guides + manual pages. 14 | 15 | ## CLIENT-SIDE 16 | 17 | * `multi-modular`: 18 | Client-side apps could be composed as it's done in express+compound now: 19 | 20 | app.use(route, anotherApp); 21 | 22 | * `configuration`: 23 | Allow client-side app configuration: blacklist/whitelist controllers/models. 24 | 25 | * `angular` and `components`: 26 | Think about using angular or maybe other frameworks. TJ's 27 | [components](http://tjholowaychuk.com/post/27984551477/components) look 28 | decent. Investigate. 29 | 30 | 31 | 32 | ## MISC 33 | 34 | * `test`: 35 | Write tests for core components or remove components from core to 36 | another packages. Make 86% - 90% coverage. 37 | 38 | * `mocha`: 39 | Port existing tests to mocha. Including generated tests for crud controllers. 40 | 41 | * `helpers`: 42 | - Reorganize helpers. Now we have 1000 lines of code, which is not okay. 43 | - Nested fieldsets using fieldsFor 44 | 45 | * `optimize loading`: 46 | It takes about 300 - 500ms to load compound app. It's time to improve loading 47 | process. 48 | -------------------------------------------------------------------------------- /docs/cli/compound.md: -------------------------------------------------------------------------------- 1 | compound(1) -- MVC framework for NodeJS 2 | ======================================= 3 | 4 | ## SYNOPSIS 5 | 6 | compound [args] [opts] 7 | 8 | ## DESCRIPTION 9 | 10 | Compound is MVC framework for the Node JavaScript platform. It adds structure 11 | and API to ExpressJS application, provides tools and generators for rapid 12 | web applications development. 13 | 14 | Run `compound help [topic]` to get help on specific topic. 15 | 16 | ### command 17 | 18 | * `g`, `generate smth`: 19 | Call **smth** generator, where **smth** could be: app, controller, model, crud, 20 | scaffold or anything else. 21 | 22 | * `r`, `routes`: 23 | Display routing map 24 | 25 | * `s`, `server [port]`: 26 | Run HTTP server on specified port, default port = 3000 27 | 28 | * `c`, `console`: 29 | Run debugging console with compound environment loaded 30 | 31 | ### opts 32 | 33 | * `--coffee` 34 | * `--tpl ENGINE` 35 | * `--db NAME` 36 | * `--stylus` 37 | 38 | ## ENVIRONMENT 39 | 40 | * `NODE_ENV`: 41 | Set environment of application 42 | 43 | * `PORT`: 44 | Set port of HTTP server 45 | 46 | ## FUTURE 47 | 48 | See compound-roadmap(3) and [github issues][issues] to catch up current 49 | development and see where compound going. 50 | 51 | ## BUGS 52 | 53 | When you find issues, please report them: 54 | 55 | * web: 56 | 57 | * email: 58 | 59 | 60 | ## HISTORY 61 | 62 | See compound-changelog(3) compound-railway-changelog(3) 63 | 64 | ## AUTHOR 65 | 66 | * [blog](http://anatoliy.in/) 67 | * [github/1602](https://github.com/1602/) 68 | * [github/anatoliychakkaev](https://github.com/anatoliychakkaev/) 69 | * [twitter@1602](http://twitter.com/1602) 70 | * 71 | 72 | ## SEE ALSO 73 | 74 | compound(3) 75 | -------------------------------------------------------------------------------- /docs/cli/index.txt: -------------------------------------------------------------------------------- 1 | # manuals 2 | compound-railway-changelog(3) railway-changelog.3 3 | compound-changelog(3) changelog.3 4 | compound-routing(3) routing.3 5 | compound-controller(3) controller.3 6 | compound-views(3) views.3 7 | compound-helpers(3) helpers.3 8 | compound-tools(3) tools.3 9 | compound-tools(1) tools.1 10 | compound(3) compound.3 11 | compound-roadmap(3) roadmap.3 12 | issues https://github.com/1602/compound/issues 13 | -------------------------------------------------------------------------------- /docs/footer.html: -------------------------------------------------------------------------------- 1 | 33 | 66 | -------------------------------------------------------------------------------- /docs/ga.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CompoundJS 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 42 | 43 | 44 | 45 | 46 | 47 |
48 |
49 |
50 |
51 | build apps with love 52 |
53 |
54 |
55 |
56 |

57 | Latest stable release: 58 |

59 |
compound@v1.1.6-2 [changelog]
60 |
61 |
62 |

63 | Actual Information (work in progress) 64 |

65 |
66 | Guides 67 | 72 |
73 |
74 | API Docs 75 | 86 |
87 |
88 |
89 |

90 | Full Docs+Guides mix (outdated) 91 |

92 | 100 |
101 |
102 |

103 | Other Resources 104 |

105 | 116 |
117 |
118 | 119 | 129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | 150 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /docs/migration-guide.md: -------------------------------------------------------------------------------- 1 | ## Migration guide from RailwayJS 1.0 to CompoundJS 1.1.3 2 | 3 | ### General guidelines 4 | 5 | * The app variable is no longer global, but accessible via compound.app . Make sure to change all your files that reference app directly 6 | * The same goes for models. They are now accessible via compound.models. . This also applies to model relations, for example: model.hasMany(compound.models.anotherModel); 7 | * The following files, autogenerated by railwayjs, should be regeneraterd or rewritten since they changed 8 | * server.js 9 | * config/environment.js, config/environments/\*, and config/initializers (see below) 10 | * app/models/\* (see below) 11 | * config/initializers/db-tools.js 12 | * views using `formTag` and `formFor`(see below) 13 | 14 | ### config/environment.js, config/environments/\*, and config/initializers 15 | 16 | now should export function 17 | 18 | ```javascript 19 | module.exports = function (compound) { 20 | var app = compound.app; 21 | var User = compound.models.User; 22 | // rest of file goes here 23 | }; 24 | ``` 25 | 26 | ### app/models/\* 27 | 28 | now should export function 29 | 30 | ```javascript 31 | module.exports = function (compound, ModelName) { 32 | ModelName.validatesPresenceOf(...); 33 | ModelName.prototype.method = function () { 34 | }; 35 | }; 36 | ``` 37 | 38 | As mentioned before, remember the new way to reference other models: 39 | 40 | ```javascript 41 | module.exports = function (compound, ModelName) { 42 | ModelName.hasMany(compound.models.anotherModelName, {as: anotherMode}); 43 | }; 44 | ``` 45 | 46 | ### views 47 | 48 | 1. Avoid using `formTag` and `formFor` with blocks, new syntax: 49 | 50 | ``` 51 | <% var form = formFor(resource) %> 52 | <%- form.begin() %> 53 | <%- form.input('propertyname') %> 54 | <%- form.submit() %> 55 | <%- form.end() %> 56 | ``` 57 | 58 | 2. Use include instead of partial: 59 | 60 | <%- partial('post/form', {form: form, resource: resource}) %> 61 | becomes 62 | <%- include '_form' %> 63 | 64 | ### csrf protection 65 | 66 | In newest CompoundJS version PUT and DELETE methods also protected from CSRF, 67 | so you need to obtain latest `javascripts/rails.js` 68 | from compoundjs (generate new project and copy to existing one). 69 | 70 | ### config/autoload 71 | 72 | This is replacement for npmfile. File should export function which return array 73 | of extensions: 74 | 75 | module.exports = function (compound) { 76 | return [ 77 | require('jugglingdb'), 78 | require('ejs-ext'), 79 | require('seedjs') 80 | ] 81 | }; 82 | 83 | ### update /javascripts/rails.js 84 | 85 | Compound protects PUT and DELETE requests from request forgery, so you need to 86 | update old public/javascripts/rails.js file to get working ajax requests. 87 | 88 | ### something else? 89 | 90 | fix this file, request pull 91 | -------------------------------------------------------------------------------- /docs/sources/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds docs from source files 3 | */ 4 | var exec = require('child_process').exec; 5 | var watch = (process.argv[2] == '--watch') 6 | , command 7 | , cwd = process.cwd() 8 | , util = require('util') 9 | , fs = require('fs') 10 | , marked = require('marked'); 11 | 12 | marked.setOptions({ gfm: true }); 13 | 14 | var template = fs.readFileSync(__dirname + '/template.html') 15 | , sections = [ 16 | 'routing', 17 | 'controllers', 18 | 'views', 19 | 'orm', 20 | 'repl', 21 | 'localization', 22 | 'generators', 23 | // 'asset-compiler', 24 | 'extension-api', 25 | 'heroku', 26 | 'code-snippets', 27 | 'about' 28 | ]; 29 | 30 | String.prototype.inlineLexer = function() { 31 | var text = this; 32 | text = text.replace(/`(.*?)`/ig, '$1'); 33 | text = text.replace(/(.*?)<\/strong>/ig, '$1'); 34 | text = text.replace(/\[(.*?)\]\((.*?)\)/ig, '$1'); 35 | return text; 36 | } 37 | 38 | String.prototype.slugify = function () { 39 | var text = this; 40 | text = text.replace(/[^-a-zA-Z0-9,&\s]+/ig, ''); 41 | text = text.replace(/-/gi, "_"); 42 | text = text.replace(/\s/gi, "-"); 43 | return text.toLowerCase(); 44 | }; 45 | 46 | /** 47 | * Compile stylus files 48 | */ 49 | command = 'stylus ' + (watch ? '-w' : '') + ' -o ' + cwd + '/stylesheets ' + cwd + '/src/stylesheets/application.styl'; 50 | var stylusProcess = exec(command); 51 | stylusProcess.stdout.on('data', function (data) { 52 | util.print(data); 53 | }); 54 | 55 | /** 56 | * Create HTML from Markdown files 57 | */ 58 | var content = '' 59 | , headingsCount = 0; 60 | 61 | console.log("Building documentation from " + sections.length + " sections..."); 62 | sections.forEach(function (section) { 63 | var sectionData = fs.readFileSync(__dirname + '/markdown/' + section + '.md'); 64 | 65 | console.log("Building section " + section + "..."); 66 | 67 | if (!sectionData) { 68 | return false; 69 | } else { 70 | sectionData = sectionData.toString(); 71 | } 72 | 73 | var lexed = marked.lexer(sectionData); 74 | lexed.forEach(function (node) { 75 | switch (node.type) { 76 | case 'heading': 77 | if (headingsCount !== 0) 78 | content += '\n\n'; 79 | 80 | var node_id = section + '-' + node.text.slugify(); 81 | 82 | content += '
\n'; 83 | 84 | content += '' + node.text + '\n\n'; 85 | 86 | headingsCount++; 87 | break; 88 | case 'paragraph': 89 | var text = node.text; 90 | 91 | // Code-only paragraph, make code node 92 | if (match = text.match(/^`(.*)`\n?$/i)) { 93 | content += '' + match[1] + ''; 94 | break; 95 | } 96 | 97 | text = text.inlineLexer(); 98 | 99 | content += '

\n'; 100 | content += text; 101 | content += '

\n\n'; 102 | break; 103 | case 'code': 104 | content += '
\n';
105 |         content += node.text.replace(//ig, '>');
106 |         content += '\n
\n\n'; 107 | break 108 | case 'list_start': 109 | content += '
    \n'; 110 | break; 111 | case 'list_item_start': 112 | content += '
  • '; 113 | break; 114 | case 'list_item_end': 115 | content += '
  • \n'; 116 | break; 117 | case 'list_end': 118 | content += '
\n'; 119 | break; 120 | case 'text': 121 | case 'html': 122 | var text = node.text; 123 | content += text; 124 | break; 125 | }; 126 | }); 127 | }); 128 | 129 | content += '
'; 130 | 131 | console.log("Documentation built!"); 132 | /** 133 | * Save file 134 | */ 135 | fs.writeFileSync(__dirname + '/index.html', template.toString().replace('{{ CONTENT }}', content)); 136 | -------------------------------------------------------------------------------- /docs/sources/images/logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/docs/sources/images/logo-dark.png -------------------------------------------------------------------------------- /docs/sources/images/logo-dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/docs/sources/images/logo-dark@2x.png -------------------------------------------------------------------------------- /docs/sources/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/docs/sources/images/logo.png -------------------------------------------------------------------------------- /docs/sources/images/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/docs/sources/images/logo@2x.png -------------------------------------------------------------------------------- /docs/sources/images/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/docs/sources/images/menu.png -------------------------------------------------------------------------------- /docs/sources/images/menu@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/docs/sources/images/menu@2x.png -------------------------------------------------------------------------------- /docs/sources/javascripts/application.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | prettyPrint(); 3 | 4 | /** 5 | * Handle style switch clicking 6 | * + Remember state 7 | */ 8 | 9 | // Default style? 10 | var style = localStorage.getItem('doc-style'); 11 | if (!style) { 12 | style = 'style-dark'; 13 | localStorage.setItem('doc-style', style); 14 | } 15 | 16 | var logo = 'images/logo-dark.png'; 17 | if (style === 'style-dark') { 18 | logo = 'images/logo.png'; 19 | } 20 | 21 | $('html').removeClass().addClass(style); 22 | $('img.logo').attr({ src: logo }); 23 | 24 | $('.style-switch').click(function() { 25 | var newClass 26 | , newLogo; 27 | if ($('html').hasClass('style-dark')) { 28 | newClass = 'style-light'; 29 | newLogo = 'images/logo-dark.png'; 30 | } else { 31 | newClass = 'style-dark'; 32 | newLogo = 'images/logo.png'; 33 | } 34 | 35 | localStorage.setItem('doc-style', newClass); 36 | $('html').removeClass().addClass(newClass); 37 | $('img.logo').attr({ src: newLogo }); 38 | }); 39 | 40 | /** 41 | * Shows / hides the sidebar depending on mouse x position 42 | */ 43 | $(document).mousemove(function (e) { 44 | var mouseX = e.pageX; 45 | var windowW = $(window).width(); 46 | var viewportTooSmall = windowW <= $('.content-wrapper').width() + $('.sidebar').width() * 2; 47 | if (mouseX <= windowW / 4 && viewportTooSmall) { 48 | $('.sidebar').removeClass('hidden'); 49 | } else if (mouseX >= windowW / 4 && viewportTooSmall) { 50 | $('.sidebar').addClass('hidden'); 51 | } else { 52 | $('.sidebar').removeClass('hidden'); 53 | } 54 | }); 55 | 56 | /** 57 | * Generates the sidebar navigation from all headlines 58 | * and gives them numbers 59 | */ 60 | 61 | var sidebarNavi = { 62 | activeTopLevelContainer: null, 63 | handle: function() { 64 | var sidebar = $('.sidebar'); 65 | sidebar.find('.level-0').click(function (){ 66 | $(this).parent().find('.level-0').removeClass('open'); 67 | $(this).addClass('open'); 68 | }); 69 | this.scrollSpy(); 70 | }, 71 | scrollSpy: function () { 72 | var sidebar = $('.sidebar'); 73 | $('section').each(function () { 74 | $(this).scrollspy({ 75 | min: $(this).position().top, 76 | max: $(this).position().top + $(this).height(), 77 | onEnter: function (element, position) { 78 | // Deactivate all subnavigation items 79 | sidebar.find('.level-0').removeClass('open'); 80 | sidebar.find('*').removeClass('active'); 81 | 82 | // Find the according navigation item 83 | var item = sidebar.find('[data-id=' + $(element).attr('id') + ']').first(); 84 | 85 | item.closest('.level-0').addClass('open'); 86 | item.find('> a').addClass('active'); 87 | } 88 | }); 89 | }); 90 | }, 91 | build: function() { 92 | var self = this; 93 | var decimals = [0, 0, 0]; 94 | var currentLevelItems = [null, null, null]; 95 | $('h1, h2, h3').each(function (headlineIndex, headline) { 96 | var $headline = $(headline); 97 | 98 | var section = $headline.closest('section') 99 | , headlineText = $headline.text() 100 | , level; 101 | if ($headline.is('h1')) { 102 | decimals[0] ++; 103 | decimals[1] = 0; 104 | decimals[2] = 0; 105 | 106 | level = 0; 107 | } else if ($headline.is('h2')) { 108 | decimals[1] ++; 109 | decimals[2] = 0; 110 | 111 | level = 1; 112 | } else if ($headline.is('h3')) { 113 | decimals[2] ++; 114 | level = 2; 115 | } 116 | 117 | var text = $headline.text(); 118 | var decimal = self.buildDecimalString(decimals) + ' '; 119 | var decimalSpan = $('').addClass('decimal').text(decimal); 120 | $headline.prepend(decimalSpan); 121 | 122 | self.addListItem(level, section.attr('id'), text, decimals) 123 | }); 124 | }, 125 | addListItem: function (level, id, text, decimalArr) { 126 | var li = $('
  • ').addClass('level-' + level).attr({ 'data-id': id }); 127 | var a = $('') 128 | .attr({ href: '#' + id }) 129 | .text(text) 130 | .appendTo(li); 131 | 132 | if (level === 0) { 133 | var ul = $('
      ').appendTo(li); 134 | 135 | this.activeTopLevelContainer = ul; 136 | 137 | li.appendTo($('.sidebar ul.items')); 138 | } else { 139 | li.appendTo(this.activeTopLevelContainer); 140 | } 141 | 142 | if (decimalArr[0] === 1 && level === 0) { 143 | li.addClass('open'); 144 | } 145 | }, 146 | buildDecimalString: function (decimals) { 147 | var usedDecimals = [] 148 | , decimal; 149 | 150 | for(var i = 0; i < decimals.length; i++) { 151 | decimal = decimals[i]; 152 | if (i === decimals.indexOf(0)) 153 | break; 154 | 155 | usedDecimals.push(decimal); 156 | } 157 | return usedDecimals.join('.') + '.'; 158 | } 159 | }; 160 | sidebarNavi.build(); 161 | sidebarNavi.handle(); 162 | }); -------------------------------------------------------------------------------- /docs/sources/javascripts/jquery.scrollspy.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Scrollspy Plugin 3 | * Author: @sxalexander 4 | * Licensed under the MIT license 5 | */ 6 | 7 | 8 | ;(function ( $, window, document, undefined ) { 9 | 10 | $.fn.extend({ 11 | scrollspy: function ( options ) { 12 | 13 | var defaults = { 14 | min: 0, 15 | max: 0, 16 | mode: 'vertical', 17 | buffer: 0, 18 | container: window, 19 | onEnter: options.onEnter ? options.onEnter : [], 20 | onLeave: options.onLeave ? options.onLeave : [], 21 | onTick: options.onTick ? options.onTick : [] 22 | } 23 | 24 | var options = $.extend( {}, defaults, options ); 25 | 26 | return this.each(function (i) { 27 | 28 | var element = this; 29 | var o = options; 30 | var $container = $(o.container); 31 | var mode = o.mode; 32 | var buffer = o.buffer; 33 | var enters = leaves = 0; 34 | var inside = false; 35 | 36 | /* add listener to container */ 37 | $container.bind('scroll', function(e){ 38 | var position = {top: $(this).scrollTop(), left: $(this).scrollLeft()}; 39 | var xy = (mode == 'vertical') ? position.top + buffer : position.left + buffer; 40 | var max = o.max; 41 | var min = o.min; 42 | 43 | /* fix max */ 44 | if($.isFunction(o.max)){ 45 | max = o.max(); 46 | } 47 | 48 | /* fix max */ 49 | if($.isFunction(o.min)){ 50 | min = o.min(); 51 | } 52 | 53 | if(max == 0){ 54 | max = (mode == 'vertical') ? $container.height() : $container.outerWidth() + $(element).outerWidth(); 55 | } 56 | 57 | /* if we have reached the minimum bound but are below the max ... */ 58 | if(xy >= min && xy <= max){ 59 | /* trigger enter event */ 60 | if(!inside){ 61 | inside = true; 62 | enters++; 63 | 64 | /* fire enter event */ 65 | $(element).trigger('scrollEnter', {position: position}) 66 | if($.isFunction(o.onEnter)){ 67 | o.onEnter(element, position); 68 | } 69 | 70 | } 71 | 72 | /* triger tick event */ 73 | $(element).trigger('scrollTick', {position: position, inside: inside, enters: enters, leaves: leaves}) 74 | if($.isFunction(o.onTick)){ 75 | o.onTick(element, position, inside, enters, leaves); 76 | } 77 | }else{ 78 | 79 | if(inside){ 80 | inside = false; 81 | leaves++; 82 | /* trigger leave event */ 83 | $(element).trigger('scrollLeave', {position: position, leaves:leaves}) 84 | 85 | if($.isFunction(o.onLeave)){ 86 | o.onLeave(element, position); 87 | } 88 | } 89 | } 90 | }); 91 | 92 | }); 93 | } 94 | 95 | }) 96 | 97 | 98 | })( jQuery, window ); -------------------------------------------------------------------------------- /docs/sources/markdown/about.md: -------------------------------------------------------------------------------- 1 | 2 | Documentation is maintained by [@1602 (Anatoliy Chakkaev)](http://twitter.com/1602) and [@rattazong (Sascha Gehlich)](http://twitter.com/rattazong). 3 | 4 | CompoundJS is licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php). Documentation is licensed under [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/). 5 | 6 | The CompoundJS and JugglingDB projects are free, but you can leave a tip here: 7 | 8 |
      9 | 10 | 11 | 12 | 13 |
      -------------------------------------------------------------------------------- /docs/sources/markdown/asset-compiler.md: -------------------------------------------------------------------------------- 1 | # Asset Compiler 2 | 3 | ## Included Compilers 4 | 5 | * coffee (default for js) 6 | * less 7 | * sass 8 | * stylus (default for css) 9 | 10 | ## Basic Usage 11 | 12 | Environment Settings: 13 | 14 | in `/config/environment.js` 15 | use the AssetCompiler middleware to set your css engine 16 | 17 | ``` 18 | ... 19 | app.configure(function(){ 20 | app.use(compound.assetsCompiler.init()); 21 | ... 22 | app.set('cssEngine', 'stylus'); 23 | ... 24 | }); 25 | ... 26 | ``` 27 | This will compile stylus files (or whatever else you use as your css engine) in 28 | `/app/assets/stylesheets` into `/public/stylesheets` and `/app/assets/coffeescripts` into `/public/javascripts` 29 | 30 | ## Configuration 31 | 32 | Configuration can be done either in an initializer or in the Environment file. 33 | 34 | Compilers should be configured using the `compound.assetCompiler.config(compilerName, options)` method. 35 | This method takes two parameters, the name of the compiler and an object containing the options to configure, 36 | and returns `compound.assetCompiler` for chaining. The default compilers use the following options: 37 | 38 | * render: a function to compile the source into js or css (see Adding Your Own Compiler for more details) 39 | * sourceExtension: `'coffee'` 40 | * destExtension: `'js'` 41 | * sourceDir: `''` or `'/coffeescripts'` 42 | * destDir: `''` or `'/javascripts'` 43 | 44 | It may also contain any other options which can be accessed in the render function ( `this.myCustomOption` ) 45 | 46 | Configure the coffee compiler to look for coffee files in `assets/coffee` instead of `/assets/coffeescripts`: 47 | 48 | `/config/environment.js` 49 | 50 | ``` 51 | ... 52 | app.configure(function(){ 53 | app.use(compound.assetsCompiler.configure('coffee', { 54 | sourceDir: '/coffee' 55 | }).init()); 56 | ... 57 | }); 58 | ... 59 | ``` 60 | 61 | ## Compiler Specific Configuration 62 | 63 | ### Stylus: 64 | When using stylus you may want to use some custom configurations as described here: http://learnboost.github.com/stylus/docs/js.html 65 | 66 | `/config/environment.js` 67 | 68 | ``` 69 | ... 70 | app.configure(function(){ 71 | app.use(compound.assetsCompiler.configure('stylus', { 72 | use: function(stylus) { 73 | stylus.use(mylib); 74 | stylus.define('families', ['Helvetica Neue', 'Helvetica', 'sans-serif']); 75 | stylus.import(path); 76 | stylus.include(path); 77 | } 78 | }).init()); 79 | ... 80 | }); 81 | ... 82 | ``` 83 | 84 | 85 | ## Adding Your Own Compiler 86 | 87 | Adding your own compiler can be done using `compound.assetCompiler.add(compilerName, options)` 88 | 89 | Options should contain the following: 90 | * render: a function to compile the source into js or css (see Adding Your Own Compiler for more details) 91 | * sourceExtension: `'coffee'` 92 | * destExtension: `'js'` 93 | * sourceDir: `''` or `'/coffeescripts'` 94 | * destDir: `''` or `'/javascripts'` 95 | * any other options your render function needs 96 | 97 | The render function takes three arguments: 98 | 99 | 1. src, a string containing the source of the file to be compiled 100 | 2. options: sourceDir, destDir, sourceFileName, destFileName. Shouldn't be necassary in most cases 101 | 3. callback: pass two arguments, error and the compiled source. 102 | 103 | 104 | An example of how to add your own coffee compiler: 105 | 106 | `/config/environment.js` 107 | 108 | ``` 109 | ... 110 | app.configure(function(){ 111 | app.use(compound.assetsCompiler.add('coffee', { 112 | render: function(str, options, fn) { 113 | try { 114 | fn(null, this.coffee.compile(str)); 115 | } catch (err) { 116 | fn(err); 117 | } 118 | }, 119 | coffee: require('coffee-script'), 120 | sourceDir: '/coffeescripts', 121 | destDir: '/javascripts', 122 | sourceExtension: 'coffee', 123 | destExtension: 'js' 124 | }).init()); 125 | ... 126 | }); 127 | ... 128 | ``` 129 | -------------------------------------------------------------------------------- /docs/sources/markdown/code-snippets.md: -------------------------------------------------------------------------------- 1 | # Code snippets 2 | 3 | ## Multiple workers compound server (node 0.8.16) 4 | 5 | Example in CoffeeScript: 6 | 7 | `server.coffee` 8 | ``` 9 | #!/usr/bin/env coffee 10 | 11 | app = module.exports = (params) -> 12 | params = params || {} 13 | # specify current dir as default root of server 14 | params.root = params.root || __dirname 15 | return require('compound').createServer(params) 16 | 17 | cluster = require('cluster') 18 | numCPUs = require('os').cpus().length 19 | 20 | if not module.parent 21 | port = process.env.PORT || 3000 22 | host = process.env.HOST || "0.0.0.0" 23 | server = app() 24 | if cluster.isMaster 25 | # Fork workers. 26 | cluster.fork() for i in [1..numCPUs] 27 | 28 | cluster.on 'exit', (worker, code, signal) -> 29 | console.log 'worker ' + worker.process.pid + ' died' 30 | else 31 | server.listen port, host, -> 32 | console.log( 33 | "Compound server listening on %s:%d within %s environment", 34 | host, port, server.set('env')) 35 | 36 | ``` 37 | 38 | ## Redis session store for Heroku deployment with redistogo addon 39 | 40 | Hook the `REDISTOGO_URL` environment variable in `config/environment.js` and pass it to the RedisStore constructor. 41 | Example in CoffeeScript: 42 | 43 | ``` 44 | 45 | module.exports = (compound) -> 46 | 47 | express = require 'express' 48 | RedisStore = require('connect-redis')(express) 49 | 50 | if process.env['REDISTOGO_URL'] 51 | url = require('url').parse(process.env['REDISTOGO_URL']) 52 | redisOpts = 53 | port: url.port 54 | host: url.hostname 55 | pass: url.auth.split(':')[1] 56 | else 57 | redisOpts = {} 58 | 59 | app.configure -> 60 | app.use compound.assetsCompiler.init() 61 | app.enable 'coffee' 62 | 63 | app.set 'cssEngine', 'stylus' 64 | 65 | app.use express.static(app.root + '/public', {maxAge: 86400000}) 66 | app.use express.bodyParser() 67 | app.use express.cookieParser() 68 | app.use express.session secret: 'secret', store: new RedisStore(redisOpts) 69 | app.use express.methodOverride() 70 | app.use app.router 71 | 72 | ``` 73 | 74 | ## Upload file to compound server 75 | 76 | *
      Discussion in Google Groups 77 | * The solution is to use this middleware 78 | * Check out this example app 79 | 80 | ``` 81 | var form = require('connect-form-sync'); 82 | app.configure(function(){ 83 | .... 84 | app.use(form({ keepExtensions: true })); 85 | app.use(express.bodyParser()); 86 | .... 87 | }); 88 | ``` 89 | 90 | And use it in the controller like that: 91 | 92 | ``` 93 | action('create', function () { 94 | this.file = new File(); 95 | var tmpFile = req.form.files.file; 96 | this.file.upload(tmpFile.name, tmpFile.path, function (err) { 97 | if (err) { 98 | console.log(err); 99 | this.title = 'New file'; 100 | flash('error', 'File can not be created'); 101 | render('new'); 102 | } else { 103 | flash('info', 'File created'); 104 | redirect(path_to.files); 105 | } 106 | }.bind(this)); 107 | }); 108 | ``` 109 | -------------------------------------------------------------------------------- /docs/sources/markdown/coffeescript.md: -------------------------------------------------------------------------------- 1 | # CoffeeScript apps 2 | 3 | Almost all parts of your app can be written in CoffeeScript. If you like coding in Coffee, please do. Just add the `--coffee` option to all `compound` commands. 4 | 5 | ``` 6 | compound init blog --coffee 7 | cd blog 8 | npm install -l 9 | compound g scaffold post title content --coffee 10 | ``` 11 | 12 | Afterwards, you can run `compound server` or `coffee server.coffee` to start your server on port 3000. 13 | 14 | For example, here is a generated CoffeeScript controller: 15 | 16 | ``` 17 | before -> 18 | Post.findById req.params.id, (err, post) => 19 | if err or not post 20 | redirect path_to.posts 21 | else 22 | @post = post 23 | next() 24 | , only: ['show', 'edit', 'update', 'destroy'] 25 | 26 | # GET /posts/new 27 | action 'new', -> 28 | @post = new Post 29 | render 30 | title: 'New post' 31 | 32 | # POST /posts 33 | action 'create', -> 34 | @post = new Post 35 | ['title', 'content'].forEach (field) => 36 | @post[field] = req.body[field] if req.body[field]? 37 | 38 | @post.save (errors) -> 39 | if errors 40 | flash 'error', 'Post can not be created' 41 | render 'new', 42 | title: 'New post' 43 | else 44 | flash 'info', 'Post created' 45 | redirect path_to.posts 46 | 47 | # GET /posts 48 | action 'index', -> 49 | Post.find (err, posts) -> 50 | render 51 | posts: posts 52 | title: 'Posts index' 53 | 54 | # GET /posts/:id 55 | action 'show', -> 56 | render 57 | title: 'Post show' 58 | 59 | # GET /posts/:id/edit 60 | action 'edit', -> 61 | render 62 | title: 'Post edit' 63 | 64 | # PUT /posts/:id 65 | action 'update', -> 66 | ['title', 'content'].forEach (field) => 67 | @post[field] = req.body[field] if req.body[field]? 68 | 69 | @post.save (err) => 70 | if not err 71 | flash 'info', 'Post updated' 72 | redirect path_to.post(@post) 73 | else 74 | flash 'error', 'Post can not be updated' 75 | render 'edit', 76 | title: 'Edit post details' 77 | 78 | # DELETE /posts/:id 79 | action 'destroy', -> 80 | @post.remove (error) -> 81 | if error 82 | flash 'error', 'Can not destroy post' 83 | else 84 | flash 'info', 'Post successfully removed' 85 | send "'" + path_to.posts + "'" 86 | 87 | ``` 88 | 89 | -------------------------------------------------------------------------------- /docs/sources/markdown/events.md: -------------------------------------------------------------------------------- 1 | ## Events 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/sources/markdown/extension-api.md: -------------------------------------------------------------------------------- 1 | # Compound API 2 | 3 | This chapter describes internal API of compound application. Compound app designed 4 | as npm module that can be used as part of other modules. 5 | 6 | Main entry point called `server.js` exports function for creating application. 7 | This function returns regular express application with one addition: `compound` 8 | object. This is object we are talking about. It contains some information about 9 | application, such as root directory path, MVC structure, models. Read this 10 | chapter to get familiar with this powerful tool. 11 | 12 | ## Compound app 13 | 14 | Let's start with the entry point, called `server.js` by default. If you want to 15 | rename it, update package.json with `"main": "server.js"` line. The purpose of that 16 | file: publish function that creates application. This function can create many 17 | instances of application which could be configured and used separately: 18 | 19 | ```javascript 20 | // load package 21 | var instantiateApp = require('.'); 22 | 23 | // create different instances 24 | var app1 = instantiateApp(); 25 | var app2 = instantiateApp(params); 26 | 27 | // run on different ports/hosts 28 | app1.listen(3000); 29 | app2.listen(3001, '10.0.0.2', callback); 30 | ``` 31 | 32 | Instantiation method accepts optional hash of params. These params hash will be 33 | passed to express. 34 | 35 | ### Tools 36 | 37 | The `compound.tools` hash contains commands that can be invoked using the command line, for example `compound routes` will call `compound.tools.routes()` . 38 | 39 | To write a tool, just add another method to the `compound.tools` object, the method name will become the command name: 40 | 41 | ``` 42 | compound.tools.database = function () { 43 | switch (compound.args.shift()) { 44 | case 'clean': 45 | // clean db 46 | break; 47 | case 'backup': 48 | // backup db 49 | break; 50 | case 'restore': 51 | // restore db 52 | break; 53 | default: 54 | console.log('Usage: compound database [clean|backup|restore]'); 55 | } 56 | }; 57 | ``` 58 | 59 | Then the following commands will be available: 60 | 61 | ``` 62 | compound database 63 | compound database backup 64 | compound database clean 65 | compound database restore 66 | ``` 67 | 68 | If you want to see this command when using `compound help`you can provide some information about the tool using the `help`hash: 69 | 70 | ``` 71 | compound.tools.db.help = { 72 | shortcut: 'db', 73 | usage: 'database [backup|restore|clean]', 74 | description: 'Some database features' 75 | }; 76 | ``` 77 | 78 | The next time you call `compound`, you will see: 79 | 80 | ``` 81 | Commands: 82 | ... 83 | db, database [backup|restore|clean] Some database features 84 | 85 | ``` 86 | 87 | If you defined a shortcut, it can be used instead of the full command name: 88 | 89 | ``` 90 | compound db clean 91 | ``` 92 | 93 | To learn more, please check out [the sources](https://github.com/1602/compound/blob/master/lib/tools.js "the sources"): `lib/tools.js` 94 | 95 | ### Generators 96 | 97 | Coming soon. It's about the `compound.generators` module and the `compound generate` commands. 98 | 99 | ### Structure 100 | 101 | Coming soon. This chapter about compound.structure api, overwriting internals of 102 | compound app without touching source code. 103 | 104 | # Extensions 105 | 106 | Any npm package can be used as an extension for CompoundJS. If it should be 107 | loaded at compound app startup, it should export `init` method. This method will 108 | receive single argument: compound app. 109 | 110 | Compound will initialize each extension listed in `config/autoload.js` file. 111 | Example of file generated on `compound init` command: 112 | 113 | ``` 114 | module.exports = function(compound) { 115 | return [ 116 | require('ejs-ext'), 117 | require('jugglingdb'), 118 | require('seedjs') 119 | ]; 120 | }; 121 | ``` 122 | 123 | We are trying to keep compound core tiny, some parts of framework now moved to 124 | separate modules: 125 | 126 | - railway-routes 127 | - jugglingdb 128 | - kontroller 129 | - seedjs 130 | 131 | Some of the modules still loaded from core, but in future everything will be 132 | moved to `config/autoload`. It means that every part of compound can be replaced 133 | with another module that should follow common API. 134 | 135 | -------------------------------------------------------------------------------- /docs/sources/markdown/generators.md: -------------------------------------------------------------------------------- 1 | # Generators 2 | 3 | CompoundJS generators are automated tools that allow you to create a bunch of files automatically. Each generator can be run via: 4 | 5 | ``` 6 | compound generate GENERATOR_NAME 7 | ``` 8 | 9 | or using the shortcut: 10 | 11 | ``` 12 | compound g GENERATOR_NAME 13 | ``` 14 | 15 | Built-in generators are: `model`, `controller`, `scaffold` (alias: `crud`), 16 | `clientside` 17 | 18 | ## Generate model 19 | 20 | Use case: You just need a model and schema. 21 | 22 | Example: 23 | 24 | ``` 25 | compound g model user email password approved:boolean 26 | ``` 27 | Generated files: 28 | 29 | ``` 30 | exists app/ 31 | exists app/models/ 32 | create app/models/user.js 33 | patch db/schema.js 34 | ``` 35 | 36 | The generated model file contains the following code: 37 | 38 | ``` 39 | module.exports = function (compound, User) { 40 | // define User here 41 | }; 42 | ``` 43 | 44 | The patched schema file contains the following code: 45 | 46 | ``` 47 | var User = describe('User', function () { 48 | property('email', String); 49 | property('password', String); 50 | property('approved', Boolean); 51 | }); 52 | ``` 53 | 54 | ## Generate controller 55 | 56 | Use case: You don't need a standard RESTful controller, just a few non-standard actions. 57 | 58 | Example: 59 | 60 | ``` 61 | compound g controller controllername actionName otherActionName 62 | ``` 63 | 64 | Generated files: 65 | 66 | ``` 67 | exists app/ 68 | exists app/controllers/ 69 | create app/controllers/controllername_controller.js 70 | exists app/helpers/ 71 | create app/helpers/controllername_helper.js 72 | exists app/views/ 73 | create app/views/controllername/ 74 | create app/views/controllername/actionName.ejs 75 | create app/views/controllername/anotherActionName.ejs 76 | ``` 77 | 78 | The generated controller file contains the following code: 79 | 80 | ``` 81 | load('application'); 82 | 83 | action("actionName", function () { 84 | render(); 85 | }); 86 | 87 | action("anotherActionName", function () { 88 | render(); 89 | }); 90 | ``` 91 | 92 | ## Generate scaffold (crud) 93 | 94 | The most commonly used generator. It creates a ready-to-use resource controller with all needed actions, views, schema definitions, routes and tests. Compound can also generate scaffolds in CoffeeScript. 95 | 96 | Example call: 97 | 98 | ``` 99 | compound g scaffold post title content createdAt:date 100 | exists app/ 101 | exists app/models/ 102 | create app/models/post.js 103 | exists app/ 104 | exists app/controllers/ 105 | create app/controllers/posts_controller.js 106 | exists app/helpers/ 107 | create app/helpers/posts_helper.js 108 | create app/views/layouts/posts_layout.ejs 109 | create public/stylesheets/scaffold.css 110 | exists app/views/ 111 | create app/views/posts/ 112 | create app/views/posts/_form.ejs 113 | create app/views/posts/new.ejs 114 | create app/views/posts/edit.ejs 115 | create app/views/posts/index.ejs 116 | create app/views/posts/show.ejs 117 | patch config/routes.js 118 | ``` 119 | 120 | ## Clientside 121 | 122 | For using compound on clientside we have to create application bundle. This 123 | bundle then could be passed to browserify to create full bundle (application + 124 | framework + dependencies). This generator allows to create bundle. 125 | 126 | # create full bundle (./public/javascripts/compound.js) 127 | compound generate clientside 128 | 129 | # create full bundle and regenerate on changes in any file 130 | compound generate clientside --watch 131 | 132 | # create full bundle and force quit after completion 133 | compound generate clientside --quit 134 | 135 | Use shortcuts to save your time: 136 | 137 | compound g cs --watch 138 | -------------------------------------------------------------------------------- /docs/sources/markdown/heroku.md: -------------------------------------------------------------------------------- 1 | # Heroku 2 | 3 | Heroku's Node.js hosting is available for public use now. Deploying a CompoundJS application is as simple as `git push`. 4 | 5 | To work with heroku you also need `ruby` as well as the `heroku` gem. 6 | 7 | ### Deploying an application 8 | 9 | First of all, create an application: 10 | 11 | ``` 12 | compound init heroku-app 13 | cd heroku-app 14 | sudo npm link 15 | compound g crud post title content 16 | ``` 17 | 18 | Then initialize a git repository: 19 | 20 | ``` 21 | git init 22 | git add . 23 | git commit -m 'Init' 24 | ``` 25 | 26 | Create a Heroku application: 27 | 28 | ``` 29 | heroku create --stack cedar 30 | ``` 31 | 32 | Want to use MongoDB? 33 | 34 | ``` 35 | heroku addons:add mongohq:free 36 | ``` 37 | 38 | Want to use Redis? 39 | 40 | ``` 41 | heroku addons:add redistogo:nano 42 | ``` 43 | 44 | And deploy: 45 | 46 | ``` 47 | git push heroku master 48 | ``` 49 | 50 | Hook up Procfile (only once): 51 | 52 | ``` 53 | heroku ps:scale web=1 54 | ``` 55 | 56 | Check application state: 57 | 58 | ``` 59 | heroku ps 60 | ``` 61 | 62 | Visit your application: 63 | 64 | ``` 65 | heroku open 66 | ``` 67 | 68 | If something went wrong, you can check out the logs: 69 | 70 | ``` 71 | heroku logs 72 | ``` 73 | 74 | To access the CompoundJS REPL console, do: 75 | 76 | ``` 77 | heroku run compound console 78 | ``` 79 | 80 | MongoHQ provides a web interface for browsing your MongoDB database, to use it go to `http://mongohq.com/`, create an account, then click "Add remote connection" and configure the link to your database. You can retrieve details required for the connection using this command: 81 | 82 | ``` 83 | heroku config --long 84 | ``` -------------------------------------------------------------------------------- /docs/sources/markdown/localization.md: -------------------------------------------------------------------------------- 1 | # Localization 2 | 3 | Basic steps: 4 | 5 | * Create dictionary to translate tokens into natural language (`config/locales/*.yml`) 6 | * Use tokens instead of natural language everywhere in your app (`t` helper) 7 | * Manually detect language for each request (`setLocale` method) 8 | 9 | CompoundJS allows you to create localized applications: Just place a `YAML`-formatted file to `config/locales` directory: 10 | 11 | `config/locales/en.yml` 12 | ``` 13 | en: 14 | session: 15 | new: "Sign in" 16 | destroy: "Sign out" 17 | user: 18 | new: "Sign up" 19 | destroy: "Cancel my account" 20 | welcome: "Hello %, howdy? 21 | validation: 22 | name: "Username required" 23 | 24 | ``` 25 | 26 | NOTE: Translations can contain `%` symbol(s) for variable substitution. 27 | 28 | Define a user locale before filter to your application controller: 29 | 30 | `app/controllers/application_controller.js` 31 | ``` 32 | before(setUserLocale); 33 | function setUserLocale () { 34 | // define locale from user settings, or from headers or use default 35 | var locale = req.user ? req.user.locale : 'en'; 36 | // call global function setLocale 37 | setLocale(locale); 38 | } 39 | ``` 40 | 41 | And use localized tokens inside your app views using the `t` helper: 42 | 43 | ``` 44 | <%= t('session.new') %> 45 | <%= t('user.new') %> 46 | <%= t(['user.welcome', user.name]) %> 47 | ``` 48 | 49 | You can also use the `t` helper in controllers: 50 | 51 | ``` 52 | flash('error', t('user.validation.name')); 53 | ``` 54 | 55 | or in models: 56 | 57 | ``` 58 | return t('email.activate', 'en') 59 | ``` 60 | 61 | NOTE: When you use the `t` helper in models, you have to pass the `locale` as the second parameter. 62 | 63 | ## Configuration 64 | 65 | Localization behavior can be configured using the following settings: 66 | 67 | * `defaultLocale`: Default locale name 68 | * `translationMissing`: Defines what action to perform when translation is missing. Possible Values: 69 |
        70 |
      • `default` - Display translation for default locale
      • 71 |
      • `display` - Show an error like "Translation missing for email.activate"
      • 72 |
      73 | * `default` - Display translation for default locale 74 | * `display` - Show an error like "Translation missing for email.activate" 75 | 76 | Example: 77 | 78 | ``` 79 | app.configure(function () { 80 | app.set('defaultLocale', 'en'); 81 | }); 82 | 83 | app.configure('development', function(){ 84 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 85 | app.set('translationMissing', 'display'); 86 | }); 87 | 88 | app.configure('production', function () { 89 | app.use(express.errorHandler()); 90 | app.set('translationMissing', 'default'); 91 | }); 92 | ``` 93 | -------------------------------------------------------------------------------- /docs/sources/markdown/repl.md: -------------------------------------------------------------------------------- 1 | # REPL console 2 | 3 | To run the REPL console use this command: 4 | 5 | ``` 6 | compound console 7 | ``` 8 | 9 | or its shortcut: 10 | 11 | ``` 12 | compound c 13 | ``` 14 | 15 | The REPL console is just a simple Node.js console with some CompoundJS, for example models. 16 | 17 | Just one note on working with the console: Node.js is asynchronous by nature which makes console debugging much more complicated, since you have to use a callback to fetch results from the database for instance. We have added one useful method to simplify asynchronous debugging using the REPL console. It's called `c` and you can pass it as a parameter to any function that requires a callback. It will store the parameters passed to the callback to variables called `_0, _1, ..., _N` where N is the length of `arguments`. 18 | 19 | Example: 20 | 21 | ``` 22 | compound c 23 | compound> User.find(53, c) 24 | Callback called with 2 arguments: 25 | _0 = null 26 | _1 = [object Object] 27 | compound> _1 28 | { email: [Getter/Setter], 29 | password: [Getter/Setter], 30 | activationCode: [Getter/Setter], 31 | activated: [Getter/Setter], 32 | forcePassChange: [Getter/Setter], 33 | isAdmin: [Getter/Setter], 34 | id: [Getter/Setter] } 35 | 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /docs/sources/src/stylesheets/_content.styl: -------------------------------------------------------------------------------- 1 | body 2 | padding: 30px 0 0 50px 3 | 4 | .content-wrapper 5 | width: 650px 6 | margin: auto 7 | 8 | .style-switch 9 | position: fixed 10 | top: 10px 11 | right: 10px 12 | 13 | width: 20px 14 | height: 20px 15 | border-radius: 3px 16 | cursor: pointer 17 | 18 | p 19 | font: normal 12px 'Lucida Grande', sans-serif 20 | padding: 0px 20px 15px 20px 21 | line-height: 24px 22 | 23 | code, pre 24 | padding: 2px 4px 25 | border-radius: 3px 26 | 27 | pre 28 | padding: 15px 29 | margin-bottom: 15px 30 | 31 | overflow-x: auto 32 | 33 | .logo 34 | margin: auto 35 | display: block 36 | margin-bottom: 50px 37 | 38 | section 39 | margin-bottom: 50px 40 | 41 | ul 42 | font: normal 12px 'Lucida Grande', sans-serif 43 | margin-bottom: 15px 44 | margin-left: 50px 45 | 46 | a:hover 47 | text-decoration: none 48 | -------------------------------------------------------------------------------- /docs/sources/src/stylesheets/_headlines.styl: -------------------------------------------------------------------------------- 1 | h1, h2, h3 2 | font: 300 24px 'Quicksand', 'Lucida Grande', sans-serif 3 | margin-bottom: 20px 4 | display: block 5 | padding: 10px 20px 10px 20px 6 | 7 | .decimal 8 | opacity: 0.8 9 | margin-right: 5px 10 | 11 | h1 12 | font-size: 30px 13 | text-transform: uppercase 14 | 15 | h2 16 | font-size: 24px 17 | text-transform: uppercase 18 | 19 | h3 20 | font-size: 18px -------------------------------------------------------------------------------- /docs/sources/src/stylesheets/_reset.styl: -------------------------------------------------------------------------------- 1 | /* 2 | html5doctor.com Reset Stylesheet 3 | v1.6.1 4 | Last Updated: 2010-09-17 5 | Author: Richard Clark - http://richclarkdesign.com 6 | Twitter: @rich_clark 7 | 8 | Stylus-ized by 9 | dale tan 10 | http://www.whatthedale.com 11 | @HellaTan 12 | 13 | */ 14 | 15 | html, body, div, span, object, iframe, 16 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 17 | abbr, address, cite, code, 18 | del, dfn, em, img, ins, kbd, q, samp, 19 | small, strong, sub, sup, var, 20 | b, i, 21 | dl, dt, dd, ol, ul, li, 22 | fieldset, form, label, legend, 23 | table, caption, tbody, tfoot, thead, tr, th, td, 24 | article, aside, canvas, details, figcaption, figure, 25 | footer, header, hgroup, menu, nav, section, summary, 26 | time, mark, audio, video 27 | background transparent 28 | border 0 29 | font-size 100% 30 | margin 0 31 | outline 0 32 | padding 0 33 | vertical-align baseline 34 | 35 | body 36 | line-height:1; 37 | 38 | article, aside, details, figcaption, figure, 39 | footer, header, hgroup, menu, nav, section 40 | display block 41 | 42 | nav ul 43 | list-style none 44 | 45 | blockquote, q 46 | quotes none 47 | 48 | blockquote:before, blockquote:after, 49 | q:before, q:after 50 | content '' 51 | content none 52 | 53 | a 54 | background transparent 55 | font-size 100% 56 | margin 0 57 | padding 0 58 | vertical-align baseline 59 | 60 | /* change colours to suit your needs */ 61 | ins 62 | background-color #ff9 63 | color #000 64 | text-decoration none 65 | 66 | /* change colours to suit your needs */ 67 | mark 68 | background-color #ff9 69 | color #000 70 | font-style italic 71 | font-weight bold 72 | 73 | del 74 | text-decoration line-through 75 | 76 | abbr[title], dfn[title] 77 | border-bottom 1px dotted 78 | cursor help 79 | 80 | table 81 | border-collapse collapse 82 | border-spacing 0 83 | 84 | /* change border colour to suit your needs */ 85 | hr 86 | border 0 87 | border-top 1px solid #ccc 88 | display block 89 | height 1px 90 | margin 1em 0 91 | padding 0 92 | 93 | input, select 94 | vertical-align middle 95 | 96 | -------------------------------------------------------------------------------- /docs/sources/src/stylesheets/_sidebar.styl: -------------------------------------------------------------------------------- 1 | $paddingLeft = 15px 2 | .sidebar 3 | position: fixed 4 | top: 0 5 | left: 0 6 | 7 | width: 230px 8 | height: 100% 9 | 10 | padding: 15px 11 | 12 | transition: left 0.5s ease-out 13 | -webkit-transition: left 0.5s ease-out 14 | -moz-transition: left 0.5s ease-out 15 | -o-transition: left 0.5s ease-out 16 | 17 | overflow-y: auto 18 | 19 | &.hidden 20 | left: -220px 21 | 22 | .menu 23 | opacity: 1 24 | 25 | .menu 26 | width: 40px 27 | height: 40px 28 | 29 | background: url('../images/menu.png') no-repeat center 30 | opacity: 0 31 | 32 | position: absolute 33 | right: 0 34 | top: 0 35 | 36 | transition: opacity 0.3s ease-out 37 | -webkit-transition: opacity 0.3s ease-out 38 | -moz-transition: opacity 0.3s ease-out 39 | -o-transition: opacity 0.3s ease-out 40 | 41 | ul 42 | list-style-type: none 43 | 44 | a 45 | font: normal 12px 'Lucida Grande', Helvetica, sans-serif 46 | display: block 47 | padding: 3px 48 | text-decoration: none 49 | 50 | li 51 | ul 52 | display: none 53 | &.open 54 | ul 55 | display: block 56 | &.level-1 57 | padding-left: ($paddingLeft * 1) 58 | &.level-2 59 | padding-left: ($paddingLeft * 2) 60 | &.level-3 61 | padding-left: ($paddingLeft * 3) 62 | 63 | -------------------------------------------------------------------------------- /docs/sources/src/stylesheets/_styled_objects.styl: -------------------------------------------------------------------------------- 1 | body 2 | background: $main-color-a 3 | 4 | h1, h2, h3 5 | color: $highlight-text-color 6 | border-bottom: 1px solid rgba($main-color-b, 0.1) 7 | 8 | p, pre 9 | color: $main-color-b 10 | 11 | code 12 | color: $highlight-text-color 13 | 14 | code, pre 15 | border: 1px solid rgba($main-color-b, 0.1) 16 | background: $code-pre-background 17 | 18 | .style-switch 19 | background: $main-color-b 20 | 21 | .sidebar 22 | background: $code-pre-background 23 | border-right: 1px solid rgba($main-color-b, 0.1) 24 | a 25 | color: $main-color-b 26 | 27 | &:hover, &.active 28 | background: $code-pre-background 29 | color: $highlight-text-color 30 | 31 | .level-0 32 | > a 33 | color: $highlight-text-color 34 | 35 | ul 36 | color: $main-color-b 37 | 38 | a 39 | color: $highlight-text-color -------------------------------------------------------------------------------- /docs/sources/src/stylesheets/_styles.styl: -------------------------------------------------------------------------------- 1 | html.style-dark 2 | $highlight-text-color = #fc0243 3 | $main-color-a = #282828 4 | $main-color-b = #ffffff 5 | $code-pre-background = rgba(black, 0.3) 6 | 7 | @import '_styled_objects' 8 | 9 | html.style-light 10 | $highlight-text-color = #fc0243 11 | $main-color-a = #ffffff 12 | $main-color-b = #282828 13 | $code-pre-background = rgba(black, 0.1) 14 | 15 | @import '_styled_objects' -------------------------------------------------------------------------------- /docs/sources/src/stylesheets/application.styl: -------------------------------------------------------------------------------- 1 | @import '_reset' 2 | 3 | @import '_styles' 4 | @import '_headlines' 5 | @import '_content' 6 | @import '_sidebar' 7 | 8 | * 9 | -webkit-transition: background-color 0.2s ease-out 10 | 11 | p 12 | -webkit-transition: color 0.5s ease-out -------------------------------------------------------------------------------- /docs/sources/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | div, 4 | span, 5 | object, 6 | iframe, 7 | h1, 8 | h2, 9 | h3, 10 | h4, 11 | h5, 12 | h6, 13 | p, 14 | blockquote, 15 | pre, 16 | abbr, 17 | address, 18 | cite, 19 | code, 20 | del, 21 | dfn, 22 | em, 23 | img, 24 | ins, 25 | kbd, 26 | q, 27 | samp, 28 | small, 29 | strong, 30 | sub, 31 | sup, 32 | var, 33 | b, 34 | i, 35 | dl, 36 | dt, 37 | dd, 38 | ol, 39 | ul, 40 | li, 41 | fieldset, 42 | form, 43 | label, 44 | legend, 45 | table, 46 | caption, 47 | tbody, 48 | tfoot, 49 | thead, 50 | tr, 51 | th, 52 | td, 53 | article, 54 | aside, 55 | canvas, 56 | details, 57 | figcaption, 58 | figure, 59 | footer, 60 | header, 61 | hgroup, 62 | menu, 63 | nav, 64 | section, 65 | summary, 66 | time, 67 | mark, 68 | audio, 69 | video { 70 | background: transparent; 71 | border: 0; 72 | font-size: 100%; 73 | margin: 0; 74 | outline: 0; 75 | padding: 0; 76 | vertical-align: baseline; 77 | } 78 | body { 79 | line-height: 1; 80 | } 81 | article, 82 | aside, 83 | details, 84 | figcaption, 85 | figure, 86 | footer, 87 | header, 88 | hgroup, 89 | menu, 90 | nav, 91 | section { 92 | display: block; 93 | } 94 | nav ul { 95 | list-style: none; 96 | } 97 | blockquote, 98 | q { 99 | quotes: none; 100 | } 101 | blockquote:before, 102 | blockquote:after, 103 | q:before, 104 | q:after { 105 | content: ''; 106 | content: none; 107 | } 108 | a { 109 | background: transparent; 110 | font-size: 100%; 111 | margin: 0; 112 | padding: 0; 113 | vertical-align: baseline; 114 | } 115 | ins { 116 | background-color: #ff9; 117 | color: #000; 118 | text-decoration: none; 119 | } 120 | mark { 121 | background-color: #ff9; 122 | color: #000; 123 | font-style: italic; 124 | font-weight: bold; 125 | } 126 | del { 127 | text-decoration: line-through; 128 | } 129 | abbr[title], 130 | dfn[title] { 131 | border-bottom: 1px dotted; 132 | cursor: help; 133 | } 134 | table { 135 | border-collapse: collapse; 136 | border-spacing: 0; 137 | } 138 | hr { 139 | border: 0; 140 | border-top: 1px solid #ccc; 141 | display: block; 142 | height: 1px; 143 | margin: 1em 0; 144 | padding: 0; 145 | } 146 | input, 147 | select { 148 | vertical-align: middle; 149 | } 150 | html.style-dark body { 151 | background: #282828; 152 | } 153 | html.style-dark h1, 154 | html.style-dark h2, 155 | html.style-dark h3 { 156 | color: #fc0243; 157 | border-bottom: 1px solid rgba(255,255,255,0.1); 158 | } 159 | html.style-dark p, 160 | html.style-dark pre { 161 | color: #fff; 162 | } 163 | html.style-dark code { 164 | color: #fc0243; 165 | } 166 | html.style-dark code, 167 | html.style-dark pre { 168 | border: 1px solid rgba(255,255,255,0.1); 169 | background: rgba(0,0,0,0.3); 170 | } 171 | html.style-dark .style-switch { 172 | background: #fff; 173 | } 174 | html.style-dark .sidebar { 175 | background: rgba(0,0,0,0.3); 176 | border-right: 1px solid rgba(255,255,255,0.1); 177 | } 178 | html.style-dark .sidebar a { 179 | color: #fff; 180 | } 181 | html.style-dark .sidebar a:hover, 182 | html.style-dark .sidebar a.active { 183 | background: rgba(0,0,0,0.3); 184 | color: #fc0243; 185 | } 186 | html.style-dark .sidebar .level-0 > a { 187 | color: #fc0243; 188 | } 189 | html.style-dark ul { 190 | color: #fff; 191 | } 192 | html.style-dark a { 193 | color: #fc0243; 194 | } 195 | html.style-light body { 196 | background: #fff; 197 | } 198 | html.style-light h1, 199 | html.style-light h2, 200 | html.style-light h3 { 201 | color: #fc0243; 202 | border-bottom: 1px solid rgba(40,40,40,0.1); 203 | } 204 | html.style-light p, 205 | html.style-light pre { 206 | color: #282828; 207 | } 208 | html.style-light code { 209 | color: #fc0243; 210 | } 211 | html.style-light code, 212 | html.style-light pre { 213 | border: 1px solid rgba(40,40,40,0.1); 214 | background: rgba(0,0,0,0.1); 215 | } 216 | html.style-light .style-switch { 217 | background: #282828; 218 | } 219 | html.style-light .sidebar { 220 | background: rgba(0,0,0,0.1); 221 | border-right: 1px solid rgba(40,40,40,0.1); 222 | } 223 | html.style-light .sidebar a { 224 | color: #282828; 225 | } 226 | html.style-light .sidebar a:hover, 227 | html.style-light .sidebar a.active { 228 | background: rgba(0,0,0,0.1); 229 | color: #fc0243; 230 | } 231 | html.style-light .sidebar .level-0 > a { 232 | color: #fc0243; 233 | } 234 | html.style-light ul { 235 | color: #282828; 236 | } 237 | html.style-light a { 238 | color: #fc0243; 239 | } 240 | h1, 241 | h2, 242 | h3 { 243 | font: 300 24px 'Quicksand', 'Lucida Grande', sans-serif; 244 | margin-bottom: 20px; 245 | display: block; 246 | padding: 10px 20px 10px 20px; 247 | } 248 | h1 .decimal, 249 | h2 .decimal, 250 | h3 .decimal { 251 | opacity: 0.8; 252 | margin-right: 5px; 253 | } 254 | h1 { 255 | font-size: 30px; 256 | text-transform: uppercase; 257 | } 258 | h2 { 259 | font-size: 24px; 260 | text-transform: uppercase; 261 | } 262 | h3 { 263 | font-size: 18px; 264 | } 265 | body { 266 | padding: 30px 0 0 50px; 267 | } 268 | .content-wrapper { 269 | width: 650px; 270 | margin: auto; 271 | } 272 | .content-wrapper .style-switch { 273 | position: fixed; 274 | top: 10px; 275 | right: 10px; 276 | width: 20px; 277 | height: 20px; 278 | border-radius: 3px; 279 | cursor: pointer; 280 | } 281 | .content-wrapper p { 282 | font: normal 12px 'Lucida Grande', sans-serif; 283 | padding: 0px 20px 15px 20px; 284 | line-height: 24px; 285 | } 286 | .content-wrapper code, 287 | .content-wrapper pre { 288 | padding: 2px 4px; 289 | border-radius: 3px; 290 | } 291 | .content-wrapper pre { 292 | padding: 15px; 293 | margin-bottom: 15px; 294 | overflow-x: auto; 295 | } 296 | .content-wrapper .logo { 297 | margin: auto; 298 | display: block; 299 | margin-bottom: 50px; 300 | } 301 | .content-wrapper section { 302 | margin-bottom: 50px; 303 | } 304 | .content-wrapper ul { 305 | font: normal 12px 'Lucida Grande', sans-serif; 306 | margin-bottom: 15px; 307 | margin-left: 50px; 308 | } 309 | .content-wrapper a:hover { 310 | text-decoration: none; 311 | } 312 | .sidebar { 313 | position: fixed; 314 | top: 0; 315 | left: 0; 316 | width: 230px; 317 | height: 100%; 318 | padding: 15px; 319 | transition: left 0.5s ease-out; 320 | -webkit-transition: left 0.5s ease-out; 321 | -moz-transition: left 0.5s ease-out; 322 | -o-transition: left 0.5s ease-out; 323 | overflow-y: auto; 324 | } 325 | .sidebar.hidden { 326 | left: -220px; 327 | } 328 | .sidebar.hidden .menu { 329 | opacity: 1; 330 | } 331 | .sidebar .menu { 332 | width: 40px; 333 | height: 40px; 334 | background: url("../images/menu.png") no-repeat center; 335 | opacity: 0; 336 | position: absolute; 337 | right: 0; 338 | top: 0; 339 | transition: opacity 0.3s ease-out; 340 | -webkit-transition: opacity 0.3s ease-out; 341 | -moz-transition: opacity 0.3s ease-out; 342 | -o-transition: opacity 0.3s ease-out; 343 | } 344 | .sidebar ul { 345 | list-style-type: none; 346 | } 347 | .sidebar ul a { 348 | font: normal 12px 'Lucida Grande', Helvetica, sans-serif; 349 | display: block; 350 | padding: 3px; 351 | text-decoration: none; 352 | } 353 | .sidebar ul li ul { 354 | display: none; 355 | } 356 | .sidebar ul li.open ul { 357 | display: block; 358 | } 359 | .sidebar ul li.level-1 { 360 | padding-left: 15px; 361 | } 362 | .sidebar ul li.level-2 { 363 | padding-left: 30px; 364 | } 365 | .sidebar ul li.level-3 { 366 | padding-left: 45px; 367 | } 368 | * { 369 | -webkit-transition: background-color 0.2s ease-out; 370 | } 371 | p { 372 | -webkit-transition: color 0.5s ease-out; 373 | } 374 | -------------------------------------------------------------------------------- /docs/sources/stylesheets/prettify.css: -------------------------------------------------------------------------------- 1 | .com { color: #93a1a1; } 2 | .lit { color: #195f91; } 3 | .pun, .opn, .clo { color: #93a1a1; } 4 | .fun { color: #dc322f; } 5 | .str, .atv { color: #268bd2; } 6 | .kwd, .tag { color: #195f91; } 7 | .typ, .atn, .dec, .var { color: #CB4B16; } 8 | .pln { color: #93a1a1; } 9 | pre.prettyprint { 10 | background: #fefbf3; 11 | padding: 9px; 12 | border: 1px solid rgba(0,0,0,.2); 13 | -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1); 14 | -moz-box-shadow: 0 1px 2px rgba(0,0,0,.1); 15 | box-shadow: 0 1px 2px rgba(0,0,0,.1); 16 | } 17 | 18 | /* Specify class=linenums on a pre to get line numbering */ 19 | ol.linenums { margin: 0 0 0 40px; } /* IE indents via margin-left */ 20 | ol.linenums li { color: rgba(0,0,0,.15); line-height: 20px; } 21 | /* Alternate shading for lines */ 22 | li.L1, li.L3, li.L5, li.L7, li.L9 { } 23 | 24 | /* 25 | $base03: #002b36; 26 | $base02: #073642; 27 | $base01: #586e75; 28 | $base00: #657b83; 29 | $base0: #839496; 30 | $base1: #93a1a1; 31 | $base2: #eee8d5; 32 | $base3: #fdf6e3; 33 | $yellow: #b58900; 34 | $orange: #cb4b16; 35 | $red: #dc322f; 36 | $magenta: #d33682; 37 | $violet: #6c71c4; 38 | $blue: #268bd2; 39 | $cyan: #2aa198; 40 | $green: #859900; 41 | */ 42 | -------------------------------------------------------------------------------- /docs/sources/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CompoundJS - build apps with love 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 |
      19 |
      20 | {{ CONTENT }} 21 |
      22 | 23 | 38 | 39 | -------------------------------------------------------------------------------- /docs/sources/to_md.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts our HTML file to Markdown files 3 | * !!! CAUTION: Bad code, this should be deprecated when the docs are done !!! 4 | */ 5 | 6 | var fs = require('fs') 7 | , jsdom = require('jsdom'); 8 | 9 | String.prototype.replaceCodeAndNL = function() { 10 | var text = this.replace(//ig, '`') 11 | .replace(/<\/code>/ig, '`') 12 | .replace(/^\s+|\s+$/ig, '') 13 | .replace(/\n\s+/ig, '\n'); 14 | return text; 15 | }; 16 | 17 | String.prototype.indent = function(indentation) { 18 | return indentation + this.split('\n').join('\n' + indentation); 19 | }; 20 | 21 | var MarkDown = { 22 | init: function () { 23 | var data = fs.readFileSync('./index.html').toString(); 24 | var self = this; 25 | jsdom.env( 26 | data, ["http://code.jquery.com/jquery.js"], 27 | function(errors, window) { 28 | self.generateMarkdownUsingWindow(window); 29 | } 30 | ); 31 | }, 32 | generateMarkdownUsingWindow: function (window) { 33 | var $ = window.$ 34 | , content = '' 35 | , lastBigHeadlineId = ''; 36 | $('section').each(function () { 37 | var section = $(this); 38 | var items = section.find('> *'); 39 | 40 | var headlineType = section.find('h1, h2, h3').first().get(0).nodeName.toLowerCase(); 41 | 42 | 43 | 44 | if (headlineType === 'h1') { 45 | if (lastBigHeadlineId !== '') { 46 | fs.writeFileSync('markdown/' + lastBigHeadlineId + '.md', content); 47 | content = ''; 48 | } 49 | lastBigHeadlineId = section.attr('id'); 50 | } 51 | 52 | items.each(function() { 53 | var $item = $(this); 54 | var tag = $item.get(0).nodeName.toLowerCase(); 55 | 56 | if (tag === 'h1') { 57 | content += '# ' + $item.text() + '\n\n'; 58 | } else if (tag === 'h2') { 59 | content += '## ' + $item.text() + '\n\n'; 60 | } else if (tag === 'h3') { 61 | content += '### ' + $item.text() + '\n\n'; 62 | } else if (tag === 'p') { 63 | $item.find('a').each(function() { 64 | var text = '[' + $(this).text() + '](' + $(this).attr('href') + ' "' + $(this).text() + '")'; 65 | $(this).replaceWith(text); 66 | }); 67 | 68 | content += $item 69 | .html().replaceCodeAndNL() + '\n\n'; 70 | } else if (tag === 'pre') { 71 | content += '```\n' + $item.text().replace(/\n$/i, '') + '\n```\n\n'; 72 | } else if (tag === 'ul') { 73 | $item.find('li').each(function() { 74 | var text = $(this).html().replaceCodeAndNL(); 75 | content += '* ' + text + '\n'; 76 | }); 77 | content += '\n'; 78 | } else if(tag === 'code') { 79 | content += '`' + $(this).html() + '`\n'; 80 | } 81 | }); 82 | }); 83 | } 84 | }; 85 | 86 | MarkDown.init(); -------------------------------------------------------------------------------- /lib/i18n.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'); 3 | 4 | /** 5 | * Initialize localization module 6 | */ 7 | module.exports = function i18n(compound, root) { 8 | var app = compound.app; 9 | root = root || compound.root; 10 | var dir = root + '/config/locales'; 11 | 12 | if (!app) { 13 | return; 14 | } 15 | 16 | if (!compound.utils.existsSync(dir)) { 17 | app.set('i18n', 'off'); 18 | } 19 | 20 | if (app.set('locale') === 'off' || app.set('i18n') === 'off') { 21 | compound.T = function () { 22 | return function (path, defVal) { 23 | return defVal; 24 | }; 25 | }; 26 | compound.t = compound.T(); 27 | return; 28 | } else { 29 | compound.t = T(true); 30 | compound.T = T; 31 | } 32 | 33 | load(dir, compound); 34 | 35 | /** 36 | * Global translation helper 37 | * 38 | * @param {Boolean} global 39 | * @public 40 | */ 41 | function T(global) { 42 | if (global) { 43 | // helper for global scope (models, initializers, etc) 44 | // requires two params (locale expected) 45 | return function t(path, locale, defaultValue) { 46 | if (!locale) { 47 | throw new Error('Locale expected'); 48 | } 49 | return translate(path, locale, defaultValue); 50 | }; 51 | } else { 52 | // helper for local scope (controllers, views, helpers) 53 | // requires one param 54 | return function t(path, defaultValue) { 55 | return translate(path, t.locale, defaultValue); 56 | }; 57 | } 58 | 59 | function translate(path, locale, defaultValue) { 60 | var translation = compound.__localeData[locale], substitute; 61 | 62 | function nextPathItem(token) { 63 | return (translation = translation[token]); 64 | } 65 | 66 | if (typeof path === 'string') { 67 | substitute = false; 68 | } else { 69 | substitute = path; 70 | path = substitute.shift(); 71 | } 72 | 73 | if (!translation || !path.split('.').every(nextPathItem)) { 74 | translation = typeof defaultValue === 'undefined' ? 75 | translationMissing(locale, path, defaultValue) : 76 | defaultValue; 77 | } 78 | 79 | if (translation && substitute && substitute.length) { 80 | substitute.forEach(function(substitution) { 81 | translation = translation.replace(/%/, substitution.toString().replace(/%/g, '')); 82 | }); 83 | } 84 | 85 | return translation; 86 | } 87 | 88 | function translationMissing(locale, path, defaultValue) { 89 | 90 | if (compound.parent) { 91 | var translation; 92 | translation = compound.parent.t(path, locale, defaultValue); 93 | if (translation) { 94 | return translation; 95 | } 96 | 97 | } 98 | 99 | switch (app.settings.translationMissing) { 100 | case 'display': 101 | return 'translation missing for ' + locale + '.' + path; 102 | case 'default': 103 | case undefined: 104 | var defLocale = app.settings.defaultLocale; 105 | return !defLocale || locale === defLocale ? '' : translate(path, defLocale, defaultValue); 106 | } 107 | } 108 | } 109 | 110 | T.localeSupported = function(localeName) { 111 | return !!compound.__localeData[localeName]; 112 | }; 113 | }; 114 | 115 | /** 116 | * Load localization files from `dir`. Locales can be in yaml, json or coffee 117 | * format 118 | * 119 | * Example locale.yml file: 120 | * 121 | * en: 122 | * key: 'Value' 123 | * 124 | * @param {String} dir - absolute path to locales directory. 125 | */ 126 | function load(dir, compound) { 127 | var coffee; 128 | fs.readdirSync(dir).forEach(function(file) { 129 | if (file.match(/^\./)) return; 130 | 131 | var filename = dir + '/' + file; 132 | var code = fs.readFileSync(filename, 'utf8').toString(); 133 | var obj; 134 | 135 | try { 136 | if (file.match(/\.ya?ml$/)) { 137 | var yaml = require(['yaml', 'js'].join('-')), 138 | obj = yaml.load(code); 139 | if (obj.shift) { 140 | obj = obj.shift(); 141 | } 142 | } else if (file.match(/\.json/)) { 143 | obj = JSON.parse(code); 144 | } else if (file.match(/\.coffee/)) { 145 | coffee = coffee || require('coffee-script'); 146 | obj = coffee.eval(code); 147 | } else { 148 | console.log('Unsupported extension of locale file ' + filename); 149 | } 150 | } catch (e) { 151 | console.log('Parsing file ' + filename); 152 | console.log(e); 153 | console.log(e.stack); 154 | } 155 | 156 | if (obj) { 157 | addTranslation(obj, compound); 158 | } 159 | 160 | }); 161 | } 162 | 163 | /** 164 | * Add translation to `lang` to application locales collection 165 | */ 166 | function addTranslation(lang, compound) { 167 | Object.keys(lang).forEach(function(localeName) { 168 | var translations = lang[localeName]; 169 | if (compound.locales.indexOf(localeName) === -1) { 170 | compound.locales.push([localeName, translations.lang && translations.lang.name || localeName]); 171 | } 172 | compound.__localeData[localeName] = compound.__localeData[localeName] || {}; 173 | Object.keys(translations).forEach(function(namespace) { 174 | if ('object' === typeof compound.__localeData[localeName] && namespace in compound.__localeData[localeName]) { 175 | merge(compound.__localeData[localeName][namespace], translations[namespace]); 176 | } else { 177 | compound.__localeData[localeName][namespace] = translations[namespace]; 178 | } 179 | }); 180 | }); 181 | 182 | function merge(dest, data) { 183 | for (var i in data) { 184 | if (i in dest && typeof dest[i] === 'object') { 185 | merge(dest[i], data[i]); 186 | } else { 187 | dest[i] = data[i]; 188 | } 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /lib/models.js: -------------------------------------------------------------------------------- 1 | // Deps 2 | var fs = require('fs'), 3 | path = require('path'); 4 | 5 | /** 6 | * Initialize models with validations and implementation 7 | * 8 | * @param {Compound} compound - compound app. 9 | */ 10 | module.exports = function loadModels(compound, root) { 11 | if (!compound.structure.models) { 12 | return; 13 | } 14 | 15 | Object.keys(compound.structure.models).forEach(function(model) { 16 | var md = compound.structure.models[model]; 17 | var foundModel = compound.model(model); 18 | if (foundModel && foundModel._validations) { 19 | delete foundModel._validations; 20 | } 21 | if (typeof md === 'function') { 22 | md(compound, foundModel); 23 | } else { 24 | // rw.models[md.name || md.modelName || model] = md; 25 | } 26 | }); 27 | 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /lib/server/controllers/crud-json.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (modelName, parentController) { 3 | 4 | var modelNameLower = modelName.toLowerCase(); 5 | var modelNamePlural = require('../../utils').pluralize(modelNameLower); 6 | var modelNamePluralCamel = require('../../utils').pluralize(modelName); 7 | 8 | function CRUDController(init) { 9 | if (parentController) { 10 | parentController.call(this, init); 11 | } 12 | 13 | init.before(loadModel, { 14 | only: ['show', 'edit', 'update', 'destroy'] 15 | }); 16 | } 17 | 18 | if (parentController) { 19 | require('util').inherits(CRUDController, parentController); 20 | } 21 | 22 | CRUDController.prototype.create = function create(c) { 23 | var self = this; 24 | c[modelName].create(c.body[modelName], function (err, model) { 25 | if (err) { 26 | c.send({ 27 | code: 500, 28 | error: model.errors || err 29 | }); 30 | } else { 31 | var res = { 32 | code: 200 33 | } 34 | res[modelNameLower] = model; 35 | c.send(res); 36 | } 37 | }); 38 | }; 39 | 40 | CRUDController.prototype.index = function index(c) { 41 | var self = this; 42 | this.title = modelNamePluralCamel + ' index'; 43 | c[modelName].all(function (err, models) { 44 | if (err) { 45 | c.send({code: 500, error: err}); 46 | } else { 47 | var res = {code: 200}; 48 | res[modelNamePlural] = models.map(function (m) { 49 | return m.toObject(); 50 | }); 51 | c.send(res); 52 | } 53 | }); 54 | }; 55 | 56 | CRUDController.prototype.show = function show(c) { 57 | this.title = modelName + ' show'; 58 | var model = this[modelNameLower]; 59 | var res = {code: 200}; 60 | res[modelNameLower] = model.toObject(); 61 | c.send(res); 62 | }; 63 | 64 | CRUDController.prototype.update = function update(c) { 65 | var model = this[modelNameLower]; 66 | var self = this; 67 | 68 | this.title = modelName + ' edit'; 69 | 70 | model.updateAttributes(c.body[modelName], function (err) { 71 | if (err) { 72 | c.send({ 73 | code: 500, 74 | error: model && model.errors || err 75 | }); 76 | } else { 77 | var res = {code: 200}; 78 | res[modelNameLower] = model.toObject(); 79 | c.send(res); 80 | } 81 | }); 82 | 83 | }; 84 | 85 | CRUDController.prototype.destroy = function destroy(c) { 86 | this[modelNameLower].destroy(function (error) { 87 | if (error) { 88 | c.send({code: 500, error: error}); 89 | } else { 90 | c.send({code: 200}); 91 | } 92 | }); 93 | }; 94 | 95 | function loadModel(c) { 96 | var self = this; 97 | c[modelName].find(c.params.id, function (err, model) { 98 | if (err) { 99 | return c.send({code: 500, error: err}); 100 | } 101 | if (!model) { 102 | return c.send({code: 404}); 103 | } 104 | self[modelNameLower] = model; 105 | c.next(); 106 | }); 107 | } 108 | 109 | return CRUDController; 110 | 111 | }; 112 | -------------------------------------------------------------------------------- /lib/server/controllers/crud.js: -------------------------------------------------------------------------------- 1 | module.exports = function (modelName, parentController) { 2 | 3 | var modelNameLower = modelName.toLowerCase(); 4 | var modelNamePlural = require('../../utils').pluralize(modelNameLower); 5 | var modelNamePluralCamel = require('../../utils').pluralize(modelName); 6 | 7 | function CRUDController(init) { 8 | if (parentController) { 9 | parentController.call(this, init); 10 | } 11 | 12 | init.before(loadModel, { 13 | only: ['show', 'edit', 'update', 'destroy'] 14 | }); 15 | } 16 | 17 | if (parentController) { 18 | require('util').inherits(CRUDController, parentController); 19 | } 20 | 21 | CRUDController.prototype['new'] = function (c) { 22 | this.title = 'New ' + modelNameLower; 23 | this[modelNameLower] = new (c[modelName]); 24 | c.render(); 25 | }; 26 | 27 | CRUDController.prototype.create = function create(c) { 28 | var self = this; 29 | c[modelName].create(c.body[modelName], function (err, model) { 30 | if (err) { 31 | c.flash('error', modelName + ' can not be created'); 32 | self[modelNameLower] = model; 33 | c.render('new', { 34 | title: 'New ' + modelNameLower 35 | }); 36 | } else { 37 | c.flash('info', modelName + ' created'); 38 | c.redirect(c.pathTo[modelNamePlural]); 39 | } 40 | }); 41 | }; 42 | 43 | CRUDController.prototype.index = function index(c) { 44 | var self = this; 45 | this.title = modelNamePluralCamel + ' index'; 46 | c[modelName].all(function (err, models) { 47 | c.respondTo(function (format) { 48 | format.json(function () { 49 | c.send(models); 50 | }); 51 | format.html(function () { 52 | self[modelNamePlural] = models; 53 | c.render(); 54 | }); 55 | }); 56 | }); 57 | }; 58 | 59 | CRUDController.prototype.show = function show(c) { 60 | this.title = modelName + ' show'; 61 | var model = this[modelNameLower]; 62 | c.respondTo(function (format) { 63 | format.json(function () { 64 | c.send(model); 65 | }); 66 | format.html(function () { 67 | c.render(); 68 | }); 69 | }); 70 | }; 71 | 72 | CRUDController.prototype.edit = function edit(c) { 73 | this.title = modelName + ' edit'; 74 | c.render(); 75 | }; 76 | 77 | CRUDController.prototype.update = function update(c) { 78 | var model = this[modelNameLower]; 79 | var self = this; 80 | 81 | this.title = modelName + ' edit'; 82 | 83 | model.updateAttributes(c.body[modelName], function (err) { 84 | c.respondTo(function (format) { 85 | format.json(function () { 86 | if (err) { 87 | c.send({ 88 | code: 500, 89 | error: model && model.errors || err 90 | }); 91 | } else { 92 | var res = {code: 200}; 93 | res[modelNameLower] = model.toObject(); 94 | c.send(res); 95 | } 96 | }); 97 | format.html(function () { 98 | if (!err) { 99 | c.flash('info', modelName + ' updated'); 100 | c.redirect(c.pathTo[modelNameLower](model)); 101 | } else { 102 | c.flash('error', modelName + ' can not be updated'); 103 | c.render('edit'); 104 | } 105 | }); 106 | }); 107 | }); 108 | 109 | }; 110 | 111 | CRUDController.prototype.destroy = function destroy(c) { 112 | this[modelNameLower].destroy(function (error) { 113 | if (error) { 114 | c.flash('error', 'Can not destroy ' + modelNameLower); 115 | } else { 116 | c.flash('info', modelName + ' successfully removed'); 117 | } 118 | c.send("'" + c.pathTo[modelNamePlural] + "'"); 119 | }); 120 | }; 121 | 122 | function loadModel(c) { 123 | var self = this; 124 | c[modelName].find(c.params.id, function (err, model) { 125 | if (err || !model) { 126 | c.redirect(c.pathTo[modelNamePlural]); 127 | } else { 128 | self[modelNameLower] = model; 129 | c.next(); 130 | } 131 | }); 132 | } 133 | 134 | return CRUDController; 135 | 136 | }; 137 | -------------------------------------------------------------------------------- /lib/server/controllers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | crud: require('./crud'), 3 | crudJSON: require('./crud-json') 4 | }; 5 | -------------------------------------------------------------------------------- /lib/server/extensions.js: -------------------------------------------------------------------------------- 1 | var utils = require('../utils'), 2 | Module = require('module').Module, 3 | fs = require('fs'), 4 | cs = require('coffee-script'), 5 | path = require('path'); 6 | 7 | /** 8 | * Initialize extensions 9 | */ 10 | module.exports = function(root) { 11 | var root = root || this.root; 12 | 13 | var autoload = path.join(root, 'config', 'autoload'); 14 | if (utils.existsSync(autoload + '.js') || 15 | utils.existsSync(autoload + '.coffee') 16 | ) { 17 | var exts = require(autoload); 18 | init(exts(this), this); 19 | } 20 | 21 | function init(exts, c) { 22 | if (exts && exts.forEach) { 23 | exts.forEach(function (e) { 24 | if (e.init) { 25 | e.init(c); 26 | } 27 | }); 28 | } 29 | } 30 | 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /lib/server/generators.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lib/generators.js 3 | * 4 | * @defines {Generators} 5 | * @creator {Sascha Gehlich } 6 | * @description { 7 | * Manages all generators 8 | * } 9 | */ 10 | 11 | /** 12 | * Generators class 13 | * Manages all generators 14 | * 15 | * @constructor 16 | */ 17 | function Generators () { 18 | this.generators = {}; 19 | this.generatorAliases = {}; 20 | this.quiet = false; 21 | }; 22 | 23 | /** 24 | * Initialization 25 | * 26 | * @param {Compound} Compound app 27 | */ 28 | Generators.prototype.init = function init(compound) { 29 | this.compound = compound; 30 | this.register('app', require('./generators/app_generator.js')); 31 | }; 32 | 33 | /** 34 | * Run generator by it's name/alias with some args array. 35 | * 36 | * @param {String} name - Generator name, e.g.: 'crud' 37 | * @param @optional {Array} args - Arguments, e.g.: ['post', 'title', 'content']. 38 | */ 39 | Generators.prototype.perform = function perform(name, args) { 40 | var generator; 41 | if (generator = this.generatorForAlias(name)) { 42 | generator.init(this.compound, args); 43 | generator.quiet = this.quiet; 44 | return generator.perform(args); 45 | } else { 46 | console.log('Generator "' + name + '" not found'); 47 | } 48 | }; 49 | 50 | /** 51 | * Register generator 52 | * 53 | * @param {String} - name of generator 54 | * @param {Generator} - constructor of generator 55 | * 56 | * Constructor should create object with `init(app, args)` and `perform` methods 57 | */ 58 | Generators.prototype.register = function register(name, generator) { 59 | var self = this; 60 | 61 | this.generators[name] = generator; 62 | 63 | generator.aliases.forEach(function (alias) { 64 | self.generatorAliases[alias] = generator; 65 | }); 66 | }; 67 | 68 | 69 | /** 70 | * Returns a generator matching the given alias 71 | * 72 | * @param {String} Generator alias 73 | */ 74 | Generators.prototype.generatorForAlias = function (alias) { 75 | if (this.generatorAliases.hasOwnProperty(alias)) { 76 | return new this.generatorAliases[alias]; 77 | } else if (alias in this.generators) { 78 | return new this.generators[alias]; 79 | } 80 | }; 81 | 82 | Generators.prototype.list = function () { 83 | return Object.keys(this.generators).join(', '); 84 | }; 85 | 86 | module.exports = new Generators(); 87 | -------------------------------------------------------------------------------- /lib/server/generators/app_generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lib/generators/app_generator.js 3 | * 4 | * @defines {AppGenerator} 5 | * @since {1.1.4} 6 | * @creator {Sascha Gehlich } 7 | * @description { 8 | * Generates an entirely new CompoundJS application 9 | * } 10 | */ 11 | 12 | /** 13 | * Module dependencies 14 | */ 15 | var util = require('util') 16 | , BaseGenerator = require('./base_generator') 17 | , path = require('path'); 18 | 19 | /** 20 | * Generates an entirely new CompoundJS application 21 | * 22 | * @constructor 23 | */ 24 | function AppGenerator () { 25 | AppGenerator.super_.call(this); 26 | }; 27 | util.inherits(AppGenerator, BaseGenerator); 28 | 29 | /** 30 | * Command line aliases 31 | */ 32 | AppGenerator.aliases = [ 'app', 'a', 'init' ]; 33 | 34 | /** 35 | * Default key name (first command line argument is stored in options[defaultKeyName]) 36 | */ 37 | AppGenerator.prototype.defaultKeyName = 'appName'; 38 | 39 | /** 40 | * Performs the generator action 41 | * 42 | * @param {Array} arguments 43 | */ 44 | AppGenerator.prototype.perform = function (args) { 45 | BaseGenerator.prototype.perform.apply(this, [].slice.call(arguments)); 46 | 47 | if (this.options.appName) { 48 | this.baseDir = path.join(this.baseDir, this.options.appName); 49 | } 50 | if (this.isBaseDirExists()) { 51 | return; 52 | } 53 | this.createDirectoryStructure(); 54 | this.copyFiles(); 55 | }; 56 | 57 | /** 58 | * Creates the basic directory structure 59 | */ 60 | AppGenerator.prototype.createDirectoryStructure = function () { 61 | var self = this; 62 | [ 63 | 'app/', 64 | 'app/assets/', 65 | 'app/assets/coffeescripts/', 66 | 'app/assets/stylesheets/', 67 | 'app/models/', 68 | 'app/controllers/', 69 | 'app/helpers/', 70 | 'app/tools/', 71 | 'app/views/', 72 | 'app/views/layouts/', 73 | 'db/', 74 | 'db/seeds/', 75 | 'db/seeds/development/', 76 | 'log/', 77 | 'public/', 78 | 'public/images', 79 | 'public/stylesheets/', 80 | 'public/javascripts/', 81 | 'node_modules/', 82 | 'config/', 83 | 'config/locales/', 84 | 'config/initializers/', 85 | 'config/environments/' 86 | ].forEach(function (dir) { 87 | self.createDirectory(dir); 88 | }); 89 | }; 90 | 91 | /** 92 | * Copy files from templates directory 93 | */ 94 | AppGenerator.prototype.copyFiles = function () { 95 | var self = this; 96 | var templateVariables = { 97 | 'ENGINE': this.getEngine(), 98 | 'STYLE': this.getCSSEngineExtension(), 99 | 'CODE': this.getCodeExtension(), 100 | 'TPL': this.getTemplateExtension(), 101 | 'DATA': this.getDataExtension(), 102 | 'VIEWENGINE': this.getTemplateEngine(), 103 | 'CSSENGINE': this.getCSSEngine(), 104 | 'APPNAME': this.getAppName(), 105 | 'SECRET': this.generateSecret(), 106 | 'DBDRIVER': this.getDatabaseDriver(), 107 | 'SUFFIX': this.isEvalAllowed() ? '_controller' : '', 108 | 'EVAL': this.isEvalAllowed() ? '_eval' : '', 109 | 'DBDEPENDENCY': this.getDatabaseDependency() 110 | }; 111 | 112 | [ 113 | 'app/assets/coffeescripts/application.coffee', 114 | 'app/assets/stylesheets/application.{{ STYLE }}', 115 | 'app/tools/database.{{ CODE }}', 116 | 'config/environment.{{ CODE }}', 117 | 'config/environments/development.{{ CODE }}', 118 | 'config/environments/production.{{ CODE }}', 119 | 'config/environments/test.{{ CODE }}', 120 | 'config/routes.{{ CODE }}', 121 | 'config/autoload.{{ CODE }}', 122 | 'db/schema.{{ CODE }}', 123 | 'public/index.html', 124 | 'public/stylesheets/bootstrap.css', 125 | 'public/stylesheets/bootstrap-responsive.css', 126 | 'public/images/glyphicons-halflings-white.png', 127 | 'public/images/glyphicons-halflings.png', 128 | 'public/images/compound.png', 129 | 'public/javascripts/rails.js', 130 | 'public/javascripts/bootstrap.js', 131 | 'public/javascripts/application.js', 132 | 'public/favicon.ico', 133 | 'Procfile', 134 | 'README.md', 135 | 'package.json', 136 | 'server.{{ CODE }}' 137 | ].forEach(function (file) { 138 | self.copyFile(file, templateVariables); 139 | }); 140 | 141 | self.copyFile('gitignore-example', '.gitignore', {}); 142 | 143 | self.copyFile('config/database_{{ DBDRIVER }}.{{ CODE }}', 'config/database.{{ CODE }}', templateVariables); 144 | self.copyTemplate('application_layout', 'app/views/layouts/application_layout.{{ TPL }}', templateVariables); 145 | self.copyController('application_controller{{ EVAL }}.{{ CODE }}', 'app/controllers/application{{ SUFFIX }}.{{ CODE }}', templateVariables); 146 | }; 147 | 148 | /** 149 | * Helper methods for renaming / replacing template variables 150 | */ 151 | 152 | module.exports = AppGenerator; 153 | -------------------------------------------------------------------------------- /lib/server/generators/base_generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lib/generators/base_generator.js 3 | * 4 | * @defines {BaseGenerator} 5 | * @since {1.1.4} 6 | * @creator {Sascha Gehlich } 7 | * @description { 8 | * Provides a base class for all other generators 9 | * } 10 | */ 11 | 12 | /** 13 | * Module dependencies 14 | */ 15 | 16 | var util = require('util') 17 | , GeneratorUtilities = require('./generator_utils'); 18 | 19 | function BaseGenerator () { 20 | GeneratorUtilities.call(this); 21 | this.options = {}; 22 | }; 23 | util.inherits(BaseGenerator, GeneratorUtilities); 24 | 25 | /** 26 | * Initialization 27 | * 28 | * @param {Compound} Compound app 29 | * @param {Array} args - Command line arguments 30 | */ 31 | BaseGenerator.prototype.init = function (compound) { 32 | this.app = compound.app; 33 | this.baseDir = compound.root; 34 | this.options = []; 35 | }; 36 | 37 | /** 38 | * Parse command line options 39 | * 40 | * @param {String} defaultKeyName - key name to interpret first arg. 41 | * @param {Array} args - Command line arguments 42 | * @private 43 | */ 44 | BaseGenerator.prototype.parseOptions = function () { 45 | var options = this.options; 46 | while (options.length) { 47 | options.pop(); 48 | } 49 | options.tpl = this.app && this.app.settings['view engine'] || 'ejs'; 50 | options.coffee = this.app && this.app.enabled('coffee'); 51 | options.db = 'memory'; 52 | options.stylus = false; 53 | 54 | var defKey = this.defaultKeyName; 55 | var key = defKey || false; 56 | this.args.forEach(function(arg) { 57 | if (arg.slice(0, 2) == '--') { 58 | key = arg.slice(2); 59 | options[key] = true; 60 | } else if (arg[0] == '-') { 61 | key = arg.slice(1); 62 | options[key] = true; 63 | } else if (key) { 64 | options[key] = arg; 65 | if (defKey && key !== defKey) { 66 | key = defKey; 67 | defKey = false; 68 | } else { 69 | key = false; 70 | } 71 | } else { 72 | options.push(arg); 73 | } 74 | }); 75 | if (options.nocoffee) { 76 | options.coffee = false; 77 | } 78 | }; 79 | 80 | /** 81 | * Performs the generator action 82 | * 83 | * @param {Array} arguments 84 | */ 85 | BaseGenerator.prototype.perform = function (args) { 86 | this.args = args; 87 | this.parseOptions(); 88 | } 89 | 90 | module.exports = BaseGenerator; 91 | -------------------------------------------------------------------------------- /lib/server/installer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lib/installer.js 3 | * 4 | * @defines {Installer} 5 | * @creator {Sascha Gehlich } 6 | * @description { 7 | * Can install additional compound modules 8 | * } 9 | */ 10 | 11 | var exec = require('child_process').exec, 12 | util = require('util'), 13 | path = require('path'), 14 | fs = require('fs'); 15 | 16 | /** 17 | * Installer class 18 | * Can install additional compound modules 19 | * 20 | * @constructor 21 | */ 22 | function Installer () {} 23 | 24 | /** 25 | * Initialization 26 | * 27 | * @param {Compound} Compound app 28 | */ 29 | Installer.prototype.init = function init(compound) { 30 | this.compound = compound; 31 | this.app = compound.app; 32 | }; 33 | 34 | Installer.prototype.install = function(packageName) { 35 | var $ = this.app.compound.utils.stylize.$; 36 | var self = this; 37 | this.npmInstall(packageName, function (statusCode) { 38 | if(statusCode !== 0) { 39 | return console.log('\n`npm install` exited with status code', statusCode); 40 | } 41 | 42 | util.puts($('install').bold.green + ' co-' + packageName); 43 | 44 | self.patchAutoload(packageName); 45 | }); 46 | }; 47 | 48 | Installer.prototype.npmInstall = function(packageName, callback) { 49 | var npm = exec('npm install co-' + packageName + ' --save --color=always'); 50 | npm.stdout.pipe(process.stdout); 51 | npm.stderr.pipe(process.stderr); 52 | npm.on('exit', function (statusCode) { 53 | callback(statusCode); 54 | }); 55 | }; 56 | 57 | Installer.prototype.patchAutoload = function(packageName) { 58 | var autoloadFile, 59 | autoloadPath, 60 | $ = this.app.compound.utils.stylize.$, 61 | regex, match, data, defaultModules; 62 | 63 | if (this.app.enabled('coffee')) { 64 | autoloadFile = 'config/autoload.coffee'; 65 | 66 | autoloadPath = path.resolve(process.cwd(), autoloadFile); 67 | 68 | data = fs.readFileSync(autoloadPath).toString(); 69 | 70 | // convert newlines to unicode character so that match works 71 | data = data.replace(/\n/g, '\uffff'); 72 | 73 | regex = /defaultModules = \[(.*?)\]/i; 74 | match = data.match(regex); 75 | 76 | // Get current defaultModules 77 | defaultModules = match[1]; 78 | defaultModules = defaultModules.replace(/\uffff/g, '\n').trim(); 79 | 80 | // Add new defaultModule 81 | defaultModules += ',\n \'co-' + packageName + '\''; 82 | 83 | // replace defaultModules 84 | data = data.replace(regex, 'defaultModules = [\n ' + defaultModules + '\n ]'); 85 | data = data.replace(/\uffff/g, '\n'); 86 | 87 | fs.writeFileSync(autoloadPath, data); 88 | } else { 89 | autoloadFile = 'config/autoload.js'; 90 | autoloadPath = path.resolve(process.cwd(), autoloadFile); 91 | 92 | data = fs.readFileSync(autoloadPath).toString(); 93 | 94 | // convert newlines to unicode character so that match works 95 | data = data.replace(/\n/g, '\uffff'); 96 | 97 | regex = /defaultModules = \[(.*?)\]/i; 98 | match = data.match(regex); 99 | 100 | // Get current defaultModules 101 | defaultModules = match[1]; 102 | defaultModules = defaultModules.replace(/\uffff/g, '\n').trim(); 103 | 104 | // Add new defaultModule 105 | defaultModules += ',\n \'co-' + packageName + '\''; 106 | 107 | // replace defaultModules 108 | data = data.replace(regex, 'defaultModules = [\n ' + defaultModules + '\n ]'); 109 | data = data.replace(/\uffff/g, '\n'); 110 | 111 | fs.writeFileSync(autoloadPath, data); 112 | } 113 | 114 | util.puts($('patch').bold.blue + ' ' + autoloadFile); 115 | 116 | process.exit(0); 117 | }; 118 | 119 | module.exports = new Installer(); 120 | -------------------------------------------------------------------------------- /lib/server/logger.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var coloring = /\u001b\[\d+m/g; 4 | 5 | /** 6 | * Store stream 7 | * 8 | * TODO: rewrite to modular style 9 | */ 10 | exports.stream = null; 11 | 12 | /** 13 | * Init logger 14 | * 15 | * @param {CompoundServer} compound - compound app. 16 | */ 17 | exports.init = function(compound) { 18 | var app = compound.app; 19 | if (!app) return; 20 | 21 | compound.constructor.prototype.log = function(s) { 22 | compound.logger.write(s); 23 | }; 24 | 25 | compound.on('after configure', function() { 26 | 27 | var quiet = app.enabled('quiet'); 28 | 29 | if (quiet) { 30 | var logDir = app.get('log dir') || path.join(compound.root, 'log'), 31 | logFile = path.join(logDir, app.get('env') + '.log'); 32 | 33 | fs.exists(logDir, function(exists) { 34 | if (!exists) return; 35 | exports.stream = fs.createWriteStream(logFile, { 36 | flags: 'a', 37 | mode: 0666, 38 | encoding: 'utf8' 39 | }); 40 | }); 41 | } else { 42 | exports.stream = process.stdout; 43 | } 44 | 45 | compound.utils.debug = quiet ? 46 | function () { 47 | compound.logger.write(Array.prototype.join.call(arguments, ' ') 48 | .replace(coloring, '')); 49 | } : function() { 50 | compound.logger.write(Array.prototype.join.call(arguments, ' ')); 51 | }; 52 | 53 | }); 54 | 55 | }; 56 | 57 | /** 58 | * Write method 59 | * 60 | * @param {String} str - string to print. 61 | */ 62 | exports.write = function(str) { 63 | var stream = exports.stream || process.stdout; 64 | stream && stream.write(str + '\n'); 65 | }; 66 | -------------------------------------------------------------------------------- /lib/server/middleware/domain.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function () { 3 | var domain = require('domain'); 4 | 5 | return function (req, res, next) { 6 | var d = domain.create(); 7 | d.on('error', function (err) { 8 | err.message = err.message || 'Unknown error'; 9 | res.statusCode = 500; 10 | res.end('
      ' + err.stack + '
      '); 11 | console.log(err); 12 | setTimeout(function () { 13 | d.dispose(); 14 | }, 500); 15 | }); 16 | d.run(next); 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/server/middleware/index.js: -------------------------------------------------------------------------------- 1 | exports.domain = require('./domain'); 2 | -------------------------------------------------------------------------------- /lib/server/structure.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var Module = require('module').Module; 4 | var cs = require('coffee-script'); 5 | 6 | var debug = function(){}; 7 | 8 | module.exports = function(compound) { 9 | 10 | if (process.env.NODE_DEBUG && /structure/.test(process.env.NODE_DEBUG)) { 11 | debug = function(x) { 12 | compound.log(x); 13 | }; 14 | } 15 | 16 | compound.structure.register = function(what, info) { 17 | debug('register ' + what + ':' + JSON.stringify(info)); 18 | var key = what; 19 | if (!key.match(/s$/)) { 20 | key = what + 's'; 21 | } 22 | if (!compound.structure.paths[key]) { 23 | compound.structure.paths[key] = {}; 24 | } 25 | compound.structure.paths[key][info.name] = info; 26 | if (!compound.structure[key]) { 27 | compound.structure[key] = {}; 28 | } 29 | var contents = compound.structure[key]; 30 | if (compound.app.enabled('watch') && key !== 'views') { 31 | if (!info.stat) { 32 | info.stat = fs.statSync(info.file); 33 | } 34 | contents.__defineGetter__(info.name, function() { 35 | var stat = fs.statSync(info.file); 36 | var file = info; 37 | if (!file.cache || file.stat && file.stat.mtime < stat.mtime) { 38 | debug('reload ' + what + ' from ' + info.file); 39 | delete Module._cache[info.file]; 40 | file.cache = requireFile(info.file); 41 | } 42 | file.stat = stat; 43 | return file.cache; 44 | }); 45 | } else { 46 | contents[info.name] = requireFile(info.file); 47 | } 48 | 49 | function requireFile(file) { 50 | switch (key) { 51 | case 'views': 52 | return path.normalize(file); 53 | case 'controllers': 54 | if (file.match(/_controller/)) { 55 | var src = fs.readFileSync(file).toString(); 56 | return file.match(/\.coffee$/) ? cs.compile(src) : src; 57 | } else { 58 | return require(file); 59 | } 60 | default: 61 | return require(file); 62 | } 63 | } 64 | }; 65 | 66 | return function(root) { 67 | root = root || compound.root; 68 | var appDir = root + '/app/'; 69 | if (fs.existsSync(appDir)) { 70 | debug('Loading structure from ' + root); 71 | fs.readdirSync(appDir).forEach(function(file) { 72 | if (isRegisteredType(file) && fs.statSync(appDir + file).isDirectory()) { 73 | read(root, file); 74 | } 75 | }); 76 | } 77 | }; 78 | 79 | function isRegisteredType(type) { 80 | return ['errors', 'tools', 'presenters', 'controllers', 'views', 'models', 'helpers'].indexOf(type) !== -1; 81 | } 82 | 83 | function read(root, key, dir, prefix) { 84 | var dir = dir || 'app/' + key; 85 | var contents = compound.structure[key]; 86 | var directory = compound.structure.paths[key]; 87 | var abspath = root + '/' + dir; 88 | prefix = prefix || ''; 89 | 90 | debug('read ' + key + 's from ' + abspath); 91 | 92 | fs.readdirSync(abspath).forEach(readNode); 93 | 94 | function readNode(filename) { 95 | debug('read ' + filename); 96 | 97 | if (filename.match(/^\./)) { 98 | // skip files starting with point 99 | return; 100 | } 101 | var file = abspath + '/' + filename; 102 | var ext = path.extname(filename); 103 | var stat = fs.statSync(file); 104 | if (stat.isDirectory()) { 105 | if (fs.existsSync(file + '/index.js')) { 106 | compound.structure.register(key, { 107 | name: filename, 108 | file: file + '/index.js', 109 | stat: stat 110 | }); 111 | } else { 112 | read(root, key, dir + '/' + filename, prefix + filename + '/'); 113 | } 114 | } else { 115 | var name = prefix + filename.replace(ext, ''); 116 | if (key !== 'views') { 117 | name = name.replace(/\/index$/, ''); 118 | } 119 | 120 | if (key === 'controllers' && name in contents) { 121 | for (var i in item.prototype) { 122 | contents[name].prototype[i] = item.prototype[i]; 123 | } 124 | } else { 125 | compound.structure.register(key, { 126 | name: name, 127 | file: path.normalize(file), 128 | stat: stat 129 | }); 130 | } 131 | 132 | } 133 | } 134 | 135 | } 136 | }; 137 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var undef, util = require('util'), 2 | path = require('path'), 3 | fs = require('fs'), 4 | vm = require('vm'), 5 | i8n = require('inflection'); 6 | 7 | exports.html_tag_params = function(params, override) { 8 | var maybe_params = ''; 9 | safe_merge(params, override); 10 | for (var key in params) { 11 | if (params[key] != undef) { 12 | maybe_params += ' ' + key + '="' + params[key].toString().replace(/&/g, '&').replace(/"/g, '"') + '"'; 13 | } 14 | } 15 | return maybe_params; 16 | }; 17 | 18 | var safe_merge = exports.safe_merge = function(merge_what) { 19 | merge_what = merge_what || {}; 20 | Array.prototype.slice.call(arguments).forEach(function(merge_with, i) { 21 | if (i === 0) { 22 | return; 23 | } 24 | for (var key in merge_with) { 25 | if (!merge_with.hasOwnProperty(key) || key in merge_what) continue; 26 | merge_what[key] = merge_with[key]; 27 | } 28 | }); 29 | return merge_what; 30 | }; 31 | 32 | exports.humanize = function(underscored) { 33 | var res = underscored.replace(/_/g, ' '); 34 | return res[0].toUpperCase() + res.substr(1); 35 | }; 36 | 37 | exports.camelize = function(underscored, upcaseFirstLetter) { 38 | var res = ''; 39 | underscored.split('_').forEach(function(part) { 40 | res += part[0].toUpperCase() + part.substr(1); 41 | }); 42 | return upcaseFirstLetter ? res : res[0].toLowerCase() + res.substr(1); 43 | }; 44 | 45 | exports.classify = function(str) { 46 | return exports.camelize(exports.singularize(str)); 47 | }; 48 | 49 | exports.underscore = function(camelCaseStr) { 50 | var initialUnderscore = camelCaseStr.match(/^_/) ? '_' : ''; 51 | var str = camelCaseStr 52 | .replace(/^_([A-Z])/g, '$1') 53 | .replace(/([A-Z])/g, '_$1') 54 | .replace(/^_/, initialUnderscore); 55 | return str.toLowerCase(); 56 | }; 57 | 58 | exports.singularize = function singularize(str, singular) { 59 | return i8n.singularize(str, singular); 60 | }; 61 | 62 | exports.pluralize = function pluralize(str, plural) { 63 | return i8n.pluralize(str, plural); 64 | }; 65 | 66 | // Stylize a string 67 | function stylize(str, style) { 68 | if (typeof window !== 'undefined') { 69 | return str; 70 | } 71 | var styles = { 72 | 'bold' : [1, 22], 73 | 'italic' : [3, 23], 74 | 'underline' : [4, 24], 75 | 'cyan' : [96, 39], 76 | 'blue' : [34, 39], 77 | 'yellow' : [33, 39], 78 | 'green' : [32, 39], 79 | 'red' : [31, 39], 80 | 'grey' : [90, 39], 81 | 'green-hi' : [92, 32] 82 | }; 83 | var s = styles[style]; 84 | return '\033[' + s[0] + 'm' + str + '\033[' + s[1] + 'm'; 85 | } 86 | 87 | var $ = function(str) { 88 | str = new(String)(str); 89 | 90 | ['bold', 'grey', 'yellow', 'red', 'green', 'cyan', 'blue', 'italic', 'underline'].forEach(function(style) { 91 | Object.defineProperty(str, style, { 92 | get: function() { 93 | return $(stylize(this, style)); 94 | } 95 | }); 96 | }); 97 | return str; 98 | }; 99 | stylize.$ = $; 100 | exports.stylize = stylize; 101 | 102 | var addCoverage = exports.addCoverage = function(code, filename) { 103 | if (!global.__cov) return code; 104 | return require('semicov').addCoverage(code, filename); 105 | }; 106 | 107 | // cache for source code 108 | var cache = {}; 109 | // cache for compiled scripts 110 | var scriptCache = {}; 111 | 112 | function addSpaces(str, len, to_start) { 113 | var str_len = str.length; 114 | for (var i = str_len; i < len; i += 1) { 115 | if (!to_start) { 116 | str += ' '; 117 | } else { 118 | str = ' ' + str; 119 | } 120 | } 121 | return str; 122 | } 123 | exports.addSpaces = addSpaces; 124 | 125 | function readYaml(file) { 126 | try { 127 | var yaml = require(['yaml', 'js'].join('-')); 128 | var obj = yaml.load(fs.readFileSync(file).toString()); 129 | if (obj && obj.shift) { 130 | obj = obj.shift(); 131 | } 132 | return obj; 133 | } catch (e) { 134 | console.log('Error in reading', file); 135 | console.log(e.message); 136 | console.log(e.stack); 137 | } 138 | } 139 | exports.readYaml = readYaml; 140 | 141 | exports.existsSync = fs.existsSync || path.existsSync; 142 | 143 | function recursivelyWalkDir(directory, filter, callback) { 144 | if(!callback) { callback = filter; filter = null; } 145 | var results = []; 146 | fs.readdir(directory, function(err, list) { 147 | if (err) return callback(err); 148 | var pending = list.length; 149 | if (!pending) return callback(null, results); 150 | 151 | list.forEach(function(file) { 152 | file = directory + '/' + file; 153 | fs.stat(file, function(err, stat) { 154 | if (stat && stat.isDirectory()) { 155 | recursivelyWalkDir(file, filter, function(err, res) { 156 | results = results.concat(res); 157 | if (!--pending) callback(null, results); 158 | }); 159 | } else { 160 | results.push(file); 161 | if (!--pending) callback(null, results); 162 | } 163 | }); 164 | }); 165 | }); 166 | } 167 | exports.recursivelyWalkDir = recursivelyWalkDir; 168 | 169 | function ensureDirectoryExists(directory, root) { 170 | var dirs = directory.split('/'), dir = dirs.shift(); 171 | root = (root || '') + dir + '/'; 172 | 173 | try { fs.mkdirSync(root); } 174 | catch (e) { 175 | if(!fs.statSync(root).isDirectory()) throw new Error(e); 176 | } 177 | 178 | return !dirs.length || ensureDirectoryExists(dirs.join('/'), root); 179 | } 180 | exports.ensureDirectoryExists = ensureDirectoryExists; 181 | 182 | exports.inherits = function(constructor, superConstructor, includeClassMethods) { 183 | util.inherits(constructor, superConstructor); 184 | if (includeClassMethods) { 185 | Object.keys(superConstructor).forEach(function(key) { 186 | constructor[key] = superConstructor[key]; 187 | }); 188 | } 189 | }; 190 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "compound", 3 | "version": "1.2.1", 4 | "author": "Anatoliy Chakkaev", 5 | "contributors": [ 6 | "Anatoliy Chakkaev (http://anatoliy.in)", 7 | "Sascha Gehlich (http://filshmedia.net)" 8 | ], 9 | "description": "CompoundJS - MVC framework for NodeJS", 10 | "keywords": [ 11 | "mvc", 12 | "web", 13 | "framework", 14 | "rails", 15 | "express", 16 | "railway" 17 | ], 18 | "url": "http://compoundjs.com", 19 | "engines": [ 20 | "node >= 0.8.0" 21 | ], 22 | "main": "lib/server/compound.js", 23 | "bin": { 24 | "compound": "bin/compound.js", 25 | "rw": "bin/compound.js" 26 | }, 27 | "man": [ 28 | "./man/compound.1", 29 | "./man/controller.3", 30 | "./man/changelog.3", 31 | "./man/railway-changelog.3", 32 | "./man/helpers.3", 33 | "./man/routing.3" 34 | ], 35 | "dependencies": { 36 | "yaml-js": ">= 0.0.2", 37 | "coffee-script": "1.7.x", 38 | "ejs-ext": ">= 0.1.4-2", 39 | "jade-ext": "= 0.0.7", 40 | "railway-routes": ">= 0.0.10", 41 | "kontroller": ">= 0.0.15", 42 | "inflection": "~1.2.5" 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "https://github.com/1602/compound.git" 47 | }, 48 | "devDependencies": { 49 | "jugglingdb": ">= 0.1.25", 50 | "nodeunit": "latest", 51 | "semicov": "*", 52 | "marked": ">= 0.2.6", 53 | "express": "4.x", 54 | "should": "1.2.2", 55 | "mocha": "1.8.1", 56 | "jshint": "^2.6.3" 57 | }, 58 | "scripts": { 59 | "test": "make test", 60 | "prepublish": "make build" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /scripts/doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | src=$1 4 | dest=$2 5 | 6 | if ! [ `which ronn` ]; then 7 | echo 'ronn rubygem is not installed, run "gem install ronn"' 8 | exit 0 9 | fi 10 | 11 | mkdir -p $(dirname $dest) 12 | 13 | # VERSION=$(grep version package.json | perl -pi -e 's/[^-\d\.]//g') 14 | 15 | case $dest in 16 | *.[13]) 17 | ronn --roff $1 --pipe --organization=1602\ Software --manual=CompoundJS > $2 18 | exit $? 19 | ;; 20 | 21 | *.html) 22 | (ronn -5 $1 --pipe\ 23 | --style=toc\ 24 | --organization=1602\ Software\ 25 | --manual=CompoundJS &&\ 26 | cat docs/ga.html &&\ 27 | cat docs/footer.html) > $2 28 | exit $? 29 | ;; 30 | esac 31 | -------------------------------------------------------------------------------- /templates/Procfile: -------------------------------------------------------------------------------- 1 | web: {{ ENGINE }} server.{{ CODE }} -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | ### Welcome to CompoundJS ### -------------------------------------------------------------------------------- /templates/app/assets/coffeescripts/application.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Add your application's coffee-script code here 3 | ### -------------------------------------------------------------------------------- /templates/app/assets/stylesheets/application.less: -------------------------------------------------------------------------------- 1 | // put your less code here 2 | @color: white; 3 | 4 | body { 5 | background: @color; 6 | } -------------------------------------------------------------------------------- /templates/app/assets/stylesheets/application.sass: -------------------------------------------------------------------------------- 1 | // put your sass.js code here 2 | !color = white 3 | 4 | body 5 | :background !white -------------------------------------------------------------------------------- /templates/app/assets/stylesheets/application.styl: -------------------------------------------------------------------------------- 1 | // put your stylus code here 2 | $color = white 3 | 4 | body 5 | background: $color -------------------------------------------------------------------------------- /templates/app/controllers/application_controller.coffee: -------------------------------------------------------------------------------- 1 | before 'protect from forgery', -> 2 | protectFromForgery '{{ SECRET }}' -------------------------------------------------------------------------------- /templates/app/controllers/application_controller.js: -------------------------------------------------------------------------------- 1 | before('protect from forgery', function () { 2 | protectFromForgery('{{ SECRET }}'); 3 | }); 4 | -------------------------------------------------------------------------------- /templates/app/helpers/model_helper.coffee: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | } -------------------------------------------------------------------------------- /templates/app/helpers/model_helper.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | }; -------------------------------------------------------------------------------- /templates/app/models/model.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (compound, {{ MODELNAME }}) -> 2 | # define {{ MODELNAME }} here -------------------------------------------------------------------------------- /templates/app/models/model.js: -------------------------------------------------------------------------------- 1 | module.exports = function (compound, {{ MODELNAME }}) { 2 | // define {{ MODELNAME }} here 3 | }; -------------------------------------------------------------------------------- /templates/app/tools/database.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (compound) -> 2 | app = compound.app 3 | 4 | getUniqueSchemas = ()-> 5 | schemas = [] 6 | Object.keys(compound.models).forEach (modelName)-> 7 | Model = compound.models[modelName] 8 | schema = Model.schema 9 | if !~schemas.indexOf(schema) 10 | schemas.push schema 11 | schemas 12 | 13 | perform = (action, callback)-> 14 | wait = 0 15 | done = ()-> 16 | if --wait == 0 then callback() 17 | 18 | console.log 'Perform', action, 'on' 19 | getUniqueSchemas().forEach (schema)-> 20 | console.log ' - ' + schema.name 21 | if schema['auto' + action] 22 | wait += 1 23 | process.nextTick -> 24 | schema['auto' + action](done) 25 | 26 | if wait == 0 27 | done() 28 | else 29 | console.log wait 30 | 31 | action = process.argv[3] 32 | switch action 33 | when 'migrate', 'update' 34 | perform action, process.exit 35 | else 36 | console.log 'Unknown action', action 37 | false 38 | 39 | module.exports.help = 40 | shortcut: 'db' 41 | usage: 'db [migrate|update]' 42 | description: 'Migrate or update database(s)' 43 | 44 | -------------------------------------------------------------------------------- /templates/app/tools/database.js: -------------------------------------------------------------------------------- 1 | module.exports = function(compound) { 2 | var app = compound.app; 3 | var action = process.argv[3]; 4 | switch (action) { 5 | case 'migrate': 6 | case 'update': 7 | perform(action, process.exit); 8 | break; 9 | default: 10 | console.log('Unknown action', action); 11 | break; 12 | } 13 | 14 | function getUniqueSchemas() { 15 | var schemas = []; 16 | Object.keys(compound.models).forEach(function (modelName) { 17 | var Model = compound.models[modelName]; 18 | var schema = Model.schema; 19 | if (!~schemas.indexOf(schema)) { 20 | schemas.push(schema); 21 | } 22 | }); 23 | return schemas; 24 | } 25 | 26 | function perform(action, callback) { 27 | console.log('Perform', action, 'on'); 28 | var wait = 0; 29 | getUniqueSchemas().forEach(function (schema) { 30 | if (schema['auto' + action]) { 31 | console.log(' - ' + schema.name); 32 | wait += 1; 33 | process.nextTick(function () { 34 | schema['auto' + action](done); 35 | }); 36 | } 37 | }); 38 | 39 | if (wait === 0) done(); else console.log(wait); 40 | 41 | function done() { 42 | if (--wait === 0) callback(); 43 | } 44 | 45 | } 46 | 47 | return false; 48 | }; 49 | 50 | module.exports.help = { 51 | shortcut: 'db', 52 | usage: 'db [migrate|update]', 53 | description: 'Migrate or update database(s)' 54 | }; 55 | 56 | -------------------------------------------------------------------------------- /templates/blank.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/templates/blank.js -------------------------------------------------------------------------------- /templates/config/autoload.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (compound) -> 2 | defaultModules = [ 3 | 'jugglingdb', 4 | 'co-assets-compiler' 5 | ] 6 | 7 | developmentModules = [] 8 | if compound.app.get('env') is 'development' 9 | developmentModules = [ 10 | '{{ VIEWENGINE }}-ext', 11 | 'seedjs', 12 | 'co-generators' 13 | ] 14 | 15 | unless window? 16 | return defaultModules.concat(developmentModules).map(require) 17 | else 18 | return [] -------------------------------------------------------------------------------- /templates/config/autoload.js: -------------------------------------------------------------------------------- 1 | module.exports = function (compound) { 2 | var defaultModules = [ 3 | 'jugglingdb', 4 | 'co-assets-compiler' 5 | ], developmentModules = []; 6 | 7 | if ('development' === compound.app.get('env')) { 8 | developmentModules = [ 9 | '{{ VIEWENGINE }}-ext', 10 | 'seedjs', 11 | 'co-generators' 12 | ] 13 | } 14 | 15 | if (typeof window === 'undefined') { 16 | return defaultModules.concat(developmentModules).map(require); 17 | } else { 18 | return [] 19 | } 20 | }; 21 | 22 | -------------------------------------------------------------------------------- /templates/config/database_memory.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | development: 3 | driver: "memory" 4 | 5 | test: 6 | driver: "memory" 7 | 8 | production: 9 | driver: "memory" 10 | -------------------------------------------------------------------------------- /templates/config/database_memory.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | { "development": 3 | { "driver": "memory" 4 | } 5 | , "test": 6 | { "driver": "memory" 7 | } 8 | , "production": 9 | { "driver": "memory" 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /templates/config/database_mongodb.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | development: 3 | driver: "mongodb" 4 | url: "mongodb://localhost/APPNAME-dev" 5 | 6 | test: 7 | driver: "mongodb" 8 | url: "mongodb://localhost/APPNAME-test" 9 | 10 | production: 11 | driver: "mongodb" 12 | url: "mongodb://localhost/APPNAME-production" 13 | -------------------------------------------------------------------------------- /templates/config/database_mongodb.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | driver: 'mongodb', 4 | url: 'mongodb://localhost/APPNAME-dev' 5 | }, 6 | test: { 7 | driver: 'mongodb', 8 | url: 'mongodb://localhost/APPNAME-test' 9 | }, 10 | production: { 11 | driver: 'mongodb', 12 | url: 'mongodb://localhost/APPNAME-production' 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /templates/config/database_mongodb.json: -------------------------------------------------------------------------------- 1 | { "development": 2 | { "driver": "mongodb" 3 | , "url": "mongodb://localhost/APPNAME-dev" 4 | } 5 | , "test": 6 | { "driver": "mongodb" 7 | , "url": "mongodb://localhost/APPNAME-test" 8 | } 9 | , "production": 10 | { "driver": "mongodb" 11 | , "url": "mongodb://localhost/APPNAME-production" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /templates/config/database_mysql.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | development: 3 | driver: "mysql" 4 | host: "localhost" 5 | port: 3306 6 | username: "root" 7 | password: "" 8 | database: "{{ APPNAME }}_dev" 9 | 10 | test: 11 | driver: "mysql" 12 | host: "localhost" 13 | port: 3306 14 | username: "root" 15 | password: "" 16 | database: "{{ APPNAME }}_test" 17 | 18 | production: 19 | driver: "mysql" 20 | host: "localhost" 21 | port: 3306 22 | username: "root" 23 | password: "" 24 | database: "{{ APPNAME }}_production" 25 | -------------------------------------------------------------------------------- /templates/config/database_mysql.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | driver: 'mysql', 4 | host: 'localhost', 5 | port: 3306, 6 | username: 'root', 7 | password: '', 8 | database: '{{ APPNAME }}_dev' 9 | }, 10 | test: { 11 | driver: 'mysql', 12 | host: 'localhost', 13 | port: 3306, 14 | username: 'root', 15 | password: '', 16 | database: '{{ APPNAME }}_test' 17 | }, 18 | production: { 19 | driver: 'mysql', 20 | host: 'localhost', 21 | port: 3306, 22 | username: 'root', 23 | password: '', 24 | database: '{{ APPNAME }}_production' 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /templates/config/database_mysql.json: -------------------------------------------------------------------------------- 1 | { "development": 2 | { "driver": "mysql" 3 | , "host": "localhost" 4 | , "port": 3306 5 | , "username": "root" 6 | , "password": "" 7 | , "database": "{{ APPNAME }}_dev" 8 | } 9 | , "test": 10 | { "driver": "mysql" 11 | , "host": "localhost" 12 | , "port": 3306 13 | , "username": "root" 14 | , "password": "" 15 | , "database": "{{ APPNAME }}_test" 16 | } 17 | , "production": 18 | { "driver": "mysql" 19 | , "host": "localhost" 20 | , "port": 3306 21 | , "username": "root" 22 | , "password": "" 23 | , "database": "{{ APPNAME }}_production" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /templates/config/database_nano.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | 3 | development: 4 | driver: "nano" 5 | url: "http://localhost:5984" 6 | 7 | test: 8 | driver: "nano" 9 | url: "http://localhost:5984" 10 | 11 | production: 12 | driver: "nano" 13 | url: "http://localhost:5984" 14 | -------------------------------------------------------------------------------- /templates/config/database_nano.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | driver: 'nano', 4 | url: 'http://localhost:5984' 5 | }, 6 | test: { 7 | driver: 'nano', 8 | url: 'http://localhost:5984' 9 | }, 10 | production: { 11 | driver: 'nano', 12 | url: 'http://localhost:5984' 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /templates/config/database_postgres.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | driver: 'postgres', 4 | host: 'localhost', 5 | port: 5432, 6 | username: 'root', 7 | password: '', 8 | database: '{{ APPNAME }}_dev', 9 | debug: true 10 | }, 11 | test: { 12 | driver: 'postgres', 13 | host: 'localhost', 14 | port: 5432, 15 | username: 'root', 16 | password: '', 17 | database: '{{ APPNAME }}_test' 18 | }, 19 | production: { 20 | driver: 'postgres', 21 | host: 'localhost', 22 | port: 5432, 23 | username: 'root', 24 | password: '', 25 | database: '{{ APPNAME }}_production' 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /templates/config/database_redis.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | development: 3 | driver: "redis" 4 | host: "localhost" 5 | database: 2 6 | 7 | test: 8 | driver: "redis" 9 | host: "localhost" 10 | database: 1 11 | 12 | production: 13 | driver: "redis" 14 | host: "localhost" 15 | database: 0 16 | -------------------------------------------------------------------------------- /templates/config/database_redis.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | driver: 'redis', 4 | host: 'localhost', 5 | database: 2 6 | }, 7 | test: { 8 | driver: 'redis', 9 | host: 'localhost', 10 | database: 1 11 | }, 12 | production: { 13 | driver: 'redis', 14 | host: 'localhost', 15 | database: 0 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /templates/config/database_redis.json: -------------------------------------------------------------------------------- 1 | { "development": 2 | { "driver": "redis" 3 | , "host": "localhost" 4 | , "database": 2 5 | } 6 | , "test": 7 | { "driver": "redis" 8 | , "host": "localhost" 9 | , "database": 1 10 | } 11 | , "production": 12 | { "driver": "redis" 13 | , "host": "localhost" 14 | , "database": 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /templates/config/database_riak.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | development: 3 | driver: "riak" 4 | host: "localhost" 5 | database: "APPNAME-dev" 6 | 7 | test: 8 | driver: "riak" 9 | host: "localhost" 10 | database: "APPNAME-test" 11 | 12 | production: 13 | driver: "riak" 14 | host: "localhost" 15 | database: "APPNAME-production" 16 | -------------------------------------------------------------------------------- /templates/config/database_riak.json: -------------------------------------------------------------------------------- 1 | { "development": 2 | { "driver": "riak" 3 | , "host": "localhost" 4 | , "port": "8098" 5 | , "database": "APPNAME-dev" 6 | } 7 | , "test": 8 | { "driver": "riak" 9 | , "host": "localhost" 10 | , "port": "8098" 11 | , "database": "APPNAME-test" 12 | } 13 | , "production": 14 | { "driver": "riak" 15 | , "host": "localhost" 16 | , "port": "8098" 17 | , "database": "APPNAME-production" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/config/database_sqlite3.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | development: 3 | driver: "sqlite3" 4 | database: ":memory:" 5 | 6 | test: 7 | driver: "sqlite3" 8 | database: ":memory:" 9 | 10 | production: 11 | driver: "sqlite3" 12 | database: ":memory:" 13 | -------------------------------------------------------------------------------- /templates/config/database_sqlite3.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | driver: 'sqlite3', 4 | database: 'db/{{ APPNAME }}-dev.sqlite3' 5 | }, 6 | test: { 7 | driver: 'sqlite3', 8 | database: ':memory:' 9 | }, 10 | production: { 11 | driver: 'sqlite3', 12 | database: 'db/{{ APPNAME }}.sqlite3' 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /templates/config/database_sqlite3.json: -------------------------------------------------------------------------------- 1 | { "development": 2 | { "driver": "sqlite3" 3 | , "database": ":memory:" 4 | } 5 | , "test": 6 | { "driver": "sqlite3" 7 | , "database": ":memory:" 8 | } 9 | , "production": 10 | { "driver": "sqlite3" 11 | , "database": ":memory:" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /templates/config/environment.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (compound) -> 2 | 3 | express = require 'express' 4 | app = compound.app 5 | 6 | app.configure -> 7 | app.enable 'coffee' 8 | 9 | app.set 'cssEngine', '{{ CSSENGINE }}' 10 | compound.loadConfigs __dirname 11 | 12 | # make sure you run `npm install railway-routes browserify` 13 | # app.enable 'clientside' 14 | app.use express.static(app.root + '/public', maxAge: 86400000) 15 | app.use express.urlencoded() 16 | app.use express.json() 17 | app.use express.cookieParser 'secret' 18 | app.use express.session secret: 'secret' 19 | app.use express.methodOverride() 20 | app.use app.router 21 | -------------------------------------------------------------------------------- /templates/config/environment.js: -------------------------------------------------------------------------------- 1 | module.exports = function (compound) { 2 | 3 | var express = require('express'); 4 | var app = compound.app; 5 | 6 | app.configure(function(){ 7 | app.use(express.static(app.root + '/public', { maxAge: 86400000 })); 8 | app.set('jsDirectory', '/javascripts/'); 9 | app.set('cssDirectory', '/stylesheets/'); 10 | app.set('cssEngine', '{{ CSSENGINE }}'); 11 | compound.loadConfigs(__dirname); 12 | app.use(express.urlencoded()); 13 | app.use(express.json()); 14 | app.use(express.cookieParser('secret')); 15 | app.use(express.session({secret: 'secret'})); 16 | app.use(express.methodOverride()); 17 | app.use(app.router); 18 | }); 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /templates/config/environments/development.coffee: -------------------------------------------------------------------------------- 1 | express = require 'express' 2 | 3 | module.exports = (compound) -> 4 | app = compound.app 5 | app.configure 'development', -> 6 | app.enable 'watch' 7 | app.enable 'log actions' 8 | app.enable 'env info' 9 | app.enable 'force assets compilation' 10 | app.set 'translationMissing', 'display' 11 | app.use express.errorHandler dumpExceptions: true, showStack: true 12 | -------------------------------------------------------------------------------- /templates/config/environments/development.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | module.exports = function (compound) { 4 | var app = compound.app; 5 | 6 | app.configure('development', function () { 7 | app.enable('watch'); 8 | app.enable('log actions'); 9 | app.enable('env info'); 10 | app.enable('force assets compilation'); 11 | app.set('translationMissing', 'display'); 12 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /templates/config/environments/production.coffee: -------------------------------------------------------------------------------- 1 | express = require 'express' 2 | 3 | module.exports = (compound) -> 4 | app = compound.app 5 | app.configure 'production', -> 6 | app.enable 'quiet' 7 | app.enable 'merge javascripts' 8 | app.enable 'merge stylesheets' 9 | app.disable 'assets timestamps' 10 | app.use express.errorHandler() 11 | -------------------------------------------------------------------------------- /templates/config/environments/production.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | module.exports = function (compound) { 4 | var app = compound.app; 5 | 6 | app.configure('production', function () { 7 | app.enable('quiet'); 8 | app.enable('merge javascripts'); 9 | app.enable('merge stylesheets'); 10 | app.disable('assets timestamps'); 11 | app.use(express.errorHandler()); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /templates/config/environments/test.coffee: -------------------------------------------------------------------------------- 1 | express = require 'express' 2 | 3 | module.exports = (compound) -> 4 | app = compound.app 5 | app.configure 'test', -> 6 | app.enable 'quiet' 7 | app.enable 'view cache' 8 | app.enable 'model cache' 9 | app.enable 'eval cache' 10 | app.use express.errorHandler dumpExceptions: true, showStack: true 11 | -------------------------------------------------------------------------------- /templates/config/environments/test.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | module.exports = function (compound) { 4 | var app = compound.app; 5 | 6 | app.configure('test', function () { 7 | app.enable('quiet'); 8 | app.enable('view cache'); 9 | app.enable('model cache'); 10 | app.enable('eval cache'); 11 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /templates/config/routes.coffee: -------------------------------------------------------------------------------- 1 | exports.routes = (map)-> 2 | # Generic routes. Add all your routes below this line 3 | # feel free to remove generic routes 4 | map.all ':controller/:action' 5 | map.all ':controller/:action/:id' 6 | -------------------------------------------------------------------------------- /templates/config/routes.js: -------------------------------------------------------------------------------- 1 | exports.routes = function (map) { 2 | // Generic routes. Add all your routes below this line 3 | // feel free to remove generic routes 4 | map.all(':controller/:action'); 5 | map.all(':controller/:action/:id'); 6 | }; 7 | -------------------------------------------------------------------------------- /templates/crud_controller.js: -------------------------------------------------------------------------------- 1 | var Application = require('./application'); 2 | 3 | var ModelsController = module.exports = function ModelsController(init) { 4 | Application.call(this, init); 5 | 6 | init.before(loadModel, { 7 | only: ['show', 'edit', 'update', 'destroy'] 8 | }); 9 | }; 10 | 11 | require('util').inherits(ModelsController, Application); 12 | 13 | ModelsController.prototype['new'] = function (c) { 14 | this.title = 'New model'; 15 | this.model = new (c.Model); 16 | c.render(); 17 | }; 18 | 19 | ModelsController.prototype.create = function create(c) { 20 | c.Model.create(c.body.Model, function (err, model) { 21 | if (err) { 22 | c.flash('error', 'Model can not be created'); 23 | c.render('new', { 24 | model: model, 25 | title: 'New model' 26 | }); 27 | } else { 28 | c.flash('info', 'Model created'); 29 | c.redirect(c.pathTo.models); 30 | } 31 | }); 32 | }; 33 | 34 | ModelsController.prototype.index = function index(c) { 35 | this.title = 'Models index'; 36 | c.Model.all(function (err, models) { 37 | c.respondTo(function (format) { 38 | format.json(function () { 39 | c.send(models); 40 | }); 41 | format.html(function () { 42 | c.render({ 43 | models: models 44 | }); 45 | }); 46 | }); 47 | }); 48 | }; 49 | 50 | ModelsController.prototype.show = function show(c) { 51 | this.title = 'Model show'; 52 | var model = this.model; 53 | c.respondTo(function (format) { 54 | format.json(function () { 55 | c.send(model); 56 | }); 57 | format.html(function () { 58 | c.render(); 59 | }); 60 | }); 61 | }; 62 | 63 | ModelsController.prototype.edit = function edit(c) { 64 | this.title = 'Model edit'; 65 | c.render(); 66 | }; 67 | 68 | ModelsController.prototype.update = function update(c) { 69 | var model = this.model; 70 | var self = this; 71 | 72 | this.title = 'Model edit'; 73 | 74 | model.updateAttributes(c.body.Model, function (err) { 75 | c.respondTo(function (format) { 76 | format.json(function () { 77 | if (err) { 78 | c.send({ 79 | code: 500, 80 | error: model && model.errors || err 81 | }); 82 | } else { 83 | c.send({ 84 | code: 200, 85 | model: model.toObject() 86 | }); 87 | } 88 | }); 89 | format.html(function () { 90 | if (!err) { 91 | c.flash('info', 'Model updated'); 92 | c.redirect(c.pathTo.model(model)); 93 | } else { 94 | c.flash('error', 'Model can not be updated'); 95 | c.render('edit'); 96 | } 97 | }); 98 | }); 99 | }); 100 | 101 | }; 102 | 103 | ModelsController.prototype.destroy = function destroy(c) { 104 | this.model.destroy(function (error) { 105 | if (error) { 106 | c.flash('error', 'Can not destroy model'); 107 | } else { 108 | c.flash('info', 'Model successfully removed'); 109 | } 110 | c.send("'" + c.pathTo.models + "'"); 111 | }); 112 | }; 113 | 114 | function loadModel(c) { 115 | var self = this; 116 | c.Model.find(c.params.id, function (err, model) { 117 | if (err || !model) { 118 | c.redirect(c.pathTo.models); 119 | } else { 120 | self.model = model; 121 | c.next(); 122 | } 123 | }); 124 | } 125 | -------------------------------------------------------------------------------- /templates/db/schema.coffee: -------------------------------------------------------------------------------- 1 | # Example of model definition: 2 | # 3 | #define 'User', -> 4 | # property 'email', String, index: true 5 | # property 'password', String 6 | # property 'activated', Boolean, default: false 7 | # 8 | -------------------------------------------------------------------------------- /templates/db/schema.js: -------------------------------------------------------------------------------- 1 | /* 2 | db/schema.js contains database schema description for application models 3 | by default (when using jugglingdb as ORM) this file uses database connection 4 | described in config/database.json. But it's possible to use another database 5 | connections and multiple different schemas, docs available at 6 | 7 | http://railwayjs.com/orm.html 8 | 9 | Example of model definition: 10 | 11 | define('User', function () { 12 | property('email', String, { index: true }); 13 | property('password', String); 14 | property('activated', Boolean, {default: false}); 15 | }); 16 | 17 | Example of schema configured without config/database.json (heroku redistogo addon): 18 | schema('redis', {url: process.env.REDISTOGO_URL}, function () { 19 | // model definitions here 20 | }); 21 | 22 | */ 23 | 24 | -------------------------------------------------------------------------------- /templates/db/schema_model.coffee: -------------------------------------------------------------------------------- 1 | {{ MODELNAME }} = describe '{{ MODELNAME }}', -> 2 | {{ PROPERTIES }} 3 | set 'restPath', pathTo.{{ models }} 4 | -------------------------------------------------------------------------------- /templates/db/schema_model.js: -------------------------------------------------------------------------------- 1 | var {{ MODELNAME }} = describe('{{ MODELNAME }}', function () { 2 | {{ PROPERTIES }} 3 | set('restPath', pathTo.{{ models }}); 4 | }); 5 | -------------------------------------------------------------------------------- /templates/gitignore-example: -------------------------------------------------------------------------------- 1 | dump.rdb 2 | lib-cov 3 | *.log 4 | *.csv 5 | *.out 6 | *.pid 7 | pids 8 | logs 9 | results 10 | node_modules 11 | npm-debug.log 12 | .idea 13 | .DS_Store 14 | log/*.log 15 | .c9revisions 16 | coverage.html 17 | .settings 18 | doc 19 | 20 | -------------------------------------------------------------------------------- /templates/package.json: -------------------------------------------------------------------------------- 1 | { "name": "{{ APPNAME }}" 2 | , "version": "0.0.1" 3 | , "engines": ["node >= 0.8.0"] 4 | , "main": "server.{{ CODE }}" 5 | , "dependencies": 6 | { "{{ VIEWENGINE }}": "*" 7 | , "{{ VIEWENGINE }}-ext": "latest" 8 | , "express": "~3.x" 9 | , "compound": ">= 1.1.0" 10 | , "jugglingdb": ">= 0.1.0" 11 | {{ DBDEPENDENCY }} 12 | , "coffee-script": ">= 1.1.1" 13 | , "{{ CSSENGINE }}": "latest" 14 | , "seedjs": "latest" 15 | , "co-assets-compiler": "*" 16 | }, 17 | "devDependencies": 18 | { "nodeunit": "*" 19 | , "sinon": "*" 20 | , "supertest": ">= 0" 21 | , "mocha": ">= 0" 22 | , "should": ">= 0" 23 | , "semicov": "*" 24 | , "co-generators": "*" 25 | } 26 | , "scripts": 27 | { "test": "./node_modules/.bin/mocha --require test/init.js test/*/*.test.js" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /templates/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/templates/public/favicon.ico -------------------------------------------------------------------------------- /templates/public/images/compound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/templates/public/images/compound.png -------------------------------------------------------------------------------- /templates/public/images/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/templates/public/images/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /templates/public/images/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1602/compound/7c5dabe3c990c672897d4ee9696ef7698495f8ba/templates/public/images/glyphicons-halflings.png -------------------------------------------------------------------------------- /templates/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome to Compound 5 | 6 | 7 | 8 | 9 | 10 | 11 |
      12 | 16 |
      17 | Information about application environment 18 |
      19 |
      20 |
      21 |
      22 |
      23 |
      24 |
      1. Start with generators
      25 |

      This is the fastest way to create an application:

      26 |
      compound generate crud post title content date:date published:boolean
      27 |
      2. Then describe a route
      28 |

      in config/routes.js and remove this file (public/index.html)

      29 |
      exports.routes = function (map) {
      30 |     map.get('/', 'posts#index');
      31 | };
      32 |
      3. Design your database
      33 |

      in db/schema.js and describe models in app/models/*

      34 |
      4. Keep your controllers thin!
      35 |

      Write tests, and good luck.
      If you have any questions feel free to ask at CompoundJS Google Group or #compoundjs on irc.freenode.net.

      36 |

      Track CompoundJS project state on our Trello board, vote for features, discuss. Help us to get better!

      37 |
      38 |
      39 | 40 |
      41 |

      42 |

      5. Links in sidebar
      43 | 49 |

      50 |
      51 | 52 |
      53 |
      54 |
      55 | 93 |
      94 | 95 | 96 | -------------------------------------------------------------------------------- /templates/public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Add your application's coffee-script code here 4 | */ 5 | 6 | 7 | (function() { 8 | 9 | 10 | 11 | }).call(this); -------------------------------------------------------------------------------- /templates/public/javascripts/rails.js: -------------------------------------------------------------------------------- 1 | jQuery(function ($) { 2 | var csrf_token = $('meta[name=csrf-token]').attr('content'), 3 | csrf_param = $('meta[name=csrf-param]').attr('content'); 4 | 5 | $.fn.extend({ 6 | /** 7 | * Triggers a custom event on an element and returns the event result 8 | * this is used to get around not being able to ensure callbacks are placed 9 | * at the end of the chain. 10 | * 11 | * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our 12 | * own events and placing ourselves at the end of the chain. 13 | */ 14 | triggerAndReturn: function (name, data) { 15 | var event = new $.Event(name); 16 | this.trigger(event, data); 17 | 18 | return event.result !== false; 19 | }, 20 | 21 | /** 22 | * Handles execution of remote calls firing overridable events along the way 23 | */ 24 | callRemote: function () { 25 | var el = this, 26 | method = el.attr('method') || el.attr('data-method') || 'GET', 27 | url = el.attr('action') || el.attr('href'), 28 | dataType = el.attr('data-type') || 'script'; 29 | 30 | if (el.attr('data-jsonp')) { 31 | dataType = 'text'; 32 | } 33 | 34 | if (el.attr('stop-propagate')) { 35 | el.attr('stop-propagate', null); 36 | return; 37 | } 38 | 39 | if (url === undefined) { 40 | throw "No URL specified for remote call (action or href must be present)."; 41 | } else { 42 | if (el.triggerAndReturn('ajax:before')) { 43 | var data = el.is('form') ? el.serializeArray() : []; 44 | var found = false; 45 | for (var i = data.length - 1; i >= 0; i -= 1) { 46 | if (data[i].name === csrf_param) found = true; 47 | } 48 | if (!found) data.push({name: csrf_param, value: csrf_token}); 49 | $.ajax({ 50 | url: url, 51 | data: data, 52 | dataType: dataType, 53 | type: method.toUpperCase(), 54 | beforeSend: function (xhr) { 55 | el.trigger('ajax:loading', xhr); 56 | }, 57 | success: function (data, status, xhr) { 58 | if (el.attr('data-jsonp')) { 59 | eval(el.attr('data-jsonp') + '(' + data + ')'); 60 | } 61 | el.trigger('ajax:success', [data, status, xhr]); 62 | }, 63 | complete: function (xhr) { 64 | el.trigger('ajax:complete', xhr); 65 | }, 66 | error: function (xhr, status, error) { 67 | el.trigger('ajax:failure', [xhr, status, error]); 68 | } 69 | }); 70 | } 71 | 72 | el.trigger('ajax:after'); 73 | } 74 | } 75 | }); 76 | 77 | /** 78 | * confirmation handler 79 | */ 80 | $('body').on('click', 'a[data-confirm],input[data-confirm]', function (e) { 81 | var el = $(this); 82 | if (el.triggerAndReturn('confirm')) { 83 | if (!confirm(el.attr('data-confirm'))) { 84 | el.attr('stop-propagate', true); 85 | return false; 86 | } 87 | } 88 | }); 89 | 90 | 91 | /** 92 | * remote handlers 93 | */ 94 | $('form[data-remote]').on('submit', function (e) { 95 | $(this).callRemote(); 96 | e.preventDefault(); 97 | }); 98 | 99 | $('body').on('click', 'a[data-remote],input[data-remote]', function (e) { 100 | $(this).callRemote(); 101 | e.preventDefault(); 102 | }); 103 | 104 | $('body').on('click', 'a[data-method]:not([data-remote])', function (e) { 105 | var link = $(this), 106 | href = link.attr('href'), 107 | method = link.attr('data-method'), 108 | form = $('
      '), 109 | metadata_input = ''; 110 | 111 | if (csrf_param != null && csrf_token != null) { 112 | metadata_input += ''; 113 | } 114 | 115 | form.hide() 116 | .append(metadata_input) 117 | .appendTo('body'); 118 | 119 | e.preventDefault(); 120 | form.submit(); 121 | }); 122 | 123 | /** 124 | * disable-with handlers 125 | */ 126 | var disable_with_input_selector = 'input[data-disable-with]'; 127 | var disable_with_form_remote_selector = 'form[data-remote]:has(' + disable_with_input_selector + ')'; 128 | var disable_with_form_not_remote_selector = 'form:not([data-remote]):has(' + disable_with_input_selector + ')'; 129 | 130 | var disable_with_input_function = function () { 131 | $(this).find(disable_with_input_selector).each(function () { 132 | var input = $(this); 133 | input.data('enable-with', input.val()) 134 | .attr('value', input.attr('data-disable-with')) 135 | .attr('disabled', 'disabled'); 136 | }); 137 | }; 138 | 139 | $(disable_with_form_remote_selector).on('ajax:before', disable_with_input_function); 140 | $(disable_with_form_not_remote_selector).on('submit', disable_with_input_function); 141 | 142 | $(disable_with_form_remote_selector).on('ajax:complete', function () { 143 | $(this).find(disable_with_input_selector).each(function () { 144 | var input = $(this); 145 | input.removeAttr('disabled') 146 | .val(input.data('enable-with')); 147 | }); 148 | }); 149 | 150 | }); 151 | -------------------------------------------------------------------------------- /templates/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; -------------------------------------------------------------------------------- /templates/server.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | app = module.exports = (params) -> 4 | params = params || {} 5 | # specify current dir as default root of server 6 | params.root = params.root || __dirname 7 | return require('compound').createServer(params) 8 | 9 | if not module.parent || module.parent.isApplicationLoader 10 | port = process.env.PORT || 3000 11 | host = process.env.HOST || "0.0.0.0" 12 | server = app() 13 | server.listen port, host, -> 14 | console.log( 15 | "Compound server listening on %s:%d within %s environment", 16 | host, port, server.set('env')) 17 | -------------------------------------------------------------------------------- /templates/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Server module exports method returning new instance of app. 5 | * 6 | * @param {Object} params - compound/express webserver initialization params. 7 | * @returns CompoundJS powered express webserver 8 | */ 9 | var app = module.exports = function getServerInstance(params) { 10 | params = params || {}; 11 | // specify current dir as default root of server 12 | params.root = params.root || __dirname; 13 | return require('compound').createServer(params); 14 | }; 15 | 16 | if (!module.parent || module.parent.isApplicationLoader) { 17 | var port = process.env.PORT || 3000; 18 | var host = process.env.HOST || '0.0.0.0'; 19 | 20 | var server = app(); 21 | server.listen(port, host, function () { 22 | console.log( 23 | 'Compound server listening on %s:%d within %s environment', 24 | host, port, server.set('env') 25 | ); 26 | }); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /templates/test/controllers/crud_controller_test.js: -------------------------------------------------------------------------------- 1 | require('../test_helper.js').controller('models', module.exports); 2 | 3 | var sinon = require('sinon'); 4 | 5 | function ValidAttributes () { 6 | return { 7 | {{ VALID_ATTRIBUTES }} 8 | }; 9 | } 10 | 11 | exports['models controller'] = { 12 | 13 | 'GET new': function (test) { 14 | test.get('/models/new', function () { 15 | test.success(); 16 | test.render('new'); 17 | test.done(); 18 | }); 19 | }, 20 | 21 | 'GET index': function (test) { 22 | test.get('/models', function () { 23 | test.success(); 24 | test.render('index'); 25 | test.done(); 26 | }); 27 | }, 28 | 29 | 'GET edit': function (test) { 30 | var find = Model.find; 31 | Model.find = sinon.spy(function (id, callback) { 32 | callback(null, new Model); 33 | }); 34 | test.get('/models/42/edit', function () { 35 | test.ok(Model.find.calledWith('42')); 36 | Model.find = find; 37 | test.success(); 38 | test.render('edit'); 39 | test.done(); 40 | }); 41 | }, 42 | 43 | 'GET show': function (test) { 44 | var find = Model.find; 45 | Model.find = sinon.spy(function (id, callback) { 46 | callback(null, new Model); 47 | }); 48 | test.get('/models/42', function (req, res) { 49 | test.ok(Model.find.calledWith('42')); 50 | Model.find = find; 51 | test.success(); 52 | test.render('show'); 53 | test.done(); 54 | }); 55 | }, 56 | 57 | 'POST create': function (test) { 58 | var model = new ValidAttributes; 59 | var create = Model.create; 60 | Model.create = sinon.spy(function (data, callback) { 61 | test.strictEqual(data, model); 62 | callback(null, model); 63 | }); 64 | test.post('/models', {Model: model}, function () { 65 | test.redirect('/models'); 66 | test.flash('info'); 67 | test.done(); 68 | }); 69 | }, 70 | 71 | 'POST create fail': function (test) { 72 | var model = new ValidAttributes; 73 | var create = Model.create; 74 | Model.create = sinon.spy(function (data, callback) { 75 | test.strictEqual(data, model); 76 | callback(new Error, model); 77 | }); 78 | test.post('/models', {Model: model}, function () { 79 | test.success(); 80 | test.render('new'); 81 | test.flash('error'); 82 | test.done(); 83 | }); 84 | }, 85 | 86 | 'PUT update': function (test) { 87 | Model.find = sinon.spy(function (id, callback) { 88 | test.equal(id, 1); 89 | callback(null, {id: 1, updateAttributes: function (data, cb) { cb(null); }}); 90 | }); 91 | test.put('/models/1', new ValidAttributes, function () { 92 | test.redirect('/models/1'); 93 | test.flash('info'); 94 | test.done(); 95 | }); 96 | }, 97 | 98 | 'PUT update fail': function (test) { 99 | Model.find = sinon.spy(function (id, callback) { 100 | test.equal(id, 1); 101 | callback(null, {id: 1, updateAttributes: function (data, cb) { cb(new Error); }}); 102 | }); 103 | test.put('/models/1', new ValidAttributes, function () { 104 | test.success(); 105 | test.render('edit'); 106 | test.flash('error'); 107 | test.done(); 108 | }); 109 | }, 110 | 111 | 'DELETE destroy': function (test) { 112 | test.done(); 113 | }, 114 | 115 | 'DELETE destroy fail': function (test) { 116 | test.done(); 117 | } 118 | }; 119 | 120 | -------------------------------------------------------------------------------- /test/compound.test.js: -------------------------------------------------------------------------------- 1 | var Compound = require('../').Compound; 2 | var should = require('should'); 3 | 4 | describe('Compound', function() { 5 | 6 | it('could be created with no app', function() { 7 | (function() { 8 | new Compound; 9 | }).should.not.throw; 10 | }); 11 | 12 | it('should have ability to run tools when instantiated with no app', function () { 13 | var c = new Compound; 14 | c.generators.init(c); 15 | c.generators.list().should.equal('app'); 16 | }); 17 | 18 | it('should allow to find model by name', function() { 19 | var c = new Compound; 20 | c.models = {Model: function Model() {}, ModelTwo: true}; 21 | should.exist(c.model('Model')); 22 | should.exist(c.model('model')); 23 | should.not.exist(c.model('model', true)); 24 | should.exist(c.model('ModelTwo'), true); 25 | 26 | // add model as named fn 27 | should.not.exist(c.model('ModelThree')); 28 | c.model(function ModelThree(){}); 29 | should.exist(c.model('ModelThree')); 30 | 31 | // add model as jugglingdb model 32 | var model = function(){}; 33 | model.modelName = 'HelloJuggling'; 34 | c.model(model); 35 | should.exist(c.model('HelloJuggling')); 36 | 37 | // throws when model is not named 38 | (function() { 39 | c.model(function() {}); 40 | }).should.throw('Named function or jugglingdb model required'); 41 | }); 42 | 43 | describe('run initializers', function(){ 44 | var app, c, root, path = '/test'; 45 | 46 | before(function(done) { 47 | app = getApp(); 48 | c = app.compound; 49 | c.on('ready', function() { 50 | root = c.root + path; 51 | done(); 52 | }); 53 | }); 54 | 55 | it('should fail to initialize files with README files', function() { 56 | (function(){ 57 | c.runInitializers(root); 58 | }).should.throw(); 59 | }); 60 | 61 | it('should initialize files without README with config pattern', function() { 62 | app.set('ignore initializers pattern', /^\.|\.md$/); 63 | (function(){ 64 | c.runInitializers(root); 65 | }).should.not.throw(); 66 | }); 67 | 68 | }); 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /test/config.test.js: -------------------------------------------------------------------------------- 1 | var should = require('./init.js'); 2 | 3 | describe('compound.loadConfigs', function() { 4 | it('should load configs from given directory', function() { 5 | var app = getApp(); 6 | var compound = app.compound; 7 | compound.loadConfigs(__dirname + '/fixtures/config'); 8 | should.exists(app.get('database'), 'load database config'); 9 | app.get('database').driver.should.equal('memory'); 10 | should.exists(app.get('foo'), 'load extra config'); 11 | app.get('foo').should.equal('bar'); 12 | should.not.exists(app.get('hello')); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/config/initializers/README.md: -------------------------------------------------------------------------------- 1 | TEST 2 | ==== 3 | 4 | File who not appear in initializer -------------------------------------------------------------------------------- /test/config/initializers/test.js: -------------------------------------------------------------------------------- 1 | module.exports = function(compound) { 2 | compound.isInitialize = true; 3 | } 4 | -------------------------------------------------------------------------------- /test/controller-extensions.test.js: -------------------------------------------------------------------------------- 1 | var app = getApp(); 2 | var compound = app.compound; 3 | 4 | function initApp() { 5 | var s = compound.structure; 6 | s.controllers.ctl = function Ctl(){}; 7 | s.views['ctl/view'] = 'exists'; 8 | s.views['layouts/application_layout'] = 'layout'; 9 | 10 | }; 11 | 12 | describe('controller-extensions', function() { 13 | 14 | before(initApp); 15 | 16 | var controller, rendered, params = {}, req = { 17 | app: app, 18 | param: function(what) { 19 | return params[what]; 20 | } 21 | }, res = { 22 | render: function (file, params, callback) { 23 | rendered.push(file); 24 | if (callback) callback(); 25 | } 26 | }; 27 | 28 | function declareAndRun(action, test) { 29 | controller.action(action.name, action); 30 | controller.perform(action.name, req, res, test); 31 | } 32 | 33 | describe('rendering', function() { 34 | 35 | beforeEach(function() { 36 | controller = compound.controllerBridge.getInstance('ctl'); 37 | rendered = []; 38 | }); 39 | 40 | it('should render existing view', function(done) { 41 | declareAndRun(function renderExisting(c) { 42 | c.render('view'); 43 | }, function() { 44 | rendered.should.eql(['exists', 'layout']); 45 | done(); 46 | }); 47 | }); 48 | 49 | it('should render file without layout', function(done) { 50 | declareAndRun(function renderNoLayout(c) { 51 | c.render('view', {layout: false}); 52 | }, function() { 53 | rendered.should.eql(['exists']); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | 59 | describe('safe', function() { 60 | beforeEach(function() { 61 | controller = compound.controllerBridge.getInstance('ctl'); 62 | rendered = []; 63 | }); 64 | 65 | it('should call callback when no error', function(done) { 66 | var args; 67 | declareAndRun(function renderDifferentFormat(c) { 68 | doAsyncStuff(c.safe(function() { 69 | args = Array.prototype.slice.call(arguments); 70 | c.next(); 71 | })); 72 | }, function() { 73 | args.should.have.lengthOf(1); 74 | args[0].should.equal('he'); 75 | done(); 76 | }); 77 | function doAsyncStuff(cb) {process.nextTick(function(){cb(null, 'he')})} 78 | }); 79 | 80 | it('should call c.next(err) when error', function(done) { 81 | var called = false; 82 | declareAndRun(function renderDifferentFormat(c) { 83 | doAsyncStuff(c.safe(function() { 84 | called = true; 85 | })); 86 | }, function() { 87 | called.should.be.false; 88 | done(); 89 | }); 90 | function doAsyncStuff(cb) {process.nextTick(function(){cb(new Error)})} 91 | }); 92 | }); 93 | 94 | describe('format', function() { 95 | beforeEach(function() { 96 | controller = compound.controllerBridge.getInstance('ctl'); 97 | rendered = []; 98 | delete params.format; 99 | }); 100 | 101 | it('should render desired format', function(done) { 102 | var args; 103 | params.format = 'mobile'; 104 | declareAndRun(function renderDesiredFormat(c) { 105 | c.format({ 106 | mobile: function() { 107 | c.next(); 108 | } 109 | }); 110 | }, done) 111 | }); 112 | 113 | it('should render default format', function(done) { 114 | var args; 115 | params.format = 'unknown'; 116 | declareAndRun(function renderDefaultFormat(c) { 117 | c.format({ 118 | html: function() { 119 | c.next(); 120 | } 121 | }); 122 | }, done) 123 | }); 124 | 125 | it('should render customized default format', function(done) { 126 | var args; 127 | app.set('default format', 'json'); 128 | params.format = 'unknown'; 129 | declareAndRun(function renderDefaultFormat(c) { 130 | c.format({ 131 | json: function() { 132 | c.next(); 133 | delete app.settings['default format']; 134 | } 135 | }); 136 | }, done) 137 | }); 138 | }); 139 | 140 | }); 141 | -------------------------------------------------------------------------------- /test/controller.test.js: -------------------------------------------------------------------------------- 1 | var should = require('./init.js'); 2 | var app; 3 | describe('controller', function() { 4 | 5 | before(function(done) { 6 | app = getApp(); 7 | app.compound.on('ready', function() { 8 | done(); 9 | }); 10 | }); 11 | 12 | it('should be reusable', function(done) { 13 | function Controller() { 14 | } 15 | var f = 14, a = 24; 16 | Controller.prototype.first = function first(c) { 17 | f = 41; 18 | c.next(); 19 | }; 20 | Controller.prototype.second = function second(c) { 21 | a = 42; 22 | c.next(); 23 | }; 24 | app.compound.structure.controllers.test = Controller; 25 | app.compound.controllerBridge.callControllerAction('test', 'first', {}, {}, function(err) { 26 | should.not.exists(err); 27 | app.compound.controllerBridge.callControllerAction('test', 'second', {}, {}, function(err) { 28 | should.not.exists(err); 29 | f.should.equal(41); 30 | a.should.equal(42); 31 | done(); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/fixtures/config/database.json: -------------------------------------------------------------------------------- 1 | {"test": {"driver": "memory"}} 2 | -------------------------------------------------------------------------------- /test/fixtures/config/environment.js: -------------------------------------------------------------------------------- 1 | module.exports = function(compound) { 2 | compound.app.set('hello', 'world'); 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/config/extra-environment.js: -------------------------------------------------------------------------------- 1 | module.exports = function(compound) { 2 | compound.app.set('foo', 'bar'); 3 | }; 4 | -------------------------------------------------------------------------------- /test/generators.test.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var sys = require('util'); 5 | 6 | var memfs = {}, writeFileSync, readFileSync, writeSync, closeSync, existsSync, 7 | mkdirSync, chmodSync, exit; 8 | 9 | 10 | describe('Generators API', function() { 11 | 12 | var app, compound, args = ['--quiet']; 13 | 14 | before(function(done) { 15 | app = getApp(); 16 | compound = app.compound; 17 | compound.generators.init(compound); 18 | // compound.generators.quiet = true; 19 | stubFS(); 20 | compound.on('ready', function() { 21 | done(); 22 | }); 23 | }); 24 | 25 | after(unstubFS); 26 | 27 | var output, puts; 28 | 29 | beforeEach(function() { 30 | output = []; 31 | puts = sys.puts; 32 | sys.puts = function(str) { 33 | output.push(str.replace(/\u001b\[\d+m/g, '')); 34 | }; 35 | }); 36 | 37 | afterEach(function() { 38 | sys.puts = puts; 39 | }); 40 | 41 | it('should generate app', function () { 42 | compound.generators.perform('init', ['--stylus']); 43 | output.should.eql([ 44 | 'create app/', 45 | 'create app/assets/', 46 | 'create app/assets/coffeescripts/', 47 | 'create app/assets/stylesheets/', 48 | 'create app/models/', 49 | 'create app/controllers/', 50 | 'create app/helpers/', 51 | 'create app/tools/', 52 | 'create app/views/', 53 | 'create app/views/layouts/', 54 | 'create db/', 55 | 'create db/seeds/', 56 | 'create db/seeds/development/', 57 | 'create log/', 58 | 'create public/', 59 | 'create public/images', 60 | 'create public/stylesheets/', 61 | 'create public/javascripts/', 62 | 'create node_modules/', 63 | 'create config/', 64 | 'create config/locales/', 65 | 'create config/initializers/', 66 | 'create config/environments/', 67 | 'create app/assets/coffeescripts/application.coffee', 68 | 'create app/assets/stylesheets/application.styl', 69 | 'create app/tools/database.js', 70 | 'create config/environment.js', 71 | 'create config/environments/development.js', 72 | 'create config/environments/production.js', 73 | 'create config/environments/test.js', 74 | 'create config/routes.js', 75 | 'create config/autoload.js', 76 | 'create db/schema.js', 77 | 'create public/index.html', 78 | 'create public/stylesheets/bootstrap.css', 79 | 'create public/stylesheets/bootstrap-responsive.css', 80 | 'create public/images/glyphicons-halflings-white.png', 81 | 'create public/images/glyphicons-halflings.png', 82 | 'create public/images/compound.png', 83 | 'create public/javascripts/rails.js', 84 | 'create public/javascripts/bootstrap.js', 85 | 'create public/javascripts/application.js', 86 | 'create public/favicon.ico', 87 | 'create Procfile', 88 | 'create README.md', 89 | 'create package.json', 90 | 'create server.js', 91 | 'create .gitignore', 92 | 'create config/database.js', 93 | 'create app/views/layouts/application_layout.ejs', 94 | 'create app/controllers/application_controller.js' 95 | ]); 96 | }); 97 | 98 | it('should generate app', function () { 99 | var package = path.normalize(__dirname + '/../package.json'); 100 | delete memfs[package]; 101 | compound.generators.perform('init', ['--db', 'mongodb']); 102 | memfs[package].should.include('jugglingdb-mongodb'); 103 | }); 104 | }); 105 | 106 | function stubFS() { 107 | exit = process.exit; 108 | writeFileSync = fs.writeFileSync; 109 | readFileSync = fs.readFileSync; 110 | closeSync = fs.closeSync; 111 | writeSync = fs.writeSync; 112 | existsSync = fs.existsSync; 113 | mkdirSync = fs.mkdirSync; 114 | chmodSync = fs.chmodSync; 115 | fs.mkdirSync = function (name) { 116 | memfs[name] = true; 117 | }; 118 | fs.chmodSync = function () {}; 119 | fs.writeFileSync = function (name, content) { 120 | memfs[name] = content; 121 | return name; 122 | }; 123 | fs.existsSync = function (path) { 124 | return path in memfs; 125 | }; 126 | } 127 | 128 | function unstubFS() { 129 | fs.writeFileSync = writeFileSync; 130 | fs.mkdirSync = mkdirSync; 131 | fs.chmodSync = chmodSync; 132 | fs.existsSync = existsSync; 133 | process.exit = exit; 134 | } 135 | -------------------------------------------------------------------------------- /test/helpers.test.js: -------------------------------------------------------------------------------- 1 | var app, compound; 2 | before(function(done) { 3 | app = getApp(); 4 | compound = app.compound; 5 | compound.on('ready', function() { 6 | done(); 7 | }); 8 | }); 9 | 10 | /* 11 | * stylesheetLinkTag helper tests 12 | */ 13 | describe('stylesheetLinkTag', function() { 14 | it('should generate a single tag', function (){ 15 | var match = /\/; 16 | var tag = compound.helpers.stylesheetLinkTag('style'); 17 | 18 | tag.should.match(match); 19 | }); 20 | 21 | it('should generate multiple tags', function (){ 22 | var match = // 42 | var tag = compound.helpers.javascriptIncludeTag('app'); 43 | 44 | tag.should.match(match); 45 | }); 46 | 47 | it('should generate multiple tags', function (){ 48 | var match = /'); 115 | buf[1].should.equal(''); 116 | buf[2].should.equal(''); 117 | }); 118 | 119 | it('should allow to override "id" attribute of tag', function() { 120 | var res = { 121 | constructor: { 122 | modelName: 'Resource' 123 | }, 124 | id: 7 125 | }; 126 | var f = compound.helpers.formFor(res, {}); 127 | f.textarea('name').should.equal(''); 128 | f.textarea('name', {id: 'over'}).should.equal(''); 129 | }); 130 | 131 | it('should work for nested resource', function() { 132 | var res = { 133 | constructor: { 134 | modelName: 'User' 135 | }, 136 | id: 7, 137 | address: { 138 | constructor: 'Address', 139 | id: 9, 140 | street: 'Liberty st' 141 | } 142 | }; 143 | var f = compound.helpers.formFor(res, {action: '/'}); 144 | var addr = f.fieldsFor('address'); 145 | addr.input('street').should.equal(''); 146 | }); 147 | 148 | }); 149 | 150 | /* 151 | * errorMessagesFor helper tests 152 | */ 153 | describe('errorMessagesFor', function () { 154 | var resource = { 155 | errors: { 156 | name: ['can\'t be blank', 'is invalid'], 157 | email: ['is not unique'] 158 | } 159 | }; 160 | it('should generate html errors', function () { 161 | var html = compound.helpers.errorMessagesFor(resource); 162 | var expectedErrorString = '

      Validation failed. Fix the following errors to continue:

      • Name can\'t be blank
      • Name is invalid
      • Email is not unique
      '; 163 | html.should.equal(expectedErrorString); 164 | }); 165 | }); 166 | 167 | describe('metaTag', function() { 168 | 169 | it('should generate metaTag(name, content)', function() { 170 | var result = compound.helpers.metaTag('pageId', 77); 171 | var expected = ''; 172 | result.should.equal(expected); 173 | }); 174 | 175 | it('should generate metaTag(name, params)', function() { 176 | var result = compound.helpers.metaTag('pageId', {foo: 'bar'}); 177 | var expected = ''; 178 | result.should.equal(expected); 179 | }); 180 | 181 | it('should generate metaTag(params)', function() { 182 | var result = compound.helpers.metaTag({name: 'foo', content: 'bar'}); 183 | var expected = ''; 184 | result.should.equal(expected); 185 | }); 186 | 187 | it('should generate metaTag()', function() { 188 | var result = compound.helpers.metaTag(); 189 | var expected = ''; 190 | result.should.equal(expected); 191 | }); 192 | 193 | }); 194 | -------------------------------------------------------------------------------- /test/init.js: -------------------------------------------------------------------------------- 1 | module.exports = require('should'); 2 | 3 | process.env.NODE_ENV = 'test'; 4 | 5 | if (!process.env.TRAVIS) { 6 | if (typeof __cov === 'undefined') { 7 | process.on('exit', function () { 8 | require('semicov').report(); 9 | }); 10 | } 11 | 12 | require('semicov').init('lib'); 13 | } 14 | 15 | global.getApp = function() { 16 | var app = require('../').createServer() 17 | app.enable('quiet'); 18 | return app; 19 | }; 20 | -------------------------------------------------------------------------------- /test/middleware-injects.test.js: -------------------------------------------------------------------------------- 1 | 2 | describe('middleware-inject', function() { 3 | describe('Compound.injectMiddleware{At,Before,After}', function() { 4 | 5 | var app, compound; 6 | 7 | before(function () { 8 | app = getApp(); 9 | compound = app.compound; 10 | }); 11 | 12 | beforeEach(function() { 13 | app.stack = [ 14 | {handle: function zero() {}}, 15 | {handle: function first() {}}, 16 | {handle: function second() {}}, 17 | {handle: function third() {}}, 18 | {handle: function fourth() {}} 19 | ]; 20 | }); 21 | 22 | it('should inject middleware at position', function() { 23 | compound.injectMiddlewareAt(1, function my() {}); 24 | app.stack.length.should.equal(6); 25 | app.stack[1].handle.name.should.equal('my'); 26 | app.stack[2].handle.name.should.equal('first'); 27 | app.stack[3].handle.name.should.equal('second'); 28 | app.stack[4].handle.name.should.equal('third'); 29 | app.stack[5].handle.name.should.equal('fourth'); 30 | }); 31 | 32 | it('should inject to end when falsy or too high', function() { 33 | compound.injectMiddlewareAt(100, function my() {}); 34 | app.stack.length.should.equal(6); 35 | app.stack[5].handle.name.should.equal('my'); 36 | }); 37 | 38 | it('should should inject to beginning when position le 0', function() { 39 | compound.injectMiddlewareAt(0, function my() {}); 40 | app.stack.length.should.equal(6); 41 | app.stack[0].handle.name.should.equal('my'); 42 | compound.injectMiddlewareAt(0, function nega() {}); 43 | app.stack.length.should.equal(7); 44 | app.stack[0].handle.name.should.equal('nega'); 45 | app.stack[1].handle.name.should.equal('my'); 46 | }); 47 | 48 | it('should inject middleware before name', function() { 49 | compound.injectMiddlewareBefore('third', function twoAndHalf() {}); 50 | app.stack.length.should.equal(6); 51 | app.stack[3].handle.name.should.equal('twoAndHalf'); 52 | app.stack[4].handle.name.should.equal('third'); 53 | app.stack[5].handle.name.should.equal('fourth'); 54 | }); 55 | 56 | it('should inject middleware before function', function() { 57 | var second = app.stack[2].handle; 58 | compound.injectMiddlewareBefore(second, function mymid() {}); 59 | app.stack.length.should.equal(6); 60 | app.stack[2].handle.name.should.equal('mymid'); 61 | app.stack[3].handle.name.should.equal('second'); 62 | app.stack[4].handle.name.should.equal('third'); 63 | app.stack[5].handle.name.should.equal('fourth'); 64 | }); 65 | 66 | it('should inject middleware after name', function() { 67 | compound.injectMiddlewareAfter('third', function threeAndHalf() {}); 68 | app.stack.length.should.equal(6); 69 | app.stack[3].handle.name.should.equal('third'); 70 | app.stack[4].handle.name.should.equal('threeAndHalf'); 71 | app.stack[5].handle.name.should.equal('fourth'); 72 | }); 73 | 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/utils.test.js: -------------------------------------------------------------------------------- 1 | var should = require('./init.js'); 2 | var app, compound; 3 | 4 | describe('utilities', function () { 5 | before(function() { 6 | app = getApp(); 7 | compound = app.compound; 8 | }); 9 | 10 | it('should camelize string', function () { 11 | var cc = compound.utils.camelize; 12 | cc('underscored_string').should.equal('underscoredString'); 13 | cc('underscored_string', 1).should.equal('UnderscoredString'); 14 | }); 15 | 16 | it('should humanize string', function () { 17 | var hs = compound.utils.humanize; 18 | hs('hey_man').should.equal('Hey man'); 19 | }); 20 | 21 | it('should classify string', function () { 22 | var hs = compound.utils.classify; 23 | hs('bio_robots').should.equal('bioRobot'); 24 | }); 25 | 26 | it('should underscore string', function () { 27 | var us = compound.utils.underscore; 28 | us('IAmARobot').should.equal('i_am_a_robot'); 29 | us('_IAmARobot').should.equal('_i_am_a_robot'); 30 | us('_iAmARobot').should.equal('_i_am_a_robot'); 31 | }); 32 | 33 | it('should add spaces', function () { 34 | compound.utils.addSpaces('hello', 8).should.equal('hello '); 35 | compound.utils.addSpaces('hello', 8, true).should.equal(' hello'); 36 | }); 37 | 38 | describe('utils#inherits', function() { 39 | 40 | it('should inherit superclass', function() { 41 | function MyClass(){} 42 | SuperClass.classMethod = function(){}; 43 | function SuperClass(){} 44 | compound.utils.inherits(MyClass, SuperClass); 45 | var myObj = new MyClass; 46 | myObj.should.be.an.instanceOf(SuperClass); 47 | MyClass.super_.should.equal(SuperClass); 48 | should.not.exists(MyClass.classMethod); 49 | }); 50 | 51 | it('should inherit superclass with class methods', function() { 52 | function MyClass(){} 53 | SuperClass.classMethod = function(){}; 54 | function SuperClass(){} 55 | compound.utils.inherits(MyClass, SuperClass, true); 56 | var myObj = new MyClass; 57 | myObj.should.be.an.instanceOf(SuperClass); 58 | MyClass.super_.should.equal(SuperClass); 59 | should.exists(MyClass.classMethod); 60 | }); 61 | 62 | }); 63 | }); 64 | 65 | -------------------------------------------------------------------------------- /vendor/date_format.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Date Format 1.2.3 3 | * (c) 2007-2009 Steven Levithan 4 | * MIT license 5 | * 6 | * Includes enhancements by Scott Trenda 7 | * and Kris Kowal 8 | * 9 | * Accepts a date, a mask, or a date and a mask. 10 | * Returns a formatted version of the given date. 11 | * The date defaults to the current date/time. 12 | * The mask defaults to dateFormat.masks.default. 13 | */ 14 | 15 | var dateFormat = function() { 16 | var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, 17 | timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, 18 | timezoneClip = /[^-+\dA-Z]/g, 19 | pad = function(val, len) { 20 | val = String(val); 21 | len = len || 2; 22 | while (val.length < len) val = '0' + val; 23 | return val; 24 | }; 25 | 26 | // Regexes and supporting functions are cached through closure 27 | return function(date, mask, utc) { 28 | var dF = dateFormat; 29 | 30 | // You can't provide utc if you skip other args (use the "UTC:" mask prefix) 31 | if (arguments.length == 1 && Object.prototype.toString.call(date) == '[object String]' && !/\d/.test(date)) { 32 | mask = date; 33 | date = undefined; 34 | } 35 | 36 | // Passing date through Date applies Date.parse, if necessary 37 | date = date ? new Date(date) : new Date; 38 | if (isNaN(date)) throw SyntaxError('invalid date'); 39 | 40 | mask = String(dF.masks[mask] || mask || dF.masks['default']); 41 | 42 | // Allow setting the utc argument via the mask 43 | if (mask.slice(0, 4) == 'UTC:') { 44 | mask = mask.slice(4); 45 | utc = true; 46 | } 47 | 48 | var _ = utc ? 'getUTC' : 'get', 49 | d = date[_ + 'Date'](), 50 | D = date[_ + 'Day'](), 51 | m = date[_ + 'Month'](), 52 | y = date[_ + 'FullYear'](), 53 | H = date[_ + 'Hours'](), 54 | M = date[_ + 'Minutes'](), 55 | s = date[_ + 'Seconds'](), 56 | L = date[_ + 'Milliseconds'](), 57 | o = utc ? 0 : date.getTimezoneOffset(), 58 | flags = { 59 | d: d, 60 | dd: pad(d), 61 | ddd: dF.i18n.dayNames[D], 62 | dddd: dF.i18n.dayNames[D + 7], 63 | m: m + 1, 64 | mm: pad(m + 1), 65 | mmm: dF.i18n.monthNames[m], 66 | mmmm: dF.i18n.monthNames[m + 12], 67 | yy: String(y).slice(2), 68 | yyyy: y, 69 | h: H % 12 || 12, 70 | hh: pad(H % 12 || 12), 71 | H: H, 72 | HH: pad(H), 73 | M: M, 74 | MM: pad(M), 75 | s: s, 76 | ss: pad(s), 77 | l: pad(L, 3), 78 | L: pad(L > 99 ? Math.round(L / 10) : L), 79 | t: H < 12 ? 'a' : 'p', 80 | tt: H < 12 ? 'am' : 'pm', 81 | T: H < 12 ? 'A' : 'P', 82 | TT: H < 12 ? 'AM' : 'PM', 83 | Z: utc ? 'UTC' : (String(date).match(timezone) || ['']).pop().replace(timezoneClip, ''), 84 | o: (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), 85 | S: ['th', 'st', 'nd', 'rd'][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] 86 | }; 87 | 88 | return mask.replace(token, function($0) { 89 | return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); 90 | }); 91 | }; 92 | }(); 93 | 94 | // Some common format strings 95 | dateFormat.masks = { 96 | 'default': 'ddd mmm dd yyyy HH:MM:ss', 97 | shortDate: 'm/d/yy', 98 | mediumDate: 'mmm d, yyyy', 99 | longDate: 'mmmm d, yyyy', 100 | fullDate: 'dddd, mmmm d, yyyy', 101 | shortTime: 'h:MM TT', 102 | mediumTime: 'h:MM:ss TT', 103 | longTime: 'h:MM:ss TT Z', 104 | isoDate: 'yyyy-mm-dd', 105 | isoTime: 'HH:MM:ss', 106 | isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", 107 | isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" 108 | }; 109 | 110 | // Internationalization strings 111 | dateFormat.i18n = { 112 | dayNames: [ 113 | 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 114 | 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' 115 | ], 116 | monthNames: [ 117 | 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 118 | 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' 119 | ] 120 | }; 121 | 122 | // For convenience... 123 | Date.prototype.format = function(mask, utc) { 124 | return dateFormat(this, mask, utc); 125 | }; 126 | --------------------------------------------------------------------------------