// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'package:pub_semver/pub_semver.dart'; import 'package:string_scanner/string_scanner.dart'; import 'utils.dart'; /// Runs a simple preprocessor over [input] to remove sections that are /// incompatible with the available barback version. /// /// [versions] are the available versions of each installed package, and /// [sourceUrl] is a [String] or [Uri] indicating where [input] came from. It's /// used for error reporting. /// /// For the most part, the preprocessor leaves text in the source document /// alone. However, it handles two types of lines specially. Lines that begin /// with `//>` are uncommented by the preprocessor, and lines that begin with /// `//#` are operators. /// /// The preprocessor currently supports one top-level operator, "if": /// /// //# if barback >=0.14.1 /// ... /// //# else /// ... /// //# end /// /// If can check against any package installed in the current package. It can /// check the version of the package, as above, or (if the version range is /// omitted) whether the package exists at all. If the condition is true, /// everything within the first block is included in the output and everything /// within the second block is removed; otherwise, the first block is removed /// and the second block is included. The `else` block is optional. /// /// It's important that the preprocessor syntax also be valid Dart code, because /// pub loads the source files before preprocessing and runs them against the /// version of barback that was compiled into pub. This is why the `//>` syntax /// exists: so that code can be hidden from the running pub process but still be /// visible to the barback isolate. For example: /// /// //# if barback >= 0.14.1 /// ClassMirror get aggregateClass => reflectClass(AggregateTransformer); /// //# else /// //> ClassMirror get aggregateClass => null; /// //# end String preprocess(String input, Map<String, Version> versions, sourceUrl) { // Short-circuit if there are no preprocessor directives in the file. if (!input.contains(new RegExp(r"^//[>#]", multiLine: true))) return input; return new _Preprocessor(input, versions, sourceUrl).run(); } /// The preprocessor class. class _Preprocessor { /// The scanner over the input string. final StringScanner _scanner; final Map<String, Version> _versions; /// The buffer to which the output is written. final _buffer = new StringBuffer(); _Preprocessor(String input, this._versions, sourceUrl) : _scanner = new StringScanner(input, sourceUrl: sourceUrl); /// Run the preprocessor and return the processed output. String run() { while (!_scanner.isDone) { if (_scanner.scan(new RegExp(r"//#[ \t]*"))) { _if(); } else { _emitText(); } } _scanner.expectDone(); return _buffer.toString(); } /// Emit lines of the input document directly until an operator is /// encountered. void _emitText() { while (!_scanner.isDone && !_scanner.matches("//#")) { if (_scanner.scan("//>")) { if (!_scanner.matches("\n")) _scanner.expect(" "); } _scanner.scan(new RegExp(r"[^\n]*\n?")); _buffer.write(_scanner.lastMatch[0]); } } /// Move through lines of the input document without emitting them until an /// operator is encountered. void _ignoreText() { while (!_scanner.isDone && !_scanner.matches("//#")) { _scanner.scan(new RegExp(r"[^\n]*\n?")); } } /// Handle an `if` operator. void _if() { _scanner.expect(new RegExp(r"if[ \t]+"), name: "if statement"); _scanner.expect(identifierRegExp, name: "package name"); var package = _scanner.lastMatch[0]; _scanner.scan(new RegExp(r"[ \t]*")); var constraint = VersionConstraint.any; if (_scanner.scan(new RegExp(r"[^\n]+"))) { try { constraint = new VersionConstraint.parse(_scanner.lastMatch[0]); } on FormatException catch (error) { _scanner.error("Invalid version constraint: ${error.message}"); } } _scanner.expect("\n"); var allowed = _versions.containsKey(package) && constraint.allows(_versions[package]); if (allowed) { _emitText(); } else { _ignoreText(); } _scanner.expect("//#"); _scanner.scan(new RegExp(r"[ \t]*")); if (_scanner.scan("else")) { _scanner.expect("\n"); if (allowed) { _ignoreText(); } else { _emitText(); } _scanner.expect("//#"); _scanner.scan(new RegExp(r"[ \t]*")); } _scanner.expect("end"); if (!_scanner.isDone) _scanner.expect("\n"); } }