├── CHANGELOG.md ├── CODINGSTANDARDS.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── USERGUIDE.md ├── build ├── build.xml ├── code-templates │ ├── core-code-templates.js │ ├── db-code-templates.js │ ├── migration-add-column │ │ └── dev │ │ │ └── migrations │ │ │ └── %%%database_connection_name%%% │ │ │ └── %%%file_name%%% │ ├── migration-create-table │ │ └── dev │ │ │ └── migrations │ │ │ └── %%%database_connection_name%%% │ │ │ └── %%%time_stamp%%%_Create%%%table_name_camel_case%%%Table.js │ ├── new-project │ │ ├── build.xml │ │ ├── config.js │ │ ├── controllers │ │ │ └── main-controllers.js │ │ ├── lib │ │ │ └── index.js │ │ ├── run_console.js │ │ ├── run_dev_server.bash │ │ ├── run_server.js │ │ ├── static │ │ │ ├── css │ │ │ │ └── screen.css │ │ │ └── images │ │ │ │ └── spludo_template_bg.png │ │ └── views │ │ │ ├── Homepage.ejs │ │ │ └── HtmlLayout.ejs │ └── service │ │ ├── lib │ │ └── services │ │ │ └── %%%service_name%%%Service.js │ │ └── test │ │ └── %%%service_name_lower_case%%%-service-test.js ├── core_dev_change_version_number.bash ├── generate_monit_config.bash ├── jshint.conf ├── jsl │ └── jsl.conf ├── run_db_tasks.js ├── run_dev_server.js ├── spludo-gen.js └── tasks │ ├── build-db.xml │ └── db-tasks.js ├── core ├── ApiServiceController.js ├── BaseApplication.js ├── BootstrapManager.js ├── Config.js ├── Context.js ├── ContextToolkit.js ├── Controller.js ├── ControllerManager.js ├── DataMapper.js ├── DataMapperManager.js ├── EjsView.js ├── JsView.js ├── Logging.js ├── ObjectToolkit.js ├── Options.js ├── ServiceManager.js ├── StringToolkit.js ├── SyncController.js ├── Validation.js ├── Validator.js ├── ValidatorManager.js ├── ViewManager.js ├── console │ └── ConsoleApplication.js ├── core-data-mappers.js ├── core-validators.js ├── database │ ├── BaseSqlDatabaseDriver.js │ ├── DatabaseManager.js │ ├── DatabaseMigration.js │ ├── MysqlDatabaseDriver.js │ └── SqliteDatabaseDriver.js ├── index.js ├── server │ ├── CookieSessionManager.js │ ├── ServerApplication.js │ ├── StaticFilesManager.js │ └── lib │ │ └── multipart.js ├── storage │ ├── MemoryStorage.js │ └── StorageManager.js └── util.js ├── node_modules ├── README.md └── inflection.js ├── package.json ├── spludo-gen └── spludotests ├── README ├── build.properties ├── build.xml ├── test ├── contexttoolkit-test.js ├── cookiesessionmanager-test.js ├── datamapper-test.js ├── ejs-test.js ├── memorystorage-test.js ├── objecttoolkit-test.js ├── stringtoolkit-test.js └── validation-test.js └── testdata ├── ejs_example_with_ejs_at_the_beginning.ejs ├── ejs_example_with_ejs_at_the_beginning.ejs.expected_output.txt ├── ejs_example_with_expression.ejs ├── ejs_example_with_expression.ejs.expected_output.txt ├── ejs_example_with_indention_and_two_blocks.ejs ├── ejs_example_with_indention_and_two_blocks.ejs.expected_output.txt ├── ejs_exception_for_not_closed_expression_tag.ejs ├── ejs_exception_for_semicolon_in_expression_tag.ejs ├── plain_html_file.ejs └── plain_html_file_with_beginning_tag.ejs /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Spludo Framework CHANGELOG 2 | ======================= 3 | 4 | Official Site: 5 | 6 | Spludo is copyright 2009-2014 by DracoBlue 7 | 8 | ## 2.0.1 (2014/08/17) 9 | 10 | * removed spludo.com from source 11 | * put [USERGUIDE.md](USERGUIDE.md) and [CODINGSTANDARDS.md](CODINGSTANDARDS.md) into the repository 12 | 13 | ## 2.0.0 (2012/01/30) 14 | 15 | * removed plugin system #4 16 | * folder for code-templates is not needed anymore #6 17 | * added proper underscore notation for generated services #7 18 | 19 | ## 1.1.0 (2011/11/20) 20 | 21 | * added dependency to node >=0.4.0 && node < 0.5.0 22 | * generator for monit+upstart configuration 23 | * code generation for migrations extended with shortcut fields definition 24 | * added support for application/json paylod 25 | * generate *ByIds method for services 26 | * added ApiServiceController to register a service as REST service 27 | * context.request now holds the current req instance 28 | * added inflection.js 29 | * added node_modules for own vendor libraries 30 | * replaced own testing system with vows (if you want to run the tests, please install vows!) 31 | * spludo_directory variable is now set always when calling spludo-gen 32 | * added bootstrap_manager.whenLoaded(callback) to get notified as 33 | soon as the app is ready. 34 | * replaced self with that 35 | * fixed core_dev_change_build_version on MACOSX 36 | * added jshint instead of jslint 37 | * added support for node_modules folder in project directory 38 | * added Criteria for DatabaseDrivers 39 | * added code generation for Migrations 40 | * added SqliteDatabaseDriver 41 | * added a Database Migration system 42 | * added MysqlDatabaseDriver 43 | * added code generation for Services 44 | * added DatabaseManager and ServiceManager 45 | * added Logging#addTracing (makes the this.trace(function_name) obsolete) 46 | * Codegeneration loads the spludo application now (this enables the developer 47 | to generate code against the base of the application) 48 | * Codegeneration #validateParameter receives the validated parameters now 49 | * TestCases are now able to call .debug+.log and so on, because the execute 50 | method is applied to the TestSuite. 51 | 52 | ## 1.0.3 (2011/03/05) 53 | 54 | * added dependency to node >=0.4.0 55 | * putting plugin's lib folder to require path before bootstrap of the index.js files 56 | 57 | ## 1.0.2 (2010/12/14) 58 | 59 | * bug #1: console and test application hang because of fs.watchFile in EjsView 60 | 61 | ## 1.0.1 (2010/09/26) 62 | 63 | * group and chain are truely async now 64 | * added "spludo-gen controller" (wizard to create a new controller) 65 | * views can now be in sub folders 66 | * added partial + partials in .ejs 67 | * .ejs-Views recompile the view as soon as it is changed 68 | 69 | ## 1.0.0 (2010/09/20) 70 | 71 | * Initial Release 72 | 73 | -------------------------------------------------------------------------------- /CODINGSTANDARDS.md: -------------------------------------------------------------------------------- 1 | # Spludo Codingstandards 2 | 3 | ## Why Standards? 4 | 5 | Everyone has his own programming style. Influenced by thousands of lines of 6 | code read and written in the past. When providing a library or framework it's 7 | important that the code follows a constant style, to make reading, 8 | understanding and extending the code easy. 9 | 10 | ## Block-Indention & File-Encoding 11 | 12 | The files are indented by 4 spaces. The file encoding is utf8. Line endings are \n. 13 | 14 | ## Names: Functions, Classes and Variables 15 | 16 | All classes are camelcase and start with an uppercase letter. 17 | 18 | MyClass, ThisClass 19 | 20 | All variables are always lowercase and words are seperated by a `_`. This is also 21 | valid for parameters! 22 | 23 | user_id, first_name 24 | 25 | All member functions are camelcase and start with a lowercase letter. 26 | 27 | MyClass.getUserName 28 | 29 | ## Braces & Indention 30 | 31 | Braces are always on current line. This example code should express how those are 32 | handled. 33 | 34 | var response_handler = function(data, seperator) { 35 | var response = []; 36 | 37 | var data_length = data.length; 38 | 39 | for (var i = 0; i < data_length; i++) { 40 | response.push(seperator + data[i] + seperator); 41 | } 42 | 43 | /* 44 | * Example for {} and [] initializers. 45 | */ 46 | var example_variable = { 47 | "key": [ 48 | 1, 2, 3 49 | ], 50 | "key_two": { 51 | "key_two_two": "value" 52 | } 53 | }; 54 | 55 | return response.join(""); 56 | } 57 | 58 | ## Continuous-Style 59 | 60 | Whenever a function in the spludo core is used async, it returns a function which 61 | takes just one argument as parameter. Why this works this way, can be read in the 62 | [understanding the framework] chapter in the user guide. 63 | 64 | [understanding the framework]: USERGUIDE.md 65 | 66 | // definition of getAlice function 67 | function getAlice(bob) { 68 | return function(cb) { 69 | cb(bob.toUpperCase()); 70 | }; 71 | } 72 | 73 | // usage of getAlice function 74 | getAlice(bob)(function(alice) { 75 | // following code 76 | }); 77 | 78 | ## Extending objects 79 | 80 | The `BaseApplication` is a good example how one can extend prototypes. 81 | 82 | /** 83 | * @class Is a base application (should be extended). 84 | * 85 | * @extends Options 86 | * @extends Logging 87 | * 88 | * @since 0.1 89 | * @author DracoBlue 90 | */ 91 | BaseApplication = function(options) { 92 | this.setOptions(options); 93 | if (typeof this.run !== "function") { 94 | throw new Error("Implement the .run method!"); 95 | } 96 | }; 97 | 98 | extend(true, BaseApplication.prototype, Options.prototype, Logging.prototype); 99 | 100 | BaseApplication.prototype.logging_prefix = 'BaseApplication'; 101 | 102 | In this case the BaseApplication is extended by Options and Logging. 103 | 104 | A new function for the prototype may be defined in this way: 105 | 106 | BaseApplication.prototype.run = function() { 107 | throw new Error("run method not implemented!"); 108 | }; 109 | 110 | 111 | ## Comments-Style 112 | 113 | You may use: 114 | 115 | /** 116 | * Entity Comment 117 | */ 118 | function getUserNameById(user_id) 119 | 120 | to comment any entity in the core. Documentation of obvious things is *highly 121 | discouraged*. This is why, there is no explanation comment for what `user_id` 122 | is or what the return value is. It's obvious that the return value is a `String` 123 | and the `user_id` is an id! 124 | 125 | You may use: 126 | 127 | /* 128 | * Comment 129 | */ 130 | 131 | to explain decisions with those comments. 132 | 133 | You may **not** use: 134 | 135 | // 136 | 137 | they are reserved for temporary comments while developing and `//` code is 138 | expected to be committed by accident. 139 | 140 | ## Header 141 | 142 | This header should be the first lines of each `.js` file: 143 | 144 | /* 145 | * This file is part of the Spludo Framework. 146 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 147 | * 148 | * Licensed under the terms of MIT License. For the full copyright and license 149 | * information, please see the LICENSE file in the root folder. 150 | */ 151 | 152 | In case of a `.xml` file, the header looks like this and should be injected right after 153 | the first node. 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | If `#` is used as comment character you may also use this style: 162 | 163 | # 164 | # This file is part of the Spludo Framework. 165 | # Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 166 | # 167 | # Licensed under the terms of MIT License. For the full copyright and license 168 | # information, please see the LICENSE file in the root folder. 169 | # 170 | 171 | ## Forbidden Functions/Statements 172 | 173 | You should not use the with, switch or eval statement. The reasons may be found 174 | in the [javascript the good parts] book by Douglas Crockford. 175 | 176 | [javascript the good parts]: http://oreilly.com/catalog/9780596517748 177 | 178 | Usually there is a better implementation possible. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Spludo 2 | 3 | ## Accessing the Source 4 | 5 | The latest development version of spludo is available at the 6 | [spludo git repository][spludo-git-repository]: 7 | 8 | [spludo-git-repository]: http://github.com/DracoBlue/spludo/commits/master 9 | 10 | git clone git://github.com/DracoBlue/spludo.git 11 | 12 | Spludo is currently developed against *node.JS 0.10*. 13 | 14 | You may find the latest version of spludo also as [.zip/.tar][tar-zip-download]. 15 | 16 | [tar-zip-download]: http://github.com/DracoBlue/spludo/archives/master 17 | 18 | 19 | ## Mailing List 20 | 21 | There is a new mailing list available at [spludo@googlegroups.com][spludo-mailing-list] 22 | ([Archive][spludo-mailing-list]) 23 | [spludo-mailing-list]: http://groups.google.com/group/spludo 24 | 25 | ## Issue Tracker 26 | 27 | There is an issue tracker at . 28 | 29 | ## How to Contribute 30 | 31 | If you want to contribute, you are welcome! Please be sure to follow and 32 | understand the [coding standards]. 33 | 34 | Before you start to develop a new core 35 | feature ask on the [mailing list][spludo-mailing-list] for input and if the 36 | feature is wanted. There is also an [issue tracker][spludo-issue-tracker]. 37 | 38 | [spludo-issue-tracker]: http://github.com/DracoBlue/spludo/issues 39 | [coding standards]: CODINGSTANDARDS.md 40 | 41 | ### 1. Clean checkout 42 | 43 | $ git clone git://github.com/DracoBlue/spludo.git 44 | $ cd spludo 45 | 46 | ### 2. Applying changes 47 | 48 | Now make your changes. 49 | 50 | ### 3. Testing the code 51 | 52 | Then check if tests run and code is lint against the [coding standards]: 53 | 54 | $ cd spludotests 55 | $ ant core-test core-lint 56 | 57 | ### 4. Creating the patch 58 | 59 | Now create a patch: 60 | 61 | $ git commit -m "Good summary of what your patch does" 62 | $ git format-patch HEAD^ 63 | 64 | ### 5. Sending the patch 65 | 66 | If you want to submit a patch, please send it to the 67 | [mailing list][spludo-mailing-list] by linking to a [gists][gist-github] 68 | post or as file attachment. 69 | 70 | [gist-github]: http://gist.github.com/ 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Spludo Framework is licensed under the terms of MIT License. 2 | 3 | Copyright (c) 2009-2010 by DracoBlue (JanS@DracoBlue.de) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Spludo Framework README 2 | ======================= 3 | 4 | Latest Release: [![GitHub version](https://badge.fury.io/gh/DracoBlue%2Fspludo.png)](https://github.com/DracoBlue/spludo/releases) 5 | 6 | Official Site: 7 | 8 | Spludo is copyright 2009-2014 by DracoBlue 9 | 10 | What is Spludo? 11 | ---------------- 12 | Spludo is high performance, evented, server side, prototype based, javascript 13 | mvc web framework. It includes DI+AOP and a Convention-Over-Configuration 14 | Approach. 15 | The framework is running on the node.JS-platform, which employs the lightning 16 | fast V8-Engine. 17 | 18 | New Project in 3 Steps 19 | ---------------------- 20 | 21 | 1. Create the new project 22 | 23 | $ spludo/spludo-gen new-project 24 | 25 | 2. Enter the folder 26 | 27 | $ cd myapp 28 | 29 | 3. Run 30 | 31 | In Server Mode 32 | 33 | $ node run_server.js 34 | 35 | and open your browser at 36 | 37 | In Console Mode 38 | 39 | $ node run_console.js shell.hello 40 | Hello, back! 41 | 42 | Userguide 43 | --------- 44 | 45 | This package contains a [USERGUIDE.md](USERGUIDE.md) with a big introduction into the framework. 46 | 47 | Resources 48 | ---------- 49 | 50 | * Site: 51 | * Contributing: [CONTRIBUTING.md](CONTRIBUTING.md) 52 | * Mailing List: 53 | * Issue Tracker: 54 | * User-Guide: [USERGUIDE.md](USERGUIDE.md) 55 | * Tutorial: 56 | 1. [New Project](https://github.com/DracoBlue/docsforit/blob/master/etc/sections/new-project.md) 57 | 2. [Base HTML Structure](https://github.com/DracoBlue/docsforit/blob/master/etc/sections/base-html-structure.md) 58 | 3. [Markdown Views](https://github.com/DracoBlue/docsforit/blob/master/etc/sections/markdown-views.md) 59 | 4. [Docs-Manager](https://github.com/DracoBlue/docsforit/blob/master/etc/sections/docs-manager.md) 60 | 5. [Extending the Docs-Manager](https://github.com/DracoBlue/docsforit/blob/master/etc/sections/extending-the-docs-manager.md) 61 | 6. [Sections-Navigation](https://github.com/DracoBlue/docsforit/blob/master/etc/sections/sections-navigation.md) 62 | 7. [Sorting the Sections](https://github.com/DracoBlue/docsforit/blob/master/etc/sections/sorting-the-sections.md) 63 | 8. [Configuration](https://github.com/DracoBlue/docsforit/blob/master/etc/sections/configuration.md) 64 | 9. [Editing the Sections](https://github.com/DracoBlue/docsforit/blob/master/etc/sections/editing-the-sections.md) 65 | 10. [Authentication](https://github.com/DracoBlue/docsforit/blob/master/etc/sections/authentication.md) 66 | 67 | License 68 | -------- 69 | 70 | Spludo is licensed under the terms of MIT. See LICENSE for more information. 71 | -------------------------------------------------------------------------------- /build/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Spludo Core Build-Targets, should be included in a project specific build.xml! 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /build/code-templates/migration-add-column/dev/migrations/%%%database_connection_name%%%/%%%file_name%%%: -------------------------------------------------------------------------------- 1 | module.exports.up = function(database_connection) { 2 | return function(cb) { 3 | database_connection.addColumn('%%%table_name%%%', '%%%column_name%%%', %%%column_definition%%%)(function(error, message) { 4 | cb(error, message); 5 | }); 6 | }; 7 | }; 8 | module.exports.down = function(database_connection) { 9 | return function(cb) { 10 | database_connection.dropColumn('%%%table_name%%%', '%%%column_name%%%')(function(error, message) { 11 | cb(error, message); 12 | }); 13 | }; 14 | }; -------------------------------------------------------------------------------- /build/code-templates/migration-create-table/dev/migrations/%%%database_connection_name%%%/%%%time_stamp%%%_Create%%%table_name_camel_case%%%Table.js: -------------------------------------------------------------------------------- 1 | module.exports.up = function(database_connection) { 2 | return function(cb) { 3 | database_connection.createTable('%%%table_name%%%', { 4 | %%%fields_data%%% 5 | })(function(error, message) { 6 | cb(error, message); 7 | }); 8 | }; 9 | }; 10 | module.exports.down = function(database_connection) { 11 | return function(cb) { 12 | database_connection.dropTable('%%%table_name%%%')(function(error, message) { 13 | cb(error, message); 14 | }); 15 | }; 16 | }; -------------------------------------------------------------------------------- /build/code-templates/new-project/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is the %%%project.name%%% application built with spludo. 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /build/code-templates/new-project/config.js: -------------------------------------------------------------------------------- 1 | config.setValues({ 2 | "logging": { 3 | "level": 2 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /build/code-templates/new-project/controllers/main-controllers.js: -------------------------------------------------------------------------------- 1 | new Controller("", { 2 | "execute": function(params, context) { 3 | var that = this; 4 | return function(cb) { 5 | context.layout_name = 'HtmlLayout'; 6 | context.view_name = 'Homepage'; 7 | cb(); 8 | }; 9 | } 10 | }); 11 | 12 | new Controller("shell.hello", { 13 | "execute": function(params, context) { 14 | var that = this; 15 | return function(cb) { 16 | cb('Hello, back!'); 17 | }; 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /build/code-templates/new-project/lib/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DracoBlue/spludo/a6f4fc6b61e903c241ebdf9f8d41801877978c4c/build/code-templates/new-project/lib/index.js -------------------------------------------------------------------------------- /build/code-templates/new-project/run_console.js: -------------------------------------------------------------------------------- 1 | require("%%%spludo.directory%%%core"); 2 | new ConsoleApplication( { 3 | "path": process.argv[2] || "" 4 | }).run(); 5 | -------------------------------------------------------------------------------- /build/code-templates/new-project/run_dev_server.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0` 3 | node %%%spludo.directory%%%build/run_dev_server.js 4 | -------------------------------------------------------------------------------- /build/code-templates/new-project/run_server.js: -------------------------------------------------------------------------------- 1 | require("%%%spludo.directory%%%core"); 2 | new ServerApplication().run(); 3 | -------------------------------------------------------------------------------- /build/code-templates/new-project/static/css/screen.css: -------------------------------------------------------------------------------- 1 | #body { 2 | background-image:url(../images/spludo_template_bg.png); 3 | font-family: Arial, Helvetica, sans-serif; 4 | } 5 | 6 | #page_margins { 7 | width: 990px; 8 | margin-left: auto; 9 | margin-right: auto; 10 | padding: 10px; 11 | } 12 | 13 | 14 | 15 | #page_header { 16 | padding: 10px; 17 | } 18 | 19 | #page_header a { 20 | color: #ffffff; 21 | text-decoration: none; 22 | } 23 | 24 | #page_header h1 { 25 | padding: 0; 26 | margin: 0; 27 | } 28 | 29 | #page_footer { 30 | text-align: center; 31 | padding: 10px; 32 | font-size: auto; 33 | color: #BBBBBB; 34 | text-transform: uppercase; 35 | font-weight: bold; 36 | font-size: 0.6em; 37 | } 38 | 39 | #page_footer a { 40 | text-decoration: none; 41 | color: #DDDDDD; 42 | } 43 | 44 | 45 | #page_main { 46 | padding: 10px; 47 | background-color: #E4EFC2; 48 | } 49 | -------------------------------------------------------------------------------- /build/code-templates/new-project/static/images/spludo_template_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DracoBlue/spludo/a6f4fc6b61e903c241ebdf9f8d41801877978c4c/build/code-templates/new-project/static/images/spludo_template_bg.png -------------------------------------------------------------------------------- /build/code-templates/new-project/views/Homepage.ejs: -------------------------------------------------------------------------------- 1 |

Welcome to Spludo

2 | 3 |

4 | This is your first spludo application. You may easily edit this page and 5 | create new ones. Getting started is easy, if you have a look at the 6 | tutorial over there at https://github.com/DracoBlue/spludo. 7 |

8 | 9 | 10 |

Edit this Application

11 | 12 |

Change the Contents

13 |

14 | If you want to change contents of this page, open 15 | %%%project.name%%%/views/Homepage.ejs and change it! 16 |

17 | 18 |

Change the Layout

19 |

