Merge pull request #15 from DrMarcII/master

Change to allow multiplex code to be used as a library.
diff --git a/bin/multiplex.dart b/bin/multiplex.dart
index 2ea011c..5c3dcc5 100644
--- a/bin/multiplex.dart
+++ b/bin/multiplex.dart
@@ -3,20 +3,14 @@
 
 library wip.multiplex;
 
-import 'dart:async' show Future;
-import 'dart:convert' show JSON;
-import 'dart:io' show HttpClientResponse, HttpServer, InternetAddress, stderr;
+import 'dart:io' show stderr;
 
 import 'package:args/args.dart' show ArgParser;
 import 'package:logging/logging.dart'
     show hierarchicalLoggingEnabled, Level, Logger, LogRecord;
-import 'package:shelf/shelf.dart' as shelf;
-import 'package:shelf/shelf_io.dart' as io;
-import 'package:shelf_web_socket/shelf_web_socket.dart' as ws;
-import 'package:webkit_inspection_protocol/dom_model.dart' show WipDomModel;
-import 'package:webkit_inspection_protocol/forwarder.dart' show WipForwarder;
+import 'package:webkit_inspection_protocol/multiplex.dart' show Server;
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
-    show ChromeConnection, ChromeTab, WipConnection;
+    show ChromeConnection;
 
 main(List<String> argv) async {
   var args = (new ArgParser()
@@ -42,164 +36,3 @@
       new ChromeConnection(args['chrome_host'], int.parse(args['chrome_port']));
   new Server(int.parse(args['listen_port']), cr, modelDom: args['model_dom']);
 }
-
-class Server {
-  static final _log = new Logger('Server');
-
-  Future<HttpServer> _server;
-  final ChromeConnection chrome;
-  final int port;
-  final bool modelDom;
-
-  final _connections = <String, Future<WipConnection>>{};
-  final _modelDoms = <String, WipDomModel>{};
-
-  Server(this.port, this.chrome, {this.modelDom}) {
-    _server = io.serve(_handler, InternetAddress.ANY_IP_V4, port);
-  }
-
-  shelf.Handler get _handler => const shelf.Pipeline()
-      .addMiddleware(shelf.logRequests(logger: _shelfLogger))
-      .addHandler(new shelf.Cascade()
-          .add(_webSocket)
-          .add(_mainPage)
-          .add(_json)
-          .add(_forward).handler);
-
-  void _shelfLogger(String msg, bool isError) {
-    if (isError) {
-      _log.severe(msg);
-    } else {
-      _log.info(msg);
-    }
-  }
-
-  Future<shelf.Response> _mainPage(shelf.Request request) async {
-    var path = request.url.pathSegments;
-    if (path.isEmpty) {
-      var resp = await _mainPageHtml();
-      _log.info('mainPage: $resp');
-      return new shelf.Response.ok(resp,
-          headers: {'Content-Type': 'text/html'});
-    }
-    return new shelf.Response.notFound(null);
-  }
-
-  Future<String> _mainPageHtml() async {
-    var html = new StringBuffer(r'''<!DOCTYPE html>
-<html>
-<head>
-<title>Chrome Windows</title>
-</head>
-<body>
-<table>
-<thead>
-<tr><td>Title</td><td>Description</td></tr>
-</thead>
-<tbody>''');
-
-    for (var tab in await chrome.getTabs()) {
-      html
-        ..write('<tr><td><a href="/devtools/devtools.html?ws=localhost:')
-        ..write(port)
-        ..write('/devtools/page/')
-        ..write(tab.id)
-        ..write('">');
-      if (tab.title != null && tab.title.isNotEmpty) {
-        html.write(tab.title);
-      } else {
-        html.write(tab.url);
-      }
-      html
-        ..write('</a></td><td>')
-        ..write(tab.description)
-        ..write('</td></tr>');
-    }
-    html.write(r'''</tbody>
-</table>
-</body>
-</html>''');
-    return html.toString();
-  }
-
-  Future<shelf.Response> _json(shelf.Request request) async {
-    var path = request.url.pathSegments;
-    if (path.length == 1 && path[0] == 'json') {
-      var resp = JSON.encode(await chrome.getTabs(), toEncodable: _jsonEncode);
-      _log.info('json: $resp');
-      return new shelf.Response.ok(resp,
-          headers: {'Content-Type': 'application/json'});
-    }
-    return new shelf.Response.notFound(null);
-  }
-
-  Future<shelf.Response> _forward(shelf.Request request) async {
-    _log.info('forwarding: ${request.url}');
-    var dtResp = await chrome.getUrl(request.url.path);
-
-    if (dtResp.statusCode == 200) {
-      return new shelf.Response.ok(dtResp,
-          headers: {'Content-Type': dtResp.headers.contentType.toString()});
-    }
-    _log.warning(
-        'Forwarded ${request.url} returned statusCode: ${dtResp.statusCode}');
-    return new shelf.Response.notFound(null);
-  }
-
-  Future<shelf.Response> _webSocket(shelf.Request request) async {
-    var path = request.url.pathSegments;
-    if (path.length != 3 || path[0] != 'devtools' || path[1] != 'page') {
-      return new shelf.Response.notFound(null);
-    }
-    _log.info('connecting to websocket: ${request.url}');
-
-    return ws.webSocketHandler((ws) async {
-      var debugger = await _connections.putIfAbsent(path[2], () async {
-        var tab = await chrome.getTab((tab) => tab.id == path[2]);
-        return WipConnection.connect(tab.webSocketDebuggerUrl);
-      });
-      var dom;
-      if (modelDom) {
-        dom = await _modelDoms.putIfAbsent(path[2], () {
-          return new WipDomModel(debugger.dom);
-        });
-      }
-      var forwarder = new WipForwarder(debugger, ws, domModel: dom);
-      debugger.onClose.listen((_) {
-        _connections.remove(path[2]);
-        _modelDoms.remove(path[2]);
-        forwarder.stop();
-      });
-    })(request);
-  }
-
-  Future close() async {
-    if (_server != null) {
-      await (await _server).close(force: true);
-      _server = null;
-    }
-  }
-
-  _jsonEncode(obj) {
-    if (obj is ChromeTab) {
-      var json = <String, dynamic>{
-        'description': obj.description,
-        'devtoolsFrontendUrl':
-            '/devtools/devtools.html?ws=localhost:$port/devtools/page/${obj.id}',
-        'id': obj.id,
-        'title': obj.title,
-        'type': obj.type,
-        'url': obj.url,
-        'webSocketDebuggerUrl': 'ws://localhost:$port/devtools/page/${obj.id}'
-      };
-      if (obj.faviconUrl != null) {
-        json['faviconUrl'] = obj.faviconUrl;
-      }
-      return json;
-    } else if (obj is Uri) {
-      return obj.toString();
-    } else {
-      throw new ArgumentError('Cannot encode $obj');
-    }
-  }
-}
diff --git a/lib/dom_model.dart b/lib/dom_model.dart
index 0ddea83..bf34846 100644
--- a/lib/dom_model.dart
+++ b/lib/dom_model.dart
@@ -324,9 +324,7 @@
   var result = <String>[];
   attributes.forEach((k, v) {
     if (k != null) {
-      result
-        ..add(k)
-        ..add(v);
+      result..add(k)..add(v);
     }
   });
   return result;
diff --git a/lib/multiplex.dart b/lib/multiplex.dart
new file mode 100644
index 0000000..dfb797c
--- /dev/null
+++ b/lib/multiplex.dart
@@ -0,0 +1,175 @@
+// Copyright 2015 Google. All rights reserved. Use of this source code is
+// governed by a BSD-style license that can be found in the LICENSE file.
+
+library wip.multiplex;
+
+import 'dart:async' show Future;
+import 'dart:convert' show JSON;
+import 'dart:io' show HttpClientResponse, HttpServer, InternetAddress;
+
+import 'package:logging/logging.dart' show Logger;
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as io;
+import 'package:shelf_web_socket/shelf_web_socket.dart' as ws;
+import 'package:webkit_inspection_protocol/dom_model.dart' show WipDomModel;
+import 'package:webkit_inspection_protocol/forwarder.dart' show WipForwarder;
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
+    show ChromeConnection, ChromeTab, WipConnection;
+
+class Server {
+  static final _log = new Logger('Server');
+
+  Future<HttpServer> _server;
+  final ChromeConnection chrome;
+  final int port;
+  final bool modelDom;
+
+  final _connections = <String, Future<WipConnection>>{};
+  final _modelDoms = <String, WipDomModel>{};
+
+  Server(this.port, this.chrome, {this.modelDom}) {
+    _server = io.serve(_handler, InternetAddress.ANY_IP_V4, port);
+  }
+
+  shelf.Handler get _handler => const shelf.Pipeline()
+      .addMiddleware(shelf.logRequests(logger: _shelfLogger))
+      .addHandler(new shelf.Cascade()
+          .add(_webSocket)
+          .add(_mainPage)
+          .add(_json)
+          .add(_forward).handler);
+
+  void _shelfLogger(String msg, bool isError) {
+    if (isError) {
+      _log.severe(msg);
+    } else {
+      _log.info(msg);
+    }
+  }
+
+  Future<shelf.Response> _mainPage(shelf.Request request) async {
+    var path = request.url.pathSegments;
+    if (path.isEmpty) {
+      var resp = await _mainPageHtml();
+      _log.info('mainPage: $resp');
+      return new shelf.Response.ok(resp,
+          headers: {'Content-Type': 'text/html'});
+    }
+    return new shelf.Response.notFound(null);
+  }
+
+  Future<String> _mainPageHtml() async {
+    var html = new StringBuffer(r'''<!DOCTYPE html>
+<html>
+<head>
+<title>Chrome Windows</title>
+</head>
+<body>
+<table>
+<thead>
+<tr><td>Title</td><td>Description</td></tr>
+</thead>
+<tbody>''');
+
+    for (var tab in await chrome.getTabs()) {
+      html
+        ..write('<tr><td><a href="/devtools/devtools.html?ws=localhost:')
+        ..write(port)
+        ..write('/devtools/page/')
+        ..write(tab.id)
+        ..write('">');
+      if (tab.title != null && tab.title.isNotEmpty) {
+        html.write(tab.title);
+      } else {
+        html.write(tab.url);
+      }
+      html..write('</a></td><td>')..write(tab.description)..write('</td></tr>');
+    }
+    html.write(r'''</tbody>
+</table>
+</body>
+</html>''');
+    return html.toString();
+  }
+
+  Future<shelf.Response> _json(shelf.Request request) async {
+    var path = request.url.pathSegments;
+    if (path.length == 1 && path[0] == 'json') {
+      var resp = JSON.encode(await chrome.getTabs(), toEncodable: _jsonEncode);
+      _log.info('json: $resp');
+      return new shelf.Response.ok(resp,
+          headers: {'Content-Type': 'application/json'});
+    }
+    return new shelf.Response.notFound(null);
+  }
+
+  Future<shelf.Response> _forward(shelf.Request request) async {
+    _log.info('forwarding: ${request.url}');
+    var dtResp = await chrome.getUrl(request.url.path);
+
+    if (dtResp.statusCode == 200) {
+      return new shelf.Response.ok(dtResp,
+          headers: {'Content-Type': dtResp.headers.contentType.toString()});
+    }
+    _log.warning(
+        'Forwarded ${request.url} returned statusCode: ${dtResp.statusCode}');
+    return new shelf.Response.notFound(null);
+  }
+
+  Future<shelf.Response> _webSocket(shelf.Request request) async {
+    var path = request.url.pathSegments;
+    if (path.length != 3 || path[0] != 'devtools' || path[1] != 'page') {
+      return new shelf.Response.notFound(null);
+    }
+    _log.info('connecting to websocket: ${request.url}');
+
+    return ws.webSocketHandler((ws) async {
+      var debugger = await _connections.putIfAbsent(path[2], () async {
+        var tab = await chrome.getTab((tab) => tab.id == path[2]);
+        return WipConnection.connect(tab.webSocketDebuggerUrl);
+      });
+      var dom;
+      if (modelDom) {
+        dom = await _modelDoms.putIfAbsent(path[2], () {
+          return new WipDomModel(debugger.dom);
+        });
+      }
+      var forwarder = new WipForwarder(debugger, ws, domModel: dom);
+      debugger.onClose.listen((_) {
+        _connections.remove(path[2]);
+        _modelDoms.remove(path[2]);
+        forwarder.stop();
+      });
+    })(request);
+  }
+
+  Future close() async {
+    if (_server != null) {
+      await (await _server).close(force: true);
+      _server = null;
+    }
+  }
+
+  _jsonEncode(obj) {
+    if (obj is ChromeTab) {
+      var json = <String, dynamic>{
+        'description': obj.description,
+        'devtoolsFrontendUrl':
+            '/devtools/devtools.html?ws=localhost:$port/devtools/page/${obj.id}',
+        'id': obj.id,
+        'title': obj.title,
+        'type': obj.type,
+        'url': obj.url,
+        'webSocketDebuggerUrl': 'ws://localhost:$port/devtools/page/${obj.id}'
+      };
+      if (obj.faviconUrl != null) {
+        json['faviconUrl'] = obj.faviconUrl;
+      }
+      return json;
+    } else if (obj is Uri) {
+      return obj.toString();
+    } else {
+      throw new ArgumentError('Cannot encode $obj');
+    }
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 657b976..3c42c9e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: webkit_inspection_protocol
-version: 0.1.0
+version: 0.1.1
 description: A client for the Webkit Inspection Protocol (WIP).
 
 homepage: https://github.com/google/webkit_inspection_protocol.dart
@@ -8,10 +8,10 @@
 environment:
   sdk: '>=1.10.0 <2.0.0'
 dependencies:
-  args: '^0.13.1'
+  args: '^0.13.0'
   logging: '^0.11.0'
   shelf: '^0.6.1+2'
   shelf_web_socket: '^0.0.1+2'
 dev_dependencies:
   shelf_static: '^0.2.2'
-  test: '^0.12.0'
+  test: '^0.12.3+2'