lib/semver.rb



require 'yaml'
require 'semver/semvermissingerror'

class SemVer

  FILE_NAME = '.semver'
  TAG_FORMAT = 'v%M.%m.%p%s'

  def SemVer.find dir=nil
    v = SemVer.new
    f = SemVer.find_file dir
    v.load f
    v
  end

  def SemVer.find_file dir=nil
    dir ||= Dir.pwd
    raise "#{dir} is not a directory" unless File.directory? dir
    path = File.join dir, FILE_NAME

    Dir.chdir dir do
      while !File.exists? path do
        raise SemVerMissingError, "#{dir} is not semantic versioned", caller if File.dirname(path).match(/(\w:\/|\/)$/i)
        path = File.join File.dirname(path), ".."
        path = File.expand_path File.join(path, FILE_NAME)
        puts "semver: looking at #{path}"
      end
      return path
    end

  end

  attr_accessor :major, :minor, :patch, :special

  def initialize major=0, minor=0, patch=0, special=''
    major.kind_of? Integer or raise "invalid major: #{major}"
    minor.kind_of? Integer or raise "invalid minor: #{minor}"
    patch.kind_of? Integer or raise "invalid patch: #{patch}"

    unless special.empty?
      special =~ /[A-Za-z][0-9A-Za-z-]+/ or raise "invalid special: #{special}"
    end

    @major, @minor, @patch, @special = major, minor, patch, special
  end

  def load file
    @file = file
    hash = YAML.load_file(file) || {}
    @major = hash[:major] or raise "invalid semver file: #{file}"
    @minor = hash[:minor] or raise "invalid semver file: #{file}"
    @patch = hash[:patch] or raise "invalid semver file: #{file}"
    @special = hash[:special]  or raise "invalid semver file: #{file}"
  end

  def save file=nil
    file ||= @file

    hash = {
      :major => @major,
      :minor => @minor,
      :patch => @patch,
      :special => @special
    }

    yaml = YAML.dump hash
    open(file, 'w') { |io| io.write yaml }
  end

  def format fmt
    fmt.gsub! '%M', @major.to_s
    fmt.gsub! '%m', @minor.to_s
    fmt.gsub! '%p', @patch.to_s
    fmt.gsub! '%s', @special.to_s
    fmt
  end

  def to_s
    format TAG_FORMAT
  end

  def <=> other
    maj = major.to_i <=> other.major.to_i
    return maj unless maj == 0

    min = minor.to_i <=> other.minor.to_i
    return min unless min == 0

    pat = patch.to_i <=> other.patch.to_i
    return pat unless pat == 0

    spe = special <=> other.special
    return spec unless spe == 0

    0
  end

  include Comparable
end