/* Copyright 2010 Aaron J. Radke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cc.drx object Version extends Applicable[String,Version]{ //puntuation | space | [digit look behind] & [letter look ahead] | [letter look behind] & [digit look ahead] val sep = """(\s*[\.\_\-\/\,\;]\s*)|\s+|(?<=\d)+(?=\p{L})|(?<=\p{L})+(?=\d)""".r implicit object ParsableVersion extends Parsable[Version]{ def apply(v:String):Version = Version(v) } final class VersionStringContext(val sc:StringContext) extends AnyVal{ def version(args:Any*):Version = Version(sc.s(args:_*)) } def apply(major:Int, minor:Int, patch:Int):Version = Version(s"$major.$minor.$patch") def apply(major:Int, minor:Int):Version = Version(s"$major.$minor") def apply(major:Int):Version = Version(s"$major") //assuming initial release with no previous specified date def kryptonInit(init:Date=Date.now):Version = Version(init.krypton take 1) // def krypton(last:Date, next:Date):Version = krypton(last.krypton take 8, next) def kryptonNext(lastVer:Version, next:Date=Date.now):Version = { val nextVer = next.krypton take 8 val same = lastVer.name lcp nextVer Version(same + nextVer.drop(same.size).take(1)) } def kryptonMap(xs:Iterable[Date]):Iterable[Version] = { xs.scanLeft(Version("0")){kryptonNext _ }.tail //pull in the tupled version of krypton } } /**Version represents a string name that may look like a version, its main feature provides a nice human sorting rather than full string bases sorting*/ case class Version(val name: String) extends AnyVal with Ordered[Version]{ // def value:String = name //TODO change name to value // import scala.math.Ordered.orderingToOrdered //FIXME is this import really not needed override def toString = name def /(minor:Int):Version = Version(name + "." + minor) def /(minor:String):Version = Version(name + "." + minor) def *(alt:String):Version = Version(name + "-" + alt) //less than 6 is a convenient size that tracks timesstamps and datestamps in iso form def isDigit(s:String):Boolean = s.nonEmpty && (s.size < 6) && s.forall{_.isDigit} def compare(that:Version):Int = { @tailrec def cmp(a:Seq[String], b:Seq[String]):Int = { //println(s"$a , $b") //--both non empty if(a.nonEmpty && b.nonEmpty){ //if head is the same keep comparing if(a.head == b.head) cmp(a.tail, b.tail) //compare by numeric if all digits else if(isDigit(a.head) && isDigit(b.head)) a.head.toInt compare b.head.toInt //if a only a digit then let 'a' be bigger else if(isDigit(a.head)) 1 //if b only is a digit then let 'b' be bigger else if(isDigit(b.head)) -1 //use the normal string comparison function else a.head compare b.head } //-- 'b' is larger if (implicityly no 'a') && ('b' is a number), //-- 'a' is larger if 'b' contains more else if(b.nonEmpty) if(isDigit(b.head)) -1 else 1 //-- 'a' is larger if (implicitly no 'b') && ('a' is a number), //-- 'b' is larger if 'a' contains more else if(a.nonEmpty) if(isDigit(a.head)) 1 else -1 // otherwise assume equality //false case else 0 //both empty so equal } if(this.name == that.name) 0 else cmp(this.terms, that.terms) } def terms = (Version.sep split name).toIndexedSeq def isSnapshot = name.toLowerCase contains "snapshot" def isFinal = name forall {d => d.isDigit || d == '.'} def isIntermediate = name exists {d => d.isLetter } def take(depth:Int):Version = Version(terms take depth mkString ".") def similar(that:Version,depth:Int) = this.take(depth) == that.take(depth) }