#! /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! } }