timesheet-reader 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 +7 -0
- data/LICENSE +21 -0
- data/README.md +1 -0
- data/bin/timesheet-reader +7 -0
- data/lib/timesheet_reader.rb +12 -0
- data/lib/timesheet_reader/cli.rb +17 -0
- data/lib/timesheet_reader/converter.rb +22 -0
- data/lib/timesheet_reader/core.rb +74 -0
- data/lib/timesheet_reader/formatter.rb +30 -0
- data/lib/timesheet_reader/parser.rb +82 -0
- metadata +79 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0750b6ab2c99dd8f5656d5e1f4ee2bf7cac9f335e1438e917d6e56181b321a02
|
|
4
|
+
data.tar.gz: 4038ecb7c4ec350955637912ab030a99764fbd514019cf0b50fa932d25ab88a5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a0ee5ded92b1053441e14385d7af111c3c5751a98c4571bbc03333ae0ad974cd5656de3f58b7a5647cfee65263b6914576c309abd3798918125a47b4cf3cda50
|
|
7
|
+
data.tar.gz: 8531c787e91d86379ed2524261c484ed44348e016b669b01d54055e1b225be1162fcb4c177b5ec6e1d0172520d5a9c74be291c739054fcd9d9d66d3e7236d122
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Diego Leal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# timesheet-reader
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'timesheet_reader/cli'
|
|
4
|
+
require 'timesheet_reader/converter'
|
|
5
|
+
require 'timesheet_reader/core'
|
|
6
|
+
require 'timesheet_reader/formatter'
|
|
7
|
+
require 'timesheet_reader/parser'
|
|
8
|
+
require 'timesheet_reader/version'
|
|
9
|
+
|
|
10
|
+
# TimesheetReader module
|
|
11
|
+
module TimesheetReader
|
|
12
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TimesheetReader
|
|
4
|
+
# CLI class
|
|
5
|
+
class CLI
|
|
6
|
+
def run
|
|
7
|
+
input = ARGV
|
|
8
|
+
timesheet_info = TimesheetReader::Core.new(input).run
|
|
9
|
+
|
|
10
|
+
TimesheetReader::Formatter.new.report(
|
|
11
|
+
timesheet_info[:timesheets],
|
|
12
|
+
timesheet_info[:total_hours],
|
|
13
|
+
timesheet_info[:total_minutes]
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TimesheetReader
|
|
4
|
+
# Converter class
|
|
5
|
+
class Converter
|
|
6
|
+
attr_accessor :minute_multiplier
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@minute_multiplier = 60
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def hour_to_minutes(hours)
|
|
13
|
+
hours.to_i * minute_multiplier
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def minutes_to_time(minutes)
|
|
17
|
+
hours = (minutes / @minute_multiplier)
|
|
18
|
+
hours_part = (minutes % @minute_multiplier)
|
|
19
|
+
[hours, hours_part]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TimesheetReader
|
|
4
|
+
# Core class
|
|
5
|
+
class Core
|
|
6
|
+
attr_reader :file_paths, :parser, :converter
|
|
7
|
+
|
|
8
|
+
def initialize(file_paths)
|
|
9
|
+
@file_paths = file_paths
|
|
10
|
+
@parser = TimesheetReader::Parser.new
|
|
11
|
+
@converter = TimesheetReader::Converter.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
timesheets = transform_timesheets(
|
|
16
|
+
generate_timesheets
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
total_time = sum_time(timesheets)
|
|
20
|
+
total_hours, total_minutes = @converter.minutes_to_time(total_time)
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
total_hours: total_hours,
|
|
24
|
+
total_minutes: total_minutes,
|
|
25
|
+
timesheets: timesheets
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def generate_timesheets
|
|
32
|
+
@file_paths.map { |path| @parser.run(path) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def transform_timesheets(timesheets)
|
|
36
|
+
timesheets.map do |timesheet|
|
|
37
|
+
lines = map_time_to_minutes(timesheet[:lines])
|
|
38
|
+
time = calculate_time_diff(lines)
|
|
39
|
+
hours, minutes = @converter.minutes_to_time(time)
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
filename: timesheet[:filename],
|
|
43
|
+
time: time, hours: hours,
|
|
44
|
+
minutes: minutes
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def map_time_to_minutes(timesheet_lines)
|
|
50
|
+
timesheet_lines.map do |line|
|
|
51
|
+
line[:minutes] + @converter.hour_to_minutes(line[:hours])
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def calculate_time_diff(lines_in_minutes)
|
|
56
|
+
diffs = []
|
|
57
|
+
|
|
58
|
+
lines_in_minutes.each_with_index do |time, index|
|
|
59
|
+
next if index.odd?
|
|
60
|
+
|
|
61
|
+
next_time = lines_in_minutes[index + 1]
|
|
62
|
+
diff = next_time - time
|
|
63
|
+
|
|
64
|
+
diffs.push(diff)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
diffs.reduce(0) { |acc, time_diff| acc + time_diff }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def sum_time(timesheets)
|
|
71
|
+
timesheets.reduce(0) { |acc, timesheet| acc + timesheet[:time] }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TimesheetReader
|
|
4
|
+
# Formatter class
|
|
5
|
+
class Formatter
|
|
6
|
+
def report(timesheets, total_hours, total_minutes)
|
|
7
|
+
timesheets.each do |timesheet|
|
|
8
|
+
filename = timesheet[:filename]
|
|
9
|
+
hours = timesheet[:hours]
|
|
10
|
+
minutes = timesheet[:minutes]
|
|
11
|
+
|
|
12
|
+
print_timesheet_line(filename, hours, minutes)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
hours = format_time(total_hours)
|
|
16
|
+
minutes = format_time(total_minutes)
|
|
17
|
+
puts "Total hours: #{hours}:#{minutes}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def format_time(time)
|
|
23
|
+
'%02d' % time
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def print_timesheet_line(filename, hours, minutes)
|
|
27
|
+
puts "#{filename}: #{format_time(hours)}:#{format_time(minutes)} hours"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TimesheetReader
|
|
4
|
+
# ParserError class
|
|
5
|
+
class ParserError < RuntimeError
|
|
6
|
+
def initialize(message)
|
|
7
|
+
super(message)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Parser class
|
|
12
|
+
class Parser
|
|
13
|
+
attr_accessor :keywords, :token
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@keyword = 'timesheet'
|
|
17
|
+
@token = '##'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run(relative_path)
|
|
21
|
+
file = read_file(relative_path)
|
|
22
|
+
lines = build_lines(file[:lines])
|
|
23
|
+
|
|
24
|
+
{ filename: file[:filename], lines: lines }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def read_file(relative_path)
|
|
30
|
+
absolute_path = File.expand_path(relative_path)
|
|
31
|
+
_, filename = File.split(relative_path)
|
|
32
|
+
lines = File.read(absolute_path).squeeze("\n").split("\n")
|
|
33
|
+
|
|
34
|
+
{ filename: filename, lines: lines }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def parse_statements_block(lines)
|
|
38
|
+
token_index = lines.index do |line|
|
|
39
|
+
line.downcase == "#{@token} #{@keyword}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
start_of_block = token_index + 1
|
|
43
|
+
|
|
44
|
+
end_of_block = lines[start_of_block..-1].index do |line|
|
|
45
|
+
line.include?(@token.to_s)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
lines[start_of_block..end_of_block]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def build_lines(lines)
|
|
52
|
+
parsed_lines = parse_statements_block(lines)
|
|
53
|
+
validate_timesheet_intervals(parsed_lines)
|
|
54
|
+
|
|
55
|
+
parsed_lines.map do |line|
|
|
56
|
+
statement, hours, minutes = line.split(':')
|
|
57
|
+
validate_time_sheet_hours(hours)
|
|
58
|
+
validate_time_sheet_minutes(minutes)
|
|
59
|
+
hours = hours.to_i.zero? ? 24 : hours.to_i
|
|
60
|
+
|
|
61
|
+
{ statement: statement, hours: hours, minutes: minutes.to_i }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def validate_timesheet_intervals(lines)
|
|
66
|
+
msg = 'You should close the interval of last line'
|
|
67
|
+
raise ParserError, msg if lines.length.odd?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def validate_time_sheet_hours(hours)
|
|
71
|
+
hours = hours.to_i
|
|
72
|
+
msg = 'You should use a number between 0-23 to define hours'
|
|
73
|
+
raise ParserError, msg if hours.negative? || hours > 23
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def validate_time_sheet_minutes(minutes)
|
|
77
|
+
minutes = minutes.to_i
|
|
78
|
+
msg = 'You should use a number between 0-59 to define minutes'
|
|
79
|
+
raise ParserError, msg if minutes.negative? || minutes > 59
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: timesheet-reader
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Diego Leal
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2019-10-24 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rubocop-rspec
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 1.3.6
|
|
20
|
+
- - "~>"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 1.3.6
|
|
23
|
+
type: :development
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 1.3.6
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 1.3.6
|
|
33
|
+
description: " A timesheet reader.\n"
|
|
34
|
+
email: leal.banks@gmail.com
|
|
35
|
+
executables:
|
|
36
|
+
- timesheet-reader
|
|
37
|
+
extensions: []
|
|
38
|
+
extra_rdoc_files:
|
|
39
|
+
- LICENSE
|
|
40
|
+
- README.md
|
|
41
|
+
files:
|
|
42
|
+
- LICENSE
|
|
43
|
+
- README.md
|
|
44
|
+
- bin/timesheet-reader
|
|
45
|
+
- lib/timesheet_reader.rb
|
|
46
|
+
- lib/timesheet_reader/cli.rb
|
|
47
|
+
- lib/timesheet_reader/converter.rb
|
|
48
|
+
- lib/timesheet_reader/core.rb
|
|
49
|
+
- lib/timesheet_reader/formatter.rb
|
|
50
|
+
- lib/timesheet_reader/parser.rb
|
|
51
|
+
homepage: https://github.com/diego-leal/timesheet-reader
|
|
52
|
+
licenses:
|
|
53
|
+
- MIT
|
|
54
|
+
metadata:
|
|
55
|
+
homepage_uri: https://github.com/diego-leal/timesheet-reader/blob/master/README.md
|
|
56
|
+
changelog_uri: https://github.com/diego-leal/timesheet-reader/blob/master/CHANGELOG.md
|
|
57
|
+
source_code_uri: https://github.com/diego-leal/timesheet-reader/
|
|
58
|
+
documentation_uri: https://github.com/diego-leal/timesheet-reader/
|
|
59
|
+
bug_tracker_uri: https://github.com/diego-leal/timesheet-reader/issues
|
|
60
|
+
post_install_message:
|
|
61
|
+
rdoc_options: []
|
|
62
|
+
require_paths:
|
|
63
|
+
- lib
|
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 2.6.0
|
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '0'
|
|
74
|
+
requirements: []
|
|
75
|
+
rubygems_version: 3.0.3
|
|
76
|
+
signing_key:
|
|
77
|
+
specification_version: 4
|
|
78
|
+
summary: A timesheet reader.
|
|
79
|
+
test_files: []
|