Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: afde85f2992b706f761d62e83711a647d4b36c2f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
/*******************************************************************************
 * Copyright (c) 2015 QNX Software Systems and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * QNX Software Systems - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.qt.ui.pro.parser;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;

/**
 * Contains all information about a variable's representation in a Qt Project (.pro) File. This includes information about offsets,
 * lengths, and textual representation of various components of a variable declaration such as its:
 * <ul>
 * <li>Name, such as "SOURCES"</li>
 * <li>Assignment operator (= or +=)</li>
 * <li>Values for a particular line</li>
 * <li>Comments for a particular line</li>
 * <li>Line feeds</li>
 * <li>Line escapes (\)</li>
 * </ul>
 * Also contains the static method <code>findNextVariable(Scanner)</code> to perform the regular expressions lookup of the next
 * variable in a document.
 */
public class QtProjectVariable {
	private static final Pattern REGEX = Pattern.compile(
			"(?m)^\\h*((?:[_a-zA-Z][_a-zA-Z0-9]*\\.)*[_a-zA-Z][_a-zA-Z0-9]*)\\h*(=|\\+=|-=|\\*=)\\h*([^#\\v]*?)\\h*((?:(\\\\)\\h*)?(#[^\\v]*)?$)"); //$NON-NLS-1$
	private static final Pattern LINE_ESCAPE_REGEX = Pattern
			.compile("(?m)^(\\h*)([^#\\v]*?)\\h*((?:(\\\\)\\h*)?(#[^\\v]*)?$)"); //$NON-NLS-1$

	private static final int GROUP_VAR_NAME = 1;
	private static final int GROUP_VAR_ASSIGNMENT = 2;
	private static final int GROUP_VAR_CONTENTS = 3;
	private static final int GROUP_VAR_TERMINATOR = 4;
	private static final int GROUP_VAR_LINE_ESCAPE = 5;
	private static final int GROUP_VAR_COMMENT = 6;

	private static final int GROUP_LINE_INDENT = 1;
	private static final int GROUP_LINE_CONTENTS = 2;
	private static final int GROUP_LINE_TERMINATOR = 3;
	private static final int GROUP_LINE_LINE_ESCAPE = 4;
	private static final int GROUP_LINE_COMMENT = 5;

	/**
	 * Finds the next Qt Project Variable within a String using the given Scanner. If there are no variables to be found, this
	 * method will return <code>null</code>.
	 *
	 * @param scanner
	 *            the scanner to use for regular expressions matching
	 * @return the next variable or <code>null</code> if none
	 */
	public static QtProjectVariable findNextVariable(Scanner scanner) {
		List<MatchResult> matchResults = new ArrayList<>();

		// Find the start of a variable declaration
		String match = scanner.findWithinHorizon(REGEX, 0);
		if (match == null) {
			return null;
		}

		// Get subsequent lines if the previous one ends with '\'
		MatchResult matchResult = scanner.match();
		matchResults.add(matchResult);
		if (matchResult.group(QtProjectVariable.GROUP_VAR_TERMINATOR).startsWith("\\")) { //$NON-NLS-1$
			do {
				match = scanner.findWithinHorizon(LINE_ESCAPE_REGEX, 0);
				if (match == null) {
					// This means that we have a newline escape where another line doesn't exist
					break;
				}

				matchResult = scanner.match();
				matchResults.add(matchResult);
			} while (matchResult.group(QtProjectVariable.GROUP_LINE_TERMINATOR).startsWith("\\")); //$NON-NLS-1$
		}
		return new QtProjectVariable(matchResults);
	}

	private final int startOffset;
	private final int endOffset;
	private final String text;

	private final List<MatchResult> matchResults;

	/**
	 * Constructs a project file variable from a list of match results obtained from a <code>Scanner</code>. This constructor is
	 * only intended to be called from within the static method <code>findNextVariable(Scanner)</code>.
	 *
	 * @param matches
	 *            list of <code>MatchResult</code>
	 */
	private QtProjectVariable(List<MatchResult> matches) {
		this.startOffset = matches.get(0).start();
		this.endOffset = matches.get(matches.size() - 1).end();
		this.matchResults = matches;

		StringBuilder sb = new StringBuilder();
		for (MatchResult m : matches) {
			sb.append(m.group());
		}
		this.text = sb.toString();
	}

