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 }