Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* actual or intended publication of such source code.
*/

import com.genestack.openapi.TemplateSpecification
import com.genestack.openapi.DownloadSpecification
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
import com.genestack.openapi.MergeSpecifications
Expand All @@ -27,13 +28,13 @@ val openApiVersion: String = System.getenv("OPENAPI_VERSION")
val mergedFileName = "odmApi.yaml"
val mergedFilePath = "${sourceDirectory}/${mergedFileName}"

val sourceFileList = KotlinPath(sourceDirectory)
.listDirectoryEntries("*.yaml")
.sorted()
.map { layout.projectDirectory.file("${sourceDirectory}/${it.name}") }

tasks {
val templateSpecs by registering(TemplateSpecification::class) {
inputDir = layout.projectDirectory.file(sourceDirectory)
outputDir = layout.projectDirectory.file(sourceDirectory)
}
val downloadSpec by registering(DownloadSpecification::class) {
dependsOn(templateSpecs)
version.set(processorsControllerVersion)
registryUsername.set(System.getenv("NEXUS_USER"))
registryPassword.set(System.getenv("NEXUS_PASSWORD"))
Expand All @@ -43,7 +44,7 @@ tasks {
}
val mergeSpecifications by registering(MergeSpecifications::class) {
dependsOn(downloadSpec)
inputFiles = sourceFileList
inputDir = layout.projectDirectory.file(sourceDirectory)
outputFile = layout.projectDirectory.file(mergedFilePath)
}
val generateOdmApiPython by registering(GenerateTask::class) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.InputDirectory

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory


abstract class MergeSpecifications : DefaultTask() {

@get:InputFiles
abstract val inputFiles: ListProperty<RegularFile>
@get:InputDirectory
abstract val inputDir: RegularFileProperty

@get:OutputFile
abstract val outputFile: RegularFileProperty

@TaskAction
fun merge() {
val objectMapper = ObjectMapper(YAMLFactory())
val mergedNode = inputFiles
.get().map { it.asFile }
.filterNot { it == outputFile.get().asFile }
val mergedNode = inputDir.get().asFile.listFiles { file ->
!file.name.contains("{Role}") && file.name.endsWith(".yaml")
}.sorted()
.map { objectMapper.readTree(it) }
.reduce { acc, node -> objectMapper.updateValue(acc, node) }
objectMapper.writeValue(outputFile.get().asFile, mergedNode)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.genestack.openapi

import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputDirectory
import java.io.File

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory


abstract class TemplateSpecification : DefaultTask() {

@get:InputDirectory
abstract val inputDir: RegularFileProperty

@get:OutputDirectory
abstract val outputDir: RegularFileProperty

@TaskAction
fun template() {
inputDir.get().asFile.listFiles { file ->
file.name.contains("{Role}") && file.name.endsWith(".yaml")
}.forEach {
writeTemplated(it, "User")
writeTemplated(it, "Curator")
}
}

private fun writeTemplated(spec: File, role: String) {
val content = spec.readText()
val outputContent = StringBuilder()

// Role in lowercase for substitutions
val roleLowercase = role.lowercase()

// Define regex patterns for role matching
val rolePattern = """\{Role=([^}]+)\}""".toRegex()
val sectionStartPattern = """##\s*\{Role=([^}]+)\}""".toRegex()
val sectionEndPattern = """##\s*end\s*\{Role=([^}]+)\}""".toRegex()
val lineCommentPattern = """#\{Role=([^}]+)\}""".toRegex()

// Flag to track if we're inside a conditional section
var insideConditionalSection = false
var shouldIncludeSection = false
var sectionRole = ""

// Process line by line
content.lineSequence().withIndex().forEach { (index, line) ->
// Check if this line is the start of a conditional section
val sectionStartMatch = sectionStartPattern.find(line)
if (sectionStartMatch != null) {
if (insideConditionalSection) {
throw IllegalStateException("Nested conditional sections are not allowed. Error in file: ${spec.name}, line ${index}")
}

insideConditionalSection = true
sectionRole = sectionStartMatch.groupValues[1]
shouldIncludeSection = (sectionRole == role)
// Skip this marker line
return@forEach
}

// Check if this line is the end of a conditional section
val sectionEndMatch = sectionEndPattern.find(line)
if (sectionEndMatch != null) {
if (!insideConditionalSection) {
throw IllegalStateException("Found end marker without start marker. Error in file: ${spec.name}, line ${index}")
}

val endRole = sectionEndMatch.groupValues[1]
if (endRole != sectionRole) {
throw IllegalStateException("Mismatched roles in section markers: $sectionRole vs $endRole. Error in file: ${spec.name}, line ${index}")
}

insideConditionalSection = false
shouldIncludeSection = false
// Skip this marker line
return@forEach
}

// If we're inside a conditional section and shouldn't include it, skip this line
if (insideConditionalSection && !shouldIncludeSection) {
return@forEach
}

// Handle single line conditional comments: #{Role=X}
val lineCommentMatch = lineCommentPattern.find(line)
if (lineCommentMatch != null) {
val commentRole = lineCommentMatch.groupValues[1]
if (commentRole == role) {
// Keep the line but remove the comment
val processedLine = line.replace(lineCommentPattern, "").trimEnd()
outputContent.appendLine(processedLine)
}
// If role doesn't match, skip this line
return@forEach
}

// Process regular lines with substitutions
val processedLine = line.replace("{Role}", role).replace("{role}", roleLowercase)
outputContent.appendLine(processedLine)
}

// Check if we ended with an unclosed section
if (insideConditionalSection) {
throw IllegalStateException("Unclosed conditional section for role: $sectionRole. Error in file: ${spec.name}")
}

// Create output file with appropriate name
val outputFileName = spec.name.replace("{Role}", role)
val outputFile = File(outputDir.get().asFile, outputFileName)
outputFile.parentFile.mkdirs()
outputFile.writeText(outputContent.toString())
}
}
120 changes: 0 additions & 120 deletions openapi/v1/cellCurator.yaml

This file was deleted.

14 changes: 7 additions & 7 deletions openapi/v1/cellUser.yaml → openapi/v1/cell{Role}.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ info:
title: ODM API
version: default-released
tags:
- name: Cells as User
- name: Cells as {Role}
paths:
/api/v1/as-user/cells/{id}:
/api/v1/as-{role}/cells/{id}:
get:
operationId: getCellByIdAsUser
operationId: getCellByIdAs{Role}
parameters:
- description: Unique cell identifier. Consists of a cell group accession and a cell barcode, separated by a hyphen, e.g. `GSF123456-AAACCTGAGCGCTCCA-1`.
in: path
Expand Down Expand Up @@ -45,15 +45,15 @@ paths:
- Genestack-API-Token: [ ]
summary: Retrieve a cell by ID
tags:
- Cells as User
/api/v1/as-user/cells/by/group/{id}:
- Cells as {Role}
/api/v1/as-{role}/cells/by/group/{id}:
get:
description: |+
## Paging
For performance reasons this endpoint returns results in "pages" of limited size together with a `cursor` tag.
To retrieve the next page of results please supply this `cursor` tag to resume the query from your previous result and get the next page.
If there are no more results you will just retrieve an empty result.
operationId: getCellsByGroupAsUser
operationId: getCellsByGroupAs{Role}
parameters:
- description: Unique identifier (accession) of the cell group.
in: path
Expand Down Expand Up @@ -102,7 +102,7 @@ paths:
- Genestack-API-Token: [ ]
summary: Retrieve cells from a given group
tags:
- Cells as User
- Cells as {Role}
components:
schemas:
Cell:
Expand Down
Loading