#!/usr/bin/env ruby
# git update-ghpages user/repo -b gh-pages -p manual/ -i

require 'fileutils'
require 'tmpdir'

module Params
  def extract(what)   i = index(what) and slice!(i, 2)[1] end;
  def first_is(what)  shift  if what.include?(self.first); end
  def self.[](*what)  what.extend Params; end
  def ===(argv)       argv.first_is(self); end
end

# ============================================================================

ARGV.extend Params

class CLI
  # CLI options
  attr_reader :prefix    #=> "doc/"
  attr_reader :input     #=> "/home/me/projects/foo"
  attr_reader :message   #=> "Updated"
  attr_reader :repo      #=> "git@github.com:me/project.git"
  attr_reader :url       #=> "http://me.github.com/project"
  attr_reader :branch    #=> "gh-pages"

  def verbose?() @verbose; end
  def force?() @force; end
  def simulate?() @simulate; end

  def initialize
    # Switches
    @verbose  = !! (ARGV.extract('--verbose') || ARGV.delete('-v'))
    @simulate = !! (ARGV.extract('--simulate') || ARGV.delete('-s'))
    @force    = !! (ARGV.delete('--force') || ARGV.delete('-f'))

    # Stuff
    @prefix   = ARGV.extract('--prefix') || ARGV.extract('-p') || ''
    @input    = File.expand_path(ARGV.extract('--input') || ARGV.extract('-i') || '.')
    @message  = ARGV.extract('--message') || ARGV.extract('-m') || 'Update'

    # Github info
    branch = ARGV.extract('--branch') || ARGV.extract('-b') || nil
    @repo, @url, @branch = get_github_info(ARGV.shift, branch)
  end

  def git_current_branch
    `git rev-parse --abbrev-ref HEAD`.strip
  end

  def git_deploy
    in_temp_path do |temppath|
      status "Cloning repository"
      system! "git clone #{repo} -b #{branch} #{temppath}"

      if git_current_branch != branch
        status "Warning: No #{branch} branch found in repo, creating one."
        return git_deploy_force
      end

      copy_files input, File.join(temppath, prefix)

      status "Committing files"
      system! "git add .; git add -u; git commit -m #{message.to_s.inspect}"

      unless simulate?
        status "Updating repo"
        system! "git push origin #{branch}"
      end
      true
    end
  end

  def git_deploy_force
    in_temp_path do |temppath|
      status "Creating new repository"
      system! "git init ."
      system! "git checkout -b gh-pages"

      copy_files input, File.join(temppath, prefix)

      status "Committing files"
      system! "git add . && git commit -m #{message.to_s.inspect}"

      unless simulate?
        status "Updating repo"
        system! "git push #{repo} gh-pages:#{branch} --force"
      end
      true
    end
  end

  def get_github_info(repo, branch=nil, prefix=nil)
    if github_format?(repo)
      user, repo_name = repo.split('/')
      r = "git@github.com:#{repo}.git"

      # User page or project page?
      if repo_name =~ /\.github\.com/
        [r, "http://#{repo_name}/#{prefix}", branch || 'master' ]
      else
        [r, "http://#{user}.github.com/#{repo_name}/#{prefix}", branch || 'gh-pages' ]
      end
    else
      [repo, nil, branch]
    end
  end

  def run!
    unless repo
      print_help
      exit 128
    end

    status "Deploying to #{repo} (branch #{branch})"
    msg "NOTE: Running in simulation mode." if simulate?
    msg "WARNING: If the repository has gh-pages history, it with be overriden." if force? && !simulate?

    result = force? ? git_deploy_force : git_deploy

    if result
      puts ""
      status "Done."
      msg "See: #{url}" if url && !simulate?
    else
      tip "Failed."
      exit 1
    end
  end

  def status(str)
    puts "#{c('===>',34)} #{c(str, 32)}"
  end

  def msg(str)
    puts "     #{c(str, 32)}"
  end

  def c(str, color)
    "\033[#{color}m#{str}\033[0m"
  end

  def print_help
    tip \
    %{Usage: git update-ghpages username/repository [options]

      Flags:
        -f,        --force       Force an update (WARNING: kills the history!)
        -s,        --simulate    Creates the repository, but doesn't push.
        -v,        --verbose     Verbose mode

      Options:
        -p PATH,   --prefix      The prefix
        -i PATH,   --input       Input (defaults to current directory)
        -b BRANCH, --branch      The branch to deploy to (defaults to gh-pages)
        -m MSG,    --message     Commit message (defaults to 'Update')

      Examples:

      Update the repo 'coffee' of github user 'james' with the files from the
      current directory. The files will be in http://james.github.com/coffee.

        $ git update-ghpages james/coffee

      Same as above, but take the files from 'doc/'.

        $ git update-ghpages james/coffee -i doc

      Same as the first, but the files will instead be in
      http://james.github.com/coffee/manual.

        $ git update-ghpages james/coffee -i doc -p manual
    }.gsub(/^ {4}/, '')
  end

private # Helpers

  def tip(msg)
    $stderr.write "#{msg}\n"
  end

  def github_format?(str)
    str =~ /^([A-Za-z0-9\-_]+)\/([A-Za-z0-9\-_\.]+)$/
  end

  # Performs actions inside a temp path.
  def in_temp_path(&blk)
    require 'tmpdir'
    Dir.mktmpdir do |dir|
      Dir.chdir(dir) { yield dir }
    end
  end

  def system!(str)
    puts `#{str} 2>&1`.strip.gsub(/^/, "     ")
    raise "Failed with exit code #{$?.to_i}" unless $?.to_i == 0
  end

  # Returns the current branch name
  def git_branch
    `git symbolic-ref HEAD`.strip.split('/').last
  end

  # Copy files from source folder to another
  def copy_files(from, to)
    status "Copying files #{from} => #{to}..."  if verbose?

    Dir["#{from}/**/*"].each do |f|
      next  unless File.file?(f)

      target = File.join(to, f.gsub(/^#{Regexp.escape from}/, ''))

      FileUtils.mkdir_p File.dirname(target)
      msg "%20s => %-20s" % [f, target]  if verbose?
      FileUtils.cp f, target
    end
  end

end

CLI.new.run!
