diff --git a/build.gradle.kts b/build.gradle.kts index 44cb3be69..584855dd7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -134,7 +134,7 @@ val toObfuscate: Configuration by configurations.creating { val obfuscationLibs: Configuration by configurations.creating -val mixcrAlgoVersion = "4.6.0-46-reduceToCommonAssembleFeature" +val mixcrAlgoVersion = "4.6.0-50-rework_analyze" // may be blank (will be inherited from mixcr-algo) val milibVersion = "" // may be blank (will be inherited from mixcr-algo or milib) diff --git a/src/main/kotlin/com/milaboratory/mixcr/cli/CommandAlign.kt b/src/main/kotlin/com/milaboratory/mixcr/cli/CommandAlign.kt index 508c8e637..28eba5679 100644 --- a/src/main/kotlin/com/milaboratory/mixcr/cli/CommandAlign.kt +++ b/src/main/kotlin/com/milaboratory/mixcr/cli/CommandAlign.kt @@ -756,6 +756,16 @@ object CommandAlign { get() = MiXCRMixinCollection.empty + pipelineMixins + alignMixins + refineTagsAndSortMixins + assembleMixins + assembleContigsMixins + exportMixins + genericMixins + qcMixins + /** + * Prefix used by `com.milaboratory.mixcr.presets.MiXCRCommandDescriptor.align.outputName` + */ + @Option( + names = ["--output-name-suffix"], + hidden = true, + arity = "0..1" + ) + var outputPrefix: String? = null + @Parameters( index = "0", arity = "2..5", @@ -1340,31 +1350,29 @@ object CommandAlign { private fun alignedWriter(outputFile: Path): Writers? = when (outputFile.toString()) { "." -> null - else -> { - object : Writers() { - private val lock = Any() - private val sampleNameWriter = outputFileList?.bufferedWriter( - Charset.defaultCharset(), DEFAULT_BUFFER_SIZE, - StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING - ) + else -> object : Writers() { + private val lock = Any() + private val sampleNameWriter = outputFileList?.bufferedWriter( + Charset.defaultCharset(), DEFAULT_BUFFER_SIZE, + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING + ) - override fun writerFactory(sample: List) = run { - val sampleName = addSampleToFileName(outputFile.fileName.toString(), sample) - synchronized(lock) { - sampleNameWriter?.appendLine(sampleName) - } - VDJCAlignmentsWriter( - outputFile.resolveSibling(sampleName), - concurrencyLimiter, VDJCAlignmentsWriter.DEFAULT_ALIGNMENTS_IN_BLOCK, highCompression - ) + override fun writerFactory(sample: List) = run { + val sampleName = addSampleToFileName(outputPrefix ?: "", outputFile.fileName.toString(), sample) + synchronized(lock) { + sampleNameWriter?.appendLine(sampleName) } + VDJCAlignmentsWriter( + outputFile.resolveSibling(sampleName), + concurrencyLimiter, VDJCAlignmentsWriter.DEFAULT_ALIGNMENTS_IN_BLOCK, highCompression + ) + } - override fun close() { - try { - sampleNameWriter?.close() - } finally { - super.close() - } + override fun close() { + try { + sampleNameWriter?.close() + } finally { + super.close() } } } @@ -1427,35 +1435,45 @@ object CommandAlign { } } - private fun toPrefixAndExtension(seedFileName: String): Pair { + private fun toPrefixAndExtension(outputNameSuffix: String, seedFileName: String): Pair { val dotIndex = seedFileName.lastIndexOf('.') - return seedFileName.substring(0, dotIndex) to seedFileName.substring(dotIndex) + return seedFileName.substring(0, dotIndex).removeSuffix(outputNameSuffix) to seedFileName.substring(dotIndex) } // Consistent with com.milaboratory.mixcr.presets.MiXCRCommandDescriptor.align.outputName - fun addSampleToFileName(seedFileName: String, sample: List): String { + fun addSampleToFileName(outputNameSuffix: String, seedFileName: String, sample: List): String { val sampleName = listToSampleName(sample) - val (prefix, extension) = toPrefixAndExtension(seedFileName) - val insert = if (sampleName == "") "" else ".$sampleName" - return prefix + insert + extension + val (prefix, extension) = toPrefixAndExtension(outputNameSuffix, seedFileName) + var insert = sampleName + if (insert.isNotBlank()) { + if (prefix.isNotBlank() && !prefix.endsWith(".")) + insert = ".$insert" + if (outputNameSuffix.isNotBlank() && !outputNameSuffix.startsWith(".")) + insert = "$insert." + } + return prefix + insert + outputNameSuffix + extension } data class FileAndSample(val fileName: String, val sample: String) /** Opposite operation to [addSampleToFileName] */ - fun listSamplesForSeedFileName(seedFileName: String, fileNames: List): List { - val (prefix, extension) = toPrefixAndExtension(seedFileName) + fun listSamplesForSeedFileName( + outputNameSuffix: String, + seedFileName: String, + fileNames: List + ): List { + val (prefix, extension) = toPrefixAndExtension(outputNameSuffix, seedFileName) val result = mutableListOf() var emptySampleDetected = false fileNames.map { name -> - require(name.startsWith(prefix) && name.endsWith(extension)) + require(name.startsWith(prefix) && name.endsWith(outputNameSuffix + extension)) if (emptySampleDetected) throw IllegalArgumentException("Unexpected file sequence.") - var sample = name.removePrefix(prefix).removeSuffix(extension) + var sample = name.removePrefix(prefix).removeSuffix(outputNameSuffix + extension) if (sample == "") emptySampleDetected = true else - sample = sample.removePrefix(".") + sample = sample.removePrefix(".").removeSuffix(".") result += FileAndSample(name, sample) } return result diff --git a/src/main/kotlin/com/milaboratory/mixcr/cli/CommandAnalyze.kt b/src/main/kotlin/com/milaboratory/mixcr/cli/CommandAnalyze.kt index 1eea05866..e2399bdac 100644 --- a/src/main/kotlin/com/milaboratory/mixcr/cli/CommandAnalyze.kt +++ b/src/main/kotlin/com/milaboratory/mixcr/cli/CommandAnalyze.kt @@ -365,8 +365,14 @@ object CommandAnalyze { if (outputNoUsedReads) pathsForNotAligned.fillWithDefaults(inputFileGroups.inputType, outputFolder, outputNamePrefix) planBuilder.addStep(AnalyzeCommandDescriptor.align) { _, _, _ -> - listOf("--preset", presetName) + extraAlignArgs + - mixins.flatMap { it.cmdArgs } + pathsForNotAligned.argsForAlign() + buildList { + this += listOf("--preset", presetName) + this += extraAlignArgs + this += mixins.flatMap { it.cmdArgs } + this += pathsForNotAligned.argsForAlign() + if (outputNamePrefix.isBlank()) + this += listOf("--output-name-suffix", "alignments") + } } planBuilder.executeSteps(dryRun) @@ -447,7 +453,7 @@ object CommandAnalyze { fun setActualAlignOutputs(fileNames: List) { val outputSeed = Path(nextInputs.requireSingleton().fileNames.requireSingleton()).name - val samples = listSamplesForSeedFileName(outputSeed, fileNames) + val samples = listSamplesForSeedFileName(if (outputNamePrefix.isBlank()) "alignments" else "", outputSeed, fileNames) nextInputs = samples.map { InputFileSet(it.sample, listOf(outputFolder.resolve(it.fileName).toString())) }