coursier.maven.Pom.scala Maven / Gradle / Ivy
The newest version!
package coursier.maven
import coursier.core._
import scalaz._
object Pom {
import coursier.util.Xml._
def property(elem: Node): String \/ (String, String) = {
// Not matching with Text, which fails on scala-js if the property value has xml comments
if (elem.isElement) \/-(elem.label -> elem.textContent)
else -\/(s"Can't parse property $elem")
}
// TODO Allow no version in some contexts
private def module(node: Node, groupIdIsOptional: Boolean = false): String \/ Module = {
for {
organization <- {
val e = text(node, "groupId", "Organization")
if (groupIdIsOptional) e.orElse(\/-(""))
else e
}
name <- text(node, "artifactId", "Name")
} yield Module(organization, name, Map.empty).trim
}
private def readVersion(node: Node) =
text(node, "version", "Version").getOrElse("").trim
private val defaultType = "jar"
private val defaultClassifier = ""
def dependency(node: Node): String \/ (String, Dependency) = {
for {
mod <- module(node)
version0 = readVersion(node)
scopeOpt = text(node, "scope", "").toOption
typeOpt = text(node, "type", "").toOption
classifierOpt = text(node, "classifier", "").toOption
xmlExclusions = node.children
.find(_.label == "exclusions")
.map(_.children.filter(_.label == "exclusion"))
.getOrElse(Seq.empty)
exclusions <- {
import Scalaz._
xmlExclusions.toList.traverseU(module(_))
}
optional = text(node, "optional", "").toOption.toSeq.contains("true")
} yield scopeOpt.getOrElse("") -> Dependency(
mod,
version0,
"",
exclusions.map(mod => (mod.organization, mod.name)).toSet,
Attributes(typeOpt getOrElse defaultType, classifierOpt getOrElse defaultClassifier),
optional,
transitive = true
)
}
private def profileActivation(node: Node): (Option[Boolean], Activation) = {
val byDefault =
text(node, "activeByDefault", "").toOption.flatMap{
case "true" => Some(true)
case "false" => Some(false)
case _ => None
}
val properties = node.children
.filter(_.label == "property")
.flatMap{ p =>
for{
name <- text(p, "name", "").toOption
valueOpt = text(p, "value", "").toOption
} yield (name, valueOpt)
}
(byDefault, Activation(properties))
}
def profile(node: Node): String \/ Profile = {
import Scalaz._
val id = text(node, "id", "Profile ID").getOrElse("")
val xmlActivationOpt = node.children
.find(_.label == "activation")
val (activeByDefault, activation) = xmlActivationOpt.fold((Option.empty[Boolean], Activation(Nil)))(profileActivation)
val xmlDeps = node.children
.find(_.label == "dependencies")
.map(_.children.filter(_.label == "dependency"))
.getOrElse(Seq.empty)
for {
deps <- xmlDeps.toList.traverseU(dependency)
xmlDepMgmts = node.children
.find(_.label == "dependencyManagement")
.flatMap(_.children.find(_.label == "dependencies"))
.map(_.children.filter(_.label == "dependency"))
.getOrElse(Seq.empty)
depMgmts <- xmlDepMgmts.toList.traverseU(dependency)
xmlProperties = node.children
.find(_.label == "properties")
.map(_.children.collect{case elem if elem.isElement => elem})
.getOrElse(Seq.empty)
properties <- {
import Scalaz._
xmlProperties.toList.traverseU(property)
}
} yield Profile(id, activeByDefault, activation, deps, depMgmts, properties.toMap)
}
def packagingOpt(pom: Node): Option[String] =
text(pom, "packaging", "").toOption
def project(pom: Node): String \/ Project = {
import Scalaz._
for {
projModule <- module(pom, groupIdIsOptional = true)
projVersion = readVersion(pom)
parentOpt = pom.children
.find(_.label == "parent")
parentModuleOpt <- parentOpt
.map(module(_).map(Some(_)))
.getOrElse(\/-(None))
parentVersionOpt = parentOpt
.map(readVersion)
xmlDeps = pom.children
.find(_.label == "dependencies")
.map(_.children.filter(_.label == "dependency"))
.getOrElse(Seq.empty)
deps <- xmlDeps.toList.traverseU(dependency)
xmlDepMgmts = pom.children
.find(_.label == "dependencyManagement")
.flatMap(_.children.find(_.label == "dependencies"))
.map(_.children.filter(_.label == "dependency"))
.getOrElse(Seq.empty)
depMgmts <- xmlDepMgmts.toList.traverseU(dependency)
groupId <- Some(projModule.organization).filter(_.nonEmpty)
.orElse(parentModuleOpt.map(_.organization).filter(_.nonEmpty))
.toRightDisjunction("No organization found")
version <- Some(projVersion).filter(_.nonEmpty)
.orElse(parentVersionOpt.filter(_.nonEmpty))
.toRightDisjunction("No version found")
_ <- parentVersionOpt
.map(v => if (v.isEmpty) -\/("Parent version missing") else \/-(()))
.getOrElse(\/-(()))
_ <- parentModuleOpt
.map(mod => if (mod.organization.isEmpty) -\/("Parent organization missing") else \/-(()))
.getOrElse(\/-(()))
xmlProperties = pom.children
.find(_.label == "properties")
.map(_.children.collect{case elem if elem.isElement => elem})
.getOrElse(Seq.empty)
properties <- xmlProperties.toList.traverseU(property)
xmlProfiles = pom.children
.find(_.label == "profiles")
.map(_.children.filter(_.label == "profile"))
.getOrElse(Seq.empty)
profiles <- xmlProfiles.toList.traverseU(profile)
extraAttrs <- properties
.collectFirst { case ("extraDependencyAttributes", s) => extraAttributes(s) }
.getOrElse(\/-(Map.empty))
extraAttrsMap = extraAttrs.map {
case (mod, ver) =>
(mod.copy(attributes = Map.empty), ver) -> mod.attributes
}.toMap
} yield {
val description = pom.children
.find(_.label == "description")
.map(_.textContent)
.getOrElse("")
val homePage = pom.children
.find(_.label == "url")
.map(_.textContent)
.getOrElse("")
val licenses = pom.children
.find(_.label == "licenses")
.toSeq
.flatMap(_.children)
.filter(_.label == "license")
.flatMap { n =>
text(n, "name", "License name").toOption.map { name =>
(name, text(n, "url", "License URL").toOption)
}.toSeq
}
val developers = pom.children
.find(_.label == "developers")
.toSeq
.flatMap(_.children)
.filter(_.label == "developer")
.map { n =>
for {
id <- text(n, "id", "Developer ID")
name <- text(n, "name", "Developer name")
url <- text(n, "url", "Developer URL")
} yield Info.Developer(id, name, url)
}
.collect {
case \/-(d) => d
}
Project(
projModule.copy(organization = groupId),
version,
deps.map {
case (config, dep0) =>
val dep = extraAttrsMap.get(dep0.moduleVersion).fold(dep0)(attrs =>
dep0.copy(module = dep0.module.copy(attributes = attrs))
)
config -> dep
},
Map.empty,
parentModuleOpt.map((_, parentVersionOpt.getOrElse(""))),
depMgmts,
properties,
profiles,
None,
None,
Nil,
Info(
description,
homePage,
licenses,
developers,
None
)
)
}
}
def versions(node: Node): String \/ Versions = {
import Scalaz._
for {
organization <- text(node, "groupId", "Organization") // Ignored
name <- text(node, "artifactId", "Name") // Ignored
xmlVersioning <- node.children
.find(_.label == "versioning")
.toRightDisjunction("Versioning info not found in metadata")
latest = text(xmlVersioning, "latest", "Latest version")
.getOrElse("")
release = text(xmlVersioning, "release", "Release version")
.getOrElse("")
versionsOpt = xmlVersioning.children
.find(_.label == "versions")
.map(_.children.filter(_.label == "version").flatMap(_.children.collectFirst{case Text(t) => t}))
lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time")
.toOption
.flatMap(parseDateTime)
} yield Versions(latest, release, versionsOpt.map(_.toList).getOrElse(Nil), lastUpdatedOpt)
}
def snapshotVersion(node: Node): String \/ SnapshotVersion = {
def textOrEmpty(name: String, desc: String) =
text(node, name, desc)
.toOption
.getOrElse("")
val classifier = textOrEmpty("classifier", "Classifier")
val ext = textOrEmpty("extension", "Extensions")
val value = textOrEmpty("value", "Value")
val updatedOpt = text(node, "updated", "Updated")
.toOption
.flatMap(parseDateTime)
\/-(SnapshotVersion(
classifier,
ext,
value,
updatedOpt
))
}
def snapshotVersioning(node: Node): String \/ SnapshotVersioning = {
import Scalaz._
// FIXME Quite similar to Versions above
for {
organization <- text(node, "groupId", "Organization")
name <- text(node, "artifactId", "Name")
version = readVersion(node)
xmlVersioning <- node.children
.find(_.label == "versioning")
.toRightDisjunction("Versioning info not found in metadata")
latest = text(xmlVersioning, "latest", "Latest version")
.getOrElse("")
release = text(xmlVersioning, "release", "Release version")
.getOrElse("")
versionsOpt = xmlVersioning.children
.find(_.label == "versions")
.map(_.children.filter(_.label == "version").flatMap(_.children.collectFirst{case Text(t) => t}))
lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time")
.toOption
.flatMap(parseDateTime)
xmlSnapshotOpt = xmlVersioning.children
.find(_.label == "snapshot")
timestamp = xmlSnapshotOpt
.flatMap(
text(_, "timestamp", "Snapshot timestamp")
.toOption
)
.getOrElse("")
buildNumber = xmlSnapshotOpt
.flatMap(
text(_, "buildNumber", "Snapshot build number")
.toOption
)
.filter(s => s.nonEmpty && s.forall(_.isDigit))
.map(_.toInt)
localCopy = xmlSnapshotOpt
.flatMap(
text(_, "localCopy", "Snapshot local copy")
.toOption
)
.collect{
case "true" => true
case "false" => false
}
xmlSnapshotVersions = xmlVersioning.children
.find(_.label == "snapshotVersions")
.map(_.children.filter(_.label == "snapshotVersion"))
.getOrElse(Seq.empty)
snapshotVersions <- xmlSnapshotVersions
.toList
.traverseU(snapshotVersion)
} yield SnapshotVersioning(
Module(organization, name, Map.empty),
version,
latest,
release,
timestamp,
buildNumber,
localCopy,
lastUpdatedOpt,
snapshotVersions
)
}
val extraAttributeSeparator = ":#@#:"
val extraAttributePrefix = "+"
val extraAttributeOrg = "organisation"
val extraAttributeName = "module"
val extraAttributeVersion = "revision"
val extraAttributeBase = Set(
extraAttributeOrg,
extraAttributeName,
extraAttributeVersion,
"branch"
)
val extraAttributeDropPrefix = "e:"
def extraAttribute(s: String): String \/ (Module, String) = {
// vaguely does the same as:
// https://github.com/apache/ant-ivy/blob/2.2.0/src/java/org/apache/ivy/core/module/id/ModuleRevisionId.java#L291
// dropping the attributes with a value of NULL here...
val rawParts = s.split(extraAttributeSeparator).toSeq
val partsOrError =
if (rawParts.length % 2 == 0) {
val malformed = rawParts.filter(!_.startsWith(extraAttributePrefix))
if (malformed.isEmpty)
\/-(rawParts.map(_.drop(extraAttributePrefix.length)))
else
-\/(s"Malformed attributes ${malformed.map("'"+_+"'").mkString(", ")} in extra attributes '$s'")
} else
-\/(s"Malformed extra attributes '$s'")
def attrFrom(attrs: Map[String, String], name: String): String \/ String =
\/.fromEither(
attrs.get(name)
.toRight(s"$name not found in extra attributes '$s'")
)
for {
parts <- partsOrError
attrs = parts.grouped(2).collect {
case Seq(k, v) if v != "NULL" =>
k.stripPrefix(extraAttributeDropPrefix) -> v
}.toMap
org <- attrFrom(attrs, extraAttributeOrg)
name <- attrFrom(attrs, extraAttributeName)
version <- attrFrom(attrs, extraAttributeVersion)
remainingAttrs = attrs.filterKeys(!extraAttributeBase(_))
} yield (Module(org, name, remainingAttrs.toVector.toMap), version)
}
def extraAttributes(s: String): String \/ Seq[(Module, String)] = {
val lines = s.split('\n').toSeq.map(_.trim).filter(_.nonEmpty)
lines.foldLeft[String \/ Seq[(Module, String)]](\/-(Vector.empty)) {
case (acc, line) =>
for {
modVers <- acc
modVer <- extraAttribute(line)
} yield modVers :+ modVer
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy