/**
* 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.resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Map;
import com.puppetlabs.geppetto.pp.pptp.Function;
import com.puppetlabs.geppetto.pp.pptp.MetaType;
import com.puppetlabs.geppetto.pp.pptp.PPTPFactory;
import com.puppetlabs.geppetto.pp.pptp.Parameter;
import com.puppetlabs.geppetto.pp.pptp.Property;
import com.puppetlabs.geppetto.pp.pptp.Type;
import com.puppetlabs.geppetto.pp.pptp.TypeFragment;
import com.puppetlabs.geppetto.ruby.PPFunctionInfo;
import com.puppetlabs.geppetto.ruby.PPTypeInfo;
import com.puppetlabs.geppetto.ruby.RubyHelper;
import com.puppetlabs.geppetto.ruby.RubySyntaxException;
import com.puppetlabs.geppetto.ruby.spi.IRubyIssue;
import com.puppetlabs.geppetto.ruby.spi.IRubyParseResult;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
/**
* A Resource that loads .rb files containing Puppet "target platform"
* information. Ruby source on particular paths are transformed into PPTP model
* contents.
*
*/
public class PptpRubyResource extends ResourceImpl {
public enum LoadType {
TYPE, TYPEFRAGMENT, META, FUNCTION, IGNORED;
}
public static class RubyIssueDiagnostic implements Diagnostic {
private IRubyIssue issue;
public RubyIssueDiagnostic(IRubyIssue issue) {
this.issue = issue;
}
/**
* @throws UnsupportedOperationException
* - column is not available.
*/
@Override
public int getColumn() {
throw new UnsupportedOperationException();
}
@Override
public int getLine() {
return issue.getLine();
}
@Override
public String getLocation() {
return issue.getFileName();
}
@Override
public String getMessage() {
return issue.getMessage();
}
}
public static class RubySyntaxExceptionDiagnostic implements Diagnostic {
private RubySyntaxException issue;
public RubySyntaxExceptionDiagnostic(RubySyntaxException issue) {
this.issue = issue;
}
/**
* @throws UnsupportedOperationException
* - column is not available.
*/
@Override
public int getColumn() {
throw new UnsupportedOperationException();
}
@Override
public int getLine() {
return issue.getLine();
}
@Override
public String getLocation() {
return issue.getFilename();
}
@Override
public String getMessage() {
return issue.getMessage();
}
}
/**
* (SOMEROOT/lib/puppet/) parser/functions/F.rb (SOMEROOT/lib/puppet/)
* type/T.rb (SOMEROOT/lib/puppet/) type/FRAGMENTDIR/TypeFragment.rb
* (SOMEROOT/lib/puppet/) type.rb - META TYPE (typically in a distro)
*
* @return
*/
public static LoadType detectLoadType(URI uri) {
List<String> segments = uri.segmentsList();
final int lastPuppet = segments.lastIndexOf("puppet");
final int segmentCount = segments.size();
if(lastPuppet < 0)
return LoadType.IGNORED;
int idx = lastPuppet + 1;
if(idx < segmentCount) {
String segment = segments.get(idx);
if("parser".equals(segment)) {
idx++;
if(idx < segmentCount) {
segment = segments.get(idx);
if("functions".equals(segment)) {
idx++;
if(idx == segmentCount - 1 && segments.get(idx).endsWith(".rb"))
return LoadType.FUNCTION;
}
}
}
else if("type".equals(segment)) {
idx++;
if(idx < segmentCount) {
segment = segments.get(idx);
// a .rb file under type
if(segment.endsWith(".rb") && idx == segmentCount - 1)
return LoadType.TYPE;
// typefragment must be in a subdir of type, e.g.
// type/file/X.rb
idx++;
if(idx == segmentCount - 1 && segments.get(idx).endsWith(".rb"))
return LoadType.TYPEFRAGMENT;
}
}
else if("type.rb".equals(segment) && idx == segmentCount - 1) {
return LoadType.META;
}
}
return LoadType.IGNORED;
}
private LoadType loadType;
/**
* Create an instance with a reference to a resource in Ruby text format.
*
* @param uri
*/
public PptpRubyResource(URI uri) {
super(uri);
}
protected LoadType detectLoadType() {
return detectLoadType(getURI());
}
@Override
public void doLoad(InputStream in, Map<?, ?> options) throws IOException {
loadType = detectLoadType();
internalLoadRuby(in);
}
/**
* Loads one (or more) PPTP Type, PPTP Function, PPTP Meta, or PPTP Fragment
* depending on the type of load (determined by looking at the path to the
* parsed .rb file).
*
* @throws IOException
*/
protected void internalLoadRuby(InputStream inputStream) throws IOException {
if(loadType == LoadType.IGNORED) {
this.getContents().clear();
return;
}
RubyHelper helper = new RubyHelper();
helper.setUp();
URI uri = getURI();
try {
switch(loadType) {
case TYPE: {
List<PPTypeInfo> typeInfo = helper.getTypeInfo(uri.path(), new InputStreamReader(inputStream));
for(PPTypeInfo info : typeInfo) {
Type type = PPTPFactory.eINSTANCE.createType();
type.setName(info.getTypeName());
type.setDocumentation(info.getDocumentation());
for(Map.Entry<String, PPTypeInfo.Entry> entry : info.getParameters().entrySet()) {
Parameter parameter = PPTPFactory.eINSTANCE.createParameter();
parameter.setName(entry.getKey());
parameter.setDocumentation(entry.getValue().documentation);
parameter.setRequired(entry.getValue().isRequired());
type.getParameters().add(parameter);
}
for(Map.Entry<String, PPTypeInfo.Entry> entry : info.getProperties().entrySet()) {
Property property = PPTPFactory.eINSTANCE.createProperty();
property.setName(entry.getKey());
property.setDocumentation(entry.getValue().documentation);
property.setRequired(entry.getValue().isRequired());
type.getProperties().add(property);
}
getContents().add(type);
}
}
break;
case FUNCTION: {
List<PPFunctionInfo> functions = helper.getFunctionInfo(uri.path(), new InputStreamReader(
inputStream));
for(PPFunctionInfo info : functions) {
Function pptpFunc = PPTPFactory.eINSTANCE.createFunction();
pptpFunc.setName(info.getFunctionName());
pptpFunc.setRValue(info.isRValue());
pptpFunc.setDocumentation(info.getDocumentation());
getContents().add(pptpFunc);
}
}
break;
case META: {
PPTypeInfo info = helper.getMetaTypeInfo(uri.path(), new InputStreamReader(inputStream));
MetaType type = PPTPFactory.eINSTANCE.createMetaType();
type.setName(info.getTypeName());
type.setDocumentation(info.getDocumentation());
for(Map.Entry<String, PPTypeInfo.Entry> entry : info.getParameters().entrySet()) {
Parameter parameter = PPTPFactory.eINSTANCE.createParameter();
parameter.setName(entry.getKey());
parameter.setDocumentation(entry.getValue().documentation);
parameter.setRequired(entry.getValue().isRequired());
type.getParameters().add(parameter);
}
// TODO: Scan the puppet source for providers for the type
// This is a CHEAT -
// https://github.com/puppetlabs/geppetto/issues/37
Parameter p = PPTPFactory.eINSTANCE.createParameter();
p.setName("provider");
p.setDocumentation("");
p.setRequired(false);
type.getParameters().add(p);
getContents().add(type);
break;
}
case TYPEFRAGMENT: {
for(PPTypeInfo type : helper.getTypeFragments(uri.path(), new InputStreamReader(inputStream))) {
TypeFragment fragment = PPTPFactory.eINSTANCE.createTypeFragment();
fragment.setName(type.getTypeName());
// add the properties (will typically load just one).
for(Map.Entry<String, PPTypeInfo.Entry> entry : type.getProperties().entrySet()) {
Property property = PPTPFactory.eINSTANCE.createProperty();
property.setName(entry.getKey());
property.setDocumentation(entry.getValue().documentation);
property.setRequired(entry.getValue().isRequired());
fragment.getProperties().add(property);
}
// add the parameters (will typically load just one).
for(Map.Entry<String, PPTypeInfo.Entry> entry : type.getParameters().entrySet()) {
Parameter parameter = PPTPFactory.eINSTANCE.createParameter();
parameter.setName(entry.getKey());
parameter.setDocumentation(entry.getValue().documentation);
parameter.setRequired(entry.getValue().isRequired());
fragment.getParameters().add(parameter);
}
getContents().add(fragment);
}
break;
}
case IGNORED:
break;
}
}
catch(RubySyntaxException syntaxException) {
getErrors().add(new RubySyntaxExceptionDiagnostic(syntaxException));
}
finally {
helper.tearDown();
}
}
@Override
public void load(Map<?, ?> options) throws IOException {
if(!super.isLoaded) {
super.isLoading = true;
loadType = detectLoadType();
internalLoadRuby(getURIConverter().createInputStream(uri));
super.isLoading = false;
super.isLoaded = true;
}
}
/**
* Translates ruby issues to diagnostics using instances of {@link RubyIssueDiagnostic}. All syntax issues are reported as errors,
* all others as warnings.
*
* @param parseResult
*/
protected void rubyIssuesToDiagnostics(IRubyParseResult parseResult) {
for(IRubyIssue issue : parseResult.getIssues()) {
if(issue.isSyntaxError())
getErrors().add(new RubyIssueDiagnostic(issue));
else
getWarnings().add(new RubyIssueDiagnostic(issue));
}
}
@Override
public void save(Map<?, ?> options) throws IOException {
throw new UnsupportedOperationException("Save of PPTP parsed from a ruby file is not possible.");
}
}