From 323f1ae4f84ced52faddf8d87a5cfd1181bbce4a Mon Sep 17 00:00:00 2001 From: Sean Hussey Date: Fri, 19 Dec 2014 10:46:49 -0500 Subject: [PATCH 01/19] Fix typos. --- README.md | 2 +- config.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index caadd2172..3e9809c57 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ The current selection is as follows. It reflects support versus patternlab-php. "m": true, "l": true, "full": true, - "ranndom": true, + "random": true, "disco": true, "hay": true, "mqs": false, diff --git a/config.json b/config.json index cd241672b..f4c95a6a3 100644 --- a/config.json +++ b/config.json @@ -13,7 +13,7 @@ "m": true, "l": true, "full": true, - "ranndom": true, + "random": true, "disco": true, "hay": true, "mqs": true, @@ -30,4 +30,4 @@ }, "patternStates": { } - } \ No newline at end of file + } From 578d14963774a55517f26b67b568cee06432d109 Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Thu, 8 Jan 2015 22:40:38 -0600 Subject: [PATCH 02/19] pattern export. closes #82 --- CHANGELOG | 3 +++ Gruntfile.js | 4 ++++ README.md | 18 ++++++++++---- builder/lineage_hunter.js | 2 +- builder/media_hunter.js | 2 +- builder/object_factory.js | 3 ++- builder/pattern_exporter.js | 48 +++++++++++++++++++++++++++++++++++++ builder/patternlab.js | 9 +++++-- builder/patternlab_grunt.js | 2 +- config.json | 4 +++- package.json | 2 +- 11 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 builder/pattern_exporter.js diff --git a/CHANGELOG b/CHANGELOG index 4c45c1c5c..4282c855d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ THIS CHANGELOG IS AN ATTEMPT TO DOCUMENT CHANGES TO THIS PROJECT. +PL-node-v0.1.7 + - ADD: pattern export + PL-node-v0.1.6 - ADD: media queries found in css added to ish controls - ADD: basic lineage support diff --git a/Gruntfile.js b/Gruntfile.js index a4ab4dac1..a8a872c16 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -31,6 +31,10 @@ module.exports = function(grunt) { patternlab_grunt: { src: './builder/patternlab_grunt.js', dest: './builder/patternlab_grunt.js' + }, + pattern_exporter: { + src: './builder/pattern_exporter.js', + dest: './builder/pattern_exporter.js' } }, copy: { diff --git a/README.md b/README.md index caadd2172..bae235580 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,6 @@ The current selection is as follows. It reflects support versus patternlab-php. "tools-docs": true } ``` - -##### Verbose Mode -`patternlab.json` is a file created for debugging purposes. Set `debug` to true in `.config.json` to see all the secrets. - ##### Pattern States You can set the state of a pattern by including it in `config.json` too. The out of the box styles are in progress (orange), in review (yellow), and complete (green). Pattern states should be lowercase and use hyphens where spaces are present. @@ -77,6 +73,20 @@ Pattern states should be lowercase and use hyphens where spaces are present. } ``` +##### Pattern Export +`config.json` also has two properties that work together to export completed patterns for use in a production environment. Provide an array of keys and an output directory. Pattern Lab doesn't ship with any pattern export keys, but the default directory is `"./pattern_exports/"` created inside the install directory. + +``` +"patternExportKeys": ["molecules-primary-nav","organisms-header", ""organisms-header""], +"patternExportDirectory": "./pattern_exports/" +``` + +Coupled with exported css (much easier to extract with existing tools like [grunt-contrib-copy](https://github.com/gruntjs/grunt-contrib-copy)), pattern export can help to maintain the relevancy of the design system by directly placing partials in a directory of your choosing. + + +##### Verbose Mode +`patternlab.json` is a file created for debugging purposes. Set `debug` to true in `.config.json` to see all the secrets. + ##### Server Running `grunt serve` will compile the patternlab front end and host it on http://localhost:9001 by default. Page will reload on any saved source code change. diff --git a/builder/lineage_hunter.js b/builder/lineage_hunter.js index 67569d554..dcabaea6f 100644 --- a/builder/lineage_hunter.js +++ b/builder/lineage_hunter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.6 - 2014 + * patternlab-node - v0.1.7 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/media_hunter.js b/builder/media_hunter.js index 7ed8f0993..e66994a14 100644 --- a/builder/media_hunter.js +++ b/builder/media_hunter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.6 - 2014 + * patternlab-node - v0.1.7 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/object_factory.js b/builder/object_factory.js index a0a2a3192..5f8308f37 100644 --- a/builder/object_factory.js +++ b/builder/object_factory.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.6 - 2014 + * patternlab-node - v0.1.7 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. @@ -23,6 +23,7 @@ this.patternGroup = name.substring(name.indexOf('-') + 1, name.indexOf('-', 4) + 1 - name.indexOf('-') + 1); this.patternSubGroup = subdir.substring(subdir.indexOf('/') + 4); this.flatPatternPath = subdir.replace(/\//g, '-'); + this.key = ''; this.lineage = []; this.lineageIndex = []; this.lineageR = []; diff --git a/builder/pattern_exporter.js b/builder/pattern_exporter.js new file mode 100644 index 000000000..8ed9ed22b --- /dev/null +++ b/builder/pattern_exporter.js @@ -0,0 +1,48 @@ +/* + * patternlab-node - v0.1.7 - 2015 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +(function () { + "use strict"; + + var fs = require('fs-extra'), + path = require('path'); + + var pattern_exporter = function(){ + + function exportPatterns(patternlab){ + + //read the config export options + var exportKeys = patternlab.config.patternExportKeys; + + //find the chosen patterns to export + for (var i = 0; i < exportKeys.length; i++){ + for (var j = 0; j < patternlab.patterns.length; j++){ + if(exportKeys[i] === patternlab.patterns[j].key){ + //write matches to the desired location + fs.outputFileSync(patternlab.config.patternExportDirectory + patternlab.patterns[j].key + '.html', patternlab.patterns[j].patternPartial); + } + } + } + + + + } + + return { + export_patterns: function(patternlab){ + exportPatterns(patternlab); + } + }; + + }; + + module.exports = pattern_exporter; + +}()); \ No newline at end of file diff --git a/builder/patternlab.js b/builder/patternlab.js index b02c286c5..e7e07f03f 100644 --- a/builder/patternlab.js +++ b/builder/patternlab.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.6 - 2014 + * patternlab-node - v0.1.7 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. @@ -17,6 +17,7 @@ var patternlab_engine = function(){ pa = require('./pattern_assembler'), mh = require('./media_hunter'), lh = require('./lineage_hunter'), + pe = require('./pattern_exporter') patternlab = {}; patternlab.package =fs.readJSONSync('./package.json'); @@ -89,6 +90,7 @@ var patternlab_engine = function(){ currentPattern = new of.oPattern(flatPatternName, subdir, filename, {}); currentPattern.patternName = patternName.substring(patternName.indexOf('-') + 1); currentPattern.data = null; + currentPattern.key = currentPattern.patternGroup + '-' + currentPattern.patternName; //see if this file has a state if(patternlab.config.patternStates[currentPattern.patternName]){ @@ -115,7 +117,6 @@ var patternlab_engine = function(){ currentPattern.patternPartial = renderPattern(currentPattern.template, patternlab.data, patternlab.partials); } - //write the compiled template to the public patterns directory currentPattern.patternLink = currentPattern.name + '/' + currentPattern.name + '.html';; //find pattern lineage @@ -148,10 +149,14 @@ var patternlab_engine = function(){ //add footer info before writing var patternFooter = renderPattern(patternlab.footer, pattern); + //write the compiled template to the public patterns directory fs.outputFileSync('./public/patterns/' + pattern.patternLink, patternlab.header + pattern.patternPartial + patternFooter); }); + //export patterns if necessary + var pattern_exporter = new pe(); + pattern_exporter.export_patterns(patternlab); } diff --git a/builder/patternlab_grunt.js b/builder/patternlab_grunt.js index 358c5318b..6c89a0fbc 100644 --- a/builder/patternlab_grunt.js +++ b/builder/patternlab_grunt.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.6 - 2014 + * patternlab-node - v0.1.7 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/config.json b/config.json index cd241672b..4087a4024 100644 --- a/config.json +++ b/config.json @@ -29,5 +29,7 @@ "tools-docs": true }, "patternStates": { - } + }, + "patternExportKeys": [], + "patternExportDirectory": "./pattern_exports/" } \ No newline at end of file diff --git a/package.json b/package.json index e2d366f83..f38dfb46a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "patternlab-node", "description": "Pattern Lab is a collection of tools to help you create atomic design systems. This is the node command line interface (CLI).", - "version": "0.1.6", + "version": "0.1.7", "devDependencies": { "grunt": "~0.4.0", "grunt-contrib-watch": "~0.6.1", From c2c256f057e5ef426fadda87c21a17b4c1fed852 Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Thu, 8 Jan 2015 23:04:04 -0600 Subject: [PATCH 03/19] Updated patternlab devDependencies. Closes #79 --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index f38dfb46a..30b7d0be9 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,17 @@ "version": "0.1.7", "devDependencies": { "grunt": "~0.4.0", - "grunt-contrib-watch": "~0.6.1", - "grunt-contrib-sass": "~0.2.2", - "grunt-contrib-copy": "~0.4.0", - "grunt-contrib-jshint": "~0.4.0", - "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-concat": "~0.3.0", - "grunt-contrib-nodeunit": "~0.3.0", - "grunt-contrib-connect": "^0.8.0", - "mustache": "~0.8.1", - "matchdep": "~0.3.0", - "fs-extra": "^0.10.0", + "grunt-contrib-watch": "^0.6.1", + "grunt-contrib-sass": "^0.8.1", + "grunt-contrib-copy": "^0.7.0", + "grunt-contrib-jshint": "^0.10.0", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-concat": "^0.5.0", + "grunt-contrib-nodeunit": "^0.4.1", + "grunt-contrib-connect": "^0.9.0", + "mustache": "^1.0.0", + "matchdep": "^0.3.0", + "fs-extra": "^0.14.0", "diveSync": "^0.2.1" }, "keywords": [ From 2e8183ab3230612411ba7d253c9d5a89c28d27c6 Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Thu, 8 Jan 2015 23:21:49 -0600 Subject: [PATCH 04/19] fixed a typo. updated CHANGELOG. added styleguide-specific.scss to the SASS task in gruntfile --- CHANGELOG | 3 +++ Gruntfile.js | 3 ++- README.md | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4282c855d..7b320d233 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,9 @@ THIS CHANGELOG IS AN ATTEMPT TO DOCUMENT CHANGES TO THIS PROJECT. PL-node-v0.1.7 - ADD: pattern export + - CHG: updated devDependencies + - FIX: fixed a type in the README and config + - THX: thanks @seanhussey for the pull request! PL-node-v0.1.6 - ADD: media queries found in css added to ish controls diff --git a/Gruntfile.js b/Gruntfile.js index a8a872c16..06f2f160d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -87,7 +87,8 @@ module.exports = function(grunt) { files: { './source/css/style.css': './source/css/style.scss', './public/styleguide/css/static.css': './public/styleguide/css/static.scss', - './public/styleguide/css/styleguide.css': './public/styleguide/css/styleguide.scss' + './public/styleguide/css/styleguide.css': './public/styleguide/css/styleguide.scss', + './public/styleguide/css/styleguide-specific.css': './public/styleguide/css/styleguide-specific.scss' } } }, diff --git a/README.md b/README.md index e1f811df4..afd23d799 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Pattern states should be lowercase and use hyphens where spaces are present. `config.json` also has two properties that work together to export completed patterns for use in a production environment. Provide an array of keys and an output directory. Pattern Lab doesn't ship with any pattern export keys, but the default directory is `"./pattern_exports/"` created inside the install directory. ``` -"patternExportKeys": ["molecules-primary-nav","organisms-header", ""organisms-header""], +"patternExportKeys": ["molecules-primary-nav", "organisms-header", "organisms-header"], "patternExportDirectory": "./pattern_exports/" ``` From 031d7e7e7f82b9fa3d9841bb2a5f797c7d251225 Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Sun, 11 Jan 2015 21:26:15 -0600 Subject: [PATCH 05/19] added livereload option to the sass watch added livereload so if someone uses this livereload will also work --- Gruntfile.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gruntfile.js b/Gruntfile.js index 06f2f160d..a3a95517a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -63,6 +63,9 @@ module.exports = function(grunt) { }, watch: { // scss: { //scss can be watched if you like + // options: { + // livereload: true + // }, // files: ['source/css/**/*.scss', 'public/styleguide/css/*.scss'], // tasks: ['default'] // }, From 1e35529adccfaa443aeea5bf6d0222d3d2167edf Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Sun, 11 Jan 2015 22:07:54 -0600 Subject: [PATCH 06/19] added encoded html and mustache versions of rendered patterns for the code viewer. fixes #73 --- builder/patternlab.js | 13 ++++++++++++- package.json | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/builder/patternlab.js b/builder/patternlab.js index e7e07f03f..d001df190 100644 --- a/builder/patternlab.js +++ b/builder/patternlab.js @@ -17,7 +17,8 @@ var patternlab_engine = function(){ pa = require('./pattern_assembler'), mh = require('./media_hunter'), lh = require('./lineage_hunter'), - pe = require('./pattern_exporter') + pe = require('./pattern_exporter'), + he = require('html-entities').AllHtmlEntities, patternlab = {}; patternlab.package =fs.readJSONSync('./package.json'); @@ -143,6 +144,8 @@ var patternlab_engine = function(){ patternlab.patterns.push(currentPattern); }); + var entity_encoder = new he(); + //render all patterns last, so lineageR works patternlab.patterns.forEach(function(pattern, index, patterns){ @@ -152,6 +155,14 @@ var patternlab_engine = function(){ //write the compiled template to the public patterns directory fs.outputFileSync('./public/patterns/' + pattern.patternLink, patternlab.header + pattern.patternPartial + patternFooter); + //write the mustache file too + fs.outputFileSync('./public/patterns/' + pattern.patternLink.replace('.html', '.mustache'), entity_encoder.encode(pattern.template)); + + //write the encoded version too + fs.outputFileSync('./public/patterns/' + pattern.patternLink.replace('.html', '.escaped.html'), entity_encoder.encode(pattern.patternPartial)); + + + }); //export patterns if necessary diff --git a/package.json b/package.json index 30b7d0be9..5788149bc 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "mustache": "^1.0.0", "matchdep": "^0.3.0", "fs-extra": "^0.14.0", - "diveSync": "^0.2.1" + "diveSync": "^0.2.1", + "html-entities": "^1.1.1" }, "keywords": [ "Pattern Lab", From 5fb37c2b8ca2e9cc23c29ce24f23c967284af90c Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Wed, 21 Jan 2015 22:24:20 -0600 Subject: [PATCH 07/19] removing PHP listeners. closes #76 --- README.md | 2 +- extras/apache/README | 37 - extras/apache/vhost.txt | 5 - listeners/contentSyncBroadcasterServer.php | 43 - listeners/lib/LICENSE | 13 - listeners/lib/SplClassLoader.php | 136 --- .../lib/Wrench/Application/Application.php | 34 - .../contentSyncBroadcasterApplication.php | 80 -- .../navSyncBroadcasterApplication.php | 71 -- listeners/lib/Wrench/BasicServer.php | 70 -- listeners/lib/Wrench/Client.php | 265 ------ listeners/lib/Wrench/Connection.php | 538 ------------ listeners/lib/Wrench/ConnectionManager.php | 332 ------- .../Wrench/Exception/BadRequestException.php | 22 - .../lib/Wrench/Exception/CloseException.php | 25 - .../Wrench/Exception/ConnectionException.php | 7 - listeners/lib/Wrench/Exception/Exception.php | 7 - .../lib/Wrench/Exception/FrameException.php | 8 - .../Wrench/Exception/HandshakeException.php | 22 - .../Exception/InvalidOriginException.php | 25 - .../lib/Wrench/Exception/PayloadException.php | 8 - .../Wrench/Exception/RateLimiterException.php | 20 - .../lib/Wrench/Exception/SocketException.php | 8 - listeners/lib/Wrench/Frame/Frame.php | 190 ---- listeners/lib/Wrench/Frame/HybiFrame.php | 376 -------- .../Listener/HandshakeRequestListener.php | 19 - listeners/lib/Wrench/Listener/Listener.php | 10 - .../lib/Wrench/Listener/OriginPolicy.php | 76 -- listeners/lib/Wrench/Listener/RateLimiter.php | 230 ----- listeners/lib/Wrench/Payload/HybiPayload.php | 22 - listeners/lib/Wrench/Payload/Payload.php | 217 ----- .../lib/Wrench/Payload/PayloadHandler.php | 110 --- .../lib/Wrench/Protocol/Hybi10Protocol.php | 35 - .../lib/Wrench/Protocol/HybiProtocol.php | 24 - listeners/lib/Wrench/Protocol/Protocol.php | 816 ------------------ .../lib/Wrench/Protocol/Rfc6455Protocol.php | 36 - listeners/lib/Wrench/Resource.php | 12 - listeners/lib/Wrench/Server.php | 303 ------- listeners/lib/Wrench/Socket/ClientSocket.php | 105 --- .../lib/Wrench/Socket/ServerClientSocket.php | 25 - listeners/lib/Wrench/Socket/ServerSocket.php | 126 --- listeners/lib/Wrench/Socket/Socket.php | 322 ------- listeners/lib/Wrench/Socket/UriSocket.php | 118 --- .../Tests/Application/EchoApplicationTest.php | 57 -- .../lib/Wrench/Tests/BasicServerTest.php | 119 --- listeners/lib/Wrench/Tests/ClientTest.php | 173 ---- .../Wrench/Tests/ConnectionManagerTest.php | 101 --- listeners/lib/Wrench/Tests/ConnectionTest.php | 386 --------- .../Tests/Frame/BaseSubclassFrameTest.php | 29 - .../lib/Wrench/Tests/Frame/FrameTest.php | 168 ---- .../lib/Wrench/Tests/Frame/HybiFrameTest.php | 14 - .../Wrench/Tests/Listener/ListenerTest.php | 23 - .../Tests/Listener/OriginPolicyTest.php | 110 --- .../Wrench/Tests/Listener/RateLimiterTest.php | 67 -- .../Wrench/Tests/Payload/HybiPayloadTest.php | 14 - .../lib/Wrench/Tests/Payload/PayloadTest.php | 140 --- .../Wrench/Tests/Protocol/ProtocolTest.php | 180 ---- .../Tests/Protocol/Rfc6455ProtocolTest.php | 14 - listeners/lib/Wrench/Tests/ServerTest.php | 78 -- .../lib/Wrench/Tests/ServerTestHelper.php | 144 ---- .../Wrench/Tests/Socket/ClientSocketTest.php | 167 ---- .../Tests/Socket/ServerClientSocketTest.php | 42 - .../Wrench/Tests/Socket/ServerSocketTest.php | 13 - .../lib/Wrench/Tests/Socket/SocketTest.php | 47 - .../lib/Wrench/Tests/Socket/UriSocketTest.php | 56 -- listeners/lib/Wrench/Tests/Test.php | 61 -- listeners/lib/Wrench/Tests/bootstrap.php | 11 - listeners/lib/Wrench/Tests/server.php | 16 - listeners/lib/Wrench/Util/Configurable.php | 67 -- listeners/lib/Wrench/Util/Ssl.php | 51 -- listeners/navSyncBroadcasterServer.php | 42 - source/_patternlab-files/index.mustache | 2 - .../partials/websockets.mustache | 6 - 73 files changed, 1 insertion(+), 7347 deletions(-) delete mode 100644 extras/apache/README delete mode 100644 extras/apache/vhost.txt delete mode 100644 listeners/contentSyncBroadcasterServer.php delete mode 100644 listeners/lib/LICENSE delete mode 100644 listeners/lib/SplClassLoader.php delete mode 100644 listeners/lib/Wrench/Application/Application.php delete mode 100644 listeners/lib/Wrench/Application/contentSyncBroadcasterApplication.php delete mode 100644 listeners/lib/Wrench/Application/navSyncBroadcasterApplication.php delete mode 100644 listeners/lib/Wrench/BasicServer.php delete mode 100644 listeners/lib/Wrench/Client.php delete mode 100644 listeners/lib/Wrench/Connection.php delete mode 100644 listeners/lib/Wrench/ConnectionManager.php delete mode 100644 listeners/lib/Wrench/Exception/BadRequestException.php delete mode 100644 listeners/lib/Wrench/Exception/CloseException.php delete mode 100644 listeners/lib/Wrench/Exception/ConnectionException.php delete mode 100644 listeners/lib/Wrench/Exception/Exception.php delete mode 100644 listeners/lib/Wrench/Exception/FrameException.php delete mode 100644 listeners/lib/Wrench/Exception/HandshakeException.php delete mode 100644 listeners/lib/Wrench/Exception/InvalidOriginException.php delete mode 100644 listeners/lib/Wrench/Exception/PayloadException.php delete mode 100644 listeners/lib/Wrench/Exception/RateLimiterException.php delete mode 100644 listeners/lib/Wrench/Exception/SocketException.php delete mode 100644 listeners/lib/Wrench/Frame/Frame.php delete mode 100644 listeners/lib/Wrench/Frame/HybiFrame.php delete mode 100644 listeners/lib/Wrench/Listener/HandshakeRequestListener.php delete mode 100644 listeners/lib/Wrench/Listener/Listener.php delete mode 100644 listeners/lib/Wrench/Listener/OriginPolicy.php delete mode 100644 listeners/lib/Wrench/Listener/RateLimiter.php delete mode 100644 listeners/lib/Wrench/Payload/HybiPayload.php delete mode 100644 listeners/lib/Wrench/Payload/Payload.php delete mode 100644 listeners/lib/Wrench/Payload/PayloadHandler.php delete mode 100644 listeners/lib/Wrench/Protocol/Hybi10Protocol.php delete mode 100644 listeners/lib/Wrench/Protocol/HybiProtocol.php delete mode 100644 listeners/lib/Wrench/Protocol/Protocol.php delete mode 100644 listeners/lib/Wrench/Protocol/Rfc6455Protocol.php delete mode 100644 listeners/lib/Wrench/Resource.php delete mode 100644 listeners/lib/Wrench/Server.php delete mode 100644 listeners/lib/Wrench/Socket/ClientSocket.php delete mode 100644 listeners/lib/Wrench/Socket/ServerClientSocket.php delete mode 100644 listeners/lib/Wrench/Socket/ServerSocket.php delete mode 100644 listeners/lib/Wrench/Socket/Socket.php delete mode 100644 listeners/lib/Wrench/Socket/UriSocket.php delete mode 100644 listeners/lib/Wrench/Tests/Application/EchoApplicationTest.php delete mode 100644 listeners/lib/Wrench/Tests/BasicServerTest.php delete mode 100644 listeners/lib/Wrench/Tests/ClientTest.php delete mode 100644 listeners/lib/Wrench/Tests/ConnectionManagerTest.php delete mode 100644 listeners/lib/Wrench/Tests/ConnectionTest.php delete mode 100644 listeners/lib/Wrench/Tests/Frame/BaseSubclassFrameTest.php delete mode 100644 listeners/lib/Wrench/Tests/Frame/FrameTest.php delete mode 100644 listeners/lib/Wrench/Tests/Frame/HybiFrameTest.php delete mode 100644 listeners/lib/Wrench/Tests/Listener/ListenerTest.php delete mode 100644 listeners/lib/Wrench/Tests/Listener/OriginPolicyTest.php delete mode 100644 listeners/lib/Wrench/Tests/Listener/RateLimiterTest.php delete mode 100644 listeners/lib/Wrench/Tests/Payload/HybiPayloadTest.php delete mode 100644 listeners/lib/Wrench/Tests/Payload/PayloadTest.php delete mode 100644 listeners/lib/Wrench/Tests/Protocol/ProtocolTest.php delete mode 100644 listeners/lib/Wrench/Tests/Protocol/Rfc6455ProtocolTest.php delete mode 100644 listeners/lib/Wrench/Tests/ServerTest.php delete mode 100644 listeners/lib/Wrench/Tests/ServerTestHelper.php delete mode 100644 listeners/lib/Wrench/Tests/Socket/ClientSocketTest.php delete mode 100644 listeners/lib/Wrench/Tests/Socket/ServerClientSocketTest.php delete mode 100644 listeners/lib/Wrench/Tests/Socket/ServerSocketTest.php delete mode 100644 listeners/lib/Wrench/Tests/Socket/SocketTest.php delete mode 100644 listeners/lib/Wrench/Tests/Socket/UriSocketTest.php delete mode 100644 listeners/lib/Wrench/Tests/Test.php delete mode 100644 listeners/lib/Wrench/Tests/bootstrap.php delete mode 100644 listeners/lib/Wrench/Tests/server.php delete mode 100644 listeners/lib/Wrench/Util/Configurable.php delete mode 100644 listeners/lib/Wrench/Util/Ssl.php delete mode 100644 listeners/navSyncBroadcasterServer.php delete mode 100644 source/_patternlab-files/partials/websockets.mustache diff --git a/README.md b/README.md index afd23d799..54028af67 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Node version of Pattern Lab is, at its core, a static site generator. It com ### Getting Started -To run patternlab-node, just do the following from the command line at the root of patternlab-node: +To run patternlab-node, run the following from the command line at the root of whichever directory you want to initialize your project in: 1. `npm install` 2. `npm install -g grunt-cli` diff --git a/extras/apache/README b/extras/apache/README deleted file mode 100644 index 4a58f3819..000000000 --- a/extras/apache/README +++ /dev/null @@ -1,37 +0,0 @@ -# How to Set-up Apache on Mac OS X - -This document reviews how to (hopefully) easily set-up Apache on Mac OS X to support Pattern Lab. You'll need to open Terminal. Note that PHP may flake out with the default Apache install. I can modify directions in the future to account for that. - -## 1. Modify hosts - -First, you'll want to modify your hosts file so that you can use a specific hostname for the site rather than just `127.0.0.1` or `localhost`. To do so do the following: - -1. In Terminal type `sudo vi /etc/hosts` -2. When prompted, enter the password you use to log-in -3. When the file loads type `i` -4. Using the arrow keys get to the end of the last line -5. Hit `return` and type `127.0.0.1 patternlab.localhost` -6. Hit the `esc` key and type `:wq` - -Your hosts should now be saved. - -## 2. Modify Apache - -Second, you'll want to add an Apache `VirtualHost` so that Apache will know to listen for your application at the correct hostname. - -1. In Terminal type `sudo vi /etc/apache2/extra/httpd-vhosts.conf` -2. When prompted, enter the password you use to log-in -3. When the file loads type `i` -4. Using the arrow keys get to the end of the last line -5. Hit `return` twice -6. Copy and paste the info from the `vhost.txt` file in this directory. -7. Modify `DocumentRoot` path to match the location of the your install of Pattern Lab -7. Hit the `esc` key and type `:wq` - -## 3. Restart Apache - -Last, you'll want to restart Apache so your changes take effect. Simply open System Preferences and go to the "Sharing" panel. Untick the "Web Sharing" checkbox and tick it again to restart Apache. - -## 4. Test By Visiting patternlab.localhost - -In a browser try to visit http://patternlab.localhost. You should get the Pattern Lab styleguide by default. If you get Google Search results just make sure you enter the http:// \ No newline at end of file diff --git a/extras/apache/vhost.txt b/extras/apache/vhost.txt deleted file mode 100644 index e4431fc02..000000000 --- a/extras/apache/vhost.txt +++ /dev/null @@ -1,5 +0,0 @@ - - DocumentRoot "/Users/dmolsen/Sites/patternlab/public" - ServerName patternlab.localhost - ServerAlias patternlab.*.xip.io - \ No newline at end of file diff --git a/listeners/contentSyncBroadcasterServer.php b/listeners/contentSyncBroadcasterServer.php deleted file mode 100644 index df6081108..000000000 --- a/listeners/contentSyncBroadcasterServer.php +++ /dev/null @@ -1,43 +0,0 @@ -register(); - -// parse the main config for the content sync port -if (!($config = @parse_ini_file(__DIR__."/../config/config.ini"))) { - print "Missing the configuration file. Please build it using the Pattern Lab builder.\n"; - exit; -} - -$port = ($config) ? trim($config['contentSyncPort']) : '8002'; - -// start the content sync server -$server = new \Wrench\Server('ws://0.0.0.0:'.$port.'/', array()); - -// register the application -$server->registerApplication('contentsync', new \Wrench\Application\contentSyncBroadcasterApplication()); - -print "\n"; -print "Auto-reload Server Started...\n"; -print "Use CTRL+C to stop this service...\n"; - -// run it -$server->run(); diff --git a/listeners/lib/LICENSE b/listeners/lib/LICENSE deleted file mode 100644 index 5c93f4565..000000000 --- a/listeners/lib/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - Version 2, December 2004 - - Copyright (C) 2004 Sam Hocevar - - Everyone is permitted to copy and distribute verbatim or modified - copies of this license document, and changing it is allowed as long - as the name is changed. - - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/listeners/lib/SplClassLoader.php b/listeners/lib/SplClassLoader.php deleted file mode 100644 index 2bc31f04b..000000000 --- a/listeners/lib/SplClassLoader.php +++ /dev/null @@ -1,136 +0,0 @@ -register(); - * - * @author Jonathan H. Wage - * @author Roman S. Borschel - * @author Matthew Weier O'Phinney - * @author Kris Wallsmith - * @author Fabien Potencier - */ -class SplClassLoader -{ - private $_fileExtension = '.php'; - private $_namespace; - private $_includePath; - private $_namespaceSeparator = '\\'; - - /** - * Creates a new SplClassLoader that loads classes of the - * specified namespace. - * - * @param string $ns The namespace to use. - */ - public function __construct($ns = null, $includePath = null) - { - $this->_namespace = $ns; - $this->_includePath = $includePath; - } - - /** - * Sets the namespace separator used by classes in the namespace of this class loader. - * - * @param string $sep The separator to use. - */ - public function setNamespaceSeparator($sep) - { - $this->_namespaceSeparator = $sep; - } - - /** - * Gets the namespace seperator used by classes in the namespace of this class loader. - * - * @return void - */ - public function getNamespaceSeparator() - { - return $this->_namespaceSeparator; - } - - /** - * Sets the base include path for all class files in the namespace of this class loader. - * - * @param string $includePath - */ - public function setIncludePath($includePath) - { - $this->_includePath = $includePath; - } - - /** - * Gets the base include path for all class files in the namespace of this class loader. - * - * @return string $includePath - */ - public function getIncludePath() - { - return $this->_includePath; - } - - /** - * Sets the file extension of class files in the namespace of this class loader. - * - * @param string $fileExtension - */ - public function setFileExtension($fileExtension) - { - $this->_fileExtension = $fileExtension; - } - - /** - * Gets the file extension of class files in the namespace of this class loader. - * - * @return string $fileExtension - */ - public function getFileExtension() - { - return $this->_fileExtension; - } - - /** - * Installs this class loader on the SPL autoload stack. - */ - public function register() - { - spl_autoload_register(array($this, 'loadClass')); - } - - /** - * Uninstalls this class loader from the SPL autoloader stack. - */ - public function unregister() - { - spl_autoload_unregister(array($this, 'loadClass')); - } - - /** - * Loads the given class or interface. - * - * @param string $className The name of the class to load. - * @return void - */ - public function loadClass($className) - { - if (null === $this->_namespace || $this->_namespace.$this->_namespaceSeparator === substr($className, 0, strlen($this->_namespace.$this->_namespaceSeparator))) { - $fileName = ''; - $namespace = ''; - if (false !== ($lastNsPos = strripos($className, $this->_namespaceSeparator))) { - $namespace = substr($className, 0, $lastNsPos); - $className = substr($className, $lastNsPos + 1); - $fileName = str_replace($this->_namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; - } - $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension; - - require ($this->_includePath !== null ? $this->_includePath . DIRECTORY_SEPARATOR : '') . $fileName; - } - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Application/Application.php b/listeners/lib/Wrench/Application/Application.php deleted file mode 100644 index 2132a8907..000000000 --- a/listeners/lib/Wrench/Application/Application.php +++ /dev/null @@ -1,34 +0,0 @@ -savedTimestamp = file_get_contents(__DIR__."/../../../../public/latest-change.txt"); - } else { - $this->savedTimestamp = time(); - } - - } - - /** - * When a client connects add it to the list of connected clients - */ - public function onConnect($client) { - $id = $client->getId(); - $this->clients[$id] = $client; - } - - /** - * When a client disconnects remove it from the list of connected clients - */ - public function onDisconnect($client) { - $id = $client->getId(); - unset($this->clients[$id]); - } - - /** - * Dead function in this instance - */ - public function onData($data, $client) { - // function not in use - } - - /** - * Sends out a message once a second to all connected clients containing the contents of latest-change.txt - */ - public function onUpdate() { - - if (file_exists(__DIR__."/../../../../public/latest-change.txt")) { - $readTimestamp = file_get_contents(__DIR__."/../../../../public/latest-change.txt"); - if ($readTimestamp != $this->savedTimestamp) { - print "pattern lab updated. alerting connected browsers...\n"; - foreach ($this->clients as $sendto) { - $sendto->send($readTimestamp); - } - $this->savedTimestamp = $readTimestamp; - } - } - - } - -} diff --git a/listeners/lib/Wrench/Application/navSyncBroadcasterApplication.php b/listeners/lib/Wrench/Application/navSyncBroadcasterApplication.php deleted file mode 100644 index 900343120..000000000 --- a/listeners/lib/Wrench/Application/navSyncBroadcasterApplication.php +++ /dev/null @@ -1,71 +0,0 @@ -getId(); - $this->clients[$id] = $client; - if ($this->data != null) { - $client->send(json_encode($this->data)); - } - } - - /** - * When a client disconnects remove it from the list of connected clients - */ - public function onDisconnect($client) { - $id = $client->getId(); - unset($this->clients[$id]); - } - - /** - * When receiving a message from a client, strip it to avoid cross-domain issues and send it to all clients except the one who sent it - * Also store the address as the current address for any new clients that attach - */ - public function onData($data, $client) { - - $dataDecoded = json_decode($data); - - $dataDecoded->url = "/".$dataDecoded->url; - $dataEncoded = json_encode($dataDecoded); - $testId = $client->getId(); - foreach ($this->clients as $sendto) { - if ($testId != $sendto->getId()) { - $sendto->send($dataEncoded); - } - } - - $this->data = $dataDecoded; - - } - - /** - * Dead function in this instance - */ - public function onUpdate() { - // not using for this application - } - -} diff --git a/listeners/lib/Wrench/BasicServer.php b/listeners/lib/Wrench/BasicServer.php deleted file mode 100644 index 88578a055..000000000 --- a/listeners/lib/Wrench/BasicServer.php +++ /dev/null @@ -1,70 +0,0 @@ -configureRateLimiter(); - $this->configureOriginPolicy(); - } - - /** - * @see Wrench.Server::configure() - */ - protected function configure(array $options) - { - $options = array_merge(array( - 'check_origin' => true, - 'allowed_origins' => array(), - 'origin_policy_class' => 'Wrench\Listener\OriginPolicy', - 'rate_limiter_class' => 'Wrench\Listener\RateLimiter' - ), $options); - - parent::configure($options); - } - - protected function configureRateLimiter() - { - $class = $this->options['rate_limiter_class']; - $this->rateLimiter = new $class(); - $this->rateLimiter->listen($this); - } - - /** - * Configures the origin policy - */ - protected function configureOriginPolicy() - { - $class = $this->options['origin_policy_class']; - $this->originPolicy = new $class($this->options['allowed_origins']); - - if ($this->options['check_origin']) { - $this->originPolicy->listen($this); - } - } - - /** - * Adds an allowed origin - * - * @param array $origin - */ - public function addAllowedOrigin($origin) - { - $this->originPolicy->addAllowedOrigin($origin); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Client.php b/listeners/lib/Wrench/Client.php deleted file mode 100644 index 87e8f11ba..000000000 --- a/listeners/lib/Wrench/Client.php +++ /dev/null @@ -1,265 +0,0 @@ - - */ - protected $received = array(); - - /** - * Constructor - * - * @param string $uri - * @param string $origin The origin to include in the handshake (required - * in later versions of the protocol) - * @param array $options (optional) Array of options - * - socket => Socket instance (otherwise created) - * - protocol => Protocol - */ - public function __construct($uri, $origin, array $options = array()) - { - parent::__construct($options); - - $uri = (string)$uri; - if (!$uri) { - throw new InvalidArgumentException('No URI specified'); - } - $this->uri = $uri; - - $origin = (string)$origin; - if (!$origin) { - throw new InvalidArgumentException('No origin specified'); - } - $this->origin = $origin; - - $this->protocol->validateUri($this->uri); - $this->protocol->validateOriginUri($this->origin); - - $this->configureSocket(); - $this->configurePayloadHandler(); - } - - /** - * Configure options - * - * @param array $options - * @return void - */ - protected function configure(array $options) - { - $options = array_merge(array( - 'socket_class' => 'Wrench\\Socket\\ClientSocket', - 'on_data_callback' => null - ), $options); - - parent::configure($options); - } - - /** - * Configures the client socket - */ - protected function configureSocket() - { - $class = $this->options['socket_class']; - $this->socket = new $class($this->uri); - } - - /** - * Configures the payload handler - */ - protected function configurePayloadHandler() - { - $this->payloadHandler = new PayloadHandler(array($this, 'onData'), $this->options); - } - - /** - * Payload receiver - * - * Public because called from our PayloadHandler. Don't call us, we'll call - * you (via the on_data_callback option). - * - * @param Payload $payload - */ - public function onData(Payload $payload) - { - $this->received[] = $payload; - if (($callback = $this->options['on_data_callback'])) { - call_user_func($callback, $payload); - } - } - - /** - * Adds a request header to be included in the initial handshake - * - * For example, to include a Cookie header - * - * @param string $name - * @param string $value - * @return void - */ - public function addRequestHeader($name, $value) - { - $this->headers[$name] = $value; - } - - /** - * Sends data to the socket - * - * @param string $data - * @param string $type Payload type - * @param boolean $masked - * @return boolean Success - */ - public function sendData($data, $type = Protocol::TYPE_TEXT, $masked = true) - { - if (is_string($type) && isset(Protocol::$frameTypes[$type])) { - $type = Protocol::$frameTypes[$type]; - } - - $payload = $this->protocol->getPayload(); - - $payload->encode( - $data, - $type, - $masked - ); - - return $payload->sendToSocket($this->socket); - } - - /** - * Receives data sent by the server - * - * @param callable $callback - * @return array Payload received since the last call to receive() - */ - public function receive() - { - if (!$this->isConnected()) { - return false; - } - - $data = $this->socket->receive(); - - if (!$data) { - return $data; - } - - $old = $this->received; - $this->payloadHandler->handle($data); - return array_diff($this->received, $old); - } - - /** - * Connect to the Wrench server - * - * @return boolean Whether a new connection was made - */ - public function connect() - { - if ($this->isConnected()) { - return false; - } - - $this->socket->connect(); - - $key = $this->protocol->generateKey(); - $handshake = $this->protocol->getRequestHandshake( - $this->uri, - $key, - $this->origin, - $this->headers - ); - - $this->socket->send($handshake); - $response = $this->socket->receive(self::MAX_HANDSHAKE_RESPONSE); - return ($this->connected = - $this->protocol->validateResponseHandshake($response, $key)); - } - - /** - * Whether the client is currently connected - * - * @return boolean - */ - public function isConnected() - { - return $this->connected; - } - - /** - * @todo Bug: what if connect has been called twice. The first socket never - * gets closed. - */ - public function disconnect() - { - if ($this->socket) { - $this->socket->disconnect(); - } - $this->connected = false; - } - - -} diff --git a/listeners/lib/Wrench/Connection.php b/listeners/lib/Wrench/Connection.php deleted file mode 100644 index 2538fc739..000000000 --- a/listeners/lib/Wrench/Connection.php +++ /dev/null @@ -1,538 +0,0 @@ - 'value' - * - * @var array - */ - protected $queryParams = null; - - /** - * Connection ID - * - * @var string|null - */ - protected $id = null; - - /** - * @var PayloadHandler - */ - protected $payloadHandler; - - /** - * Constructor - * - * @param Server $server - * @param ServerClientSocket $socket - * @param array $options - * @throws InvalidArgumentException - */ - public function __construct( - ConnectionManager $manager, - ServerClientSocket $socket, - array $options = array() - ) { - $this->manager = $manager; - $this->socket = $socket; - - - parent::__construct($options); - - $this->configureClientInformation(); - $this->configurePayloadHandler(); - - $this->log('Connected'); - } - - /** - * Gets the connection manager of this connection - * - * @return \Wrench\ConnectionManager - */ - public function getConnectionManager() - { - return $this->manager; - } - - /** - * @see Wrench\Util.Configurable::configure() - */ - protected function configure(array $options) - { - $options = array_merge(array( - 'connection_id_secret' => 'asu5gj656h64Da(0crt8pud%^WAYWW$u76dwb', - 'connection_id_algo' => 'sha512', - ), $options); - - parent::configure($options); - } - - protected function configurePayloadHandler() - { - $this->payloadHandler = new PayloadHandler( - array($this, 'handlePayload'), - $this->options - ); - } - - /** - * @throws RuntimeException - */ - protected function configureClientInformation() - { - $this->ip = $this->socket->getIp(); - $this->port = $this->socket->getPort(); - $this->configureClientId(); - } - - /** - * Configures the client ID - * - * We hash the client ID to prevent leakage of information if another client - * happens to get a hold of an ID. The secret *must* be lengthy, and must - * be kept secret for this to work: otherwise it's trivial to search the space - * of possible IP addresses/ports (well, if not trivial, at least very fast). - */ - protected function configureClientId() - { - $message = sprintf( - '%s:uri=%s&ip=%s&port=%s', - $this->options['connection_id_secret'], - rawurlencode($this->manager->getUri()), - rawurlencode($this->ip), - rawurlencode($this->port) - ); - - $algo = $this->options['connection_id_algo']; - - if (extension_loaded('gmp')) { - $hash = hash($algo, $message); - $hash = gmp_strval(gmp_init('0x' . $hash, 16), 62); - } else { - // @codeCoverageIgnoreStart - $hash = hash($algo, $message); - // @codeCoverageIgnoreEnd - } - - $this->id = $hash; - } - - /** - * Data receiver - * - * Called by the connection manager when the connection has received data - * - * @param string $data - */ - public function onData($data) - { - if (!$this->handshaked) { - return $this->handshake($data); - } - return $this->handle($data); - } - - /** - * Performs a websocket handshake - * - * @param string $data - * @throws BadRequestException - * @throws HandshakeException - * @throws WrenchException - */ - public function handshake($data) - { - try { - list($path, $origin, $key, $extensions, $protocol, $headers, $params) - = $this->protocol->validateRequestHandshake($data); - - $this->headers = $headers; - $this->queryParams = $params; - - $this->application = $this->manager->getApplicationForPath($path); - if (!$this->application) { - throw new BadRequestException('Invalid application'); - } - - $this->manager->getServer()->notify( - Server::EVENT_HANDSHAKE_REQUEST, - array($this, $path, $origin, $key, $extensions) - ); - - $response = $this->protocol->getResponseHandshake($key); - - if (!$this->socket->isConnected()) { - throw new HandshakeException('Socket is not connected'); - } - - if ($this->socket->send($response) === false) { - throw new HandshakeException('Could not send handshake response'); - } - - $this->handshaked = true; - - $this->log(sprintf( - 'Handshake successful: %s:%d (%s) connected to %s', - $this->getIp(), - $this->getPort(), - $this->getId(), - $path - ), 'info'); - - $this->manager->getServer()->notify( - Server::EVENT_HANDSHAKE_SUCCESSFUL, - array($this) - ); - - if (method_exists($this->application, 'onConnect')) { - $this->application->onConnect($this); - } - } catch (WrenchException $e) { - $this->log('Handshake failed: ' . $e, 'err'); - $this->close($e); - } - } - - /** - * Returns a string export of the given binary data - * - * @param string $data - * @return string - */ - protected function export($data) - { - $export = ''; - foreach (str_split($data) as $chr) { - $export .= '\\x' . ord($chr); - } - } - - /** - * Handle data received from the client - * - * The data passed in may belong to several different frames across one or - * more protocols. It may not even contain a single complete frame. This method - * manages slotting the data into separate payload objects. - * - * @todo An endpoint MUST be capable of handling control frames in the - * middle of a fragmented message. - * @param string $data - * @return void - */ - public function handle($data) - { - $this->payloadHandler->handle($data); - } - - /** - * Handle a complete payload received from the client - * - * Public because called from our PayloadHandler - * - * @param string $payload - */ - public function handlePayload(Payload $payload) - { - $app = $this->getClientApplication(); - - $this->log('Handling payload: ' . $payload->getPayload(), 'debug'); - - switch ($type = $payload->getType()) { - case Protocol::TYPE_TEXT: - if (method_exists($app, 'onData')) { - $app->onData($payload, $this); - } - return; - - case Protocol::TYPE_BINARY: - if(method_exists($app, 'onBinaryData')) { - $app->onBinaryData($payload, $this); - } else { - $this->close(1003); - } - break; - - case Protocol::TYPE_PING: - $this->log('Ping received', 'notice'); - $this->send($payload->getPayload(), Protocol::TYPE_PONG); - $this->log('Pong!', 'debug'); - break; - - /** - * A Pong frame MAY be sent unsolicited. This serves as a - * unidirectional heartbeat. A response to an unsolicited Pong - * frame is not expected. - */ - case Protocol::TYPE_PONG: - $this->log('Received unsolicited pong', 'info'); - break; - - case Protocol::TYPE_CLOSE: - $this->log('Close frame received', 'notice'); - $this->close(); - $this->log('Disconnected', 'info'); - break; - - default: - throw new ConnectionException('Unhandled payload type'); - } - } - - /** - * Sends the payload to the connection - * - * @param string $payload - * @param string $type - * @throws HandshakeException - * @throws ConnectionException - * @return boolean - */ - public function send($data, $type = Protocol::TYPE_TEXT) - { - if (!$this->handshaked) { - throw new HandshakeException('Connection is not handshaked'); - } - - $payload = $this->protocol->getPayload(); - - // Servers don't send masked payloads - $payload->encode($data, $type, false); - - if (!$payload->sendToSocket($this->socket)) { - $this->log('Could not send payload to client', 'warn'); - throw new ConnectionException('Could not send data to connection: ' . $this->socket->getLastError()); - } - - return true; - } - - /** - * Processes data on the socket - * - * @throws CloseException - */ - public function process() - { - $data = $this->socket->receive(); - $bytes = strlen($data); - - if ($bytes === 0 || $data === false) { - throw new CloseException('Error reading data from socket: ' . $this->socket->getLastError()); - } - - $this->onData($data); - } - - /** - * Closes the connection according to the WebSocket protocol - * - * If an endpoint receives a Close frame and that endpoint did not - * previously send a Close frame, the endpoint MUST send a Close frame - * in response. It SHOULD do so as soon as is practical. An endpoint - * MAY delay sending a close frame until its current message is sent - * (for instance, if the majority of a fragmented message is already - * sent, an endpoint MAY send the remaining fragments before sending a - * Close frame). However, there is no guarantee that the endpoint which - * has already sent a Close frame will continue to process data. - - * After both sending and receiving a close message, an endpoint - * considers the WebSocket connection closed, and MUST close the - * underlying TCP connection. The server MUST close the underlying TCP - * connection immediately; the client SHOULD wait for the server to - * close the connection but MAY close the connection at any time after - * sending and receiving a close message, e.g. if it has not received a - * TCP close from the server in a reasonable time period. - * - * @param int|Exception $statusCode - * @return boolean - */ - public function close($code = Protocol::CLOSE_NORMAL) - { - try { - if (!$this->handshaked) { - $response = $this->protocol->getResponseError($code); - $this->socket->send($response); - } else { - $response = $this->protocol->getCloseFrame($code); - $this->socket->send($response); - } - } catch (Exception $e) { - $this->log('Unable to send close message', 'warning'); - } - - if ($this->application && method_exists($this->application, 'onDisconnect')) { - $this->application->onDisconnect($this); - } - - $this->socket->disconnect(); - $this->manager->removeConnection($this); - } - - /** - * Logs a message - * - * @param string $message - * @param string $priority - */ - public function log($message, $priority = 'info') - { - $this->manager->log(sprintf( - '%s: %s:%d (%s): %s', - __CLASS__, - $this->getIp(), - $this->getPort(), - $this->getId(), - $message - ), $priority); - } - - /** - * Gets the IP address of the connection - * - * @return string Usually dotted quad notation - */ - public function getIp() - { - return $this->ip; - } - - /** - * Gets the port of the connection - * - * @return int - */ - public function getPort() - { - return $this->port; - } - - /** - * Gets the non-web-sockets headers included with the original request - * - * @return array - */ - public function getHeaders() - { - return $this->headers; - } - - /** - * Gets the query parameters included with the original request - * - * @return array - */ - public function getQueryParams() - { - return $this->queryParams; - } - - /** - * Gets the connection ID - * - * @return string - */ - public function getId() - { - return $this->id; - } - - /** - * Gets the socket object - * - * @return Socket\ServerClientSocket - */ - public function getSocket() - { - return $this->socket; - } - - /** - * Gets the client application - * - * @return Application - */ - public function getClientApplication() - { - return (isset($this->application)) ? $this->application : false; - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/ConnectionManager.php b/listeners/lib/Wrench/ConnectionManager.php deleted file mode 100644 index 8a503de03..000000000 --- a/listeners/lib/Wrench/ConnectionManager.php +++ /dev/null @@ -1,332 +0,0 @@ - Connection> - */ - protected $connections = array(); - - /** - * An array of raw socket resources, corresponding to connections, roughly - * - * @var array resource> - */ - protected $resources = array(); - - /** - * Constructor - * - * @param Server $server - * @param array $options - */ - public function __construct(Server $server, array $options = array()) - { - $this->server = $server; - - parent::__construct($options); - } - - /** - * @see Countable::count() - */ - public function count() - { - return count($this->connections); - } - - /** - * @see Wrench\Socket.Socket::configure() - * Options include: - * - timeout_select => int, seconds, default 0 - * - timeout_select_microsec => int, microseconds (NB: not milli), default: 200000 - */ - protected function configure(array $options) - { - $options = array_merge(array( - 'socket_master_class' => 'Wrench\Socket\ServerSocket', - 'socket_master_options' => array(), - 'socket_client_class' => 'Wrench\Socket\ServerClientSocket', - 'socket_client_options' => array(), - 'connection_class' => 'Wrench\Connection', - 'connection_options' => array(), - 'timeout_select' => self::TIMEOUT_SELECT, - 'timeout_select_microsec' => self::TIMEOUT_SELECT_MICROSEC - ), $options); - - parent::configure($options); - - $this->configureMasterSocket(); - } - - /** - * Gets the application associated with the given path - * - * @param string $path - */ - public function getApplicationForPath($path) - { - $path = ltrim($path, '/'); - return $this->server->getApplication($path); - } - - /** - * Configures the main server socket - * - * @param string $uri - */ - protected function configureMasterSocket() - { - $class = $this->options['socket_master_class']; - $options = $this->options['socket_master_options']; - $this->socket = new $class($this->server->getUri(), $options); - } - - /** - * Listens on the main socket - * - * @return void - */ - public function listen() - { - $this->socket->listen(); - $this->resources[$this->socket->getResourceId()] = $this->socket->getResource(); - } - - /** - * Gets all resources - * - * @return array resource) - */ - protected function getAllResources() - { - return array_merge($this->resources, array( - $this->socket->getResourceId() => $this->socket->getResource() - )); - } - - /** - * Returns the Connection associated with the specified socket resource - * - * @param resource $socket - * @return Connection - */ - protected function getConnectionForClientSocket($socket) - { - if (!isset($this->connections[$this->resourceId($socket)])) { - return false; - } - return $this->connections[$this->resourceId($socket)]; - } - - /** - * Select and process an array of resources - * - * @param array $resources - */ - public function selectAndProcess() - { - $read = $this->resources; - $unused_write = null; - $unsued_exception = null; - - stream_select( - $read, - $unused_write, - $unused_exception, - $this->options['timeout_select'], - $this->options['timeout_select_microsec'] - ); - - foreach ($read as $socket) { - if ($socket == $this->socket->getResource()) { - $this->processMasterSocket(); - } else { - $this->processClientSocket($socket); - } - } - } - - /** - * Process events on the master socket ($this->socket) - * - * @return void - */ - protected function processMasterSocket() - { - $new = null; - - try { - $new = $this->socket->accept(); - } catch (Exception $e) { - $this->server->log('Socket error: ' . $e, 'err'); - return; - } - - $connection = $this->createConnection($new); - $this->server->notify(Server::EVENT_SOCKET_CONNECT, array($new, $connection)); - } - - /** - * Creates a connection from a socket resource - * - * The create connection object is based on the options passed into the - * constructor ('connection_class', 'connection_options'). This connection - * instance and its associated socket resource are then stored in the - * manager. - * - * @param resource $resource A socket resource - * @return Connection - */ - protected function createConnection($resource) - { - if (!$resource || !is_resource($resource)) { - throw new InvalidArgumentException('Invalid connection resource'); - } - - $socket_class = $this->options['socket_client_class']; - $socket_options = $this->options['socket_client_options']; - - $connection_class = $this->options['connection_class']; - $connection_options = $this->options['connection_options']; - - $socket = new $socket_class($resource, $socket_options); - $connection = new $connection_class($this, $socket, $connection_options); - - $id = $this->resourceId($resource); - $this->resources[$id] = $resource; - $this->connections[$id] = $connection; - - return $connection; - } - - /** - * Process events on a client socket - * - * @param resource $socket - */ - protected function processClientSocket($socket) - { - $connection = $this->getConnectionForClientSocket($socket); - - if (!$connection) { - $this->log('No connection for client socket', 'warning'); - return; - } - - try { - $connection->process(); - } catch (CloseException $e) { - $this->log('Client connection closed: ' . $e, 'notice'); - $connection->close($e); - } catch (WrenchException $e) { - $this->log('Error on client socket: ' . $e, 'warning'); - $connection->close($e); - } - } - - /** - * This server makes an explicit assumption: PHP resource types may be cast - * to a integer. Furthermore, we assume this is bijective. Both seem to be - * true in most circumstances, but may not be guaranteed. - * - * This method (and $this->getResourceId()) exist to make this assumption - * explicit. - * - * This is needed on the connection manager as well as on resources - * - * @param resource $resource - */ - protected function resourceId($resource) - { - return (int)$resource; - } - - /** - * Gets the connection manager's listening URI - * - * @return string - */ - public function getUri() - { - return $this->server->getUri(); - } - - /** - * Logs a message - * - * @param string $message - * @param string $priority - */ - public function log($message, $priority = 'info') - { - $this->server->log(sprintf( - '%s: %s', - __CLASS__, - $message - ), $priority); - } - - /** - * @return \Wrench\Server - */ - public function getServer() - { - return $this->server; - } - - /** - * Removes a connection - * - * @param Connection $connection - */ - public function removeConnection(Connection $connection) - { - $socket = $connection->getSocket(); - - if ($socket->getResource()) { - $index = $socket->getResourceId(); - } else { - $index = array_search($connection, $this->connections); - } - - if (!$index) { - $this->log('Could not remove connection: not found', 'warning'); - } - - unset($this->connections[$index]); - unset($this->resources[$index]); - - $this->server->notify( - Server::EVENT_SOCKET_DISCONNECT, - array($connection->getSocket(), $connection) - ); - } -} diff --git a/listeners/lib/Wrench/Exception/BadRequestException.php b/listeners/lib/Wrench/Exception/BadRequestException.php deleted file mode 100644 index 8b8a1591f..000000000 --- a/listeners/lib/Wrench/Exception/BadRequestException.php +++ /dev/null @@ -1,22 +0,0 @@ -buffer) { - return false; - } - - try { - return $this->getBufferLength() >= $this->getExpectedBufferLength(); - } catch (FrameException $e) { - return false; - } - } - - /** - * Receieves data into the frame - * - * @param string $buffer - */ - public function receiveData($data) - { - $this->buffer .= $data; - } - - /** - * Gets the remaining number of bytes before this frame will be complete - * - * @return number - */ - public function getRemainingData() - { - try { - return $this->getExpectedBufferLength() - $this->getBufferLength(); - } catch (FrameException $e) { - return null; - } - } - - /** - * Whether this frame is waiting for more data - * - * @return boolean - */ - public function isWaitingForData() - { - return $this->getRemainingData() > 0; - } - - /** - * Gets the contents of the frame payload - * - * The frame must be complete to call this method. - * - * @return string - */ - public function getFramePayload() - { - if (!$this->isComplete()) { - throw new FrameException('Cannot get payload: frame is not complete'); - } - - if (!$this->payload && $this->buffer) { - $this->decodeFramePayloadFromBuffer(); - } - - return $this->payload; - } - - /** - * Gets the contents of the frame buffer - * - * This is the encoded value, receieved into the frame with receiveData(). - * - * @throws FrameException - * @return string binary - */ - public function getFrameBuffer() - { - if (!$this->buffer && $this->payload) { - throw new FrameException('Cannot get frame buffer'); - } - return $this->buffer; - } - - /** - * Gets the expected length of the frame payload - * - * @return int - */ - protected function getBufferLength() - { - return strlen($this->buffer); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Frame/HybiFrame.php b/listeners/lib/Wrench/Frame/HybiFrame.php deleted file mode 100644 index da6377d7e..000000000 --- a/listeners/lib/Wrench/Frame/HybiFrame.php +++ /dev/null @@ -1,376 +0,0 @@ -= 0 - */ - public function encode($payload, $type = Protocol::TYPE_TEXT, $masked = false) - { - if (!is_int($type) || !in_array($type, Protocol::$frameTypes)) { - throw new InvalidArgumentException('Invalid frame type'); - } - - $this->type = $type; - $this->masked = $masked; - $this->payload = $payload; - $this->length = strlen($this->payload); - $this->offset_mask = null; - $this->offset_payload = null; - - $this->buffer = "\x00\x00"; - - $this->buffer[self::BYTE_HEADER] = chr( - (self::BITFIELD_TYPE & $this->type) - | (self::BITFIELD_FINAL & PHP_INT_MAX) - ); - - $masked_bit = (self::BITFIELD_MASKED & ($this->masked ? PHP_INT_MAX : 0)); - - if ($this->length <= 125) { - $this->buffer[self::BYTE_INITIAL_LENGTH] = chr( - (self::BITFIELD_INITIAL_LENGTH & $this->length) | $masked_bit - ); - } elseif ($this->length <= 65536) { - $this->buffer[self::BYTE_INITIAL_LENGTH] = chr( - (self::BITFIELD_INITIAL_LENGTH & 126) | $masked_bit - ); - $this->buffer .= pack('n', $this->length); - } else { - $this->buffer[self::BYTE_INITIAL_LENGTH] = chr( - (self::BITFIELD_INITIAL_LENGTH & 127) | $masked_bit - ); - - if (PHP_INT_MAX > 2147483647) { - $this->buffer .= pack('NN', $this->length >> 32, $this->length); - // $this->buffer .= pack('I', $this->length); - } else { - $this->buffer .= pack('NN', 0, $this->length); - } - } - - if ($this->masked) { - $this->mask = $this->generateMask(); - $this->buffer .= $this->mask; - $this->buffer .= $this->mask($this->payload); - } else { - $this->buffer .= $this->payload; - } - - $this->offset_mask = $this->getMaskOffset(); - $this->offset_payload = $this->getPayloadOffset(); - - return $this; - } - - /** - * Masks/Unmasks the frame - * - * @param string $payload - * @return string - */ - protected function mask($payload) - { - $length = strlen($payload); - $mask = $this->getMask(); - - $unmasked = ''; - for ($i = 0; $i < $length; $i++) { - $unmasked .= $payload[$i] ^ $mask[$i % 4]; - } - - return $unmasked; - } - - /** - * Masks a payload - * - * @param string $payload - * @return string - */ - protected function unmask($payload) - { - return $this->mask($payload); - } - - public function receiveData($data) - { - if ($this->getBufferLength() <= self::BYTE_INITIAL_LENGTH) { - $this->length = null; - $this->offset_payload = null; - } - parent::receiveData($data); - } - - /** - * Gets the mask - * - * @throws FrameException - * @return string - */ - protected function getMask() - { - if (!isset($this->mask)) { - if (!$this->isMasked()) { - throw new FrameException('Cannot get mask: frame is not masked'); - } - $this->mask = substr($this->buffer, $this->getMaskOffset(), $this->getMaskSize()); - } - return $this->mask; - } - - /** - * Generates a suitable masking key - * - * @return string - */ - protected function generateMask() - { - if (extension_loaded('openssl')) { - return openssl_random_pseudo_bytes(4); - } else { - // SHA1 is 128 bit (= 16 bytes) - // So we pack it into 32 bits - return pack('N', sha1(spl_object_hash($this) . mt_rand(0, PHP_INT_MAX) . uniqid('', true), true)); - } - } - - /** - * Whether the frame is masked - * - * @return boolean - */ - public function isMasked() - { - if (!isset($this->masked)) { - if (!isset($this->buffer[1])) { - throw new FrameException('Cannot tell if frame is masked: not enough frame data received'); - } - $this->masked = (boolean)(ord($this->buffer[1]) & self::BITFIELD_MASKED); - } - return $this->masked; - } - - /** - * @see Wrench\Frame.Frame::getExpectedDataLength() - */ - protected function getExpectedBufferLength() - { - return $this->getLength() + $this->getPayloadOffset(); - } - - /** - * Gets the offset of the payload in the frame - * - * @return int - */ - protected function getPayloadOffset() - { - if (!isset($this->offset_payload)) { - $offset = $this->getMaskOffset(); - $offset += $this->getMaskSize(); - - $this->offset_payload = $offset; - } - return $this->offset_payload; - } - - /** - * Gets the offset in the frame to the masking bytes - * - * @return int - */ - protected function getMaskOffset() - { - if (!isset($this->offset_mask)) { - $offset = self::BYTE_INITIAL_LENGTH + 1; - $offset += $this->getLengthSize(); - - $this->offset_mask = $offset; - } - return $this->offset_mask; - } - - /** - * @see Wrench\Frame.Frame::getLength() - */ - public function getLength() - { - if (!$this->length) { - $initial = $this->getInitialLength(); - - if ($initial < 126) { - $this->length = $initial; - } elseif ($initial >= 126) { - // Extended payload length: 2 or 8 bytes - $start = self::BYTE_INITIAL_LENGTH + 1; - $end = self::BYTE_INITIAL_LENGTH + $this->getLengthSize(); - - if ($end > $this->getBufferLength()) { - throw new FrameException('Cannot get extended length: need more data'); - } - - $length = 0; - for ($i = $start; $i <= $end; $i++) { - $length <<= 8; - $length += ord($this->buffer[$i]); - } - - $this->length = $length; - } - } - return $this->length; - } - - /** - * Gets the inital length value, stored in the first length byte - * - * This determines how the rest of the length value is parsed out of the - * frame. - * - * @return int - */ - protected function getInitialLength() - { - if (!isset($this->buffer[self::BYTE_INITIAL_LENGTH])) { - throw new FrameException('Cannot yet tell expected length'); - } - $a = (int)(ord($this->buffer[self::BYTE_INITIAL_LENGTH]) & self::BITFIELD_INITIAL_LENGTH); - - return (int)(ord($this->buffer[self::BYTE_INITIAL_LENGTH]) & self::BITFIELD_INITIAL_LENGTH); - } - - /** - * Returns the byte size of the length part of the frame - * - * Not including the initial 7 bit part - * - * @return int - */ - protected function getLengthSize() - { - $initial = $this->getInitialLength(); - - if ($initial < 126) { - return 0; - } elseif ($initial === 126) { - return 2; - } elseif ($initial === 127) { - return 8; - } - } - - /** - * Returns the byte size of the mask part of the frame - * - * @return int - */ - protected function getMaskSize() - { - if ($this->isMasked()) { - return 4; - } - return 0; - } - - /** - * @see Wrench\Frame.Frame::decodeFramePayloadFromBuffer() - */ - protected function decodeFramePayloadFromBuffer() - { - $payload = substr($this->buffer, $this->getPayloadOffset()); - - if ($this->isMasked()) { - $payload = $this->unmask($payload); - } - - $this->payload = $payload; - } - - /** - * @see Wrench\Frame.Frame::isFinal() - */ - public function isFinal() - { - if (!isset($this->buffer[self::BYTE_HEADER])) { - throw new FrameException('Cannot yet tell if frame is final'); - } - return (boolean)(ord($this->buffer[self::BYTE_HEADER]) & self::BITFIELD_FINAL); - } - - /** - * @throws FrameException - * @see Wrench\Frame.Frame::getType() - */ - public function getType() - { - if (!isset($this->buffer[self::BYTE_HEADER])) { - throw new FrameException('Cannot yet tell type of frame'); - } - - $type = (int)(ord($this->buffer[self::BYTE_HEADER]) & self::BITFIELD_TYPE); - - if (!in_array($type, Protocol::$frameTypes)) { - throw new FrameException('Invalid payload type'); - } - - return $type; - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Listener/HandshakeRequestListener.php b/listeners/lib/Wrench/Listener/HandshakeRequestListener.php deleted file mode 100644 index 9b5b841ee..000000000 --- a/listeners/lib/Wrench/Listener/HandshakeRequestListener.php +++ /dev/null @@ -1,19 +0,0 @@ -allowed = $allowed; - } - - /** - * Handshake request listener - * - * Closes the connection on handshake from an origin that isn't allowed - * - * @param Connection $connection - * @param string $path - * @param string $origin - * @param string $key - * @param array $extensions - */ - public function onHandshakeRequest(Connection $connection, $path, $origin, $key, $extensions) - { - if (!$this->isAllowed($origin)) { - $connection->close(new InvalidOriginException('Origin not allowed')); - } - } - - /** - * Whether the specified origin is allowed under this policy - * - * @param string $origin - * @return boolean - */ - public function isAllowed($origin) - { - $scheme = parse_url($origin, PHP_URL_SCHEME); - $host = parse_url($origin, PHP_URL_HOST) ?: $origin; - - foreach ($this->allowed as $allowed) { - $allowed_scheme = parse_url($allowed, PHP_URL_SCHEME); - - if ($allowed_scheme && $scheme != $allowed_scheme) { - continue; - } - - $allowed_host = parse_url($allowed, PHP_URL_HOST) ?: $allowed; - - if ($host != $allowed_host) { - continue; - } - - return true; - } - - return false; - } - - /** - * @param Server $server - */ - public function listen(Server $server) - { - $server->addListener( - Server::EVENT_HANDSHAKE_REQUEST, - array($this, 'onHandshakeRequest') - ); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Listener/RateLimiter.php b/listeners/lib/Wrench/Listener/RateLimiter.php deleted file mode 100644 index 6ca0fe86e..000000000 --- a/listeners/lib/Wrench/Listener/RateLimiter.php +++ /dev/null @@ -1,230 +0,0 @@ - - */ - protected $ips = array(); - - /** - * Request tokens per IP address - * - * @var array> - */ - protected $requests = array(); - - /** - * Constructor - * - * @param array $options - */ - public function __construct(array $options = array()) - { - parent::__construct($options); - } - - /** - * @param array $options - */ - protected function configure(array $options) - { - $options = array_merge(array( - 'connections' => 200, // Total - 'connections_per_ip' => 5, // At once - 'requests_per_minute' => 200 // Per connection - ), $options); - - parent::configure($options); - } - - /** - * @see Wrench\Listener.Listener::listen() - */ - public function listen(Server $server) - { - $this->server = $server; - - $server->addListener( - Server::EVENT_SOCKET_CONNECT, - array($this, 'onSocketConnect') - ); - - $server->addListener( - Server::EVENT_SOCKET_DISCONNECT, - array($this, 'onSocketDisconnect') - ); - - $server->addListener( - Server::EVENT_CLIENT_DATA, - array($this, 'onClientData') - ); - } - - /** - * Event listener - * - * @param resource $socket - * @param Connection $connection - */ - public function onSocketConnect($socket, $connection) - { - $this->checkConnections($connection); - $this->checkConnectionsPerIp($connection); - } - - /** - * Event listener - * - * @param resource $socket - * @param Connection $connection - */ - public function onSocketDisconnect($socket, $connection) - { - $this->releaseConnection($connection); - } - - /** - * Event listener - * - * @param resource $socket - * @param Connection $connection - */ - public function onClientData($socket, $connection) - { - $this->checkRequestsPerMinute($connection); - } - - /** - * Idempotent - * - * @param Connection $connection - */ - protected function checkConnections($connection) - { - $connections = $connection->getConnectionManager()->count(); - - if ($connections > $this->options['connections']) { - $this->limit($connection, 'Max connections'); - } - } - - /** - * NOT idempotent, call once per connection - * - * @param Connection $connection - */ - protected function checkConnectionsPerIp($connection) - { - $ip = $connection->getIp(); - - if (!$ip) { - $this->log('Cannot check connections per IP', 'warning'); - return; - } - - if (!isset($this->ips[$ip])) { - $this->ips[$ip] = 1; - } else { - $this->ips[$ip] = min( - $this->options['connections_per_ip'], - $this->ips[$ip] + 1 - ); - } - - if ($this->ips[$ip] > $this->options['connections_per_ip']) { - $this->limit($connection, 'Connections per IP'); - } - } - - /** - * NOT idempotent, call once per disconnection - * - * @param Connection $connection - */ - protected function releaseConnection($connection) - { - $ip = $connection->getIp(); - - if (!$ip) { - $this->log('Cannot release connection', 'warning'); - return; - } - - if (!isset($this->ips[$ip])) { - $this->ips[$ip] = 0; - } else { - $this->ips[$ip] = max(0, $this->ips[$ip] - 1); - } - - unset($this->requests[$connection->getId()]); - } - - /** - * NOT idempotent, call once per data - * - * @param Connection $connection - */ - protected function checkRequestsPerMinute($connection) - { - $id = $connection->getId(); - - if (!isset($this->requests[$id])) { - $this->requests[$id] = array(); - } - - // Add current token - $this->requests[$id][] = time(); - - // Expire old tokens - while (reset($this->requests[$id]) < time() - 60) { - array_shift($this->requests[$id]); - } - - if (count($this->requests[$id]) > $this->options['requests_per_minute']) { - $this->limit($connection, 'Requests per minute'); - } - } - - /** - * Limits the given connection - * - * @param Connection $connection - * @param string $limit Reason - */ - protected function limit($connection, $limit) - { - $this->log(sprintf( - 'Limiting connection %s: %s', - $connection->getIp(), - $limit - ), 'notice'); - - $connection->close(new RateLimiterException($limit)); - } - - /** - * Logger - * - * @param string $message - * @param string $priority - */ - public function log($message, $priority = 'info') - { - $this->server->log('RateLimiter: ' . $message, $priority); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Payload/HybiPayload.php b/listeners/lib/Wrench/Payload/HybiPayload.php deleted file mode 100644 index 92d4821ab..000000000 --- a/listeners/lib/Wrench/Payload/HybiPayload.php +++ /dev/null @@ -1,22 +0,0 @@ - - */ - protected $frames = array(); - - /** - * Gets the current frame for the payload - * - * @return mixed - */ - protected function getCurrentFrame() - { - if (empty($this->frames)) { - array_push($this->frames, $this->getFrame()); - } - return end($this->frames); - } - - /** - * Gets the frame into which data should be receieved - * - * @throws PayloadException - * @return Frame - */ - protected function getReceivingFrame() - { - $current = $this->getCurrentFrame(); - - if ($current->isComplete()) { - if ($current->isFinal()) { - throw new PayloadException('Payload cannot receieve data: it is already complete'); - } else { - $current = array_push($this->frames, $this->getFrame()); - } - } - - return $current; - } - - /** - * Get a frame object - * - * @return Frame - */ - abstract protected function getFrame(); - - /** - * Whether the payload is complete - * - * @return boolean - */ - public function isComplete() - { - return $this->getCurrentFrame()->isComplete() && $this->getCurrentFrame()->isFinal(); - } - - /** - * Encodes a payload - * - * @param string $data - * @param int $type - * @param boolean $masked - * @return Payload - * @todo No splitting into multiple frames just yet - */ - public function encode($data, $type = Protocol::TYPE_TEXT, $masked = false) - { - $this->frames = array(); - - $frame = $this->getFrame(); - array_push($this->frames, $frame); - - $frame->encode($data, $type, $masked); - - return $this; - } - - /** - * Gets the number of remaining bytes before this payload will be - * complete - * - * May return 0 (no more bytes required) or null (unknown number of bytes - * required). - * - * @return number|NULL - */ - public function getRemainingData() - { - if ($this->isComplete()) { - return 0; - } - - try { - if ($this->getCurrentFrame()->isFinal()) { - return $this->getCurrentFrame()->getRemainingData(); - } - } catch (FrameException $e) { - return null; - } - - return null; - } - - /** - * Whether this payload is waiting for more data - * - * @return boolean - */ - public function isWaitingForData() - { - return $this->getRemainingData() > 0; - } - - /** - * @param Socket $socket - * @return boolean - */ - public function sendToSocket(Socket $socket) - { - $success = true; - foreach ($this->frames as $frame) { - $success = $success && ($socket->send($frame->getFrameBuffer()) !== false); - } - return $success; - } - - /** - * Receive raw data into the payload - * - * @param string $data - * @return void - */ - public function receiveData($data) - { - while ($data) { - $frame = $this->getReceivingFrame(); - - $size = strlen($data); - $remaining = $frame->getRemainingData(); - - if ($remaining === null) { - $chunk_size = 2; - } elseif ($remaining > 0) { - $chunk_size = $remaining; - } - - $chunk_size = min(strlen($data), $chunk_size); - $chunk = substr($data, 0, $chunk_size); - $data = substr($data, $chunk_size); - - $frame->receiveData($chunk); - } - } - - /** - * @return string - */ - public function getPayload() - { - $this->buffer = ''; - - foreach ($this->frames as $frame) { - $this->buffer .= $frame->getFramePayload(); - } - - return $this->buffer; - } - - /** - * @return string - */ - public function __toString() - { - try { - return $this->getPayload(); - } catch (\Exception $e) { - // __toString must not throw an exception - return ''; - } - } - - /** - * Gets the type of the payload - * - * The type of a payload is taken from its first frame - * - * @throws PayloadException - * @return int - */ - public function getType() - { - if (!isset($this->frames[0])) { - throw new PayloadException('Cannot tell payload type yet'); - } - return $this->frames[0]->getType(); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Payload/PayloadHandler.php b/listeners/lib/Wrench/Payload/PayloadHandler.php deleted file mode 100644 index 2be036ff1..000000000 --- a/listeners/lib/Wrench/Payload/PayloadHandler.php +++ /dev/null @@ -1,110 +0,0 @@ -callback = $callback; - } - - /** - * Handles the raw socket data given - * - * @param string $data - */ - public function handle($data) - { - if (!$this->payload) { - $this->payload = $this->protocol->getPayload(); - } - - while ($data) { // Each iteration pulls off a single payload chunk - $size = strlen($data); - $remaining = $this->payload->getRemainingData(); - - // If we don't yet know how much data is remaining, read data into - // the payload in two byte chunks (the size of a WebSocket frame - // header to get the initial length) - // - // Then re-loop. For extended lengths, this will happen once or four - // times extra, as the extended length is read in. - if ($remaining === null) { - $chunk_size = 2; - } elseif ($remaining > 0) { - $chunk_size = $remaining; - } elseif ($remaining === 0) { - $chunk_size = 0; - } - - $chunk_size = min(strlen($data), $chunk_size); - $chunk = substr($data, 0, $chunk_size); - $data = substr($data, $chunk_size); - - $this->payload->receiveData($chunk); - - if ($remaining !== 0 && !$this->payload->isComplete()) { - continue; - } - - if ($this->payload->isComplete()) { - $this->emit($this->payload); - $this->payload = $this->protocol->getPayload(); - } else { - throw new PayloadException('Payload will not complete'); - } - } - } - - /** - * Get the current payload - * - * @return Payload - */ - public function getCurrent() - { - return $this->getPayloadHandler->getCurrent(); - } - - /** - * Emits a complete payload to the callback - * - * @param Payload $payload - */ - protected function emit(Payload $payload) - { - call_user_func($this->callback, $payload); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Protocol/Hybi10Protocol.php b/listeners/lib/Wrench/Protocol/Hybi10Protocol.php deleted file mode 100644 index 33cfe15e3..000000000 --- a/listeners/lib/Wrench/Protocol/Hybi10Protocol.php +++ /dev/null @@ -1,35 +0,0 @@ -= 10) { - return true; - } - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Protocol/HybiProtocol.php b/listeners/lib/Wrench/Protocol/HybiProtocol.php deleted file mode 100644 index 1d73c2fab..000000000 --- a/listeners/lib/Wrench/Protocol/HybiProtocol.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ - protected static $schemes = array( - self::SCHEME_WEBSOCKET, - self::SCHEME_WEBSOCKET_SECURE, - self::SCHEME_UNDERLYING, - self::SCHEME_UNDERLYING_SECURE - ); - - /** - * Close status codes - * - * @var array string> - */ - public static $closeReasons = array( - self::CLOSE_NORMAL => 'normal close', - self::CLOSE_GOING_AWAY => 'going away', - self::CLOSE_PROTOCOL_ERROR => 'protocol error', - self::CLOSE_DATA_INVALID => 'data invalid', - self::CLOSE_DATA_INCONSISTENT => 'data inconsistent', - self::CLOSE_POLICY_VIOLATION => 'policy violation', - self::CLOSE_MESSAGE_TOO_BIG => 'message too big', - self::CLOSE_EXTENSION_NEEDED => 'extension needed', - self::CLOSE_UNEXPECTED => 'unexpected error', - self::CLOSE_RESERVED => null, // Don't use these! - self::CLOSE_RESERVED_NONE => null, - self::CLOSE_RESERVED_ABNORM => null, - self::CLOSE_RESERVED_TLS => null - ); - - /** - * Frame types - * - * @todo flip values and keys? - * @var array int> - */ - public static $frameTypes = array( - 'continuation' => self::TYPE_CONTINUATION, - 'text' => self::TYPE_TEXT, - 'binary' => self::TYPE_BINARY, - 'close' => self::TYPE_CLOSE, - 'ping' => self::TYPE_PING, - 'pong' => self::TYPE_PONG - ); - - /** - * HTTP errors - * - * @var array string> - */ - public static $httpResponses = array( - self::HTTP_SWITCHING_PROTOCOLS => 'Switching Protocols', - self::HTTP_BAD_REQUEST => 'Bad Request', - self::HTTP_UNAUTHORIZED => 'Unauthorized', - self::HTTP_FORBIDDEN => 'Forbidden', - self::HTTP_NOT_FOUND => 'Not Found', - self::HTTP_NOT_IMPLEMENTED => 'Not Implemented', - self::HTTP_RATE_LIMITED => 'Enhance Your Calm' - ); - - /** - * Gets a version number - * - * @return - */ - abstract public function getVersion(); - - /** - * Subclasses should implement this method and return a boolean to the given - * version string, as to whether they would like to accept requests from - * user agents that specify that version. - * - * @return boolean - */ - abstract public function acceptsVersion($version); - - /** - * Gets a payload instance, suitable for use in decoding/encoding protocol - * frames - * - * @return Payload - */ - abstract public function getPayload(); - - /** - * Generates a key suitable for use in the protocol - * - * This base implementation returns a 16-byte (128 bit) random key as a - * binary string. - * - * @return string - */ - public function generateKey() - { - if (extension_loaded('openssl')) { - $key = openssl_random_pseudo_bytes(16); - } else { - // SHA1 is 128 bit (= 16 bytes) - $key = sha1(spl_object_hash($this) . mt_rand(0, PHP_INT_MAX) . uniqid('', true), true); - } - - return base64_encode($key); - } - - /** - * Gets request handshake string - * - * The leading line from the client follows the Request-Line format. - * The leading line from the server follows the Status-Line format. The - * Request-Line and Status-Line productions are defined in [RFC2616]. - * - * An unordered set of header fields comes after the leading line in - * both cases. The meaning of these header fields is specified in - * Section 4 of this document. Additional header fields may also be - * present, such as cookies [RFC6265]. The format and parsing of - * headers is as defined in [RFC2616]. - * - * @param string $uri WebSocket URI, e.g. ws://example.org:8000/chat - * @param string $key 16 byte binary string key - * @param string $origin Origin of the request - * @return string - */ - public function getRequestHandshake( - $uri, - $key, - $origin, - array $headers = array() - ) { - if (!$uri || !$key || !$origin) { - throw new InvalidArgumentException('You must supply a URI, key and origin'); - } - - list($scheme, $host, $port, $path) = self::validateUri($uri); - - $handshake = array( - sprintf(self::REQUEST_LINE_FORMAT, $path) - ); - - $headers = array_merge( - $this->getDefaultRequestHeaders( - $host . ':' . $port, $key, $origin - ), - $headers - ); - - foreach ($headers as $name => $value) { - $handshake[] = sprintf(self::HEADER_LINE_FORMAT, $name, $value); - } - return implode($handshake, "\r\n") . "\r\n\r\n"; - } - - /** - * Gets a handshake response body - * - * @param string $key - * @param array $headers - */ - public function getResponseHandshake($key, array $headers = array()) - { - $headers = array_merge( - $this->getSuccessResponseHeaders( - $key - ), - $headers - ); - - return $this->getHttpResponse(self::HTTP_SWITCHING_PROTOCOLS, $headers); - } - - /** - * Gets a response to an error in the handshake - * - * @param int|Exception $e Exception or HTTP error - * @param array $headers - */ - public function getResponseError($e, array $headers = array()) - { - $code = false; - - if ($e instanceof Exception) { - $code = $e->getCode(); - } elseif (is_numeric($e)) { - $code = (int)$e; - } - - if (!$code || $code < 400 || $code > 599) { - $code = self::HTTP_SERVER_ERROR; - } - - return $this->getHttpResponse($code, $headers); - } - - /** - * Gets an HTTP response - * - * @param int $status - * @param array $headers - */ - protected function getHttpResponse($status, array $headers = array()) - { - if (array_key_exists($status, self::$httpResponses)) { - $response = self::$httpResponses[$status]; - } else { - $response = self::$httpResponses[self::HTTP_NOT_IMPLEMENTED]; - } - - $handshake = array( - sprintf(self::RESPONSE_LINE_FORMAT, $status, $response) - ); - - foreach ($headers as $name => $value) { - $handshake[] = sprintf(self::HEADER_LINE_FORMAT, $name, $value); - } - - return implode($handshake, "\r\n") . "\r\n\r\n"; - } - - /** - * @todo better header handling - * @todo throw exception - * @param unknown_type $response - * @param unknown_type $key - * @return boolean - */ - public function validateResponseHandshake($response, $key) - { - if (!$response) { - return false; - } - - $headers = $this->getHeaders($response); - - if (!isset($headers[self::HEADER_ACCEPT])) { - throw new HandshakeException('No accept header receieved on handshake response'); - } - - $accept = $headers[self::HEADER_ACCEPT]; - - if (!$accept) { - throw new HandshakeException('Invalid accept header'); - } - - $expected = $this->getAcceptValue($key); - - preg_match('#Sec-WebSocket-Accept:\s(.*)$#mU', $response, $matches); - $keyAccept = trim($matches[1]); - - return ($keyAccept === $this->getEncodedHash($key)) ? true : false; - } - - /** - * Gets an encoded hash for a key - * - * @param string $key - * @return string - */ - public function getEncodedHash($key) - { - return base64_encode(pack('H*', sha1($key . self::MAGIC_GUID))); - } - - /** - * Validates a request handshake - * - * @param string $request - * @throws BadRequestException - */ - public function validateRequestHandshake( - $request - ) { - if (!$request) { - return false; - } - - list($request, $headers) = $this->getRequestHeaders($request); - // make a copy of the headers array to store all extra headers - $extraHeaders = $headers; - - // parse the resulting url to separate query parameters from the path - $url = parse_url($this->validateRequestLine($request)); - $path = isset($url['path']) ? $url['path'] : null; - $urlParams = array(); - if (isset($url['query'])) { - parse_str($url['query'], $urlParams); - } - - if (empty($headers[self::HEADER_ORIGIN])) { - throw new BadRequestException('No origin header'); - } else { - unset($extraHeaders[self::HEADER_ORIGIN]); - } - - $origin = $headers[self::HEADER_ORIGIN]; - - if (!isset($headers[self::HEADER_UPGRADE]) - || strtolower($headers[self::HEADER_UPGRADE]) != self::UPGRADE_VALUE - ) { - throw new BadRequestException('Invalid upgrade header'); - } else { - unset($extraHeaders[self::HEADER_UPGRADE]); - } - - if (!isset($headers[self::HEADER_CONNECTION]) - || stripos($headers[self::HEADER_CONNECTION], self::CONNECTION_VALUE) === false - ) { - throw new BadRequestException('Invalid connection header'); - } else { - unset($extraHeaders[self::HEADER_CONNECTION]); - } - - if (!isset($headers[self::HEADER_HOST])) { - // @todo Validate host == listening socket? Or would that break - // TCP proxies? - throw new BadRequestException('No host header'); - } else { - unset($extraHeaders[self::HEADER_HOST]); - } - - if (!isset($headers[self::HEADER_VERSION])) { - throw new BadRequestException('No version header received on handshake request'); - } - - if (!$this->acceptsVersion($headers[self::HEADER_VERSION])) { - throw new BadRequestException('Unsupported version: ' . $version); - } else { - unset($extraHeaders[self::HEADER_VERSION]); - } - - if (!isset($headers[self::HEADER_KEY])) { - throw new BadRequestException('No key header received'); - } - - $key = trim($headers[self::HEADER_KEY]); - - if (!$key) { - throw new BadRequestException('Invalid key'); - } else { - unset($extraHeaders[self::HEADER_KEY]); - } - - // Optional - $protocol = null; - if (isset($headers[self::HEADER_PROTOCOL])) { - $protocol = $headers[self::HEADER_PROTOCOL]; - unset($extraHeaders[self::HEADER_PROTOCOL]); - } - - $extensions = array(); - if (!empty($headers[self::HEADER_EXTENSIONS])) { - $extensions = $headers[self::HEADER_EXTENSIONS]; - if (is_scalar($extensions)) { - $extensions = array($extensions); - } - } - unset($extraHeaders[self::HEADER_EXTENSIONS]); - - return array($path, $origin, $key, $extensions, $protocol, $extraHeaders, $urlParams); - } - - /** - * Gets a suitable WebSocket close frame - * - * @param Exception|int $e - */ - public function getCloseFrame($e) - { - $code = false; - - if ($e instanceof Exception) { - $code = $e->getCode(); - } elseif (is_numeric($e)) { - $code = (int)$e; - } - - if (!$code || !key_exists($code, self::$closeReasons)) { - $code = self::CLOSE_UNEXPECTED; - } - - $body = pack('n', $code) . self::$closeReasons[$code]; - - $payload = $this->getPayload(); - return $payload->encode($body, self::TYPE_CLOSE); - } - - /** - * Validates a WebSocket URI - * - * @param string $uri - * @return array(string $scheme, string $host, int $port, string $path) - */ - public function validateUri($uri) - { - $uri = (string)$uri; - if (!$uri) { - throw new InvalidArgumentException('Invalid URI'); - } - - $scheme = parse_url($uri, PHP_URL_SCHEME); - $this->validateScheme($scheme); - - $host = parse_url($uri, PHP_URL_HOST); - if (!$host) { - throw new InvalidArgumentException("Invalid host"); - } - - $port = parse_url($uri, PHP_URL_PORT); - if (!$port) { - $port = $this->getPort($scheme); - } - - $path = parse_url($uri, PHP_URL_PATH); - if (!$path) { - throw new InvalidArgumentException('Invalid path'); - } - - return array($scheme, $host, $port, $path); - } - - /** - * Validates a socket URI - * - * @param string $uri - * @throws InvalidArgumentException - * @return array(string $scheme, string $host, string $port) - */ - public function validateSocketUri($uri) - { - $uri = (string)$uri; - if (!$uri) { - throw new InvalidArgumentException('Invalid URI'); - } - - $scheme = parse_url($uri, PHP_URL_SCHEME); - $scheme = $this->validateScheme($scheme); - - $host = parse_url($uri, PHP_URL_HOST); - if (!$host) { - throw new InvalidArgumentException("Invalid host"); - } - - $port = parse_url($uri, PHP_URL_PORT); - if (!$port) { - $port = $this->getPort($scheme); - } - - return array($scheme, $host, $port); - } - - /** - * Validates an origin URI - * - * @param string $origin - * @throws InvalidArgumentException - * @return string - */ - public function validateOriginUri($origin) - { - $origin = (string)$origin; - if (!$origin) { - throw new InvalidArgumentException('Invalid URI'); - } - - $scheme = parse_url($origin, PHP_URL_SCHEME); - if (!$scheme) { - throw new InvalidArgumentException('Invalid scheme'); - } - - $host = parse_url($origin, PHP_URL_HOST); - if (!$host) { - throw new InvalidArgumentException("Invalid host"); - } - - return $origin; - } - - /** - * Validates a request line - * - * @param string $line - * @throws BadRequestException - */ - protected function validateRequestLine($line) - { - $matches = array(0 => null, 1 => null); - - if (!preg_match(self::REQUEST_LINE_REGEX, $line, $matches) || !$matches[1]) { - throw new BadRequestException('Invalid request line', 400); - } - - return $matches[1]; - } - - /** - * Gets the expected accept value for a handshake response - * - * Note that the protocol calls for the base64 encoded value to be hashed, - * not the original 16 byte random key. - * - * @see http://tools.ietf.org/html/rfc6455#section-4.2.2 - * @param string $key - */ - protected function getAcceptValue($encoded_key) - { - return base64_encode(sha1($encoded_key . self::MAGIC_GUID, true)); - } - - /** - * Gets the headers from a full response - * - * @param string $response - * @return array() - * @throws InvalidArgumentException - */ - protected function getHeaders($response, &$request_line = null) - { - $parts = explode("\r\n\r\n", $response, 2); - - if (count($parts) != 2) { - $parts = array($parts, ''); - } - - list($headers, $body) = $parts; - - $return = array(); - foreach (explode("\r\n", $headers) as $header) { - $parts = explode(': ', $header, 2); - if (count($parts) == 2) { - list($name, $value) = $parts; - if (!isset($return[$name])) { - $return[$name] = $value; - } else { - if (is_array($return[$name])) { - $return[$name][] = $value; - } else { - $return[$name] = array($return[$name], $value); - } - } - } - } - - return $return; - } - - /** - * Gets request headers - * - * @param string $response - * @return array> The request line, and an array of - * headers - * @throws InvalidArgumentException - */ - protected function getRequestHeaders($response) - { - $eol = stripos($response, "\r\n"); - - if ($eol === false) { - throw new InvalidArgumentException('Invalid request line'); - } - - $request = substr($response, 0, $eol); - $headers = $this->getHeaders(substr($response, $eol + 2)); - - return array($request, $headers); - } - - /** - * Validates a scheme - * - * @param string $scheme - * @return string Underlying scheme - * @throws InvalidArgumentException - */ - protected function validateScheme($scheme) - { - if (!$scheme) { - throw new InvalidArgumentException('No scheme specified'); - } - if (!in_array($scheme, self::$schemes)) { - throw new InvalidArgumentException( - 'Unknown socket scheme: ' . $scheme - ); - } - - if ($scheme == self::SCHEME_WEBSOCKET_SECURE) { - return self::SCHEME_UNDERLYING_SECURE; - } - return self::SCHEME_UNDERLYING; - } - - /** - * Gets the default request headers - * - * @param string $host - * @param string $key - * @param string $origin - * @param int $version - * @return multitype:unknown string NULL - */ - protected function getDefaultRequestHeaders($host, $key, $origin) - { - return array( - self::HEADER_HOST => $host, - self::HEADER_UPGRADE => self::UPGRADE_VALUE, - self::HEADER_CONNECTION => self::CONNECTION_VALUE, - self::HEADER_KEY => $key, - self::HEADER_ORIGIN => $origin, - self::HEADER_VERSION => $this->getVersion() - ); - } - - /** - * Gets the default response headers - * - * @param string $key - */ - protected function getSuccessResponseHeaders($key) - { - return array( - self::HEADER_UPGRADE => self::UPGRADE_VALUE, - self::HEADER_CONNECTION => self::CONNECTION_VALUE, - self::HEADER_ACCEPT => $this->getAcceptValue($key) - ); - } - - /** - * Gets the default port for a scheme - * - * By default, the WebSocket Protocol uses port 80 for regular WebSocket - * connections and port 443 for WebSocket connections tunneled over - * Transport Layer Security - * - * @param string $uri - * @return int - */ - protected function getPort($scheme) - { - if ($scheme == self::SCHEME_WEBSOCKET) { - return 80; - } elseif ($scheme == self::SCHEME_WEBSOCKET_SECURE) { - return 443; - } elseif ($scheme == self::SCHEME_UNDERLYING) { - return 80; - } elseif ($scheme == self::SCHEME_UNDERLYING_SECURE) { - return 443; - } else { - throw new InvalidArgumentException('Unknown websocket scheme'); - } - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Protocol/Rfc6455Protocol.php b/listeners/lib/Wrench/Protocol/Rfc6455Protocol.php deleted file mode 100644 index b0f5c5289..000000000 --- a/listeners/lib/Wrench/Protocol/Rfc6455Protocol.php +++ /dev/null @@ -1,36 +0,0 @@ - - * @author Simon Samtleben - * @author Dominic Scheirlinck - */ -class Server extends Configurable -{ - /**#@+ - * Events - * - * @var string - */ - const EVENT_SOCKET_CONNECT = 'socket_connect'; - const EVENT_SOCKET_DISCONNECT = 'socket_disconnect'; - const EVENT_HANDSHAKE_REQUEST = 'handshake_request'; - const EVENT_HANDSHAKE_SUCCESSFUL = 'handshake_successful'; - const EVENT_CLIENT_DATA = 'client_data'; - /**#@-*/ - - /** - * The URI of the server - * - * @var string - */ - protected $uri; - - /** - * Options - * - * @var array - */ - protected $options = array(); - - /** - * A logging callback - * - * The default callback simply prints to stdout. You can pass your own logger - * in the options array. It should take a string message and string priority - * as parameters. - * - * @var Closure - */ - protected $logger; - - /** - * Event listeners - * - * Add listeners using the addListener() method. - * - * @var array array> - */ - protected $listeners = array(); - - /** - * Connection manager - * - * @var ConnectionManager - */ - protected $connectionManager; - - /** - * Applications - * - * @var array Application> - */ - protected $applications = array(); - - /** - * Constructor - * - * @param string $uri Websocket URI, e.g. ws://localhost:8000/, path will - * be ignored - * @param array $options (optional) See configure - */ - public function __construct($uri, array $options = array()) - { - $this->uri = $uri; - - parent::__construct($options); - - $this->log('Server initialized', 'info'); - } - - /** - * Configure options - * - * Options include - * - socket_class => The socket class to use, defaults to ServerSocket - * - socket_options => An array of socket options - * - logger => Closure($message, $priority = 'info'), used - * for logging - * - * @param array $options - * @return void - */ - protected function configure(array $options) - { - $options = array_merge(array( - 'connection_manager_class' => 'Wrench\ConnectionManager', - 'connection_manager_options' => array() - ), $options); - - parent::configure($options); - - $this->configureConnectionManager(); - $this->configureLogger(); - } - - /** - * Configures the logger - * - * @return void - */ - protected function configureLogger() - { - // Default logger - if (!isset($this->options['logger'])) { - $this->options['logger'] = function ($message, $priority = 'info') { - printf("%s: %s%s", $priority, $message, PHP_EOL); - }; - } - $this->setLogger($this->options['logger']); - } - - /** - * Configures the connection manager - * - * @return void - */ - protected function configureConnectionManager() - { - $class = $this->options['connection_manager_class']; - $options = $this->options['connection_manager_options']; - $this->connectionManager = new $class($this, $options); - } - - /** - * Gets the connection manager - * - * @return \Wrench\ConnectionManager - */ - public function getConnectionManager() - { - return $this->connectionManager; - } - - /** - * @return string - */ - public function getUri() - { - return $this->uri; - } - - /** - * Sets a logger - * - * @param Closure $logger - * @return void - */ - public function setLogger($logger) - { - if (!is_callable($logger)) { - throw new \InvalidArgumentException('Logger must be callable'); - } - $this->logger = $logger; - } - - /** - * Main server loop - * - * @return void This method does not return! - */ - public function run() - { - $this->connectionManager->listen(); - - while (true) { - /* - * If there's nothing changed on any of the sockets, the server - * will sleep and other processes will have a change to run. Control - * this behaviour with the timeout options. - */ - $this->connectionManager->selectAndProcess(); - - /* - * If the application wants to perform periodic operations or queries and push updates to clients based on the result then that logic can be implemented in the 'onUpdate' method. - */ - foreach($this->applications as $application) { - if(method_exists($application, 'onUpdate')) { - $application->onUpdate(); - } - } - } - } - - /** - * Logs a message to the server log - * - * The default logger simply prints the message to stdout. You can provide - * a logging closure. This is useful, for instance, if you've daemonized - * and closed STDOUT. - * - * @param string $message Message to display. - * @param string $type Type of message. - * @return void - */ - public function log($message, $priority = 'info') - { - call_user_func($this->logger, $message, $priority); - } - - /** - * Notifies listeners of an event - * - * @param string $event - * @param array $arguments Event arguments - * @return void - */ - public function notify($event, array $arguments = array()) - { - if (!isset($this->listeners[$event])) { - return; - } - - foreach ($this->listeners[$event] as $listener) { - call_user_func_array($listener, $arguments); - } - } - - /** - * Adds a listener - * - * Provide an event (see the Server::EVENT_* constants) and a callback - * closure. Some arguments may be provided to your callback, such as the - * connection the caused the event. - * - * @param string $event - * @param Closure $callback - * @return void - * @throws InvalidArgumentException - */ - public function addListener($event, $callback) - { - if (!isset($this->listeners[$event])) { - $this->listeners[$event] = array(); - } - - if (!is_callable($callback)) { - throw new InvalidArgumentException('Invalid listener'); - } - - $this->listeners[$event][] = $callback; - } - - /** - * Returns a server application. - * - * @param string $key Name of application. - * @return Application The application object. - */ - public function getApplication($key) - { - if (empty($key)) { - return false; - } - - if (array_key_exists($key, $this->applications)) { - return $this->applications[$key]; - } - - return false; - } - - /** - * Adds a new application object to the application storage. - * - * @param string $key Name of application. - * @param object $application The application object - * @return void - */ - public function registerApplication($key, $application) - { - $this->applications[$key] = $application; - } -} diff --git a/listeners/lib/Wrench/Socket/ClientSocket.php b/listeners/lib/Wrench/Socket/ClientSocket.php deleted file mode 100644 index 9f5fbe293..000000000 --- a/listeners/lib/Wrench/Socket/ClientSocket.php +++ /dev/null @@ -1,105 +0,0 @@ - int, seconds, default 2 - */ -class ClientSocket extends UriSocket -{ - /** - * Default connection timeout - * - * @var int seconds - */ - const TIMEOUT_CONNECT = 2; - - /** - * @see Wrench\Socket.Socket::configure() - * Options include: - * - ssl_verify_peer => boolean, whether to perform peer verification - * of SSL certificate used - * - ssl_allow_self_signed => boolean, whether ssl_verify_peer allows - * self-signed certs - * - timeout_connect => int, seconds, default 2 - */ - protected function configure(array $options) - { - $options = array_merge(array( - 'timeout_connect' => self::TIMEOUT_CONNECT, - 'ssl_verify_peer' => false, - 'ssl_allow_self_signed' => true - ), $options); - - parent::configure($options); - } - - /** - * Connects to the given socket - */ - public function connect() - { - if ($this->isConnected()) { - return true; - } - - $errno = null; - $errstr = null; - - $this->socket = stream_socket_client( - $this->getUri(), - $errno, - $errstr, - $this->options['timeout_connect'], - STREAM_CLIENT_CONNECT, - $this->getStreamContext() - ); - - if (!$this->socket) { - throw new \Wrench\Exception\ConnectionException(sprintf( - 'Could not connect to socket: %s (%d)', - $errstr, - $errno - )); - } - - stream_set_timeout($this->socket, $this->options['timeout_socket']); - - return ($this->connected = true); - } - - public function reconnect() - { - $this->disconnect(); - $this->connect(); - } - - /** - * @see Wrench\Socket.UriSocket::getSocketStreamContextOptions() - */ - protected function getSocketStreamContextOptions() - { - $options = array(); - return $options; - } - - /** - * @see Wrench\Socket.UriSocket::getSslStreamContextOptions() - */ - protected function getSslStreamContextOptions() - { - $options = array(); - - if ($this->options['ssl_verify_peer']) { - $options['verify_peer'] = true; - } - - if ($this->options['ssl_allow_self_signed']) { - $options['allow_self_signed'] = true; - } - - return $options; - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Socket/ServerClientSocket.php b/listeners/lib/Wrench/Socket/ServerClientSocket.php deleted file mode 100644 index 61583f6cc..000000000 --- a/listeners/lib/Wrench/Socket/ServerClientSocket.php +++ /dev/null @@ -1,25 +0,0 @@ -connect() or whatnot. - * - * @param resource $accepted_socket - * @param array $options - */ - public function __construct($accepted_socket, array $options = array()) - { - parent::__construct($options); - - $this->socket = $accepted_socket; - $this->connected = (boolean)$accepted_socket; - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Socket/ServerSocket.php b/listeners/lib/Wrench/Socket/ServerSocket.php deleted file mode 100644 index facb89f86..000000000 --- a/listeners/lib/Wrench/Socket/ServerSocket.php +++ /dev/null @@ -1,126 +0,0 @@ - int, used to limit the number of outstanding - * connections in the socket's listen queue - * - ssl_cert_file => string, server SSL certificate - * file location. File should contain - * certificate and private key - * - ssl_passphrase => string, passphrase for the key - * - timeout_accept => int, seconds, default 5 - */ - protected function configure(array $options) - { - $options = array_merge(array( - 'backlog' => 50, - 'ssl_cert_file' => null, - 'ssl_passphrase' => null, - 'ssl_allow_self_signed' => false, - 'timeout_accept' => self::TIMEOUT_ACCEPT - ), $options); - - parent::configure($options); - } - - /** - * Listens - * - * @throws ConnectionException - */ - public function listen() - { - $this->socket = stream_socket_server( - $this->getUri(), - $errno, - $errstr, - STREAM_SERVER_BIND|STREAM_SERVER_LISTEN. - $this->getStreamContext() - ); - - if (!$this->socket) { - throw new ConnectionException(sprintf( - 'Could not listen on socket: %s (%d)', - $errstr, - $errno - )); - } - - $this->listening = true; - } - - /** - * Accepts a new connection on the socket - * - * @throws ConnectionException - * @return resource - */ - public function accept() - { - $new = stream_socket_accept( - $this->socket, - $this->options['timeout_accept'] - ); - - if (!$new) { - throw new ConnectionException(socket_strerror(socket_last_error($new))); - } - - return $new; - } - - /** - * @see Wrench\Socket.UriSocket::getSocketStreamContextOptions() - */ - protected function getSocketStreamContextOptions() - { - $options = array(); - - if ($this->options['backlog']) { - $options['backlog'] = $this->options['backlog']; - } - - return $options; - } - - /** - * @see Wrench\Socket.UriSocket::getSslStreamContextOptions() - */ - protected function getSslStreamContextOptions() - { - $options = array(); - - if ($this->options['server_ssl_cert_file']) { - $options['local_cert'] = $this->options['server_ssl_cert_file']; - if ($this->options['server_ssl_passphrase']) { - $options['passphrase'] = $this->options['server_ssl_passphrase']; - } - } - - return $options; - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Socket/Socket.php b/listeners/lib/Wrench/Socket/Socket.php deleted file mode 100644 index 97f6b28cc..000000000 --- a/listeners/lib/Wrench/Socket/Socket.php +++ /dev/null @@ -1,322 +0,0 @@ - int, seconds, default 2 - * - timeout_socket => int, seconds, default 5 - * - * @param array $options - * @return void - */ - protected function configure(array $options) - { - $options = array_merge(array( - 'timeout_socket' => self::TIMEOUT_SOCKET, - ), $options); - - parent::configure($options); - } - - /** - * Gets the name of the socket - */ - protected function getName() - { - if (!isset($this->name) || !$this->name) { - $this->name = @stream_socket_get_name($this->socket, true); - } - return $this->name; - } - - /** - * Gets part of the name of the socket - * - * PHP seems to return IPV6 address/port combos like this: - * ::1:1234, where ::1 is the address and 1234 the port - * So, the part number here is either the last : delimited section (the port) - * or all the other sections (the whole initial part, the address). - * - * @param string $name (from $this->getName() usually) - * @param int<0, 1> $part - * @return string - * @throws SocketException - */ - public static function getNamePart($name, $part) - { - if (!$name) { - throw new InvalidArgumentException('Invalid name'); - } - - $parts = explode(':', $name); - - if (count($parts) < 2) { - throw new SocketException('Could not parse name parts: ' . $name); - } - - if ($part == self::NAME_PART_PORT) { - return end($parts); - } elseif ($part == self::NAME_PART_IP) { - return implode(':', array_slice($parts, 0, -1)); - } else { - throw new InvalidArgumentException('Invalid name part'); - } - - return null; - } - - /** - * Gets the IP address of the socket - * - * @return string - */ - public function getIp() - { - $name = $this->getName(); - - if ($name) { - return self::getNamePart($name, self::NAME_PART_IP); - } else { - throw new SocketException('Cannot get socket IP address'); - } - } - - /** - * Gets the port of the socket - * - * @return int - */ - public function getPort() - { - $name = $this->getName(); - - if ($name) { - return self::getNamePart($name, self::NAME_PART_PORT); - } else { - throw new SocketException('Cannot get socket IP address'); - } - } - - /** - * Get the last error that occurred on the socket - * - * @return int|string - */ - public function getLastError() - { - if ($this->isConnected() && $this->socket) { - $err = @socket_last_error($this->socket); - if ($err) { - $err = socket_strerror($err); - } - if (!$err) { - $err = 'Unknown error'; - } - return $err; - } else { - return 'Not connected'; - } - } - - /** - * Whether the socket is currently connected - * - * @return boolean - */ - public function isConnected() - { - return $this->connected; - } - - /** - * Disconnect the socket - * - * @return void - */ - public function disconnect() - { - if ($this->socket) { - stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR); - } - $this->socket = null; - $this->connected = false; - } - - /** - * @see Wrench.Resource::getResource() - */ - public function getResource() - { - return $this->socket; - } - - /** - * @see Wrench.Resource::getResourceId() - */ - public function getResourceId() - { - return (int)$this->socket; - } - - /** - * @param unknown_type $data - * @throws SocketException - * @return boolean|int The number of bytes sent or false on error - */ - public function send($data) - { - if (!$this->isConnected()) { - throw new SocketException('Socket is not connected'); - } - - $length = strlen($data); - - if ($length == 0) { - return 0; - } - - for ($i = $length; $i > 0; $i -= $written) { - $written = @fwrite($this->socket, substr($data, -1 * $i)); - - if ($written === false) { - return false; - } elseif ($written === 0) { - return false; - } - } - - return $length; - } - - /** - * Receive data from the socket - * - * @param int $length - * @return string - */ - public function receive($length = self::DEFAULT_RECEIVE_LENGTH) - { - $remaining = $length; - - $buffer = ''; - $metadata['unread_bytes'] = 0; - - do { - if (feof($this->socket)) { - return $buffer; - } - - $result = fread($this->socket, $length); - - if ($result === false) { - return $buffer; - } - - $buffer .= $result; - - if (feof($this->socket)) { - return $buffer; - } - - $continue = false; - - if ($this->firstRead == true && strlen($result) == 1) { - // Workaround Chrome behavior (still needed?) - $continue = true; - } - $this->firstRead = false; - - if (strlen($result) == $length) { - $continue = true; - } - - // Continue if more data to be read - $metadata = stream_get_meta_data($this->socket); - if ($metadata && isset($metadata['unread_bytes']) && $metadata['unread_bytes']) { - $continue = true; - $length = $metadata['unread_bytes']; - } - } while ($continue); - - return $buffer; - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Socket/UriSocket.php b/listeners/lib/Wrench/Socket/UriSocket.php deleted file mode 100644 index 8e1cbc047..000000000 --- a/listeners/lib/Wrench/Socket/UriSocket.php +++ /dev/null @@ -1,118 +0,0 @@ - Wrench\Protocol object, latest protocol - * version used if not specified - * - timeout_socket => int, seconds, default 5 - * - server_ssl_cert_file => string, server SSL certificate - * file location. File should contain - * certificate and private key - * - server_ssl_passphrase => string, passphrase for the key - * - server_ssl_allow_self_signed => boolean, whether to allows self- - * signed certs - */ - public function __construct($uri, array $options = array()) - { - parent::__construct($options); - - list($this->scheme, $this->host, $this->port) - = $this->protocol->validateSocketUri($uri); - } - - /** - * Gets the canonical/normalized URI for this socket - * - * @return string - */ - protected function getUri() - { - return sprintf( - '%s://%s:%d', - $this->scheme, - $this->host, - $this->port - ); - } - - /** - * @todo DNS lookup? Override getIp()? - * @see Wrench\Socket.Socket::getName() - */ - protected function getName() - { - return sprintf('%s:%s', $this->host, $this->port); - } - - /** - * Gets the host name - */ - public function getHost() - { - return $this->host; - } - - /** - * @see Wrench\Socket.Socket::getPort() - */ - public function getPort() - { - return $this->port; - } - - /** - * Gets a stream context - */ - protected function getStreamContext($listen = false) - { - $options = array(); - - if ($this->scheme == Protocol::SCHEME_UNDERLYING_SECURE - || $this->scheme == Protocol::SCHEME_UNDERLYING) { - $options['socket'] = $this->getSocketStreamContextOptions(); - } - - if ($this->scheme == Protocol::SCHEME_UNDERLYING_SECURE) { - $options['ssl'] = $this->getSslStreamContextOptions(); - } - - return stream_context_create( - $options, - array() - ); - } - - /** - * Returns an array of socket stream context options - * - * See http://php.net/manual/en/context.socket.php - * - * @return array - */ - abstract protected function getSocketStreamContextOptions(); - - /** - * Returns an array of ssl stream context options - * - * See http://php.net/manual/en/context.ssl.php - * - * @return array - */ - abstract protected function getSslStreamContextOptions(); -} diff --git a/listeners/lib/Wrench/Tests/Application/EchoApplicationTest.php b/listeners/lib/Wrench/Tests/Application/EchoApplicationTest.php deleted file mode 100644 index bd32aeb33..000000000 --- a/listeners/lib/Wrench/Tests/Application/EchoApplicationTest.php +++ /dev/null @@ -1,57 +0,0 @@ -assertInstanceOfClass($this->getInstance()); - } - - /** - * @param unknown_type $payload - * @dataProvider getValidPayloads - */ - public function testOnData($payload) - { - $connection = $this->getMockBuilder('Wrench\Connection') - ->disableOriginalConstructor() - ->getMock(); - - $connection - ->expects($this->once()) - ->method('send') - ->with($this->equalTo($payload), $this->equalTo(Protocol::TYPE_TEXT)) - ->will($this->returnValue(true)); - - $this->getInstance()->onData($payload, $connection); - } - - /** - * Data provider - * - * @return array> - */ - public function getValidPayloads() - { - return array( - array('asdkllakdaowidoaw noaoinosdna nwodinado ndsnd aklndiownd'), - array(' ') - ); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/BasicServerTest.php b/listeners/lib/Wrench/Tests/BasicServerTest.php deleted file mode 100644 index 20d004419..000000000 --- a/listeners/lib/Wrench/Tests/BasicServerTest.php +++ /dev/null @@ -1,119 +0,0 @@ -getInstance('ws://localhost:8000', array( - 'allowed_origins' => $allowed, - 'logger' => array($this, 'log') - )); - - $connection = $this->getMockBuilder('Wrench\Connection') - ->disableOriginalConstructor() - ->getMock(); - - $connection - ->expects($this->never()) - ->method('close') - ->will($this->returnValue(true)); - - $server->notify( - Server::EVENT_HANDSHAKE_REQUEST, - array($connection, '', $origin, '', array()) - ); - } - - /** - * @param array $allowed - * @param string $origin - * @dataProvider getInvalidOrigins - */ - public function testInvalidOriginPolicy(array $allowed, $origin) - { - $server = $this->getInstance('ws://localhost:8000', array( - 'allowed_origins' => $allowed, - 'logger' => array($this, 'log') - )); - - $connection = $this->getMockBuilder('Wrench\Connection') - ->disableOriginalConstructor() - ->getMock(); - - $connection - ->expects($this->once()) - ->method('close') - ->will($this->returnValue(true)); - - $server->notify( - Server::EVENT_HANDSHAKE_REQUEST, - array($connection, '', $origin, '', array()) - ); - } - - /** - * @see Wrench\Tests.ServerTest::getValidConstructorArguments() - */ - public function getValidConstructorArguments() - { - return array_merge(parent::getValidConstructorArguments(), array( - array( - 'ws://localhost:8000', - array('logger' => function () {}) - ) - )); - } - - /** - * Data provider - * - * @return array> - */ - public function getValidOrigins() - { - return array( - array(array('localhost'), 'localhost'), - array(array('somewhere.com'), 'somewhere.com'), - ); - } - - /** - * Data provider - * - * @return array> - */ - public function getInvalidOrigins() - { - return array( - array(array('localhost'), 'blah'), - array(array('somewhere.com'), 'somewhereelse.com'), - array(array('somewhere.com'), 'subdomain.somewhere.com') - ); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/ClientTest.php b/listeners/lib/Wrench/Tests/ClientTest.php deleted file mode 100644 index 824774eef..000000000 --- a/listeners/lib/Wrench/Tests/ClientTest.php +++ /dev/null @@ -1,173 +0,0 @@ -assertInstanceOfClass( - $client = new Client( - 'ws://localhost/test', 'http://example.org/' - ), - 'ws:// scheme, default socket' - ); - - $this->assertInstanceOfClass( - $client = new Client( - 'ws://localhost/test', 'http://example.org/', - array('socket' => $this->getMockSocket()) - ), - 'ws:// scheme, socket specified' - ); - } - - /** - * Gets a mock socket - * - * @return Socket - */ - protected function getMockSocket() - { - return $this->getMock('Wrench\Socket\ClientSocket', array(), array('wss://localhost:8000')); - } - - /** - * @expectedException PHPUnit_Framework_Error - */ - public function testConstructorSocketUnspecified() - { - $w = new Client(); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testConstructorUriInvalid() - { - $w = new Client('invalid uri', 'http://www.example.com/'); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testConstructorUriEmpty() - { - $w = new Client(null, 'http://www.example.com/'); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testConstructorUriPathUnspecified() - { - $w = new Client('ws://localhost', 'http://www.example.com/'); - } - - /** - * @expectedException PHPUnit_Framework_Error - */ - public function testConstructorOriginUnspecified() - { - $w = new Client('ws://localhost'); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testConstructorOriginEmpty() - { - $w = new Client('wss://localhost', null); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testConstructorOriginInvalid() - { - $w = new Client('ws://localhost:8000', 'NOTAVALIDURI'); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testSendInvalidType() - { - $client = new Client('ws://localhost/test', 'http://example.org/'); - $client->sendData('blah', 9999); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testSendInvalidTypeString() - { - $client = new Client('ws://localhost/test', 'http://example.org/'); - $client->sendData('blah', 'fooey'); - } - - public function testSend() - { - try { - $helper = new ServerTestHelper(); - $helper->setUp(); - - /* @var $instance Wrench\Client */ - $instance = $this->getInstance($helper->getEchoConnectionString(), 'http://www.example.com/send'); - $instance->addRequestHeader('X-Test', 'Custom Request Header'); - - $this->assertFalse($instance->receive(), 'Receive before connect'); - - $success = $instance->connect(); - $this->assertTrue($success, 'Client can connect to test server'); - $this->assertTrue($instance->isConnected()); - - $this->assertFalse($instance->connect(), 'Double connect'); - - $this->assertFalse((boolean)$instance->receive(), 'No data'); - - $bytes = $instance->sendData('foobar', 'text'); - $this->assertTrue($bytes >= 6, 'sent text frame'); - sleep(1); - - $bytes = $instance->sendData('baz', Protocol::TYPE_TEXT); - $this->assertTrue($bytes >= 3, 'sent text frame'); - sleep(1); - - $responses = $instance->receive(); - $this->assertTrue(is_array($responses)); - $this->assertCount(2, $responses); - $this->assertInstanceOf('Wrench\\Payload\\Payload', $responses[0]); - $this->assertInstanceOf('Wrench\\Payload\\Payload', $responses[1]); - - $instance->disconnect(); - - $this->assertFalse($instance->isConnected()); - } catch (\Exception $e) { - $helper->tearDown(); - throw $e; - } - - $helper->tearDown(); - } -} diff --git a/listeners/lib/Wrench/Tests/ConnectionManagerTest.php b/listeners/lib/Wrench/Tests/ConnectionManagerTest.php deleted file mode 100644 index ed3de0b03..000000000 --- a/listeners/lib/Wrench/Tests/ConnectionManagerTest.php +++ /dev/null @@ -1,101 +0,0 @@ -assertInstanceOfClass( - $instance = $this->getInstance( - $server, - $options - ), - 'Valid constructor arguments' - ); - } - - /** - * Tests the constructor - */ - public function testConstructor() - { - $this->assertInstanceOfClass( - $instance = $this->getInstance( - $this->getMockServer(), - array() - ), - 'Constructor' - ); - return $instance; - } - - /** - * @depends testConstructor - * @param ConnectionManager $instance - */ - public function testCount($instance) - { - $this->assertTrue(is_numeric($instance->count())); - } - - /** - * Data provider - */ - public function getValidConstructorArguments() - { - return array( - array($this->getMockServer(), array()) - ); - } - - /** - * Gets a mock server - */ - protected function getMockServer() - { - $server = $this->getMock('Wrench\Server', array(), array(), '', false); - - $server->registerApplication('/echo', $this->getMockApplication()); - - $server->expects($this->any()) - ->method('getUri') - ->will($this->returnValue('ws://localhost:8000/')); - - return $server; - } - - /** - * Gets a mock application - * - * @return EchoApplication - */ - protected function getMockApplication() - { - return new EchoApplication(); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/ConnectionTest.php b/listeners/lib/Wrench/Tests/ConnectionTest.php deleted file mode 100644 index 25d57ffbc..000000000 --- a/listeners/lib/Wrench/Tests/ConnectionTest.php +++ /dev/null @@ -1,386 +0,0 @@ -assertInstanceOfClass( - $instance = $this->getInstance( - $manager, - $socket, - $options - ), - 'Valid constructor arguments' - ); - - return $instance; - } - - /** - * @dataProvider getValidCloseCodes - */ - public function testClose($code) - { - $socket = $this->getMockSocket(); - - $socket->expects($this->any()) - ->method('getIp') - ->will($this->returnValue('127.0.0.1')); - - $socket->expects($this->any()) - ->method('getPort') - ->will($this->returnValue(mt_rand(1025, 50000))); - - $manager = $this->getMockConnectionManager(); - - $connection = $this->getInstance($manager, $socket); - $connection->close($code); - } - - /** - * @dataProvider getValidHandshakeData - */ - public function testHandshake($path, $request) - { - $connection = $this->getConnectionForHandshake( - $this->getConnectedSocket(), - $path, - $request - ); - $connection->handshake($request); - $connection->onData('somedata'); - $this->assertTrue($connection->send('someotherdata')); - return $connection; - } - - /** - * @dataProvider getValidHandshakeData - * @expectedException Wrench\Exception\HandshakeException - */ - public function testHandshakeBadSocket($path, $request) - { - $connection = $this->getConnectionForHandshake( - $this->getNotConnectedSocket(), - $path, - $request - ); - $connection->handshake($request); - } - - /** - * Because expectation is that only $path application is available - * - * @dataProvider getWrongPathHandshakeData - * @expectedException PHPUnit_Framework_ExpectationFailedException - */ - public function testWrongPathHandshake($path, $request) - { - $connection = $this->getConnectionForHandshake( - $this->getConnectedSocket(), - $path, - $request - ); - $connection->handshake($request); - } - - /** - * @dataProvider getValidHandleData - */ - public function testHandle($path, $request_handshake, array $requests, array $counts) - { - $connection = $this->getConnectionForHandle( - $this->getConnectedSocket(), - $path, - $request_handshake, - $counts - ); - - $connection->handshake($request_handshake); - - foreach ($requests as $request) { - $connection->handle($request); - } - - return $connection; - } - - /** - * @return Socket - */ - protected function getConnectedSocket() - { - $socket = $this->getMockSocket(); - - $socket->expects($this->any()) - ->method('isConnected') - ->will($this->returnValue(true)); - - return $socket; - } - - /** - * @return Socket - */ - protected function getNotConnectedSocket() - { - $socket = $this->getMockSocket(); - - $socket->expects($this->any()) - ->method('isConnected') - ->will($this->returnValue(false)); - - return $socket; - } - - protected function getConnectionForHandshake($socket, $path, $request) - { - $manager = $this->getMockConnectionManager(); - - $application = $this->getMockApplication(); - - $server = $this->getMock('Wrench\Server', array(), array(), '', false); - $server->registerApplication($path, $application); - - $manager->expects($this->any()) - ->method('getApplicationForPath') - ->with($path) - ->will($this->returnValue($application)); - - $manager->expects($this->any()) - ->method('getServer') - ->will($this->returnValue($server)); - - $connection = $this->getInstance($manager, $socket); - - return $connection; - } - - protected function getConnectionForHandle($socket, $path, $handshake, array $counts) - { - $connection = $this->getConnectionForHandshake($socket, $path, $handshake); - - $manager = $this->getMockConnectionManager(); - - $application = $this->getMockApplication(); - - $application->expects($this->exactly(isset($counts['onData']) ? $counts['onData'] : 0)) - ->method('onData') - ->will($this->returnValue(true)); - - $server = $this->getMock('Wrench\Server', array(), array(), '', false); - $server->registerApplication($path, $application); - - $manager->expects($this->any()) - ->method('getApplicationForPath') - ->with($path) - ->will($this->returnValue($application)); - - $manager->expects($this->exactly(isset($counts['removeConnection']) ? $counts['removeConnection'] : 0)) - ->method('removeConnection'); - - $manager->expects($this->any()) - ->method('getServer') - ->will($this->returnValue($server)); - - $connection = $this->getInstance($manager, $socket); - - return $connection; - } - - /** - * @return ConnectionManager - */ - protected function getMockConnectionManager() - { - return $this->getMock('Wrench\ConnectionManager', array(), array(), '', false); - } - - /** - * Gets a mock socket - * - * @return Socket - */ - protected function getMockSocket() - { - return $this->getMock('Wrench\Socket\ServerClientSocket', array(), array(), '', false); - } - - /** - * Gets a mock application - * - * @return EchoApplication - */ - protected function getMockApplication() - { - return $this->getMock('Wrench\Application\EchoApplication'); - } - - /** - * Data provider - * - * @return array> - */ - public function getValidCloseCodes() - { - $arguments = array(); - foreach (Protocol::$closeReasons as $code => $reason) { - $arguments[] = array($code); - } - return $arguments; - } - - /** - * Data provider - * - * @return array> - */ - public function getValidConstructorArguments() - { - $socket = $this->getMockSocket(); - - $socket->expects($this->any()) - ->method('getIp') - ->will($this->returnValue('127.0.0.1')); - - $socket->expects($this->any()) - ->method('getPort') - ->will($this->returnValue(mt_rand(1025, 50000))); - - $manager = $this->getMockConnectionManager(); - - return array( - array( - $manager, - $socket, - array('logger' => function() {}) - ), - array( - $manager, - $socket, - array('logger' => function () {}, - 'connection_id_algo' => 'sha512') - ) - ); - } - - /** - * Data provider - * - * Uses this awkward valid request array so that splitting of payloads - * across multiple calls to handle can be tested - * - * testHandle($path, $request_handshake, array $requests, array $counts) - */ - public function getValidHandleData() - { - $valid_requests = array( - array( - 'data' => array( - "\x81\xad\x2e\xab\x82\xac\x6f\xfe\xd6\xe4\x14\x8b\xf9\x8c\x0c" - ."\xde\xf1\xc9\x5c\xc5\xe3\xc1\x4b\x89\xb8\x8c\x0c\xcd\xed\xc3" - ."\x0c\x87\xa2\x8e\x5e\xca\xf1\xdf\x59\xc4\xf0\xc8\x0c\x91\xa2" - ."\x8e\x4c\xca\xf0\x8e\x53\x81\xad\xd4\xfd\x81\xfe\x95\xa8\xd5" - ."\xb6\xee\xdd\xfa\xde\xf6\x88\xf2\x9b\xa6\x93\xe0\x93\xb1\xdf" - ."\xbb\xde\xf6\x9b\xee\x91\xf6\xd1\xa1\xdc\xa4\x9c\xf2\x8d\xa3" - ."\x92\xf3\x9a\xf6\xc7\xa1\xdc\xb6\x9c\xf3\xdc\xa9\x81\x80\x8e" - ."\x12\xcd\x8e\x81\x8c\xf6\x8a\xf0\xee\x9a\xeb\x83\x9a\xd6\xe7" - ."\x95\x9d\x85\xeb\x97\x8b" // Four text frames - ), - 'counts' => array( - 'onData' => 4 - ) - ), - array( - 'data' => array( - "\x88\x80\xdc\x8e\xa2\xc5" // Close frame - ), - 'counts' => array( - 'removeConnection' => 1 - ) - ) - ); - - $data = array(); - - $handshakes = $this->getValidHandshakeData(); - - foreach ($handshakes as $handshake) { - foreach ($valid_requests as $handle_args) { - $arguments = $handshake; - $arguments[] = $handle_args['data']; - $arguments[] = $handle_args['counts']; - - $data[] = $arguments; - } - } - - return $data; - } - - /** - * Data provider - */ - public function getValidHandshakeData() - { - return array( - array( - '/chat', -"GET /chat HTTP/1.1\r -Host: server.example.com\r -Upgrade: websocket\r -Connection: Upgrade\r -Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r -Origin: http://example.com\r -Sec-WebSocket-Version: 13\r\n\r\n" - ) - ); - } - - /** - * Data provider - */ - public function getWrongPathHandshakeData() - { - return array( - array( - '/foobar', -"GET /chat HTTP/1.1\r -Host: server.example.com\r -Upgrade: websocket\r -Connection: Upgrade\r -Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r -Origin: http://example.com\r -Sec-WebSocket-Version: 13\r\n\r\n" - ), - ); - } -} diff --git a/listeners/lib/Wrench/Tests/Frame/BaseSubclassFrameTest.php b/listeners/lib/Wrench/Tests/Frame/BaseSubclassFrameTest.php deleted file mode 100644 index bcced6695..000000000 --- a/listeners/lib/Wrench/Tests/Frame/BaseSubclassFrameTest.php +++ /dev/null @@ -1,29 +0,0 @@ -getFrameBuffer(); - } - - protected function getClass() - { - return 'Wrench\Tests\Frame\BadSubclassFrame'; - } -} diff --git a/listeners/lib/Wrench/Tests/Frame/FrameTest.php b/listeners/lib/Wrench/Tests/Frame/FrameTest.php deleted file mode 100644 index 848c7103e..000000000 --- a/listeners/lib/Wrench/Tests/Frame/FrameTest.php +++ /dev/null @@ -1,168 +0,0 @@ -frame = $this->getNewFrame(); - } - - protected function getNewFrame() - { - $class = $this->getClass(); - return new $class(); - } - - /** - * @see PHPUnit_Framework_TestCase::tearDown() - */ - protected function tearDown() - { - parent::tearDown(); - unset($this->frame); - } - - /** - * @param string $payload - * @dataProvider getValidEncodePayloads - */ - public function testBijection($type, $payload, $masked) - { - // Encode the payload - $this->frame->encode($payload, $type, $masked); - - // Get the resulting buffer - $buffer = $this->frame->getFrameBuffer(); - $this->assertTrue((boolean)$buffer, 'Got raw frame buffer'); - - // And feed it back into a new frame - $frame = $this->getNewFrame(); - $frame->receiveData($buffer); - - // Check the properties of the new frame against the old, all match - $this->assertEquals( - $this->frame->getType(), - $frame->getType(), - 'Types match after encode -> receiveData' - ); - - $this->assertEquals( - $this->frame->getFramePayload(), - $frame->getFramePayload(), - 'Payloads match after encode -> receiveData' - ); - - // Masking key should not be different, because we read the buffer in directly - $this->assertEquals( - $this->frame->getFrameBuffer(), - $frame->getFrameBuffer(), - 'Raw buffers match too' - ); - - // This time, we create a new frame and read the data in with encode - $frame = $this->getNewFrame(); - $frame->encode($this->frame->getFramePayload(), $type, $masked); - - // These still match - $this->assertEquals( - $this->frame->getType(), - $frame->getType(), - 'Types match after encode -> receiveData -> encode' - ); - - $this->assertEquals( - $this->frame->getFramePayload(), - $frame->getFramePayload(), - 'Payloads match after encode -> receiveData -> encode' - ); - - // But the masking key should be different, thus, so are the buffers - if ($masked) { - $this->assertNotEquals( - $this->frame->getFrameBuffer(), - $frame->getFrameBuffer(), - 'Raw buffers don\'t match because of masking' - ); - } else { - $this->assertEquals( - $this->frame->getFramePayload(), - $frame->getFramePayload(), - 'Payloads match after encode -> receiveData -> encode' - ); - } - } - - /** - * @param string $payload - * @dataProvider getValidEncodePayloads - */ - public function testEncodeTypeReflection($type, $payload, $masked) - { - $this->frame->encode($payload, $type); - $this->assertEquals(Protocol::TYPE_TEXT, $this->frame->getType(), 'Encode retains type information'); - } - - /** - * @param string $payload - * @dataProvider getValidEncodePayloads - */ - public function testEncodeLengthReflection($type, $payload, $masked) - { - $this->frame->encode($payload, $type); - $this->assertEquals(strlen($payload), $this->frame->getLength(), 'Encode does not alter payload length'); - } - - /** - * @param string $payload - * @dataProvider getValidEncodePayloads - */ - public function testEncodePayloadReflection($type, $payload, $masked) - { - $this->frame->encode($payload, $type, $masked); - $this->assertEquals($payload, $this->frame->getFramePayload(), 'Encode retains payload information'); - } - - /** - * Data provider - * - * @return array - */ - public function getValidEncodePayloads() - { - return array( - array( - Protocol::TYPE_TEXT, - "123456\x007890!@#$%^&*()qwe\trtyuiopQWERTYUIOPasdfghjklASFGH\n - JKLzxcvbnmZXCVBNM,./<>?;[]{}-=_+\|'asdad0x11\aasdassasdasasdsd", - true - ), - array( - Protocol::TYPE_TEXT, - pack('CCCCCCC', 0x00, 0x01, 0x02, 0x03, 0x04, 0xff, 0xf0), - true - ), - array(Protocol::TYPE_TEXT, ' ', true) - ); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/Frame/HybiFrameTest.php b/listeners/lib/Wrench/Tests/Frame/HybiFrameTest.php deleted file mode 100644 index 721429513..000000000 --- a/listeners/lib/Wrench/Tests/Frame/HybiFrameTest.php +++ /dev/null @@ -1,14 +0,0 @@ -getMock('Wrench\Server', array(), array(), '', false); - - $instance->listen($server); - } - - abstract public function testConstructor(); -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/Listener/OriginPolicyTest.php b/listeners/lib/Wrench/Tests/Listener/OriginPolicyTest.php deleted file mode 100644 index 103af9bfe..000000000 --- a/listeners/lib/Wrench/Tests/Listener/OriginPolicyTest.php +++ /dev/null @@ -1,110 +0,0 @@ -getInstance(array()); - $this->assertInstanceOfClass($instance, 'No constructor arguments'); - return $instance; - } - - /** - * @dataProvider getValidArguments - * @param array $allowed - * @param string $domain - */ - public function testValidAllowed($allowed, $domain) - { - $instance = $this->getInstance($allowed); - $this->assertTrue($instance->isAllowed($domain)); - } - - /** - * @dataProvider getValidArguments - * @param array $allowed - * @param string $domain - */ - public function testValidHandshake($allowed, $domain) - { - $instance = $this->getInstance($allowed); - - $connection = $this->getMock('Wrench\Connection', array(), array(), '', false); - - $connection - ->expects($this->never()) - ->method('close'); - - $instance->onHandshakeRequest($connection, '/', $domain, 'abc', array()); - } - - /** - * @dataProvider getInvalidArguments - * @param array $allowed - * @param string $bad_domain - */ - public function testInvalidAllowed($allowed, $bad_domain) - { - $instance = $this->getInstance($allowed); - $this->assertFalse($instance->isAllowed($bad_domain)); - } - - /** - * @dataProvider getInvalidArguments - * @param array $allowed - * @param string $domain - */ - public function testInvalidHandshake($allowed, $bad_domain) - { - $instance = $this->getInstance($allowed); - - $connection = $this->getMock('Wrench\Connection', array(), array(), '', false); - - $connection - ->expects($this->once()) - ->method('close'); - - $instance->onHandshakeRequest($connection, '/', $bad_domain, 'abc', array()); - } - - /** - * Data provider - */ - public function getValidArguments() - { - return array( - array(array('localhost'), 'http://localhost'), - array(array('foobar.com'), 'https://foobar.com'), - array(array('https://foobar.com'), 'https://foobar.com') - ); - } - - /** - * Data provider - */ - public function getInvalidArguments() - { - return array( - array(array('localhost'), 'localdomain'), - array(array('foobar.com'), 'foobar.org'), - array(array('https://foobar.com'), 'http://foobar.com'), - array(array('http://foobar.com'), 'foobar.com') - ); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/Listener/RateLimiterTest.php b/listeners/lib/Wrench/Tests/Listener/RateLimiterTest.php deleted file mode 100644 index 602442308..000000000 --- a/listeners/lib/Wrench/Tests/Listener/RateLimiterTest.php +++ /dev/null @@ -1,67 +0,0 @@ -getInstance(); - $this->assertInstanceOfClass($instance, 'No constructor arguments'); - return $instance; - } - - public function testOnSocketConnect() - { - $this->getInstance()->onSocketConnect(null, $this->getConnection()); - } - - public function testOnSocketDisconnect() - { - $this->getInstance()->onSocketDisconnect(null, $this->getConnection()); - } - - public function testOnClientData() - { - $this->getInstance()->onClientData(null, $this->getConnection()); - } - - protected function getConnection() - { - $connection = $this->getMock('Wrench\Connection', array(), array(), '', false); - - $connection - ->expects($this->any()) - ->method('getIp') - ->will($this->returnValue('127.0.0.1')); - - $connection - ->expects($this->any()) - ->method('getId') - ->will($this->returnValue('abcdef01234567890')); - - $manager = $this->getMock('Wrench\ConnectionManager', array(), array(), '', false); - $manager->expects($this->any())->method('count')->will($this->returnValue(5)); - - $connection - ->expects($this->any()) - ->method('getConnectionManager') - ->will($this->returnValue($manager)); - - return $connection; - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/Payload/HybiPayloadTest.php b/listeners/lib/Wrench/Tests/Payload/HybiPayloadTest.php deleted file mode 100644 index 5dd2b2497..000000000 --- a/listeners/lib/Wrench/Tests/Payload/HybiPayloadTest.php +++ /dev/null @@ -1,14 +0,0 @@ -payload = $this->getInstance(); - } - - /** - * Tests the constructor - */ - public function testConstructor() - { - $this->assertInstanceOfClass($this->getInstance()); - } - - /** - * @param string $payload - * @dataProvider getValidEncodePayloads - */ - public function testBijection($type, $payload) - { - // Encode the payload - $this->payload->encode($payload, $type); - - // Create a new payload and read the data in with encode - $payload = $this->getInstance(); - $payload->encode($this->payload->getPayload(), $type); - - // These still match - $this->assertEquals( - $this->payload->getType(), - $payload->getType(), - 'Types match after encode -> receiveData' - ); - - $this->assertEquals( - $this->payload->getPayload(), - $payload->getPayload(), - 'Payloads match after encode -> receiveData' - ); - } - - /** - * @param string $payload - * @dataProvider getValidEncodePayloads - */ - public function testEncodeTypeReflection($type, $payload) - { - $this->payload->encode($payload, Protocol::TYPE_TEXT); - $this->assertEquals(Protocol::TYPE_TEXT, $this->payload->getType(), 'Encode retains type information'); - } - - /** - * @param string $payload - * @dataProvider getValidEncodePayloads - */ - public function testEncodePayloadReflection($type, $payload) - { - $this->payload->encode($payload, Protocol::TYPE_TEXT); - $this->assertEquals($payload, $this->payload->getPayload(), 'Encode retains payload information'); - } - - /** - * Tests sending to a socket - * @dataProvider getValidEncodePayloads - */ - public function testSendToSocket($type, $payload) - { - $successfulSocket = $this->getMock('Wrench\Socket\ClientSocket', array(), array('wss://localhost:8000')); - $failedSocket = clone $successfulSocket; - - $successfulSocket->expects($this->any()) - ->method('send') - ->will($this->returnValue(true)); - - $failedSocket->expects($this->any()) - ->method('send') - ->will($this->returnValue(false)); - - $this->payload->encode($payload, $type); - - $this->assertTrue($this->payload->sendToSocket($successfulSocket)); - $this->assertFalse($this->payload->sendToSocket($failedSocket)); - } - - /** - * Tests receiving data - * @dataProvider getValidEncodePayloads - */ - public function testReceieveData($type, $payload) - { - $payload = $this->getInstance(); - $payload->receiveData($payload); - } - - /** - * Data provider - * - * @return array - */ - public function getValidEncodePayloads() - { - return array( - array( - Protocol::TYPE_TEXT, - "123456\x007890!@#$%^&*()qwe\trtyuiopQWERTYUIOPasdfghjklASFGH\n - JKLzxcvbnmZXCVBNM,./<>?;[]{}-=_+\|'asdad0x11\aasdassasdasasdsd" - ), - array( - Protocol::TYPE_TEXT, - pack('CCCCCCC', 0x00, 0x01, 0x02, 0x03, 0x04, 0xff, 0xf0) - ), - array(Protocol::TYPE_TEXT, ' ') - ); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/Protocol/ProtocolTest.php b/listeners/lib/Wrench/Tests/Protocol/ProtocolTest.php deleted file mode 100644 index afba179da..000000000 --- a/listeners/lib/Wrench/Tests/Protocol/ProtocolTest.php +++ /dev/null @@ -1,180 +0,0 @@ -getInstance()->validateRequestHandshake($request); - - $this->assertEquals('/chat', $path); - $this->assertEquals('http://example.com', $origin); - $this->assertEquals('dGhlIHNhbXBsZSBub25jZQ==', $key); - $this->assertTrue(is_array($extensions), 'Extensions returned as array'); - $this->assertEquals(array('x-test', 'x-test2'), $extensions, 'Extensions match'); - $this->assertEquals('chat, superchat', $protocol); - } catch (Exception $e) { - $this->fail($e); - } - } - - /** - * @dataProvider getValidHandshakeResponses - */ - public function testValidateHandshakeResponseValid($response, $key) - { - try { - $valid = $this->getInstance()->validateResponseHandshake($response, $key); - $this->assertTrue(is_bool($valid), 'Validation return value is boolean'); - $this->assertTrue($valid, 'Handshake response validates'); - } catch (Exception $e) { - $this->fail('Validated valid response handshake as invalid'); - } - } - - /** - * @dataProvider getValidHandshakeResponses - */ - public function testGetResponseHandsake($unused, $key) - { - try { - $response = $this->getInstance()->getResponseHandshake($key); - $this->assertHttpResponse($response); - } catch (Exception $e) { - $this->fail('Unable to get handshake response: ' . $e); - } - } - - /** - * Asserts the string response is an HTTP response - * - * @param string $response - */ - protected function assertHttpResponse($response, $message = '') - { - $this->assertStringStartsWith('HTTP', $response, $message . ' - response starts well'); - $this->assertStringEndsWith("\r\n", $response, $message . ' - response ends well'); - } - - public function testGetVersion() - { - $version = $this->getInstance()->getVersion(); - $this->assertTrue(is_int($version)); - } - - public function testGetResponseError() - { - $response = $this->getInstance()->getResponseError(400); - $this->assertHttpResponse($response, 'Code as int'); - - $response = $this->getInstance()->getResponseError(new Exception('Some message', 500)); - $this->assertHttpResponse($response, 'Code in Exception'); - - $response = $this->getInstance()->getResponseError(888); - $this->assertHttpResponse($response, 'Invalid code produces unimplemented response'); - } - - /** - * @dataProvider getValidOriginUris - */ - public function testValidateOriginUriValid($uri) - { - try { - $this->getInstance()->validateOriginUri($uri); - } catch (\Exception $e) { - $this->fail('Valid URI validated as invalid: ' . $e); - } - } - - /** - * @dataProvider getInvalidOriginUris - * @expectedException InvalidArgumentException - */ - public function testValidateOriginUriInvalid($uri) - { - $this->getInstance()->validateOriginUri($uri); - } - - public function getValidOriginUris() - { - return array( - array('http://www.example.org'), - array('http://www.example.com/some/page'), - array('https://localhost/') - ); - } - - public function getInvalidOriginUris() - { - return array( - array(false), - array(true), - array(''), - array('blah') - ); - } - - public function getValidHandshakeRequests() - { - $cases = array(); - - - $cases[] = array("GET /chat HTTP/1.1\r -Host: server.example.com\r -Upgrade: websocket\r -Connection: Upgrade\r -Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r -Origin: http://example.com\r -Sec-WebSocket-Extensions: x-test\r -Sec-WebSocket-Extensions: x-test2\r -Sec-WebSocket-Protocol: chat, superchat\r -Sec-WebSocket-Version: 13\r -\r\n"); - - $cases[] = array("GET /chat HTTP/1.1\r -Host: server.example.com\r -Upgrade: Websocket\r -Connection: Upgrade\r -Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r -Origin: http://example.com\r -Sec-WebSocket-Extensions: x-test\r -Sec-WebSocket-Extensions: x-test2\r -Sec-WebSocket-Protocol: chat, superchat\r -Sec-WebSocket-Version: 13\r -\r\n"); - - return $cases; - } - - public function getValidHandshakeResponses() - { - $cases = array(); - - for ($i = 10; $i > 0; $i--) { - $key = sha1(time() . uniqid('', true)); - $response = "Sec-WebSocket-Accept: " - . base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)) - . "\r\n\r\n"; - - $cases[] = array($response, $key); - } - - return $cases; - } -} diff --git a/listeners/lib/Wrench/Tests/Protocol/Rfc6455ProtocolTest.php b/listeners/lib/Wrench/Tests/Protocol/Rfc6455ProtocolTest.php deleted file mode 100644 index ee329f31e..000000000 --- a/listeners/lib/Wrench/Tests/Protocol/Rfc6455ProtocolTest.php +++ /dev/null @@ -1,14 +0,0 @@ -assertInstanceOfClass( - $this->getInstance($url, $options), - 'Valid constructor arguments' - ); - } - - /** - * Tests logging - */ - public function testLogging() - { - $test = $this; - $logged = false; - - $server = $this->getInstance('ws://localhost:8000', array( - 'logger' => function ($message, $priority) use ($test, &$logged) { - $test->assertTrue(is_string($message), 'Log had a string message'); - $test->assertTrue(is_string($priority), 'Log had a string priority'); - $logged = true; - } - )); - - $this->assertTrue($logged, 'The log callback was hit'); - } - - /** - * Data provider - * - * @return array> - */ - public function getValidConstructorArguments() - { - return array( - array( - 'ws://localhost:8000', - array('logger' => array($this, 'log')) - ), - array( - 'ws://localhost', - array('logger' => array($this, 'log')) - ) - ); - } - -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/ServerTestHelper.php b/listeners/lib/Wrench/Tests/ServerTestHelper.php deleted file mode 100644 index 57dd1ec05..000000000 --- a/listeners/lib/Wrench/Tests/ServerTestHelper.php +++ /dev/null @@ -1,144 +0,0 @@ -tearDown(); - } - - /** - * @return string - */ - public function getConnectionString() - { - return 'ws://localhost:' . $this->port; - } - - /** - * @return string - */ - public function getEchoConnectionString() - { - return $this->getConnectionString() . '/echo'; - } - - /** - * Sets up the server process and sleeps for a few seconds while - * it wakes up - */ - public function setUp() - { - $this->port = self::getNextPort(); - - $this->process = proc_open( - $this->getCommand(), - array( - 0 => array('file', '/dev/null', 'r'), - 1 => array('file', __DIR__ . '/../../../build/server.log', 'a+'), - 2 => array('file', __DIR__ . '/../../../build/server.err.log', 'a+') - ), - $this->pipes, - __DIR__ . '../' - ); - - sleep(3); - } - - /** - * Tears down the server process - * - * This method *must* be called - */ - public function tearDown() - { - if ($this->process) { - foreach ($this->pipes as &$pipe) { - fclose($pipe); - } - $this->pipes = null; - - // Sigh - $status = proc_get_status($this->process); - - if ($status && isset($status['pid']) && $status['pid']) { - // More sigh, this is the pid of the parent sh process, we want - // to terminate the server directly - $this->log('Command: /bin/ps -ao pid,ppid | /usr/bin/col | /usr/bin/tail -n +2 | /bin/grep \' ' . $status['pid'] . "'", 'info'); - exec('/bin/ps -ao pid,ppid | /usr/bin/col | /usr/bin/tail -n +2 | /bin/grep \' ' . $status['pid'] . "'", $processes, $return); - - if ($return === 0) { - foreach ($processes as $process) { - list($pid, $ppid) = explode(' ', str_replace(' ', ' ', $process)); - if ($pid) { - $this->log('Killing ' . $pid, 'info'); - exec('/bin/kill ' . $pid . ' > /dev/null 2>&1'); - } - } - } else { - $this->log('Unable to find child processes', 'warning'); - } - - sleep(1); - - $this->log('Killing ' . $status['pid'], 'info'); - exec('/bin/kill ' . $status['pid'] . ' > /dev/null 2>&1'); - - sleep(1); - } - - proc_close($this->process); - $this->process = null; - } - } - - /** - * Gets the server command - * - * @return string - */ - protected function getCommand() - { - return sprintf('/usr/bin/env php %s/server.php %d', __DIR__, $this->port); - } - - /** - * Logs a message - * - * @param string $message - * @param string $priority - */ - public function log($message, $priority = 'info') - { - //echo $message . "\n"; - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/Socket/ClientSocketTest.php b/listeners/lib/Wrench/Tests/Socket/ClientSocketTest.php deleted file mode 100644 index d64ea5df2..000000000 --- a/listeners/lib/Wrench/Tests/Socket/ClientSocketTest.php +++ /dev/null @@ -1,167 +0,0 @@ -assertInstanceOfClass( - new ClientSocket('ws://localhost/'), - 'ws:// scheme, default port' - ); - - $this->assertInstanceOfClass( - new ClientSocket('ws://localhost/some-arbitrary-path'), - 'with path' - ); - - $this->assertInstanceOfClass( - new ClientSocket('wss://localhost/test', array()), - 'empty options' - ); - - $this->assertInstanceOfClass( - new ClientSocket('ws://localhost:8000/foo'), - 'specified port' - ); - - return $instance; - } - - public function testOptions() - { - $socket = null; - - $this->assertInstanceOfClass( - $socket = new ClientSocket( - 'ws://localhost:8000/foo', array( - 'timeout_connect' => 10 - ) - ), - 'connect timeout' - ); - - $this->assertInstanceOfClass( - $socket = new ClientSocket( - 'ws://localhost:8000/foo', array( - 'timeout_socket' => 10 - ) - ), - 'socket timeout' - ); - - $this->assertInstanceOfClass( - $socket = new ClientSocket( - 'ws://localhost:8000/foo', array( - 'protocol' => new Rfc6455Protocol() - ) - ), - 'protocol' - ); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testProtocolTypeError() - { - $socket = new ClientSocket( - 'ws://localhost:8000/foo', array( - 'protocol' => new stdClass() - ) - ); - } - - /** - * @expectedException PHPUnit_Framework_Error - */ - public function testConstructorUriUnspecified() - { - $w = new ClientSocket(); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testConstructorUriEmpty() - { - $w = new ClientSocket(null); - } - - - /** - * @expectedException InvalidArgumentException - */ - public function testConstructorUriInvalid() - { - $w = new ClientSocket('Bad argument'); - } - - - /** - * @depends testConstructor - * @expectedException Wrench\Exception\SocketException - */ - public function testSendTooEarly($instance) - { - $instance->send('foo'); - } - - /** - * Test the connect, send, receive method - */ - public function testConnect() - { - try { - $helper = new ServerTestHelper(); - $helper->setUp(); - - $instance = $this->getInstance($helper->getConnectionString()); - $success = $instance->connect(); - - $this->assertTrue($success, 'Client socket can connect to test server'); - - $sent = $instance->send("GET /echo HTTP/1.1\r -Host: localhost\r -Upgrade: websocket\r -Connection: Upgrade\r -Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r -Origin: http://localhost\r -Sec-WebSocket-Version: 13\r\n\r\n"); - $this->assertNotEquals(false, $sent, 'Client socket can send to test server'); - - $response = $instance->receive(); - $this->assertStringStartsWith('HTTP', $response, 'Response looks like HTTP handshake response'); - - } catch (\Exception $e) { - $helper->tearDown(); - throw $e; - } - - $helper->tearDown(); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/Socket/ServerClientSocketTest.php b/listeners/lib/Wrench/Tests/Socket/ServerClientSocketTest.php deleted file mode 100644 index ca71983b5..000000000 --- a/listeners/lib/Wrench/Tests/Socket/ServerClientSocketTest.php +++ /dev/null @@ -1,42 +0,0 @@ -getInstance($resource); - $this->assertInstanceOfClass($instance); - return $instance; - } - - /** - * @expectedException Wrench\Exception\SocketException - * @depends testConstructor - */ - public function testGetIpTooSoon($instance) - { - $instance->getIp(); - } - - /** - * @expectedException Wrench\Exception\SocketException - * @depends testConstructor - */ - public function testGetPortTooSoon($instance) - { - $instance->getPort(); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/Socket/ServerSocketTest.php b/listeners/lib/Wrench/Tests/Socket/ServerSocketTest.php deleted file mode 100644 index a4d4251c5..000000000 --- a/listeners/lib/Wrench/Tests/Socket/ServerSocketTest.php +++ /dev/null @@ -1,13 +0,0 @@ -isConnected(); - $this->assertTrue(is_bool($connected), 'isConnected returns boolean'); - $this->assertFalse($connected); - } - - /** - * @dataProvider getValidNames - * @param string $name - */ - public function testGetNamePart($name, $ip, $port) - { - $this->assertEquals($ip, Socket::getNamePart($name, Socket::NAME_PART_IP), 'splits ip correctly'); - $this->assertEquals($port, Socket::getNamePart($name, Socket::NAME_PART_PORT), 'splits port correctly'); - } - - /** - * Data provider - */ - public function getValidNames() - { - return array( - array('127.0.0.1:52339', '127.0.0.1', '52339'), - array('255.255.255.255:1025', '255.255.255.255', '1025'), - array('::1:56670', '::1', '56670') - ); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/Socket/UriSocketTest.php b/listeners/lib/Wrench/Tests/Socket/UriSocketTest.php deleted file mode 100644 index d0b7f8bdc..000000000 --- a/listeners/lib/Wrench/Tests/Socket/UriSocketTest.php +++ /dev/null @@ -1,56 +0,0 @@ -getInstance('ws://localhost:8000'); - $this->assertInstanceOfClass($instance); - return $instance; - } - - /** - * @dataProvider getInvalidConstructorArguments - * @expectedException InvalidArgumentException - */ - public function testInvalidConstructor($uri) - { - $this->getInstance($uri); - } - - /** - * @depends testConstructor - */ - public function testGetIp($instance) - { - $this->assertStringStartsWith('localhost', $instance->getIp(), 'Correct host'); - } - - /** - * @depends testConstructor - */ - public function testGetPort($instance) - { - $this->assertEquals(8000, $instance->getPort(), 'Correct port'); - } - - /** - * Data provider - */ - public function getInvalidConstructorArguments() - { - return array( - array(false), - array('http://www.google.com/'), - array('ws:///'), - array(':::::'), - ); - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/Test.php b/listeners/lib/Wrench/Tests/Test.php deleted file mode 100644 index f9b421fd9..000000000 --- a/listeners/lib/Wrench/Tests/Test.php +++ /dev/null @@ -1,61 +0,0 @@ -assertInstanceOf( - $this->getClass(), - $instance, - $message - ); - } - - /** - * Gets an instance of the class under test - * - * @param mixed Normal constructor arguments - * @magic This method accepts a variable number of arguments - * @return object Of type given by getClass() - */ - public function getInstance(/* ... */) - { - $reflection = new ReflectionClass($this->getClass()); - return $reflection->newInstanceArgs(func_get_args()); - } - - /** - * Logging function - * - * Passed into some classes under test as a callable - * - * @param string $message - * @param string $priority - * @return void - */ - public function log($message, $priority = 'info') - { - // nothing - } -} diff --git a/listeners/lib/Wrench/Tests/bootstrap.php b/listeners/lib/Wrench/Tests/bootstrap.php deleted file mode 100644 index 84bd83943..000000000 --- a/listeners/lib/Wrench/Tests/bootstrap.php +++ /dev/null @@ -1,11 +0,0 @@ -register(); \ No newline at end of file diff --git a/listeners/lib/Wrench/Tests/server.php b/listeners/lib/Wrench/Tests/server.php deleted file mode 100644 index 34d680622..000000000 --- a/listeners/lib/Wrench/Tests/server.php +++ /dev/null @@ -1,16 +0,0 @@ -register(); - -$server = new Wrench\Server('ws://localhost:' . $port); -$server->registerApplication('echo', new Wrench\Application\EchoApplication()); -$server->run(); \ No newline at end of file diff --git a/listeners/lib/Wrench/Util/Configurable.php b/listeners/lib/Wrench/Util/Configurable.php deleted file mode 100644 index f1983d5e6..000000000 --- a/listeners/lib/Wrench/Util/Configurable.php +++ /dev/null @@ -1,67 +0,0 @@ - Wrench\Protocol object, latest protocol - * version used if not specified - */ - public function __construct( - array $options = array() - ) { - $this->configure($options); - $this->configureProtocol(); - } - - /** - * Configures the options - * - * @param array $options - */ - protected function configure(array $options) - { - $this->options = array_merge(array( - 'protocol' => new Rfc6455Protocol() - ), $options); - } - - /** - * Configures the protocol option - * - * @throws InvalidArgumentException - */ - protected function configureProtocol() - { - $protocol = $this->options['protocol']; - - if (!$protocol || !($protocol instanceof Protocol)) { - throw new InvalidArgumentException('Invalid protocol option'); - } - - $this->protocol = $protocol; - } -} \ No newline at end of file diff --git a/listeners/lib/Wrench/Util/Ssl.php b/listeners/lib/Wrench/Util/Ssl.php deleted file mode 100644 index e8cc8bd7e..000000000 --- a/listeners/lib/Wrench/Util/Ssl.php +++ /dev/null @@ -1,51 +0,0 @@ - $country_name, - 'stateOrProvinceName' => $state_or_province_name, - 'localityName' => $locality_name, - 'organizationName' => $organization_name, - 'organizationalUnitName' => $organizational_unit_name, - 'commonName' => $common_name, - 'emailAddress' => $email_address - ); - - $privkey = openssl_pkey_new(); - $cert = openssl_csr_new($dn, $privkey); - $cert = openssl_csr_sign($cert, null, $privkey, 365); - - $pem = array(); - - openssl_x509_export($cert, $pem[0]); - - if ($pem_passphrase !== null) { - openssl_pkey_export($privkey, $pem[1], $pem_passphrase); - } - - $pem = implode($pem); - file_put_contents($pem_file, $pem); - } -} \ No newline at end of file diff --git a/listeners/navSyncBroadcasterServer.php b/listeners/navSyncBroadcasterServer.php deleted file mode 100644 index 1bb19be43..000000000 --- a/listeners/navSyncBroadcasterServer.php +++ /dev/null @@ -1,42 +0,0 @@ -register(); - -// parse the main config for the content sync port -if (!($config = @parse_ini_file(__DIR__."/../config/config.ini"))) { - print "Missing the configuration file. Please build it using the Pattern Lab builder.\n"; - exit; -} - -$port = ($config) ? trim($config['navSyncPort']) : '8000'; - -// start the content sync server -$server = new \Wrench\Server('ws://0.0.0.0:'.$port.'/', array()); - -// register the application & run it -$server->registerApplication('navsync', new \Wrench\Application\navSyncBroadcasterApplication()); - -print "\n"; -print "Page Follow Server Started...\n"; -print "Use CTRL+C to stop this service...\n"; - -$server->run(); diff --git a/source/_patternlab-files/index.mustache b/source/_patternlab-files/index.mustache index 630680200..a24e94e3a 100644 --- a/source/_patternlab-files/index.mustache +++ b/source/_patternlab-files/index.mustache @@ -83,8 +83,6 @@ {{> patternPaths }} {{> viewAllPaths }} - {{> websockets }} - diff --git a/source/_patternlab-files/partials/websockets.mustache b/source/_patternlab-files/partials/websockets.mustache deleted file mode 100644 index 139a9b254..000000000 --- a/source/_patternlab-files/partials/websockets.mustache +++ /dev/null @@ -1,6 +0,0 @@ - From abf83e6f2f7f1638e9217a0b32bb30dd9e34bf90 Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Fri, 6 Feb 2015 22:40:55 -0600 Subject: [PATCH 08/19] pages can be built from templates if needed. closes #5 had to remove an arbitrary limit. now supports the tactic @dmolsen outlined here: https://github.com/pattern-lab/patternlab-php/issues/31 --- builder/patternlab.js | 23 +++----- .../_patterns/04-pages/00-homepage.mustache | 38 +------------- source/_patterns/04-pages/02-article.mustache | 52 +------------------ 3 files changed, 8 insertions(+), 105 deletions(-) diff --git a/builder/patternlab.js b/builder/patternlab.js index d001df190..792e7a014 100644 --- a/builder/patternlab.js +++ b/builder/patternlab.js @@ -130,14 +130,14 @@ var patternlab_engine = function(){ var cleanSub = sub.substring(0, folderIndex); //add any templates found to an object of partials, so downstream templates may use them too - //exclude the template patterns - we don't need them as partials because pages will just swap data + //look for the full path on nested patters, else expect it to be flat + var partialname = ''; if(cleanSub !== ''){ - var partialname = cleanSub + '-' + patternName.substring(patternName.indexOf('-') + 1); - - patternlab.partials[partialname] = currentPattern.template; - - //done + partialname = cleanSub + '-' + patternName.substring(patternName.indexOf('-') + 1); + } else{ + partialname = currentPattern.patternGroup + '-' + patternName.substring(patternName.indexOf('-') + 1); } + patternlab.partials[partialname] = currentPattern.template; //add to patternlab arrays so we can look these up later. this could probably just be an object. patternlab.patternIndex.push(currentPattern.name); @@ -160,9 +160,6 @@ var patternlab_engine = function(){ //write the encoded version too fs.outputFileSync('./public/patterns/' + pattern.patternLink.replace('.html', '.escaped.html'), entity_encoder.encode(pattern.patternPartial)); - - - }); //export patterns if necessary @@ -335,19 +332,11 @@ var patternlab_engine = function(){ var viewAllPathsTemplate = fs.readFileSync('./source/_patternlab-files/partials/viewAllPaths.mustache', 'utf8'); var viewAllPathersPartialHtml = renderPattern(viewAllPathsTemplate, {'viewallpaths': JSON.stringify(patternlab.viewAllPaths)}); - //websockets - var websocketsTemplate = fs.readFileSync('./source/_patternlab-files/partials/websockets.mustache', 'utf8'); - patternlab.contentsyncport = patternlab.config.contentSyncPort; - patternlab.navsyncport = patternlab.config.navSyncPort; - - var websocketsPartialHtml = renderPattern(websocketsTemplate, patternlab); - //render the patternlab template, with all partials var patternlabSiteHtml = renderPattern(patternlabSiteTemplate, {}, { 'ishControls': ishControlsPartialHtml, 'patternNav': patternNavPartialHtml, 'patternPaths': patternPathsPartialHtml, - 'websockets': websocketsPartialHtml, 'viewAllPaths': viewAllPathersPartialHtml }); fs.outputFileSync('./public/index.html', patternlabSiteHtml); diff --git a/source/_patterns/04-pages/00-homepage.mustache b/source/_patterns/04-pages/00-homepage.mustache index 4e99ff54d..68a930daa 100644 --- a/source/_patterns/04-pages/00-homepage.mustache +++ b/source/_patterns/04-pages/00-homepage.mustache @@ -1,37 +1 @@ -
- {{> organisms-header }} -
- - {{# hero }} - {{> molecules-block-hero }} - {{/ hero}} - -
- {{# touts}} -
- {{> molecules-inset-block }} -
- {{/ touts}} -
- -
- -
-
-
-

Latest Posts

-
    - {{# latest-posts}} -
  • {{> molecules-media-block }}
  • - {{/ latest-posts}} -
- View more posts -
-
- - -
- {{> organisms-footer }} -
\ No newline at end of file +{{> templates-homepage }} \ No newline at end of file diff --git a/source/_patterns/04-pages/02-article.mustache b/source/_patterns/04-pages/02-article.mustache index 1f5101a4a..0449d165c 100644 --- a/source/_patterns/04-pages/02-article.mustache +++ b/source/_patterns/04-pages/02-article.mustache @@ -1,51 +1 @@ -
- {{> organisms-header }} -
-
-
-
-
-

Top 10 Trails You Have To Hike Before You Die

- {{> molecules-byline-author-time }} -
-

This is where a post about hiking trails would live. I'm not going to write an article about hiking trails. That would take a lot of time.

- -

This is a link, possibly to an external resource. In a post about hiking trails, there would probably be talk about how to get to featured trails, what to pack, where to camp, and so on and so forth. I don't have any expertise in this subject matter so I'm not going to attempt to write about it.

- - {{> atoms-landscape-16x9 }} - -

If I were to write about it, I'd probably do some research on the topic first. But even then, I probably Sed a orci turpis. Aliquam aliquet placerat dui, consectetur tincidunt leo tristique et. Vivamus enim nisi, blandit a venenatis quis, convallis et arcu. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris libero sapien, placerat in sodales eu, tempor quis dui. Vivamus egestas faucibus pulvinar. Maecenas eget diam nunc. Phasellus at sem eros, ac suscipit neque. Phasellus sollicitudin libero a odio dignissim scelerisque. Aliquam purus nulla, tempor eget ullamcorper quis, rhoncus non dui. -

- -
- This trail is amazing. I've never seen anything like it. -
- -

Cras at fringilla ipsum. Donec nec libero eget est blandit dignissim a eu ante. Morbi augue nulla, luctus eu sagittis vel, malesuada ut felis. Aliquam erat volutpat. Morbi malesuada augue ac massa hendrerit fermentum. Integer scelerisque lacus a dolor convallis lobortis. Curabitur mollis ante in massa ultricies dignissim. -

- - {{> atoms-unordered }} - - {{> atoms-ordered }} - -

Donec posuere fringilla nunc, vitae venenatis diam scelerisque vel. Nullam vitae mauris magna. Mauris et diam quis justo volutpat tincidunt congue nec magna. Curabitur vitae orci elit. Ut mollis massa id magna vestibulum consequat. Proin rutrum lectus justo, sit amet tincidunt est. Vivamus vitae lacinia risus. -

- - {{> molecules-pullquote }} - -

Donec venenatis imperdiet tortor, vitae blandit odio interdum ut. Integer orci metus, lobortis id lacinia eget, rutrum vitae justo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In pretium fermentum justo nec pharetra. Maecenas eget dapibus justo. Ut quis est risus. Nullam et eros at odio commodo venenatis quis et augue. Sed sed augue at tortor porttitor hendrerit nec ut nibh. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin sollicitudin enim consectetur mi commodo quis cursus ante pretium. Nunc gravida cursus nisi in gravida. Suspendisse eget tortor sed urna consequat tincidunt. Etiam eget convallis lectus. Suspendisse cursus rutrum massa ac faucibus. -

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reiciendis, suscipit repellendus nulla accusantium deserunt sed explicabo voluptate sapiente ratione inventore molestiae nihil earum repellat quia odit vitae perspiciatis aliquam amet?

-
- {{> molecules-social-share }} - {{> organisms-comment-thread }} -
- - -
-
- {{> organisms-footer }} -
\ No newline at end of file +{{> templates-article }} \ No newline at end of file From 631c7036f42bef995dc83baf1c736c7037240d8c Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Fri, 6 Feb 2015 23:37:07 -0600 Subject: [PATCH 09/19] updated default pattern data and the home page to reflect a new structure from php - and get ready for psuedo patterns --- source/_data/data.json | 25 +++++++++++- .../03-templates/00-homepage.mustache | 39 ++++++++++++------- source/_patterns/04-pages/00-homepage.json | 5 ++- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/source/_data/data.json b/source/_data/data.json index 20d5d30f9..3be1f4aca 100644 --- a/source/_data/data.json +++ b/source/_data/data.json @@ -1,4 +1,7 @@ { + "title" : "Pattern Lab", + "htmlClass": "pl", + "bodyClass": "body", "img": { "landscape-4x3": { "src": "../../images/fpo_4x3.png", @@ -15,6 +18,10 @@ "avatar" : { "src" : "../../images/fpo_avatar.png", "alt" : "Person Name" + }, + "rectangle": { + "src": "http://placeimg.com/400/300/tech", + "alt": "Rectangle" } }, "headline" : { @@ -27,7 +34,7 @@ "long" : "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." }, "description" : "So, setting about it as methodically as men might smoke out a wasps' nest, the Martians spread this strange stifling vapour over the Londonward country. The horns of the crescent slowly moved apart, until at last they formed a line from Hanwell to Coombe and Malden. All night through their destructive tubes advanced.", - "url" : "http://www.fillerati.com", + "url" : "#", "name" : { "first": "Lacy", "firsti": "L", @@ -68,5 +75,19 @@ "author" : { "first-name": "Author", "last-name": "Name" - } + }, + "hero": true, + "emergency" : false, + "touts" : [ + { }, + { }, + { } + ], + "latest-posts" : [ + { }, + { }, + { }, + { }, + { } + ] } \ No newline at end of file diff --git a/source/_patterns/03-templates/00-homepage.mustache b/source/_patterns/03-templates/00-homepage.mustache index cda5d0445..8d26c6ba0 100644 --- a/source/_patterns/03-templates/00-homepage.mustache +++ b/source/_patterns/03-templates/00-homepage.mustache @@ -1,31 +1,40 @@
{{> organisms-header }}
- {{> molecules-block-hero }} + {{# emergency }} + {{> molecules-alert:error }} + {{/ emergency }} + {{# hero }} + {{> molecules-block-hero }} + {{/ hero}} +
-
- {{> molecules-inset-block }} -
-
- {{> molecules-inset-block }} -
-
- {{> molecules-inset-block }} -
+ {{# touts}} +
+ {{> molecules-block-inset }} +
+ {{/ touts}}

- {{> organisms-latest-posts }} -
+
+

Latest Posts

+
    + {{# latest-posts }} +
  • {{> molecules-media-block }}
  • + {{/ latest-posts }} +
+ View more posts +
+
-
- +
+
{{> organisms-footer }} \ No newline at end of file diff --git a/source/_patterns/04-pages/00-homepage.json b/source/_patterns/04-pages/00-homepage.json index 989cce722..b42aa6061 100644 --- a/source/_patterns/04-pages/00-homepage.json +++ b/source/_patterns/04-pages/00-homepage.json @@ -1,4 +1,7 @@ { + "title" : "Home Page", + "bodyClass": "home", + "emergency" : false, "hero" : [ { "img": { @@ -8,7 +11,7 @@ } }, "headline" : { - "medium" : "Top 10 mountin ranges for hiking" + "medium" : "Top 10 mountain ranges for hiking" } } ], From 523df6ae6109628f85aa226795967bba11e20f30 Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Fri, 6 Feb 2015 23:41:32 -0600 Subject: [PATCH 10/19] Support for patternlinks. Included these in the default nav templates to showcase how this works closes #89 --- builder/object_factory.js | 2 +- builder/patternlab.js | 28 +++++++++++-------- .../05-navigation/00-primary-nav.mustache | 4 +-- .../05-navigation/01-footer-nav.mustache | 4 +-- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/builder/object_factory.js b/builder/object_factory.js index 5f8308f37..dbfb88237 100644 --- a/builder/object_factory.js +++ b/builder/object_factory.js @@ -19,7 +19,7 @@ this.template = ''; this.patternPartial = ''; this.patternName = ''; //this is the display name for the ui - this.patternLink = ''; + this.patternLink = name + '/' + name + '.html'; this.patternGroup = name.substring(name.indexOf('-') + 1, name.indexOf('-', 4) + 1 - name.indexOf('-') + 1); this.patternSubGroup = subdir.substring(subdir.indexOf('/') + 4); this.flatPatternPath = subdir.replace(/\//g, '-'); diff --git a/builder/patternlab.js b/builder/patternlab.js index 792e7a014..b719e96c5 100644 --- a/builder/patternlab.js +++ b/builder/patternlab.js @@ -11,6 +11,7 @@ var patternlab_engine = function(){ var path = require('path'), fs = require('fs-extra'), + extend = require('util')._extend, diveSync = require('diveSync'), mustache = require('mustache'), of = require('./object_factory'), @@ -59,6 +60,7 @@ var patternlab_engine = function(){ patternlab.patterns = []; patternlab.patternIndex = []; patternlab.partials = {}; + patternlab.data.link = {}; diveSync('./source/_patterns', function(err, file){ @@ -108,18 +110,8 @@ var patternlab_engine = function(){ catch(e) { } - currentPattern.template = fs.readFileSync(abspath, 'utf8'); - - //render the pattern. pass partials object just in case. - if(currentPattern.data) { // Pass JSON as data - currentPattern.patternPartial = renderPattern(currentPattern.template, currentPattern.data, patternlab.partials); - }else{ // Pass global patternlab data - currentPattern.patternPartial = renderPattern(currentPattern.template, patternlab.data, patternlab.partials); - } - currentPattern.patternLink = currentPattern.name + '/' + currentPattern.name + '.html';; - //find pattern lineage var lineage_hunter = new lh(); lineage_hunter.find_lineage(currentPattern, patternlab); @@ -138,9 +130,10 @@ var patternlab_engine = function(){ partialname = currentPattern.patternGroup + '-' + patternName.substring(patternName.indexOf('-') + 1); } patternlab.partials[partialname] = currentPattern.template; - - //add to patternlab arrays so we can look these up later. this could probably just be an object. + + //add to patternlab object so we can look these up later. patternlab.patternIndex.push(currentPattern.name); + patternlab.data.link[currentPattern.patternGroup + '-' + currentPattern.patternName] = '/patterns/' + currentPattern.patternLink; patternlab.patterns.push(currentPattern); }); @@ -149,6 +142,17 @@ var patternlab_engine = function(){ //render all patterns last, so lineageR works patternlab.patterns.forEach(function(pattern, index, patterns){ + //render the pattern. pass partials and data + if(pattern.data) { // Pass found pattern-specific JSON as data + + //extend patternIndex into link for pattern link shortcuts to work. we do this locally and globally + pattern.data.link = extend({}, patternlab.data.link); + + pattern.patternPartial = renderPattern(pattern.template, pattern.data, patternlab.partials); + }else{ // Pass global patternlab data + pattern.patternPartial = renderPattern(pattern.template, patternlab.data, patternlab.partials); + } + //add footer info before writing var patternFooter = renderPattern(patternlab.footer, pattern); diff --git a/source/_patterns/01-molecules/05-navigation/00-primary-nav.mustache b/source/_patterns/01-molecules/05-navigation/00-primary-nav.mustache index 09c70e8ca..226cfd40c 100644 --- a/source/_patterns/01-molecules/05-navigation/00-primary-nav.mustache +++ b/source/_patterns/01-molecules/05-navigation/00-primary-nav.mustache @@ -1,8 +1,8 @@ diff --git a/source/_patterns/01-molecules/05-navigation/01-footer-nav.mustache b/source/_patterns/01-molecules/05-navigation/01-footer-nav.mustache index 41d5f87f8..182633b78 100644 --- a/source/_patterns/01-molecules/05-navigation/01-footer-nav.mustache +++ b/source/_patterns/01-molecules/05-navigation/01-footer-nav.mustache @@ -1,6 +1,6 @@ \ No newline at end of file From 392bed849b6168979bf59ec945e5585690a47458 Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Sat, 7 Feb 2015 00:10:17 -0600 Subject: [PATCH 11/19] fixed unit test --- test/object_factory_tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/object_factory_tests.js b/test/object_factory_tests.js index 8fcd2aa5f..3a8dd41f0 100644 --- a/test/object_factory_tests.js +++ b/test/object_factory_tests.js @@ -13,7 +13,7 @@ test.equals(p.template, ''); test.equals(p.patternPartial, ''); test.equals(p.patternName, ''); - test.equals(p.patternLink, ''); + test.equals(p.patternLink, '00-atoms-00-global-00-colors/00-atoms-00-global-00-colors.html'); test.equals(p.patternGroup, 'atoms'); test.equals(p.patternSubGroup, 'global'); test.equals(p.flatPatternPath, '00-atoms-00-global'); From 15eae9315981db93feba77b892ad66df07e7a27c Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Sat, 7 Feb 2015 00:28:15 -0600 Subject: [PATCH 12/19] prep for 0.8.0 release --- CHANGELOG | 7 +++++++ builder/lineage_hunter.js | 2 +- builder/media_hunter.js | 2 +- builder/object_factory.js | 2 +- builder/pattern_exporter.js | 2 +- builder/patternlab.js | 2 +- builder/patternlab_grunt.js | 2 +- package.json | 2 +- 8 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7b320d233..7254122d2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,12 @@ THIS CHANGELOG IS AN ATTEMPT TO DOCUMENT CHANGES TO THIS PROJECT. +PL-node-v0.8.0 + - CHG: note the change in versioning to proper semver. wanted to do ths to inch closer to a 1.0.0 release + - DEL: deleted most of the lingering PHP sync listener code + - FIX: support for displaying the HTML and Mustache in the code viewer + - ADD: pattern link support + - CHG: updated included mustache templates to reflect pattern links in navigation and compiling pages direct from templates + PL-node-v0.1.7 - ADD: pattern export - CHG: updated devDependencies diff --git a/builder/lineage_hunter.js b/builder/lineage_hunter.js index dcabaea6f..ac248c05d 100644 --- a/builder/lineage_hunter.js +++ b/builder/lineage_hunter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.7 - 2015 + * patternlab-node - v0.8.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/media_hunter.js b/builder/media_hunter.js index e66994a14..8985b9bd6 100644 --- a/builder/media_hunter.js +++ b/builder/media_hunter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.7 - 2015 + * patternlab-node - v0.8.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/object_factory.js b/builder/object_factory.js index dbfb88237..46f6b154e 100644 --- a/builder/object_factory.js +++ b/builder/object_factory.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.7 - 2015 + * patternlab-node - v0.8.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/pattern_exporter.js b/builder/pattern_exporter.js index 8ed9ed22b..3009d01c0 100644 --- a/builder/pattern_exporter.js +++ b/builder/pattern_exporter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.7 - 2015 + * patternlab-node - v0.8.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/patternlab.js b/builder/patternlab.js index b719e96c5..801a5148b 100644 --- a/builder/patternlab.js +++ b/builder/patternlab.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.7 - 2015 + * patternlab-node - v0.8.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/patternlab_grunt.js b/builder/patternlab_grunt.js index 6c89a0fbc..6ba3e35fc 100644 --- a/builder/patternlab_grunt.js +++ b/builder/patternlab_grunt.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.1.7 - 2015 + * patternlab-node - v0.8.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/package.json b/package.json index 5788149bc..6d50d9b3b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "patternlab-node", "description": "Pattern Lab is a collection of tools to help you create atomic design systems. This is the node command line interface (CLI).", - "version": "0.1.7", + "version": "0.8.0", "devDependencies": { "grunt": "~0.4.0", "grunt-contrib-watch": "^0.6.1", From 4dee74d824d8d626c5530abd7844a57a1bd9896e Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Sat, 7 Feb 2015 00:33:47 -0600 Subject: [PATCH 13/19] documentation for pattern links --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 54028af67..aa49aea53 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,22 @@ Pattern states should be lowercase and use hyphens where spaces are present. } ``` +##### Pattern Linking +You can build patterns that link to one another to help simulate using a real website. This is especially useful when working with the Pages and Templates pattern types. The basic format is: + +`{{ link.pattern-name }}` + +For example, if you wanted to add a link to the `home page` template from your `blog` template you could write the following: + +`Home` + +This would compile to: + +`Home` + +As you can see, it's a much easier way of linking patterns to one another. + + ##### Pattern Export `config.json` also has two properties that work together to export completed patterns for use in a production environment. Provide an array of keys and an output directory. Pattern Lab doesn't ship with any pattern export keys, but the default directory is `"./pattern_exports/"` created inside the install directory. From 51ee958de39a545a5ef4da5fa3bc691aec677a45 Mon Sep 17 00:00:00 2001 From: Seth Broweleit Date: Sat, 7 Feb 2015 08:52:39 -0600 Subject: [PATCH 14/19] Update 05-inline-elements.mustache typo --- source/_patterns/00-atoms/01-text/05-inline-elements.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/_patterns/00-atoms/01-text/05-inline-elements.mustache b/source/_patterns/00-atoms/01-text/05-inline-elements.mustache index dd4ff1f39..47aaa8928 100644 --- a/source/_patterns/00-atoms/01-text/05-inline-elements.mustache +++ b/source/_patterns/00-atoms/01-text/05-inline-elements.mustache @@ -37,5 +37,5 @@

This is sample output from a computer program

-

The variarble element, such as x = y

- \ No newline at end of file +

The variable element, such as x = y

+ From 5517416bdca61891ed46b1ec1526579a08c70657 Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Sat, 7 Feb 2015 13:22:13 -0600 Subject: [PATCH 15/19] refactored to better utilize the pattern object constructor. less locals in patternlab.js closes #81 --- builder/object_factory.js | 20 ++++++++++---------- builder/patternlab.js | 30 ++++++++++-------------------- test/object_factory_tests.js | 15 ++++++++++----- 3 files changed, 30 insertions(+), 35 deletions(-) diff --git a/builder/object_factory.js b/builder/object_factory.js index 46f6b154e..c88682ab4 100644 --- a/builder/object_factory.js +++ b/builder/object_factory.js @@ -11,19 +11,19 @@ (function () { "use strict"; - var oPattern = function(name, subdir, filename, data){ - this.name = name; //this is the unique name with the subDir + var oPattern = function(subdir, filename, data){ + this.fileName = filename.substring(0, filename.indexOf('.')); this.subdir = subdir; - this.filename = filename; - this.data = data; - this.template = ''; - this.patternPartial = ''; - this.patternName = ''; //this is the display name for the ui - this.patternLink = name + '/' + name + '.html'; - this.patternGroup = name.substring(name.indexOf('-') + 1, name.indexOf('-', 4) + 1 - name.indexOf('-') + 1); + this.name = (subdir.replace(/[\/\\]/g, '-') + '-' + this.fileName).replace(/\\/g, '-'); //this is the unique name with the subDir + this.data = data || null; + this.patternName = this.fileName.substring(this.fileName.indexOf('-') + 1); //this is the display name for the ui + this.patternLink = this.name + '/' + this.name + '.html'; + this.patternGroup = this.name.substring(this.name.indexOf('-') + 1, this.name.indexOf('-', 4) + 1 - this.name.indexOf('-') + 1); this.patternSubGroup = subdir.substring(subdir.indexOf('/') + 4); this.flatPatternPath = subdir.replace(/\//g, '-'); - this.key = ''; + this.key = this.patternGroup + '-' + this.patternName; + this.template = ''; + this.patternPartial = ''; this.lineage = []; this.lineageIndex = []; this.lineageR = []; diff --git a/builder/patternlab.js b/builder/patternlab.js index 801a5148b..5ac0dac61 100644 --- a/builder/patternlab.js +++ b/builder/patternlab.js @@ -58,7 +58,6 @@ var patternlab_engine = function(){ patternlab.header = fs.readFileSync('./source/_patternlab-files/pattern-header-footer/header.html', 'utf8'); patternlab.footer = fs.readFileSync('./source/_patternlab-files/pattern-header-footer/footer.html', 'utf8'); patternlab.patterns = []; - patternlab.patternIndex = []; patternlab.partials = {}; patternlab.data.link = {}; @@ -75,25 +74,15 @@ var patternlab_engine = function(){ var subdir = path.dirname(path.relative('./source/_patterns', file)); var filename = path.basename(file); - //check if the pattern already exists. - var patternName = filename.substring(0, filename.indexOf('.')), - patternIndex = patternlab.patternIndex.indexOf(subdir + '-' + patternName), - currentPattern, - flatPatternPath; - - //ignore _underscored patterns, json, and dotfiles + //ignore _underscored patterns, json (for now), and dotfiles if(filename.charAt(0) === '_' || path.extname(filename) === '.json' || filename.charAt(0) === '.'){ return; } + //check for pattern parameters before we do much else. need to remove them into a data object so the rest of the filename parsing works + //make a new Pattern Object - var flatPatternName = subdir.replace(/[\/\\]/g, '-') + '-' + patternName; - - flatPatternName = flatPatternName.replace(/\\/g, '-'); - currentPattern = new of.oPattern(flatPatternName, subdir, filename, {}); - currentPattern.patternName = patternName.substring(patternName.indexOf('-') + 1); - currentPattern.data = null; - currentPattern.key = currentPattern.patternGroup + '-' + currentPattern.patternName; + currentPattern = new of.oPattern(subdir, filename, {}); //see if this file has a state if(patternlab.config.patternStates[currentPattern.patternName]){ @@ -111,6 +100,8 @@ var patternlab_engine = function(){ } currentPattern.template = fs.readFileSync(abspath, 'utf8'); + + //look for a pseudo pattern by checking if there is a file containing same name, with ~ in it, ending in .json //find pattern lineage var lineage_hunter = new lh(); @@ -125,14 +116,13 @@ var patternlab_engine = function(){ //look for the full path on nested patters, else expect it to be flat var partialname = ''; if(cleanSub !== ''){ - partialname = cleanSub + '-' + patternName.substring(patternName.indexOf('-') + 1); + partialname = cleanSub + '-' + currentPattern.patternName; } else{ - partialname = currentPattern.patternGroup + '-' + patternName.substring(patternName.indexOf('-') + 1); + partialname = currentPattern.patternGroup + '-' + currentPattern.patternName; } patternlab.partials[partialname] = currentPattern.template; //add to patternlab object so we can look these up later. - patternlab.patternIndex.push(currentPattern.name); patternlab.data.link[currentPattern.patternGroup + '-' + currentPattern.patternName] = '/patterns/' + currentPattern.patternLink; patternlab.patterns.push(currentPattern); }); @@ -145,7 +135,7 @@ var patternlab_engine = function(){ //render the pattern. pass partials and data if(pattern.data) { // Pass found pattern-specific JSON as data - //extend patternIndex into link for pattern link shortcuts to work. we do this locally and globally + //extend pattern data links into link for pattern link shortcuts to work. we do this locally and globally pattern.data.link = extend({}, patternlab.data.link); pattern.patternPartial = renderPattern(pattern.template, pattern.data, patternlab.partials); @@ -356,7 +346,7 @@ var patternlab_engine = function(){ function addToPatternPaths(bucketName, pattern){ //this is messy, could use a refactor. - patternlab.patternPaths[bucketName][pattern.patternName] = pattern.subdir.replace(/\\/g, '/') + "/" + pattern.filename.substring(0, pattern.filename.indexOf('.')); + patternlab.patternPaths[bucketName][pattern.patternName] = pattern.subdir.replace(/\\/g, '/') + "/" + pattern.fileName; } return { diff --git a/test/object_factory_tests.js b/test/object_factory_tests.js index 3a8dd41f0..23cd90587 100644 --- a/test/object_factory_tests.js +++ b/test/object_factory_tests.js @@ -5,18 +5,23 @@ exports['oPattern initialization'] = { 'test oPattern initializes correctly' : function(test){ - var p = new of.oPattern('00-atoms-00-global-00-colors', '00-atoms/00-global', 'file.txt', { d: 123}); + var p = new of.oPattern('00-atoms/00-global', '00-colors.mustache', { d: 123}); test.equals(p.name, '00-atoms-00-global-00-colors'); test.equals(p.subdir, '00-atoms/00-global'); - test.equals(p.filename, 'file.txt'); + test.equals(p.fileName, '00-colors'); test.equals(p.data.d, 123); - test.equals(p.template, ''); - test.equals(p.patternPartial, ''); - test.equals(p.patternName, ''); + test.equals(p.patternName, 'colors'); test.equals(p.patternLink, '00-atoms-00-global-00-colors/00-atoms-00-global-00-colors.html'); test.equals(p.patternGroup, 'atoms'); test.equals(p.patternSubGroup, 'global'); test.equals(p.flatPatternPath, '00-atoms-00-global'); + test.equals(p.key, 'atoms-colors'); + test.equals(p.template, ''); + test.equals(p.patternPartial, ''); + test.equals(p.lineage.length, 0); + test.equals(p.lineageIndex.length, 0); + test.equals(p.lineageR.length, 0); + test.equals(p.lineageRIndex.length, 0); test.done(); } }; From 53a30913e3d1c208795951305dcccb40d31b9a9e Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Wed, 11 Feb 2015 00:31:26 -0600 Subject: [PATCH 16/19] working version of pseudo patterns without DRYness --- CHANGELOG | 1 + builder/object_factory.js | 1 + builder/pattern_assembler.js | 22 +++++++++- builder/patternlab.js | 44 ++++++++++++++++++- config.json | 2 +- package.json | 3 +- .../07-messaging/00-alert.mustache | 3 ++ .../03-templates/00-homepage.mustache | 2 +- source/_patterns/04-pages/00-homepage.json | 1 - .../04-pages/00-homepage~emergency.json | 8 ++++ 10 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 source/_patterns/01-molecules/07-messaging/00-alert.mustache create mode 100644 source/_patterns/04-pages/00-homepage~emergency.json diff --git a/CHANGELOG b/CHANGELOG index 7254122d2..b19590994 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ PL-node-v0.8.0 - FIX: support for displaying the HTML and Mustache in the code viewer - ADD: pattern link support - CHG: updated included mustache templates to reflect pattern links in navigation and compiling pages direct from templates + - THX: @getsetbro for finding and fixing a typo PL-node-v0.1.7 - ADD: pattern export diff --git a/builder/object_factory.js b/builder/object_factory.js index c88682ab4..e4cd5686f 100644 --- a/builder/object_factory.js +++ b/builder/object_factory.js @@ -12,6 +12,7 @@ "use strict"; var oPattern = function(subdir, filename, data){ + //console.log(filename); this.fileName = filename.substring(0, filename.indexOf('.')); this.subdir = subdir; this.name = (subdir.replace(/[\/\\]/g, '-') + '-' + this.fileName).replace(/\\/g, '-'); //this is the unique name with the subDir diff --git a/builder/pattern_assembler.js b/builder/pattern_assembler.js index 2f77339d3..057f984b0 100644 --- a/builder/pattern_assembler.js +++ b/builder/pattern_assembler.js @@ -1,5 +1,23 @@ (function () { - "use strict"; - + "use strict"; + + var fs = require('fs-extra'), + path = require('path'); + + var pattern_assembler = function(){ + + function exportPatterns(patternlab){ + + } + + return { + export_patterns: function(patternlab){ + exportPatterns(patternlab); + } + }; + + }; + + module.exports = pattern_assembler; }()); \ No newline at end of file diff --git a/builder/patternlab.js b/builder/patternlab.js index 5ac0dac61..2a56aa78d 100644 --- a/builder/patternlab.js +++ b/builder/patternlab.js @@ -14,11 +14,13 @@ var patternlab_engine = function(){ extend = require('util')._extend, diveSync = require('diveSync'), mustache = require('mustache'), + glob = require('glob'), of = require('./object_factory'), pa = require('./pattern_assembler'), mh = require('./media_hunter'), lh = require('./lineage_hunter'), pe = require('./pattern_exporter'), + pa = require('./pattern_assembler'), he = require('html-entities').AllHtmlEntities, patternlab = {}; @@ -53,6 +55,8 @@ var patternlab_engine = function(){ } function buildPatterns(callback){ + var assembler = new pa(); + patternlab.data = fs.readJSONSync('./source/_data/data.json'); patternlab.listitems = fs.readJSONSync('./source/_data/listitems.json'); patternlab.header = fs.readFileSync('./source/_patternlab-files/pattern-header-footer/header.html', 'utf8'); @@ -101,8 +105,6 @@ var patternlab_engine = function(){ } currentPattern.template = fs.readFileSync(abspath, 'utf8'); - //look for a pseudo pattern by checking if there is a file containing same name, with ~ in it, ending in .json - //find pattern lineage var lineage_hunter = new lh(); lineage_hunter.find_lineage(currentPattern, patternlab); @@ -122,6 +124,44 @@ var patternlab_engine = function(){ } patternlab.partials[partialname] = currentPattern.template; + //look for a pseudo pattern by checking if there is a file containing same name, with ~ in it, ending in .json + var needle = currentPattern.subdir + '/' + currentPattern.fileName+ '~*.json'; + var pseudoPatterns = glob.sync(needle, { + cwd: 'source/_patterns/', //relative to gruntfile + debug: false, + nodir: true, + }); + + if(pseudoPatterns.length > 0){ + for(var i = 0; i < pseudoPatterns.length; i++){ + //we want to do everything we normally would here, except instead head the pseudoPattern data + var variantFileData = fs.readJSONSync('source/_patterns/' + pseudoPatterns[i]); + + //extend any existing data with variant data + variantFileData = extend(variantFileData, currentPattern.data); + + var variantName = pseudoPatterns[i].substring(pseudoPatterns[i].indexOf('~') + 1).split('.')[0]; + var patternVariant = new of.oPattern(subdir, currentPattern.fileName + '-' + variantName + '.mustache', variantFileData); + + //see if this file has a state + if(patternlab.config.patternStates[patternVariant.patternName]){ + patternVariant.patternState = patternlab.config.patternStates[patternVariant.patternName]; + } else{ + patternVariant.patternState = ""; + } + + //use the same template as the non-variant + patternVariant.template = currentPattern.template; + + //find pattern lineage + lineage_hunter.find_lineage(patternVariant, patternlab); + + //add to patternlab object so we can look these up later. + patternlab.data.link[patternVariant.patternGroup + '-' + patternVariant.patternName] = '/patterns/' + patternVariant.patternLink; + patternlab.patterns.push(patternVariant); + } + } + //add to patternlab object so we can look these up later. patternlab.data.link[currentPattern.patternGroup + '-' + currentPattern.patternName] = '/patterns/' + currentPattern.patternLink; patternlab.patterns.push(currentPattern); diff --git a/config.json b/config.json index 3336c93de..da06e5dde 100644 --- a/config.json +++ b/config.json @@ -7,7 +7,7 @@ "ignored-directories" : ["scss"], "contentSyncPort" : 8002, "navSyncPort" : 8003, - "debug": false, + "debug": true, "ishControlsVisible": { "s": true, "m": true, diff --git a/package.json b/package.json index 6d50d9b3b..1ce4a4912 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "matchdep": "^0.3.0", "fs-extra": "^0.14.0", "diveSync": "^0.2.1", - "html-entities": "^1.1.1" + "html-entities": "^1.1.1", + "glob": "^4.3.5" }, "keywords": [ "Pattern Lab", diff --git a/source/_patterns/01-molecules/07-messaging/00-alert.mustache b/source/_patterns/01-molecules/07-messaging/00-alert.mustache new file mode 100644 index 000000000..b5e479b31 --- /dev/null +++ b/source/_patterns/01-molecules/07-messaging/00-alert.mustache @@ -0,0 +1,3 @@ +
+ {{ excerpt.short }} +
\ No newline at end of file diff --git a/source/_patterns/03-templates/00-homepage.mustache b/source/_patterns/03-templates/00-homepage.mustache index 8d26c6ba0..038a514b3 100644 --- a/source/_patterns/03-templates/00-homepage.mustache +++ b/source/_patterns/03-templates/00-homepage.mustache @@ -2,7 +2,7 @@ {{> organisms-header }}
{{# emergency }} - {{> molecules-alert:error }} + {{> molecules-alert }} {{/ emergency }} {{# hero }} {{> molecules-block-hero }} diff --git a/source/_patterns/04-pages/00-homepage.json b/source/_patterns/04-pages/00-homepage.json index b42aa6061..b98007204 100644 --- a/source/_patterns/04-pages/00-homepage.json +++ b/source/_patterns/04-pages/00-homepage.json @@ -1,7 +1,6 @@ { "title" : "Home Page", "bodyClass": "home", - "emergency" : false, "hero" : [ { "img": { diff --git a/source/_patterns/04-pages/00-homepage~emergency.json b/source/_patterns/04-pages/00-homepage~emergency.json new file mode 100644 index 000000000..7484c9adc --- /dev/null +++ b/source/_patterns/04-pages/00-homepage~emergency.json @@ -0,0 +1,8 @@ +{ + "emergency": { + "alertClass" : "error", + "excerpt" : { + "short" : "Emergency! This is a variation of the core homepage template." + } + } +} \ No newline at end of file From dc03af5c3ca0d02db9c01f7a3fab02ce2de85453 Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Wed, 11 Feb 2015 00:50:58 -0600 Subject: [PATCH 17/19] fixed a bug preventing template/page pattern states from displaying --- builder/object_factory.js | 1 - config.json | 5 +++-- source/_patternlab-files/partials/patternNav.mustache | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/object_factory.js b/builder/object_factory.js index e4cd5686f..c88682ab4 100644 --- a/builder/object_factory.js +++ b/builder/object_factory.js @@ -12,7 +12,6 @@ "use strict"; var oPattern = function(subdir, filename, data){ - //console.log(filename); this.fileName = filename.substring(0, filename.indexOf('.')); this.subdir = subdir; this.name = (subdir.replace(/[\/\\]/g, '-') + '-' + this.fileName).replace(/\\/g, '-'); //this is the unique name with the subDir diff --git a/config.json b/config.json index da06e5dde..d68c81a2a 100644 --- a/config.json +++ b/config.json @@ -28,8 +28,9 @@ "tools-shortcuts": false, "tools-docs": true }, - "patternStates": { - }, + "patternStates": { + "homepage-emergency" : "inprogress" + }, "patternExportKeys": [], "patternExportDirectory": "./pattern_exports/" } diff --git a/source/_patternlab-files/partials/patternNav.mustache b/source/_patternlab-files/partials/patternNav.mustache index 7664a1f94..86933aa55 100644 --- a/source/_patternlab-files/partials/patternNav.mustache +++ b/source/_patternlab-files/partials/patternNav.mustache @@ -9,7 +9,7 @@ {{/ navItems }} {{# patternItems }} -
  • {{ patternName }}
  • +
  • {{ patternName }}
  • {{/ patternItems }} {{/ buckets }} From bbeb32cdf0334ed58216397ffee81a3b4382f37a Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Wed, 11 Feb 2015 01:05:01 -0600 Subject: [PATCH 18/19] pseudo pattern support added to changelog. a little more dry closes #33 --- CHANGELOG | 2 ++ builder/pattern_assembler.js | 18 +++++++++++++++--- builder/patternlab.js | 21 ++++++--------------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b19590994..de3cb571e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,8 @@ PL-node-v0.8.0 - ADD: pattern link support - CHG: updated included mustache templates to reflect pattern links in navigation and compiling pages direct from templates - THX: @getsetbro for finding and fixing a typo + - FIX: fixed a bug preventing pattern states from displaying on the flat template/pages + - ADD: support for basic pseudo-patterns PL-node-v0.1.7 - ADD: pattern export diff --git a/builder/pattern_assembler.js b/builder/pattern_assembler.js index 057f984b0..d91136763 100644 --- a/builder/pattern_assembler.js +++ b/builder/pattern_assembler.js @@ -6,13 +6,25 @@ var pattern_assembler = function(){ - function exportPatterns(patternlab){ + function setState(pattern, patternlab){ + if(patternlab.config.patternStates[pattern.patternName]){ + pattern.patternState = patternlab.config.patternStates[pattern.patternName]; + } else{ + pattern.patternState = ""; + } + } + function addPattern(pattern, patternLab){ + patternLab.data.link[pattern.patternGroup + '-' + pattern.patternName] = '/patterns/' + pattern.patternLink; + patternLab.patterns.push(pattern); } return { - export_patterns: function(patternlab){ - exportPatterns(patternlab); + setPatternState: function(pattern, patternlab){ + setState(pattern, patternlab); + }, + addPattern: function(pattern, patternLab){ + addPattern(pattern, patternLab); } }; diff --git a/builder/patternlab.js b/builder/patternlab.js index 2a56aa78d..3186822d2 100644 --- a/builder/patternlab.js +++ b/builder/patternlab.js @@ -83,17 +83,14 @@ var patternlab_engine = function(){ return; } - //check for pattern parameters before we do much else. need to remove them into a data object so the rest of the filename parsing works + //TODO: https://github.com/pattern-lab/patternlab-node/issues/88 check for pattern parameters before we do much else. need to remove them into a data object so the rest of the filename parsing works + //TODO: https://github.com/pattern-lab/patternlab-node/issues/95 check for patternstylemodifiers before we do much else. need to remove these from the template for proper rendering //make a new Pattern Object currentPattern = new of.oPattern(subdir, filename, {}); //see if this file has a state - if(patternlab.config.patternStates[currentPattern.patternName]){ - currentPattern.patternState = patternlab.config.patternStates[currentPattern.patternName]; - } else{ - currentPattern.patternState = ""; - } + assembler.setPatternState(currentPattern, patternlab); //look for a json file for this template try { @@ -144,11 +141,7 @@ var patternlab_engine = function(){ var patternVariant = new of.oPattern(subdir, currentPattern.fileName + '-' + variantName + '.mustache', variantFileData); //see if this file has a state - if(patternlab.config.patternStates[patternVariant.patternName]){ - patternVariant.patternState = patternlab.config.patternStates[patternVariant.patternName]; - } else{ - patternVariant.patternState = ""; - } + assembler.setPatternState(patternVariant, patternlab); //use the same template as the non-variant patternVariant.template = currentPattern.template; @@ -157,14 +150,12 @@ var patternlab_engine = function(){ lineage_hunter.find_lineage(patternVariant, patternlab); //add to patternlab object so we can look these up later. - patternlab.data.link[patternVariant.patternGroup + '-' + patternVariant.patternName] = '/patterns/' + patternVariant.patternLink; - patternlab.patterns.push(patternVariant); + assembler.addPattern(patternVariant, patternlab); } } //add to patternlab object so we can look these up later. - patternlab.data.link[currentPattern.patternGroup + '-' + currentPattern.patternName] = '/patterns/' + currentPattern.patternLink; - patternlab.patterns.push(currentPattern); + assembler.addPattern(currentPattern, patternlab); }); var entity_encoder = new he(); From 727cb794ad35dc6fdc8909c45d553534efb83f53 Mon Sep 17 00:00:00 2001 From: BRIAN MUENZENMEYER Date: Wed, 11 Feb 2015 01:15:12 -0600 Subject: [PATCH 19/19] Pseudopattern documentation. I really dont like including this here while not having a solution for patternlab.io documentation. may need to focus on that more. --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/README.md b/README.md index aa49aea53..b33d50d12 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,55 @@ Pattern states should be lowercase and use hyphens where spaces are present. } ``` +##### Pseudo-Patterns +Pseudo-patterns are meant to give developers the ability to build multiple and unique **rendered** patterns off of one base pattern and its mark-up while giving them control over the data that is injected into the base pattern. This feature is especially useful when developing template- and page-style patterns. + +Pseudo-patterns are, essentially, the pattern-specific JSON files that would accompany a pattern. Rather than require a Mustache pattern, though, pseudo-patterns are hinted so a developer can reference a shared pattern. The basic syntax: + +`patternName~pseudoPatternName.json` + +The tilde, `~`, and JSON extension denotes that this is a pseudo-pattern. `patternName` is the parent pattern that will be used when rendering the pseudo-pattern. `patternName` and `pseudoPatternName` are combined when adding the pseudo-pattern to the navigation. + +The JSON file itself works exactly like the [pattern-specific JSON file](http://patternlab.io/docs/data-pattern-specific.html). It has the added benefit that the pseudo-pattern will also import any values from the parent pattern's pattern-specific JSON file. Here is an example (which ships with the package) where we want to show an emergency notification on our homepage template. Our `03-templates/` directory looks like this: + +``` +00-homepage.mustache +01-blog.mustache +02-article.mustache +``` + +Our `00-homepage.mustache` template might look like this: + +``` +
    + {{# emergency }} +
    Oh Noes! Emergency!
    + {{/ emergency }} + { ...a bunch of other content... } +
    +``` + +If our `_data.json` file doesn't give a value for `emergency` that section will never show up when `00-homepage.mustache` is rendered. + +We want to show both the regular and emergency states of the homepage but we don't want to duplicate the entire `00-homepage.mustache` template. That would be a maintenance nightmare. So let's add our pseudo-pattern: + +``` +00-homepage.mustache +00-homepage~emergency.json +01-blog.mustache +02-article.mustache +``` + +In our pseudo-pattern, `00-homepage~emergency.json`, we add our `emergency` attribute: + +``` +{ + "emergency": true +} +``` + +Now when we generate our site we'll have our homepage template rendered twice. Once as the regular template and once as a pseudo-pattern showing the emergency section. Note that the pseudo-pattern will show up in our navigation as `Homepage Emergency`. + ##### Pattern Linking You can build patterns that link to one another to help simulate using a real website. This is especially useful when working with the Pages and Templates pattern types. The basic format is: