#! /usr/bin/env ruby

Main {
  description <<-txt
    i can has time tracking!

    punch is a k.i.s.s. tool for tracking the hours spent on various projects.
    it supports logging hours under a project name, adding notes about work 
    done during that period, and several very simple reporting tools that 
    operate over a window of time.

    run 'punch help modename' for more info.
  txt

  examples <<-txt
    . punch in projectname
    . punch in projectname 'an hour ago'
    . punch log projectname 'rewriting your java app in ruby...'
    . punch out projectname
    . punch total projectname --after 2007-12-03 --before 2007-12-07
    . punch total projectname --after 'yesterday morning' --before 'today at noon' 
  txt


  mode(:in){
    description 'punch in on a project'

    examples <<-txt
      . punch in projectname
      . punch in projectname 2007-01-01T09 
      . punch in projectname now
      . punch in projectname 'today at around noon' 
      . punch in projectname 2007-01-01T09 --message 'new years day'
    txt

    mixin :argument_project, :optional_now, :option_db, :option_message, :option_noop

    run{ y db.punch(:in, project, 'now' => now, 'message' => message) }
  }


  mode(:out){
    description 'punch out of a project or, iff no project given, all open projects'

    examples <<-txt
      . punch out projectname
      . punch out projectname 2007-01-01T09 
      . punch out projectname now
      . punch out projectname 2007-01-01T17 --message 'new years day'
      . punch out projectname 2007-01-01T05pm -m 'new years day'
      . punch out projectname an hour ago
      . punch out
    txt

    mixin :optional_project, :optional_now, :option_db, :option_message, :option_noop

    run{ y db.punch(:out, project, 'now' => now, 'message' => message) }
  }


  mode(:log){
    description 'log a message for a project you are currently logged into'

    examples <<-txt
      . punch log projectname 'xml - welcome to my pain cave!'
      . punch log projectname should have used yaml 
    txt

    mixin :argument_project, :option_db, :argument_message, :option_noop

    run{ y db.log(project, message) }
  }


  mode(:clock){
    description 'punch in and out retroactively'

    examples <<-txt
      . punch clock projectname 2007-01-01T09am 2007-01-01T05pm -m "working on new year's day"
    txt

    mixin :argument_project, :argument_punch_in, :argument_punch_out, :option_db, :option_message, :option_noop

    run{ y db.clock(project, punch_in, punch_out, 'message' => message) }
  }

  mode(:status){
    description 'shows the status of named project, or all projects iff none given'

    examples <<-txt
      . punch status
      . punch status projectname
    txt

    mixin :optional_project, :option_db

    run{ y db.status(project) }
  }

  mode(:list){
    description 'list a single/all projects, possibly filtering by time'

    examples <<-txt
      . punch list projectname
      . punch list projectname --after 2007-01-01 --before 2007-01-31
      . punch list
      . punch list -A 2007-01-01 -B 2007-01-31
    txt

    mixin :optional_project, :option_after, :option_before, :option_db

    run{ y db.list(project, 'window' => (after .. before)) }
  }

  mode(:total){
    description 'total the time for a single/all projects, possibly filtering by time'

    examples <<-txt
      . punch total projectname
      . punch total projectname --after 2007-01-01 --before 2007-01-31
      . punch total
      . punch total -A 2007-01-01 -B 2007-01-31
    txt

    mixin :optional_project, :option_after, :option_before, :option_db

    run{ y db.total(project, 'window' => (after .. before)) }
  }

  mode(:delete){
    description 'delete all records for a project'

    examples <<-txt
      . punch delete projectname
    txt

    mixin :argument_project, :option_db, :option_noop

    run{ y db.delete(project) }
  }

  mode(:dump){
    description 'cat the yaml db with lock held'

    examples <<-txt
      . punch dump 
      . punch dump projectname
    txt

    mixin :optional_project, :option_db

    run{ y db.dump(project) }
  }

  mode(:parse){
    description 'show how a time string will be parsed'

    examples <<-txt
      . punch parse 'yesterday morning' 
      . punch parse today at noon 
      . punch parse yesterday at 9pm 
    txt

    argument(:spec){
      arity -2
      attr
    }

    run{ y Time.parse(spec.join(' ')) }
  }


  mixin :argument_project do
    argument(:project){
      attr
    }
  end

  mixin :optional_project do
    argument(:project){
      optional
      attr
    }
  end

  mixin :argument_punch_in do
    argument(:punch_in){
      cast{|value| Time.parse value}
      attr
    }
  end

  mixin :argument_punch_out do
    argument(:punch_out){
      cast{|value| Time.parse value}
      attr
    }
  end

  mixin :argument_message do
    argument(:message){
      attr
      arity -2
    }
  end

  mixin :option_message do
    option(:message, :m){
      argument :required
      attr
    }
  end

  mixin :option_now do
    option(:now, :n){
      argument :required
      desc 'consider this time as the current time'
      cast{|value| Time.parse value}
      default Time::Now
      attr
    }
  end

  mixin :optional_now do
    argument(:now){
      optional
      desc 'consider this time as the current time'
      arity -2
      default Time::Now
    }
    attribute(:now){ Time.parse param['now'].values.join(' ') }
  end

  mixin :option_db do
    option(:db){
      argument :required
      default File.join(Home, '.punch.yml')
      attr{|param| DB.new(param.value) }
    }
  end

  mixin :option_noop do
    option(:noop, :N){
      cast{|value| $NOOP = value}
    }
  end

  mixin :option_after do
    option(:after, :A){
      argument :required
      desc 'limit data shown to entries after this iso8601 timestamp'
      default Time::Beginning
      cast :time
      attr
    }
  end

  mixin :option_before do
    option(:before, :B){
      argument :required
      desc 'limit data shown to entries before this iso8601 timestamp'
      default Time::End
      cast :time
      attr
    }
  end

  run{ help! }
}