	/**
	 * Gets the offset of this variable relative to the start of its containing document.
	 *
	 * @return the offset of this variable
	 */
	public int getOffset() {
		return startOffset;
	}

	/**
	 * Gets the length of this variable as it appears in its containing document.
	 *
	 * @return the total length of this variable
	 */
	public int getLength() {
		return endOffset - startOffset;
	}

	/**
	 * Gets the name of this variable as it appears in the document. For example, the <code>"SOURCES"</code> variable.
	 *
	 * @return the name of this variable
	 */
	public String getName() {
		return matchResults.get(0).group(GROUP_VAR_NAME);
	}

	/**
	 * the assignment operator of this variable (<code>+=</code> or <code>"="</code>)
	 *
	 * @return the assignment operator
	 */
	public String getAssignmentOperator() {
		return matchResults.get(0).group(GROUP_VAR_ASSIGNMENT);
	}

	/**
	 * Returns a list of value(s) assigned to this variable. Each entry in the list represents a new line.
	 *
	 * @return a List containing all of the value(s) assigned to this variable
	 */
	public List<String> getValues() {
		List<String> values = new ArrayList<>();
		values.add(matchResults.get(0).group(GROUP_VAR_CONTENTS));
		for (int i = 1; i < matchResults.size(); i++) {
			values.add(matchResults.get(i).group(GROUP_LINE_CONTENTS));
		}
		return values;
	}

