Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(765)

Side by Side Diff: sdk/lib/_internal/pub/lib/src/command/serve.dart

Issue 20204003: First stab at a dev server in pub using barback. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library pub.command.serve;
6
7 import 'dart:async';
8 import 'dart:io';
9
10 import 'package:barback/barback.dart';
11 import 'package:path/path.dart' as path;
12
13 import '../command.dart';
14 import '../entrypoint.dart';
15 import '../exit_codes.dart' as exit_codes;
16 import '../log.dart' as log;
17 import '../utils.dart';
18
19 final _green = getPlatformString('\u001b[32m');
20 final _red = getPlatformString('\u001b[31m');
21 final _none = getPlatformString('\u001b[0m');
22
23 /// Handles the `serve` pub command.
24 class ServeCommand extends PubCommand {
25 String get description => "Run a local web development server.";
26 String get usage => 'pub serve';
27
28 ServeCommand() {
29 commandParser.addOption('port', defaultsTo: '8080',
30 help: 'The port to listen on.');
31 }
32
33 Future onRun() {
34 return PubPackageProvider.create(entrypoint).then((provider) {
35 var port;
36 try {
37 port = int.parse(commandOptions['port']);
38 } on FormatException catch(_) {
39 log.error('Could not parse port "${commandOptions['port']}"');
40 this.printUsage();
41 exit(exit_codes.USAGE);
42 }
nweiz 2013/07/25 22:58:58 Style nit: put this in a getter to avoid the awkwa
Bob Nystrom 2013/07/26 20:20:11 It is a bit awkward, but I'm not crazy about a get
43
44 var barback = new Barback(provider);
45
46 barback.results.listen((result) {
nweiz 2013/07/25 22:58:58 Add a TODO to report errors and successful build c
Bob Nystrom 2013/07/26 20:20:11 Do we actually intend to do that? That seems painf
nweiz 2013/07/29 20:23:14 In my experience, people like it. We can be smart
Bob Nystrom 2013/07/29 21:45:43 Done.
47 if (result.succeeded) {
48 log.message("Build completed ${_green}successfully$_none");
49 } else {
50 log.message("Build completed with "
51 "${_red}${result.errors.length}$_none errors.");
52 }
53 });
54
55 barback.errors.listen((error) {
56 log.error("${_red}Build error:\n$error$_none");
57 });
58
59 // Add all of the visible files.
60 for (var package in provider.packages) {
61 barback.updateSources(provider.listAssets(package));
nweiz 2013/07/25 22:58:58 Add a TODO to re-update these sources when they ch
Bob Nystrom 2013/07/26 20:20:11 Done.
62 }
63
64 HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, port).then((server) {
nweiz 2013/07/25 22:58:58 Doesn't "localhost" work these days?
Bob Nystrom 2013/07/26 20:20:11 Not sure. I figured an enum is better than a magic
nweiz 2013/07/29 20:23:14 We're using "localhost" below, and "127.0.0.1" els
Bob Nystrom 2013/07/29 21:45:43 Done.
65 log.message("Serving ${entrypoint.root.name} "
66 "on http://localhost:${server.port}...");
nweiz 2013/07/25 22:58:58 The "..." here may make copy/pasting this URL diff
Bob Nystrom 2013/07/26 20:20:11 Done.
67
68 server.listen((HttpRequest request) {
nweiz 2013/07/25 22:58:58 Don't type annotate [request]. We should check th
Bob Nystrom 2013/07/26 20:20:11 Done.
69 var id = getIdFromUri(request.uri);
70 barback.getAssetById(id).then((asset) {
71 log.message(
72 "$_green${request.method}$_none ${request.uri} -> $asset");
73 request.response.addStream(asset.read()).then((_) {
nweiz 2013/07/25 22:58:58 Add a TODO to set the Content-Type based on the ex
Bob Nystrom 2013/07/26 20:20:11 Done.
74 request.response.close();
nweiz 2013/07/25 22:58:58 I thought [addStream] automatically closed the con
Bob Nystrom 2013/07/26 20:20:11 Nope: "When the IOSink is bound to a stream (thro
75 });
nweiz 2013/07/25 22:58:58 We should handle errors that come off this future.
Bob Nystrom 2013/07/26 20:20:11 Done.
nweiz 2013/07/29 20:23:14 Unlike errors coming off [getAssetById], errors co
Bob Nystrom 2013/07/29 21:45:43 Took out the 500 stuff. There isn't any tests for
76 }).catchError((error) {
77 if (error is! AssetNotFoundException) throw error;
nweiz 2013/07/25 22:58:58 This will top-level the error. That's not what we
Bob Nystrom 2013/07/26 20:20:11 Done. I log it and respond with a 500. Seems about
nweiz 2013/07/29 20:23:14 The only error getAssetById is expected to raise i
Bob Nystrom 2013/07/29 21:45:43 Done.
78 log.error("$_red${request.method}$_none ${request.uri} -> $error");
79 request.response.statusCode = 404;
80 request.response.reasonPhrase = error.toString();
nweiz 2013/07/25 22:58:58 The reason phrase is expected to be very brief, an
Bob Nystrom 2013/07/26 20:20:11 Done.
81 request.response.close();
82 return;
nweiz 2013/07/25 22:58:58 Remove.
Bob Nystrom 2013/07/26 20:20:11 Done.
83 });
84 });
85 });
86
87 // TODO(rnystrom): Hack! Return a future that never completes to leave
88 // pub running.
89 return new Completer().future;
nweiz 2013/07/25 22:58:58 This seems, as you say, hacky. It feels like we sh
Bob Nystrom 2013/07/26 20:20:11 In the above code, I'm catching all errors that co
nweiz 2013/07/29 20:23:14 As with [getAssetById], programmatic errors in bar
Bob Nystrom 2013/07/29 21:45:43 Done.
90 });
91 }
92
93 AssetId getIdFromUri(Uri uri) {
94 // Strip the leading "/" from the URL.
95 var relative = uri.path.substring(1);
nweiz 2013/07/25 22:58:58 Rather than explicitly using substring, it seems c
Bob Nystrom 2013/07/26 20:20:11 Done.
96 if (relative == "") relative = "index.html";
nweiz 2013/07/25 22:58:58 Supporting "index.html" only for the root seems wr
Bob Nystrom 2013/07/26 20:20:11 That the nice thing about "/". For that one case,
nweiz 2013/07/29 20:23:14 I think supporting only "/" is actively harmful. U
Bob Nystrom 2013/07/29 21:45:43 Removed it.
97
98 var parts = path.split(relative);
nweiz 2013/07/25 22:58:58 This will break on windows. You need to make a URL
Bob Nystrom 2013/07/26 20:20:11 Done.
99
100 // Checks to see if [uri]'s path contains a special "symlink" directory
101 // named [name]. If so, maps the path after that to be within [dir].
nweiz 2013/07/25 22:58:58 This isn't strictly accurate; it treats the direct
Bob Nystrom 2013/07/26 20:20:11 Done.
Bob Nystrom 2013/07/26 20:20:11 Done.
102 AssetId _trySpecialUrl(String name, String dir) {
103 // Find the package name and the relative path in the package.
104 var index = parts.indexOf(name);
105 if (index == -1) return null;
106
107 var package = parts[index + 1];
108 var assetPath = path.join(dir, path.joinAll(parts.skip(index + 2)));
109 return new AssetId(package, assetPath);
nweiz 2013/07/25 22:58:58 Right now this will crash on paths like "packages"
Bob Nystrom 2013/07/26 20:20:11 Done.
110 }
111
112 // See if it's "packages" URL.
113 var id = _trySpecialUrl("packages", "lib");
114 if (id != null) return id;
115
116 // See if it's an "assets" URL.
117 id = _trySpecialUrl("assets", "asset");
118 if (id != null) return id;
119
120 // Otherwise, it's a path in current package's web directory.
121 relative = path.join("web", relative);
122 return new AssetId(entrypoint.root.name, relative);
123 }
124 }
125
126 class PubPackageProvider implements PackageProvider {
nweiz 2013/07/25 22:58:58 This is going to be used elsewhere pretty soon. Mi
Bob Nystrom 2013/07/26 20:20:11 Done.
127 /// The [Entrypoint] package being served.
128 final Entrypoint _entrypoint;
129
130 /// Maps the names of all of the packages in [_entrypoint]'s transitive
131 /// dependency graph to the local path of the directory for that package.
132 final Map<String, String> _packageDirs;
133
134 /// Creates a new provider for [entrypoint].
135 static Future<PubPackageProvider> create(Entrypoint entrypoint) {
136 var packageDirs = <String, String>{};
137 var futures = [];
138
139 packageDirs[entrypoint.root.name] = entrypoint.root.dir;
140
141 // Cache package directories up front so we can have synchronous access
142 // to them.
143 // TODO(rnystrom): Handle missing or out of date lockfile.
144 entrypoint.loadLockFile().packages.forEach((name, package) {
145 var source = entrypoint.cache.sources[package.source];
146 futures.add(source.getDirectory(package).then((packageDir) {
147 packageDirs[name] = packageDir;
148 }));
149 });
nweiz 2013/07/25 22:58:58 Rather than manually expanding it, just use `entry
Bob Nystrom 2013/07/26 20:20:11 packages is a Map, not an Iterable, so I tried thi
150
151 return Future.wait(futures).then((_) {
152 return new PubPackageProvider._(entrypoint, packageDirs);
153 });
154 }
155
156 PubPackageProvider._(this._entrypoint, this._packageDirs);
157
158 Iterable<String> get packages => _packageDirs.keys;
159
160 List<AssetId> listAssets(String package, {String within}) {
nweiz 2013/07/25 22:58:58 Right now [listAssets] is used nowhere in barback.
Bob Nystrom 2013/07/26 20:20:11 It was being used forever ago when I was prototypi
161 var files = <AssetId>[];
162
163 addFiles(String dirPath) {
164 var packageDir = _packageDirs[package];
165 var dir = new Directory(path.join(packageDir, dirPath));
166 if (!dir.existsSync()) return;
nweiz 2013/07/25 22:58:58 Use [dirExists].
Bob Nystrom 2013/07/29 21:45:43 Done.
167 var entries = dir.listSync(recursive: true);
nweiz 2013/07/25 22:58:58 Use [listDir] rather than [Directory.listSync].
Bob Nystrom 2013/07/26 20:20:11 Done.
168 files.addAll(entries
169 .where((entry) => entry is File)
170 .map((entry) => new AssetId(package,
171 path.relative(entry.path, from: packageDir))));
172 }
173
174 // If we aren't given a subdirectory, use all externally visible files.
175 if (within == null) {
176 // Expose the "asset" and "lib" directories.
177 addFiles("asset");
178 addFiles("lib");
179
180 // The entrypoint's "web" directory is also visible.
181 if (package == _entrypoint.root.name) {
182 addFiles("web");
183 }
184 } else {
185 addFiles(within);
186 }
187
188 return files;
189 }
nweiz 2013/07/25 22:58:58 This shouldn't list the contents of packages direc
Bob Nystrom 2013/07/26 20:20:11 Done.
190
191 // TODO(rnystrom): Actually support transformers.
192 Iterable<Iterable<Transformer>> getTransformers(String package) => [];
193
194 Future<Asset> getAsset(AssetId id) {
195 var file = path.normalize(path.join(_packageDirs[id.package], id.path));
nweiz 2013/07/25 22:58:58 What's the value of normalizing the path here?
Bob Nystrom 2013/07/26 20:20:11 Not much. Removed.
196 return new Future.value(new Asset.fromPath(id, file));
197 }
198 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698