freshbookstimestats 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5bba95bba59822e0df7d3f6187da642a167363ba
4
+ data.tar.gz: 8112cbdf0b675d6a6cdfdbe808333b378f1f2906
5
+ SHA512:
6
+ metadata.gz: c305ede55d96feebdc120b74d16a982ccc7e2aab1e4e74840535b600bdaa676ab39db66729ca666906d42d4c7d03fa8da9377a8d63c13b4a5324b2384cbb9999
7
+ data.tar.gz: cad6a12999783a34ec83221dd88124bd915158c9fd71055aba742456a9fcf4b0b6dabb20f4867ee5d1bfbe4bee6dd837207ac776487ce50248703d8b88ad9253
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Amiel Martin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # Project Stats
2
+
3
+ Connect to Freshbooks API to enumerate time spent on one project.
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ gem install freshbookstimestats
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ Create a `~/.freshbooks` with the following:
14
+
15
+ ```shell
16
+ FESHBOOKS_DOMAIN="<subdomain>.freshbooks.com"
17
+ FRESHBOOKS_AUTH_TOKEN="<auth_token>"
18
+ ```
19
+
20
+ Fill in `<subdomain>` and `<auth_token>`. Your `auth_token` can be found on the
21
+ profile page under API Access -> Authentication Token.
22
+
23
+ ## Usage
24
+
25
+ Run
26
+
27
+ ```shell
28
+ freshbookstimestats
29
+ ```
30
+
31
+ and follow the instructions.
32
+
33
+
34
+ That's it!
35
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'freshbookstimestats'
4
+ Freshbookstimestats.load '~/.freshbooks'
5
+ Freshbookstimestats.run
@@ -0,0 +1,156 @@
1
+ require 'freshbooks.rb'
2
+ require 'dotenv'
3
+ require 'terminal-table'
4
+
5
+
6
+ class Task
7
+ def self.find(id)
8
+ memoize :task, id do
9
+ FreshBooks::Task.get id
10
+ end
11
+ end
12
+
13
+ def self.memoize(*keys)
14
+ @_cache ||= {}
15
+ @_cache[keys.join('-')] ||= yield
16
+ end
17
+ end
18
+
19
+ class Projects
20
+ Project = Struct.new(:number, :id, :name)
21
+
22
+ def self.pick_project
23
+ new.select_project
24
+ end
25
+
26
+ def select_project
27
+ puts project_table
28
+ print "\nChoose a project number: "
29
+ project = ask_project_select
30
+ FreshBooks::Project.get project.id if project
31
+ end
32
+
33
+ private
34
+
35
+ def project_table
36
+ Terminal::Table.new do |t|
37
+ t << ['Number', 'Project']
38
+ t << :separator
39
+
40
+ puts "Loading projects..."
41
+ projects.each do |project|
42
+ t << [project.number, project.name]
43
+ end
44
+ end
45
+ end
46
+
47
+ def ask_project_select
48
+ project_number = gets.chomp
49
+ project = find_project_by_number project_number.to_i
50
+ puts "Project not found" if project.nil?
51
+ project
52
+ end
53
+
54
+ def projects
55
+ @_projects ||= FreshBooks::Project.list.map { |project|
56
+ Project.new next_number, project.project_id, project.name
57
+ }
58
+ end
59
+
60
+ def find_project_by_number(number)
61
+ projects.find { |project| project.number == number }
62
+ end
63
+
64
+ def next_number
65
+ @n ||= 0
66
+ @n += 1 # always increment. This way we'll start with 1
67
+ end
68
+ end
69
+
70
+
71
+ class TimeEntries
72
+ def self.render_project(project)
73
+ new(project).render
74
+ end
75
+
76
+ def initialize(project)
77
+ @project = project
78
+ @total_hours = 0
79
+ end
80
+
81
+ def render
82
+ puts time_entries_table
83
+ end
84
+
85
+ private
86
+
87
+ attr_reader :project
88
+
89
+ def time_entries_table
90
+ @_time_entries_table ||= Terminal::Table.new title: title do |t|
91
+ add_header t
92
+ t.add_separator
93
+ add_entries t
94
+ t.add_separator
95
+ add_totals t
96
+ end
97
+ end
98
+
99
+ def title
100
+ project.name
101
+ end
102
+
103
+ def add_header(t)
104
+ t << ['Task', 'Date', 'Hours', 'Notes']
105
+ end
106
+
107
+ def add_entries(t)
108
+ entries.each do |entry|
109
+ task = Task.find entry.task_id
110
+ @total_hours += entry.hours
111
+ t << [task.name, entry.date.inspect, render_hours(entry.hours), entry.notes]
112
+ end
113
+ end
114
+
115
+ def add_totals(t)
116
+ t << [{ value: 'Project Budget', colspan: 2 }, '%7.2f' % project_budget, '']
117
+ t << [{ value: 'Total Hours', colspan: 2 }, '%7.2f' % @total_hours, '']
118
+
119
+ if !project_budget.zero?
120
+ remaining = project_budget - @total_hours
121
+ t << [{ value: 'Time Remaining', colspan: 2 }, '%7.2f' % remaining, '']
122
+ end
123
+ end
124
+
125
+ def render_hours(hours)
126
+ '%7.2f' % hours
127
+ end
128
+
129
+ def project_budget
130
+ @_project_budget ||= project.budget.hours.to_f
131
+ end
132
+
133
+ def entries
134
+ puts "\nLoading...\n\n"
135
+ FreshBooks::TimeEntry.list project_id: project_id
136
+ end
137
+
138
+ def project_id
139
+ project.project_id
140
+ end
141
+ end
142
+
143
+
144
+ module Freshbookstimestats
145
+ def self.load(dir = nil)
146
+ Dotenv.load(*Array(dir))
147
+ FreshBooks::Base.establish_connection(ENV['FRESHBOOKS_DOMAIN'], ENV['FRESHBOOKS_AUTH_TOKEN'])
148
+ end
149
+
150
+ def self.run
151
+ project = Projects.pick_project
152
+ TimeEntries.render_project project if project
153
+ end
154
+ end
155
+
156
+
@@ -0,0 +1,3 @@
1
+ module Freshbookstimestats
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: freshbookstimestats
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Amiel Martin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: freshbooks.rb
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dotenv
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: terminal-table
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: "\n Freshbookstimestats is a simple command line script that asks
84
+ shows a nice table\n of your time entries for a given project.\n "
85
+ email:
86
+ - amiel@carnesmedia.com
87
+ executables:
88
+ - freshbookstimestats
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - LICENSE.txt
93
+ - README.md
94
+ - bin/freshbookstimestats
95
+ - lib/freshbookstimestats.rb
96
+ - lib/freshbookstimestats/version.rb
97
+ homepage: https://github.com/carnesmedia/freshbookstimestats
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.2.0
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: A simple command-line utility to breakdown project hours
121
+ test_files: []