Skip to content

Commit

Permalink
Accept empty versions
Browse files Browse the repository at this point in the history
  • Loading branch information
alexarchambault committed Dec 5, 2024
1 parent 8090e77 commit c42fdff
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 12 deletions.
12 changes: 10 additions & 2 deletions dependency/shared/src/main/scala/dependency/DependencyLike.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ final case class DependencyLike[+A <: NameAttributes, +E <: NameAttributes](
.mkString
private def paramsString: String =
excludeString ++ userParamsString
def render: String =
s"${module.render}:$version$paramsString"
def render: String = {
val b = new StringBuilder
b ++= module.render
if (version.nonEmpty) {
b += ':'
b ++= version
}
b ++= paramsString
b.result()
}
override def toString: String =
render
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ object DependencyParser {
case (module, remaining) =>
val (version, configOpt, params) = splitRemainingPart(remaining, acceptInlineConfiguration)
assert(acceptInlineConfiguration || configOpt.isEmpty)
val version0 =
if (acceptInlineConfiguration && version == " ") ""
else version

val (excludeParams, remainingParams) = params.partition(_.startsWith("exclude="))
val maybeExclusions = excludeParams
Expand All @@ -38,21 +41,18 @@ object DependencyParser {
}

for {
_ <- ModuleParser.validateValue(version, "version")
_ <- ModuleParser.validateValue(version0, "version")
exclusions <- maybeExclusions
} yield {
val userParams = remainingParams.iterator.map(parseParam).toSeq ++ configOpt.toSeq.map("$inlineConfiguration" -> Some(_))
DependencyLike(module, version, exclusions, userParams)
DependencyLike(module, version0, exclusions, userParams)
}
}

private def attrSeparator = ","
private def argSeparator = ":"

private def splitRemainingPart(input: String, acceptInlineConfiguration: Boolean): (String, Option[String], Seq[String]) = {

def simpleSplit(s: String): (String, Seq[String]) =
s.split(attrSeparator) match {
s.split(ModuleParser.paramSeparator) match {
case Array(coordsEnd, attrs @ _*) => (coordsEnd, attrs)
}

Expand All @@ -72,7 +72,7 @@ object DependencyParser {
}

if (acceptInlineConfiguration)
versionPart.split(argSeparator, 2) match {
versionPart.split(ModuleParser.argSeparator, 2) match {
case Array(ver, config) => (ver, Some(config), attrs0)
case Array(ver) => (ver, None, attrs0)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package parser

object ModuleParser {

private[parser] def paramSeparator = ","
private[parser] def argSeparator = ":"

private implicit class EitherWithFilter[L, R](private val e: Either[L, R]) extends AnyVal {
def withFilter(f: R => Boolean): Either[L, R] =
if (e.forall(f)) e else throw new MatchError(e)
Expand All @@ -19,7 +22,7 @@ object ModuleParser {
*/
def parse(input: String): Either[String, AnyModule] = {

val parts = input.split(":", -1).map(Some(_).filter(_.nonEmpty))
val parts = input.split(argSeparator, -1).map(Some(_).filter(_.nonEmpty))

val values = parts match {
case Array(Some(org), Some(name)) => Right((org, name, NoAttributes))
Expand All @@ -40,7 +43,11 @@ object ModuleParser {

def parsePrefix(input: String): Either[String, (AnyModule, String)] = {

val parts = input.split(":", -1).map(Some(_).filter(_.nonEmpty))
val (input0, paramsOpt) = input.split(paramSeparator, 2) match {
case Array(input0) => (input0, None)
case Array(input0, params) => (input0, Some(params))
}
val parts = input0.split(argSeparator, -1).map(Some(_).filter(_.nonEmpty))

val values = parts match {
case Array(Some(org), Some(name), rest @ _*) => Right((org, name, NoAttributes, rest))
Expand All @@ -56,7 +63,7 @@ object ModuleParser {
_ <- validateValue(org, "organization")
(name, attributes) <- parseNamePart(name0)
_ <- validateValue(name, "module name")
} yield (ModuleLike(org, name, nameAttributes, attributes), rest.map(_.getOrElse("")).mkString(":"))
} yield (ModuleLike(org, name, nameAttributes, attributes), rest.map(_.getOrElse("")).mkString(argSeparator) + paramsOpt.fold("")(paramSeparator + _))
}

private def parseNamePart(input: String): Either[String, (String, Map[String, String])] = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package dependency
package parser

import com.eed3si9n.expecty.Expecty.expect

class DependencyParserNoVersionTests extends munit.FunSuite {

test("simple") {
val res = DependencyParser.parse("org:name")
val expected = Right(Dependency("org", "name", ""))
expect(res == expected)
}

test("simple with colon") {
val res = DependencyParser.parse("org:name:")
val expected = Right(Dependency("org", "name", ""))
expect(res == expected)
}

test("scala") {
val res = DependencyParser.parse("org::name")
val expected = Right(ScalaDependency("org", "name", ""))
expect(res == expected)
}

test("scala full cross-version") {
val res = DependencyParser.parse("org:::name")
val expected = Right(ScalaDependency(ScalaModule("org", "name", fullCrossVersion = true), ""))
expect(res == expected)
}

test("scala platform") {
val res = DependencyParser.parse("org::name::")
val expected = Right(ScalaDependency(ScalaModule("org", "name", fullCrossVersion = false, platform = true), ""))
expect(res == expected)
}

test("scala with attributes") {
val res = DependencyParser.parse("org::name;scala=2.12;sbt=1.0")
val expected = Right(ScalaDependency(ScalaModule("org", "name").copy(attributes = Map("scala" -> "2.12", "sbt" -> "1.0")), ""))
expect(res == expected)
}

test("attributes") {
val res = DependencyParser.parse("org:name;scala=2.12;sbt=1.0")
val expected = Right(Dependency(Module("org", "name").copy(attributes = Map("scala" -> "2.12", "sbt" -> "1.0")), ""))
expect(res == expected)
}

test("exclude") {
val res = DependencyParser.parse("org:name,exclude=fu%ba")
val expected = Right(Dependency("org", "name", "").copy(exclude = CovariantSet(Module("fu", "ba"))))
pprint.err.log(res)
pprint.err.log(expected)
expect(res == expected)
}

test("scala exclude") {
val res = DependencyParser.parse("org:name,exclude=fu%%ba")
val expected = Right(Dependency("org", "name", "").copy(exclude = CovariantSet(ScalaModule("fu", "ba"))))
expect(res == expected)
}

test("several exclude") {
val res = DependencyParser.parse("org:name,exclude=fu%ba,exclude=aa%%aa-1")
val expected = Right(Dependency("org", "name", "").copy(exclude = CovariantSet(Module("fu", "ba"), ScalaModule("aa", "aa-1"))))
expect(res == expected)
}

test("param") {
val res = DependencyParser.parse("org:name,something=ba")
val expected = Right(Dependency("org", "name", "").copy(userParams = Seq("something" -> Some("ba"))))
expect(res == expected)
}

test("no-value param") {
val res = DependencyParser.parse("org:name,something")
val expected = Right(Dependency("org", "name", "").copy(userParams = Seq("something" -> None)))
expect(res == expected)
}

test("multiple same key params") {
val res = DependencyParser.parse("org:name,something=a,something,something=b")
val expected = Right(Dependency("org", "name", "").copy(
userParams = Seq(
"something" -> Some("a"),
"something" -> None,
"something" -> Some("b")
)
))
expect(res == expected)
}

test("scala + params + exclusions") {
val res = DependencyParser.parse("org:::name::,intransitive,exclude=foo%*,exclude=comp%%*,url=aaaa")
val expected = Right(
ScalaDependency(ScalaModule("org", "name", fullCrossVersion = true, platform = true), "")
.copy(
exclude = CovariantSet(
Module("foo", "*"),
ScalaModule("comp", "*")
),
userParams = Seq(
"intransitive" -> None,
"url" -> Some("aaaa")
)
)
)
expect(res == expected)
}

test("inline config") {
val res = DependencyParser.parse("org:name: :runtime")
val expected = Right(
Dependency("org", "name", "").copy(
userParams = Seq(
"$inlineConfiguration" -> Some("runtime")
)
)
)
expect(res == expected)
}
test("inline config with param") {
val res = DependencyParser.parse("org:name: :runtime,something=ba")
val expected = Right(
Dependency("org", "name", "").copy(
userParams = Seq(
"something" -> Some("ba"),
"$inlineConfiguration" -> Some("runtime")
)
)
)
expect(res == expected)
}

test("reject slash in org") {
val res = DependencyParser.parse("o/rg::name")
expect(res.isLeft)
}
test("reject backslash in org") {
val res = DependencyParser.parse("o\\rg::name")
expect(res.isLeft)
}
test("reject slash in name") {
val res = DependencyParser.parse("org::/name")
expect(res.isLeft)
}
test("reject backslash in name") {
val res = DependencyParser.parse("org::\\name")
expect(res.isLeft)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,11 @@ class DependencyParserTests extends munit.FunSuite {
val res = DependencyParser.parse("org::name:1.\\2")
expect(res.isLeft)
}

test("no version") {
val res = DependencyParser.parse("org:name")
val expected = Right(Dependency("org", "name", ""))
expect(res == expected)
}

}

0 comments on commit c42fdff

Please sign in to comment.