toggler 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Koen Van der Auwera
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ = toggl
2
+
3
+ Toggl API wrapper in a Ruby gem
4
+
5
+ https://www.toggl.com
6
+
7
+ https://www.toggl.com/public/api
8
+
9
+ == toggl command
10
+
11
+ If you want to create new tasks from the command line, make sure you create ~/.toggl file containing nothing but your API key.
12
+
13
+ run 'toggl' to see all options.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Koen Van der Auwera. See LICENSE for details.
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+
4
+
5
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
6
+ require 'toggl'
7
+ require 'toggl_cmd/runner'
8
+ require 'toggl_cmd/runner_options'
9
+ require 'hirb'
10
+ require 'hirb/import_object'
11
+
12
+ Hirb.enable
13
+ TogglCmd::Runner.toggl(ARGV)
@@ -0,0 +1,113 @@
1
+ require "rubygems"
2
+ require "httparty"
3
+ require "chronic_duration"
4
+
5
+ class Toggl
6
+ include HTTParty
7
+ base_uri "https://toggl.com"
8
+ format :json
9
+
10
+ attr_reader :name, :api_token
11
+
12
+ def initialize(token, name="toggl-gem", debug=false)
13
+ self.class.default_params :output => 'json'
14
+ @api_token = token
15
+ @name = name
16
+ self.class.debug_output if debug
17
+ end
18
+
19
+ def delete_task(task_id)
20
+ delete 'tasks', task_id
21
+ end
22
+
23
+ def create_task(params={})
24
+ workspace = params[:workspace] || default_workspace_id
25
+ project_id = find_project_id(params[:project]) || create_project(params, workspace)
26
+ params[:billable] = true
27
+
28
+ params.merge!({ :created_with => name,
29
+ :workspace => {:id => workspace},
30
+ :project => {:id => project_id},
31
+ :tag_names => [name],
32
+ :start => start(params[:start]),
33
+ :duration => duration(params[:duration])})
34
+
35
+ post 'tasks', {:task => params}
36
+ end
37
+
38
+ def create_project(params={}, workspace=nil)
39
+ workspace ||= default_workspace_id
40
+ if project = post("projects",
41
+ :project => {:name => params[:project],
42
+ :workspace => {:id => workspace},
43
+ :billable => (params[:billable] || true)})
44
+ project["id"]
45
+ end
46
+ end
47
+
48
+ def default_workspace_id
49
+ self.workspaces.first["id"]
50
+ end
51
+
52
+ def find_project_id(str)
53
+ if project = self.projects.find{|project| project["client_project_name"].downcase =~ /#{str}/}
54
+ project["id"]
55
+ end
56
+ end
57
+
58
+ def duration(str)
59
+ str ? ChronicDuration.parse(str) : 1800
60
+ end
61
+
62
+ def start(value)
63
+ if value
64
+ case value
65
+ when "today"
66
+ Date.today
67
+ when "yesterday"
68
+ Date.today - 1
69
+ when "tomorrow"
70
+ Date.today + 1
71
+ else
72
+ DateTime.parse(value)
73
+ end
74
+ else
75
+ DateTime.now
76
+ end
77
+ end
78
+
79
+ def workspaces
80
+ get 'workspaces'
81
+ end
82
+
83
+ def tasks(params={})
84
+ get 'tasks', params
85
+ end
86
+
87
+ def projects
88
+ get 'projects'
89
+ end
90
+
91
+ def user_dump
92
+ get 'me', {:with_related_data => true}
93
+ end
94
+
95
+ private
96
+
97
+ def get(resource_name, data={})
98
+ self.class.get("/api/v3/#{resource_name}.json", :basic_auth => basic_auth, :query => data)
99
+ end
100
+
101
+ def post(resource_name, data)
102
+ self.class.post("/api/v3/#{resource_name}.json", :body => data, :basic_auth => basic_auth)
103
+ end
104
+
105
+ def delete(resource_name, id)
106
+ self.class.delete("/api/v3/#{resource_name}/#{id}.json", :basic_auth => basic_auth)
107
+ end
108
+
109
+ def basic_auth
110
+ {:username => self.api_token, :password => "api_token"}
111
+ end
112
+
113
+ end
@@ -0,0 +1,118 @@
1
+ require "optparse"
2
+ require "chronic_duration"
3
+ require 'yaml'
4
+ require 'active_record'
5
+
6
+ module TogglCmd
7
+
8
+ NAME = "toggl-gem"
9
+ PROJECT_FIELDS = %w(client name)
10
+ TASK_FIELDS = %w(id project description start duration billable)
11
+
12
+ class Runner
13
+
14
+ def self.toggl(args)
15
+ token = team_tokens.first[1]
16
+ options = RunnerOptions.new(args)
17
+ if options[:tasks]
18
+ prettify_tasks(Toggl.new(token, NAME).tasks)
19
+ elsif options[:projects]
20
+ prettify_projects(Toggl.new(token, NAME).projects)
21
+ elsif options[:delete]
22
+ toggl = Toggl.new(token, NAME)
23
+ toggl.delete_task(options[:delete])
24
+ prettify_tasks(toggl.tasks)
25
+ elsif options[:dump]
26
+ dump_tasks(options[:since], options[:through])
27
+ elsif options.any?
28
+ prettify_tasks(Toggl.new(token, NAME, options.delete(:debug)).create_task(options))
29
+ else
30
+ puts options.opts
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def self.prettify_tasks(values)
37
+ values = values["data"]
38
+ values = [values] unless values.is_a?(Array)
39
+ values.each do |value|
40
+ value["project"] = value["project"] ? value["project"]["name"] : ""
41
+ value["workspace"] = value["workspace"]["name"]
42
+ value["duration"] = ChronicDuration.output(value["duration"].to_i, :format => :short)
43
+ value["start"] = value["start"].strftime("%d/%m/%Y")
44
+ end
45
+ values.view(:class => :table, :fields => TASK_FIELDS)
46
+ end
47
+
48
+ def self.prettify_projects(values)
49
+ values = values["data"]
50
+ values = [values] unless values.is_a?(Array)
51
+ values.each do |value|
52
+ value["client"] = value["client"] ? value["client"]["name"] : ""
53
+ end
54
+
55
+ values.view(:class => :table, :fields => PROJECT_FIELDS)
56
+ end
57
+
58
+ def self.dump_tasks(since=nil, through=nil)
59
+ ActiveRecord::Base.establish_connection(config['database'])
60
+
61
+ team_tokens.each do |user, api_key|
62
+ tasks = TogglTask.load_tasks(user, Toggl.new(api_key), since, through)
63
+ puts "Dumping #{tasks.count} tasks from #{user} to the database"
64
+ tasks.each do |task|
65
+ task.save unless TogglTask.exists?(task.id) or task.duration < 0
66
+ end
67
+ end
68
+ end
69
+
70
+ def self.team_tokens
71
+ tokens = Hash.new
72
+ config["users"].each { |user, key| tokens[user] = key }
73
+ tokens
74
+ end
75
+
76
+ def self.config
77
+ YAML.load_file(File.expand_path("~/toggl_config.yaml"))
78
+ end
79
+
80
+ end
81
+
82
+ class TogglTask < ActiveRecord::Base
83
+ #attr_accessor :id, :start, :stop, :duration, :client, :project, :user, :billable, :description
84
+
85
+ def self.load_tasks(user, toggl, since, through)
86
+ client_projects = map_projects_to_clients(toggl.projects)
87
+ task_options = {:start_date => since || '1/1/2000', :end_date => through }
88
+
89
+ tasks = []
90
+ toggl.tasks(task_options)["data"].each do |t|
91
+ next if t["duration"] < 0
92
+ task = TogglTask.new(:start => t["start"],
93
+ :stop => t["stop"],
94
+ :duration => t["duration"],
95
+ :client => client_projects[t["project"]["id"]],
96
+ :project => t["project"]["name"],
97
+ :user => user,
98
+ :billable => t["billable"],
99
+ :description => t["description"])
100
+ task.id = t["id"]
101
+ tasks << task
102
+ end
103
+
104
+ tasks
105
+ end
106
+
107
+ def self.map_projects_to_clients(json_obj)
108
+ result = Hash.new
109
+
110
+ json_obj["data"].each do |project|
111
+ result[project["id"]] = project["client"] == nil ? '' : project["client"]["name"]
112
+ end
113
+
114
+ result
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,71 @@
1
+ module TogglCmd
2
+
3
+ class RunnerOptions < Hash
4
+ attr_reader :opts
5
+
6
+ def initialize(args)
7
+ super()
8
+
9
+ @opts = OptionParser.new do |o|
10
+ o.banner = "Usage: #{File.basename($0)} [options]"
11
+
12
+ o.on('-D', '--dump', 'Dumps data for each user in ~/.toggl_team to a table in CRM.') do |dump|
13
+ self[:dump] = dump
14
+ end
15
+
16
+ o.on('-S', '--since DATE', 'The date (inclusive) to start dumping tasks from with the --dump option.') do |since|
17
+ self[:since] = since
18
+ end
19
+
20
+ o.on('-T', '--through DATE', 'The date (inclusive) to stop dumping tasks from with the --dump option.') do |through|
21
+ self[:through] = through
22
+ end
23
+
24
+ o.on('-t', '--title TITLE', 'What do you want to register?') do |title|
25
+ self[:description] = title
26
+ end
27
+
28
+ o.on('-p', '--project PROJECT', 'Who is going to pay?') do |project|
29
+ self[:project] = project
30
+ end
31
+
32
+ o.on('-s', '--duration DURATION', 'How long did it take?') do |duration|
33
+ self[:duration] = duration
34
+ end
35
+
36
+ o.on('-d', '--date DATE', 'When exactly did it happen?') do |date|
37
+ self[:start] = date
38
+ end
39
+
40
+ o.on('--tasks', 'Show tasks') do |tasks|
41
+ self[:tasks] = tasks
42
+ end
43
+
44
+ o.on('--projects', 'Show projects') do |projects|
45
+ self[:projects] = projects
46
+ end
47
+
48
+ o.on('--delete TASK_ID', 'Delete tasks with id') do |task_id|
49
+ self[:delete] = task_id
50
+ end
51
+
52
+ o.on('-v', '--verbose', 'What\'s happening?') do |debug|
53
+ self[:debug] = debug
54
+ end
55
+
56
+ o.on_tail('-h', '--help', 'Display this help and exit') do
57
+ puts @opts
58
+ exit
59
+ end
60
+
61
+ end
62
+
63
+ begin
64
+ @opts.parse!(args)
65
+ rescue OptionParser::InvalidOption => e
66
+ self[:invalid_argument] = e.message
67
+ end
68
+ end
69
+
70
+ end
71
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: toggler
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 4
9
+ version: 0.2.4
10
+ platform: ruby
11
+ authors:
12
+ - Caleb Harrelson
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-01 00:00:00 -05:00
18
+ default_executable: toggler
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: crack
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - "="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 1
31
+ - 8
32
+ version: 0.1.8
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: httparty
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - "="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ - 6
46
+ - 1
47
+ version: 0.6.1
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: chronic_duration
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ - 9
61
+ - 0
62
+ version: 0.9.0
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: hirb
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ - 3
76
+ - 1
77
+ version: 0.3.1
78
+ type: :runtime
79
+ version_requirements: *id004
80
+ description: Toggler provides a simple REST-style JSON API over standard HTTP - http://www.toggl.com
81
+ email: charrelson@sincerasolutions.com
82
+ executables:
83
+ - toggler
84
+ extensions: []
85
+
86
+ extra_rdoc_files:
87
+ - LICENSE
88
+ - README.rdoc
89
+ files:
90
+ - lib/toggl.rb
91
+ - lib/toggl_cmd/runner.rb
92
+ - lib/toggl_cmd/runner_options.rb
93
+ - LICENSE
94
+ - README.rdoc
95
+ - bin/toggler
96
+ has_rdoc: true
97
+ homepage: http://github.com/atrophic/toggler
98
+ licenses: []
99
+
100
+ post_install_message:
101
+ rdoc_options:
102
+ - --charset=UTF-8
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ segments:
111
+ - 0
112
+ version: "0"
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ requirements: []
122
+
123
+ rubyforge_project: toggler
124
+ rubygems_version: 1.3.7
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Toggl api ruby gem
128
+ test_files: []
129
+