blob: 4a71a00989a33037cb7e7579657045d11f31b864 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2018 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat - initial API and implementation
* Alphonse Van Assche
*******************************************************************************/
package org.eclipse.linuxtools.rpm.ui.editor.parser;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.BUILD_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.CHANGELOG_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.CLEAN_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.DESCRIPTION_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.FILES_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.INSTALL_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.PACKAGE_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.POSTTRANS_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.POSTUN_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.POST_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.PREP_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.PRETRANS_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.PREUN_SECTION;
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.PRE_SECTION;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.linuxtools.internal.rpm.ui.editor.Activator;
import org.eclipse.linuxtools.internal.rpm.ui.editor.ISpecfileSpecialSymbols;
import org.eclipse.linuxtools.internal.rpm.ui.editor.RpmTags;
import org.eclipse.linuxtools.internal.rpm.ui.editor.SpecfileLog;
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.Messages;
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.SpecfileParseException;
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.SpecfilePatchMacro;
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.SpecfileSource;
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.SpecfileSource.SourceType;
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.SpecfileTag;
import org.eclipse.linuxtools.internal.rpm.ui.editor.preferences.PreferenceConstants;
import org.eclipse.linuxtools.rpm.ui.editor.markers.SpecfileErrorHandler;
import org.eclipse.linuxtools.rpm.ui.editor.markers.SpecfileTaskHandler;
public class SpecfileParser {
private static final String DEFINE_SEPARATOR = ":"; //$NON-NLS-1$
private static final String SPACE_REGEX = "\\s+"; //$NON-NLS-1$
/**
* These are SRPM-wide sections, and they also cannot have any flags like -n or
* -f. Hence they are called simple. This is probably a misleading name and it
* should be renamed to reflect that they are SRPM-wide sections.
*/
public static final String[] simpleSections = { PREP_SECTION, BUILD_SECTION, INSTALL_SECTION, CLEAN_SECTION,
CHANGELOG_SECTION };
/**
* These are sections that apply to a particular sub-package (i.e. binary RPM),
* including the main package. These can also have flags like -f or -n appended
* to them, hence they are called complex. This should probably be renamed to
* reflect that they are in fact per-RPM sections.
*/
private static String[] complexSections = { PRETRANS_SECTION, PRE_SECTION, PREUN_SECTION, POST_SECTION,
POSTUN_SECTION, POSTTRANS_SECTION, FILES_SECTION, PACKAGE_SECTION, DESCRIPTION_SECTION };
private static String[] simpleDefinitions = { RpmTags.EPOCH, RpmTags.NAME, RpmTags.VERSION, RpmTags.RELEASE,
RpmTags.URL, RpmTags.BUILD_ARCH };
private static String[] directValuesDefinitions = { RpmTags.LICENSE, RpmTags.BUILD_ROOT };
// Note that the ordering here should match that in
// SpecfileSource#SOURCETYPE
private static String[] complexDefinitions = { "Source", "Patch" }; //$NON-NLS-1$ //$NON-NLS-2$
private static String[] packageLevelDefinitions = { RpmTags.SUMMARY, RpmTags.GROUP, RpmTags.OBSOLETES,
RpmTags.PROVIDES, RpmTags.REQUIRES, RpmTags.REQUIRES_PRE, RpmTags.REQUIRES_POST, RpmTags.REQUIRES_POSTUN };
private SpecfileErrorHandler errorHandler;
private SpecfileTaskHandler taskHandler;
private SpecfileSection lastSection;
private SpecfilePackage activePackage;
public Specfile parse(IDocument specfileDocument) {
// remove all existing markers, if a SpecfileErrorHandler is
// instantiated.
if (errorHandler != null) {
errorHandler.removeExistingMarkers();
}
if (taskHandler != null) {
taskHandler.removeExistingMarkers();
}
LineNumberReader reader = new LineNumberReader(new StringReader(specfileDocument.get()));
String line = ""; //$NON-NLS-1$
int lineStartPosition = 0;
Specfile specfile = new Specfile();
specfile.setDocument(specfileDocument);
try {
while ((line = reader.readLine()) != null) {
if (taskHandler != null) {
generateTaskMarker(reader.getLineNumber() - 1, line);
}
// IDocument.getLine(#) is 0-indexed whereas
// reader.getLineNumber appears to be 1-indexed
SpecfileElement element = parseLine(line, specfile, reader.getLineNumber() - 1);
if (element != null) {
element.setLineNumber(reader.getLineNumber() - 1);
element.setLineStartPosition(lineStartPosition);
element.setLineEndPosition(lineStartPosition + line.length());
if (element.getClass() == SpecfileTag.class) {
SpecfileTag tag = (SpecfileTag) element;
specfile.addDefine(tag);
} else if ((element.getClass() == SpecfilePatchMacro.class)) {
SpecfilePatchMacro thisPatchMacro = (SpecfilePatchMacro) element;
if (thisPatchMacro != null) {
thisPatchMacro.setSpecfile(specfile);
}
SpecfileSource thisPatch = specfile.getPatch(thisPatchMacro.getPatchNumber());
if (thisPatch != null) {
thisPatch.addLineUsed(reader.getLineNumber() - 1);
thisPatch.setSpecfile(specfile);
}
} else if ((element.getClass() == SpecfileDefine.class)) {
specfile.addDefine((SpecfileDefine) element);
} else if ((element.getClass() == SpecfileSource.class)) {
SpecfileSource source = (SpecfileSource) element;
source.setLineNumber(reader.getLineNumber() - 1);
if (source.getSourceType() == SpecfileSource.SourceType.SOURCE) {
specfile.addSource(source);
} else {
specfile.addPatch(source);
}
}
}
// sets the last SpecfileSection's end line to that of the end
// of
// the end of the specfileDocument.
// SpecfileParser#parseMacro will handle correcting the end line
// if the last SpecfileSection was not truly the last 1
// This is for the purpose of making DocumentRangeNode work
if (lastSection != null) {
lastSection.setSectionEndLine(specfileDocument.getNumberOfLines() - 1);
}
// The +1 is for the line delimiter. FIXME: will we end up off
// by one on the last line?
lineStartPosition += line.length() + 1;
}
} catch (IOException e) {
// FIXME
SpecfileLog.logError(e);
}
return specfile;
}
/**
* Parse a File into a specfile
*
* @param file The File to be parsed
* @return A Specfile object
*/
public Specfile parse(IFile file) {
SpecfileParser parser = new SpecfileParser();
StringBuilder sb = new StringBuilder();
String line = null;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(file.getContents()));
while ((line = reader.readLine()) != null) {
sb.append(line).append('\n');
}
} catch (IOException e) {
SpecfileLog.logError(Messages.getString("SpecfileParseFile.1"), e); //$NON-NLS-1$
} catch (CoreException e) {
SpecfileLog.logError(Messages.getString("SpecfileParseFile.2"), e); //$NON-NLS-1$
}
return parser.parse(sb.toString());
}
private void generateTaskMarker(int lineNumber, String line) {
String[] taskTags = Activator.getDefault().getPreferenceStore().getString(PreferenceConstants.P_TASK_TAGS)
.split(";"); //$NON-NLS-1$
int commentCharIndex = line.indexOf(ISpecfileSpecialSymbols.COMMENT_START);
if (commentCharIndex > -1) {
for (String item : taskTags) {
int taskIndex = line.indexOf(item);
if (taskIndex > commentCharIndex) {
taskHandler.handleTask(lineNumber, line, item);
}
}
}
}
public Specfile parse(String specfileContent) {
return parse(new Document(specfileContent));
}
public SpecfileElement parseLine(String lineText, Specfile specfile, int lineNumber) {
if (lineText.startsWith("%")) {//$NON-NLS-1$
return parseMacro(lineText, specfile, lineNumber);
}
for (String simpleDefinition : simpleDefinitions) {
if (lineText.startsWith(simpleDefinition + DEFINE_SEPARATOR)) {
return parseSimpleDefinition(lineText, specfile, lineNumber, false);
}
}
for (String directValuesDefinition : directValuesDefinitions) {
if (lineText.startsWith(directValuesDefinition + DEFINE_SEPARATOR)) {
return parseDirectDefinition(lineText, specfile, lineNumber);
}
}
for (String directValuesDefinition : packageLevelDefinitions) {
if (lineText.startsWith(directValuesDefinition + DEFINE_SEPARATOR)) {
SpecfileElement definition = parseDirectDefinition(lineText, specfile, lineNumber);
if (directValuesDefinition.equals(RpmTags.REQUIRES)) {
if (activePackage != null) {
activePackage.addRequire((SpecfileTag) definition);
} else {
specfile.addRequire((SpecfileTag) definition);
}
}
return definition;
}
}
if (lineText.startsWith(complexDefinitions[0]) && lineText.contains(DEFINE_SEPARATOR)) {
return parseComplexDefinition(lineText, lineNumber, SourceType.SOURCE);
} else if (lineText.startsWith(complexDefinitions[1]) && lineText.contains(DEFINE_SEPARATOR)) {
return parseComplexDefinition(lineText, lineNumber, SourceType.PATCH);
} else if (lineText.startsWith("BuildRequires")) { //$NON-NLS-1$
return parseBuildRequire(lineText, lineNumber, specfile);
}
return null;
}
private SpecfileElement parseBuildRequire(String lineText, int lineNumber, Specfile specfile) {
String value = lineText.substring(lineText.indexOf(':') + 1).trim();
SpecfileDefine buildRequire = new SpecfileDefine("BuildRequires", value, specfile, null); //$NON-NLS-1$
buildRequire.setLineNumber(lineNumber);
specfile.addBuildRequire(buildRequire);
return buildRequire;
}
private SpecfileSection parseSection(String lineText, Specfile specfile, int lineNumber) {
List<String> tokens = Arrays.asList(lineText.split(SPACE_REGEX));
SpecfileSection toReturn = null;
boolean isSimpleSection = false;
for (Iterator<String> iter = tokens.iterator(); iter.hasNext();) {
String token = iter.next();
// Sections
// Simple Section Headers
for (String simpleSection : simpleSections) {
if (token.equals(simpleSection)) {
toReturn = new SpecfileSection(token.substring(1), specfile);
specfile.addSection(toReturn);
isSimpleSection = true;
}
}
// Complex Section Headers
for (String complexSection : complexSections) {
if (token.equals(complexSection)) {
String name = token.substring(1);
if (!name.equals("package")) { //$NON-NLS-1$
toReturn = new SpecfileSection(name, specfile);
specfile.addComplexSection(toReturn);
}
while (iter.hasNext()) {
String nextToken = iter.next();
if (nextToken.equals("-n")) { //$NON-NLS-1$
if (!iter.hasNext()) {
errorHandler.handleError(new SpecfileParseException(
Messages.getString("SpecfileParser.1") //$NON-NLS-1$
+ name + Messages.getString("SpecfileParser.2"), //$NON-NLS-1$
lineNumber, 0, lineText.length(), IMarker.SEVERITY_ERROR));
continue;
}
nextToken = iter.next();
if (nextToken.startsWith("-")) { //$NON-NLS-1$
errorHandler.handleError(new SpecfileParseException(
Messages.getString("SpecfileParser.3") //$NON-NLS-1$
+ nextToken + Messages.getString("SpecfileParser.4"), //$NON-NLS-1$
lineNumber, 0, lineText.length(), IMarker.SEVERITY_ERROR));
}
} else if (nextToken.equals("-p")) { //$NON-NLS-1$
// FIXME: rest of line is the actual section
break;
} else if (nextToken.equals("-f")) { //$NON-NLS-1$
break;
}
// this is a package
if (toReturn == null) {
SpecfilePackage tmpPackage = specfile.getPackage(nextToken);
if (tmpPackage == null) {
tmpPackage = new SpecfilePackage(nextToken, specfile);
specfile.addPackage(tmpPackage);
}
activePackage = tmpPackage;
return tmpPackage;
}
// this is another section
SpecfilePackage enclosingPackage = specfile.getPackage(nextToken);
if (enclosingPackage == null) {
enclosingPackage = new SpecfilePackage(nextToken, specfile);
specfile.addPackage(enclosingPackage);
}
toReturn.setPackage(enclosingPackage);
enclosingPackage.addSection(toReturn);
}
}
}
}
// if this package is part of the top level package, add it to
// it
if (toReturn != null && toReturn.getPackage() == null) {
SpecfilePackage topPackage = specfile.getPackage(specfile.getName());
if (topPackage == null) {
topPackage = new SpecfilePackage(specfile.getName(), specfile);
specfile.addPackage(topPackage);
}
if (!isSimpleSection) {
topPackage.addSection(toReturn);
}
}
if (lastSection != null) {
lastSection.setSectionEndLine(lineNumber);
}
return toReturn;
}
private SpecfileElement parseMacro(String lineText, Specfile specfile, int lineNumber) {
// FIXME: handle other macros
if (lineText.startsWith("%define") || lineText.startsWith("%global")) { //$NON-NLS-1$ //$NON-NLS-2$
return parseDefine(lineText, specfile, lineNumber);
} else if (lineText.startsWith("%patch")) { //$NON-NLS-1$
return parsePatch(lineText, lineNumber);
}
String[] sections = new String[simpleSections.length + complexSections.length];
System.arraycopy(simpleSections, 0, sections, 0, simpleSections.length);
System.arraycopy(complexSections, 0, sections, simpleSections.length, complexSections.length);
for (String section : sections) {
if (lineText.startsWith(section)) {
lastSection = parseSection(lineText, specfile, lineNumber);
if (lastSection != null) {
lastSection.setSectionEndLine(lineNumber + 1);
}
return lastSection;
}
}
// FIXME: add handling of lines containing %{SOURCENNN}
return null;
}
private SpecfileElement parsePatch(String lineText, int lineNumber) {
SpecfilePatchMacro toReturn = null;
List<String> tokens = Arrays.asList(lineText.split(SPACE_REGEX));
for (String token : tokens) {
// %patchN+
try {
if (token.startsWith("%patch")) { //$NON-NLS-1$
int patchNumber = 0;
if (token.length() > 6) {
patchNumber = Integer.parseInt(token.substring(6));
}
toReturn = new SpecfilePatchMacro(patchNumber);
}
} catch (NumberFormatException e) {
errorHandler.handleError(new SpecfileParseException(Messages.getString("SpecfileParser.5"), //$NON-NLS-1$
lineNumber, 0, lineText.length(), IMarker.SEVERITY_ERROR));
return null;
}
}
return toReturn;
}
private SpecfileDefine parseDefine(String lineText, Specfile specfile, int lineNumber) {
List<String> tokens = Arrays.asList(lineText.split(SPACE_REGEX));
SpecfileDefine toReturn = null;
for (Iterator<String> iter = tokens.iterator(); iter.hasNext();) {
// Eat the actual "%define" or "%global" token
iter.next();
while (iter.hasNext()) {
String defineName = iter.next();
// FIXME: is this true? investigate in rpmbuild source
// Definitions must being with a letter
if (!Character.isLetter(defineName.charAt(0)) && (defineName.charAt(0) != '_')) {
errorHandler.handleError(new SpecfileParseException(Messages.getString("SpecfileParser.6"), //$NON-NLS-1$
lineNumber, 0, lineText.length(), IMarker.SEVERITY_ERROR));
return null;
} else {
if (iter.hasNext()) {
String defineStringValue = iter.next();
// Defines that are more than one token
if (iter.hasNext()) {
defineStringValue = lineText.substring(lineText.indexOf(defineStringValue));
// Eat up the rest of the tokens
while (iter.hasNext()) {
iter.next();
}
}
int defineIntValue = -1;
try {
defineIntValue = Integer.parseInt(defineStringValue);
} catch (NumberFormatException e) {
toReturn = new SpecfileDefine(defineName, defineStringValue, specfile, null);
}
if (toReturn == null) {
toReturn = new SpecfileDefine(defineName, defineIntValue, specfile, null);
}
} else {
errorHandler.handleError(
new SpecfileParseException(defineName + Messages.getString("SpecfileParser.14"), //$NON-NLS-1$
lineNumber, 0, lineText.length(), IMarker.SEVERITY_ERROR));
}
}
}
}
return toReturn;
}
private SpecfileElement parseComplexDefinition(String lineText, int lineNumber, SourceType sourceType) {
SpecfileSource toReturn = null;
List<String> tokens = Arrays.asList(lineText.split(SPACE_REGEX));
int number = -1;
boolean firstToken = true;
for (Iterator<String> iter = tokens.iterator(); iter.hasNext();) {
String token = iter.next();
if (token != null && token.length() > 0) {
if (firstToken) {
if (token.endsWith(DEFINE_SEPARATOR)) {
token = token.substring(0, token.length() - 1);
} else {
// FIXME: come up with a better error message here
// FIXME: what about descriptions that begin a line with
// the word "Source" or "Patch"?
errorHandler.handleError(new SpecfileParseException(Messages.getString("SpecfileParser.8"), //$NON-NLS-1$
lineNumber, 0, lineText.length(), IMarker.SEVERITY_WARNING));
return null;
}
if (sourceType == SourceType.PATCH) {
if (token.length() > 5) {
number = Integer.parseInt(token.substring(5));
if (!("patch" + number).equalsIgnoreCase(token)) { //$NON-NLS-1$
errorHandler
.handleError(new SpecfileParseException(Messages.getString("SpecfileParser.10"), //$NON-NLS-1$
lineNumber, 0, lineText.length(), IMarker.SEVERITY_ERROR));
return null;
}
} else {
number = 0;
}
} else {
if (token.length() > 6) {
number = Integer.parseInt(token.substring(6));
if (!("source" + number).equalsIgnoreCase(token)) { //$NON-NLS-1$
errorHandler
.handleError(new SpecfileParseException(Messages.getString("SpecfileParser.11"), //$NON-NLS-1$
lineNumber, 0, lineText.length(), IMarker.SEVERITY_ERROR));
return null;
}
} else {
number = 0;
}
}
toReturn = new SpecfileSource(number, ""); //$NON-NLS-1$
toReturn.setSourceType(sourceType);
firstToken = false;
} else {
// toReturn should never be null but check just in case
if (toReturn != null) {
toReturn.setFileName(token);
}
if (iter.hasNext()) {
errorHandler.handleError(new SpecfileParseException(Messages.getString("SpecfileParser.12"), //$NON-NLS-1$
lineNumber, 0, lineText.length(), IMarker.SEVERITY_ERROR));
}
}
}
}
return toReturn;
}
private SpecfileElement parseSimpleDefinition(String lineText, Specfile specfile, int lineNumber,
boolean warnMultipleValues) {
List<String> tokens = Arrays.asList(lineText.split(SPACE_REGEX));
SpecfileTag toReturn = null;
for (Iterator<String> iter = tokens.iterator(); iter.hasNext();) {
String token = iter.next();
if (token.length() <= 0) {
break;
}
if (iter.hasNext()) {
String possValue = iter.next();
if (possValue.startsWith("%") && iter.hasNext()) { //$NON-NLS-1$
possValue += ' ' + iter.next();
}
toReturn = new SpecfileTag(token.substring(0, token.length() - 1).toLowerCase(), possValue, specfile,
null);
if (iter.hasNext() && !warnMultipleValues) {
errorHandler.handleError(new SpecfileParseException(
token.substring(0, token.length() - 1) + Messages.getString("SpecfileParser.13"), //$NON-NLS-1$
lineNumber, 0, lineText.length(), IMarker.SEVERITY_ERROR));
return null;
}
} else {
errorHandler.handleError(new SpecfileParseException(
token.substring(0, token.length() - 1) + Messages.getString("SpecfileParser.14"), lineNumber, //$NON-NLS-1$
0, lineText.length(), IMarker.SEVERITY_ERROR));
toReturn = null;
}
}
if ((toReturn != null)) {
String returnStringValue = toReturn.getStringValue();
if (returnStringValue != null) {
try {
int intValue = Integer.parseInt(returnStringValue);
toReturn.setValue(intValue);
} catch (NumberFormatException e) {
if (toReturn.getName().equalsIgnoreCase(RpmTags.EPOCH)) {
errorHandler.handleError(
new SpecfileParseException(Messages.getString("SpecfileParser.16"), lineNumber, //$NON-NLS-1$
0, lineText.length(), IMarker.SEVERITY_ERROR));
toReturn = null;
}
}
}
}
return toReturn;
}
private SpecfileElement parseDirectDefinition(String lineText, Specfile specfile, int lineNumber) {
String[] parts = lineText.split(DEFINE_SEPARATOR, 2);
SpecfileTag directDefinition;
if (parts.length == 2) {
directDefinition = new SpecfileTag(parts[0], parts[1].trim(), specfile, activePackage);
directDefinition.setLineNumber(lineNumber);
} else {
errorHandler.handleError(
new SpecfileParseException(parts[0] + Messages.getString("SpecfileParser.14"), lineNumber, //$NON-NLS-1$
0, lineText.length(), IMarker.SEVERITY_ERROR));
directDefinition = null;
}
return directDefinition;
}
public void setErrorHandler(SpecfileErrorHandler specfileErrorHandler) {
errorHandler = specfileErrorHandler;
}
public void setTaskHandler(SpecfileTaskHandler specfileTaskHandler) {
taskHandler = specfileTaskHandler;
}
}