/**
* Copyright (c) 2013 Puppet Labs, Inc. and other contributors, as listed below.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Puppet Labs
*/
package com.puppetlabs.geppetto.ruby.jruby;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
import java.util.Map;
import com.puppetlabs.geppetto.ruby.PPFunctionInfo;
import com.puppetlabs.geppetto.ruby.PPTypeInfo;
import com.puppetlabs.geppetto.ruby.RubySyntaxException;
import com.puppetlabs.geppetto.ruby.spi.IRubyIssue;
import com.puppetlabs.geppetto.ruby.spi.IRubyParseResult;
import com.puppetlabs.geppetto.ruby.spi.IRubyServices;
import org.jruby.CompatVersion;
import org.jruby.Ruby;
import org.jruby.RubyInstanceConfig;
import org.jruby.ast.FCallNode;
import org.jruby.ast.ModuleNode;
import org.jruby.ast.Node;
import org.jruby.lexer.yacc.InputStreamLexerSource;
import org.jruby.lexer.yacc.LexerSource;
import org.jruby.lexer.yacc.SyntaxException;
import org.jruby.parser.ParserConfiguration;
import org.jruby.parser.RubyParser;
import org.jruby.parser.RubyParserPool;
import org.jruby.parser.RubyParserResult;
import com.google.common.collect.Lists;
public class JRubyServices implements IRubyServices{
/**
* The number of the first line in a source file.
*/
private final int startLine = 1;
/**
* No idea what this is. Setting to true did not seem to add any information to errors.
*/
private final boolean extraPositionInfo = false;
/**
* No idea what this is. Setting to false still reports errors with some source and a caret indicating
* position.
*/
private final boolean inlineSource = false;
/**
* Compatibility of Ruby language version.
*/
private final CompatVersion rubyVersion = CompatVersion.RUBY1_9;
/**
* Holds the JRuby parser result (the AST and any reported issues/errors).
*
*/
public static class Result implements IRubyParseResult{
private List<IRubyIssue> issues;
private Node AST;
Result(RubyParserResult parserResult, List<IRubyIssue> issues) {
this.issues = issues;
this.AST = parserResult == null ? null : parserResult.getAST();
}
/**
* Returns a list of issues. Will return an empty list if there were no issues.
* @return
*/
@Override
public List<IRubyIssue> getIssues() {
return issues;
}
/**
* @return the parsed AST, or null in case of errors.
*/
public Node getAST() {
return AST;
}
@Override
public boolean hasErrors() {
if(issues != null)
for(IRubyIssue issue : issues)
if(issue.isSyntaxError())
return true;
return false;
}
@Override
public boolean hasIssues() {
return issues != null && issues.size() > 0;
}
}
private ParserConfiguration parserConfiguration;
private Ruby rubyRuntime;
/**
* IOExceptions thrown FileNotFound, and while reading
* @param file
* @return
* @throws IOException
*/
public IRubyParseResult parse(File file) throws IOException {
return internalParse(file);
}
protected Result internalParse(File file) throws IOException {
if(rubyRuntime == null)
setUp();
// Set up input
final List<String> lexerCapture = null; // do not want to record the read text lines
FileInputStream input = new FileInputStream(file);
LexerSource source = new InputStreamLexerSource(file.getPath(),
input, lexerCapture, startLine, extraPositionInfo);
// Get a parser
RubyParser parser = RubyParserPool.getInstance().borrowParser(rubyVersion);
// Create a warnings collector to give to the parser
RubyParserWarningsCollector warnings = new RubyParserWarningsCollector(rubyRuntime);
parser.setWarnings(warnings);
RubyParserResult parserResult = null;
try {
parserResult = parser.parse(parserConfiguration, source);
} catch (SyntaxException e) {
warnings.syntaxError(e);
} finally {
RubyParserPool.getInstance().returnParser(parser);
}
return new Result(parserResult, warnings.getIssues());
}
/**
* Configure a Ruby Runtime, it is needed for the warnings processor even if we are not
* going to be running any of the scripts.
*/
public void setUp() {
RubyInstanceConfig config = new RubyInstanceConfig();
config.setCompatVersion(rubyVersion);
rubyRuntime = Ruby.newInstance(config);
parserConfiguration = new ParserConfiguration(rubyRuntime,startLine,extraPositionInfo,inlineSource,rubyVersion);
}
public void tearDown() {
rubyRuntime = null;
parserConfiguration = null;
}
private static final String[] functionModuleFQN = new String[] { "Puppet", "Parser", "Functions"};
private static final String functionDefinition = "newfunction";
@Override
public List<PPFunctionInfo> getFunctionInfo(File file) throws IOException, RubySyntaxException {
List<PPFunctionInfo> functions = Lists.newArrayList();
Result result = internalParse(file);
if(result.hasErrors())
throw new RubySyntaxException(result.getIssues());
RubyModuleFinder finder = new RubyModuleFinder();
ModuleNode foundModule = finder.findModule(result.getAST(), functionModuleFQN);
if(foundModule == null)
return functions;
// find the function
FCallNode foundFunction = new RubyFunctionCallFinder().findFuntion(foundModule, functionDefinition);
if(foundFunction == null)
return functions;
Object arguments = new ConstEvaluator().eval(foundFunction.getArgsNode());
// Result should be a list with a String, and a Map
if(!(arguments instanceof List))
return functions;
List<?> argList = (List<?>)arguments;
if(argList.size() != 2)
return functions;
Object name = argList.get(0);
if(! (name instanceof String))
return functions;
Object hash = argList.get(1);
if(! (hash instanceof Map<?, ?>))
return functions;
Object type = ((Map<?,?>)hash).get("type");
boolean rValue = "rvalue".equals(type);
Object doc = ((Map<?,?>)hash).get("doc");
String docString = doc == null ? "" : doc.toString();
functions.add(new PPFunctionInfo((String)name, rValue, docString));
return functions;
}
@Override
public List<PPTypeInfo> getTypeInfo(File file) throws IOException, RubySyntaxException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isMockService() {
return false;
}
@Override
public List<PPTypeInfo> getTypePropertiesInfo(File file)
throws IOException, RubySyntaxException {
throw new UnsupportedOperationException("Implement this method");
}
@Override
public PPTypeInfo getMetaTypeProperties(File file) throws IOException,
RubySyntaxException {
throw new UnsupportedOperationException("Implement this method");
}
@Override
public IRubyParseResult parse(String path, Reader reader)
throws IOException {
throw new UnsupportedOperationException("Please implement me");
}
@Override
public List<PPTypeInfo> getTypeInfo(String fileName, Reader reader)
throws IOException, RubySyntaxException {
throw new UnsupportedOperationException("Please implement me");
}
@Override
public List<PPFunctionInfo> getFunctionInfo(String fileName, Reader reader) {
throw new UnsupportedOperationException("Please implement me");
}
@Override
public PPTypeInfo getMetaTypeProperties(String fileName,
Reader reader) {
throw new UnsupportedOperationException("Please implement me");
}
@Override
public List<PPTypeInfo> getTypePropertiesInfo(String fileName, Reader reader)
throws IOException, RubySyntaxException {
throw new UnsupportedOperationException("Please implement me");
}
@Override
public List<PPFunctionInfo> getLogFunctions(File file) throws IOException,
RubySyntaxException {
throw new UnsupportedOperationException("Please implement me");
}
}