time-sheet 0.8.0 → 0.11.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 +4 -4
- data/README.md +8 -0
- data/lib/time_sheet.rb +15 -0
- data/lib/time_sheet/time.rb +1 -1
- data/lib/time_sheet/time/cmd.rb +9 -0
- data/lib/time_sheet/time/entry.rb +35 -3
- data/lib/time_sheet/time/parser.rb +14 -6
- data/lib/time_sheet/version.rb +1 -1
- data/time_sheet.gemspec +2 -2
- metadata +12 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f5e2d47f0a17abcab7e6b134fd87b176dd753a5dc8022a66d59dcd8b16b3393b
|
|
4
|
+
data.tar.gz: 3335117c3db218920b623aa6759d231f77867b41db80a4bf532a2ba65d334451
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 449c5648f9734732fe4f7c89bdfbcba97b847c213460ac66ecaf69601fd90524ba451cc2abed6b3cb87c1ef1a519218843046b63783e01496e4662f80cb9bb42
|
|
7
|
+
data.tar.gz: cc06940f56f3c29aa6f1dadde4f3c171f9589ffb28abcd3ca1adebfacb5df3b88477d9527b49e5ea7dc44b73a149330489ef84dcf570570e1d6a965ae881543f
|
data/README.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
This gem allows you to parse a spreadsheet with time tracking information to
|
|
4
4
|
generate various statistics suitable for invoices and project effort estimates.
|
|
5
5
|
|
|
6
|
+
## Changelog
|
|
7
|
+
|
|
8
|
+
v0.9.0
|
|
9
|
+
|
|
10
|
+
* introduced employee data column: To use it, add a column "employee" to the
|
|
11
|
+
spreadsheets and use flag `-e` to filter by it. If employee is not set, it
|
|
12
|
+
falls back to "Me".
|
|
13
|
+
|
|
6
14
|
## Installation
|
|
7
15
|
|
|
8
16
|
```bash
|
data/lib/time_sheet.rb
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
require 'time_sheet/version'
|
|
2
2
|
|
|
3
|
+
# silence HTTPClient
|
|
4
|
+
module Warning
|
|
5
|
+
def warn(mgs)
|
|
6
|
+
# drop
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
3
10
|
if defined?(Bundler)
|
|
4
11
|
begin
|
|
5
12
|
require 'pry'
|
|
@@ -15,4 +22,12 @@ module TimeSheet
|
|
|
15
22
|
def self.root
|
|
16
23
|
@root ||= File.expand_path(File.dirname(__FILE__) + '/..')
|
|
17
24
|
end
|
|
25
|
+
|
|
26
|
+
def self.options=(options)
|
|
27
|
+
@options = options
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.options
|
|
31
|
+
@options
|
|
32
|
+
end
|
|
18
33
|
end
|
data/lib/time_sheet/time.rb
CHANGED
|
@@ -41,7 +41,7 @@ module TimeSheet::Time
|
|
|
41
41
|
unless results['entries'].empty?
|
|
42
42
|
time = (
|
|
43
43
|
(options[:to] || Util.day_end) -
|
|
44
|
-
(options[:from] || results['entries'].first[
|
|
44
|
+
(options[:from] || results['entries'].first[1].to_time)
|
|
45
45
|
).to_i
|
|
46
46
|
days = (time.to_f / 60 / 60 / 24).round
|
|
47
47
|
end
|
data/lib/time_sheet/time/cmd.rb
CHANGED
|
@@ -3,6 +3,8 @@ require 'time'
|
|
|
3
3
|
|
|
4
4
|
class TimeSheet::Time::Cmd
|
|
5
5
|
def run
|
|
6
|
+
TimeSheet.options = options
|
|
7
|
+
|
|
6
8
|
if d = options[:from]
|
|
7
9
|
if d.match(/^\d\d?-\d\d?$/)
|
|
8
10
|
d = "#{TimeSheet::Time::Util.now.year}-#{d}"
|
|
@@ -64,6 +66,10 @@ class TimeSheet::Time::Cmd
|
|
|
64
66
|
options[:to] = TimeSheet::Time::Util.month_end(-1)
|
|
65
67
|
options[:summary] = true
|
|
66
68
|
report
|
|
69
|
+
when 'year-to-day', 'year'
|
|
70
|
+
options[:from] = TimeSheet::Time::Util.year_start(-1)
|
|
71
|
+
options[:summary] = true
|
|
72
|
+
report
|
|
67
73
|
when 'overview'
|
|
68
74
|
overview
|
|
69
75
|
else
|
|
@@ -111,11 +117,14 @@ class TimeSheet::Time::Cmd
|
|
|
111
117
|
o.string '-t', '--to', 'ignore entries more recent than the date given'
|
|
112
118
|
o.string '-p', '--project', 'take only entries of this project into account'
|
|
113
119
|
o.string '-a', '--activity', 'take only entries of this activity into account'
|
|
120
|
+
o.string '--tags', 'take only entries with these tags into account (comma separated, not case sensitive)'
|
|
114
121
|
o.string '-d', '--description', 'consider only entries matching this description'
|
|
122
|
+
o.string '-e', '--employee', 'consider only entries for this employee'
|
|
115
123
|
o.float '-r', '--rate', 'use an alternative hourly rate (default: 80.0)', default: 80.00
|
|
116
124
|
o.boolean '-s', '--summary', 'when reporting, add summary section'
|
|
117
125
|
o.boolean '--trim', 'compact the output for processing as CSV', default: false
|
|
118
126
|
o.boolean '-v', '--verbose', 'be more verbose'
|
|
127
|
+
o.boolean '--debug', 'drop into a REPL on errors'
|
|
119
128
|
o.separator "\n invoice options:"
|
|
120
129
|
o.integer '--package', 'for invoice output: build packages of this duration in hours', default: 0
|
|
121
130
|
o.integer '--petty', 'fold records under a certain threshold into a "misc" activity', default: 0
|
|
@@ -41,6 +41,10 @@ class TimeSheet::Time::Entry
|
|
|
41
41
|
)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
def employee
|
|
45
|
+
@employee ||= @data['employee'] || (self.prev ? self.prev.employee : 'Me')
|
|
46
|
+
end
|
|
47
|
+
|
|
44
48
|
# Experiment to add timezone support. However, this would complicate every day
|
|
45
49
|
# handing because of daylight saving time changes.
|
|
46
50
|
# def start_zone
|
|
@@ -74,7 +78,7 @@ class TimeSheet::Time::Entry
|
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
def tags
|
|
77
|
-
(@data['tags']
|
|
81
|
+
self.class.parse_tags(@data['tags'])
|
|
78
82
|
end
|
|
79
83
|
|
|
80
84
|
def working_day?
|
|
@@ -86,7 +90,10 @@ class TimeSheet::Time::Entry
|
|
|
86
90
|
from = from.to_time if from.is_a?(Date)
|
|
87
91
|
to = (filters[:to] ? filters[:to] : nil)
|
|
88
92
|
to = (to + 1).to_time if to.is_a?(Date)
|
|
93
|
+
tags = self.class.parse_tags(filters[:tags])
|
|
89
94
|
|
|
95
|
+
has_tags?(tags) &&
|
|
96
|
+
self.class.attrib_matches_any?(employee, filters[:employee]) &&
|
|
90
97
|
self.class.attrib_matches_any?(description, filters[:description]) &&
|
|
91
98
|
self.class.attrib_matches_any?(project, filters[:project]) &&
|
|
92
99
|
self.class.attrib_matches_any?(activity, filters[:activity]) &&
|
|
@@ -94,12 +101,23 @@ class TimeSheet::Time::Entry
|
|
|
94
101
|
(!to || to >= self.end)
|
|
95
102
|
end
|
|
96
103
|
|
|
104
|
+
def has_tags?(tags)
|
|
105
|
+
return true if tags.empty?
|
|
106
|
+
|
|
107
|
+
tags.all? do |tag|
|
|
108
|
+
self.tags.include?(tag)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
97
112
|
def valid?
|
|
98
113
|
valid!
|
|
99
114
|
true
|
|
100
115
|
rescue TimeSheet::Time::Exception => e
|
|
101
116
|
self.exception = e
|
|
102
117
|
false
|
|
118
|
+
rescue StandardError => e
|
|
119
|
+
binding.pry if Timesheet.options[:debug]
|
|
120
|
+
false
|
|
103
121
|
end
|
|
104
122
|
|
|
105
123
|
def valid!
|
|
@@ -114,14 +132,22 @@ class TimeSheet::Time::Entry
|
|
|
114
132
|
if (self.start >= self.end) && self.next
|
|
115
133
|
raise TimeSheet::Time::Exception.new('time entry has no end')
|
|
116
134
|
end
|
|
135
|
+
|
|
136
|
+
if !employee
|
|
137
|
+
raise TimeSheet::Time::Exception.new('no employee set')
|
|
138
|
+
end
|
|
117
139
|
end
|
|
118
140
|
|
|
119
141
|
def to_row
|
|
120
|
-
[
|
|
142
|
+
[
|
|
143
|
+
employee, date, start, self.end, duration.to_i, project, activity,
|
|
144
|
+
description
|
|
145
|
+
]
|
|
121
146
|
end
|
|
122
147
|
|
|
123
148
|
def to_s
|
|
124
149
|
values = [
|
|
150
|
+
employee,
|
|
125
151
|
date.strftime('%Y-%m-%d'),
|
|
126
152
|
start.strftime('%H:%M'),
|
|
127
153
|
self.end.strftime('%H:%M'),
|
|
@@ -134,13 +160,15 @@ class TimeSheet::Time::Entry
|
|
|
134
160
|
|
|
135
161
|
def to_hash
|
|
136
162
|
return {
|
|
163
|
+
'employee' => employee,
|
|
137
164
|
'date' => date,
|
|
138
165
|
'start' => start,
|
|
139
166
|
'end' => self.end,
|
|
140
167
|
'duration' => duration,
|
|
141
168
|
'project' => project,
|
|
142
169
|
'activity' => activity,
|
|
143
|
-
'description' => description
|
|
170
|
+
'description' => description,
|
|
171
|
+
'tags' => tags
|
|
144
172
|
}
|
|
145
173
|
end
|
|
146
174
|
|
|
@@ -156,4 +184,8 @@ class TimeSheet::Time::Entry
|
|
|
156
184
|
end
|
|
157
185
|
end
|
|
158
186
|
|
|
187
|
+
def self.parse_tags(string)
|
|
188
|
+
(string || '').to_s.downcase.split(/\s*,\s*/).map{|t| t.strip}
|
|
189
|
+
end
|
|
190
|
+
|
|
159
191
|
end
|
|
@@ -68,10 +68,11 @@ class TimeSheet::Time::Parser
|
|
|
68
68
|
|
|
69
69
|
Spreadsheet.open(filename).worksheets.each do |sheet|
|
|
70
70
|
headers = sheet.rows.first.to_a
|
|
71
|
-
sheet.rows[1..-1].each do |row|
|
|
71
|
+
sheet.rows[1..-1].each.with_index do |row, i|
|
|
72
72
|
# TODO find a way to guard against xls sheets with 65535 (empty)
|
|
73
73
|
# lines, perhaps:
|
|
74
74
|
# break if row[1].nil?
|
|
75
|
+
next if row.all?{|cell| [nil, ''].include?(cell)}
|
|
75
76
|
|
|
76
77
|
record = {}
|
|
77
78
|
row.each_with_index do |value, i|
|
|
@@ -84,16 +85,16 @@ class TimeSheet::Time::Parser
|
|
|
84
85
|
results
|
|
85
86
|
end
|
|
86
87
|
|
|
87
|
-
def parse_google_doc(
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
def parse_google_doc(url)
|
|
89
|
+
# Chart Tools datasource protocol, see
|
|
90
|
+
# https://developers.google.com/chart/interactive/docs/querylanguage
|
|
90
91
|
response = HTTPClient.get(url)
|
|
91
92
|
|
|
92
93
|
if response.status == 200
|
|
93
|
-
data = CSV.parse(response.body,
|
|
94
|
+
data = CSV.parse(response.body, liberal_parsing: true)
|
|
94
95
|
headers = data.shift
|
|
95
96
|
data.map do |row|
|
|
96
|
-
record = headers.zip(row).to_h
|
|
97
|
+
record = nullify_empties(headers.zip(row).to_h)
|
|
97
98
|
parse_date_and_time(record)
|
|
98
99
|
end
|
|
99
100
|
else
|
|
@@ -108,9 +109,16 @@ class TimeSheet::Time::Parser
|
|
|
108
109
|
'end' => (record['end'] ? DateTime.parse(record['end']) : nil)
|
|
109
110
|
)
|
|
110
111
|
rescue ArgumentError => e
|
|
112
|
+
binding.pry if TimeSheet.options[:debug]
|
|
111
113
|
puts "current record: #{record.inspect}"
|
|
112
114
|
return {}
|
|
113
115
|
# raise e
|
|
114
116
|
end
|
|
115
117
|
|
|
118
|
+
def nullify_empties(record)
|
|
119
|
+
record.transform_values do |v|
|
|
120
|
+
v == '' ? nil : v
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
116
124
|
end
|
data/lib/time_sheet/version.rb
CHANGED
data/time_sheet.gemspec
CHANGED
|
@@ -28,8 +28,8 @@ Gem::Specification.new do |spec|
|
|
|
28
28
|
spec.add_dependency 'slop'
|
|
29
29
|
spec.add_dependency 'httpclient'
|
|
30
30
|
|
|
31
|
-
spec.add_development_dependency "bundler", "~>
|
|
32
|
-
spec.add_development_dependency "rake", "~>
|
|
31
|
+
spec.add_development_dependency "bundler", "~> 2.2.16"
|
|
32
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
33
33
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
34
34
|
spec.add_development_dependency 'pry'
|
|
35
35
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: time-sheet
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Moritz Schepp
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-06-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: spreadsheet
|
|
@@ -58,28 +58,28 @@ dependencies:
|
|
|
58
58
|
requirements:
|
|
59
59
|
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
61
|
+
version: 2.2.16
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version:
|
|
68
|
+
version: 2.2.16
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
70
|
name: rake
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
73
|
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
75
|
+
version: '13.0'
|
|
76
76
|
type: :development
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
82
|
+
version: '13.0'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: rspec
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -108,7 +108,7 @@ dependencies:
|
|
|
108
108
|
- - ">="
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '0'
|
|
111
|
-
description:
|
|
111
|
+
description:
|
|
112
112
|
email:
|
|
113
113
|
- moritz.schepp@gmail.com
|
|
114
114
|
executables:
|
|
@@ -134,13 +134,13 @@ files:
|
|
|
134
134
|
- lib/time_sheet/time/util.rb
|
|
135
135
|
- lib/time_sheet/version.rb
|
|
136
136
|
- time_sheet.gemspec
|
|
137
|
-
homepage:
|
|
137
|
+
homepage:
|
|
138
138
|
licenses:
|
|
139
139
|
- GPL-3.0-only
|
|
140
140
|
metadata:
|
|
141
141
|
bug_tracker_uri: https://github.com/moritzschepp/time-sheet/issues
|
|
142
142
|
documentation_uri: https://github.com/moritzschepp/time-sheet
|
|
143
|
-
post_install_message:
|
|
143
|
+
post_install_message:
|
|
144
144
|
rdoc_options: []
|
|
145
145
|
require_paths:
|
|
146
146
|
- lib
|
|
@@ -155,9 +155,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
155
155
|
- !ruby/object:Gem::Version
|
|
156
156
|
version: '0'
|
|
157
157
|
requirements: []
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
signing_key:
|
|
158
|
+
rubygems_version: 3.1.6
|
|
159
|
+
signing_key:
|
|
161
160
|
specification_version: 4
|
|
162
161
|
summary: a time tracking solution based on spreadsheets
|
|
163
162
|
test_files: []
|