Skip to content

Commit

Permalink
Merge pull request #231 from TypeFox/dh/yang-processor
Browse files Browse the repository at this point in the history
Added 'yang-tool' cli application
  • Loading branch information
dhuebner authored Nov 29, 2023
2 parents de58f96 + fe36d6d commit 81eea95
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 21 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ jobs:
name: yang-language-server_diagram-extension_${{ env.PROJECT_VERSION }}
path: |
yang-lsp/build/yang-language-server_diagram-extension*.zip
- name: Archive yang-tool_cli
uses: actions/upload-artifact@v3
with:
name: yang-tool_cli_${{ env.PROJECT_VERSION }}
path: |
yang-lsp/build/yang-tool_cli_*.zip
9 changes: 9 additions & 0 deletions yang-lsp/io.typefox.yang/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
apply from: "${rootDir}/gradle/distribution.gradle"

ext.title = 'Yang Language'
description = 'Language infrastructure for Yang based on Xtext'

Expand All @@ -10,6 +12,7 @@ configurations {
dependencies {
implementation "org.eclipse.xtext:org.eclipse.xtext:${versions.xtext}"
implementation "com.google.code.gson:gson:${versions.gson}"
implementation "org.jcommander:jcommander:1.83"
implementation("xerces:xercesImpl:${versions.xerces}") {
exclude module: "xml-apis"
}
Expand All @@ -36,6 +39,12 @@ task generateXtextLanguage(type: JavaExec) {
args += "rootPath=/${projectDir}/.."
}

mainClassName = 'io.typefox.yang.processor.YangProcessorApp'
applicationName = 'yang-tool'
ext.applicationQualifier = 'cli'

generateXtext.dependsOn(generateXtextLanguage)
clean.dependsOn(cleanGenerateXtextLanguage)
eclipse.classpath.plusConfigurations += [configurations.mwe2]


Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public List<HasStatements> getChildren() {
public static class ElementIdentifier {
final public String name, prefix;

final static public ElementIdentifier UNRESOLVED = new ElementIdentifier("<unresoved>", null);

public ElementIdentifier(String name, String prefix) {
super();
this.name = name;
Expand Down Expand Up @@ -152,11 +154,11 @@ static public class ValueType {
public ValueType(String prefix, String name) {
this(prefix, name, false);
}

public ValueType(String prefix, String name, boolean forceSimpleName) {
super();
this.prefix = prefix;
this.name = name;
this.name = name == null ? "unknown" : name;
this.forceSimpleName = forceSimpleName;
}

Expand Down Expand Up @@ -289,7 +291,7 @@ private ValueType createValueType(Type typeStatement) {
var typeRefModule = ProcessorUtility.moduleIdentifier(typeRef);
var sameModule = Objects.equal(typeModule.name, typeRefModule.name);
String prefix = sameModule ? null : typeModule.prefix;

var refText = referenceText(typeRef);
if (prefix != null && refText != null && !refText.equals(prefix + ":" + typeDef.getName())) {
// use reference text if type
Expand All @@ -300,12 +302,12 @@ private ValueType createValueType(Type typeStatement) {

private String referenceText(TypeReference typeRef) {
var node = NodeModelUtils.getNode(typeRef);
if(node == null) {
if (node == null) {
return null;
}
return NodeModelUtils.getTokenText(node);
}

private String serializedXpath(XpathExpression reference) {
// TODO use serializer or implement a an own simple one
ICompositeNode nodeFor = NodeModelUtils.findActualNodeFor(reference);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public static ElementIdentifier moduleIdentifier(EObject eObj) {
return foreignAdapter.moduleId;
}
var module = new YangExtensions().getMainModule(eObj);
if (module == null) {
return ElementIdentifier.UNRESOLVED;
}
Prefix prefix = (Prefix) module.getSubstatements().stream().filter(s -> (s instanceof Prefix)).findFirst()
.get();
return new ElementIdentifier(module.getName(), prefix != null ? prefix.getPrefix() : null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,15 @@

public class YangProcessor {

public static void main(String[] args) {
var processedData = new YangProcessor().process(newArrayList(), newArrayList(), newArrayList());
new GsonBuilder().create().toJson(processedData, System.out);
public static enum Format {
tree, json
}

/**
*
* @param modules
* @param includedFeatures
* @param excludedFeatures
* @param modules loaded modules
* @param includedFeatures features to include
* @param excludedFeatures features to exclude
* @return ProcessedDataTree or <code>null</code> if modules is
* <code>null</code> or empty.
*/
Expand All @@ -66,18 +65,34 @@ public ProcessedDataTree process(List<AbstractModule> modules, List<String> incl
excludedFeatures == null ? newArrayList() : excludedFeatures);
}

/**
* @param processedData data to serialize
* @param format tree or json. tree is default
* @param output target
*/
public void serialize(ProcessedDataTree processedData, Format format, StringBuilder output) {
switch (format) {
case json: {
new GsonBuilder().setPrettyPrinting().create().toJson(processedData, output);
break;
}
case tree: {
// TODO pick module by file name
output.append(new DataTreeSerializer().serialize(processedData.getModules().get(0)));
break;
}
}
}

protected ProcessedDataTree processInternal(List<AbstractModule> modules, List<String> includedFeatures,
List<String> excludedFeatures) {
var evalCtx = new FeatureEvaluationContext(includedFeatures, excludedFeatures);
ProcessedDataTree processedDataTree = new ProcessedDataTree();
modules.forEach((module) -> module.eAllContents().forEachRemaining((ele) -> {
if (ele instanceof Deviate) {
/*
* var deviation = ((Deviation) ele); deviation.getSubstatements().forEach((sub)
* -> { });
*/
Deviate deviate = (Deviate) ele;
switch (deviate.getArgument()) {
// TODO implements other cases
case "add":
case "replace":
break;
Expand Down Expand Up @@ -141,12 +156,8 @@ protected ProcessedDataTree processInternal(List<AbstractModule> modules, List<S
return processedDataTree;
}

private void processChildren(Statement statement, HasStatements parent, FeatureEvaluationContext evalCtx) {
/*if (!ProcessorUtility.isEnabled(statement, evalCtx)) {
// filtered by a feature
return;
}*/
statement.getSubstatements().stream().filter( ele -> ProcessorUtility.isEnabled(ele, evalCtx)).forEach((ele) -> {
protected void processChildren(Statement statement, HasStatements parent, FeatureEvaluationContext evalCtx) {
statement.getSubstatements().stream().filter(ele -> ProcessorUtility.isEnabled(ele, evalCtx)).forEach((ele) -> {
ElementData child = null;
if (ele instanceof Container) {
child = new ElementData((Container) ele, ElementKind.Container);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package io.typefox.yang.processor;

import static com.google.common.collect.Lists.newArrayList;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.resource.FileExtensionProvider;
import org.eclipse.xtext.resource.XtextResourceSet;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;

import io.typefox.yang.YangStandaloneSetup;
import io.typefox.yang.processor.YangProcessor.Format;
import io.typefox.yang.yang.AbstractModule;

public class YangProcessorApp {

public static class Args {

@Parameter(names = "--help", help = true)
private boolean help;

@Parameter(description = "<filename>", required = true)
public String module;

@Parameter(names = { "-d",
"--deviation-module" }, description = "DISABLED! Use to apply the deviations defined in this file.")
public String deviationModule;

@Parameter(names = { "-f", "--format" }, description = "Output format.")
public Format format = Format.tree;

@Parameter(names = { "-p",
"--path" }, description = "A colon (:) separated list of directories to search for imported modules. Default is the current directory.")
public String path;

@Parameter(names = { "-F", "--features" }, description = "Included features.")
public List<String> includedFeatures = newArrayList();

@Parameter(names = { "-X", "--exclude-features" }, description = "Excluded features.")
public List<String> excludedFeatures = newArrayList();

}

public static void main(String[] args) {
Args cliArgs = null;
StringBuilder out = new StringBuilder();
try {
cliArgs = parseArgs(out, args);
} catch (ParameterException pe) {
System.err.println(pe.getMessage());
pe.usage();
System.exit(29);
}

if (cliArgs.help) {
System.out.println(out.toString());
return;
}

var yangProcessor = new YangProcessor();

List<AbstractModule> modules = null;
try {
var pathes = cliArgs.path != null ? cliArgs.path.split(":") : null;
modules = loadModuleFileAndDependencies(cliArgs.module, pathes);
} catch (IOException e) {
String msg = e.getMessage();
if (msg == null) {
msg = "An exception occured when loading file: " + cliArgs.module;
}
System.err.println(msg);
System.exit(11);
}
var processedData = yangProcessor.process(modules, cliArgs.includedFeatures, cliArgs.excludedFeatures);

if (processedData == null || processedData.getModules() == null) {
String msg = "No module found in file: " + (cliArgs.module == null ? "<empty>" : cliArgs.module);
System.err.println(msg);
System.exit(23);
}

var output = new StringBuilder();
yangProcessor.serialize(processedData, cliArgs.format, output);
System.out.println(output.toString());
}

private static List<AbstractModule> loadModuleFileAndDependencies(String moduleFilePath, String... paths)
throws IOException {
var injector = new YangStandaloneSetup().createInjectorAndDoEMFRegistration();
var moduleFile = new File(moduleFilePath);
if (!moduleFile.exists()) {
throw new IOException(
"File " + moduleFilePath + " doesn't exists in directory " + moduleFile.getAbsolutePath());
}

var rs = injector.getInstance(XtextResourceSet.class);
// add main module as first resource
var moduleResource = rs.createResource(URI.createFileURI(moduleFile.getAbsolutePath()));

// add files from the current directory
var implicitLookup = moduleFile.getAbsoluteFile().getParentFile();
var extensionProvider = injector.getInstance(FileExtensionProvider.class);
var fileExts = Collections.singleton("yang");
if (extensionProvider != null && extensionProvider.getFileExtensions() != null) {
if (!extensionProvider.getFileExtensions().isEmpty())
fileExts = extensionProvider.getFileExtensions();
}
loadAdditionalFiles(implicitLookup, rs, fileExts);

// handle --path argument
if (paths != null) {
for (String path : paths) {
var folder = new File(path);
if (!folder.isDirectory()) {
System.err.println(folder.getAbsolutePath() + " is not a directory. Skipped.");
} else {
loadAdditionalFiles(folder.getAbsoluteFile(), rs, fileExts);
}
}
}

// load models
moduleResource.load(rs.getLoadOptions());
EcoreUtil.resolveAll(moduleResource);

// Collect all contained modules
var modules = new ArrayList<AbstractModule>();
for (Resource res : rs.getResources()) {
var rootObj = res.getContents().get(0);
if (rootObj instanceof AbstractModule) {
modules.add((AbstractModule) rootObj);
}
}
return modules;
}

private static void loadAdditionalFiles(File parent, XtextResourceSet rs, Set<String> fileExtensions) {
for (File file : parent.listFiles()) {
URI fileURI = URI.createFileURI(file.getAbsolutePath());
if (file.isFile() && fileExtensions.contains(fileURI.fileExtension())) {
rs.getResource(fileURI, true);
}
}
}

public static Args parseArgs(StringBuilder out, String... args) {
var cliArgs = new Args();
JCommander commander = JCommander.newBuilder().programName("yang-tool").addObject(cliArgs).build();
commander.parse(args);
if (cliArgs.help && out != null) {
commander.usage(out);
}
return cliArgs;
}

}
Loading

0 comments on commit 81eea95

Please sign in to comment.