diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rw-r--r-- | README.markdown | 25 | ||||
| -rwxr-xr-x | bin/hcl | 9 | ||||
| m--------- | ext/harvest | 0 | ||||
| -rw-r--r-- | hcl_conf.yml.example | 3 | ||||
| -rw-r--r-- | lib/hcl.rb | 69 | ||||
| -rw-r--r-- | lib/hcl/day_entry.rb | 66 | 
8 files changed, 176 insertions, 0 deletions
| diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1e0636 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.sw[nop] diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..90ce88a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ext/harvest"] +	path = ext/harvest +	url = git://github.com/aiaio/harvest.git diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..8903b1c --- /dev/null +++ b/README.markdown @@ -0,0 +1,25 @@ +# hcl + +HCl is a command-line tool for interacting with Harvest time sheets using the +[Harvest time tracking API][1]. + +## Usage + +    hcl [opts] add <project> <task> <duration> [msg] +    hcl [opts] rm [entry_id] +    hcl [opts] start <project> <task> [msg] +    hcl [opts] stop [msg] +    hcl [opts] show [date] + +## TODO + + * Implement time-tracking API methods: +   - get daily time sheet +   - get time sheet entry +   - toggle a timer +   - post a time sheet entry +   - delete a time sheet entry +   - update a time sheet entry + +[1]: http://www.getharvest.com/api/time_tracking + @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +$:.unshift File.dirname(__FILE__) + '/../lib' +$:.unshift File.dirname(__FILE__) + '/../ext/harvest/lib' + +require 'hcl' + +HCl.command *ARGV + diff --git a/ext/harvest b/ext/harvest new file mode 160000 +Subproject 485940b1205549c8bea1e0a9c5175c2a51ad728 diff --git a/hcl_conf.yml.example b/hcl_conf.yml.example new file mode 100644 index 0000000..e570ce0 --- /dev/null +++ b/hcl_conf.yml.example @@ -0,0 +1,3 @@ +subdomain: mycompany +login: me@mycompany.com +password: secret diff --git a/lib/hcl.rb b/lib/hcl.rb new file mode 100644 index 0000000..5b8abba --- /dev/null +++ b/lib/hcl.rb @@ -0,0 +1,69 @@ +require 'yaml' + +require 'rubygems' +require 'curb' + +require 'hcl/day_entry' + +class HCl +  class UnknownCommand < RuntimeError; end + +  def self.command *args +    command = args.shift +    unless command +      help +      return +    end +    hcl = new.process_args *args +    if hcl.respond_to? command +      hcl.send command +    else +      raise UknownCommand, "unrecognized command `#{command}'" +    end +  end + +  def initialize +    config = YAML::load(File.read('hcl_conf.yml')) +    TimesheetResource.configure config +  end + +  def self.help +    puts <<-EOM +    Usage: + +    hcl [opts] add <project> <task> <duration> [msg] +    hcl [opts] rm [entry_id] +    hcl [opts] start <project> <task> [msg] +    hcl [opts] stop [msg] +    hcl [opts] show [date] +    EOM +  end +  def help; self.class.help; end + +  def process_args *args +    # TODO process command-line args +    self +  end + +  def show +    total_hours = 0.0 +    DayEntry.all.each do |day| +      # TODO more information and formatting options +      puts "#{day.task} / #{day.hours}" +      total_hours = total_hours + day.hours.to_f +    end +    puts "Total #{total_hours} hours" +  end + +  def not_implemented +    puts "not yet implemented" +  end + +  # TODO implement the following commands +  alias start not_implemented +  alias stop not_implemented +  alias add not_implemented +  alias rm not_implemented + +end + diff --git a/lib/hcl/day_entry.rb b/lib/hcl/day_entry.rb new file mode 100644 index 0000000..c4e24f4 --- /dev/null +++ b/lib/hcl/day_entry.rb @@ -0,0 +1,66 @@ +require 'rexml/document' + +class HCl +  class TimesheetResource +    def self.configure opts = nil +      if opts +        self.login = opts['login'] +        self.password = opts['password'] +        self.subdomain = opts['subdomain'] +      else +        yield self +      end +    end +   +    # configuration accessors +    %w[ login password subdomain ].each do |config_var| +      class_eval <<-EOC +        def self.#{config_var}= arg +          @@#{config_var} = arg +        end +        def self.#{config_var} +          @@#{config_var} +        end +      EOC +    end +   +    def initialize params +      @data = params +    end +   +    def self.perform action +      client = Curl::Easy.new("https://#{subdomain}.harvestapp.com/#{action}") +      client.headers['Accept'] = 'application/xml' +      client.headers['Content-Type'] = 'application/xml' +      client.http_auth_types = Curl::CURLAUTH_BASIC +      client.userpwd = "#{login}:#{password}" +      if client.http_get +        client.body_str +      else +        raise "failed" +      end +    end +   +    def method_missing method, *args +      if @data.key? method.to_sym +        @data[method] +      else +        super +      end +    end +  end +   +  class DayEntry < TimesheetResource +    def self.all +      doc = REXML::Document.new perform('daily') +      doc.root.elements.collect('day_entries/day_entry') do |day| +        new( +          day.elements.map { |e| e.name }.inject({}) do |a, f| +            a[f.to_sym] = day.elements[f].text if day.elements[f] +            a +          end +        ) +      end +    end +  end +end | 
