| OLD | NEW | 
|---|
| 1 // Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file | 
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. | 
| 4 | 4 | 
| 5 library glob.single_component; |  | 
| 6 |  | 
| 7 import 'package:path/path.dart' as p; | 5 import 'package:path/path.dart' as p; | 
| 8 import 'package:string_scanner/string_scanner.dart'; | 6 import 'package:string_scanner/string_scanner.dart'; | 
| 9 | 7 | 
| 10 import 'ast.dart'; | 8 import 'ast.dart'; | 
| 11 import 'utils.dart'; | 9 import 'utils.dart'; | 
| 12 | 10 | 
| 13 const _HYPHEN = 0x2D; | 11 const _HYPHEN = 0x2D; | 
| 14 const _SLASH = 0x2F; | 12 const _SLASH = 0x2F; | 
| 15 | 13 | 
| 16 /// A parser for globs. | 14 /// A parser for globs. | 
| 17 class Parser { | 15 class Parser { | 
| 18   /// The scanner used to scan the source. | 16   /// The scanner used to scan the source. | 
| 19   final StringScanner _scanner; | 17   final StringScanner _scanner; | 
| 20 | 18 | 
| 21   /// The path context for the glob. | 19   /// The path context for the glob. | 
| 22   final p.Context _context; | 20   final p.Context _context; | 
| 23 | 21 | 
| 24   Parser(String component, this._context) | 22   /// Whether this glob is case-sensitive. | 
| 25       : _scanner = new StringScanner(component); | 23   final bool _caseSensitive; | 
|  | 24 | 
|  | 25   Parser(String component, this._context, {bool caseSensitive: true}) | 
|  | 26       : _scanner = new StringScanner(component), | 
|  | 27         _caseSensitive = caseSensitive; | 
| 26 | 28 | 
| 27   /// Parses an entire glob. | 29   /// Parses an entire glob. | 
| 28   SequenceNode parse() => _parseSequence(); | 30   SequenceNode parse() => _parseSequence(); | 
| 29 | 31 | 
| 30   /// Parses a [SequenceNode]. | 32   /// Parses a [SequenceNode]. | 
| 31   /// | 33   /// | 
| 32   /// If [inOptions] is true, this is parsing within an [OptionsNode]. | 34   /// If [inOptions] is true, this is parsing within an [OptionsNode]. | 
| 33   SequenceNode _parseSequence({bool inOptions: false}) { | 35   SequenceNode _parseSequence({bool inOptions: false}) { | 
| 34     var nodes = []; | 36     var nodes = <AstNode>[]; | 
| 35 | 37 | 
| 36     if (_scanner.isDone) { | 38     if (_scanner.isDone) { | 
| 37       _scanner.error('expected a glob.', position: 0, length: 0); | 39       _scanner.error('expected a glob.', position: 0, length: 0); | 
| 38     } | 40     } | 
| 39 | 41 | 
| 40     while (!_scanner.isDone) { | 42     while (!_scanner.isDone) { | 
| 41       if (inOptions && (_scanner.matches(',') || _scanner.matches('}'))) break; | 43       if (inOptions && (_scanner.matches(',') || _scanner.matches('}'))) break; | 
| 42       nodes.add(_parseNode(inOptions: inOptions)); | 44       nodes.add(_parseNode(inOptions: inOptions)); | 
| 43     } | 45     } | 
| 44 | 46 | 
| 45     return new SequenceNode(nodes); | 47     return new SequenceNode(nodes, caseSensitive: _caseSensitive); | 
| 46   } | 48   } | 
| 47 | 49 | 
| 48   /// Parses an [AstNode]. | 50   /// Parses an [AstNode]. | 
| 49   /// | 51   /// | 
| 50   /// If [inOptions] is true, this is parsing within an [OptionsNode]. | 52   /// If [inOptions] is true, this is parsing within an [OptionsNode]. | 
| 51   AstNode _parseNode({bool inOptions: false}) { | 53   AstNode _parseNode({bool inOptions: false}) { | 
| 52     var star = _parseStar(); | 54     var star = _parseStar(); | 
| 53     if (star != null) return star; | 55     if (star != null) return star; | 
| 54 | 56 | 
| 55     var anyChar = _parseAnyChar(); | 57     var anyChar = _parseAnyChar(); | 
| 56     if (anyChar != null) return anyChar; | 58     if (anyChar != null) return anyChar; | 
| 57 | 59 | 
| 58     var range = _parseRange(); | 60     var range = _parseRange(); | 
| 59     if (range != null) return range; | 61     if (range != null) return range; | 
| 60 | 62 | 
| 61     var options = _parseOptions(); | 63     var options = _parseOptions(); | 
| 62     if (options != null) return options; | 64     if (options != null) return options; | 
| 63 | 65 | 
| 64     return _parseLiteral(inOptions: inOptions); | 66     return _parseLiteral(inOptions: inOptions); | 
| 65   } | 67   } | 
| 66 | 68 | 
| 67   /// Tries to parse a [StarNode] or a [DoubleStarNode]. | 69   /// Tries to parse a [StarNode] or a [DoubleStarNode]. | 
| 68   /// | 70   /// | 
| 69   /// Returns `null` if there's not one to parse. | 71   /// Returns `null` if there's not one to parse. | 
| 70   AstNode _parseStar() { | 72   AstNode _parseStar() { | 
| 71     if (!_scanner.scan('*')) return null; | 73     if (!_scanner.scan('*')) return null; | 
| 72     return _scanner.scan('*') ? new DoubleStarNode(_context) : new StarNode(); | 74     return _scanner.scan('*') | 
|  | 75         ? new DoubleStarNode(_context, caseSensitive: _caseSensitive) | 
|  | 76         : new StarNode(caseSensitive: _caseSensitive); | 
| 73   } | 77   } | 
| 74 | 78 | 
| 75   /// Tries to parse an [AnyCharNode]. | 79   /// Tries to parse an [AnyCharNode]. | 
| 76   /// | 80   /// | 
| 77   /// Returns `null` if there's not one to parse. | 81   /// Returns `null` if there's not one to parse. | 
| 78   AstNode _parseAnyChar() { | 82   AstNode _parseAnyChar() { | 
| 79     if (!_scanner.scan('?')) return null; | 83     if (!_scanner.scan('?')) return null; | 
| 80     return new AnyCharNode(); | 84     return new AnyCharNode(caseSensitive: _caseSensitive); | 
| 81   } | 85   } | 
| 82 | 86 | 
| 83   /// Tries to parse an [RangeNode]. | 87   /// Tries to parse an [RangeNode]. | 
| 84   /// | 88   /// | 
| 85   /// Returns `null` if there's not one to parse. | 89   /// Returns `null` if there's not one to parse. | 
| 86   AstNode _parseRange() { | 90   AstNode _parseRange() { | 
| 87     if (!_scanner.scan('[')) return null; | 91     if (!_scanner.scan('[')) return null; | 
| 88     if (_scanner.matches(']')) _scanner.error('unexpected "]".'); | 92     if (_scanner.matches(']')) _scanner.error('unexpected "]".'); | 
| 89     var negated = _scanner.scan('!') || _scanner.scan('^'); | 93     var negated = _scanner.scan('!') || _scanner.scan('^'); | 
| 90 | 94 | 
| 91     readRangeChar() { | 95     readRangeChar() { | 
| 92       var char = _scanner.readChar(); | 96       var char = _scanner.readChar(); | 
| 93       if (negated || char != _SLASH) return char; | 97       if (negated || char != _SLASH) return char; | 
| 94       _scanner.error('"/" may not be used in a range.', | 98       _scanner.error('"/" may not be used in a range.', | 
| 95           position: _scanner.position - 1); | 99           position: _scanner.position - 1); | 
| 96     } | 100     } | 
| 97 | 101 | 
| 98     var ranges = []; | 102     var ranges = <Range>[]; | 
| 99     while (!_scanner.scan(']')) { | 103     while (!_scanner.scan(']')) { | 
| 100       var start = _scanner.position; | 104       var start = _scanner.position; | 
| 101       // Allow a backslash to escape a character. | 105       // Allow a backslash to escape a character. | 
| 102       _scanner.scan('\\'); | 106       _scanner.scan('\\'); | 
| 103       var char = readRangeChar(); | 107       var char = readRangeChar(); | 
| 104 | 108 | 
| 105       if (_scanner.scan('-')) { | 109       if (_scanner.scan('-')) { | 
| 106         if (_scanner.matches(']')) { | 110         if (_scanner.matches(']')) { | 
| 107           ranges.add(new Range.singleton(char)); | 111           ranges.add(new Range.singleton(char)); | 
| 108           ranges.add(new Range.singleton(_HYPHEN)); | 112           ranges.add(new Range.singleton(_HYPHEN)); | 
| 109           continue; | 113           continue; | 
| 110         } | 114         } | 
| 111 | 115 | 
| 112         // Allow a backslash to escape a character. | 116         // Allow a backslash to escape a character. | 
| 113         _scanner.scan('\\'); | 117         _scanner.scan('\\'); | 
| 114 | 118 | 
| 115         var end = readRangeChar(); | 119         var end = readRangeChar(); | 
| 116 | 120 | 
| 117         if (end < char) { | 121         if (end < char) { | 
| 118           _scanner.error("Range out of order.", | 122           _scanner.error("Range out of order.", | 
| 119               position: start, | 123               position: start, | 
| 120               length: _scanner.position - start); | 124               length: _scanner.position - start); | 
| 121         } | 125         } | 
| 122         ranges.add(new Range(char, end)); | 126         ranges.add(new Range(char, end)); | 
| 123       } else { | 127       } else { | 
| 124         ranges.add(new Range.singleton(char)); | 128         ranges.add(new Range.singleton(char)); | 
| 125       } | 129       } | 
| 126     } | 130     } | 
| 127 | 131 | 
| 128     return new RangeNode(ranges, negated: negated); | 132     return new RangeNode(ranges, | 
|  | 133         negated: negated, caseSensitive: _caseSensitive); | 
| 129   } | 134   } | 
| 130 | 135 | 
| 131   /// Tries to parse an [OptionsNode]. | 136   /// Tries to parse an [OptionsNode]. | 
| 132   /// | 137   /// | 
| 133   /// Returns `null` if there's not one to parse. | 138   /// Returns `null` if there's not one to parse. | 
| 134   AstNode _parseOptions() { | 139   AstNode _parseOptions() { | 
| 135     if (!_scanner.scan('{')) return null; | 140     if (!_scanner.scan('{')) return null; | 
| 136     if (_scanner.matches('}')) _scanner.error('unexpected "}".'); | 141     if (_scanner.matches('}')) _scanner.error('unexpected "}".'); | 
| 137 | 142 | 
| 138     var options = []; | 143     var options = <SequenceNode>[]; | 
| 139     do { | 144     do { | 
| 140       options.add(_parseSequence(inOptions: true)); | 145       options.add(_parseSequence(inOptions: true)); | 
| 141     } while (_scanner.scan(',')); | 146     } while (_scanner.scan(',')); | 
| 142 | 147 | 
| 143     // Don't allow single-option blocks. | 148     // Don't allow single-option blocks. | 
| 144     if (options.length == 1) _scanner.expect(','); | 149     if (options.length == 1) _scanner.expect(','); | 
| 145     _scanner.expect('}'); | 150     _scanner.expect('}'); | 
| 146 | 151 | 
| 147     return new OptionsNode(options); | 152     return new OptionsNode(options, caseSensitive: _caseSensitive); | 
| 148   } | 153   } | 
| 149 | 154 | 
| 150   /// Parses a [LiteralNode]. | 155   /// Parses a [LiteralNode]. | 
| 151   AstNode _parseLiteral({bool inOptions: false}) { | 156   AstNode _parseLiteral({bool inOptions: false}) { | 
| 152     // If we're in an options block, we want to stop parsing as soon as we hit a | 157     // If we're in an options block, we want to stop parsing as soon as we hit a | 
| 153     // comma. Otherwise, commas are fair game for literals. | 158     // comma. Otherwise, commas are fair game for literals. | 
| 154     var regExp = new RegExp( | 159     var regExp = new RegExp( | 
| 155         inOptions ? r'[^*{[?\\}\],()]*' : r'[^*{[?\\}\]()]*'); | 160         inOptions ? r'[^*{[?\\}\],()]*' : r'[^*{[?\\}\]()]*'); | 
| 156 | 161 | 
| 157     _scanner.scan(regExp); | 162     _scanner.scan(regExp); | 
| 158     var buffer = new StringBuffer()..write(_scanner.lastMatch[0]); | 163     var buffer = new StringBuffer()..write(_scanner.lastMatch[0]); | 
| 159 | 164 | 
| 160     while (_scanner.scan('\\')) { | 165     while (_scanner.scan('\\')) { | 
| 161       buffer.writeCharCode(_scanner.readChar()); | 166       buffer.writeCharCode(_scanner.readChar()); | 
| 162       _scanner.scan(regExp); | 167       _scanner.scan(regExp); | 
| 163       buffer.write(_scanner.lastMatch[0]); | 168       buffer.write(_scanner.lastMatch[0]); | 
| 164     } | 169     } | 
| 165 | 170 | 
| 166     for (var char in const [']', '(', ')']) { | 171     for (var char in const [']', '(', ')']) { | 
| 167       if (_scanner.matches(char)) _scanner.error('unexpected "$char"'); | 172       if (_scanner.matches(char)) _scanner.error('unexpected "$char"'); | 
| 168     } | 173     } | 
| 169     if (!inOptions && _scanner.matches('}')) _scanner.error('unexpected "}"'); | 174     if (!inOptions && _scanner.matches('}')) _scanner.error('unexpected "}"'); | 
| 170 | 175 | 
| 171     return new LiteralNode(buffer.toString(), _context); | 176     return new LiteralNode(buffer.toString(), | 
|  | 177         context: _context, caseSensitive: _caseSensitive); | 
| 172   } | 178   } | 
| 173 } | 179 } | 
| OLD | NEW | 
|---|