time-sheet 0.15.0 → 1.1.0
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 +4 -4
- data/README.md +5 -0
- data/lib/time_sheet/time/cmd.rb +28 -15
- data/lib/time_sheet/time/entry.rb +40 -5
- data/lib/time_sheet/time/parser.rb +13 -17
- data/lib/time_sheet/time.rb +7 -3
- data/lib/time_sheet/version.rb +1 -1
- data/time_sheet.gemspec +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35406dddfbbe1e74c061171bea36962f3d5632a6cedc75e846a66e53fc2fbd9e
|
4
|
+
data.tar.gz: deab85ae87457cab458141d8a4987ca592a07d0860405d2ecea1190a316e2b9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e262b17d41fbc239710af28d16d6fa00334d5cb691489e0b017f8864db76ce7b4b5ac7ba7fec53d39b821cda78522bd7fc234680ece63c184adf7b9275dcddee
|
7
|
+
data.tar.gz: 03eb5990ebcae8f95d4931a1dcaa7b5469e54f6eac61fcfe20a752c8e0c75146028b066153fed8bb8d46afeff5fa417235418db7ea82120694f224626c9ccbcc
|
data/README.md
CHANGED
@@ -5,6 +5,11 @@ generate various statistics suitable for invoices and project effort estimates.
|
|
5
5
|
|
6
6
|
## Changelog
|
7
7
|
|
8
|
+
v1.0.0
|
9
|
+
|
10
|
+
* input files need to be .xlsx files now because we needed to replace the
|
11
|
+
spreadsheet parser, see [#1](https://github.com/moritzschepp/time-sheet/issues/1)
|
12
|
+
|
8
13
|
v0.9.0
|
9
14
|
|
10
15
|
* introduced employee data column: To use it, add a column "employee" to the
|
data/lib/time_sheet/time/cmd.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
1
3
|
require 'slop'
|
2
4
|
require 'time'
|
3
5
|
|
4
6
|
class TimeSheet::Time::Cmd
|
5
7
|
def run
|
6
|
-
TimeSheet.options = options
|
7
|
-
|
8
8
|
if d = options[:from]
|
9
9
|
if d.match(/^\d\d?-\d\d?$/)
|
10
10
|
d = "#{TimeSheet::Time::Util.now.year}-#{d}"
|
@@ -85,17 +85,22 @@ class TimeSheet::Time::Cmd
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
88
|
+
def config
|
89
|
+
defaults = {
|
90
|
+
'location' => "#{ENV['HOME']}/time",
|
91
|
+
'carry_over_activity' => 'previous'
|
92
|
+
}
|
93
|
+
|
94
|
+
file = "#{ENV['HOME']}/.time-sheet.conf"
|
95
|
+
return defaults unless File.exist?(file)
|
96
|
+
|
97
|
+
result = YAML.load_file(file)
|
98
|
+
result = defaults.merge(result)
|
99
|
+
|
100
|
+
unless result['location'].is_a?(Array)
|
101
|
+
result['location'] = [result['location']]
|
97
102
|
end
|
98
|
-
|
103
|
+
|
99
104
|
result
|
100
105
|
end
|
101
106
|
|
@@ -114,7 +119,7 @@ class TimeSheet::Time::Cmd
|
|
114
119
|
o.boolean '-h', '--help', 'show help'
|
115
120
|
o.boolean '--version', 'show the version'
|
116
121
|
o.array('-l', '--location', 'a location to gather data from (file, directory or google docs share-url)',
|
117
|
-
default:
|
122
|
+
default: config['location']
|
118
123
|
)
|
119
124
|
o.string '-f', '--from', 'ignore entries older than the date given'
|
120
125
|
o.string '-t', '--to', 'ignore entries more recent than the date given'
|
@@ -123,6 +128,12 @@ class TimeSheet::Time::Cmd
|
|
123
128
|
o.string '--tags', 'filter by tag (comma separated, not case sensitive, prefix tag with ! to exclude)'
|
124
129
|
o.string '-d', '--description', 'consider only entries matching this description'
|
125
130
|
o.string '-e', '--employee', 'consider only entries for this employee'
|
131
|
+
o.string(
|
132
|
+
'--carry-over-activity',
|
133
|
+
'mode for carrying over activity (previous|same-description)',
|
134
|
+
default: config['carry_over_activity']
|
135
|
+
)
|
136
|
+
o.string '--default-activity', 'default activity', default: config['default_activity']
|
126
137
|
o.float '-r', '--rate', 'use an alternative hourly rate (default: 80.0)', default: 80.00
|
127
138
|
o.boolean '-s', '--summary', 'when reporting, add summary section'
|
128
139
|
o.boolean '--trim', 'compact the output for processing as CSV', default: false
|
@@ -148,6 +159,8 @@ class TimeSheet::Time::Cmd
|
|
148
159
|
end
|
149
160
|
|
150
161
|
def verify
|
162
|
+
TimeSheet.options = options
|
163
|
+
|
151
164
|
convert_to_time
|
152
165
|
|
153
166
|
entries = TimeSheet::Time::Parser.new(options[:location]).entries
|
@@ -222,7 +235,7 @@ class TimeSheet::Time::Cmd
|
|
222
235
|
TimeSheet::Time::Util.hours(pdata['total']),
|
223
236
|
TimeSheet::Time::Util.price(pdata['total'], options[:rate])
|
224
237
|
]
|
225
|
-
|
238
|
+
|
226
239
|
pdata['activities'].sort_by{|k, v| v}.reverse.to_h.each do |aname, atotal|
|
227
240
|
tdata << [
|
228
241
|
'',
|
@@ -250,4 +263,4 @@ class TimeSheet::Time::Cmd
|
|
250
263
|
end
|
251
264
|
end
|
252
265
|
|
253
|
-
end
|
266
|
+
end
|
@@ -14,7 +14,20 @@ class TimeSheet::Time::Entry
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def activity
|
17
|
-
|
17
|
+
if !@data['activity']
|
18
|
+
carry_mode = TimeSheet.options[:carry_over_activity]
|
19
|
+
use_prev = (
|
20
|
+
carry_mode == 'previous' ||
|
21
|
+
(carry_mode == 'same-description' && self.prev.description == description)
|
22
|
+
)
|
23
|
+
if use_prev
|
24
|
+
@data['activity'] = self.prev.activity
|
25
|
+
end
|
26
|
+
|
27
|
+
@data['activity'] ||= TimeSheet.options[:default_activity] || ''
|
28
|
+
end
|
29
|
+
|
30
|
+
@data['activity']
|
18
31
|
end
|
19
32
|
|
20
33
|
def description
|
@@ -28,7 +41,8 @@ class TimeSheet::Time::Entry
|
|
28
41
|
def start
|
29
42
|
@start ||= Time.mktime(
|
30
43
|
date.year, date.month, date.day,
|
31
|
-
@data['start']
|
44
|
+
hour_for(@data['start']),
|
45
|
+
minute_for(@data['start'])
|
32
46
|
)
|
33
47
|
end
|
34
48
|
|
@@ -37,10 +51,31 @@ class TimeSheet::Time::Entry
|
|
37
51
|
|
38
52
|
@end ||= Time.mktime(
|
39
53
|
date.year, date.month, date.day,
|
40
|
-
ends_at
|
54
|
+
hour_for(ends_at),
|
55
|
+
minute_for(ends_at)
|
41
56
|
)
|
42
57
|
end
|
43
58
|
|
59
|
+
def hour_for(timish)
|
60
|
+
case timish
|
61
|
+
when Time then timish.hour
|
62
|
+
when DateTime then timish.hour
|
63
|
+
when Integer then timish / 60 / 60
|
64
|
+
else
|
65
|
+
binding.pry
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def minute_for(timish)
|
70
|
+
case timish
|
71
|
+
when Time then timish.min
|
72
|
+
when DateTime then timish.min
|
73
|
+
when Integer then timish / 60 % 60
|
74
|
+
else
|
75
|
+
binding.pry
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
44
79
|
def employee
|
45
80
|
@employee ||= @data['employee'] || (self.prev ? self.prev.employee : 'Me')
|
46
81
|
end
|
@@ -121,7 +156,7 @@ class TimeSheet::Time::Entry
|
|
121
156
|
self.exception = e
|
122
157
|
false
|
123
158
|
rescue StandardError => e
|
124
|
-
binding.pry if
|
159
|
+
binding.pry if TimeSheet.options[:debug]
|
125
160
|
false
|
126
161
|
end
|
127
162
|
|
@@ -193,4 +228,4 @@ class TimeSheet::Time::Entry
|
|
193
228
|
(string || '').to_s.downcase.split(/\s*,\s*/).map{|t| t.strip}
|
194
229
|
end
|
195
230
|
|
196
|
-
end
|
231
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'roo'
|
2
2
|
require 'httpclient'
|
3
3
|
require 'csv'
|
4
4
|
|
@@ -12,7 +12,7 @@ class TimeSheet::Time::Parser
|
|
12
12
|
results = []
|
13
13
|
@dirs.each do |dir|
|
14
14
|
if File.directory?(dir)
|
15
|
-
results += Dir["#{dir}/**/*.
|
15
|
+
results += Dir["#{dir}/**/*.xlsx"]
|
16
16
|
else
|
17
17
|
results << dir
|
18
18
|
end
|
@@ -38,7 +38,6 @@ class TimeSheet::Time::Parser
|
|
38
38
|
results.sort!
|
39
39
|
results.each do |r|
|
40
40
|
unless r.valid?
|
41
|
-
# byebug
|
42
41
|
raise r.exception, [
|
43
42
|
r.exception.message, ': ',
|
44
43
|
r.data.inspect,
|
@@ -57,28 +56,25 @@ class TimeSheet::Time::Parser
|
|
57
56
|
if f.match(/https:\/\/docs\.google\.com/)
|
58
57
|
parse_google_doc(f)
|
59
58
|
else
|
60
|
-
|
59
|
+
parse_xlsx(f)
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
64
63
|
end
|
65
64
|
|
66
|
-
def
|
65
|
+
def parse_xlsx(filename)
|
67
66
|
results = []
|
68
67
|
|
69
|
-
Spreadsheet.open(filename)
|
70
|
-
|
71
|
-
sheet.
|
72
|
-
|
73
|
-
# lines, perhaps:
|
74
|
-
# break if row[1].nil?
|
75
|
-
next if row.all?{|cell| [nil, ''].include?(cell)}
|
68
|
+
xlsx = Roo::Spreadsheet.open(filename)
|
69
|
+
xlsx.each_with_pagename do |name, sheet|
|
70
|
+
headers = sheet.row(1).to_h{|e| [e, e]}
|
71
|
+
rows = sheet.parse(headers)
|
76
72
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
results <<
|
73
|
+
rows.each do |row|
|
74
|
+
next unless row['start']
|
75
|
+
next if row.values.all?{|v| [nil, ''].include?(v)}
|
76
|
+
|
77
|
+
results << row
|
82
78
|
end
|
83
79
|
end
|
84
80
|
|
data/lib/time_sheet/time.rb
CHANGED
@@ -8,6 +8,8 @@ module TimeSheet::Time
|
|
8
8
|
autoload :Util, 'time_sheet/time/util'
|
9
9
|
|
10
10
|
def self.report(options)
|
11
|
+
TimeSheet.options = options
|
12
|
+
|
11
13
|
results = {
|
12
14
|
'entries' => [],
|
13
15
|
'total' => 0.0,
|
@@ -40,7 +42,7 @@ module TimeSheet::Time
|
|
40
42
|
|
41
43
|
unless results['entries'].empty?
|
42
44
|
time = (
|
43
|
-
(options[:to] || Util.day_end) -
|
45
|
+
(options[:to] || Util.day_end) -
|
44
46
|
(options[:from] || results['entries'].first[1].to_time)
|
45
47
|
).to_i
|
46
48
|
days = (time.to_f / 60 / 60 / 24).round
|
@@ -65,6 +67,8 @@ module TimeSheet::Time
|
|
65
67
|
end
|
66
68
|
|
67
69
|
def self.invoice(options)
|
70
|
+
TimeSheet.options = options
|
71
|
+
|
68
72
|
grouped = {}
|
69
73
|
Parser.new(options[:location]).entries.each do |e|
|
70
74
|
if e.matches?(options)
|
@@ -102,7 +106,7 @@ module TimeSheet::Time
|
|
102
106
|
next
|
103
107
|
end
|
104
108
|
end
|
105
|
-
|
109
|
+
|
106
110
|
packages.last << row
|
107
111
|
ptotal += row[1]
|
108
112
|
end
|
@@ -131,4 +135,4 @@ module TimeSheet::Time
|
|
131
135
|
packages
|
132
136
|
end
|
133
137
|
|
134
|
-
end
|
138
|
+
end
|
data/lib/time_sheet/version.rb
CHANGED
data/time_sheet.gemspec
CHANGED
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: time-sheet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Moritz Schepp
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: roo
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -141,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
141
|
- !ruby/object:Gem::Version
|
142
142
|
version: '0'
|
143
143
|
requirements: []
|
144
|
-
rubygems_version: 3.5.
|
144
|
+
rubygems_version: 3.5.22
|
145
145
|
signing_key:
|
146
146
|
specification_version: 4
|
147
147
|
summary: a time tracking solution based on spreadsheets
|