20 | If you want to add additional stylesheets or any other stuff, which is same 21 | for most of the pages, you may want to alter the layout at 22 | %%%project.name%%%/views/HtmlLayout.ejs. 23 |

24 | 25 |

Change the URL

26 |

27 | If you want to change the url of that page, open 28 | %%%project.name%%%/controllers/main-controllers.js and change: 29 |

30 |
new Controller("", {
31 |

32 | to: 33 |

34 |
new Controller("hello-world", {
35 |

36 | Now you can open the page by using: /hello-world 37 | instead of simple /. 38 |

39 | 40 |

Change the CSS

41 |

42 | The used css file is available at 43 | %%%project.name%%%/static/css/screen.css. 44 |

45 | 46 |

Run in Console Mode

47 |

48 | If you want to launch the console mode, you can easily 49 | node run_console.js shell.hello in your 50 | %%%project.name%%%/ folder. 51 |

52 |

It should output something like this:

53 |
$ node run_console.js shell.hello
54 | Hello, back!
55 | 56 |

Edit Console Output

57 | 58 |

59 | Open %%%project.name%%%/controllers/main-controllers.js and 60 | change: 61 |

62 |
cb('Hello, back!');
63 |

64 | to: 65 |

66 |
cb('How are you?');
67 | 68 |

Help?

69 |

70 | Checkout the official spludo site at 71 | https://github.com/DracoBlue/spludo for a user guide, 72 | tutorial and the mailing list. 73 |

-------------------------------------------------------------------------------- /build/code-templates/new-project/views/HtmlLayout.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | 3 | context.headers = context.headers || {}; 4 | context.headers["Content-Type"] = "text/html; charset=UTF-8"; 5 | 6 | var base_url = "http://" + context.request_headers["host"] + "/"; 7 | 8 | var application_title = "%%%project.title%%%"; 9 | 10 | %> 11 | 12 | 13 | 14 | 15 | 16 | <%= StringToolkit.encodeXml(application_title) %> 17 | 18 | 19 | 20 | 21 | 22 | <% 23 | if (context.javascripts) { 24 | var javascripts = context.javascripts; 25 | var javascripts_length = javascripts.length; 26 | for (var i = 0; i < javascripts_length; i++) { 27 | %> 28 | 29 | <% 30 | 31 | } 32 | } 33 | %> 34 | 35 | 36 |
37 | 40 |
41 | <%= inner %> 42 |
43 | 44 | 47 |
48 | 49 | -------------------------------------------------------------------------------- /build/code-templates/service/lib/services/%%%service_name%%%Service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module %%%service_name%%%Service 3 | * 4 | * @extends Logging 5 | */ 6 | var %%%service_name%%%Service = function() { 7 | this.trace('initialize', arguments); 8 | this.addTracing(); 9 | var database_connection_name = config.get('%%%service_name_lower_case%%%', {}).database_connection; 10 | this.database_connection = database_manager.getDatabase(database_connection_name); 11 | }; 12 | 13 | extend(true, %%%service_name%%%Service.prototype, Logging.prototype); 14 | %%%service_name%%%Service.prototype.logging_prefix = '%%%service_name%%%Service'; 15 | 16 | var assert = require('assert'); 17 | 18 | var %%%service_name%%% = function(initial_values) { 19 | this.values = initial_values || {}; 20 | }; 21 | %%%service_object_definition%%% 22 | 23 | %%%service_name%%%Service.prototype.get%%%service_name%%%ById = function(cb, id) { 24 | assert.equal(typeof cb, 'function', 'First parameter must be a callback!'); 25 | 26 | this.database_connection.selectTableRows('%%%database_table_name%%%', '%%%id_key%%% = ?', [id])(function(error, results) { 27 | if (results.length === 0) { 28 | cb(); 29 | } else { 30 | cb(new %%%service_name%%%(results[0])); 31 | } 32 | }); 33 | }; 34 | 35 | %%%service_name%%%Service.prototype.get%%%service_name_plural%%%ByIds = function(cb, ids) { 36 | assert.equal(typeof cb, 'function', 'First parameter must be a callback!'); 37 | 38 | var criteria = database_manager.createCriteria(); 39 | criteria.andWhereIn('%%%id_key%%%', ids); 40 | 41 | this.database_connection.selectTableRows('%%%database_table_name%%%', criteria)(function(error, results) { 42 | var %%%service_name_plural_lower_case%%% = []; 43 | 44 | var results_length = results.length; 45 | for (var i = 0; i < results_length; i++) { 46 | %%%service_name_plural_lower_case%%%.push(new %%%service_name%%%(results[i])); 47 | } 48 | 49 | cb(%%%service_name_plural_lower_case%%%); 50 | }); 51 | }; 52 | 53 | %%%service_name%%%Service.prototype.update%%%service_name%%% = function(cb, %%%service_name_lower_case%%%, values) { 54 | assert.equal(typeof cb, 'function', 'First parameter must be a callback!'); 55 | 56 | this.database_connection.updateTableRows('%%%database_table_name%%%', values, '%%%id_key%%% = ?', [%%%service_name_lower_case%%%.getId()])(function(error, affected_rows) { 57 | cb(error, affected_rows); 58 | }); 59 | }; 60 | 61 | %%%service_name%%%Service.prototype.delete%%%service_name%%% = function(cb, %%%service_name_lower_case%%%) { 62 | assert.equal(typeof cb, 'function', 'First parameter must be a callback!'); 63 | 64 | this.database_connection.deleteTableRows('%%%database_table_name%%%', '%%%id_key%%% = ?', [%%%service_name_lower_case%%%.getId()])(function(error, affected_rows) { 65 | cb(error, affected_rows); 66 | }); 67 | }; 68 | 69 | %%%service_name%%%Service.prototype.create%%%service_name%%% = function(cb, values) { 70 | assert.equal(typeof cb, 'function', 'First parameter must be a callback!'); 71 | 72 | this.database_connection.createTableRow('%%%database_table_name%%%', values)(function(error, last_insert_id) { 73 | cb(error, last_insert_id); 74 | }); 75 | }; 76 | 77 | /* 78 | * Initialize the Module 79 | */ 80 | module.exports = new %%%service_name%%%Service(); 81 | -------------------------------------------------------------------------------- /build/code-templates/service/test/%%%service_name_lower_case%%%-service-test.js: -------------------------------------------------------------------------------- 1 | require('%%%spludo_directory%%%/core'); 2 | 3 | var vows = require("vows"); 4 | var assert = require("assert"); 5 | 6 | vows.describe("services").addBatch({ 7 | 8 | "%%%service_name%%%Service": { 9 | 10 | 'topic': function() { 11 | bootstrap_manager.whenLoaded(this.callback); 12 | }, 13 | 14 | createUpdateDelete: { 15 | 'topic': function() { 16 | var that = this; 17 | var %%%service_name_lower_case%%%_service = service_manager.get('%%%service_name%%%'); 18 | 19 | var test_data_key = 'name'; 20 | var test_data_key_getter = 'getName'; 21 | var test_data_initial_value = 'MyName'; 22 | var test_data_updated_value = 'MyUpdatedName'; 23 | 24 | var %%%service_name_lower_case%%%_id = null; 25 | var %%%service_name_lower_case%%% = null; 26 | chain(function(chain_cb) { 27 | /* 28 | * Let's create a new %%%service_name_lower_case%%% 29 | */ 30 | var creation_parameters = {}; 31 | creation_parameters[test_data_key] = test_data_initial_value; 32 | 33 | %%%service_name_lower_case%%%_service.create%%%service_name%%%(function(error, new_%%%service_name_lower_case%%%_id) { 34 | assert.equal(error, false); 35 | %%%service_name_lower_case%%%_id = new_%%%service_name_lower_case%%%_id; 36 | chain_cb(); 37 | }, creation_parameters); 38 | }, function(chain_cb) { 39 | /* 40 | * Now let's try to find the created one again 41 | */ 42 | %%%service_name_lower_case%%%_service.get%%%service_name%%%ById(function(found_%%%service_name_lower_case%%%) { 43 | assert.equal(typeof %%%service_name_lower_case%%% !== 'undefined', true); 44 | %%%service_name_lower_case%%% = found_%%%service_name_lower_case%%%; 45 | assert.equal(%%%service_name_lower_case%%%[test_data_key_getter](), test_data_initial_value); 46 | chain_cb(); 47 | }, %%%service_name_lower_case%%%_id); 48 | }, function(chain_cb) { 49 | /* 50 | * Now let's update it 51 | */ 52 | var update_parameters = {}; 53 | update_parameters[test_data_key] = test_data_updated_value; 54 | 55 | %%%service_name_lower_case%%%_service.update%%%service_name%%%(function(error, affected_rows) { 56 | assert.equal(error, false); 57 | assert.equal(affected_rows, 1); 58 | chain_cb(); 59 | }, %%%service_name_lower_case%%%, update_parameters); 60 | }, function(chain_cb) { 61 | /* 62 | * Now let's see if we find that row again, with new 63 | * values 64 | */ 65 | %%%service_name_lower_case%%%_service.get%%%service_name%%%ById(function(found_%%%service_name_lower_case%%%) { 66 | assert.equal(typeof found_%%%service_name_lower_case%%% !== 'undefined', true); 67 | assert.equal(found_%%%service_name_lower_case%%%[test_data_key_getter](), test_data_updated_value); 68 | chain_cb(); 69 | }, %%%service_name_lower_case%%%_id); 70 | }, function(chain_cb) { 71 | /* 72 | * Let's remove the created one now! 73 | */ 74 | %%%service_name_lower_case%%%_service.delete%%%service_name%%%(function(error, affected_rows) { 75 | assert.equal(error, false); 76 | assert.equal(affected_rows, 1); 77 | chain_cb(); 78 | }, %%%service_name_lower_case%%%); 79 | }, function(chain_cb) { 80 | /* 81 | * Now let's see if it's removed! 82 | */ 83 | %%%service_name_lower_case%%%_service.get%%%service_name%%%ById(function(found_%%%service_name_lower_case%%%) { 84 | assert.equal(typeof found_%%%service_name_lower_case%%%, 'undefined'); 85 | chain_cb(); 86 | }, %%%service_name_lower_case%%%_id); 87 | }, function(chain_cb) { 88 | /* 89 | * Now let's see if an update doesn't update anything 90 | */ 91 | var update_parameters = {}; 92 | update_parameters[test_data_key] = test_data_initial_value; 93 | 94 | %%%service_name_lower_case%%%_service.update%%%service_name%%%(that.callback, %%%service_name_lower_case%%%, update_parameters); 95 | }); 96 | }, 97 | 'was the removal successful': function(error, affected_rows) { 98 | assert.isTrue(!error); 99 | assert.equal(affected_rows, 0); 100 | } 101 | } 102 | } 103 | }).export(module); -------------------------------------------------------------------------------- /build/core_dev_change_version_number.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file is just for spludo core development. 4 | # usage: (changes 1.0.3-dev to 1.0.4-dev) 5 | # ./build/core_dev_change_version_number.bash 1.0.3-dev 1.0.4-dev 6 | 7 | cd `dirname $0` 8 | cd .. 9 | 10 | old_version="$1" 11 | new_version="$2" 12 | 13 | if [ "" == "$1" ]; 14 | then 15 | echo "Usage: ./build/core_dev_change_version_number.bash 1.0.3-dev 1.0.4-dev" 16 | exit 1 17 | fi 18 | 19 | if [ "" == "$2" ]; 20 | then 21 | echo "Usage: ./build/core_dev_change_version_number.bash 1.0.3-dev 1.0.4-dev" 22 | exit 1 23 | fi 24 | 25 | grep -R "$old_version" * | cut -f "1" -d ":" | while read line 26 | do 27 | sed -i'' "s/$old_version/$new_version/g" "$line" 28 | done 29 | -------------------------------------------------------------------------------- /build/generate_monit_config.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | appname="$1" 4 | port="$2" 5 | 6 | if [ -z "$appname" ] 7 | then 8 | echo "Missing application name!" 9 | echo "" 10 | echo "Correct usage:" 11 | echo " sudo $0 app_name port" 12 | echo "For Example:" 13 | echo " sudo $0 my_magic_app 3000" 14 | exit 1 15 | fi 16 | 17 | if [ -z "$port" ] 18 | then 19 | echo "Missing port!" 20 | echo "" 21 | echo "Correct usage:" 22 | echo " sudo $0 app_name port" 23 | echo "For Example:" 24 | echo " sudo $0 my_magic_app 3000" 25 | exit 1 26 | fi 27 | 28 | username=`ls -al | head -n 2 | tail -n "1" | tr -s " " | cut -f "3" -d " "` 29 | group=`ls -al | head -n 2 | tail -n "1" | tr -s " " | cut -f "4" -d " "` 30 | 31 | echo "Configuring for:" 32 | echo " application_name: $appname" 33 | echo " port: $port" 34 | echo " username: $username" 35 | echo " group: $group" 36 | 37 | upstartconf_name="/etc/init/$appname.conf" 38 | monitrc_name="/etc/monit/conf.d/$appname.monitrc" 39 | logfile_name="`pwd`/node.log" 40 | 41 | echo "Will generate:" 42 | echo " $monitrc_name" 43 | echo " $upstartconf_name" 44 | echo "The daemons output will be stored at:" 45 | echo " $logfile_name" 46 | 47 | echo "Press enter to continue ... (stop with CTRL+C)" 48 | read 49 | 50 | echo -n "" > $monitrc_name 51 | echo "check process $appname" >> $monitrc_name 52 | echo " with pidfile \"/var/run/$appname.pid\"" >> $monitrc_name 53 | echo " start program = \"/sbin/start $appname\"" >> $monitrc_name 54 | echo " stop program = \"/sbin/stop $appname\"" >> $monitrc_name 55 | echo " if 2 restarts within 3 cycles then timeout" >> $monitrc_name 56 | echo " if totalmem > 100 Mb then alert" >> $monitrc_name 57 | echo " if children > 255 for 5 cycles then stop" >> $monitrc_name 58 | echo " if cpu usage > 95% for 3 cycles then restart" >> $monitrc_name 59 | echo " if failed port $port protocol http" >> $monitrc_name 60 | echo " request /" >> $monitrc_name 61 | echo " with timeout 5 seconds" >> $monitrc_name 62 | echo " then restart" >> $monitrc_name 63 | 64 | echo "#!upstart" > $upstartconf_name 65 | echo "description \"$appname nodejs server\"" >> $upstartconf_name 66 | echo "author \"spludo generator\"" >> $upstartconf_name 67 | echo "" > $upstartconf_name 68 | echo "start on (local-filesystems and net-device-up IFACE=eth0)" >> $upstartconf_name 69 | echo "stop on runlevel 0" >> $upstartconf_name 70 | echo "" > $upstartconf_name 71 | echo "respawn # restart when job dies" >> $upstartconf_name 72 | echo "respawn limit 5 60 # give up restart after 5 respawns in 60 seconds" >> $upstartconf_name 73 | echo "" > $upstartconf_name 74 | echo "script " >> $upstartconf_name 75 | echo " exec start-stop-daemon --start --make-pidfile --pidfile /var/run/$appname.pid --chdir `pwd` --chuid $username:$group --exec /usr/local/bin/node run_server.js 2>&1 >> \"$logfile_name\"" >> $upstartconf_name 76 | echo "end script" >> $upstartconf_name 77 | 78 | echo "Finished. Please restart monit with /etc/init.d/monit restart" 79 | -------------------------------------------------------------------------------- /build/jshint.conf: -------------------------------------------------------------------------------- 1 | { 2 | "sub": true 3 | } 4 | -------------------------------------------------------------------------------- /build/jsl/jsl.conf: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the Spludo Framework. 3 | # Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | # 5 | # Licensed under the terms of MIT License. For the full copyright and license 6 | # information, please see the LICENSE file in the root folder. 7 | # 8 | 9 | # 10 | # This is Configuration File for JavaScript Lint 0.3.0 11 | # Javascript Lint is developed by Matthias Miller (http://www.JavaScriptLint.com) 12 | # 13 | 14 | ### Warnings 15 | # Enable or disable warnings based on requirements. 16 | # Use "+WarningName" to display or "-WarningName" to suppress. 17 | # 18 | +no_return_value # function {0} does not always return a value 19 | +duplicate_formal # duplicate formal argument {0} 20 | +equal_as_assign # test for equality (==) mistyped as assignment (=)?{0} 21 | +var_hides_arg # variable {0} hides argument 22 | +redeclared_var # redeclaration of {0} {1} 23 | +anon_no_return_value # anonymous function does not always return a value 24 | +missing_semicolon # missing semicolon 25 | +meaningless_block # meaningless block; curly braces have no impact 26 | +comma_separated_stmts # multiple statements separated by commas (use semicolons?) 27 | +unreachable_code # unreachable code 28 | +missing_break # missing break statement 29 | +missing_break_for_last_case # missing break statement for last case in switch 30 | +comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==) 31 | +inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement 32 | +useless_void # use of the void type may be unnecessary (void is always undefined) 33 | +multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs 34 | +use_of_label # use of label 35 | -block_without_braces # block statement without curly braces 36 | +leading_decimal_point # leading decimal point may indicate a number or an object member 37 | +trailing_decimal_point # trailing decimal point may indicate a number or an object member 38 | +octal_number # leading zeros make an octal number 39 | +nested_comment # nested comment 40 | +misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma 41 | +ambiguous_newline # unexpected end of line; it is ambiguous whether these lines are part of the same statement 42 | +empty_statement # empty statement or extra semicolon 43 | -missing_option_explicit # the "option explicit" control comment is missing 44 | +partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag 45 | +dup_option_explicit # duplicate "option explicit" control comment 46 | +useless_assign # useless assignment 47 | +ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity 48 | +ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent) 49 | +missing_default_case # missing default case in switch statement 50 | +duplicate_case_in_switch # duplicate case in switch statements 51 | +default_not_at_end # the default case is not at the end of the switch statement 52 | +legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax 53 | +jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax 54 | +useless_comparison # useless comparison; comparing identical expressions 55 | +with_statement # with statement hides undeclared variables; use temporary variable instead 56 | +trailing_comma_in_array # extra comma is not recommended in array initializers 57 | +assign_to_function_call # assignment to a function call 58 | +parseint_missing_radix # parseInt missing radix parameter 59 | 60 | 61 | ### Output format 62 | # Customize the format of the error message. 63 | # __FILE__ indicates current file path 64 | # __FILENAME__ indicates current file name 65 | # __LINE__ indicates current line 66 | # __ERROR__ indicates error message 67 | # 68 | # Visual Studio syntax (default): 69 | +output-format __FILE__(__LINE__): __ERROR__ 70 | # Alternative syntax: 71 | #+output-format __FILE__:__LINE__: __ERROR__ 72 | 73 | 74 | ### Context 75 | # Show the in-line position of the error. 76 | # Use "+context" to display or "-context" to suppress. 77 | # 78 | +context 79 | 80 | 81 | ### Semicolons 82 | # By default, assignments of an anonymous function to a variable or 83 | # property (such as a function prototype) must be followed by a semicolon. 84 | # 85 | +lambda_assign_requires_semicolon 86 | 87 | 88 | ### Control Comments 89 | # Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for 90 | # the /*@keyword@*/ control comments and JScript conditional comments. (The latter is 91 | # enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason, 92 | # although legacy control comments are enabled by default for backward compatibility. 93 | # 94 | +legacy_control_comments 95 | 96 | 97 | ### JScript Function Extensions 98 | # JScript allows member functions to be defined like this: 99 | # function MyObj() { /*constructor*/ } 100 | # function MyObj.prototype.go() { /*member function*/ } 101 | # 102 | # It also allows events to be attached like this: 103 | # function window::onload() { /*init page*/ } 104 | # 105 | # This is a Microsoft-only JavaScript extension. Enable this setting to allow them. 106 | # 107 | -jscript_function_extensions 108 | 109 | 110 | ### Defining identifiers 111 | # By default, "option explicit" is enabled on a per-file basis. 112 | # To enable this for all files, use "+always_use_option_explicit" 113 | -always_use_option_explicit 114 | 115 | # Define certain identifiers of which the lint is not aware. 116 | # (Use this in conjunction with the "undeclared identifier" warning.) 117 | # 118 | # Common uses for webpages might be: 119 | #+define window 120 | #+define document 121 | 122 | 123 | ### Interactive 124 | # Prompt for a keystroke before exiting. 125 | #+pauseatend 126 | 127 | -------------------------------------------------------------------------------- /build/run_db_tasks.js: -------------------------------------------------------------------------------- 1 | require("./../core"); 2 | require("./tasks/db-tasks"); 3 | new ConsoleApplication({ 4 | "path": "db:" + process.argv[2] 5 | }).run(); 6 | -------------------------------------------------------------------------------- /build/run_dev_server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var child_process = require('child_process'); 10 | var fs = require("fs"); 11 | var sys = require("sys"); 12 | 13 | dev_server = { 14 | 15 | process: null, 16 | 17 | files: [], 18 | 19 | restarting: false, 20 | 21 | "restart": function() { 22 | this.restarting = true; 23 | sys.debug('DEVSERVER: Stopping server for restart'); 24 | this.process.kill(); 25 | }, 26 | 27 | "start": function() { 28 | var that = this; 29 | sys.debug('DEVSERVER: Starting server'); 30 | that.watchFiles(); 31 | 32 | this.process = child_process.spawn(process.argv[0], ['run_server.js']); 33 | 34 | this.process.stdout.addListener('data', function (data) { 35 | process.stdout.write(data); 36 | }); 37 | 38 | this.process.stderr.addListener('data', function (data) { 39 | sys.print(data); 40 | }); 41 | 42 | this.process.addListener('exit', function (code) { 43 | sys.debug('DEVSERVER: Child process exited: ' + code); 44 | this.process = null; 45 | if (that.restarting) { 46 | that.restarting = true; 47 | that.unwatchFiles(); 48 | that.start(); 49 | } 50 | }); 51 | 52 | }, 53 | 54 | "watchFiles": function() { 55 | var that = this; 56 | 57 | child_process.exec('find . | grep "\.js$"', function(error, stdout, stderr) { 58 | var files = stdout.trim().split("\n"); 59 | 60 | files.forEach(function(file) { 61 | that.files.push(file); 62 | fs.watchFile(file, {interval : 500}, function(curr, prev) { 63 | if (curr.mtime.valueOf() != prev.mtime.valueOf() || curr.ctime.valueOf() != prev.ctime.valueOf()) { 64 | sys.debug('DEVSERVER: Restarting because of changed file at ' + file); 65 | dev_server.restart(); 66 | } 67 | }); 68 | }); 69 | }); 70 | }, 71 | 72 | "unwatchFiles": function() { 73 | this.files.forEach(function(file) { 74 | fs.unwatchFile(file); 75 | }); 76 | this.files = []; 77 | } 78 | } 79 | 80 | 81 | dev_server.start(); 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /build/spludo-gen.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var sys = require("sys"); 10 | var fs = require("fs"); 11 | var child_process = require("child_process"); 12 | var path = require("path"); 13 | 14 | try { 15 | require("./../core"); 16 | } catch (error) { 17 | /* 18 | * Wasn't able to load core, so we are not in a spludo project 19 | * and thus can't use all features of spludo-gen 20 | */ 21 | } 22 | 23 | require("./../core/util"); 24 | 25 | var working_directory = process.cwd() + '/'; 26 | var spludo_directory = path.dirname(__dirname) + '/'; 27 | 28 | SpludoGenerator = {}; 29 | 30 | SpludoGenerator.code_templates = {}; 31 | 32 | SpludoGenerator.target_directory = working_directory; 33 | SpludoGenerator.working_directory = working_directory; 34 | 35 | SpludoGenerator.addCodeTemplate = function(name, options) { 36 | options.name = name; 37 | SpludoGenerator.code_templates[name] = options; 38 | }; 39 | 40 | SpludoGenerator.handleCodeTemplate = function(name) { 41 | var that = this; 42 | 43 | var code_template = this.code_templates[name]; 44 | var parameters = code_template.parameters; 45 | var parameters_length = parameters.length; 46 | 47 | var values_chain = []; 48 | var values = []; 49 | 50 | return function(cb) { 51 | for (var i = 0; i < parameters_length; i++) { 52 | (function(parameter) { 53 | var validate_paramter_handler = function(chain_cb) { 54 | 55 | var default_value = ""; 56 | var value = ""; 57 | 58 | chain(function(sub_chain_cb) { 59 | code_template.getParameterDefault(parameter.name)(function(err, parameter_default_value) { 60 | if (err) { 61 | default_value = ""; 62 | } else { 63 | default_value = parameter_default_value; 64 | } 65 | sub_chain_cb(); 66 | }); 67 | }, function(sub_chain_cb) { 68 | sys.print(parameter.caption); 69 | 70 | if (default_value !== "") { 71 | sys.print(" [" + default_value + "]"); 72 | } 73 | 74 | sys.print(': '); 75 | var st = process.openStdin(); 76 | 77 | var data_handler = function(input_value) { 78 | st.removeListener("data", data_handler); 79 | value = input_value.toString().split("\n")[0]; 80 | sub_chain_cb(); 81 | }; 82 | st.addListener("data", data_handler); 83 | }, function() { 84 | if (value === "") { 85 | value = default_value; 86 | } 87 | 88 | code_template.validateParameter(parameter.name, value, values)(function(err, valid_value) { 89 | if (err) { 90 | validate_paramter_handler(chain_cb); 91 | } else { 92 | values.push([parameter.name, valid_value]); 93 | chain_cb(); 94 | } 95 | }); 96 | }); 97 | 98 | }; 99 | 100 | values_chain.push(validate_paramter_handler); 101 | })(parameters[i]); 102 | } 103 | 104 | values_chain.push(function() { 105 | sys.puts(" "); 106 | sys.puts(" Working ... "); 107 | sys.puts(""); 108 | 109 | if (typeof code_template.preExecuteHook !== "undefined") { 110 | code_template.preExecuteHook(values)(function() { 111 | that.performCodeTemplate(__dirname + '/code-templates/' + code_template.name + '/', values)(function() { 112 | if (typeof code_template.postExecuteHook !== "undefined") { 113 | code_template.postExecuteHook(values)(function() { 114 | cb(); 115 | }); 116 | } else { 117 | cb(); 118 | } 119 | }); 120 | }); 121 | } else { 122 | that.performCodeTemplate(__dirname + '/code-templates/' + code_template.name + '/', values)(function() { 123 | if (typeof code_template.postExecuteHook !== "undefined") { 124 | code_template.postExecuteHook(values)(function() { 125 | cb(); 126 | }); 127 | } else { 128 | cb(); 129 | } 130 | }); 131 | } 132 | 133 | }); 134 | 135 | chain.apply(GLOBAL, values_chain); 136 | }; 137 | }; 138 | 139 | SpludoGenerator.performCodeTemplate = function(template_directory, values) { 140 | var that = this; 141 | 142 | values.push(['spludo_directory', spludo_directory]); 143 | 144 | var values_length = values.length; 145 | 146 | var token_replacer = function(input) { 147 | var output = input; 148 | 149 | for (var i = 0; i < values_length; i++) { 150 | output = output.split("%%%" + values[i][0] + "%%%").join(values[i][1]); 151 | } 152 | 153 | return output; 154 | }; 155 | 156 | var files = []; 157 | 158 | return function(cb) { 159 | 160 | chain(function(chain_cb) { 161 | child_process.exec("cd " + template_directory + " && find .", function(err, raw_files) { 162 | if (err) { 163 | /* 164 | * Ok, so now files! 165 | */ 166 | } else { 167 | var raw_files = raw_files.split("\n"); 168 | var raw_files_length = raw_files.length; 169 | for (var i = 0; i < raw_files_length; i++) { 170 | var file = raw_files[i].substr(2); 171 | if (file !== '') { 172 | files.push(file); 173 | } 174 | } 175 | } 176 | chain_cb(); 177 | }); 178 | }, function() { 179 | var files_length = files.length; 180 | for (var i = 0; i < files_length; i++) { 181 | var file = files[i]; 182 | var file_stat = fs.statSync(template_directory + file); 183 | 184 | if (file_stat.isDirectory()) { 185 | try { 186 | fs.mkdirSync(that.target_directory + token_replacer(file), 0755); 187 | sys.puts("Created folder: " + that.target_directory + token_replacer(file)); 188 | } catch (error) 189 | { 190 | sys.puts("Folder already exists: " + that.target_directory + token_replacer(file)); 191 | } 192 | } else { 193 | if (path.extname(file) === '.jpg' || path.extname(file) === '.png') { 194 | var raw_file_contents = fs.readFileSync(template_directory + file, 'binary').toString(); 195 | fs.writeFileSync(that.target_directory + token_replacer(file), raw_file_contents, 'binary'); 196 | sys.puts("Created (binary) file: " + that.target_directory + token_replacer(file)); 197 | } else { 198 | var raw_file_contents = fs.readFileSync(template_directory + file).toString(); 199 | fs.writeFileSync(that.target_directory + token_replacer(file), token_replacer(raw_file_contents)); 200 | sys.puts("Created file: " + that.target_directory + token_replacer(file)); 201 | } 202 | } 203 | } 204 | 205 | sys.puts(" "); 206 | sys.puts(" Finished!"); 207 | sys.puts(""); 208 | 209 | cb(); 210 | }); 211 | }; 212 | }; 213 | 214 | require("./code-templates/core-code-templates"); 215 | require("./code-templates/db-code-templates"); 216 | 217 | var code_template = process.argv[2]; 218 | 219 | sys.puts(" "); 220 | sys.puts(" Spludo Generator - https://github.com/DracoBlue/spludo"); 221 | sys.puts(" "); 222 | 223 | if (!code_template || code_template === "help") { 224 | for (var name in SpludoGenerator.code_templates) { 225 | sys.puts(" $ spludo-gen " + name); 226 | sys.puts(" " + SpludoGenerator.code_templates[name].description); 227 | sys.puts(" "); 228 | } 229 | process.exit(0); 230 | } 231 | 232 | sys.puts(" Template: " + code_template); 233 | sys.puts(""); 234 | 235 | 236 | if (typeof SpludoGenerator.code_templates[code_template] === "undefined") { 237 | sys.puts(" Error: Unsupported code template: " + code_template); 238 | process.exit(1); 239 | } 240 | 241 | SpludoGenerator.handleCodeTemplate(code_template)(function() { 242 | process.exit(0); 243 | }); 244 | -------------------------------------------------------------------------------- /build/tasks/build-db.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Spludo Core Database Build-Targets, should be included in a project specific build.xml! 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /build/tasks/db-tasks.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2011 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var forEachDatabaseMigration = function(sub_cb) { 10 | return function(cb) { 11 | var migration_chain = []; 12 | var database_connection_configs = config.get('database_connections'); 13 | for (var key in database_connection_configs) { 14 | (function(database_name, database_config) { 15 | migration_chain.push(function(chain_cb) { 16 | var migration = new DatabaseMigration(database_name); 17 | sub_cb(database_name, migration, chain_cb); 18 | }) 19 | })(key, database_connection_configs[key]); 20 | }; 21 | 22 | migration_chain.push(function(chain_cb) { 23 | cb(); 24 | }); 25 | 26 | chain.apply(GLOBAL, migration_chain); 27 | }; 28 | }; 29 | 30 | new Controller("db:migrate", { 31 | "execute": function(params, context) { 32 | return forEachDatabaseMigration(function(database_name, migration, chain_cb) { 33 | console.log('Migrating: ' + database_name); 34 | 35 | var doMigrateAll = function() { 36 | migration.migrateAll()(function() { 37 | chain_cb(); 38 | }); 39 | }; 40 | 41 | migration.connection.selectTableRows("executed_migrations")(function(error, migrated_version_rows) { 42 | if (error) { 43 | /* 44 | * We have no structure, yet: 45 | */ 46 | console.log(' > Not setup yet, loading structure first.'); 47 | migration.loadStructureDump()(function() { 48 | console.log(' ... done!'); 49 | doMigrateAll(); 50 | }); 51 | } else { 52 | /* 53 | * Structure is there, let's just migrate it! 54 | */ 55 | doMigrateAll(); 56 | } 57 | }); 58 | }); 59 | } 60 | }); 61 | 62 | new Controller("db:rollback", { 63 | "execute": function(params, context) { 64 | return forEachDatabaseMigration(function(database_name, migration, chain_cb) { 65 | console.log('Downgrading: ' + database_name); 66 | migration.downgradeAll()(function() { 67 | chain_cb(); 68 | }); 69 | }); 70 | } 71 | }); 72 | 73 | new Controller("db:structure:dump", { 74 | "execute": function(params, context) { 75 | return forEachDatabaseMigration(function(database_name, migration, chain_cb) { 76 | console.log('Dumping: ' + database_name); 77 | migration.createStructureDump()(function() { 78 | chain_cb(); 79 | }); 80 | }); 81 | } 82 | }); 83 | 84 | new Controller("db:structure:load", { 85 | "execute": function(params, context) { 86 | return forEachDatabaseMigration(function(database_name, migration, chain_cb) { 87 | console.log('Loading: ' + database_name); 88 | migration.loadStructureDump()(function() { 89 | chain_cb(); 90 | }); 91 | }); 92 | } 93 | }); 94 | 95 | new Controller("db:structure:reset", { 96 | "execute": function(params, context) { 97 | return forEachDatabaseMigration(function(database_name, migration, chain_cb) { 98 | console.log('Resetting structure dump for: ' + database_name); 99 | migration.removeStructureDump()(function() { 100 | chain_cb(); 101 | }); 102 | }); 103 | } 104 | }); 105 | 106 | new Controller("db:fixtures:dump", { 107 | "execute": function(params, context) { 108 | return forEachDatabaseMigration(function(database_name, migration, chain_cb) { 109 | console.log('Dumping: ' + database_name); 110 | migration.createFixturesDump()(function() { 111 | chain_cb(); 112 | }); 113 | }); 114 | } 115 | }); 116 | 117 | new Controller("db:fixtures:load", { 118 | "execute": function(params, context) { 119 | return forEachDatabaseMigration(function(database_name, migration, chain_cb) { 120 | console.log('Loading: ' + database_name); 121 | migration.loadFixturesDump()(function() { 122 | chain_cb(); 123 | }); 124 | }); 125 | } 126 | }); 127 | 128 | new Controller("db:fixtures:reset", { 129 | "execute": function(params, context) { 130 | return forEachDatabaseMigration(function(database_name, migration, chain_cb) { 131 | console.log('Resetting: ' + database_name); 132 | migration.removeFixturesDump()(function() { 133 | chain_cb(); 134 | }); 135 | }); 136 | } 137 | }); -------------------------------------------------------------------------------- /core/ApiServiceController.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2011 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var inflection = require('inflection'); 10 | 11 | /** 12 | * @class A new REST web service controller 13 | */ 14 | ApiServiceController = function(base_path, service_class, options) { 15 | options = options || {}; 16 | var singular = options.override_singular || {}; 17 | 18 | var callServiceMethod = function(cb, method_name, parameters, context) { 19 | if (typeof service_class[method_name] !== 'function') { 20 | context.status = 403; 21 | cb(JSON.stringify({ 22 | 'status': false, 23 | 'error': 'Method not found!' 24 | })); 25 | } else { 26 | parameters.unshift(function(status_code, result) { 27 | if (status_code && status_code !== 200) { 28 | context.status = status_code; 29 | cb(JSON.stringify({ 30 | 'status': false, 31 | 'error': result || '' 32 | })); 33 | } else { 34 | context.status = 200; 35 | cb(JSON.stringify({ 36 | 'status': true, 37 | 'data': result 38 | })); 39 | } 40 | }); 41 | service_class[method_name].apply(service_class, parameters); 42 | } 43 | }; 44 | 45 | new Controller(new RegExp('^' + base_path + '/(.+)/(.+)$'), { 46 | 'execute': function(params, context) { 47 | return function(cb) { 48 | var request_method = (context.params['_method'] || context.request.method).toUpperCase(); 49 | if ([ 50 | 'PUT', 'GET', 'DELETE' 51 | ].indexOf(request_method) === -1) { 52 | context.status = 405; 53 | context.headers['Allow'] = 'PUT, GET, DELETE'; 54 | cb(JSON.stringify({ 55 | 'status': false, 56 | 'error': 'Unsupported Request Method' 57 | })); 58 | return; 59 | } 60 | var entities_name = params[1]; 61 | var entity_id = params[2]; 62 | if (typeof singular[entities_name] === 'undefined') { 63 | singular[entities_name] = inflection.singularize(entities_name) || entities_name; 64 | } 65 | var entity_name = singular[entities_name]; 66 | 67 | var method_name = request_method.toLowerCase() + entity_name.substr(0, 1).toUpperCase() + entity_name.substr(1); 68 | callServiceMethod(cb, method_name, [ 69 | entity_id, context.params, context 70 | ], context); 71 | }; 72 | } 73 | }); 74 | 75 | new Controller(new RegExp('^' + base_path + '/(.+)/$'), { 76 | 'execute': function(params, context) { 77 | return function(cb) { 78 | var request_method = (context.params['_method'] || context.request.method).toUpperCase(); 79 | if (request_method === 'POST') { 80 | var entities_name = params[1]; 81 | if (typeof singular[entities_name] === 'undefined') { 82 | singular[entities_name] = inflection.singularize(entities_name) || entities_name; 83 | } 84 | var entity_name = singular[entities_name]; 85 | var method_name = request_method.toLowerCase() + entity_name.substr(0, 1).toUpperCase() + entity_name.substr(1); 86 | callServiceMethod(cb, method_name, [ 87 | context.params, context 88 | ], context); 89 | } else if (request_method === 'GET') { 90 | var entities_name = params[1]; 91 | var method_name = request_method.toLowerCase() + entities_name.substr(0, 1).toUpperCase() + entities_name.substr(1); 92 | callServiceMethod(cb, method_name, [ 93 | context.params, context 94 | ], context); 95 | } else { 96 | context.status = 405; 97 | context.headers['Allow'] = 'POST'; 98 | cb(JSON.stringify({ 99 | 'status': false, 100 | 'error': 'Unsupported Request Method' 101 | })); 102 | } 103 | }; 104 | } 105 | }); 106 | }; 107 | -------------------------------------------------------------------------------- /core/BaseApplication.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class Is a base application (should be extended). 11 | * 12 | * @extends Options 13 | * @extends Logging 14 | */ 15 | BaseApplication = function(options) { 16 | this.setOptions(options); 17 | if (typeof this.run !== "function") { 18 | throw new Error("Implement the .run method!"); 19 | } 20 | }; 21 | 22 | extend(true, BaseApplication.prototype, Options.prototype, Logging.prototype); 23 | 24 | BaseApplication.prototype.logging_prefix = 'BaseApplication'; 25 | 26 | /** 27 | * Runs the application. 28 | */ 29 | BaseApplication.prototype.run = function() { 30 | throw new Error("run method not implemented!"); 31 | }; 32 | 33 | /** 34 | * Runs a specific path 35 | */ 36 | BaseApplication.executePath = function(path, context, inner) { 37 | context = context || {}; 38 | inner = inner || null; 39 | 40 | var controller = controller_manager.getController(path); 41 | 42 | return function(cb) { 43 | var response = ""; 44 | 45 | chain(function(chain_cb) { 46 | controller[0].execute(controller[1], context)(function(chain_response) { 47 | response = chain_response || inner; 48 | chain_cb(); 49 | }); 50 | }, function(chain_cb) { 51 | if (typeof context.view_name !== "undefined") { 52 | /* 53 | * We need the view manager, since the view-name is set! 54 | */ 55 | var view = view_manager.getView(context.view_name); 56 | view.render(controller[1], context, response)(function(chain_response) { 57 | response = chain_response; 58 | chain_cb(); 59 | }); 60 | } else { 61 | chain_cb(); 62 | } 63 | }, function(chain_cb) { 64 | if (typeof context.layout_name !== "undefined") { 65 | /* 66 | * We need the view manager, since the view-name is set! 67 | */ 68 | var layout_view = view_manager.getView(context.layout_name); 69 | layout_view.render(controller[1], context, response)(function(chain_response) { 70 | response = chain_response; 71 | chain_cb(); 72 | }); 73 | } else { 74 | chain_cb(); 75 | } 76 | }, function() { 77 | cb(response); 78 | }); 79 | }; 80 | }; 81 | -------------------------------------------------------------------------------- /core/BootstrapManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var events = require("events"); 10 | 11 | /** 12 | * @class The bootstrap manager. Register to it's end-event, before starting any action on the application! 13 | * 14 | * @extends Logging 15 | */ 16 | BootstrapManager = function() { 17 | var that = this; 18 | 19 | this.mandatory_elements_missing_count = 0; 20 | this.mandatory_elements_missing = {}; 21 | this.token_name_map = {}; 22 | this.name_token_map = {}; 23 | 24 | this.taken_tokens = {}; 25 | 26 | this.event_emitter = new events.EventEmitter(); 27 | 28 | this.is_loaded = false; 29 | 30 | this.loaded_listeners = []; 31 | 32 | this.event_emitter.addListener("finishPart", function() { 33 | that.trace("finishPart"); 34 | that.mandatory_elements_missing_count--; 35 | 36 | that.trace("finishPart", "there are " + that.mandatory_elements_missing_count + " to go"); 37 | if (that.mandatory_elements_missing_count === 0) { 38 | that.info("All necessary parts loaded."); 39 | that.event_emitter.emit("end"); 40 | that.is_loaded = true; 41 | that.loaded_listeners.forEach(function(listener) { 42 | process.nextTick(function() { 43 | listener(); 44 | }); 45 | }); 46 | that.loaded_listeners = []; 47 | } 48 | }); 49 | }; 50 | 51 | extend(true, BootstrapManager.prototype, Logging.prototype); 52 | 53 | BootstrapManager.prototype.logging_prefix = 'BootstrapManager'; 54 | 55 | BootstrapManager.prototype.createToken = function() { 56 | this.trace("createToken", arguments); 57 | 58 | var token = false; 59 | 60 | while (!token) { 61 | token = Math.floor(Math.random()*999999999).toString(); 62 | 63 | if (typeof this.taken_tokens[token] !== "undefined") { 64 | token = false; 65 | } else { 66 | this.taken_tokens[token] = true; 67 | } 68 | } 69 | 70 | return token; 71 | }; 72 | 73 | BootstrapManager.prototype.createMandatoryElement = function(name) { 74 | this.trace("createMandatoryElement", arguments); 75 | var token = this.createToken(); 76 | 77 | this.mandatory_elements_missing[token] = true; 78 | this.mandatory_elements_missing_count++; 79 | 80 | this.token_name_map[token] = name; 81 | this.name_token_map[name] = token; 82 | 83 | return token; 84 | }; 85 | 86 | BootstrapManager.prototype.finishMandatoryElement = function(token) { 87 | this.trace("finishMandatoryElement", arguments); 88 | if (typeof this.mandatory_elements_missing[token] === "undefined") { 89 | throw new Error("Cannot finish mandatory element for token " + token + ", because it is not missing."); 90 | } 91 | 92 | delete this.mandatory_elements_missing[token]; 93 | 94 | this.event_emitter.emit("finishPart", this.token_name_map[token], token); 95 | }; 96 | 97 | BootstrapManager.prototype.whenReady = function(parts, callback) { 98 | this.trace("whenReady", arguments); 99 | 100 | var that = this; 101 | 102 | var check_if_ready_timer = null; 103 | 104 | var parts_length = parts.length; 105 | var checkIfReadyHandler = function() { 106 | var tokens_missing = 0; 107 | var parts_waiting = 0; 108 | for (var i=0; i// Initializing a new context is very simple, just create a new object. 41 | * var context = {}; 42 | * // now let's do some magic on that context! 43 | * ContextToolkit.setCookie(context, "key", "value"); 44 | * // and so on .. 45 | * 46 | */ 47 | Context = function() { 48 | return {}; 49 | }; -------------------------------------------------------------------------------- /core/ContextToolkit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class A toolkit for convenient functions to work on the context. 11 | */ 12 | ContextToolkit = { 13 | 14 | /** 15 | * Set a cookie. 16 | * 17 | * @param {Context} 18 | * context Specifies the context to set this cookie for. 19 | * @param {String} 20 | * key Set the key for the cookie. Must be a string. 21 | * @param {String|Object|Array|Number} 22 | * value Set value of the cookie. May even be an Object. Will be 23 | * encoded to JSON. 24 | * @param {Number} 25 | * [life_time=null] Set the amount of seconds this cookie is 26 | * meant to be alive. Can be 0 to indicate that this is set for 27 | * the lifetime of the browser session only. 28 | * @param {String} 29 | * [path=null] Set the path for the cookie. 30 | */ 31 | setCookie: function(context, key, value, life_time, path) { 32 | context.cookies = context.cookies || {}; 33 | context.cookies_path = context.cookies_path || {}; 34 | context.cookies_life_time = context.cookies_life_time || {}; 35 | context.cookies[key] = value; 36 | 37 | /* 38 | * If this is a clean cookie, we have to remove it from clean cookies, 39 | * so we don't forget to send it! 40 | */ 41 | if (context.clean_cookies && context.clean_cookies[key]) { 42 | delete context.clean_cookies[key]; 43 | } 44 | 45 | if (typeof life_time !== "undefined") { 46 | if (life_time !== null) { 47 | if (life_time === 0) { 48 | /* 49 | * This is not a browser session cookie 50 | */ 51 | context.cookies_life_time[key] = "0"; 52 | } else { 53 | if (life_time < 0) { 54 | /* 55 | * That cookie expired already! 56 | */ 57 | context.cookies_life_time[key] = "Tue, 23-May-1985 17:05:20 GMT"; 58 | } else { 59 | /* 60 | * That cookie expired already! 61 | */ 62 | var life_time_date = new Date(); 63 | 64 | /* 65 | * Now let's calculate the difference to the current 66 | * date. 67 | */ 68 | life_time_date.setTime(life_time_date.getTime() + life_time); 69 | 70 | /* 71 | * This is not 100% "%a, %d-%b-%Y %T GMT" because it has 72 | * GMT-600 for instance, but it seems to work. 73 | */ 74 | context.cookies_life_time[key] = life_time_date.toString(); 75 | } 76 | } 77 | } 78 | 79 | if (typeof path !== "undefined") { 80 | context.cookies_path[key] = path; 81 | } 82 | } 83 | }, 84 | 85 | /** 86 | * Remove a cookie. This is achieved by setting the lifetime to -1. 87 | * 88 | * @param {Context} 89 | * context Specifies the context to remove this cookie from. 90 | * @param {String} 91 | * key Set the key for the cookie. Must be a string. 92 | * @param {String} 93 | * [path=null] Set the path for the cookie. 94 | */ 95 | removeCookie: function(context, key, path) { 96 | this.setCookie(context, key, "", -1, path); 97 | }, 98 | 99 | /** 100 | * Apply the request-headers to a context. This will for instance set the 101 | * Context#clean_cookies and Context#cookies property. 102 | * 103 | * @param {Context} 104 | * context The context for the operation. 105 | * @param {Object} 106 | * headers The request headers (usually taken from 107 | * http.ServerRequest.headers) 108 | */ 109 | applyRequestHeaders: function(context, headers) { 110 | context.request_headers = headers; 111 | 112 | /* 113 | * If we have no request cookie, we don't need this stuff. 114 | */ 115 | if (typeof headers["cookie"] === "undefined") { 116 | return; 117 | } 118 | 119 | context.clean_cookies = context.clean_cookies || {}; 120 | context.cookies = context.cookies || {}; 121 | var raw = headers["cookie"].split("; "); 122 | 123 | var cookie_key = null; 124 | var cookie_value = null; 125 | 126 | for (var i in raw) { 127 | /* 128 | * Transform (raw_line): test1=%7B%22key%22%3A%22value%22%7D To 129 | * (cookie_key: cookie_value): "test1": { "key": "value" } 130 | */ 131 | var raw_line = raw[i].split("="); 132 | try { 133 | cookie_key = decodeURIComponent(raw_line[0]); 134 | cookie_value = raw_line[1] || ""; 135 | cookie_value = decodeURIComponent(raw_line.splice(1).join("=")); 136 | 137 | context.clean_cookies[cookie_key] = JSON.parse(cookie_value, true); 138 | context.cookies[cookie_key] = context.clean_cookies[cookie_key]; 139 | } catch (e) { 140 | /* 141 | * If there is an exception with an item -> ignore. 142 | */ 143 | } 144 | } 145 | }, 146 | 147 | /** 148 | * Apply the cookies, which are currently available on the context, to the 149 | * correct headers. This is usually triggered by the application, which 150 | * delivers the response for the context. 151 | * 152 | * @param {Context} 153 | * context The context for the operation. 154 | */ 155 | applyCookiesToHeaders: function(context) { 156 | if (!context || !context.cookies) { 157 | 158 | /* 159 | * Ok, we have nothing in the cookies, let's remove the 160 | * headers['Set-Cookie'] (if it exists) 161 | */ 162 | if (context && context.headers) { 163 | delete context.headers["Set-Cookie"]; 164 | } 165 | 166 | return; 167 | } 168 | 169 | context = context || {}; 170 | context.headers = context.headers || {}; 171 | context.headers["Set-Cookie"] = []; 172 | 173 | var cookies = context.cookies; 174 | 175 | var set_cookie_headers = []; 176 | 177 | for (var key in cookies) { 178 | /* 179 | * We did not received that cookie as request cookie, or at least 180 | * changed it afterwards. 181 | */ 182 | if (!context.clean_cookies || typeof context.clean_cookies[key] === "undefined") { 183 | 184 | var set_cookie_string = encodeURIComponent(key) + "="; 185 | set_cookie_string = set_cookie_string + encodeURIComponent(JSON.stringify(cookies[key])); 186 | 187 | if (typeof context.cookies_life_time[key] !== "undefined") { 188 | set_cookie_string = set_cookie_string + "; expires=" + context.cookies_life_time[key]; 189 | } 190 | if (typeof context.cookies_path[key] !== "undefined") { 191 | set_cookie_string = set_cookie_string + "; path=" + context.cookies_path[key]; 192 | } 193 | set_cookie_headers.push(set_cookie_string); 194 | } 195 | } 196 | 197 | /* 198 | * Even though http://tools.ietf.org/html/rfc2109#page-4 says that it's 199 | * possible to seperate SetCookie with ',' it does not work. So we are 200 | * doing this evil evil hack. 201 | * 202 | * Yes I am serious. This: .join("\nSet-Cookie: ") is just for that 203 | * hack! :( 204 | */ 205 | context.headers["Set-Cookie"] = set_cookie_headers.join("\nSet-Cookie: "); 206 | 207 | }, 208 | 209 | /** 210 | * Redirect to a different url. 211 | * 212 | * @param {Context} 213 | * context The context for the operation. 214 | * @param {String} 215 | * path The path where to redirect to. 216 | */ 217 | applyRedirect: function(context, path) { 218 | context.status = 302; 219 | context.headers["Location"] = path; 220 | } 221 | }; 222 | -------------------------------------------------------------------------------- /core/Controller.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class A simple controller with either String or RegExp-Path 11 | */ 12 | Controller = function(path, options) { 13 | if (typeof options.execute === "function") { 14 | this.execute = options.execute; 15 | delete options.execute; 16 | } 17 | 18 | if (options.path) { 19 | throw new Error("The path for a controller cannot be set by using the options hash!"); 20 | } 21 | 22 | this.setOptions(options); 23 | this.options.path = path; 24 | 25 | controller_manager.addController(path, this); 26 | }; 27 | 28 | extend(true, Controller.prototype, Options.prototype, Logging.prototype); 29 | 30 | Controller.prototype.execute = function() { 31 | throw new Error("Implement execute-method me!"); 32 | }; 33 | -------------------------------------------------------------------------------- /core/ControllerManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var child_process = require("child_process"); 10 | 11 | /** 12 | * @class The manager for all registered controllers. 13 | * 14 | * @extends Logging 15 | */ 16 | ControllerManager = function() { 17 | this.controllers_string = {}; 18 | this.controllers_regexp = []; 19 | }; 20 | 21 | extend(true, ControllerManager.prototype, Logging.prototype); 22 | 23 | ControllerManager.prototype.logging_prefix = 'ControllerManager'; 24 | 25 | ControllerManager.prototype.addController = function(path, controller) { 26 | this.trace("addController", arguments); 27 | var plugin_name = this.current_plugin_name || null; 28 | 29 | if (path instanceof RegExp) { 30 | /* 31 | * Handle those pretty regexp objects as path! 32 | */ 33 | this.debug("addController", "type:RegExp, plugin:" + plugin_name + ", path:" + path); 34 | this.controllers_regexp = this.controllers_regexp || []; 35 | 36 | this.controllers_regexp.push( [ path, controller, plugin_name ]); 37 | 38 | return; 39 | } 40 | 41 | this.controllers_string = this.controllers_string || {}; 42 | 43 | if (this.controllers_string[path]) { 44 | throw new Error("Path already served by " + this.controllers[path]); 45 | } 46 | 47 | this.debug("addController", "type:String, plugin:" + plugin_name + ", path:" + path); 48 | this.controllers_string[path] = [ controller, plugin_name ]; 49 | }; 50 | 51 | ControllerManager.prototype.getController = function(path) { 52 | this.trace("getController", arguments); 53 | if (this.controllers_string[path]) { 54 | return [ this.controllers_string[path][0], [ path ], this.controllers_string[path][1] ]; 55 | } 56 | 57 | for (var i in this.controllers_regexp) { 58 | var match = String(path).match(this.controllers_regexp[i][0]); 59 | if (match) { 60 | return [ this.controllers_regexp[i][1], match, this.controllers_regexp[i][2] ]; 61 | } 62 | } 63 | 64 | throw new Error("Controller for path " + path + " not found!"); 65 | }; 66 | 67 | /** 68 | * Get all available controllers and load them ... . 69 | */ 70 | ControllerManager.prototype.loadControllers = function(path, plugin_name) { 71 | this.trace("loadControllers", arguments); 72 | var that = this; 73 | 74 | var bootstrap_token_name = "controllers"; 75 | 76 | if (plugin_name) { 77 | bootstrap_token_name = "plugin." + plugin_name + '.controllers'; 78 | } 79 | 80 | var bootstrap_token = bootstrap_manager.createMandatoryElement(bootstrap_token_name); 81 | var controller_files = []; 82 | try { 83 | child_process.exec("ls " + path + "controllers/*.js", function(err, stdout, stderr) { 84 | var files_in_folder = stdout.split("\n"); 85 | for (var i in files_in_folder) { 86 | if (files_in_folder[i] !== "") { 87 | controller_files.push(files_in_folder[i]); 88 | } 89 | } 90 | 91 | that.current_plugin_name = plugin_name; 92 | 93 | for (i in controller_files) { 94 | require(controller_files[i].substr(0, controller_files[i].length - 3)); 95 | } 96 | 97 | delete that.current_plugin_name; 98 | }); 99 | bootstrap_manager.finishMandatoryElement(bootstrap_token); 100 | } catch (e) { 101 | /* 102 | * controllers folder does not exist! 103 | */ 104 | this.log(e); 105 | bootstrap_manager.finishMandatoryElement(bootstrap_token); 106 | } 107 | 108 | 109 | }; 110 | 111 | -------------------------------------------------------------------------------- /core/DataMapper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The base for all data_mappers. 11 | * 12 | * @extends Options 13 | * @extends Logging 14 | */ 15 | DataMapper = function(name, options) { 16 | this.setOptions(options); 17 | 18 | this.encode = this.options.encode || this.encode; 19 | this.decode = this.options.decode || this.decode; 20 | 21 | this.encodeSync = this.options.encodeSync || this.encodeSync; 22 | this.decodeSync = this.options.decodeSync || this.decodeSync; 23 | 24 | data_mapper_manager.addDataMapper(name, this); 25 | }; 26 | 27 | extend(true, DataMapper.prototype, Options.prototype, Logging.prototype); 28 | 29 | DataMapper.prototype.encode = function(parameter, options) { 30 | throw new Error("Implement encode-method me!"); 31 | }; 32 | 33 | DataMapper.prototype.decode = function(parameter, options) { 34 | throw new Error("Implement decode-method me!"); 35 | }; 36 | 37 | DataMapper.prototype.encodeSync = function(parameter, options) { 38 | throw new Error("Implement encodeSync-method me!"); 39 | }; 40 | 41 | DataMapper.prototype.decodeSync = function(parameter, options) { 42 | throw new Error("Implement decodeSync-method me!"); 43 | }; 44 | -------------------------------------------------------------------------------- /core/DataMapperManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The manager for all registered data_mappers. 11 | * 12 | * @extends Logging 13 | */ 14 | DataMapperManager = function() { 15 | this.data_mappers = {}; 16 | }; 17 | 18 | extend(true, DataMapperManager.prototype, Logging.prototype); 19 | 20 | DataMapperManager.prototype.logging_prefix = 'DataMapperManager'; 21 | 22 | DataMapperManager.prototype.addDataMapper = function(name, data_mapper) { 23 | this.trace("addDataMapper", arguments); 24 | this.data_mappers[name] = data_mapper; 25 | }; 26 | 27 | DataMapperManager.prototype.getDataMapper = function(name) { 28 | if (this.data_mappers[name]) { 29 | return this.data_mappers[name]; 30 | } 31 | 32 | throw new Error("DataMapper for name " + name + " not found!"); 33 | }; 34 | 35 | DataMapperManager.prototype.shutdown = function() { 36 | this.trace("shutdown", arguments); 37 | for (var name in this.data_mappers) { 38 | /* 39 | * Check wether this data_mapper has a shutdown method. 40 | */ 41 | if (typeof this.data_mappers[name].shutdown === "function") { 42 | try { 43 | this.data_mappers[name].shutdown(); 44 | } catch (e) { 45 | this.warn("Exception when trying to shutdown data_mapper " + name); 46 | this.warn(e); 47 | } 48 | } 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /core/JsView.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class A simple JavascriptView, which needs a render function as parameter 11 | * for the real business logic. 12 | */ 13 | JsView = function(name, render_function) { 14 | if (typeof render_function === "function") { 15 | this.render = render_function; 16 | } 17 | view_manager.addView(name, this); 18 | }; 19 | 20 | JsView.prototype.render = function(params, context, inner) { 21 | throw new Error("Implement the .render method!"); 22 | }; 23 | -------------------------------------------------------------------------------- /core/Logging.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var sys = require('sys'); 10 | 11 | /** 12 | * The purpose of this pretty simple class is to make logging for classes easy 13 | * to handle. This class makes no sense if you use it directly but becomes 14 | * powerful if you extend an other class with it. 15 | * 16 | * @class Offers logging facility. 17 | */ 18 | Logging = function() { 19 | }; 20 | 21 | Logging.LEVEL_ALL = 127; 22 | Logging.LEVEL_TRACE = 6; 23 | Logging.LEVEL_LOG = 5; 24 | Logging.LEVEL_DEBUG = 5; 25 | Logging.LEVEL_INFO = 4; 26 | Logging.LEVEL_WARN = 3; 27 | Logging.LEVEL_ERROR = 2; 28 | Logging.LEVEL_FATAL = 1; 29 | Logging.LEVEL_OFF = 0; 30 | 31 | (function() { 32 | var log_configuration = config.get("logging", { 33 | "level": Logging.LEVEL_WARN 34 | }); 35 | 36 | log_configuration.hide_classes = ' ' + (log_configuration.hide_classes || []).join(' ') + ' '; 37 | 38 | /** 39 | * Logs with a specific prefix. Should not be used directly at all! 40 | * @private 41 | */ 42 | var logWithPrefix = function(log_level, log_prefix, args) { 43 | var message = "null"; 44 | if (log_configuration.hide_classes.indexOf(' ' + log_prefix + ' ') == '-1') { 45 | if (args.length > 1) { 46 | if (args.length === 2 && log_level === "TRACE" && (args[1][0] || args[1].length === 0)) { 47 | log_prefix = log_prefix + "." + args[0]; 48 | message = sys.inspect(args[1]); 49 | } else { 50 | message = sys.inspect(args); 51 | } 52 | } else if (args.length === 1) { 53 | message = (typeof args[0] === "string") ? args[0] : sys.inspect(args[0]); 54 | } 55 | 56 | sys.puts(log_level + ' [' + log_prefix + '] ' + message); 57 | } 58 | }; 59 | 60 | Logging.prototype.logging_prefix = 'Please configure: this.logging_prefix in this class!'; 61 | 62 | /** 63 | * Empty function which is just available to allow calling a log method without effect! 64 | * @private 65 | */ 66 | var doNotLog = function() { 67 | }; 68 | 69 | if (log_configuration.level >= Logging.LEVEL_TRACE) { 70 | Logging.prototype.trace = function() { 71 | logWithPrefix("TRACE", this.logging_prefix, arguments); 72 | }; 73 | Logging.prototype.addTracing = function(included_names, excluded_names) { 74 | var that = this; 75 | 76 | excluded_logging_names = ['trace', 'error', 'log', 'debug', 'warn', 'info', 'addTracing']; 77 | excluded_names = excluded_names || []; 78 | if (!included_names) 79 | { 80 | included_names = []; 81 | for (var key in this) 82 | { 83 | if (this.hasOwnProperty(key) && typeof this[key] === 'function' && excluded_names.indexOf(key) === -1) 84 | { 85 | if (excluded_logging_names.indexOf(key) === -1) 86 | { 87 | included_names.push(key); 88 | } 89 | } 90 | } 91 | } 92 | 93 | included_names.forEach(function(function_name) { 94 | var original_function = that[function_name]; 95 | that[function_name] = function() { 96 | that.trace(function_name, arguments); 97 | return original_function.apply(that, arguments); 98 | }; 99 | }); 100 | }; 101 | } else { 102 | Logging.prototype.trace = doNotLog; 103 | Logging.prototype.addTracing = doNotLog; 104 | } 105 | 106 | if (log_configuration.level >= Logging.LEVEL_DEBUG) { 107 | Logging.prototype.log = function() { 108 | logWithPrefix("LOG ", this.logging_prefix, arguments); 109 | }; 110 | Logging.prototype.debug = function() { 111 | logWithPrefix("DEBUG", this.logging_prefix, arguments); 112 | }; 113 | } else { 114 | Logging.prototype.debug = doNotLog; 115 | Logging.prototype.log = doNotLog; 116 | } 117 | 118 | if (log_configuration.level >= Logging.LEVEL_INFO) { 119 | Logging.prototype.info = function() { 120 | logWithPrefix("INFO ", this.logging_prefix, arguments); 121 | }; 122 | } else { 123 | Logging.prototype.info = doNotLog; 124 | } 125 | 126 | if (log_configuration.level >= Logging.LEVEL_WARN) { 127 | Logging.prototype.warn = function() { 128 | logWithPrefix("WARN ", this.logging_prefix, arguments); 129 | }; 130 | } else { 131 | Logging.prototype.warn = doNotLog; 132 | } 133 | 134 | if (log_configuration.level >= Logging.LEVEL_ERROR) { 135 | Logging.prototype.error = function() { 136 | logWithPrefix("ERROR", this.logging_prefix, arguments); 137 | }; 138 | } else { 139 | Logging.prototype.error = doNotLog; 140 | } 141 | 142 | if (log_configuration.level >= Logging.LEVEL_FATAL) { 143 | Logging.prototype.fatal = function() { 144 | logWithPrefix("FATAL", this.logging_prefix, arguments); 145 | }; 146 | } else { 147 | Logging.prototype.fatal = doNotLog; 148 | } 149 | })(); 150 | -------------------------------------------------------------------------------- /core/ObjectToolkit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class A toolkit for convenient functions on javascript objects. 11 | */ 12 | ObjectToolkit = { 13 | /** 14 | * This little method expands an object as long as the path needs to be 15 | * expanded to allow setting a property. 16 | * 17 | * So if you object is something like that: 18 | * 19 | *
 20 |      * a = {
 21 |      *     "b": {}
 22 |      * }
 23 |      * 
24 | * 25 | * and you want to create 26 | * 27 | *
 28 |      * a = {
 29 |      *     "b": {
 30 |      *         "c": {
 31 |      *             "d": "hallo"
 32 |      *         }
 33 |      *     }
 34 |      * }
 35 |      * 
36 | * 37 | * you would just have to use: 38 | * 39 | *
 40 |      * ObjectToolkit.setPathValue(a, "hallo", [
 41 |      *     "b", "c", "d"
 42 |      * ]);
 43 |      * 
44 | * 45 | * @param {Object} 46 | * [entity={}] The object to apply the path to. 47 | * @param {Object|String|Number|null} 48 | * value The value to apply to the path 49 | * @param {Array} 50 | * path The path to apply the value to. 51 | * 52 | * @return {Object} The modified (or newly created) object 53 | */ 54 | setPathValue: function(entity, path, value) { 55 | return this.rawSetPathValue(entity || {}, path, value, 0, path.length); 56 | }, 57 | 58 | /** 59 | * This little magic method does the opposite to ObjectToolkit.setPathValue. 60 | * 61 | * @param {Object} 62 | * [entity] The object to retrieve the path from. 63 | * @param {Array} 64 | * path The path to retrieve the value from. 65 | * 66 | * @return {Object|String|Number|null} The value or undefined 67 | */ 68 | getPathValue: function(entity, path) { 69 | return this.rawGetPathValue(entity, path, 0, path.length); 70 | }, 71 | 72 | /** 73 | * This magic little method expands an object as long as the path needs to 74 | * be expanded to allow setting a property. 75 | * 76 | * @private 77 | * 78 | * @param {Object} 79 | * [entity={}] The object to apply the path to. 80 | * @param {Object|String|Number|null} 81 | * value The value to apply to the path 82 | * @param {Array} 83 | * path The path to apply the value to. 84 | * @param {Number} 85 | * path_position The position in the path (for performance 86 | * reasons) 87 | * @param {Number} 88 | * path_length The lengths of the path (for performance reasons) 89 | * 90 | * @return {Object} The modified (or newly created) object 91 | */ 92 | rawSetPathValue: function(entity, path, value, path_position, path_length) { 93 | /* 94 | * It's the final one, let's set the value and we're done! 95 | */ 96 | if (path_position + 1 === path_length) { 97 | entity[path[path_position]] = value; 98 | } else { 99 | entity[path[path_position]] = this.rawSetPathValue(entity[path[path_position]] || {}, path, value, path_position + 1, 100 | path_length); 101 | } 102 | 103 | return entity; 104 | }, 105 | 106 | /** 107 | * This little magic method does the opposite to 108 | * ObjectToolkit.rawSetPathValue. 109 | * 110 | * @private 111 | * 112 | * @param {Object} 113 | * [entity] The object to retrieve the path from. 114 | * @param {Array} 115 | * path The path to retrieve the value from. 116 | * @param {Number} 117 | * path_position The position in the path (for performance 118 | * reasons) 119 | * @param {Number} 120 | * path_length The lengths of the path (for performance reasons) 121 | * 122 | * @return {Object|String|Number|null} The value or undefined 123 | */ 124 | rawGetPathValue: function(entity, path, path_position, path_length) { 125 | if (!entity) { 126 | return entity; 127 | } 128 | 129 | /* 130 | * It's the final one, let's retrieve the value and we're done! 131 | */ 132 | if (path_position + 1 === path_length) { 133 | return entity[path[path_position]]; 134 | } 135 | 136 | return this.rawGetPathValue(entity[path[path_position]], path, path_position + 1, path_length); 137 | } 138 | 139 | }; 140 | -------------------------------------------------------------------------------- /core/Options.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * The purpose of this pretty simple class is to make options for classes easy 11 | * to handle. This class makes no sense if you use it directly but becomes 12 | * powerful if you extend an other class with it. 13 | * 14 | * @class Offers a options-property and a setOptions method for convenient 15 | * definition and retrieval of options. 16 | */ 17 | Options = function() { 18 | /** 19 | * The options object. 20 | * 21 | * You may read a specific option with this.options[key]. 22 | */ 23 | this.options = {}; 24 | }; 25 | 26 | Options.prototype = { 27 | 28 | /** 29 | * This function extends the this.options with those new key-value pairs. 30 | * Will overwrite existing properties only if the key is the same. 31 | * 32 | * @param {Object} 33 | * options The new options. 34 | */ 35 | setOptions: function(options) { 36 | this.options = this.options || {}; 37 | 38 | if (typeof options === "undefined") { 39 | return; 40 | } 41 | 42 | for (var option_key in options) { 43 | this.options[option_key] = options[option_key]; 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /core/ServiceManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The service manager. 11 | * 12 | * @extends Logging 13 | */ 14 | ServiceManager = function() { 15 | var that = this; 16 | this.instances = {}; 17 | }; 18 | 19 | extend(true, ServiceManager.prototype, Logging.prototype); 20 | 21 | ServiceManager.prototype.logging_prefix = 'ServiceManager'; 22 | 23 | ServiceManager.prototype.get = function(key) { 24 | if (!this.instances[key]) { 25 | var path = config.get('services', {})[key]; 26 | if (typeof path === 'undefined') { 27 | this.instances[key] = require(config.get('core').application_path + 'lib/services/' + key + 'Service'); 28 | } else { 29 | this.instances[key] = require(path); 30 | } 31 | } 32 | return this.instances[key]; 33 | }; 34 | -------------------------------------------------------------------------------- /core/StringToolkit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @private 11 | */ 12 | var xml_special_to_escaped_one_map = { 13 | '&': '&', 14 | '"': '"', 15 | '<': '<', 16 | '>': '>' 17 | }; 18 | 19 | /** 20 | * @private 21 | */ 22 | var escaped_one_to_xml_special_map = { 23 | '&': '&', 24 | '"': '"', 25 | '<': '<', 26 | '>': '>' 27 | }; 28 | 29 | /** 30 | * @class A toolkit for convenient functions to de-/encode uri, html and so on. 31 | */ 32 | StringToolkit = { 33 | 34 | /** 35 | * Escapes the 4 special entities for xml with the proper xml entities. 36 | * 37 | * @param {String} string the input string 38 | * @returns {String} The escaped xml string 39 | */ 40 | encodeXml: function(string) { 41 | return string.replace(/([\&"<>])/g, function(str, item) { 42 | return xml_special_to_escaped_one_map[item]; 43 | }); 44 | }, 45 | 46 | /** 47 | * Unescapes the 4 special entities for xml. 48 | * 49 | * @param {String} string the encoded input string 50 | * @returns {String} The decoded xml string 51 | */ 52 | decodeXml: function(string) { 53 | return string.replace(/("|<|>|&)/g, function(str, item) { 54 | return escaped_one_to_xml_special_map[item]; 55 | }); 56 | } 57 | 58 | }; 59 | -------------------------------------------------------------------------------- /core/SyncController.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class A sync controller with either String or RegExp-Path 11 | * 12 | * @extends Controller 13 | */ 14 | SyncController = function(path, sync_function) { 15 | var options = [ 16 | path, { 17 | execute: function() { 18 | var args = arguments; 19 | return function(cb) { 20 | cb(sync_function.apply(this, args)); 21 | }; 22 | } 23 | } 24 | ]; 25 | Controller.prototype.constructor.apply(this, options); 26 | }; 27 | 28 | extend(true, SyncController.prototype, Controller.prototype); 29 | -------------------------------------------------------------------------------- /core/Validation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The validation, which can be filled with multiple validators and 11 | * finally validate the input. 12 | */ 13 | Validation = function() { 14 | this.validators = {}; 15 | 16 | /** 17 | * All values, which are already validated (since last execute-call). 18 | */ 19 | this.validated_values = {}; 20 | }; 21 | 22 | extend(true, Validation.prototype, Options.prototype, Logging.prototype); 23 | 24 | Validation.prototype.execute = function(values) { 25 | var that = this; 26 | 27 | return function(cb) { 28 | var errors = []; 29 | var validated_values = {}; 30 | 31 | var validation_group = []; 32 | 33 | var add_validator_group_element = function(key, validator) { 34 | validation_group.push(function(chain_cb) { 35 | validator.instance.execute(values[key], validator.options)(function(value_errors) { 36 | var value_errors_length = value_errors.length; 37 | 38 | if (value_errors_length === 0) { 39 | validated_values[key] = values[key]; 40 | } else { 41 | var value_error_messages = []; 42 | 43 | for (var m = 0; m < value_errors_length; m++) { 44 | var validator_message = validator.error_messages[value_errors[m]]; 45 | 46 | /* 47 | * If we have no validator message, let's try the 48 | * default. 49 | */ 50 | validator_message = validator_message || validator.error_messages[""]; 51 | 52 | if (validator_message) { 53 | value_error_messages.push(validator_message); 54 | } 55 | } 56 | 57 | errors.push([ 58 | key, value_errors, value_error_messages 59 | ]); 60 | } 61 | 62 | chain_cb(); 63 | }); 64 | }); 65 | }; 66 | 67 | for (var key in values) { 68 | var validators = that.validators[key]; 69 | if (validators) { 70 | var validators_length = validators.length; 71 | for (var v = 0; v < validators_length; v++) { 72 | add_validator_group_element(key, validators[v]); 73 | } 74 | } 75 | /* 76 | * If no validator is registered, this parameter won't be pushed to 77 | * validated_values. 78 | */ 79 | } 80 | 81 | group.apply(GLOBAL, validation_group)(function() { 82 | that.validated_values = validated_values; 83 | 84 | cb(errors); 85 | }); 86 | }; 87 | }; 88 | 89 | Validation.prototype.add = function(key, validator_name, options, error_messages) { 90 | this.validators[key] = this.validators[key] || []; 91 | this.validators[key].push({ 92 | "instance": validator_manager.getValidator(validator_name), 93 | "options": options || {}, 94 | "error_messages": error_messages || {} 95 | }); 96 | }; 97 | 98 | Validation.prototype.getValidatedValues = function() { 99 | return this.validated_values || {}; 100 | }; 101 | -------------------------------------------------------------------------------- /core/Validator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The base for all validators. 11 | */ 12 | Validator = function(name, options) { 13 | this.setOptions(options); 14 | 15 | this.execute = this.options.execute || this.execute; 16 | 17 | delete this.options.execute; 18 | 19 | validator_manager.addValidator(name, this); 20 | }; 21 | 22 | extend(true, Validator.prototype, Options.prototype, Logging.prototype); 23 | 24 | Validator.prototype.execute = function(parameter, options) { 25 | throw new Error("Implement execute-method me!"); 26 | }; 27 | -------------------------------------------------------------------------------- /core/ValidatorManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The manager for all registered validators. 11 | * 12 | * @extends Logging 13 | */ 14 | ValidatorManager = function() { 15 | this.validators = {}; 16 | }; 17 | 18 | extend(true, ValidatorManager.prototype, Logging.prototype); 19 | 20 | ValidatorManager.prototype.logging_prefix = 'ValidatorManager'; 21 | 22 | ValidatorManager.prototype.addValidator = function(name, validator) { 23 | this.trace("addValidator", arguments); 24 | this.validators[name] = validator; 25 | }; 26 | 27 | ValidatorManager.prototype.getValidator = function(name) { 28 | if (this.validators[name]) { 29 | return this.validators[name]; 30 | } 31 | 32 | throw new Error("Validator for name " + name + " not found!"); 33 | }; 34 | 35 | ValidatorManager.prototype.shutdown = function() { 36 | this.trace("shutdown", arguments); 37 | for (var name in this.validators) { 38 | /* 39 | * Check wether this validator has a shutdown method. 40 | */ 41 | if (typeof this.validators[name].shutdown === "function") { 42 | try { 43 | this.validators[name].shutdown(); 44 | } catch (e) { 45 | this.warn("Exception when trying to shutdown validator " + name); 46 | this.warn(e); 47 | } 48 | } 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /core/ViewManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var child_process = require('child_process'); 10 | 11 | /** 12 | * @class The manager for all registered views. 13 | * 14 | * @extends Logging 15 | */ 16 | ViewManager = function() { 17 | this.views = {}; 18 | 19 | this.view_engines = []; 20 | }; 21 | 22 | extend(true, ViewManager.prototype, Logging.prototype); 23 | 24 | ViewManager.prototype.logging_prefix = 'ViewManager'; 25 | 26 | ViewManager.prototype.addView = function(name, view, plugin_name) { 27 | this.trace('addView', arguments); 28 | this.views = this.views || {}; 29 | plugin_name = plugin_name || this.current_plugin_name; 30 | 31 | if (plugin_name) { 32 | this.views[plugin_name + "." + name] = view; 33 | if (typeof this.views[name] === "undefined") { 34 | this.views[name] = view; 35 | } 36 | } else { 37 | this.views[name] = view; 38 | } 39 | }; 40 | 41 | ViewManager.prototype.getView = function(name, plugin_name) { 42 | this.trace('getView', arguments); 43 | plugin_name = plugin_name || null; 44 | 45 | var view = null; 46 | 47 | if (plugin_name !== null) { 48 | view = this.views[plugin_name + "." + name] || this.views[name]; 49 | } else { 50 | view = this.views[name] || null; 51 | } 52 | 53 | if (view === null) { 54 | throw new Error("View not found for name " + name + " (plugin: " + (plugin_name || "") + ")!"); 55 | } 56 | 57 | return view; 58 | }; 59 | 60 | /** 61 | * Get all available views and load them ... . 62 | */ 63 | ViewManager.prototype.loadViews = function(path, plugin_name) { 64 | this.trace('loadViews', arguments); 65 | var that = this; 66 | 67 | var bootstrap_token_name = "views"; 68 | 69 | if (plugin_name) { 70 | bootstrap_token_name = "plugin." + plugin_name + '.views'; 71 | } 72 | 73 | var views_bootstrap_token = bootstrap_manager.createMandatoryElement(bootstrap_token_name); 74 | var views_bootstrap_parts = []; 75 | 76 | var js_bootstrap_token_name = bootstrap_token_name + '.js'; 77 | views_bootstrap_parts.push(js_bootstrap_token_name); 78 | 79 | var js_bootstrap_token = bootstrap_manager.createMandatoryElement(js_bootstrap_token_name); 80 | 81 | try { 82 | this.debug('loadViews',"loading views for file extension: js"); 83 | child_process.exec("cd " + path + "views && find . -name '*.js'", function(err, stdout, stderr) { 84 | var view_files = []; 85 | 86 | if (!err) { 87 | var files_in_folder = stdout.split("\n"); 88 | 89 | for (var i in files_in_folder) { 90 | var file = files_in_folder[i].substr(2); 91 | if (file !== "") { 92 | view_files.push(file); 93 | } 94 | } 95 | 96 | that.current_plugin_name = plugin_name; 97 | 98 | 99 | for (var m in view_files) { 100 | var view_file_name = view_files[m]; 101 | require(path + 'views/' + view_file_name); 102 | } 103 | 104 | delete that.current_plugin_name; 105 | } 106 | bootstrap_manager.finishMandatoryElement(js_bootstrap_token); 107 | }); 108 | } catch (e) { 109 | /* 110 | * views folder does not exist! 111 | */ 112 | bootstrap_manager.finishMandatoryElement(js_bootstrap_token); 113 | } 114 | 115 | this.view_engines.forEach(function(view_engine_options) { 116 | var file_extension = view_engine_options[0]; 117 | that.debug('loadViews',"loading views for file extension: " + file_extension); 118 | var engine = GLOBAL[view_engine_options[1]]; 119 | 120 | var part_bootstrap_token_name = bootstrap_token_name + '.' + file_extension; 121 | views_bootstrap_parts.push(part_bootstrap_token_name); 122 | 123 | var part_bootstrap_token = bootstrap_manager.createMandatoryElement(part_bootstrap_token_name); 124 | 125 | try { 126 | child_process.exec("cd " + path + "views && find . -name '*." + file_extension + "'", function(err, stdout, stderr) { 127 | var view_files = []; 128 | 129 | if (!err) { 130 | var files_in_folder = stdout.split("\n"); 131 | 132 | for (var i in files_in_folder) { 133 | var file = files_in_folder[i].substr(2); 134 | if (file !== "") { 135 | view_files.push(file); 136 | } 137 | } 138 | } 139 | 140 | that.current_plugin_name = plugin_name; 141 | 142 | for (var m in view_files) { 143 | var view_file_name = view_files[m]; 144 | view_name = view_file_name.substr(0, view_file_name.length - file_extension.length - 1); 145 | new engine(view_name, path + 'views/' + view_file_name); 146 | } 147 | 148 | delete that.current_plugin_name; 149 | 150 | bootstrap_manager.finishMandatoryElement(part_bootstrap_token); 151 | }); 152 | } catch (e) { 153 | /* 154 | * views folder does not exist! 155 | */ 156 | bootstrap_manager.finishMandatoryElement(part_bootstrap_token); 157 | } 158 | }); 159 | 160 | bootstrap_manager.whenReady(views_bootstrap_parts, function() { 161 | bootstrap_manager.finishMandatoryElement(views_bootstrap_token); 162 | }); 163 | }; 164 | 165 | /** 166 | * Add a new view engine by file pattern. 167 | */ 168 | ViewManager.prototype.addViewEngine = function(file_pattern, engine_name) { 169 | this.trace('addViewEngine', arguments); 170 | this.view_engines.push([ 171 | file_pattern, engine_name 172 | ]); 173 | }; 174 | -------------------------------------------------------------------------------- /core/console/ConsoleApplication.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class Runs the application exactly one time with a given path. 11 | * 12 | * @extends BaseApplication 13 | * 14 | * @param {Object} options Options to specify the behaviour 15 | * @param {String} options.path The initial path to launch (when {@link ConsoleApplication#run} gets called) 16 | */ 17 | ConsoleApplication = function(options) { 18 | this.setOptions(options); 19 | }; 20 | 21 | extend(true, ConsoleApplication.prototype, BaseApplication.prototype); 22 | 23 | ConsoleApplication.prototype.logging_prefix = 'ConsoleApplication'; 24 | 25 | var sys = require("sys"); 26 | /** 27 | * Runs the application. 28 | */ 29 | ConsoleApplication.prototype.run = function() { 30 | var that = this; 31 | var response = null; 32 | 33 | var finish_handler = function() { 34 | storage_manager.shutdown()(function() { 35 | /* 36 | * We need to call this, because for instance watchFile does not 37 | * allows the process to exit. 38 | * @see https://github.com/DracoBlue/spludo/issues/issue/1 39 | */ 40 | process.exit(0); 41 | }); 42 | }; 43 | 44 | bootstrap_manager.event_emitter.addListener('end', function() { 45 | try { 46 | BaseApplication.executePath(that.options["path"])(function (response) { 47 | if (response !== null) { 48 | sys.puts(response); 49 | } 50 | finish_handler(); 51 | }); 52 | } catch (e) { 53 | response = "Error\n" + (e.stack || e.message) + "\n\n"; 54 | response = response + "Arguments: " + sys.inspect(e['arguments']); 55 | sys.puts(response); 56 | finish_handler(); 57 | } 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /core/core-data-mappers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | new DataMapper("json", { 10 | "decodeSync": function (value, options) { 11 | var result = null; 12 | try { 13 | result = JSON.parse(value); 14 | } catch (e) { 15 | throw new Error("Could not decode JSON-String to object: " + e.message); 16 | } 17 | return result; 18 | }, 19 | 20 | "encodeSync": function (value, options) { 21 | return JSON.stringify(value); 22 | }, 23 | 24 | "decode": function (value, options) { 25 | return function(cb) { 26 | var result = null; 27 | try { 28 | result = JSON.parse(value); 29 | } catch (e) { 30 | cb(null, "Could not decode JSON-String to object: " + e.message); 31 | return ; 32 | } 33 | cb(result); 34 | }; 35 | }, 36 | 37 | "encode": function (value, options) { 38 | return function(cb) { 39 | cb(JSON.stringify(value)); 40 | }; 41 | } 42 | }); 43 | 44 | new DataMapper("string", { 45 | "decodeSync": function (value, options) { 46 | return value; 47 | }, 48 | 49 | "encodeSync": function (value, options) { 50 | return String(value); 51 | }, 52 | 53 | "decode": function (value, options) { 54 | return function(cb) { 55 | cb(value); 56 | }; 57 | }, 58 | 59 | "encode": function (value, options) { 60 | return function(cb) { 61 | cb(String(value)); 62 | }; 63 | } 64 | }); 65 | 66 | new DataMapper("number", { 67 | "decodeSync": function (value, options) { 68 | return Number(value); 69 | }, 70 | 71 | "encodeSync": function (value, options) { 72 | return String(value); 73 | }, 74 | 75 | "decode": function (value, options) { 76 | return function(cb) { 77 | cb(Number(value)); 78 | }; 79 | }, 80 | 81 | "encode": function (value, options) { 82 | return function(cb) { 83 | cb(value); 84 | }; 85 | } 86 | }); 87 | 88 | -------------------------------------------------------------------------------- /core/core-validators.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | new Validator("string", { 10 | "execute": function (parameter, options) { 11 | 12 | return function(cb) { 13 | var errors = []; 14 | 15 | if (typeof options.max !== "undefined") { 16 | if (parameter.length > options.max) { 17 | errors.push("max"); 18 | } 19 | } 20 | 21 | if (typeof options.min !== "undefined") { 22 | if (parameter.length < options.min) { 23 | errors.push("min"); 24 | } 25 | } 26 | 27 | cb(errors); 28 | }; 29 | } 30 | }); 31 | 32 | new Validator("number", { 33 | "execute": function (parameter, options) { 34 | return function(cb) { 35 | var errors = []; 36 | 37 | var num_parameter = 0; 38 | 39 | try { 40 | num_parameter = parseInt(parameter, 10); 41 | } catch (e) { 42 | errors.push("type"); 43 | cb(errors); 44 | return ; 45 | } 46 | 47 | if (String(num_parameter) !== String(parameter)) { 48 | errors.push("type"); 49 | cb(errors); 50 | return ; 51 | } 52 | 53 | if (typeof options.max !== "undefined") { 54 | if (num_parameter > options.max) { 55 | errors.push("max"); 56 | } 57 | } 58 | 59 | if (typeof options.min !== "undefined") { 60 | if (num_parameter < options.min) { 61 | errors.push("min"); 62 | } 63 | } 64 | 65 | cb(errors); 66 | }; 67 | } 68 | }); 69 | 70 | new Validator("array", { 71 | "execute": function (parameter, options) { 72 | return function(cb) { 73 | var errors = []; 74 | 75 | if (typeof parameter.join !== "function") { 76 | errors.push("type"); 77 | cb(errors); 78 | return ; 79 | } 80 | 81 | cb(errors); 82 | }; 83 | } 84 | }); 85 | -------------------------------------------------------------------------------- /core/database/DatabaseManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The manager for all registered databases. 11 | * 12 | * @extends Logging 13 | */ 14 | DatabaseManager = function() { 15 | this.databases = []; 16 | this.databases_by_name = {}; 17 | }; 18 | 19 | extend(true, DatabaseManager.prototype, Logging.prototype); 20 | 21 | DatabaseManager.prototype.logging_prefix = 'DatabaseManager'; 22 | 23 | var Criteria = function() { 24 | this.where_parts = []; 25 | this.limit = null; 26 | this.offset = null; 27 | this.order_by_parts = []; 28 | }; 29 | 30 | extend(true, Criteria.prototype, Logging.prototype); 31 | 32 | Criteria.prototype.logging_prefix = 'Criteria'; 33 | 34 | Criteria.prototype.andWhere = function(key, operator, value) { 35 | this.where_parts.push([key, operator, value]); 36 | }; 37 | 38 | Criteria.prototype.andWhereIn = function(key, values) { 39 | this.where_parts.push([key, 'IN', values]); 40 | }; 41 | 42 | Criteria.prototype.getWhereParts = function() { 43 | return this.where_parts; 44 | }; 45 | 46 | Criteria.prototype.getOrderByParts = function() { 47 | return this.order_by_parts; 48 | }; 49 | 50 | Criteria.prototype.getLimit = function() { 51 | return this.limit; 52 | }; 53 | 54 | Criteria.prototype.getOffset = function() { 55 | return this.offset; 56 | }; 57 | 58 | Criteria.prototype.setLimit = function(limit) { 59 | this.limit = limit; 60 | }; 61 | 62 | Criteria.prototype.setOffset = function(offset) { 63 | this.offset = offset; 64 | }; 65 | 66 | Criteria.prototype.removeLimit = function() { 67 | this.limit = null; 68 | }; 69 | 70 | Criteria.prototype.removeOffset = function() { 71 | this.offset = null; 72 | }; 73 | 74 | Criteria.prototype.addOrderBy = function(key, direction) { 75 | this.order_by_parts.push([key, direction]); 76 | }; 77 | 78 | DatabaseManager.prototype.getDatabase = function(name) { 79 | if (!this.databases_by_name[name]) { 80 | var options = config.get('database_connections', {})[name]; 81 | if (typeof options === 'undefined') { 82 | throw new Error("Database Configuration for name " + name + " not found!"); 83 | } else { 84 | var engine = GLOBAL[options.driver_name || "StorageDatabaseDriver"]; 85 | var database = new engine(name, options.driver_options || {}); 86 | this.databases_by_name[name] = database; 87 | this.databases.push(database); 88 | } 89 | } 90 | return this.databases_by_name[name]; 91 | }; 92 | 93 | DatabaseManager.prototype.createCriteria = function() { 94 | return new Criteria(); 95 | }; 96 | 97 | DatabaseManager.prototype.shutdown = function() { 98 | this.trace("shutdown", arguments); 99 | var that = this; 100 | 101 | return function(cb) { 102 | var shutdown_chain = []; 103 | 104 | that.databases.forEach(function(current_database) { 105 | /* 106 | * Check whether this database has a shutdown method. 107 | */ 108 | if (typeof current_database.shutdown === "function") { 109 | shutdown_chain.push(function(chain_cb) { 110 | try { 111 | current_database.shutdown()(function() { 112 | chain_cb(); 113 | }); 114 | } catch (e) { 115 | that.warn("Exception when trying to shutdown database " + name); 116 | that.warn(e); 117 | chain_cb(); 118 | } 119 | }); 120 | } 121 | }); 122 | 123 | if (shutdown_chain.length) { 124 | group.apply(GLOBAL, shutdown_chain)(function() { 125 | cb(); 126 | }); 127 | } else { 128 | cb(); 129 | } 130 | }; 131 | }; 132 | -------------------------------------------------------------------------------- /core/database/DatabaseMigration.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2011 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var fs = require("fs"); 10 | var path = require("path"); 11 | 12 | DatabaseMigration = function(connection_name) { 13 | this.connection_name = connection_name; 14 | this.connection = database_manager.getDatabase(connection_name); 15 | this.migrations_folder = process.cwd() + '/dev/migrations/' + connection_name + '/'; 16 | this.structure_dump_file_path = process.cwd() + '/dev/migrations/' + connection_name + '_structure.sql'; 17 | this.fixtures_dump_file_path = process.cwd() + '/dev/migrations/' + connection_name + '_fixtures.sql'; 18 | }; 19 | 20 | DatabaseMigration.prototype.migrateAll = function() { 21 | var that = this; 22 | return function(cb) { 23 | fs.readdir(that.migrations_folder, function(error, raw_files) { 24 | if (error) { 25 | cb(true, "Migrations folder not found at: " + that.migrations_folder); 26 | return ; 27 | } 28 | that.connection.selectTableRows("executed_migrations")(function(error, migrated_version_rows) { 29 | migrated_versions = []; 30 | if (error) { 31 | /* 32 | * Ok, we don't have a executed_migrations table, yet! 33 | */ 34 | } else { 35 | migrated_version_rows.forEach(function(row) { 36 | migrated_versions.push(row.name); 37 | }); 38 | } 39 | 40 | var files = raw_files.slice(); 41 | 42 | var not_yet_migrated_files = []; 43 | for (var i = 0; i < raw_files.length; i++) { 44 | var raw_file_name = raw_files[i]; 45 | var timestamp = raw_file_name.substr(0, raw_file_name.indexOf('_')); 46 | if (migrated_versions.indexOf(timestamp) !== -1) { 47 | console.log(' > Already executed: ' + timestamp); 48 | } else { 49 | not_yet_migrated_files.push(that.migrations_folder + raw_file_name); 50 | } 51 | } 52 | 53 | if (not_yet_migrated_files.length === 0) { 54 | /* 55 | * Nothing to do. 56 | */ 57 | cb(false); 58 | return ; 59 | } 60 | 61 | not_yet_migrated_files.sort(); 62 | 63 | console.log(' Executing missing migrations:'); 64 | that.connection.query("CREATE TABLE executed_migrations (" + 65 | "`name` VARCHAR( 255 ) NOT NULL," + 66 | "UNIQUE ( `name` )" + 67 | ");")(function(error) { 68 | that.migrateFiles(not_yet_migrated_files)(cb); 69 | }); 70 | }); 71 | }); 72 | }; 73 | }; 74 | 75 | DatabaseMigration.prototype.downgradeAll = function() { 76 | var that = this; 77 | return function(cb) { 78 | fs.readdir(that.migrations_folder, function(error, raw_files) { 79 | if (error) { 80 | cb(true, "Migrations folder not found at: " + that.migrations_folder); 81 | return ; 82 | } 83 | that.connection.selectTableRows("executed_migrations")(function(error, migrated_version_rows) { 84 | migrated_versions = []; 85 | if (error) { 86 | /* 87 | * Ok, we don't have a executed_migrations table, yet! 88 | */ 89 | cb(false); 90 | return ; 91 | } else { 92 | migrated_version_rows.forEach(function(row) { 93 | migrated_versions.push(row.name); 94 | }); 95 | } 96 | 97 | var files = raw_files.slice(); 98 | 99 | var already_migrated_files = []; 100 | for (var i = 0; i < raw_files.length; i++) { 101 | var raw_file_name = raw_files[i]; 102 | var timestamp = raw_file_name.substr(0, raw_file_name.indexOf('_')); 103 | if (migrated_versions.indexOf(timestamp) !== -1) { 104 | console.log(' > Selecting for downgrade: ' + timestamp); 105 | already_migrated_files.push(that.migrations_folder + raw_file_name); 106 | } 107 | } 108 | 109 | already_migrated_files.sort(); 110 | already_migrated_files.reverse(); 111 | 112 | if (already_migrated_files.length === 0) { 113 | /* 114 | * Nothing to do. 115 | */ 116 | cb(false); 117 | return ; 118 | } 119 | 120 | console.log(' Executing missing downgrades:'); 121 | that.downgradeFiles(already_migrated_files)(cb); 122 | }); 123 | }); 124 | }; 125 | }; 126 | 127 | DatabaseMigration.prototype.downgradeFiles = function(files) { 128 | var that = this; 129 | return function(cb) { 130 | var migration_chain = []; 131 | files.forEach(function(file_path) { 132 | migration_chain.push(function(chain_cb) { 133 | var file_name_without_file_extension = file_path.split('.js')[0]; 134 | var file_timestamp = path.basename(file_path, '.js').split('_')[0]; 135 | console.log(' > Executing downgrade: ' + file_timestamp); 136 | require(file_name_without_file_extension).down(that.connection)(function(error, message) { 137 | if (error) { 138 | console.log('Failed downgrading: ' + file_timestamp, message); 139 | cb(true, message); 140 | return ; 141 | } 142 | 143 | that.connection.deleteTableRows("executed_migrations", "name = ?", [file_timestamp])(function(error, message) { 144 | console.log(' ... done!'); 145 | chain_cb(); 146 | }); 147 | }); 148 | }); 149 | }); 150 | 151 | migration_chain.push(function() { 152 | cb(false); 153 | }); 154 | 155 | chain.apply(GLOBAL, migration_chain); 156 | }; 157 | }; 158 | 159 | DatabaseMigration.prototype.migrateFiles = function(files) { 160 | var that = this; 161 | return function(cb) { 162 | var migration_chain = []; 163 | files.forEach(function(file_path) { 164 | migration_chain.push(function(chain_cb) { 165 | var file_name_without_file_extension = file_path.split('.js')[0]; 166 | var file_timestamp = path.basename(file_path, '.js').split('_')[0]; 167 | console.log(' > Executing migration: ' + file_timestamp); 168 | require(file_name_without_file_extension).up(that.connection)(function(error, message) { 169 | if (error) { 170 | console.log('Failed migrating: ' + file_timestamp, message); 171 | cb(true, message); 172 | return ; 173 | } 174 | var values = { 175 | "name": file_timestamp 176 | }; 177 | that.connection.createTableRow("executed_migrations", values)(function(error, message) { 178 | console.log(' ... done!'); 179 | chain_cb(); 180 | }); 181 | }); 182 | }); 183 | }); 184 | 185 | migration_chain.push(function() { 186 | cb(false); 187 | }); 188 | 189 | chain.apply(GLOBAL, migration_chain); 190 | }; 191 | }; 192 | 193 | DatabaseMigration.prototype.createStructureDump = function() { 194 | var that = this; 195 | return function(cb) { 196 | that.connection.dumpStructureToFile(that.structure_dump_file_path)(function(error) { 197 | cb(); 198 | }); 199 | }; 200 | }; 201 | 202 | DatabaseMigration.prototype.loadStructureDump = function() { 203 | var that = this; 204 | return function(cb) { 205 | that.connection.loadStructureFromFile(that.structure_dump_file_path)(function(error) { 206 | cb(); 207 | }); 208 | }; 209 | }; 210 | 211 | DatabaseMigration.prototype.removeStructureDump = function() { 212 | var that = this; 213 | return function(cb) { 214 | fs.unlink(that.structure_dump_file_path, function(error) { 215 | cb(error); 216 | }); 217 | }; 218 | }; 219 | 220 | DatabaseMigration.prototype.createFixturesDump = function() { 221 | var that = this; 222 | return function(cb) { 223 | that.connection.dumpDatabaseToFile(that.fixtures_dump_file_path)(function(error) { 224 | cb(); 225 | }); 226 | }; 227 | }; 228 | 229 | DatabaseMigration.prototype.loadFixturesDump = function() { 230 | var that = this; 231 | return function(cb) { 232 | that.connection.loadDatabaseFromFile(that.fixtures_dump_file_path)(function(error) { 233 | cb(); 234 | }); 235 | }; 236 | }; 237 | 238 | DatabaseMigration.prototype.removeFixturesDump = function() { 239 | var that = this; 240 | return function(cb) { 241 | fs.unlink(that.fixtures_dump_file_path, function(error) { 242 | cb(error); 243 | }); 244 | }; 245 | }; 246 | -------------------------------------------------------------------------------- /core/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var sys = require('sys'); 10 | var http = require('http'); 11 | var fs = require('fs'); 12 | 13 | var application_directory = process.cwd() + "/"; 14 | 15 | require('./util'); 16 | 17 | require("./StringToolkit"); 18 | require("./ObjectToolkit"); 19 | 20 | require("./Config"); 21 | 22 | /** 23 | * The global configuration object. 24 | * 25 | * @type Config 26 | */ 27 | config = new Config(); 28 | 29 | config.setValues({ 30 | "core": { 31 | "version": '2.0', 32 | "application_path": application_directory 33 | } 34 | }); 35 | try { 36 | require(application_directory + "config"); 37 | } catch (e) { 38 | /* 39 | * It's a pity, we don't have a config file :(. 40 | */ 41 | } 42 | 43 | try { 44 | require(application_directory + "local.config"); 45 | } catch (e) { 46 | /* 47 | * It's a pity, we don't have a local.config file :(. 48 | */ 49 | } 50 | 51 | if (!config.get('logging', {}).log_core) { 52 | (function() { 53 | var logging_config = config.get('logging', {}); 54 | logging_config.hide_classes = logging_config.hide_classes || []; 55 | 56 | var core_classes = [ 57 | "BaseApplication", 58 | "BootstrapManager", 59 | "ControllerManager", 60 | "ConsoleApplication", 61 | "DataMapperManager", 62 | "ServerApplication", 63 | "StaticFilesManager", 64 | "StorageManager", 65 | "ViewManager", 66 | "ValidatorManager" 67 | ]; 68 | 69 | var core_classes_length = core_classes.length; 70 | 71 | for (var i = 0; i < core_classes_length; i++) { 72 | logging_config.hide_classes.push(core_classes[i]); 73 | } 74 | 75 | config.setValues('logging', logging_config); 76 | })(); 77 | } 78 | 79 | require("./Options"); 80 | require("./Logging"); 81 | 82 | require("./BootstrapManager"); 83 | 84 | /** 85 | * The global bootstrap manager 86 | * 87 | * @type BootstrapManager 88 | */ 89 | bootstrap_manager = new BootstrapManager(); 90 | 91 | require("./ContextToolkit"); 92 | require("./Context"); 93 | 94 | require("./ServiceManager"); 95 | require("./ApiServiceController"); 96 | 97 | /** 98 | * The global service manager 99 | * 100 | * @type ServiceManager 101 | */ 102 | service_manager = new ServiceManager(); 103 | 104 | require("./storage/MemoryStorage"); 105 | require("./storage/StorageManager"); 106 | 107 | /** 108 | * The global storage manager. 109 | * 110 | * @type StorageManager 111 | */ 112 | storage_manager = new StorageManager(); 113 | 114 | require("./database/DatabaseManager"); 115 | 116 | /** 117 | * The global database manager 118 | * 119 | * @type DatabaseManager 120 | */ 121 | database_manager = new DatabaseManager(); 122 | 123 | /* 124 | * Databases: 125 | */ 126 | require("./database/DatabaseMigration"); 127 | require("./database/BaseSqlDatabaseDriver"); 128 | require("./database/MysqlDatabaseDriver"); 129 | require("./database/SqliteDatabaseDriver"); 130 | 131 | require("./server/CookieSessionManager"); 132 | 133 | require("./server/StaticFilesManager"); 134 | static_files_manager = new StaticFilesManager(); 135 | 136 | require("./Controller"); 137 | require("./SyncController"); 138 | require("./ControllerManager"); 139 | 140 | /** 141 | * The global controller manager. 142 | * 143 | * @type ControllerManager 144 | */ 145 | controller_manager = new ControllerManager(); 146 | 147 | require("./JsView"); 148 | require("./EjsView"); 149 | require("./ViewManager"); 150 | 151 | /** 152 | * The global view manager. 153 | * 154 | * @type ViewManager 155 | */ 156 | view_manager = new ViewManager(); 157 | view_manager.addViewEngine('ejs', 'EjsView'); 158 | 159 | require("./DataMapper"); 160 | require("./DataMapperManager"); 161 | 162 | /** 163 | * The global data mapper manager. 164 | * 165 | * @type DataMapperManager 166 | */ 167 | data_mapper_manager = new DataMapperManager(); 168 | 169 | if (!config.get('core', {}).disable_core_data_mappers) { 170 | require("./core-data-mappers"); 171 | } 172 | 173 | require("./Validator"); 174 | require("./ValidatorManager"); 175 | 176 | /** 177 | * The global validator manager. 178 | * 179 | * @type ValidatorManager 180 | */ 181 | validator_manager = new ValidatorManager(); 182 | 183 | require("./Validation"); 184 | 185 | if (!config.get('core', {}).disable_core_validators) { 186 | require("./core-validators"); 187 | } 188 | 189 | var lib_token = bootstrap_manager.createMandatoryElement('lib'); 190 | 191 | /* 192 | * Initialize all lib/index.js files 193 | */ 194 | var lib_folder_exists = false; 195 | var lib_folder_path = application_directory + "lib"; 196 | try { 197 | lib_folder_stats = fs.statSync(lib_folder_path); 198 | if (lib_folder_stats.isDirectory()) { 199 | lib_folder_exists = true; 200 | } 201 | } catch (e) { 202 | /* 203 | * Folder does not exist! 204 | */ 205 | } 206 | if (lib_folder_exists) { 207 | var lib_index_file_exists = false; 208 | try { 209 | index_js_stats = fs.statSync(lib_folder_path + "/index.js"); 210 | lib_index_file_exists = true; 211 | } catch (e) { 212 | /* 213 | * Index file does not exist! 214 | */ 215 | } 216 | 217 | if (lib_index_file_exists) { 218 | require(lib_folder_path + "/index"); 219 | } 220 | } 221 | 222 | 223 | bootstrap_manager.finishMandatoryElement(lib_token); 224 | 225 | /* 226 | * Load the static folder, if the core has one. 227 | */ 228 | try { 229 | static_folder_stats = fs.statSync(application_directory + "static"); 230 | static_files_manager.addFolder(application_directory + "static/"); 231 | } catch (e) { 232 | /* 233 | * Folder does not exist! 234 | */ 235 | } 236 | 237 | var session_manager_engine = GLOBAL[config.get("session_manager_engine", "CookieSessionManager")]; 238 | 239 | /** 240 | * The global session manager. 241 | * 242 | * @type SessionManager 243 | */ 244 | session_manager = new session_manager_engine(config.get("session", {})); 245 | 246 | /* 247 | * Load controllers and views 248 | */ 249 | controller_manager.loadControllers(application_directory); 250 | view_manager.loadViews(application_directory); 251 | 252 | require("./BaseApplication"); 253 | require("./server/ServerApplication"); 254 | require("./console/ConsoleApplication"); 255 | -------------------------------------------------------------------------------- /core/server/CookieSessionManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The manager for all registered sessions. 11 | * 12 | * @extends Logging 13 | */ 14 | CookieSessionManager = function(options) { 15 | options = options || {}; 16 | 17 | if (typeof options.cookie_path !== "undefined") { 18 | this.cookie_path = options.cookie_path; 19 | } else { 20 | this.cookie_path = '/'; 21 | } 22 | 23 | if (typeof options.cookie_key !== "undefined") { 24 | this.cookie_key = options.cookie_key; 25 | } else { 26 | this.cookie_key = "s"; 27 | } 28 | 29 | var engine = GLOBAL[options.engine || "MemoryStorage"]; 30 | 31 | this.storage = new engine("session_manager", options.engine_options || {}); 32 | }; 33 | 34 | extend(true, CookieSessionManager.prototype, Logging.prototype); 35 | 36 | CookieSessionManager.prototype.logging_prefix = 'CookieSessionManager'; 37 | 38 | CookieSessionManager.prototype.removeSession = function(session_id) { 39 | var that = this; 40 | that.trace("removeSession", arguments); 41 | return function(cb) { 42 | that.storage.remove(session_id)(function() { 43 | cb(); 44 | }); 45 | }; 46 | }; 47 | 48 | CookieSessionManager.prototype.getSession = function(session_id) { 49 | var that = this; 50 | return function(cb) { 51 | that.storage.get(session_id)(function(session_data) { 52 | if (!session_data) { 53 | cb(null); 54 | } else { 55 | that.log('getSession found', session_data); 56 | cb(JSON.parse(session_data)); 57 | } 58 | }); 59 | }; 60 | }; 61 | 62 | CookieSessionManager.prototype.setSession = function(session_id, session) { 63 | var that = this; 64 | return function(cb) { 65 | that.storage.set(session_id, JSON.stringify(session))(function() { 66 | cb(); 67 | }); 68 | }; 69 | }; 70 | 71 | CookieSessionManager.prototype.createSession = function(session) { 72 | var that = this; 73 | that.trace("createSession", arguments); 74 | return function(cb) { 75 | var withUniqueSessionHandler = function(session_id) { 76 | that.storage.set(session_id, JSON.stringify(session))(function() { 77 | cb(session_id); 78 | }); 79 | }; 80 | 81 | /* 82 | * FIXME: This is an ugly solution. Guess what happens if we have lots 83 | * of session. We need some way better function here. 84 | */ 85 | var tryToFindAnUnusedSessionId = function() { 86 | var session_id = Math.floor(Math.random()*999999999).toString(); 87 | that.storage.has(session_id)(function(is_in_use) { 88 | if (is_in_use) { 89 | session_id = null; 90 | tryToFindAnUnusedSessionId(); 91 | } else { 92 | withUniqueSessionHandler(session_id); 93 | } 94 | }); 95 | 96 | }; 97 | 98 | tryToFindAnUnusedSessionId(); 99 | }; 100 | }; 101 | 102 | 103 | CookieSessionManager.prototype.initializeWebContextSession = function (context, request) { 104 | var that = this; 105 | var session_id = (context.cookies && context.cookies[this.cookie_key]) || null; 106 | 107 | return function(cb) { 108 | if (session_id) { 109 | that.getSession(session_id)(function(session) { 110 | if (typeof session === 'undefined') { 111 | /* 112 | * Seems like that cookie is invalid by now. Let's remove the 113 | * cookie. 114 | */ 115 | ContextToolkit.removeCookie(context, that.cookie_key); 116 | cb(null); 117 | } else { 118 | context.session = session; 119 | cb(session_id); 120 | } 121 | }); 122 | } else { 123 | cb(null); 124 | } 125 | }; 126 | }; 127 | 128 | CookieSessionManager.prototype.finishWebContextSession = function (session_id, context, request) { 129 | var that = this; 130 | return function(cb) { 131 | if (session_id !== context.session_id) { 132 | session_id = context.session_id; 133 | 134 | if (session_id === null) { 135 | ContextToolkit.removeCookie(context, that.cookie_key); 136 | } else { 137 | ContextToolkit.setCookie(context, that.cookie_key, session_id, 0, that.cookie_path); 138 | } 139 | } 140 | cb(); 141 | }; 142 | }; 143 | -------------------------------------------------------------------------------- /core/server/ServerApplication.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The application running and listening on a specific port. 11 | * 12 | * @extends BaseApplication 13 | * 14 | * @param {Object} 15 | * options Options to specify the behaviour 16 | * @param {Number} 17 | * options.port The port, which should be used when launching the 18 | * application server. 19 | */ 20 | ServerApplication = function(options) { 21 | this.setOptions(options); 22 | 23 | if (typeof this.options.port === "undefined") { 24 | this.options.port = config.getPathValue(["server", "port"], 8000); 25 | } 26 | 27 | if (typeof this.options.server_name === "undefined") { 28 | this.options.server_name = config.getPathValue(["server", "name"], 'Spludo ' + config.get('core').version + ', node.JS ' + process.version); 29 | } 30 | 31 | /** 32 | * The Http-Server listening for new connections. 33 | * 34 | * @private 35 | */ 36 | this.server = null; 37 | }; 38 | 39 | extend(true, ServerApplication.prototype, BaseApplication.prototype); 40 | 41 | ServerApplication.prototype.logging_prefix = 'ServerApplication'; 42 | 43 | var http = require("http"); 44 | var sys = require("sys"); 45 | var multipart = require("./lib/multipart"); 46 | var url = require("url"); 47 | var querystring = require("querystring"); 48 | 49 | /** 50 | * Runs the application. 51 | */ 52 | ServerApplication.prototype.run = function() { 53 | var that = this; 54 | 55 | var finishRequest = function(req, res, body, params) { 56 | var context = { 57 | status: 200, 58 | headers: { 59 | 'Content-Type': 'text/plain', 60 | 'Server': that.options.server_name 61 | } 62 | }; 63 | 64 | context.params = params || {}; 65 | context.request = req; 66 | 67 | var execution_path = null; 68 | 69 | var request_url = url.parse(req.url, true); 70 | extend(true, context.params, request_url.query); 71 | 72 | execution_path = request_url.pathname.substr(1); 73 | 74 | if (body !== null) { 75 | /* 76 | * Ok, we need to parse that! 77 | */ 78 | try { 79 | if (req.headers['content-type'] && req.headers['content-type'].match(/^application\/json.*/)) { 80 | var body_as_utf8 = new Buffer(body, 'binary'); 81 | extend(true, context.params, JSON.parse(body_as_utf8.toString('utf8'))); 82 | } else { 83 | extend(true, context.params, querystring.parse(body)); 84 | } 85 | } catch (error) { 86 | 87 | } 88 | } 89 | 90 | ContextToolkit.applyRequestHeaders(context, req.headers); 91 | 92 | var response = null; 93 | 94 | var session_id = null; 95 | 96 | var responseHandler = function(response) { 97 | session_manager.finishWebContextSession(session_id, context, req.headers)(function() { 98 | ContextToolkit.applyCookiesToHeaders(context); 99 | 100 | res.writeHead(context.status, context.headers); 101 | 102 | if (req.method !== "HEAD") { 103 | if(response || false) res.write(response, context.encoding || "utf8"); 104 | } 105 | res.end(); 106 | }); 107 | }; 108 | 109 | session_manager.initializeWebContextSession(context, req)(function(real_session_id) { 110 | session_id = real_session_id; 111 | context.session_id = session_id; 112 | try { 113 | BaseApplication.executePath(execution_path, context)(responseHandler); 114 | } catch (e) { 115 | context.status = 404; 116 | response = "Page not found!\n\n" + (e.stack || e.message) + "\n\n"; 117 | response = response + "Arguments: " + sys.inspect(e['arguments']); 118 | responseHandler(response); 119 | } 120 | }); 121 | }; 122 | 123 | this.server = http.createServer(function(req, res) { 124 | if (static_files_manager.canHandleRequest(req)) { 125 | static_files_manager.handleRequest(req, res); 126 | return ; 127 | } 128 | 129 | if (req.method === "GET") { 130 | finishRequest(req, res, null); 131 | return ; 132 | } 133 | 134 | req.setEncoding("binary"); 135 | 136 | var stream = multipart.parse(req); 137 | 138 | if (stream.isMultiPart) { 139 | 140 | var parts_base_name_last_index = {}; 141 | 142 | var parts = {}; 143 | var current_part_name = null; 144 | 145 | stream.addListener("partBegin", function (part) { 146 | current_part_name = part.name; 147 | if (current_part_name.substr(-2) === '[]') { 148 | var current_part_base_name = current_part_name.substr(0, current_part_name.length - 2); 149 | if (typeof parts_base_name_last_index[current_part_base_name] === "undefined") { 150 | parts_base_name_last_index[current_part_base_name] = 0; 151 | } else { 152 | parts_base_name_last_index[current_part_base_name]++; 153 | } 154 | 155 | current_part_name = current_part_base_name + '[' + parts_base_name_last_index[current_part_base_name] + ']'; 156 | } 157 | 158 | parts[current_part_name] = { 159 | "name": current_part_name, 160 | "filename": part.filename, 161 | "body": [] 162 | }; 163 | }); 164 | 165 | stream.addListener("body", function(chunk) { 166 | parts[current_part_name].body.push(chunk); 167 | }); 168 | 169 | stream.addListener("partEnd", function () { 170 | if (current_part_name !== null) { 171 | parts[current_part_name].body = parts[current_part_name].body.join(""); 172 | } 173 | current_part_name = null; 174 | }); 175 | 176 | stream.addListener('complete', function(content) { 177 | finishRequest(req, res, null, parts); 178 | }); 179 | } else { 180 | var body = []; 181 | 182 | stream.addListener("body", function(chunk) { 183 | if (chunk === null) { 184 | return ; 185 | } 186 | body.push(chunk); 187 | }); 188 | 189 | stream.addListener('complete', function(content) { 190 | finishRequest(req, res, body.join("")); 191 | }); 192 | } 193 | 194 | }); 195 | 196 | this.info("Listening on port " + this.options["port"]); 197 | 198 | this.server.listen(this.options["port"]); 199 | }; 200 | -------------------------------------------------------------------------------- /core/server/StaticFilesManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | var Buffer = require('buffer').Buffer; 10 | 11 | /** 12 | * @class A manager for static files. It will automatically dispatch the static 13 | * files (from the core/plugins' static folders). 14 | */ 15 | StaticFilesManager = function() { 16 | this.folders = []; 17 | this.files = {}; 18 | }; 19 | 20 | extend(true, StaticFilesManager.prototype, Logging.prototype); 21 | 22 | var child_process = require("child_process"); 23 | var fs = require("fs"); 24 | 25 | StaticFilesManager.prototype.logging_prefix = 'StaticFilesManager'; 26 | 27 | StaticFilesManager.prototype.addFolder = function(folder_name) { 28 | this.trace('addFolder', arguments); 29 | var that = this; 30 | 31 | if (this.folders[folder_name]) { 32 | throw new Error("Folder " + folder_name + " already added!"); 33 | } 34 | this.folders[folder_name] = true; 35 | 36 | that.debug("addFolder","adding " + folder_name + " as static folder."); 37 | child_process.exec("cd " + folder_name + " && find . -type f", function(err, stdout, stderr) { 38 | var files = stdout.split("\n"); 39 | var files_length = files.length; 40 | for ( var f = 0; f < files_length; f++) { 41 | if (files[f] !== "") { 42 | var file_name = files[f].substr(2); 43 | that.addFile(file_name, folder_name + file_name); 44 | } 45 | } 46 | }); 47 | }; 48 | 49 | 50 | StaticFilesManager.prototype.addFile = function(file_name, absolute_path) { 51 | this.trace('addFile', arguments); 52 | this.files["static/" + file_name] = absolute_path; 53 | }; 54 | 55 | StaticFilesManager.prototype.removeFile = function(file_name) { 56 | this.trace("removeFile", arguments); 57 | delete this.files["static/" + file_name]; 58 | }; 59 | 60 | StaticFilesManager.prototype.getFileAbsolutePath = function(file_name) { 61 | this.trace("getFileAbsolutePath", arguments); 62 | return this.files[file_name]; 63 | }; 64 | 65 | StaticFilesManager.prototype.canHandleRequest = function(req) { 66 | this.trace("canHandleRequest", arguments); 67 | var uri = req.url.substr(1); 68 | return typeof this.files[uri] === "undefined" ? false : true; 69 | }; 70 | 71 | var file_extension_to_mime_type_map = { 72 | "html": "text/html", 73 | "js": "application/x-javascript", 74 | "css": "text/css", 75 | "jpg": "image/jpeg", 76 | "jpeg": "image/jpeg", 77 | "png": "image/png" 78 | }; 79 | 80 | StaticFilesManager.prototype.handleRequest = function(req, res) { 81 | this.trace("handleRequest", arguments); 82 | var uri = req.url.substr(1); 83 | 84 | var file_extension = /\.([\w]+)$/.exec(uri); 85 | 86 | var mime_type = "application/octet-stream"; 87 | 88 | if (file_extension) { 89 | mime_type = file_extension_to_mime_type_map[file_extension[1]] || mime_type; 90 | } 91 | 92 | if (this.files[uri] === "undefined") { 93 | res.writeHead(500, { 94 | "Content-Type": "text/plain" 95 | }); 96 | res.sendBody("cannot find static file " + uri); 97 | res.finish(); 98 | } 99 | 100 | var static_file_cache = {}; 101 | 102 | fs.readFile(this.files[uri], "binary", function(err, content) { 103 | if (err) { 104 | res.writeHead(404, { 105 | }); 106 | } else { 107 | var content_buffer_length = content.length; 108 | 109 | res.writeHead(200, { 110 | "Content-Type": mime_type, 111 | "Cache-Control": "public, max-age=300", 112 | "Content-Length": content_buffer_length 113 | }); 114 | 115 | res.write(content, "binary"); 116 | } 117 | res.end(); 118 | }); 119 | }; 120 | -------------------------------------------------------------------------------- /core/storage/MemoryStorage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The memory storage. 11 | */ 12 | MemoryStorage = function(name, options) { 13 | this.values = {}; 14 | 15 | this.setOptions(options); 16 | 17 | storage_manager.addStorage(name, this); 18 | }; 19 | 20 | extend(true, MemoryStorage.prototype, Options.prototype, Logging.prototype); 21 | 22 | MemoryStorage.prototype.set = function(key, value) { 23 | var that = this; 24 | return function(cb) { 25 | that.values[key] = value; 26 | cb(true); 27 | }; 28 | }; 29 | 30 | MemoryStorage.prototype.get = function(key) { 31 | var that = this; 32 | return function(cb) { 33 | if (typeof that.values[key] === 'undefined') { 34 | cb(); 35 | } else { 36 | cb(that.values[key]); 37 | } 38 | }; 39 | }; 40 | 41 | MemoryStorage.prototype.has = function(key) { 42 | var that = this; 43 | return function(cb) { 44 | if (typeof that.values[key] === 'undefined') { 45 | cb(false); 46 | } else { 47 | cb(true); 48 | } 49 | }; 50 | }; 51 | 52 | MemoryStorage.prototype.remove = function(key) { 53 | var that = this; 54 | return function(cb) { 55 | if (typeof that.values[key] === 'undefined') { 56 | cb(false); 57 | } else { 58 | delete that.values[key]; 59 | cb(true); 60 | } 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /core/storage/StorageManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * @class The manager for all registered storages. 11 | * 12 | * @extends Logging 13 | */ 14 | StorageManager = function() { 15 | this.storages_by_name = {}; 16 | this.storages = []; 17 | }; 18 | 19 | extend(true, StorageManager.prototype, Logging.prototype); 20 | 21 | StorageManager.prototype.logging_prefix = 'StorageManager'; 22 | 23 | StorageManager.prototype.addStorage = function(name, storage) { 24 | this.trace("addStorage", arguments); 25 | this.storages_by_name[name] = storage; 26 | this.storages.push(storage); 27 | }; 28 | 29 | StorageManager.prototype.getStorage = function(name) { 30 | if (this.storages_by_name[name]) { 31 | return this.storages_by_name[name]; 32 | } 33 | 34 | throw new Error("Storage for name " + name + " not found!"); 35 | }; 36 | 37 | StorageManager.prototype.shutdown = function() { 38 | this.trace("shutdown", arguments); 39 | var that = this; 40 | 41 | return function(cb) { 42 | var shutdown_chain = []; 43 | 44 | that.storages.forEach(function(current_storage) { 45 | /* 46 | * Check whether this storage has a shutdown method. 47 | */ 48 | if (typeof current_storage.shutdown === "function") { 49 | shutdown_chain.push(function(chain_cb) { 50 | try { 51 | current_storage.shutdown()(function() { 52 | chain_cb(); 53 | }); 54 | } catch (e) { 55 | that.warn("Exception when trying to shutdown storage " + name); 56 | that.warn(e); 57 | chain_cb(); 58 | } 59 | }); 60 | } 61 | }); 62 | 63 | if (shutdown_chain.length) { 64 | group.apply(GLOBAL, shutdown_chain)(function() { 65 | cb(); 66 | }); 67 | } else { 68 | cb(); 69 | } 70 | }; 71 | }; 72 | -------------------------------------------------------------------------------- /core/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | 9 | /** 10 | * Creates a group of all passed arguments (each of them must be a function) 11 | * and returns a function, which executes all. 12 | * 13 | * @return function 14 | */ 15 | GLOBAL.group = function () { 16 | var args = arguments; 17 | var args_length = args.length; 18 | 19 | return function(cb) { 20 | if (args_length === 0) { 21 | process.nextTick(cb); 22 | return ; 23 | } 24 | 25 | var items_left_to_execute = args_length; 26 | 27 | var after_group_item = function() { 28 | items_left_to_execute--; 29 | if (!items_left_to_execute) { 30 | process.nextTick(cb); 31 | } 32 | }; 33 | 34 | var call_group_item = function(arg) { 35 | arg(after_group_item); 36 | }; 37 | 38 | for ( var i = 0; i < args_length; i++) { 39 | call_group_item(args[i]); 40 | } 41 | }; 42 | }; 43 | 44 | /** 45 | * Executes all functions (passed as arguments) in order. 46 | * 47 | * @return 48 | */ 49 | GLOBAL.chain = function () { 50 | var args = arguments; 51 | var args_length = args.length; 52 | 53 | if (args_length === 0) { 54 | return ; 55 | } 56 | 57 | var args_pos = 0; 58 | 59 | var start_func = function() { 60 | args[args_pos](function() { 61 | args_pos++; 62 | if (args_length > args_pos) { 63 | process.nextTick(start_func); 64 | } 65 | }); 66 | }; 67 | 68 | process.nextTick(start_func); 69 | }; 70 | 71 | /** 72 | * Adopted from jquery's extend method. Under the terms of MIT License. 73 | * 74 | * http://code.jquery.com/jquery-1.4.2.js 75 | */ 76 | GLOBAL.extend = function() { 77 | // copy reference to target object 78 | var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy; 79 | 80 | // Handle a deep copy situation 81 | if ( typeof target === "boolean" ) { 82 | deep = target; 83 | target = arguments[1] || {}; 84 | // skip the boolean and the target 85 | i = 2; 86 | } 87 | 88 | // Handle case when target is a string or something (possible in deep copy) 89 | if ( typeof target !== "object" && typeof target !== 'function') { 90 | target = {}; 91 | } 92 | 93 | var isArray = function(obj) { 94 | return toString.call(copy) === "[object Array]" ? true : false; 95 | }; 96 | 97 | var isPlainObject = function( obj ) { 98 | // Must be an Object. 99 | // Because of IE, we also have to check the presence of the constructor property. 100 | // Make sure that DOM nodes and window objects don't pass through, as well 101 | if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { 102 | return false; 103 | } 104 | 105 | var has_own_constructor = Object.hasOwnProperty.call(obj, "constructor"); 106 | var has_is_property_of_method = Object.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf"); 107 | // Not own constructor property must be Object 108 | if ( obj.constructor && !has_own_constructor && !has_is_property_of_method) { 109 | return false; 110 | } 111 | 112 | // Own properties are enumerated firstly, so to speed up, 113 | // if last one is own, then all properties are own. 114 | 115 | var last_key; 116 | for (var key in obj) { 117 | last_key = key; 118 | } 119 | 120 | return typeof last_key === "undefined" || Object.hasOwnProperty.call( obj, last_key ); 121 | }; 122 | 123 | 124 | for ( ; i < length; i++ ) { 125 | // Only deal with non-null/undefined values 126 | if ( (options = arguments[ i ]) !== null ) { 127 | // Extend the base object 128 | for ( name in options ) { 129 | src = target[ name ]; 130 | copy = options[ name ]; 131 | 132 | // Prevent never-ending loop 133 | if ( target === copy ) { 134 | continue; 135 | } 136 | 137 | // Recurse if we're merging object literal values or arrays 138 | if ( deep && copy && ( isPlainObject(copy) || isArray(copy) ) ) { 139 | var clone = src && ( isPlainObject(src) || isArray(src) ) ? src : isArray(copy) ? [] : {}; 140 | 141 | // Never move original objects, clone them 142 | target[ name ] = GLOBAL.extend( deep, clone, copy ); 143 | 144 | // Don't bring in undefined values 145 | } else if ( typeof copy !== "undefined" ) { 146 | target[ name ] = copy; 147 | } 148 | } 149 | } 150 | } 151 | 152 | // Return the modified object 153 | return target; 154 | }; 155 | 156 | -------------------------------------------------------------------------------- /node_modules/README.md: -------------------------------------------------------------------------------- 1 | This folder contains all external libraries, shipped with spludo. 2 | 3 | They are all either Public Domain or MIT licensed. 4 | -------------------------------------------------------------------------------- /node_modules/inflection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A port of the Rails/ActiveSupport Inflector class 3 | * http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html 4 | * 5 | * Copyright (c) 2010 George Moschovitis, [http://www.gmosx.com](http://www.gmosx.com) 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to 9 | * deal in the Software without restriction, including without limitation the 10 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | * sell copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | var inflections = exports.inflections = { 26 | plurals: [], 27 | singulars: [], 28 | uncountables: [], 29 | humans: [] 30 | }; 31 | 32 | var PLURALS = inflections.plurals, 33 | SINGULARS = inflections.singulars, 34 | UNCOUNTABLES = inflections.uncountables, 35 | HUMANS = inflections.humans; 36 | 37 | /** 38 | * Specifies a new pluralization rule and its replacement. The rule can either 39 | * be a string or a regular expression. The replacement should always be a 40 | * string that may include references to the matched data from the rule. 41 | */ 42 | var plural = function (rule, replacement) { 43 | //inflections.uncountables.delete(rule) if rule.is_a?(String) 44 | //inflections.uncountables.delete(replacement) 45 | inflections.plurals.unshift([rule, replacement]); 46 | } 47 | 48 | /** 49 | * Specifies a new singularization rule and its replacement. The rule can either 50 | * be a string or a regular expression. The replacement should always be a 51 | * string that may include references to the matched data from the rule. 52 | */ 53 | var singular = function (rule, replacement) { 54 | //inflections.uncountables.delete(rule) if rule.is_a?(String) 55 | //inflections.uncountables.delete(replacement) 56 | inflections.singulars.unshift([rule, replacement]); 57 | } 58 | 59 | /** 60 | * Add uncountable words that shouldn't be attempted inflected. 61 | */ 62 | var uncountable = function (word) { 63 | inflections.uncountables.unshift(word); 64 | } 65 | 66 | /** 67 | * Specifies a new irregular that applies to both pluralization and 68 | * singularization at the same time. This can only be used for strings, not 69 | * regular expressions. You simply pass the irregular in singular and plural 70 | * form. 71 | * 72 | * Examples: 73 | * irregular("octopus", "octopi"); 74 | * irregular("person", "people"); 75 | */ 76 | var irregular = function (s, p) { 77 | //inflections.uncountables.delete(singular); 78 | //inflections.uncountables.delete(plural); 79 | if (s.substr(0, 1).toUpperCase() == p.substr(0, 1).toUpperCase()) { 80 | plural(new RegExp("(" + s.substr(0, 1) + ")" + s.substr(1) + "$", "i"), '$1' + p.substr(1)); 81 | plural(new RegExp("(" + p.substr(0, 1) + ")" + p.substr(1) + "$", "i"), '$1' + p.substr(1)); 82 | singular(new RegExp("(" + p.substr(0, 1) + ")" + p.substr(1) + "$", "i"), '$1' + s.substr(1)); 83 | } else { 84 | plural(new RegExp(s.substr(0, 1).toUpperCase() + s.substr(1) + "$"), p.substr(0, 1).toUpperCase() + p.substr(1)); 85 | plural(new RegExp(s.substr(0, 1).toLowerCase() + s.substr(1) + "$"), p.substr(0, 1).toLowerCase() + p.substr(1)); 86 | plural(new RegExp(p.substr(0, 1).toUpperCase() + p.substr(1) + "$"), p.substr(0, 1).toUpperCase() + p.substr(1)); 87 | plural(new RegExp(p.substr(0, 1).toLowerCase() + p.substr(1) + "$"), p.substr(0, 1).toLowerCase() + p.substr(1)); 88 | singular(new RegExp(p.substr(0, 1).toUpperCase() + p.substr(1) + "$"), s.substr(0, 1).toUpperCase() + s.substr(1)); 89 | singular(new RegExp(p.substr(0, 1).toLowerCase() + p.substr(1) + "$"), s.substr(0, 1).toLowerCase() + s.substr(1)); 90 | } 91 | } 92 | 93 | /** 94 | * Specifies a humanized form of a string by a regular expression rule or by a 95 | * string mapping. When using a regular expression based replacement, the normal 96 | * humanize formatting is called after the replacement. 97 | */ 98 | var human = function (rule, replacement) { 99 | //inflections.uncountables.delete(rule) if rule.is_a?(String) 100 | //inflections.uncountables.delete(replacement) 101 | inflections.humans.push([rule, replacement]); 102 | } 103 | 104 | plural(/$/, "s"); 105 | plural(/s$/i, "s"); 106 | plural(/(ax|test)is$/i, "$1es"); 107 | plural(/(octop|vir)us$/i, "$1i"); 108 | plural(/(alias|status)$/i, "$1es"); 109 | plural(/(bu)s$/i, "$1ses"); 110 | plural(/(buffal|tomat)o$/i, "$1oes"); 111 | plural(/([ti])um$/i, "$1a"); 112 | plural(/sis$/i, "ses"); 113 | plural(/(?:([^f])fe|([lr])f)$/i, "$1$2ves"); 114 | plural(/(hive)$/i, "$1s"); 115 | plural(/([^aeiouy]|qu)y$/i, "$1ies"); 116 | plural(/(x|ch|ss|sh)$/i, "$1es"); 117 | plural(/(matr|vert|ind)(?:ix|ex)$/i, "$1ices"); 118 | plural(/([m|l])ouse$/i, "$1ice"); 119 | plural(/^(ox)$/i, "$1en"); 120 | plural(/(quiz)$/i, "$1zes"); 121 | 122 | singular(/s$/i, "") 123 | singular(/(n)ews$/i, "$1ews") 124 | singular(/([ti])a$/i, "$1um") 125 | singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, "$1$2sis") 126 | singular(/(^analy)ses$/i, "$1sis") 127 | singular(/([^f])ves$/i, "$1fe") 128 | singular(/(hive)s$/i, "$1") 129 | singular(/(tive)s$/i, "$1") 130 | singular(/([lr])ves$/i, "$1f") 131 | singular(/([^aeiouy]|qu)ies$/i, "$1y") 132 | singular(/(s)eries$/i, "$1eries") 133 | singular(/(m)ovies$/i, "$1ovie") 134 | singular(/(x|ch|ss|sh)es$/i, "$1") 135 | singular(/([m|l])ice$/i, "$1ouse") 136 | singular(/(bus)es$/i, "$1") 137 | singular(/(o)es$/i, "$1") 138 | singular(/(shoe)s$/i, "$1") 139 | singular(/(cris|ax|test)es$/i, "$1is") 140 | singular(/(octop|vir)i$/i, "$1us") 141 | singular(/(alias|status)es$/i, "$1") 142 | singular(/^(ox)en/i, "$1") 143 | singular(/(vert|ind)ices$/i, "$1ex") 144 | singular(/(matr)ices$/i, "$1ix") 145 | singular(/(quiz)zes$/i, "$1") 146 | singular(/(database)s$/i, "$1") 147 | 148 | irregular("person", "people"); 149 | irregular("man", "men"); 150 | irregular("child", "children"); 151 | irregular("sex", "sexes"); 152 | irregular("move", "moves"); 153 | irregular("cow", "kine"); 154 | 155 | uncountable("equipment"); 156 | uncountable("information"); 157 | uncountable("rice"); 158 | uncountable("money"); 159 | uncountable("species"); 160 | uncountable("series"); 161 | uncountable("fish"); 162 | uncountable("sheep"); 163 | uncountable("jeans"); 164 | 165 | /** 166 | * Returns the plural form of the word in the string. 167 | */ 168 | exports.pluralize = function (word) { 169 | var wlc = word.toLowerCase(); 170 | 171 | for (var i = 0; i < UNCOUNTABLES.length; i++) { 172 | var uncountable = UNCOUNTABLES[i]; 173 | if (wlc == uncountable) { 174 | return word; 175 | } 176 | } 177 | 178 | for (var i = 0; i < PLURALS.length; i++) { 179 | var rule = PLURALS[i][0], 180 | replacement = PLURALS[i][1]; 181 | if (rule.test(word)) { 182 | return word.replace(rule, replacement); 183 | } 184 | } 185 | } 186 | 187 | /** 188 | * Returns the singular form of the word in the string. 189 | */ 190 | exports.singularize = function (word) { 191 | var wlc = word.toLowerCase(); 192 | 193 | for (var i = 0; i < UNCOUNTABLES.length; i++) { 194 | var uncountable = UNCOUNTABLES[i]; 195 | if (wlc == uncountable) { 196 | return word; 197 | } 198 | } 199 | 200 | for (var i = 0; i < SINGULARS.length; i++) { 201 | var rule = SINGULARS[i][0], 202 | replacement = SINGULARS[i][1]; 203 | if (rule.test(word)) { 204 | return word.replace(rule, replacement); 205 | } 206 | } 207 | } 208 | 209 | /** 210 | * Capitalizes the first word and turns underscores into spaces and strips a 211 | * trailing "Key", if any. Like +titleize+, this is meant for creating pretty 212 | * output. 213 | * 214 | * Examples: 215 | * "employeeSalary" => "employee salary" 216 | * "authorKey" => "author" 217 | */ 218 | exports.humanize = function (word) { 219 | for (var i = 0; i < HUMANS.length; i++) { 220 | var rule = HUMANS[i][0], 221 | replacement = HUMANS[i][1]; 222 | if (rule.test(word)) { 223 | word = word.replace(rule, replacement); 224 | } 225 | } 226 | 227 | return exports.split(word, " ").toLowerCase(); 228 | } 229 | 230 | /** 231 | * Split a camel case word in its terms. 232 | */ 233 | exports.split = function (word, delim) { 234 | delim = delim || " "; 235 | var replacement = "$1" + delim + "$2"; 236 | return word. 237 | replace(/([A-Z]+)([A-Z][a-z])/g, replacement). 238 | replace(/([a-z\d])([A-Z])/g, replacement); 239 | } 240 | 241 | /** 242 | * Converts a CamelCase word to underscore format. 243 | */ 244 | exports.underscore = function (word) { 245 | return exports.split(word, "_").toLowerCase(); 246 | } 247 | 248 | /** 249 | * Converts a CamelCase word to dash (lisp style) format. 250 | */ 251 | exports.dash = exports.dasherize = function (word) { 252 | return exports.split(word, "-").toLowerCase(); 253 | } 254 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "spludo", 3 | 4 | "engines": { "node": ">= 0.6.0" }, 5 | 6 | "homepage": "https://github.com/DracoBlue/spludo", 7 | 8 | "author": "DracoBlue ", 9 | "contributors": [ 10 | { "name": "DracoBlue", "email": "JanS@DracoBlue.de" } 11 | ], 12 | 13 | "keywords": ["framework", "mvc", "rest", "web", "configuration", "routing", "templating"], 14 | 15 | "directories" : { 16 | "." : "." 17 | }, 18 | 19 | "main" : "./core", 20 | 21 | "bin" : { 22 | "spludo-gen" : "./build/spludo-gen.js" 23 | }, 24 | 25 | "repository": { 26 | "type": "git", 27 | "web": "http://github.com/DracoBlue/spludo.git" 28 | }, 29 | 30 | "bugs": { 31 | "mail": "spludo@googlegroups.com", 32 | "url": "http://github.com/DracoBlue/spludo/issues" 33 | }, 34 | 35 | "licenses" : [ 36 | { "type" : "MIT", "url" : "https://github.com/DracoBlue/spludo/blob/master/LICENSE" } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /spludo-gen: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | node `dirname $0`/build/spludo-gen.js $* 3 | -------------------------------------------------------------------------------- /spludotests/README: -------------------------------------------------------------------------------- 1 | SpludoTests - Spludo Framework README 2 | ======================= 3 | 4 | This is the test application for the entire spludo framework. 5 | 6 | 7 | Running the tests 8 | ----------------- 9 | 10 | Enter the directory spludotests and execute: 11 | ant core-test 12 | 13 | 14 | Integrating with Continous Integration Tools 15 | -------------------------------------------- 16 | 17 | The tests also support an option to return .xml format (junit-style) to 18 | integrate with tools like hudson or cruise control. 19 | 20 | You may run the tests with the following command to achieve that: 21 | ant core-test-xml 22 | 23 | Please use that ONLY to retrieve the xml format, because the "ant core-test" 24 | command is way more convenient for the normal test execution. 25 | -------------------------------------------------------------------------------- /spludotests/build.properties: -------------------------------------------------------------------------------- 1 | jsdoctoolkit.directory=/home/spludo/jsdoc_toolkit-2.3.2/jsdoc-toolkit/ 2 | -------------------------------------------------------------------------------- /spludotests/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is the spludo core tests application. 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /spludotests/test/contexttoolkit-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | require('./../../core'); 9 | 10 | var vows = require("vows"); 11 | var assert = require("assert"); 12 | 13 | vows.describe("core.contexttoolkit").addBatch({ 14 | 15 | "Cookies": { 16 | 17 | 'topic': function() { 18 | bootstrap_manager.whenLoaded(this.callback); 19 | }, 20 | 21 | settingCookiesWithNoLifetime: function() { 22 | var context = {}; 23 | ContextToolkit.setCookie(context, "key", "value"); 24 | assert.equal(context.cookies["key"], "value"); 25 | }, 26 | 27 | settingCookiesMultipleTimes: function() { 28 | var context = {}; 29 | ContextToolkit.setCookie(context, "old_key", "value"); 30 | ContextToolkit.setCookie(context, "key", "value"); 31 | assert.equal(context.cookies["key"], "value"); 32 | }, 33 | 34 | tryingToApplyCookiesToHeaders: function() { 35 | var context = {}; 36 | ContextToolkit.setCookie(context, "key", "value"); 37 | ContextToolkit.setCookie(context, "key2", "value2"); 38 | 39 | ContextToolkit.applyCookiesToHeaders(context); 40 | 41 | assert.equal(context.headers["Set-Cookie"].indexOf("key="), 0); 42 | } 43 | 44 | } 45 | 46 | }).export(module); 47 | -------------------------------------------------------------------------------- /spludotests/test/cookiesessionmanager-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | require('./../../core'); 9 | 10 | var vows = require("vows"); 11 | var assert = require("assert"); 12 | 13 | vows.describe("core.session").addBatch({ 14 | 15 | "CookieSessionManager": { 16 | 17 | 'topic': function() { 18 | bootstrap_manager.whenLoaded(this.callback); 19 | }, 20 | 21 | settingGettingAndRemove: { 22 | 23 | 'topic': function() { 24 | var that = this; 25 | var session = {}; 26 | session.name = "test"; 27 | session.value = "value"; 28 | 29 | session_manager.setSession("testid", session)(function() { 30 | delete session.name; 31 | delete session.value; 32 | delete session; 33 | 34 | session_manager.getSession("testid")(function(get_session) { 35 | assert.equal(get_session.name, "test"); 36 | assert.equal(get_session.value, "value"); 37 | 38 | session_manager.removeSession("testid")(function() { 39 | session_manager.getSession("testid")(function(non_existant_session) { 40 | that.callback(null, !non_existant_session); 41 | }); 42 | }); 43 | }); 44 | }); 45 | }, 46 | 47 | 'test_if_session_is_really_removed' : function(res, session_did_not_exist) { 48 | assert.isTrue(session_did_not_exist, "undefined", "the session testid should not exist!"); 49 | } 50 | } 51 | } 52 | 53 | }).export(module); 54 | -------------------------------------------------------------------------------- /spludotests/test/datamapper-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | require('./../../core'); 9 | 10 | var vows = require("vows"); 11 | var assert = require("assert"); 12 | 13 | vows.describe("core.datamapper").addBatch({ 14 | "JsonMapperTests": { 15 | 16 | 'topic': function() { 17 | bootstrap_manager.whenLoaded(this.callback); 18 | }, 19 | 20 | simpleEncodeDecode: function() { 21 | var data_mapper = data_mapper_manager.getDataMapper('json'); 22 | 23 | assert.equal(data_mapper.encodeSync("abcde"), "\"abcde\""); 24 | }, 25 | 26 | expectingAnExceptionOnInvalidJson: function() { 27 | var data_mapper = data_mapper_manager.getDataMapper('json'); 28 | 29 | try { 30 | data_mapper.decodeSync("abcde"); 31 | fail("Should fail, because invalid json!"); 32 | } catch (e) { 33 | assert.notEqual(e.message.indexOf("Could not decode JSON"), -1, "Should throw something with 'Could not decode JSON'"); 34 | } 35 | }, 36 | 37 | simpleEncodeOfUtf8Data: function() { 38 | var data_mapper = data_mapper_manager.getDataMapper('json'); 39 | 40 | assert.equal(data_mapper.encodeSync("Dateigröße (in KB)"), "\"Dateigröße (in KB)\""); 41 | assert.equal(data_mapper.decodeSync("\"Dateigr\\u00f6\\u00dfe (in KB)\""), "Dateigröße (in KB)"); 42 | assert.equal(data_mapper.decodeSync(data_mapper.encodeSync("Dateigröße (in KB)")), "Dateigröße (in KB)"); 43 | } 44 | 45 | } 46 | }).export(module); 47 | -------------------------------------------------------------------------------- /spludotests/test/ejs-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | require('./../../core'); 9 | 10 | var vows = require("vows"); 11 | var assert = require("assert"); 12 | 13 | var fs = require("fs"); 14 | 15 | function checkEjsRenderVsExpectedFile(ejs_file_name, expected_file_name) { 16 | var new_view = new EjsView("plain_html_file", "testdata/" + ejs_file_name); 17 | 18 | var real_contents = fs.readFileSync("testdata/" + expected_file_name); 19 | return { 20 | 'topic': function() { 21 | var that = this; 22 | new_view.render()(function(response) { 23 | that.callback(null, response); 24 | }); 25 | }, 26 | 'returns valid response': function(res, response) { 27 | 28 | assert.equal(response, real_contents); 29 | } 30 | }; 31 | } 32 | 33 | vows.describe("core.ejs").addBatch({ 34 | 35 | "PlainHtmlFiles": { 36 | 37 | 'topic': function() { 38 | bootstrap_manager.whenLoaded(this.callback); 39 | }, 40 | 41 | loadingAPlainHtmlFile: checkEjsRenderVsExpectedFile("plain_html_file.ejs", "plain_html_file.ejs"), 42 | loadingAPlainHtmlFileWithATag: checkEjsRenderVsExpectedFile("plain_html_file_with_beginning_tag.ejs", "plain_html_file_with_beginning_tag.ejs") 43 | }, 44 | 45 | 'SimpleEjsFiles': { 46 | 47 | 'topic': function() { 48 | bootstrap_manager.whenLoaded(this.callback); 49 | }, 50 | 51 | withTwoBlocks: checkEjsRenderVsExpectedFile("ejs_example_with_indention_and_two_blocks.ejs", "ejs_example_with_indention_and_two_blocks.ejs.expected_output.txt"), 52 | withBlockAtBeginning: checkEjsRenderVsExpectedFile("ejs_example_with_ejs_at_the_beginning.ejs", "ejs_example_with_ejs_at_the_beginning.ejs.expected_output.txt") 53 | }, 54 | 55 | 'ExpressionEjsFiles': { 56 | 57 | 'topic': function() { 58 | bootstrap_manager.whenLoaded(this.callback); 59 | }, 60 | 61 | 'ejsFileWithMultipleExpressionBlocks': checkEjsRenderVsExpectedFile("ejs_example_with_expression.ejs", "ejs_example_with_expression.ejs.expected_output.txt"), 62 | 'exceptionWithWrongBlock': function() { 63 | var new_view = new EjsView("plain_html_file", "testdata/ejs_exception_for_not_closed_expression_tag.ejs"); 64 | 65 | try { 66 | new_view.render(); 67 | fail("Should throw an exception!"); 68 | } catch (e) { 69 | if (e.message.indexOf("tag not finished") === -1) { 70 | fail("Should throw something about 'tag not finished', but threw a: " + e.message); 71 | } 72 | } 73 | } 74 | }, 75 | 76 | exceptionForSemicolonInExpressionBlock: function() { 77 | var new_view = new EjsView("plain_html_file", "testdata/ejs_exception_for_semicolon_in_expression_tag.ejs"); 78 | 79 | try { 80 | new_view.render(); 81 | fail("Should throw an exception!"); 82 | } catch (e) { 83 | if (e.message.indexOf("Syntax Error") === -1) { 84 | fail("Should throw a Syntax Error, but threw a: " + e.message); 85 | } 86 | } 87 | } 88 | 89 | }).export(module); 90 | 91 | -------------------------------------------------------------------------------- /spludotests/test/memorystorage-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | require('./../../core'); 9 | 10 | var vows = require("vows"); 11 | var assert = require("assert"); 12 | 13 | vows.describe("core.storage").addBatch({ 14 | 15 | "MemoryStorage": { 16 | 17 | 'topic': function() { 18 | bootstrap_manager.whenLoaded(this.callback); 19 | }, 20 | 21 | simpleSetAndGet: { 22 | 'topic': function() { 23 | var that = this; 24 | var storage = new MemoryStorage("simple_set_and_get_storage"); 25 | storage.has("key")(function(value) { 26 | assert.equal(value, false); 27 | storage.get("key")(function(value) { 28 | that.callback(null, value); 29 | }); 30 | }); 31 | }, 32 | 33 | 'returns the proper value': function(res, value) { 34 | assert.equal(value || true, true); 35 | assert.equal(value || false, false); 36 | assert.equal(value || null, null); 37 | assert.equal(value, undefined); 38 | } 39 | }, 40 | 41 | setNullAsValue: { 42 | 'topic': function() { 43 | var that = this; 44 | var storage = new MemoryStorage("set_null_as_value_storage"); 45 | storage.has("key")(function(value) { 46 | assert.equal(value, false); 47 | storage.get("key")(function(value) { 48 | assert.equal(value || undefined, undefined); 49 | storage.set("key", null)(function(was_possible) { 50 | assert.equal(was_possible, true); 51 | storage.get("key")(function(value) { 52 | assert.equal(value, null); 53 | storage.remove("key")(function(was_possible) { 54 | assert.equal(was_possible, true); 55 | storage.has("key")(function(value) { 56 | assert.equal(value, false); 57 | storage.get("key")(function(value) { 58 | that.callback(null, typeof value); 59 | }); 60 | }); 61 | }); 62 | }); 63 | }); 64 | }); 65 | }); 66 | }, 67 | 'check if it has finally an undefined value': function(res, value_type) { 68 | assert.equal(value_type, "undefined"); 69 | } 70 | } 71 | } 72 | 73 | }).export(module); 74 | -------------------------------------------------------------------------------- /spludotests/test/objecttoolkit-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | require('./../../core'); 9 | 10 | var vows = require("vows"); 11 | var assert = require("assert"); 12 | 13 | vows.describe("core.objecttoolkit").addBatch({ 14 | 15 | "setPathValue": { 16 | 17 | 'topic': function() { 18 | bootstrap_manager.whenLoaded(this.callback); 19 | }, 20 | 21 | testWithNewObject: function() { 22 | var initial_data = {}; 23 | 24 | var data = ObjectToolkit.setPathValue(initial_data, ["a", "b", "c"], "hallo"); 25 | 26 | assert.equal(typeof data.a, "object"); 27 | assert.equal(typeof data.a.b, "object"); 28 | assert.equal(data.a.b.c, "hallo"); 29 | }, 30 | 31 | testWithFilledObject: function() { 32 | var initial_data = { 33 | "a": { 34 | "d": { 35 | }, 36 | "f": "hmpf" 37 | } 38 | }; 39 | 40 | var data = ObjectToolkit.setPathValue(initial_data, ["a", "b", "c"], "hallo"); 41 | 42 | assert.equal(typeof data.a, "object"); 43 | assert.equal(typeof data.a.b, "object"); 44 | assert.equal(data.a.b.c, "hallo"); 45 | 46 | assert.equal(data.a.f, "hmpf"); 47 | assert.equal(typeof data.a.d, "object"); 48 | } 49 | }, 50 | 51 | "getPathValue": { 52 | 53 | testWithNewObject: function() { 54 | var initial_data = {}; 55 | 56 | var data = ObjectToolkit.getPathValue(initial_data, ["a", "b", "c"]); 57 | 58 | assert.equal(typeof data, "undefined"); 59 | }, 60 | 61 | testWithFilledObject: function() { 62 | var initial_data = { 63 | "a": { 64 | "d": { 65 | }, 66 | "f": "hmpf" 67 | } 68 | }; 69 | 70 | var data = ObjectToolkit.getPathValue(initial_data, ["a", "f"]); 71 | 72 | assert.equal(data, "hmpf"); 73 | } 74 | } 75 | }).export(module); 76 | 77 | -------------------------------------------------------------------------------- /spludotests/test/stringtoolkit-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Spludo Framework. 3 | * Copyright (c) 2009-2010 DracoBlue, http://dracoblue.net/ 4 | * 5 | * Licensed under the terms of MIT License. For the full copyright and license 6 | * information, please see the LICENSE file in the root folder. 7 | */ 8 | require('./../../core'); 9 | 10 | var vows = require("vows"); 11 | var assert = require("assert"); 12 | 13 | vows.describe("core.stringtoolkit").addBatch({ 14 | 15 | "encodeXml and decodeXml": { 16 | 17 | 'topic': function() { 18 | bootstrap_manager.whenLoaded(this.callback); 19 | }, 20 | 21 | convertingSomethingWithoutSpecialChars: function() { 22 | assert.equal(StringToolkit.encodeXml("this is a test"), "this is a test"); 23 | assert.equal(StringToolkit.decodeXml("this is a test"), "this is a test"); 24 | }, 25 | convertingSimpleAmpersand: function() { 26 | assert.equal(StringToolkit.encodeXml("have fun & a nice day"),"have fun & a nice day"); 27 | assert.equal(StringToolkit.decodeXml("have fun & a nice day"),"have fun & a nice day"); 28 | }, 29 | 30 | convertingSpecialCharsInSameOrderAsReplacement: function() { 31 | assert.equal(StringToolkit.encodeXml("&\"<>"),"&"<>"); 32 | assert.equal(StringToolkit.decodeXml("&"<>"),"&\"<>"); 33 | }, 34 | 35 | encodingAllWithSimpleCases: function() { 36 | var items = { 37 | "&": "&", 38 | "\"": "quot;", 39 | "<": "<", 40 | ">": ">" 41 | }; 42 | 43 | var items_length = items.length; 44 | 45 | for (var i = 0; i": ">" 58 | }; 59 | 60 | var items_length = items.length; 61 | 62 | for (var i = 0; i1 4 | 2 5 | <% 6 | content.push(" 3"); 7 | %> 8 | 4 9 | <% 10 | content.push(" 5"); 11 | content.push("\n 6"); 12 | %> 13 | 7 -------------------------------------------------------------------------------- /spludotests/testdata/ejs_example_with_ejs_at_the_beginning.ejs.expected_output.txt: -------------------------------------------------------------------------------- 1 | 01 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 -------------------------------------------------------------------------------- /spludotests/testdata/ejs_example_with_expression.ejs: -------------------------------------------------------------------------------- 1 | <%= 2 | 0 3 | %>1 4 | 2 5 | <%=" 3"%> 6 | 4 7 | <% 8 | content.push(" 5"); 9 | content.push("\n 6"); 10 | %> 11 | 7<%= 12 | 13 | "\n 8"%> -------------------------------------------------------------------------------- /spludotests/testdata/ejs_example_with_expression.ejs.expected_output.txt: -------------------------------------------------------------------------------- 1 | 01 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 8 | 8 -------------------------------------------------------------------------------- /spludotests/testdata/ejs_example_with_indention_and_two_blocks.ejs: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | <% 4 | content.push(" 3"); 5 | %> 6 | 4 7 | <% 8 | content.push(" 5"); 9 | content.push("\n 6"); 10 | %> 11 | 7 -------------------------------------------------------------------------------- /spludotests/testdata/ejs_example_with_indention_and_two_blocks.ejs.expected_output.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 -------------------------------------------------------------------------------- /spludotests/testdata/ejs_exception_for_not_closed_expression_tag.ejs: -------------------------------------------------------------------------------- 1 | <%= 2 | 0 3 | %>1 4 | 2 5 | <%=" 3"%> 6 | 4 7 | <% 8 | content.push(" 5"); 9 | content.push("\n 6"); 10 | %> 11 | 7<%= 12 | /* This tag is not closed! */ 13 | "\n 8"> -------------------------------------------------------------------------------- /spludotests/testdata/ejs_exception_for_semicolon_in_expression_tag.ejs: -------------------------------------------------------------------------------- 1 | <%= 2 | 0 3 | %>1 4 | 2 5 | <%=" 3"%> 6 | 4 7 | <% 8 | content.push(" 5"); 9 | content.push("\n 6"); 10 | %> 11 | 7<%= 12 | /* This expression tag contains a ;! */ 13 | "\n 8";%> -------------------------------------------------------------------------------- /spludotests/testdata/plain_html_file.ejs: -------------------------------------------------------------------------------- 1 | 2 | Hello World! 3 | 4 | -------------------------------------------------------------------------------- /spludotests/testdata/plain_html_file_with_beginning_tag.ejs: -------------------------------------------------------------------------------- 1 | 2 | Hello World! 3 | 4 | --------------------------------------------------------------------------------