From f88d537615f04d7c14c8805ae50df28596cdf46e Mon Sep 17 00:00:00 2001 From: Siddharth Sharma Date: Mon, 1 Apr 2024 12:49:03 +0200 Subject: [PATCH] Add `yangIgnorePath` setting This allows for exclusion of specific subdirs or files under the dirs specified via `yangPath`. Also formatted markdown files to fix markdownlint issues. This addresses TypeFox/yang-lsp#232 Signed-off-by: Siddharth Sharma --- .editorconfig | 11 +- docs/Extensions.md | 109 ++++++++-------- docs/Processing_Files.md | 116 +++++++++-------- docs/Settings.md | 118 ++++++++++++------ schema/yang-lsp-settings-schema.json | 11 +- .../typefox/yang/utils/YangPathProvider.xtend | 21 +++- 6 files changed, 236 insertions(+), 150 deletions(-) diff --git a/.editorconfig b/.editorconfig index a1c64f2f..70183e5a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,17 +1,18 @@ root = true -[*.json] +[*] charset = utf-8 trim_trailing_whitespace = true end_of_line = lf insert_final_newline = true + +[*.json] indent_style = space indent_size = 2 [*.md] -charset = utf-8 -trim_trailing_whitespace = true -end_of_line = lf -insert_final_newline = true indent_style = space indent_size = 2 + +[{*.xtext,*.xtend}] +indent_size = 4 diff --git a/docs/Extensions.md b/docs/Extensions.md index 714241f0..4f168c4b 100644 --- a/docs/Extensions.md +++ b/docs/Extensions.md @@ -1,16 +1,18 @@ # Extensions -The yang-lsp allows to have additional third party extensions, configured through the [`yang.settings` file](Settings.md). -That file must be located in the root of an opened directory must conform to JSON syntax. +The yang-lsp allows to have additional third party extensions, configured +through the [`yang.settings` file](Settings.md). That file must be located in +the root of an opened directory must conform to JSON syntax. So far two different kinds of extensions are supported : - - Validators (`IValidatorExtension`) - - Commands (`ICommandExtension`) + +- Validators (`IValidatorExtension`) +- Commands (`ICommandExtension`) ## Create a Validator -A validator extension is a Java class that implements the interface `io.typefox.yang.validation.IValidatorExtension`. -Here is a small example: +A validator extension is a Java class that implements the interface +`io.typefox.yang.validation.IValidatorExtension`. Here is a small example: ```java package my.pack; @@ -19,72 +21,75 @@ package my.pack; public class MyExampleValidator implements IValidatorExtension { - public static final String BAD_NAME = "bad_name"; + public static final String BAD_NAME = "bad_name"; - @Override - public void validate(AbstractModule module, IAcceptor issueAcceptor, CancelIndicator cancelIndicator) { - if (module.getName().equals("foo")) { - issueAcceptor.accept(IssueFactory.createIssue(module, YangPackage.Literals.ABSTRACT_MODULE__NAME, "'foo' is a bad name", BAD_NAME)); - } - } + @Override + public void validate(AbstractModule module, IAcceptor issueAcceptor, CancelIndicator cancelIndicator) { + if (module.getName().equals("foo")) { + issueAcceptor.accept(IssueFactory.createIssue(module, YangPackage.Literals.ABSTRACT_MODULE__NAME, "'foo' is a bad name", BAD_NAME)); + } + } } -``` +``` ## Create a Command Extension -A command extension contributes actions, that will be shown in the context menu of a supporting client (currently only Yangster supports it). +A command extension contributes actions, that will be shown in the context menu +of a supporting client (currently only Yangster supports it). + Here is an example: -```xtend +```java class MyCommand implements ICommandExtension { - static val COMMAND = "Create a file name 'foo.txt'." - - /** - * return a list of commands. A command string is used as ID internally and as a label in the UI. - */ - override getCommands() { - #[COMMAND] - } - - /** - * Called when the user asked to execute a certain command. - */ - override executeCommand(String command, Resource resource, LanguageClient client) { - if (COMMAND == command) { - // get the project directory - val uri = ProjectConfigAdapter.findInEmfObject(resource.resourceSet)?.projectConfig?.path.toFileString - val f = new File(uri, 'foo.txt') - if (f.exists) { - client.showMessage(new MessageParams => [ - message = 'Such a file already exists.' - ]) - } else { - f.createNewFile - } - } - } - + static val COMMAND = "Create a file name 'foo.txt'." + + /** + * return a list of commands. A command string is used as ID internally and as a label in the UI. + */ + override getCommands() { + #[COMMAND] + } + + /** + * Called when the user asked to execute a certain command. + */ + override executeCommand(String command, Resource resource, LanguageClient client) { + if (COMMAND == command) { + // get the project directory + val uri = ProjectConfigAdapter.findInEmfObject(resource.resourceSet)?.projectConfig?.path.toFileString + val f = new File(uri, 'foo.txt') + if (f.exists) { + client.showMessage(new MessageParams => [ + message = 'Such a file already exists.' + ]) + } else { + f.createNewFile + } + } + } + } ``` - ## Package an extension -The class needs to be packaged in a jar. So simply use the java build tool of your preference to create a jar from it. Put the jar somewhere relative to the project's root directory. +The class needs to be packaged in a jar. So simply use the java build tool of +your preference to create a jar from it. Put the jar somewhere relative to the +project's root directory. ## Add the plugin -Create (or open) the `yang.settings` file and add the plugin using the following configuration: +Create (or open) the `yang.settings` file and add the plugin using the following +configuration: ```json - "extension" : { - "classpath" : "extension.jar", - "validators" : "my.pack.MyExampleValidator", - "commands" : "my.pack.MyCommand" - } + "extension" : { + "classpath" : "extension.jar", + "validators" : "my.pack.MyExampleValidator", + "commands" : "my.pack.MyCommand" + } ``` The language server will automatically pick up the extension. - diff --git a/docs/Processing_Files.md b/docs/Processing_Files.md index 80673c5d..8a62d5a7 100644 --- a/docs/Processing_Files.md +++ b/docs/Processing_Files.md @@ -1,10 +1,15 @@ -# Processing YANG files +# Processing YANG files -As yang-lsp contains all the tools to parse, link and validate YANG models. If you want to further process the YANG files you authored with yang-lsp it makes sense to make reuse of the existing functionality. +As yang-lsp contains all the tools to parse, link and validate YANG models. If +you want to further process the YANG files you authored with yang-lsp it makes +sense to make reuse of the existing functionality. -Here is some example code in Xtend for an application that reads in all YANG files from a given directory. The files are parsed into our YANG EMF model, all cross-references are resolved and all files are validated. If there are no errors, the method `generate()` is called for all resources: +Here is some example code in Xtend for an application that reads in all YANG +files from a given directory. The files are parsed into our YANG EMF model, all +cross-references are resolved and all files are validated. If there are no +errors, the method `generate()` is called for all resources: -```Xtend +```xtend package io.typefox.yang.example import com.google.inject.Inject @@ -20,66 +25,67 @@ import org.eclipse.xtext.util.CancelIndicator import org.eclipse.xtext.validation.CheckMode import org.eclipse.xtext.validation.IResourceValidator -class StandaloneExample { - - def static void main(String... args) { - val injector = new YangStandaloneSetup().createInjectorAndDoEMFRegistration - injector.getInstance(StandaloneExample).run(args) - } - - @Inject XtextResourceSet resourceSet - @Inject IResourceValidator validator - boolean hasErrors = false - - def void run(String... args) { - addFile(new File(args.head)) - resourceSet.resources.forEach [ resource | - EcoreUtil.resolveAll(resource) - val issues = validator.validate(resource, CheckMode.ALL, CancelIndicator.NullImpl) - issues.forEach [ issue | - hasErrors = hasErrors || issue.severity === Severity.ERROR - System.err.println(issue) - ] - ] - if(!hasErrors) { - resourceSet.resources.forEach [ - generate - ] - } - } - - def void addFile(File file) { - if(file.isDirectory) - file.listFiles.forEach[ addFile ] - else if(file.name.endsWith('.yang')) - resourceSet.getResource(URI.createURI(file.toURI.toString), true) - } - - def generate(Resource resource) { - // do your own processing here - resource.allContents.filter(Module).forEach [ module | - println(''' - Found module «module.name» - ''') - ] - } +class StandaloneExample { + + def static void main(String... args) { + val injector = new YangStandaloneSetup().createInjectorAndDoEMFRegistration + injector.getInstance(StandaloneExample).run(args) + } + + @Inject XtextResourceSet resourceSet + @Inject IResourceValidator validator + boolean hasErrors = false + + def void run(String... args) { + addFile(new File(args.head)) + resourceSet.resources.forEach [ resource | + EcoreUtil.resolveAll(resource) + val issues = validator.validate(resource, CheckMode.ALL, CancelIndicator.NullImpl) + issues.forEach [ issue | + hasErrors = hasErrors || issue.severity === Severity.ERROR + System.err.println(issue) + ] + ] + if(!hasErrors) { + resourceSet.resources.forEach [ + generate + ] + } + } + + def void addFile(File file) { + if(file.isDirectory) + file.listFiles.forEach[ addFile ] + else if(file.name.endsWith('.yang')) + resourceSet.getResource(URI.createURI(file.toURI.toString), true) + } + + def generate(Resource resource) { + // do your own processing here + resource.allContents.filter(Module).forEach [ module | + println(''' + Found module «module.name» + ''') + ] + } } ``` -A number of useful helper methods can be found in the [utils package](https://github.com/theia-ide/yang-lsp/tree/master/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/utils) + +A number of useful helper methods can be found in the [utils package][1] Here is sample `build.gradle` to build the above class: ```groovy buildscript { - repositories.jcenter() - dependencies { - classpath 'org.xtext:xtext-gradle-plugin:1.0.19' - } + repositories.jcenter() + dependencies { + classpath 'org.xtext:xtext-gradle-plugin:1.0.19' + } } repositories { jcenter() - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } } apply plugin: 'org.xtext.xtend' @@ -90,10 +96,12 @@ apply plugin: 'maven' group = 'io.typefox.yang' version = '0.1.0-SNAPSHOT' -dependencies { +dependencies { compile 'io.typefox.yang:io.typefox.yang:0.1.0-SNAPSHOT' compile 'org.eclipse.xtext:org.eclipse.xtext:2.13.0' compile 'org.eclipse.xtend:org.eclipse.xtend.lib:2.13.0' compile 'com.google.inject:guice:3.0' } ``` + +[1]: https://github.com/TypeFox/yang-lsp/tree/master/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/utils diff --git a/docs/Settings.md b/docs/Settings.md index 3e5dbf99..d1bd9420 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -2,14 +2,16 @@ The yang-lsp allos the user to configure various settings, though setting files. A setting file has the name `yang.settings` and can be located - - at the root of a project - - in the user's directory under `~/.yang/yang.settings`. -The file syntax is a JSONC and its schema can be found [here](../schema/yang-lsp-settings-schema.json). +- at the root of a project +- in the user's directory under `~/.yang/yang.settings`. + +The file syntax is a JSON with Comments and its schema can be found [here][1]. ## Disable Code Lens -If you don't want to see the code lenses you can turn it of with the following property: +If you don't want to see the code lenses you can turn it of with the following +property: ```json { @@ -19,7 +21,10 @@ If you don't want to see the code lenses you can turn it of with the following p ## Excluded Paths -Many IDEs and tools copy YANG files into another folder within the same project. As the YANG LSP treats all files within a project the same, this usually infers issues about duplicate elements. To avoid that, you can exclude several directories in the project setting `excludePath`, e.g. +Many IDEs and tools copy YANG files into another folder within the same +project. As the YANG LSP treats all files within a project the same, this +usually informs issues about duplicate elements. To avoid that, you can exclude +several directories in the project setting `excludePath`, e.g. ```json { @@ -27,11 +32,16 @@ Many IDEs and tools copy YANG files into another folder within the same project. } ``` -exludes the default output folder of Maven/Gradle and Eclipse JDT. The path elements are project relative directory names. You can specify multiple elements separated with a colon. The file separator is always `/` independent from the OS. +exludes the default output folder of Maven/Gradle and Eclipse JDT. The path +elements are project relative directory names. You can specify multiple elements +separated with a colon. The file separator is always `/` independent from the +OS. ## YANG Libraries -Often you don't specify a self contained set of YANG models but rely on existing standard libs instead, e.g. from the IETF. Thes don't necessarily reside in your workspace. To specify such libraries, use `yangPath` +Often you don't specify a self contained set of YANG models but rely on existing +standard libs instead, e.g. from the IETF. These don't necessarily reside in +your workspace. To specify such libraries, use `yangPath` ```json { @@ -39,21 +49,38 @@ Often you don't specify a self contained set of YANG models but rely on existing } ``` -You can specify individual files, directories (contents will be added recursively) or ZIP files. The file name format is OS specific, and so is the path separator (`;` on Windows, `:` elsewhere). +If `yangPath` is provided to refer to an SDK location which includes a version +of the project's YANG models that need to be ignored, following approach can be +used. + +```json +{ + "yangPath": "/path/to/sdk/yang/dir", + "yangIgnorePath": "/path/to/sdk/yang/dir/project" +} +``` + +You can specify individual files, directories (contents will be added +recursively) or ZIP files. The file name format is OS specific, and so is the +path separator (`;` on Windows, `:` elsewhere). ## Extensions -The settings is used to register an extension. Please find the details [here](Extensions.md). +The settings is used to register an extension. Please find the details [here][2]. ## Diagnostics -The user can change the severity of diagnostics, by setting the value of a diagnostic preference key to either - - `"error"` - - `"warning"` - - `"ignore"` +The user can change the severity of diagnostics, by setting the value of a +diagnostic preference key to either + +- `"error"` +- `"warning"` +- `"ignore"` -The settings contains a `diagnostics` section in which the serverioties for the below diagnostics can be adjusted. +The settings contains a `diagnostics` section in which the serverioties for the +below diagnostics can be adjusted. An example : + ```json { "diagnostic" : { @@ -62,21 +89,26 @@ An example : } ``` +### Diagnostic Issue Codes + #### `substatement-cardinality` -Issue code that are entangled with cardinality problems of container statement's sub-statements. +Issue code that are entangled with cardinality problems of container statement's +sub-statements. (default severity: error) #### `unexpected-statement` -Issue code indicating an invalid sub-statement inside its parent statement container. +Issue code indicating an invalid sub-statement inside its parent statement +container. (default severity: error) #### `substatement-ordering` -Issue code for cases when a sub-statement incorrectly precedes another sub-statement. +Issue code for cases when a sub-statement incorrectly precedes another +sub-statement. (default severity: error) @@ -88,7 +120,8 @@ Issues code that is used when a module has anything but {@code '1.1'} version. #### `type-error` -Errors for types. Such as invalid type restriction, range error, fraction-digits issue. +Errors for types. Such as invalid type restriction, range error, fraction-digits +issue. (default severity: error) @@ -100,12 +133,12 @@ A duplicate local name. #### `missing-prefix` - (default severity: error) #### `missing-revision` -Diagnostic that indicates a module is available in multiple revisions when no revision is provided on an import. +Diagnostic that indicates a module is available in multiple revisions when no +revision is provided on an import. (default severity: warning) @@ -135,26 +168,30 @@ Issue code when the revision date does not conform the "YYYY-MM-DD" format. #### `revision-order` -Issue code that applies on a revision if that is not in a reverse chronological order. +Issue code that applies on a revision if that is not in a reverse chronological +order. (default severity: warning) #### `bad-type-name` -Issue code when the name of a type does not conform with the existing constraints. -For instance; the name contains any invalid characters, or equals to any YANG built-in type name. +Issue code when the name of a type does not conform with the existing +constraints. For instance, the name contains any invalid characters, or equals +to any YANG built-in type name. (default severity: error) #### `bad-include-yang-version` -Issues code when there is an inconsistency between a module's version and the version of the included modules. +Issues code when there is an inconsistency between a module's version and the +version of the included modules. (fixed severity: error) #### `bad-import-yang-version` -Issues code when there is an inconsistency between a module's version and the version of the included modules. +Issues code when there is an inconsistency between a module's version and the +version of the included modules. (fixed severity: error) @@ -172,13 +209,15 @@ Issue code indicating that all assigned values in an enumerable must be unique. #### `enumerable-restriction-name` -Issue code indicating that an enumerable introduces a new name that is not declared among the parent restriction. +Issue code indicating that an enumerable introduces a new name that is not +declared among the parent restriction. (default severity: error) #### `enumerable-restriction-value` -Issue code indicating that an enumerable introduces a new value that is not declared among the parent restriction. +Issue code indicating that an enumerable introduces a new value that is not +declared among the parent restriction. (default severity: error) @@ -202,44 +241,51 @@ Controls the indentation string when formatting or serializing yang files. #### `invalid-config` -Issue code when a `config=true` is a child of a `config=false` (see https://tools.ietf.org/html/rfc7950#section-7.21.1) +Issue code when a `config=true` is a child of a `config=false` +(see ) (default severity: error) #### `invalid-augmentation` -Issue code when an augmented node declares invalid sub-statements. For instance when an augmented leaf node has leaf nodes. +Issue code when an augmented node declares invalid sub-statements. For instance, +when an augmented leaf node has leaf nodes. (default severity: error) #### `invalid-default` -Issue code for cases when the a choice has default value and the mandatory sub-statement is "true". +Issue code for cases when the a choice has default value and the mandatory +sub-statement is "true". (default severity: error) #### `mandatory-after-default-case` -Issue code when any mandatory nodes are declared after the default case in a "choice". +Issue code when any mandatory nodes are declared after the default case in a +"choice". (default severity: error) #### `invalid-action-ancestor` -Issue code when an action (or notification) has a "list" ancestor node without a "key" statement. -Also applies, when an action (or notification) is declared within another action, rpc or notification. +Issue code when an action (or notification) has a "list" ancestor node without a +"key" statement. Also applies, when an action (or notification) is declared +within another action, rpc or notification. (default severity: error) #### `identity-cycle` -Issue code when an identity references itself, either directly or indirectly through a chain of other identities. +Issue code when an identity references itself, either directly or indirectly +through a chain of other identities. (default severity: error) #### `leaf-key-with-if-feature` -This issue code is used when a leaf node is declared as a list key and have any "if-feature" statements. +This issue code is used when a leaf node is declared as a list key and have any +"if-feature" statements. (default severity: error) @@ -273,3 +319,5 @@ Diagnostic for unresolvable Xpath expressions. (default severity: ignore) +[1]: ../schema/yang-lsp-settings-schema.json +[2]: ./Extensions.md diff --git a/schema/yang-lsp-settings-schema.json b/schema/yang-lsp-settings-schema.json index 78475f03..a585085f 100644 --- a/schema/yang-lsp-settings-schema.json +++ b/schema/yang-lsp-settings-schema.json @@ -15,11 +15,18 @@ }, "excludePath": { "description": "The path elements are project relative directory names.\nYou can specify multiple elements separated with a colon ':'.\nThe file separator is always '/' independent from the OS", - "type": "string" + "type": "string", + "default": "" }, "yangPath": { "description": "You can specify individual files, directories (contents will be added recursively) or ZIP files.\nThe file name format is OS specific, and so is the path separator (';' on Windows, ':' elsewhere).", - "type": "string" + "type": "string", + "default": "" + }, + "yangIgnorePath": { + "description": "You can specify individual files or directories to be ignored from `yangPath`.\nThe file name format is OS specific, and so is the path separator (';' on Windows, ':' elsewhere).", + "type": "string", + "default": "" }, "extension": { "description": "YANG LSP extension settings", diff --git a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/utils/YangPathProvider.xtend b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/utils/YangPathProvider.xtend index bfddb236..4ae32d21 100644 --- a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/utils/YangPathProvider.xtend +++ b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/utils/YangPathProvider.xtend @@ -21,6 +21,7 @@ import org.eclipse.xtext.util.internal.Log class YangPathProvider { public static val YANG_PATH = new PreferenceKey("yangPath", "") + public static val YANG_IGNORE_PATH = new PreferenceKey("yangIgnorePath", "") @Inject PreferenceValuesProvider preferenceProvider @Inject Provider resourceSetProvider @@ -38,12 +39,20 @@ class YangPathProvider { ] val prefs = preferenceProvider.getPreferenceValues(resource) val yangpath = prefs.getPreference(YANG_PATH) + val yangignorepath = prefs.getPreference(YANG_IGNORE_PATH) + val yangignoredpaths = newArrayList + if (!yangignorepath.isNullOrEmpty) { + Splitter.on(File.pathSeparator).split(yangpath).forEach [ + val file = new File(it) + yangignoredpaths += file + ] + } val resourceDescriptions = newArrayList if (!yangpath.isNullOrEmpty) { val resourceSet = resourceSetProvider.get(); Splitter.on(File.pathSeparator).split(yangpath).forEach [ val file = new File(it) - file.process(resourceSet) + file.process(resourceSet, yangignoredpaths) ] resourceSet.resources.forEach [ try { @@ -57,13 +66,21 @@ class YangPathProvider { return resourceDescriptions } - private def void process(File file, ResourceSet resourceSet) { + private def void process(File file, ResourceSet resourceSet, ArrayList ignoredFiles) { try { if (!file.canRead) return else if (file.isDirectory) + while (ignoredFiles.hasMoreElements) + ignoredfile = ignoredFiles.nextElement + if (file.absolutePath == ignoredfile.absolutePath) + return file.listFiles.forEach[process(resourceSet)] else if (file.name.endsWith('.yang')) + while (ignoredFiles.hasMoreElements) + ignoredfile = ignoredFiles.nextElement + if (file.absolutePath == ignoredfile.absolutePath) + return resourceSet.getResource(URI.createFileURI(file.absolutePath), true) else if (file.name.endsWith('.zip')) { val zipFile = new ZipFile(file)