Skip to content

Commit

Permalink
GH-4 Working on the workspace configurations usage
Browse files Browse the repository at this point in the history
Signed-off-by: Uladzislau <[email protected]>
  • Loading branch information
KUGDev committed Oct 23, 2024
1 parent e8e1ef1 commit d1e67bd
Show file tree
Hide file tree
Showing 12 changed files with 774 additions and 71 deletions.
5 changes: 5 additions & 0 deletions .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ plugins {

group = properties("pluginGroup").get()
version = properties("pluginVersion").get()
val gsonVersion = "2.11.0"
val kotestVersion = "5.9.1"
val mockkVersion = "1.13.12"
val junitVersion = "1.10.3"
Expand All @@ -40,10 +41,13 @@ intellij {
// pluginsRepositories {
// custom("https://plugins.jetbrains.com/plugins/nightly/23257")
// }
plugins.set(listOf("org.jetbrains.plugins.textmate", "com.redhat.devtools.lsp4ij:0.4.0"))
plugins.set(listOf("org.jetbrains.plugins.textmate", "com.redhat.devtools.lsp4ij:0.7.0"))
}

dependencies {
// ===== Runtime env stup ===
// Gson
implementation("com.google.code.gson:gson:$gsonVersion")
// ===== Test env setup =====
// Kotest
testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
Expand Down
47 changes: 47 additions & 0 deletions src/main/kotlin/org/zowe/cobol/Sections.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2024 IBA Group.
*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBA Group
* Zowe Community
*/

package org.zowe.cobol

/**
* Various config section options that could be found across configuration files
* @property section the section string representation
*/
enum class Sections(val section: String) {
DIALECTS_SECTION("cobol-lsp.dialects"),
DIALECT_REGISTRY("cobol-lsp.dialect.registry"),
DIALECT_LIBS("cobol-lsp.dialect.libs"),
CPY_SECTION("cobol-lsp.cpy-manager"),
CPY_LOCAL_PATH("cobol-lsp.cpy-manager.paths-local"),
CPY_EXTENSIONS("cobol-lsp.cpy-manager.copybook-extensions"),
CPY_FILE_ENCODING("cobol-lsp.cpy-manager.copybook-file-encoding"),
SQL_BACKEND("cobol-lsp.target-sql-backend"),
COMPILER_OPTIONS("cobol-lsp.compiler.options"),
LOGGIN_LEVEL_ROOT("cobol-lsp.logging.level.root"),
LOCALE("cobol-lsp.locale"),
COBOL_PROGRAM_LAYOUT("cobol-lsp.cobol.program.layout"),
SUBROUTINE_LOCAL_PATH("cobol-lsp.subroutine-manager.paths-local"),
CICS_TRANSLATOR("cobol-lsp.cics.translator"),
UNRECOGNIZED("unrecognized-section");

companion object {
operator fun invoke(section: String): Sections {
return entries.find { it.section == section } ?: UNRECOGNIZED
}
}

override fun toString(): String {
return section
}
}
70 changes: 70 additions & 0 deletions src/main/kotlin/org/zowe/cobol/VSCodeSettingsAdapterService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2024 IBA Group.
*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBA Group
* Zowe Community
*/

package org.zowe.cobol

import com.google.gson.JsonParser
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import java.io.File
import java.io.FileNotFoundException
import java.io.FileReader
import java.nio.file.Path

/** VS Code settings adapter service. Needed to recognize configs in .vscode folder */
@Service
class VSCodeSettingsAdapterService {

companion object {
fun getService(): VSCodeSettingsAdapterService = service()
}

/**
* Read the .vscode/settings.json file section
* @param workspaceFolder the workspace folder path to search for the file in
* @param readBlock the function to handle the read section
* @return the result of the read section handling or error if file is not found
*/
private fun <T : Any> readConfigFileSection(workspaceFolder: Path, readBlock: (FileReader) -> T?): T? {
return try {
val settingsPath = workspaceFolder.resolve(".vscode").resolve("settings.json")
val settingsFile = File(settingsPath.toUri())
FileReader(settingsFile).use(readBlock)
} catch (e: FileNotFoundException) {
// TODO: logger
// println("settings.json file not found")
null
}
}

/**
* Read the .vscode/settings.json file section and expect to return a list of strings as a result
* @param workspaceFolder the workspace folder path to search for the file in
* @param section the section to read
* @return the list of strings, read from the section, or empty list on failure or if there are no items
*/
fun getListOfStringsConfiguration(workspaceFolder: Path, section: Sections): List<String> {
return readConfigFileSection(workspaceFolder) { settingsJsonReader ->
val settingsJsonElement = JsonParser.parseReader(settingsJsonReader)
if (settingsJsonElement.isJsonObject) {
val settingsJsonObject = settingsJsonElement.asJsonObject
val settingsDialectJsonArray = settingsJsonObject.get(section.toString())?.asJsonArray
settingsDialectJsonArray?.map { it.asString } ?: listOf()
} else {
listOf()
}
} ?: listOf()
}

}
223 changes: 223 additions & 0 deletions src/main/kotlin/org/zowe/cobol/lsp/CobolConfigsRecognitionService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
* Copyright (c) 2024 IBA Group.
*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBA Group
* Zowe Community
*/

package org.zowe.cobol.lsp

import com.google.gson.*
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import org.eclipse.lsp4j.ConfigurationItem
import org.zowe.cobol.findFilesInWorkspace
import java.io.File
import java.lang.reflect.Type
import java.net.URI
import java.nio.file.*

private const val PROCESSOR_GROUP_FOLDER = ".cobolplugin"
private const val PROCESSOR_GROUP_PGM = "pgm_conf.json"
private const val PROCESSOR_GROUP_PROC = "proc_grps.json"
private val COBOL_EXTENSTIONS = listOf(".CBL", ".COB", ".COBOL")

/** Service to handle COBOL-related configs */
@Service
class CobolConfigsRecognitionService {

companion object {
fun getService(): CobolConfigsRecognitionService = service()
}

/**
* Preprocessor description item class
* @property name the name of the preprocessor
* @property libs the optional preprocessor libs
*/
private data class PreprocessorItem(val name: String, val libs: List<String> = emptyList())

/**
* Processor group class to represent processor groups
* @property name the name of the processor group
* @property libs the optional libs to be used
* @property preprocessor the optional preprocessors list to be used with the processor group
*/
private data class ProcessorGroup(
val name: String,
val libs: List<String> = emptyList(),
val preprocessor: List<PreprocessorItem> = emptyList()
)

/**
* Processor groups container class
* @property pgroups the processor groups list
*/
private data class PGroupContainer(val pgroups: List<ProcessorGroup>)

/** Class to provide a deserialization mechanism for processor groups */
private class ProcessorGroupDeserializer : JsonDeserializer<ProcessorGroup> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ProcessorGroup {
val jsonObject = json.asJsonObject

val name = jsonObject.get("name").asString
val libs = jsonObject.getAsJsonArray("libs")?.map { it.asString } ?: emptyList()

val preprocessor = when {
jsonObject.has("preprocessor") -> {
val preprocessorElement = jsonObject.get("preprocessor")
if (preprocessorElement.isJsonArray) {
preprocessorElement.asJsonArray.map { parsePreprocessorItem(it) }
} else {
listOf(parsePreprocessorItem(preprocessorElement))
}
}
else -> emptyList()
}

return ProcessorGroup(name, libs, preprocessor)
}

/**
* Parse preprocessor item from JSON. It is either a string or an object
* @param element the [JsonElement] to parse preprocessor item from
* @return parsed and formed [PreprocessorItem] instance
*/
private fun parsePreprocessorItem(element: JsonElement): PreprocessorItem {
return if (element.isJsonPrimitive && element.asJsonPrimitive.isString) {
PreprocessorItem(name = element.asString)
} else {
val jsonObject = element.asJsonObject
val name = jsonObject.get("name").asString
val libs = jsonObject.getAsJsonArray("libs")?.map { it.asString } ?: emptyList()
PreprocessorItem(name = name, libs = libs)
}
}
}

/**
* Program config item class to represent a single COBOL program configuration
* @param program a name or a wildcard of a file to be considered as the main program (open code)
* @param pgroup name of a processor group as defined in proc_grps.json
*/
private data class ProgramItem(val program: String, val pgroup: String)

/**
* Programs config class to represent program configuration options for the COBOL programs
* @param pgms a list of main programs
*/
private data class ProgramContainer(val pgms: List<ProgramItem>)

// TODO: doc
// TODO: recheck functionality correctness
private fun matchProcessorGroup(pgmCfg: ProgramContainer, documentPathStr: String, workspaceFolder: Path): String? {
val documentPath = Paths.get(documentPathStr)
val relativeDocPath = workspaceFolder.relativize(documentPath)

val candidates = mutableListOf<String>()

for ((program, pgroup) in pgmCfg.pgms) {
val programPath = try {
Paths.get(program)
} catch (e: InvalidPathException) {
null
}
// exact match
if (programPath != null && programPath.isAbsolute) {
if (
programPath == documentPath
|| programPath.toString().uppercase() == documentPath.toString().uppercase()
) {
return pgroup
}
} else {
if (relativeDocPath == programPath) {
candidates.add(pgroup)
}
}

val relativeDocPathIgnoreCase = Paths.get(relativeDocPath.toString().uppercase())
val matcher = FileSystems.getDefault().getPathMatcher("glob:${program.uppercase()}")
if (matcher.matches(relativeDocPathIgnoreCase)) {
candidates.add(pgroup)
}
}

return candidates.getOrNull(0)
}

// TODO: doc
private fun loadProcessorsConfig(workspaceFolder: Path, documentUri: URI): ProcessorGroup? {
val documentPath = Paths.get(documentUri).toString()
val cfgPath = workspaceFolder.resolve(PROCESSOR_GROUP_FOLDER)
val procCfgPath = cfgPath.resolve(PROCESSOR_GROUP_PROC)
val pgmCfgPath = cfgPath.resolve(PROCESSOR_GROUP_PGM)
if (!Files.exists(pgmCfgPath) || !Files.exists(procCfgPath)) {
return null
}
val procCfgFile = File(procCfgPath.toUri()).readText()
val procCfgGson = GsonBuilder()
.registerTypeAdapter(ProcessorGroup::class.java, ProcessorGroupDeserializer())
.create()
val procCfg = procCfgGson.fromJson(procCfgFile, PGroupContainer::class.java)

val pgmCfgFile = File(pgmCfgPath.toUri()).readText()
val pgmCfgGson = Gson()
val pgmCfg = pgmCfgGson.fromJson(pgmCfgFile, ProgramContainer::class.java)

val pgroup = matchProcessorGroup(pgmCfg, documentPath, workspaceFolder)

val result = procCfg.pgroups.find { p -> pgroup == p.name }
return result
}

// TODO: doc
fun loadProcessorGroupDialectConfig(
workspaceFolder: Path,
item: ConfigurationItem,
configObject: List<String>
): List<String> {
return try {
// "SQL" is not a real dialect, we will use it only to set up sql backend for now
loadProcessorsConfig(workspaceFolder, URI(item.scopeUri))
?.preprocessor
?.map { (name, _) -> name }
?.filter { name -> name != "SQL" }
?.ifEmpty { configObject }
?: configObject
} catch (e: Exception) {
println(e)
configObject
}
}

// TODO: doc
fun loadProcessorGroupCopybookPathsConfig(
workspaceFolder: Path,
item: ConfigurationItem,
configObject: List<String>
): List<String> {
val copybookPaths = loadProcessorsConfig(workspaceFolder, URI(item.scopeUri))
?.libs
?.ifEmpty { configObject }
?: configObject
return findFilesInWorkspace(copybookPaths, workspaceFolder)
}

// TODO: doc
// TODO: implement
// fun resolveSubroutineURI(project: Project, name: String): String {
// val folders = VSCodeSettingsAdapterService
// .getService(project)
// .getListOfStringsConfiguration(Sections.SUBROUTINE_LOCAL_PATH)
// return searchCopybookInWorkspace(project, name, folders, COBOL_EXTENSTIONS)
// }

}
Loading

0 comments on commit d1e67bd

Please sign in to comment.