/* sbt -- Simple Build Tool * Copyright 2008 Mark Harrah */ package sbt sealed trait Version case class BasicVersion(major: Int, minor: Option[Int], micro: Option[Int], extra: Option[String]) extends Version { import Version._ require(major >= 0, "Major revision must be nonnegative.") require(minor.isDefined || micro.isEmpty, "Cannot define micro revision without defining minor revision.") requirePositive(minor) requirePositive(micro) require(isValidExtra(extra)) def incrementMicro = BasicVersion(major, minor orElse Some(0), increment(micro), extra) def incrementMinor = BasicVersion(major, increment(minor), micro, extra) def incrementMajor = BasicVersion(major+1, minor, micro, extra) def withExtra(newExtra: Option[String]) = BasicVersion(major, minor, micro, newExtra) override def toString = major + minor.map(minorI => "." + minorI + micro.map(microI => "." + microI).getOrElse("")).getOrElse("") + extra.map(x => "-" + x).getOrElse("") } case class OpaqueVersion(value: String) extends Version { require(!value.trim.isEmpty) override def toString = value } object Version { private[sbt] def increment(i: Option[Int]) = Some(i.getOrElse(0) + 1) private[sbt] def requirePositive(i: Option[Int]) { i.foreach(x => require(x >= 0)) } import java.util.regex.Pattern val versionPattern = Pattern.compile("""(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:-(.+))?""") def fromString(v: String): Either[String, Version] = { val trimmed = v.trim if(trimmed.isEmpty) Left("Version cannot be empty.") else { val matcher = versionPattern.matcher(trimmed) import matcher._ if(matches) { def toOption(index: Int) = { val v = group(index) if(v == null) None else Some(v) } def toInt(index: Int) = toOption(index).map(_.toInt) val extra = toOption(4) if(isValidExtra(extra)) Right(BasicVersion(group(1).toInt, toInt(2), toInt(3), extra)) else Right(OpaqueVersion(trimmed)) } else Right(OpaqueVersion(trimmed)) } } def isValidExtra(e: Option[String]): Boolean = e.map(isValidExtra).getOrElse(true) def isValidExtra(s: String): Boolean = !(s.trim.isEmpty || s.exists(java.lang.Character.isISOControl)) }