	/**
	 * Returns the indentation of the given line as a String. Mainly used by the QtProjectFileWriter to write back to the Document.
	 *
	 * @param line
	 *            the line number to check
	 * @return a <code>String</code> representing the indentation of the given line
	 */
	public String getIndentString(int line) {
		MatchResult match = matchResults.get(line);
		if (line == 0) {
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < match.start(GROUP_VAR_CONTENTS) - match.start(); i++) {
				sb.append(' ');
			}
			return sb.toString();
		}
		return match.group(GROUP_LINE_INDENT);
	}

	/**
	 * Retrieves the offset of the value portion of a given line relative to the start of its containing document.
	 *
	 * @param line
	 *            the line to check
	 * @return the offset of the value
	 */
	public int getValueOffsetForLine(int line) {
		if (line == 0) {
			return matchResults.get(line).start(GROUP_VAR_CONTENTS);
		}
		return matchResults.get(line).start(GROUP_LINE_CONTENTS);
	}

	/**
	 * Retrieves a String representing the value at a specific line of this variable.
	 *
	 * @param line
	 *            the line to check
	 * @return the value
	 */
	public String getValueForLine(int line) {
		if (line == 0) {
			return matchResults.get(line).group(GROUP_VAR_CONTENTS);
		}
		return matchResults.get(line).group(GROUP_LINE_CONTENTS);
	}

	/**
	 * Returns the ideal offset in the containing document at which a line escape can be inserted.
	 *
	 * @param line
	 *            the line to check
	 * @return the ideal location for a line escape
	 */
	public int getLineEscapeReplacementOffset(int line) {
		if (line == 0) {
			return matchResults.get(line).end(GROUP_VAR_CONTENTS);
		}
		return matchResults.get(line).end(GROUP_LINE_CONTENTS);
	}

	/**
	 * Returns the ideal String for the line escape character. This is mostly for spacing requirements and should be used in tandem
	 * with the method <code>getLineEscapeReplacementOffset</code>.
	 *
	 * @param line
	 *            the line to check
	 * @return the ideal String for the line escape character
	 */
	public String getLineEscapeReplacementString(int line) {
		int commentOffset = -1;
		int contentsOffset = -1;
		if (line == 0) {
			commentOffset = matchResults.get(line).start(GROUP_VAR_COMMENT);
			contentsOffset = matchResults.get(line).end(GROUP_VAR_CONTENTS);
		} else {
			commentOffset = matchResults.get(line).start(GROUP_LINE_COMMENT);
			contentsOffset = matchResults.get(line).end(GROUP_LINE_CONTENTS);
		}

		if (commentOffset > 0) {
			if (commentOffset - contentsOffset == 0) {
				return " \\ "; //$NON-NLS-1$
			}
		}
		return " \\"; //$NON-NLS-1$
	}

	/**
	 * Retrieves the offset of the line escape for a given line relative to its containing document. This method takes into account
	 * spacing and should be used to determine how to best remove a line escape character from a given line.
	 *
	 * @param line
	 *            the line to check
	 * @return the offset of the line escape character
	 */
	public int getLineEscapeOffset(int line) {
		if (line == 0) {
			return matchResults.get(line).end(GROUP_VAR_CONTENTS);
		}
		return matchResults.get(line).end(GROUP_LINE_CONTENTS);
	}

	/**
	 * Get the end position relative to the start of the containing document that contains the line escape character of the given
	 * line. This is used for removal of the line escape character and takes into account the spacing of the line.
	 *
	 * @param line
	 *            the line to check
	 * @return the end position of the line escape character
	 */
	public int getLineEscapeEnd(int line) {
		int end = -1;
		if (line == 0) {
			end = matchResults.get(line).end(GROUP_VAR_LINE_ESCAPE);
		} else {
			end = matchResults.get(line).end(GROUP_LINE_LINE_ESCAPE);
		}

		if (end > 0) {
			return end;
		}

		if (line == 0) {
			return matchResults.get(line).end(GROUP_VAR_TERMINATOR);
		}
		return matchResults.get(line).end(GROUP_LINE_TERMINATOR);
	}

	/**
	 * Gets the end position of this variable relative to the containing document.
	 *
	 * @return the end position of this variable
	 */
	public int getEndOffset() {
		return matchResults.get(matchResults.size() - 1).end();
	}

	/**
	 * Retrieves the full text of this variable as it appears in the document.
	 *
	 * @return the full String of this variable as it appears in the document
	 */
	public String getText() {
		return text;
	}

	/**
	 * Gets the total number of lines in this variable declaration.
	 *
	 * @return the total number of lines
	 */
	public int getNumberOfLines() {
		return matchResults.size();
	}

	/**
	 * Retrieves a String representing the given line as it appears in the document.
	 *
	 * @param line
	 *            the line to retrieve
	 * @return a String representing the line
	 */
	public String getLine(int line) {
		return matchResults.get(line).group();
	}

	/**
	 * Retrieves the offset of the given line relative to its containing document.
	 *
	 * @param line
	 *            the line to retrieve
	 * @return the line's offset in the document
	 */
	public int getLineOffset(int line) {
		return matchResults.get(line).start();
	}

	/**
	 * Returns the line at which the specified value appears. This method checks the whole line for the value and will not match a
	 * subset of that String. This is equivalent to calling <code>getValueIndex(value,false)</code>.
	 *
	 * @param value
	 *            the value to search for
	 * @return the line that the value appears on or -1 if it doesn't exist
	 */
	public int getValueIndex(String value) {
		return getValueIndex(value, false);
	}

	/**
	 * Returns the line at which the specified value appears. This method checks the whole line for the value and will not match a
	 * subset of that String. If <code>ignoreCase</code> is <code>false</code>, this method searches for the value using
	 * <code>equalsIgnoreCase</code> instead of <code>equals</code>.
	 *
	 * @param value
	 *            the value to search for
	 * @param ignoreCase
	 *            whether or not the value is case-sensitive
	 * @return the line that the value appears on or -1 if it doesn't exist
	 */
	public int getValueIndex(String value, boolean ignoreCase) {
		int line = 0;
		for (String val : getValues()) {
			if (ignoreCase) {
				if (val.equalsIgnoreCase(value)) {
					return line;
				}
			} else {
				if (val.equals(value)) {
					return line;
				}
			}
			line++;
		}
		return -1;
	}

	/**
	 * Gets the offset of the end of a given line relative to its containing document.
	 *
	 * @param line
	 *            the line to check
	 * @return the offset of the end of the line
	 */
	public int getLineEnd(int line) {
		return matchResults.get(line).end();
	}
}

Back to the top