blob: 2d7d1d1cf6da320ae9dc4a034c121a9bcf3dc7c3 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package git4idea.util;
import org.jetbrains.annotations.NotNull;
/**
* A parser of strings that is oriented to scanning typical git outputs
*/
public class StringScanner {
/**
* The text to scan
*/
private final String myText;
/**
* The text position
*/
private int myPosition;
/**
* The constructor from text
*
* @param text the text to scan
*/
public StringScanner(@NotNull final String text) {
myText = text;
myPosition = 0;
}
/**
* @return true if there are more data available
*/
public boolean hasMoreData() {
return myPosition < myText.length();
}
/**
* @return true if the current position is end of line or end of the file
*/
public boolean isEol() {
if (!hasMoreData()) {
return true;
}
final char ch = myText.charAt(myPosition);
return ch == '\n' || ch == '\r';
}
/**
* Continue to the next line, the rest of the current line is skipped
*/
public void nextLine() {
while (!isEol()) {
myPosition++;
}
if (hasMoreData()) {
final char ch = myText.charAt(myPosition++);
if (hasMoreData()) {
final char ch2 = myText.charAt(myPosition);
if (ch == '\n' && ch2 == '\r' || ch == '\r' && ch2 == '\n') {
myPosition++;
}
}
}
}
/**
* Gets next token that is ended by space or new line. Consumes space but not a new line.
* Start position is the current. So if the string starts with space a empty token is returned.
*
* @return a token
*/
public String spaceToken() {
return boundedToken(' ');
}
/**
* Gets next token that is ended by tab or new line. Consumes tab but not a new line.
* Start position is the current. So if the string starts with space a empty token is returned.
*
* @return a token
*/
public String tabToken() {
return boundedToken('\t');
}
/**
* Gets next token that is ended by {@code boundaryChar} or new line. Consumes {@code boundaryChar} but not a new line.
* Start position is the current. So if the string starts with {@code boundaryChar} a empty token is returned.
*
* @param boundaryChar a boundary character
* @return a token
*/
public String boundedToken(final char boundaryChar) {
return boundedToken(boundaryChar, false);
}
/**
* Gets next token that is ended by {@code boundaryChar} or new line. Consumes {@code boundaryChar} but not a new line (if it is not ignored).
* Start position is the current. So if the string starts with {@code boundaryChar} a empty token is returned.
*
* @param boundaryChar a boundary character
* @param ignoreEol if true, the end of line is considered as normal character and consumed
* @return a token
*/
public String boundedToken(char boundaryChar, boolean ignoreEol) {
int start = myPosition;
for (; myPosition < myText.length(); myPosition++) {
final char ch = myText.charAt(myPosition);
if (ch == boundaryChar) {
final String rc = myText.substring(start, myPosition);
myPosition++;
return rc;
}
if (!ignoreEol && isEol()) {
return myText.substring(start, myPosition);
}
}
throw new IllegalStateException("Unexpected text end at " + myPosition);
}
/**
* Check if the next character is the specified one
*
* @param c the expected character
* @return true if the character matches expected.
*/
public boolean startsWith(final char c) {
return hasMoreData() && myText.charAt(myPosition) == c;
}
/**
* Check if the rest of the string starts with the specified text
*
* @param text the text to check
* @return true if the text contains the string.
*/
public boolean startsWith(String text) {
return myText.startsWith(text, myPosition);
}
/**
* Get text from the current position until the end of the line. After return, the current position is the start of the next line.
*
* @return the text until end of the line
*/
public String line() {
return line(false);
}
/**
* Get text from the current position until the end of the line. After return, the current position is the start of the next line.
*
* @param includeNewLine include new line characters into included string
* @return the text until end of the line
*/
public String line(boolean includeNewLine) {
int start = myPosition;
while (!isEol()) {
myPosition++;
}
int end;
if (includeNewLine) {
nextLine();
end = myPosition;
}
else {
end = myPosition;
nextLine();
}
return myText.substring(start, end);
}
/**
* Skip specified amount of characters
*
* @param n characters to skip
*/
public void skipChars(final int n) {
if (n < 0) {
throw new IllegalArgumentException("Amount of chars to skip must be non neagitve: " + n);
}
if (myPosition + n >= myText.length()) {
throw new IllegalArgumentException("Skipping beyond end of the text (" + myPosition + " + " + n + " >= " + myText.length() + ")");
}
myPosition += n;
}
/**
* Try string and consume it if it matches
*
* @param c a character to try
* @return true if the string was consumed.
*/
public boolean tryConsume(final char c) {
if (startsWith(c)) {
skipChars(1);
return true;
}
return false;
}
/**
* Try consuming a sequence of characters
*
* @param chars a sequence of characters
* @return true if consumed successfully
*/
public boolean tryConsume(String chars) {
if (startsWith(chars)) {
skipChars(chars.length());
return true;
}
return false;
}
/**
* @return the next character to be consumed
*/
public char peek() {
if (!hasMoreData()) {
throw new IllegalStateException("There is no next character");
}
return myText.charAt(myPosition);
}
public String getAllText() {
return myText;
}
}