freshtrack 0.4.0 → 0.4.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.
- data/History.txt +5 -0
- data/README.txt +9 -0
- data/bin/freshtrack +15 -0
- data/config/hoe.rb +9 -3
- data/lib/freshbooks/extensions.rb +1 -0
- data/lib/freshtrack/version.rb +1 -1
- data/lib/freshtrack.rb +17 -0
- data/spec/freshtrack_command_spec.rb +73 -0
- data/spec/freshtrack_spec.rb +88 -0
- metadata +27 -5
data/History.txt
CHANGED
data/README.txt
CHANGED
@@ -46,6 +46,15 @@ Freshtrack is used to automatically create time entries in FreshBooks from your
|
|
46
46
|
The 'collector' is what freshtrack will use to gather the time data that will end up as FreshBooks time entries.
|
47
47
|
Freshtrack ships with two collectors: 'punch' and 'one_inch_punch'. These are both gems that can be installed (by `gem install [collector name]`) and used without much effort. If these time-tracking tools aren't to your liking, you are free to write your own collector. Further documentation on that is forthcoming, but for now just take a look at the two collectors that already exist.
|
48
48
|
|
49
|
+
Collector requirements:
|
50
|
+
|
51
|
+
* It needs to be a class in the FreshTrack::TimeCollector namespace
|
52
|
+
* The file needs to exist in the freshtrack/time_collectors directory
|
53
|
+
* The class name should match the file name like Punch -> punch.rb or OneInchPunch -> one_inch_punch.rb
|
54
|
+
* It needs an initializer that takes an options argument (which will be a hash possibly having :before and :after keys with Date values)
|
55
|
+
* It needs a get_time_data instance method which takes a project name argument
|
56
|
+
* The get_time_data method needs to return an array of hashes like { 'date' => date_object, 'hours' => number_of_hours_as_float, 'notes' => notes_string }
|
57
|
+
|
49
58
|
== INSTALL:
|
50
59
|
|
51
60
|
* gem install freshtrack
|
data/bin/freshtrack
CHANGED
@@ -13,6 +13,7 @@ require 'freshtrack'
|
|
13
13
|
require 'optparse'
|
14
14
|
require 'time'
|
15
15
|
|
16
|
+
aging = false
|
16
17
|
OPTIONS = {}
|
17
18
|
MANDATORY_OPTIONS = %w[]
|
18
19
|
|
@@ -31,6 +32,9 @@ BANNER
|
|
31
32
|
"Restrict command to only before the given time") { |time| OPTIONS[:before] = Time.parse(time) }
|
32
33
|
opts.on("-h", "--help",
|
33
34
|
"Show this help message.") { puts opts; exit }
|
35
|
+
opts.on('--aging',
|
36
|
+
"Show invoice aging info.") { aging = true }
|
37
|
+
|
34
38
|
opts.parse!(ARGV)
|
35
39
|
|
36
40
|
if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
|
@@ -38,6 +42,17 @@ BANNER
|
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
45
|
+
if aging
|
46
|
+
Freshtrack.init
|
47
|
+
aging = Freshtrack.invoice_aging
|
48
|
+
printf "%-10s\t%-50s\t%s\t%s\n", 'Invoice', 'Client', 'Age', 'Status'
|
49
|
+
printf "%s\n", '-' * 86
|
50
|
+
aging.each do |inv|
|
51
|
+
printf "%-10s\t%-50s\t%s\t%s\n", *inv.values_at(:number, :client, :age, :status)
|
52
|
+
end
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
41
56
|
project = ARGV.shift
|
42
57
|
|
43
58
|
unless project
|
data/config/hoe.rb
CHANGED
@@ -7,6 +7,13 @@ GEM_NAME = 'freshtrack' # what ppl will type to install your gem
|
|
7
7
|
RUBYFORGE_PROJECT = 'yomendel' # The unix name for your project
|
8
8
|
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
9
9
|
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
10
|
+
EXTRA_DEPENDENCIES = [
|
11
|
+
['freshbooks', '= 2.1']
|
12
|
+
] # An array of rubygem dependencies [name, version]
|
13
|
+
EXTRA_DEV_DEPENDENCIES = [
|
14
|
+
['rspec', '>= 1.1.4'],
|
15
|
+
['mocha', '>= 0.9.1']
|
16
|
+
] # An array of rubygem dependencies [name, version]
|
10
17
|
|
11
18
|
@config_file = "~/.rubyforge/user-config.yml"
|
12
19
|
@config = nil
|
@@ -59,9 +66,8 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
|
59
66
|
|
60
67
|
# == Optional
|
61
68
|
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
62
|
-
p.extra_deps =
|
63
|
-
|
64
|
-
]
|
69
|
+
p.extra_deps = EXTRA_DEPENDENCIES
|
70
|
+
p.extra_dev_deps = EXTRA_DEV_DEPENDENCIES
|
65
71
|
|
66
72
|
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
67
73
|
|
data/lib/freshtrack/version.rb
CHANGED
data/lib/freshtrack.rb
CHANGED
@@ -74,5 +74,22 @@ module Freshtrack
|
|
74
74
|
klass = Freshtrack::TimeCollector.const_get(class_name)
|
75
75
|
klass.new(options)
|
76
76
|
end
|
77
|
+
|
78
|
+
def open_invoices
|
79
|
+
invoices = FreshBooks::Invoice.list || []
|
80
|
+
invoices.select { |i| i.open? }
|
81
|
+
end
|
82
|
+
|
83
|
+
def invoice_aging
|
84
|
+
open_invoices.collect do |i|
|
85
|
+
{
|
86
|
+
:id => i.invoice_id,
|
87
|
+
:number => i.number,
|
88
|
+
:client => i.client.organization,
|
89
|
+
:age => Date.today - i.date,
|
90
|
+
:status => i.status
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
77
94
|
end
|
78
95
|
end
|
@@ -71,4 +71,77 @@ describe 'freshtrack command' do
|
|
71
71
|
Freshtrack.expects(:track).with(@project, {})
|
72
72
|
run_command(@project)
|
73
73
|
end
|
74
|
+
|
75
|
+
describe 'when --aging specified' do
|
76
|
+
before :each do
|
77
|
+
@aging_info = [
|
78
|
+
{ :id => 5, :number => '123', :age => 31, :client => 'blah', :status => 'viewed' },
|
79
|
+
{ :id => 53, :number => '234', :age => 43, :client => 'bang', :status => 'sent' },
|
80
|
+
{ :id => 20, :number => '938', :age => 3, :client => 'boom', :status => 'viewed' }
|
81
|
+
]
|
82
|
+
Freshtrack.stubs(:invoice_aging).returns(@aging_info)
|
83
|
+
self.stubs(:printf)
|
84
|
+
end
|
85
|
+
|
86
|
+
def aging_run
|
87
|
+
run_command('--aging')
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should not require a project' do
|
91
|
+
self.expects(:puts) { |text| text.match(/usage.+project/i) }.never
|
92
|
+
aging_run
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should init Freshtrack' do
|
96
|
+
Freshtrack.expects(:init)
|
97
|
+
aging_run
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should get the invoice aging information' do
|
101
|
+
Freshtrack.expects(:invoice_aging).returns(@aging_info)
|
102
|
+
aging_run
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should print the number of each invoice' do
|
106
|
+
pending 'making this actually test what it purports to'
|
107
|
+
@aging_info.each do |info|
|
108
|
+
self.expects(:printf) { |*args| args.include?(info[:number]) }
|
109
|
+
end
|
110
|
+
aging_run
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should print the client of each invoice' do
|
114
|
+
pending 'making this actually test what it purports to'
|
115
|
+
@aging_info.each do |info|
|
116
|
+
self.expects(:printf) { |*args| args.include?(info[:client]) }
|
117
|
+
end
|
118
|
+
aging_run
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should print the age of each invoice' do
|
122
|
+
pending 'making this actually test what it purports to'
|
123
|
+
@aging_info.each do |info|
|
124
|
+
self.expects(:printf) { |*args| args.include?(info[:age]) }
|
125
|
+
end
|
126
|
+
aging_run
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should print the status of each invoice' do
|
130
|
+
pending 'making this actually test what it purports to'
|
131
|
+
@aging_info.each do |info|
|
132
|
+
self.expects(:printf) { |*args| args.include?(info[:status]) }
|
133
|
+
end
|
134
|
+
aging_run
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should not track time' do
|
138
|
+
Freshtrack.expects(:track).never
|
139
|
+
aging_run
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should not track time even when given a project' do
|
143
|
+
Freshtrack.expects(:track).never
|
144
|
+
run_command('--aging', @project)
|
145
|
+
end
|
146
|
+
end
|
74
147
|
end
|
data/spec/freshtrack_spec.rb
CHANGED
@@ -396,4 +396,92 @@ describe Freshtrack do
|
|
396
396
|
end
|
397
397
|
end
|
398
398
|
end
|
399
|
+
|
400
|
+
it 'should list open invoices' do
|
401
|
+
Freshtrack.should respond_to(:open_invoices)
|
402
|
+
end
|
403
|
+
|
404
|
+
describe 'listing open invoices' do
|
405
|
+
before :each do
|
406
|
+
@invoices = Array.new(5) { stub('invoice', :open? => false) }
|
407
|
+
FreshBooks::Invoice.stubs(:list).returns(@invoices)
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'should get a list of invoices' do
|
411
|
+
FreshBooks::Invoice.expects(:list).returns(@invoices)
|
412
|
+
Freshtrack.open_invoices
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'should return only the open invoices' do
|
416
|
+
open_invoices = @invoices.values_at(0,1,2)
|
417
|
+
open_invoices.each { |i| i.stubs(:open?).returns(true) }
|
418
|
+
|
419
|
+
Freshtrack.open_invoices.should == open_invoices
|
420
|
+
end
|
421
|
+
|
422
|
+
it 'should return an empty array if there are no open invoices' do
|
423
|
+
Freshtrack.open_invoices.should == []
|
424
|
+
end
|
425
|
+
|
426
|
+
it 'should return an empty array if there are no invoices' do
|
427
|
+
FreshBooks::Invoice.stubs(:list).returns([])
|
428
|
+
Freshtrack.open_invoices.should == []
|
429
|
+
end
|
430
|
+
|
431
|
+
it 'should return an empty array if the invoice list returns nil' do
|
432
|
+
FreshBooks::Invoice.stubs(:list).returns(nil)
|
433
|
+
Freshtrack.open_invoices.should == []
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
it 'should show invoice aging' do
|
438
|
+
Freshtrack.should respond_to(:invoice_aging)
|
439
|
+
end
|
440
|
+
|
441
|
+
describe 'showing invoice aging' do
|
442
|
+
before :each do
|
443
|
+
today = Date.today
|
444
|
+
|
445
|
+
@invoices = [
|
446
|
+
stub('invoice', :invoice_id => '1234', :number => '4567', :client => stub('client', :organization => 'client 20'), :date => today - 3, :status => 'partial'),
|
447
|
+
stub('invoice', :invoice_id => '19873', :number => '1456', :client => stub('client', :organization => 'client 3'), :date => today - 20, :status => 'viewed'),
|
448
|
+
stub('invoice', :invoice_id => '0038', :number => '30267', :client => stub('client', :organization => 'client 4'), :date => today - 59, :status => 'sent')
|
449
|
+
]
|
450
|
+
Freshtrack.stubs(:open_invoices).returns(@invoices)
|
451
|
+
end
|
452
|
+
|
453
|
+
it 'should get open invoices' do
|
454
|
+
Freshtrack.expects(:open_invoices).returns(@invoices)
|
455
|
+
Freshtrack.invoice_aging
|
456
|
+
end
|
457
|
+
|
458
|
+
it 'should extract the ID for each open invoice' do
|
459
|
+
ids = @invoices.collect { |i| i.invoice_id }
|
460
|
+
Freshtrack.invoice_aging.collect { |i| i[:id] }.should == ids
|
461
|
+
end
|
462
|
+
|
463
|
+
it 'should extract the number for each open invoice' do
|
464
|
+
numbers = @invoices.collect { |i| i.number }
|
465
|
+
Freshtrack.invoice_aging.collect { |i| i[:number] }.should == numbers
|
466
|
+
end
|
467
|
+
|
468
|
+
it 'should extract the client for each open invoice' do
|
469
|
+
clients = @invoices.collect { |i| i.client.organization }
|
470
|
+
Freshtrack.invoice_aging.collect { |i| i[:client] }.should == clients
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'should extract the age for each open invoice' do
|
474
|
+
Freshtrack.invoice_aging.collect { |i| i[:age] }.should == [3, 20, 59]
|
475
|
+
end
|
476
|
+
|
477
|
+
it 'should extract the status for each open invoice' do
|
478
|
+
statuses = @invoices.collect { |i| i.status }
|
479
|
+
Freshtrack.invoice_aging.collect { |i| i[:status] }.should == statuses
|
480
|
+
end
|
481
|
+
|
482
|
+
it 'should return an empty array if there are no open invoices' do
|
483
|
+
Freshtrack.stubs(:open_invoices).returns([])
|
484
|
+
Freshtrack.invoice_aging.should == []
|
485
|
+
end
|
486
|
+
end
|
399
487
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: freshtrack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yossef Mendelssohn
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-07-31 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,6 +22,26 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: "2.1"
|
24
24
|
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.4
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: mocha
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.9.1
|
44
|
+
version:
|
25
45
|
- !ruby/object:Gem::Dependency
|
26
46
|
name: hoe
|
27
47
|
type: :development
|
@@ -30,7 +50,7 @@ dependencies:
|
|
30
50
|
requirements:
|
31
51
|
- - ">="
|
32
52
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
53
|
+
version: 2.3.2
|
34
54
|
version:
|
35
55
|
description: Track your time on FreshBooks
|
36
56
|
email: ymendel@pobox.com
|
@@ -89,6 +109,8 @@ files:
|
|
89
109
|
- tasks/website.rake
|
90
110
|
has_rdoc: true
|
91
111
|
homepage: http://yomendel.rubyforge.org
|
112
|
+
licenses: []
|
113
|
+
|
92
114
|
post_install_message:
|
93
115
|
rdoc_options:
|
94
116
|
- --main
|
@@ -110,9 +132,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
110
132
|
requirements: []
|
111
133
|
|
112
134
|
rubyforge_project: yomendel
|
113
|
-
rubygems_version: 1.
|
135
|
+
rubygems_version: 1.3.5
|
114
136
|
signing_key:
|
115
|
-
specification_version:
|
137
|
+
specification_version: 3
|
116
138
|
summary: Track your time on FreshBooks
|
117
139
|
test_files: []
|
118
140
|
|