blob: 3566f5c56254fcbb023dbcfbb22994c47e86a610 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 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:
* Neil Guzman - python implementation (B#350065)
*******************************************************************************/
package org.eclipse.linuxtools.internal.rpmstubby.parser;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.linuxtools.internal.rpmstubby.StubbyLog;
/**
* Class to parse a Python setup.py to grab specfile properties
*
*/
public class PythonEggParser {
private Map<String, String> variables;
private Map<String, String> setupOptions;
private IFile file;
/**
* Initialize and then parse the file
*
* @param file The Python setup.py file
* @throws CoreException Throws CoreException
* @throws IOException Throws IOException
*/
public PythonEggParser(IFile file) throws IOException, CoreException {
setupOptions = new HashMap<>();
variables = new HashMap<>();
// end if file is empty or cannot get its contents
if (file.getContents().available() <= 0) {
return;
}
this.file = file;
parse();
}
/**
* Parse the contents of the Python setup.py file and grab
* variables and meta-data from the setup(...) function
*
*/
public void parse() {
String line = "";
String setupLine = "";
List<String> vars = new ArrayList<>();
int offset = 0;
try (RandomAccessFile raf = new RandomAccessFile(file.getRawLocation().makeAbsolute().toFile(), "r")){
// end if cannot find setup(
long bytesToSkip = findStartSetup(raf);
if (bytesToSkip == -1) {
return;
}
// end if the end of setup cannot be found
long stop = findEndSetup(raf, bytesToSkip);
if (stop == -1) {
return;
}
raf.seek(0);
while ((line = raf.readLine()) != null) {
if (!line.trim().startsWith("#")) {
if (isLineSimpleDefinition(line)) {
vars.add(line);
} else if (!vars.isEmpty() && vars.get(vars.size()-1).trim().endsWith("\\") && isLineContinuation(line)){
offset = vars.get(vars.size()-1).lastIndexOf('\\');
vars.set(vars.size() -1, vars.get(vars.size() - 1).substring(0, offset));
vars.set(vars.size() - 1, vars.get(vars.size() - 1)
.concat(line.trim()));
}
}
}
raf.seek(bytesToSkip);
while ((line = raf.readLine()) != null && raf.getFilePointer() <= stop) {
line = line.trim();
if (!line.startsWith("#")) {
if (setupLine.equals("")) {
setupLine = line.trim();
} else {
setupLine = setupLine.concat(line.trim());
}
}
}
List<String> list = prepareSetupOptions(setupLine);
for (String str : vars) {
variables.putAll(parseLine(str));
}
for (String str : list) {
setupOptions.putAll(parseLine(str));
}
resolveVariables(variables, setupOptions);
} catch (IOException e) {
StubbyLog.logError(e);
}
}
/**
* Check to see if the string passed in is a function
*
* @param str The string to check
* @return True if the string matches the function regex
*/
public boolean checkFunction(String str) {
boolean rc = false;
Pattern pattern = Pattern.compile("\\s*\\w*\\s*?\\(.*\\)\\s*");
Matcher variableMatcher = pattern.matcher(str);
if (variableMatcher.matches()) {
rc = true;
}
return rc;
}
/**
* Get the value of the variable
*
* @param key The variable to get the value of
* @return The value of the variable
*/
public String getValue(String key) {
String rc = "";
Pattern pattern = Pattern.compile("\\s*\\((.+)\\)\\s*");
Matcher variableMatcher = null;
if (setupOptions.containsKey(key)) {
rc = setupOptions.get(key).replaceAll("('|\")", "").trim();
variableMatcher = pattern.matcher(rc);
if (variableMatcher.matches()) {
rc = variableMatcher.group(1);
}
}
return rc;
}
/**
* Get the list of strings for a key.
* Use with classifiers, platforms, install_requires, etc.
*
* @param key The variable to get the value of
* @return The value of the variable
*/
public List<String> getValueList(String key) {
List<String> rc = new ArrayList<>();
Pattern pattern = Pattern.compile("^\\[(.*)\\]");
if (setupOptions.containsKey(key)) {
Matcher variableMatcher = pattern.matcher(setupOptions.get(key).trim());
if (variableMatcher.find()) {
String[] temp = variableMatcher.group(1).replaceAll("('|\")", "").split(",");
for (String str : temp) {
if (!str.isEmpty() && !str.trim().startsWith("#")) {
rc.add(str.trim());
}
}
}
}
return rc;
}
/**
* Prepare the setup options by returning each comma delimited option
*
* @param setupLine The single string containing all the setup options
* @return A list of setup options
*/
private static List<String> prepareSetupOptions(String setupLine) {
List<String> rc = new ArrayList<>();
// match the setup(...) pattern
Pattern pattern = Pattern.compile("\\bsetup\\b(\\s+)?\\((.*)\\)");
Matcher variableMatcher = pattern.matcher(setupLine);
if (variableMatcher.find()) {
setupLine = variableMatcher.group(2);
}
String[] tempList = setupLine.split("(?=,)");
for (String str : tempList) {
if (isOptionLineKeyValuePair(str) && !str.trim().startsWith("#")) {
if (str.startsWith(",")) {
str = str.substring(1, str.length()).trim();
}
rc.add(str);
} else if (!str.trim().startsWith("#") && !rc.isEmpty()) {
rc.set(rc.size() - 1, rc.get(rc.size() - 1).concat(str.trim()));
}
}
return rc;
}
/**
* Resolves the setup option variables if they are referencing a
* define from outside the setup() function.
*
* @param variables The variables outside the setup() function
* @param options The options to be resolved within the setup() function
*/
private static void resolveVariables(Map<String, String> variables, Map<String, String> options) {
for (Entry<String, String> entry : options.entrySet()) {
if (variables.containsKey(entry.getValue())) {
options.put(entry.getKey(), variables.get(entry.getValue()));
}
}
}
/**
* Check to see if the line in setup option is a new key->value pair
*
* @param line Line to check
* @return True if the line contains a key->value
*/
private static boolean isOptionLineKeyValuePair(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile("(\\w+)(\\s+)?=[^=].*");
Matcher variableMatcher = pattern.matcher(line.toLowerCase());
if (variableMatcher.find()) {
rc = true;
}
return rc;
}
/**
* Check to see if the line is a simple variable declaration (var=value)
*
* @param line Line to check
* @return True if it is a simple variable declaration
*/
private static boolean isLineSimpleDefinition(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile("^(\\w+)(\\s+)?=(\\s+)?");
Matcher variableMatcher = pattern.matcher(line.toLowerCase());
if (variableMatcher.find()) {
rc = true;
}
return rc;
}
/**
* Check to see if the line is a continuation from the previous.
* It is a continuation from the previous if it starts
* with a ' or "
*
* @param line Line to check
* @return True if the line is a continuation
*/
private static boolean isLineContinuation(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile(".*[\'\"](/s+)?$");
Matcher variableMatcher = pattern.matcher(line.toLowerCase());
if (variableMatcher.find()) {
rc = true;
}
return rc;
}
/**
* Parse the line and split it into a key->value pair
*
* @param line The line to be parsed
* @return The map containing the key->value pair
*/
private static Map<String, String> parseLine(String line) {
Map<String, String> rc = new HashMap<>();
Pattern pattern = Pattern.compile("(\\s+)?(\\w+)(\\s+)?=(\\s+)?(.*)");
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.find()) {
String value = variableMatcher.group(5);
if (value.charAt(value.length()-1) == ',') {
value = value.substring(0, value.length()-1);
}
rc.put(variableMatcher.group(2), value);
}
return rc;
}
/**
* Find the offset of when setup(...) starts
*
* @param reader The file reader
* @return The position of the start of setup(...
* @throws IOException
*/
private static long findStartSetup(RandomAccessFile reader) throws IOException {
long rc = -1;
long previous = 0;
Pattern pattern = Pattern.compile("^\\bsetup\\b(\\s+)?(\\()?");
Matcher variableMatcher = null;
String line = "";
reader.seek(0);
while ((line = reader.readLine()) != null && rc == -1) {
variableMatcher = pattern.matcher(line.toLowerCase());
if (variableMatcher.find()) {
// get the previous line's file pointer location
rc = previous;
}
previous = reader.getFilePointer();
}
return rc;
}
/**
* Find the offset of when setup(...) ends based on the
* position AFTER the closing bracket of setup()
*
* @param reader The file reader
* @param startPosition The position of the start of setup(...
* @return The position of the end of setup ...)
* @throws IOException
*/
private static long findEndSetup(RandomAccessFile reader, long startPosition) throws IOException {
int bracketCounter = 0;
boolean flag = false;
boolean stop = false;
String line = "";
reader.seek(startPosition);
while ((line = reader.readLine()) != null && !stop) {
for (char x : line.toCharArray()) {
if (x == '(') {
bracketCounter++;
} else if (x == ')') {
bracketCounter--;
}
if (flag && bracketCounter == 0) {
stop = true;
}
// prevent ending prematurely
if (bracketCounter != 0) {
flag = true;
}
}
}
return reader.getFilePointer();
}
}