#!/usr/bin/env ruby
# Copyright (C) 2010 Gregoire Lejeune <gregoire.lejeune@free.fr>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA

require 'rubygems'
require 'getoptlong'
require 'graphviz'

class Git2Gv
  def initialize( xGVPath, xOutFile, xOutFormat, bNothugly )
    @xGVPath, @xOutFile, @xOutFormat, @bNothugly = xGVPath, xOutFile, xOutFormat.to_sym, bNothugly
  end

  def run
    git = GraphViz.new( :G, :path => @xGVPath )

    git.node[:shape => "record"]

    branches.each do |branch|
      git.add_nodes( branch, :style => "filled", :fillcolor => :lightgrey )
    end

    commits.each do |commit|
      git.add_nodes(
        commit[:short],
        :style => "filled",
        :fillcolor => :lightblue,
        :label => "{ #{commit[:long]} |{ #{commit[:comment].gsub(/\{/, "\\{").gsub(/\}/, "\\}").gsub(/\|/, "\\|").gsub(/</, "\\<").gsub(/>/, "\\>")}}| { #{commit[:commiter]} | #{commit[:date]} } }"
      )
    end

    refs.each do |from, to|
      git.add_edges( from, to )
    end

    relations.each do |from, to|
      git.add_edges( to, from, :dir => "back" )
    end

    git.output( @xOutFormat => @xOutFile, :nothugly => @bNothugly )
  end

  # private
  def cmd(c, &blk)
    `#{c}`.split($/).map(&blk || proc {|a| a })
  end

  def commits
    @commits ||= begin
      data = []
      cmd('git log --pretty=format:"%h - %H - %s - %cn - %cd" --date=short').each do |commit|
        x = commit.split( " - " )
        data << {
          :short => x[0],
          :long => x[1],
          :comment => x[2],
          :commiter => x[3],
          :date => x[4]
        }
      end
      data
    end
  end

  def relations
    @rels ||= cmd("git log --pretty=format:\"%h %p\"") { |l|
      c, *parents = l.split
      parents.map {|p| [p, c] }
    }.flatten(1)
  end

  def branches
    @branches ||= cmd("git branch") {|b| b[2..-1] }
  end

  def refs
    branches.inject({}) {|h,b|
      h.tap { h[b] = `git log -1 #{b} --pretty=format:"%h"` }
    }
  end

  def current_branch
    cmd("git branch").select {|l| l =~ /^\*/ }.first.strip[2..-1]
  end
end

def usage
  puts "usage: git2gv [-Tformat] [-ofile] [-h] [-V]"
  puts "-T, --output-format format    Output format (default: png)"
  puts "    --nothugly                Use nothugly if SVG output"
  puts "-o, --output-file file        Output file (default: STDOUT)"
  puts "-p, --path                    Graphviz path"
  puts "-V, --version                 Show version"
  puts "-h, --help                    Show this usage message"
end

def version
  puts "Git2GraphViz v#{GraphViz::Constants::RGV_VERSION}, (c)2010 Gregoire Lejeune <gregoire.lejeune@free.fr>"
  puts ""
  puts "This program is free software; you can redistribute it and/or modify"
  puts "it under the terms of the GNU General Public License as published by"
  puts "the Free Software Foundation; either version 2 of the License, or"
  puts "(at your option) any later version."
  puts ""
  puts "This program is distributed in the hope that it will be useful,"
  puts "but WITHOUT ANY WARRANTY; without even the implied warranty of"
  puts "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the"
  puts "GNU General Public License for more details."
  puts ""
  puts "You should have received a copy of the GNU General Public License"
  puts "along with this program; if not, write to the Free Software"
  puts "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA"
end

oOpt = GetoptLong.new(
  ['--output-format', '-T', GetoptLong::REQUIRED_ARGUMENT],
  ['--output-file',   '-o', GetoptLong::REQUIRED_ARGUMENT],
  ['--path',          '-p', GetoptLong::REQUIRED_ARGUMENT],
  ['--nothugly',            GetoptLong::NO_ARGUMENT],
  ['--help',          '-h', GetoptLong::NO_ARGUMENT],
  ['--version',       '-V', GetoptLong::NO_ARGUMENT]
)

xOutFormat = "png"
xOutFile = nil
xGVPath = ""
bNothugly = false

begin
  oOpt.each_option do |xOpt, xValue|
    case xOpt
      when '--output-format'
        xOutFormat = xValue
      when '--output-file'
        xOutFile = xValue
      when '--path'
        xGVPath = xValue
      when '--nothugly'
        bNothugly = true
      when '--help'
        usage( )
        exit
      when '--version'
        version( )
        exit
    end
  end
rescue GetoptLong::InvalidOption
  usage( )
  exit
end

Git2Gv.new( xGVPath, xOutFile, xOutFormat, bNothugly ).run

