Skip to content

Commit

Permalink
Port to Scala 3
Browse files Browse the repository at this point in the history
  • Loading branch information
mtomko committed Jan 10, 2024
1 parent 4723212 commit 63ae339
Show file tree
Hide file tree
Showing 78 changed files with 256 additions and 376 deletions.
1 change: 0 additions & 1 deletion .scalafix.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ OrganizeImports {
expandRelative = true
groups = ["re:javax?\\.", "scala.", "*"]
groupedImports = Merge
removeUnused = true
}
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ rewrite.redundantBraces.generalExpressions = false
rewrite.redundantBraces.includeUnitMethods = false
rewrite.redundantBraces.maxBreaks = 16
rewrite.redundantBraces.stringInterpolation = true
runner.dialect = scala213source3
runner.dialect = scala3
15 changes: 3 additions & 12 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ val artifactId = "poolq"

inThisBuild(
List(
scalaVersion := "2.13.11",
scalaVersion := "3.3.1",
semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision,
versionScheme := Some("early-semver")
)
)

lazy val versions = new {
val acyclic = "0.2.1"
val betterFiles = "3.9.2"
val betterMonadicFor = "0.3.1"
val catsEffect3 = "3.5.2"
val cats = "2.10.0"
val commonsIo = "2.15.1"
Expand All @@ -28,15 +26,13 @@ lazy val versions = new {
val scalaCheck = "1.17.0"
val scalaCsv = "1.3.10"
val scalaTest = "3.2.17"
val scalaTestPlusScalaCheck = "3.2.2.0"
val scalaTestPlusScalaCheck = "3.2.17.0"
val scopt = "4.1.0"
val slf4j = "1.7.36"
}

lazy val libraries = new {
val acyclic = "com.lihaoyi" %% "acyclic" % versions.acyclic
val betterFiles = "com.github.pathikrit" %% "better-files" % versions.betterFiles
val betterMonadicFor = "com.olegpy" %% "better-monadic-for" % versions.betterMonadicFor
val cats = "org.typelevel" %% "cats-core" % versions.cats
val catsEffect3 = "org.typelevel" %% "cats-effect" % versions.catsEffect3
val commonsIo = "commons-io" % "commons-io" % versions.commonsIo
Expand All @@ -59,12 +55,11 @@ lazy val libraries = new {
val munitCatsEffect3 = "org.typelevel" %% "munit-cats-effect-3" % versions.munitCatsEffect3
val scalaTest = "org.scalatest" %% "scalatest" % versions.scalaTest
val scalaCheck = "org.scalacheck" %% "scalacheck" % versions.scalaCheck
val scalaTestPlusScalaCheck = "org.scalatestplus" %% "scalacheck-1-14" % versions.scalaTestPlusScalaCheck
val scalaTestPlusScalaCheck = "org.scalatestplus" %% "scalacheck-1-17" % versions.scalaTestPlusScalaCheck
}

lazy val dependencies =
List(
libraries.acyclic % "provided",
libraries.cats,
libraries.commonsIo,
libraries.commonsMath3,
Expand Down Expand Up @@ -130,13 +125,9 @@ lazy val poolq = project
name := "poolq3",
organization := "org.broadinstitute.gpp",
libraryDependencies := dependencies,
scalacOptions ++= List("-P:acyclic:force", "-Xsource:3"),
buildInfoKeys := Seq[BuildInfoKey](name, version),
buildInfoPackage := "org.broadinstitute.gpp.poolq3",
addCompilerPlugin(libraries.acyclic),
addCompilerPlugin(libraries.betterMonadicFor),
testFrameworks += new TestFramework("munit.Framework"),
scalacOptions += "-Yrangepos", // ensure munit clues work
// Tests pass in parallel, but SLF4J logging behaves weirdly. Disable this flag to examine test
// log output; leave this enabled for very fast test execution.
Test / parallelExecution := true
Expand Down
12 changes: 6 additions & 6 deletions src/main/scala/org/broadinstitute/gpp/poolq3/PoolQ.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import java.nio.file.{Files, Path}

import scala.util.{Failure, Success, Try, Using}

import cats.syntax.all._
import cats.syntax.all.*
import org.broadinstitute.gpp.poolq3.PoolQConfig.synthesizeArgs
import org.broadinstitute.gpp.poolq3.barcode.{BarcodePolicy, Barcodes, barcodeSource}
import org.broadinstitute.gpp.poolq3.parser.{BarcodeSet, CloseableIterable, ReferenceData}
Expand Down Expand Up @@ -122,7 +122,7 @@ object PoolQ {
barcodeSource(config.input, rowBarcodePolicy, revRowBarcodePolicyOpt, colBarcodePolicyOrLength, umiInfo.map(_._2))

lazy val unexpectedSequenceCacheDir: Option[Path] =
if (config.skipUnexpectedSequenceReport) None
if config.skipUnexpectedSequenceReport then None
else {
val ret = config.unexpectedSequenceCacheDir.map(Files.createDirectories(_)).orElse {
val ret: Path = Files.createTempDirectory("unexpected-sequence-cache")
Expand All @@ -133,7 +133,7 @@ object PoolQ {
}

val consumer =
if (config.noopConsumer) new NoOpConsumer
if config.noopConsumer then new NoOpConsumer
else
new ScoringConsumer(
rowReference,
Expand All @@ -145,7 +145,7 @@ object PoolQ {
config.isPairedEnd
)

for {
for
runSummary <- runProcess(barcodes, consumer)
state = runSummary.state
counts = state.known
Expand Down Expand Up @@ -195,7 +195,7 @@ object PoolQ {
globalReference
)
.as(UnexpectedSequencesFileType.some)
if (config.removeUnexpectedSequenceCache) {
if config.removeUnexpectedSequenceCache then {
log.info(s"Removing unexpected sequence cache ${config.unexpectedSequenceCacheDir}")
UnexpectedSequenceWriter.removeCache(dir)
}
Expand All @@ -204,7 +204,7 @@ object PoolQ {
_ = log.info(s"Writing run info ${config.output.unexpectedSequencesFile}")
_ <- RunInfoWriter.write(config.output.runInfoFile, config)
_ = log.info("PoolQ complete")
} yield PoolQSummary(runSummary, AlwaysWrittenFiles ++ Set(cfto, usfto).flatten)
yield PoolQSummary(runSummary, AlwaysWrittenFiles ++ Set(cfto, usfto).flatten)
}

def runProcess(barcodes: CloseableIterable[Barcodes], consumer: Consumer): Try[PoolQRunSummary] =
Expand Down
26 changes: 13 additions & 13 deletions src/main/scala/org/broadinstitute/gpp/poolq3/PoolQConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import java.nio.file.{Files, Path, Paths}
import scala.collection.mutable

import cats.data.{NonEmptyList => Nel}
import cats.syntax.all._
import cats.syntax.all.*
import org.broadinstitute.gpp.poolq3.PoolQConfig.DefaultPath
import org.broadinstitute.gpp.poolq3.reports.{GctDialect, PoolQ2Dialect, PoolQ3Dialect, ReportsDialect}
import org.broadinstitute.gpp.poolq3.types.{PoolQException, ReadIdCheckPolicy}
Expand Down Expand Up @@ -42,7 +42,7 @@ final case class PoolQInput(

case (Some(rr), None, Some(cr), None, false) =>
val rs = ReadsSource.Split(Nel(cr, addlColReads), Nel(rr._2, addlRowReads.view.map(_._2).toList))
if (rs.forward.length == rs.index.length) Right(rs)
if rs.forward.length == rs.index.length then Right(rs)
else Left(PoolQException("Number of row, column, and reverse reads files must match"))

case (Some(rr), Some(rrr), Some(cr), None, false) =>
Expand All @@ -51,15 +51,15 @@ final case class PoolQInput(
Nel(rr._2, addlRowReads.view.map(_._2).toList),
Nel(rrr._2, addlReverseRowReads.view.map(_._2).toList)
)
if (rs.forward.length == rs.index.length && rs.forward.length == rs.reverse.length) Right(rs)
if rs.forward.length == rs.index.length && rs.forward.length == rs.reverse.length then Right(rs)
else Left(PoolQException("Number of row and column reads files must match"))

case (Some(rr), None, None, None, true) =>
Right(ReadsSource.Dmuxed(Nel(rr, addlRowReads)))

case (Some(rr), Some(rrr), None, None, true) =>
val rs = ReadsSource.DmuxedPairedEnd(Nel(rr, addlRowReads), Nel(rrr, addlReverseRowReads))
if (rs.read1.map(_._1) == rs.read2.map(_._1)) Right(rs)
if rs.read1.map(_._1) == rs.read2.map(_._1) then Right(rs)
else Left(PoolQException("Row and column reads files must match"))

case _ => Left(PoolQException("Conflicting input options"))
Expand Down Expand Up @@ -147,8 +147,8 @@ object PoolQConfig {

val parser: OptionParser[PoolQConfig] = new OptionParser[PoolQConfig]("poolq") {
private[this] def existsAndIsReadable(f: Path): Either[String, Unit] =
if (!Files.exists(f)) failure(s"Could not find ${f.toAbsolutePath}")
else if (!Files.isReadable(f)) failure(s"Could not read ${f.toAbsolutePath}")
if !Files.exists(f) then failure(s"Could not find ${f.toAbsolutePath}")
else if !Files.isReadable(f) then failure(s"Could not read ${f.toAbsolutePath}")
else success

locally {
Expand Down Expand Up @@ -283,7 +283,7 @@ object PoolQConfig {
.valueName("<number>")
.action((n, c) => c.copy(unexpectedSequencesToReport = n))
.validate { n =>
if (n > 0) success
if n > 0 then success
else failure(s"Unexpected sequence threshold must be greater than 0, got: $n")
}

Expand Down Expand Up @@ -388,7 +388,7 @@ object PoolQConfig {
// run control
args += (("row-matcher", config.rowMatchFn))
args += (("col-matcher", config.colMatchFn))
if (config.countAmbiguous) {
if config.countAmbiguous then {
args += (("count-ambiguous", ""))
}
args += (("row-barcode-policy", config.rowBarcodePolicyStr))
Expand All @@ -397,18 +397,18 @@ object PoolQConfig {
umiInfo.map(_._2).foreach(str => args += (("umi-barcode-policy", str)))

// deal with the unexpected sequence options
if (config.skipUnexpectedSequenceReport) {
if config.skipUnexpectedSequenceReport then {
args += (("skip-unexpected-sequence-report", ""))
} else {
// give whatever path we were given here - this _may_ not need to be included at all, honestly
config.unexpectedSequenceCacheDir.foreach(file => args += (("unexpected-sequence-cache", file.toString)))
args += (("unexpected-sequence-threshold", config.unexpectedSequencesToReport.toString))
}

if (config.skipShortReads) {
if config.skipShortReads then {
args += (("skip-short-reads", ""))
}
if (config.reportsDialect == PoolQ2Dialect) {
if config.reportsDialect == PoolQ2Dialect then {
args += (("compat", ""))
}

Expand All @@ -424,10 +424,10 @@ object PoolQConfig {
output.umiCountsFilesDir.foreach(d => args += (("umi-counts-dir", d.toString)))
output.umiBarcodeCountsFilesDir.foreach(d => args += (("umi-barcode-counts-dir", d.toString)))
}
if (!config.skipUnexpectedSequenceReport) {
if !config.skipUnexpectedSequenceReport then {
args += (("unexpected-sequences", output.unexpectedSequencesFile.getFileName.toString))
}
if (config.alwaysCountColumnBarcodes) {
if config.alwaysCountColumnBarcodes then {
args += (("always-count-col-barcodes", ""))
}
args += (("run-info", output.runInfoFile.getFileName.toString))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ final case class FixedOffsetPolicy(startPos0: Int, length: Int, skipShortReads:
private[this] val endPos0: Int = startPos0 + length

override def find(read: Read): Option[FoundBarcode] =
if (read.seq.length < minLength) {
if (skipShortReads) None
if read.seq.length < minLength then {
if skipShortReads then None
else throw ReadTooShortException(read.seq, startPos0, minLength)
} else Some(FoundBarcode(copyOfRange(read.seq.toCharArray, startPos0, endPos0), startPos0))

Expand Down Expand Up @@ -83,9 +83,9 @@ final case class IndexOfKnownPrefixPolicy(
override def find(read: Read): Option[FoundBarcode] = {
val index = read.seq.indexOf(prefix, minPrefixStartPosInt)
val barcodeStart = index + prefixLength
if (index < 0) None
else if (index > maxPrefixStartPosInt) None
else if ((read.seq.length - barcodeStart) < length) None
if index < 0 then None
else if index > maxPrefixStartPosInt then None
else if (read.seq.length - barcodeStart) < length then None
else Some(FoundBarcode(copyOfRange(read.seq.toCharArray, barcodeStart, barcodeStart + length), barcodeStart))
}

Expand Down Expand Up @@ -141,15 +141,15 @@ object TemplatePolicy {
case Regex1(ctx, minStr, maxStr) =>
ctx match {
case Regex2(p1, b1, gap, p2, b2) =>
if ((b1.length + b2.length) != refBarcodeLength) {
if (b1.length + b2.length) != refBarcodeLength then {
throw new IllegalArgumentException(s"$s is not compatible with the provided reference file")
}
val min = Option(minStr).map(_.toInt)
val max = Option(maxStr).map(_.tail.toInt)
SplitBarcodePolicy(p1.toUpperCase, b1.length, gap.length, p2.toUpperCase, b2.length, min, max)
case _ =>
val km = KeyMask(ctx)
if (km.keyLengthInBases != refBarcodeLength) {
if km.keyLengthInBases != refBarcodeLength then {
throw new IllegalArgumentException(s"$s is not compatible with the provided reference file")
}
val min = Option(minStr).map(_.toInt)
Expand Down Expand Up @@ -200,8 +200,8 @@ object TemplatePolicy {

final def satisfies(template: Array[Char], seq: String, seqOffset: Int): Boolean = {
var i = 0
while (i < template.length) {
if (!compatible(template(i), seq(seqOffset + i))) return false
while i < template.length do {
if !compatible(template(i), seq(seqOffset + i)) then return false
i += 1
}
true
Expand Down Expand Up @@ -230,8 +230,8 @@ final case class GeneralTemplatePolicy(template: KeyMask, minStartPos: Option[In

@tailrec
def find(i: Int): Option[Int] =
if (i > maxPos) None
else if (TemplatePolicy.satisfies(templateChars, read.seq, i)) Some(i)
if i > maxPos then None
else if TemplatePolicy.satisfies(templateChars, read.seq, i) then Some(i)
else find(i + 1)

find(minStartPosInt).map(i => extract(read, i))
Expand Down Expand Up @@ -273,13 +273,13 @@ final case class SplitBarcodePolicy(
@tailrec
def loop(start: Int): Option[FoundBarcode] = {
val e = math.min(maxPrefix1StartPosInt, read.seq.length - patternLength)
if (start > e) None
if start > e then None
else {
indexOf(prefix1, read.seq, start, e) match {
case None => None
case Some(p1Index) =>
val p2Index = p1Index + expectedP2Offset
if (matches(prefix2, read.seq, p2Index)) {
if matches(prefix2, read.seq, p2Index) then {
val dest = Array.ofDim[Char](length)
// copy in the the barcodes
read.seq.getChars(p1Index + p1Length, p1Index + p1Length + b1Length, dest, 0)
Expand All @@ -306,10 +306,10 @@ object SplitBarcodePolicy {
final private[barcode] def indexOf(needle: String, haystack: String, start: Int, end: Int): Option[Int] = {
@tailrec
def loop(i: Int): Option[Int] =
if (i > math.min(haystack.length - needle.length, end)) {
if i > math.min(haystack.length - needle.length, end) then {
None
} else {
if (matches(needle, haystack, i)) Some(i)
if matches(needle, haystack, i) then Some(i)
else loop(i + 1)
}
loop(start)
Expand All @@ -318,9 +318,9 @@ object SplitBarcodePolicy {
final private def matches(needle: String, haystack: String, haystackOffset: Int): Boolean = {
@tailrec
def loop(i: Int): Boolean =
if (i >= needle.length) true
if i >= needle.length then true
else {
if (needle(i) != haystack(haystackOffset + i)) false
if needle(i) != haystack(haystackOffset + i) then false
else loop(i + 1)
}
loop(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ object KeyMask {
mergedRanges.length match {
case 1 =>
val r = mergedRanges.head
if (r.start0 == 0 && r.length == pattern.length)
KeyMask0(pattern)
if r.start0 == 0 && r.length == pattern.length then KeyMask0(pattern)
else KeyMask1(pattern, r)
case 2 =>
KeyMask2(pattern, mergedRanges(0), mergedRanges(1))
Expand All @@ -64,8 +63,7 @@ object KeyMask {
acc match {
case Nil => current :: Nil
case head :: tail =>
if (head.end0 >= current.start0 - 1)
KeyRange(head.start0, current.end0) :: tail
if head.end0 >= current.start0 - 1 then KeyRange(head.start0, current.end0) :: tail
else current :: acc
}
// but use an IndexedSeq for efficiency later
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final case class KeyRange(start0: Int, end0: Int) {

def end1: Int = end0 + 1

override def toString: String = if (length == 1) start1.toString else s"$start1..$end1"
override def toString: String = if length == 1 then start1.toString else s"$start1..$end1"

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ final class KnuthMorrisPratt(word: String) {
val wordLength = word.length
var m = fromIndex
var i = 0
while ((m + i) < toIndex) {
if (word.charAt(i) == text.charAt(m + i)) {
if (i == (wordLength - 1)) return Some(m)
while (m + i) < toIndex do {
if word.charAt(i) == text.charAt(m + i) then {
if i == (wordLength - 1) then return Some(m)
i += 1
} else {
val fi = f(i)
if (fi > -1) {
if fi > -1 then {
m = m + i - fi
i = fi
} else {
Expand Down Expand Up @@ -51,12 +51,12 @@ object KnuthMorrisPratt {
var wi = 2 // the word index
var si = 0 // the substring index

while (wi < word.length) {
if (word(wi - 1) == word(si)) {
while wi < word.length do {
if word(wi - 1) == word(si) then {
f(wi) = si + 1
si = si + 1
wi = wi + 1
} else if (si > 0) {
} else if si > 0 then {
si = f(si)
} else {
f(wi) = 0
Expand Down
Loading

0 comments on commit 63ae339

Please sign in to comment.