1 /* 2 * Copyright (c) 2017-2019 sel-project 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 * 22 */ 23 /** 24 * Copyright: Copyright (c) 2017-2019 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/selery/source/selery/command/args.d, selery/command/args.d) 28 */ 29 module selery.command.args; 30 31 import std.conv : ConvException; 32 import std..string : strip, indexOf; 33 34 import selery.command.util : Target, Position; 35 36 /** 37 * Reads various types of strings from a single stream. 38 * Example: 39 * --- 40 * auto reader = StringReader(`a " quoted " long string`); 41 * assert(reader.readString() == "a"); 42 * assert(reader.readQuotedString() == " quoted "); 43 * assert(reader.readText() == "long string"); 44 * --- 45 */ 46 struct StringReader { 47 48 private string str; 49 private size_t index = 0; 50 51 public this(string str) { 52 this.str = str.strip; 53 } 54 55 /** 56 * Indicates whether the string has been fully readed. 57 */ 58 public @property bool eof() { 59 return this.str.length <= this.index; 60 } 61 62 /** 63 * Skips spaces. 64 * Example: 65 * --- 66 * auto r = StringReader(` test test`); 67 * r.skip(); 68 * assert(r.index == 3); // private variable 69 * --- 70 */ 71 public void skip() { 72 while(!this.eof() && this.str[this.index] == ' ') this.index++; 73 } 74 75 private void skipAndThrow() { 76 this.skip(); 77 if(this.eof()) throw new StringTerminatedException(); 78 } 79 80 /* 81 * Reads a string without skipping the white characters, 82 * assuming that they've already been skipped. 83 */ 84 private string readStringImpl() { 85 size_t start = this.index; 86 while(!this.eof && this.str[this.index] != ' ') this.index++; 87 return this.str[start..this.index]; 88 } 89 90 /** 91 * Reads a string until the next space character. 92 * Throws: 93 * StringTerminatedException if there's nothing left to read 94 * Example: 95 * --- 96 * auto r = StringReader(`hello world`); 97 * assert(r.readString() == "hello"); 98 * assert(r.readString() == "world"); 99 * --- 100 */ 101 public string readString() { 102 this.skipAndThrow(); 103 return this.readStringImpl(); 104 } 105 106 /** 107 * Reads a string that may be quoted. 108 * Throws: 109 * StringTerminatedException if there's nothing left to read 110 * QuotedStringNotClosedException if the string is quoted but never terminated 111 * Example: 112 * --- 113 * auto r = StringReader(`quoted "not quoted"`); 114 * assert(r.readQuotedString() == "quoted"); 115 * assert(r.readQuotedString() == "not quoted"); 116 * 117 * // escaping is not supported 118 * r = StringReader(`"escaped \""`); 119 * assert(r.readQuotedString() == `escaped \`); 120 * try { 121 * r.readQuotedString(); 122 * assert(0); 123 * } catch(QuotedStringNotTerminatedException) {} 124 * --- 125 */ 126 public string readQuotedString() { 127 this.skipAndThrow(); 128 if(this.str[this.index] == '"') { 129 auto start = ++this.index; 130 if(this.eof()) throw new QuotedStringNotClosedException(); 131 while(this.str[this.index] != '"') { 132 if(++this.index == this.str.length) throw new QuotedStringNotClosedException(); 133 } 134 return this.str[start..++this.index-1]; 135 } else { 136 return this.readStringImpl(); 137 } 138 } 139 140 /** 141 * Reads the remaining text. 142 * Throws: 143 * StringTerminatedException if there's nothing left to read 144 * Example: 145 * --- 146 * auto r = StringReader(`this is not a string`); 147 * assert(r.readString() == "this"); 148 * assert(r.readQuotedString() == "is"); 149 * assert(r.readText() == "not a string"); 150 * --- 151 */ 152 public string readText() { 153 this.skipAndThrow(); 154 size_t start = this.index; 155 this.index = this.str.length; 156 return this.str[start..$]; 157 } 158 159 } 160 161 class StringTerminatedException : ConvException { 162 163 public this(string file=__FILE__, size_t line=__LINE__) { 164 super("The string is terminated", file, line); 165 } 166 167 } 168 169 class QuotedStringNotClosedException : ConvException { 170 171 public this(string file=__FILE__, size_t line=__LINE__) { 172 super("The quoted string isn't closed", file, line); 173 } 174 175 }