uur 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Changelog +5 -0
- data/LICENSE +20 -0
- data/README.md +116 -0
- data/bin/uur +5 -0
- data/lib/uur.rb +24 -0
- data/lib/uur/action_executor.rb +39 -0
- data/lib/uur/app.rb +27 -0
- data/lib/uur/args_parser.rb +83 -0
- data/lib/uur/duration_formatters/hours_and_minutes_duration_formatter.rb +12 -0
- data/lib/uur/duration_formatters/hours_in_decimal_notation_duration_formatter.rb +10 -0
- data/lib/uur/entries.rb +16 -0
- data/lib/uur/entries_file_reader.rb +16 -0
- data/lib/uur/entries_parser.rb +31 -0
- data/lib/uur/entry.rb +26 -0
- data/lib/uur/mixins/integer_mixin.rb +14 -0
- data/lib/uur/mixins/time_mixin.rb +72 -0
- data/lib/uur/printers/entries_printer.rb +22 -0
- data/lib/uur/printers/no_entries_message_printer.rb +21 -0
- data/lib/uur/printers/printer.rb +25 -0
- data/lib/uur/printers/summary_printer.rb +39 -0
- data/lib/uur/table_printing/table.rb +21 -0
- data/lib/uur/table_printing/table_printer.rb +31 -0
- data/lib/uur/table_printing/tables_printer.rb +27 -0
- data/lib/uur/time_mapper.rb +34 -0
- data/lib/uur/version.rb +3 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9685f20a21111d9c19d91982f06c6ca324330596
|
4
|
+
data.tar.gz: 6f415d51d2642ef46be4f15326a4b41360761d06
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9ccd7cd9337d558499c3558b6bda2fc22f457bb6c1767c41cdbc39924789c7d351a05e444c49317688026211121ab320dc14e0281595a1bc7a3a5c7caddebe59
|
7
|
+
data.tar.gz: 47a3ea9fbb6e846c925681c97d0b3e76401d6046e686cfbe5adfb596c8a37a89356f66a60dfe427f86939b51bf24db515743097adf682a652fe269e15be82340
|
data/Changelog
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Jacek Mikrut
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
uur
|
2
|
+
===
|
3
|
+
|
4
|
+
**uur** is a command-line tool that reads time log entries from a text file and then generates simple reports.
|
5
|
+
|
6
|
+
Workflow
|
7
|
+
--------
|
8
|
+
1. One adds time entries to `~/.uur/entries` file using one's favourite text editor.
|
9
|
+
2. Then at any time one can use **uur** to print reports.
|
10
|
+
|
11
|
+
Entries file
|
12
|
+
------------
|
13
|
+
|
14
|
+
The entries file should be created at `~/.uur/entries` by the user.
|
15
|
+
|
16
|
+
Each line in this file corresponds to one time entry and has the following format:
|
17
|
+
```
|
18
|
+
YYYY-MM-DD hh:mm - hh:mm Project name Entry description.
|
19
|
+
```
|
20
|
+
- the above data fields should be separated by at least one whitespace character;
|
21
|
+
- the project name should be separated from the entry description by at least two whitespace characters.
|
22
|
+
|
23
|
+
An example:
|
24
|
+
```
|
25
|
+
2013-07-17 10:15 - 10:45 Uur Writing README file content.
|
26
|
+
```
|
27
|
+
|
28
|
+
Blank lines and those that start with `#` (hash) sign are ignored.
|
29
|
+
|
30
|
+
Reports
|
31
|
+
-------
|
32
|
+
|
33
|
+
There are two types of reports: entry lists and entry summaries.
|
34
|
+
|
35
|
+
#### Entries list report
|
36
|
+
|
37
|
+
Simply prints the entries within the specified period, grouped by day.
|
38
|
+
|
39
|
+
An example:
|
40
|
+
```
|
41
|
+
> uur --list --within last-month
|
42
|
+
Mon, 10 June 2013
|
43
|
+
09:15 - 11:45 2h30m Project A Working on task A1
|
44
|
+
11:45 - 12:15 30m Project B Working on task B1
|
45
|
+
15:00 - 16:00 1h00m Project B Completed task A1
|
46
|
+
Total: 4h00m
|
47
|
+
|
48
|
+
Tue, 11 June 2013
|
49
|
+
08:45 - 12:00 3h15m Project A Completed task B1
|
50
|
+
Total: 3h15m
|
51
|
+
|
52
|
+
...
|
53
|
+
```
|
54
|
+
|
55
|
+
#### Entries summary report
|
56
|
+
|
57
|
+
Prints the sum of hours spent on each of the projects within the specified period grouped by the specified interval.
|
58
|
+
|
59
|
+
An example:
|
60
|
+
```
|
61
|
+
> uur --summary monthly --within this-year
|
62
|
+
January 2013
|
63
|
+
Project A 12h15m
|
64
|
+
Project B 120h30m
|
65
|
+
Project C 6h00m
|
66
|
+
Total: 138h45m
|
67
|
+
|
68
|
+
February 2013
|
69
|
+
Project B 140h00m
|
70
|
+
Project C 8h30m
|
71
|
+
Total: 148h30m
|
72
|
+
|
73
|
+
...
|
74
|
+
```
|
75
|
+
|
76
|
+
Usage
|
77
|
+
-----
|
78
|
+
|
79
|
+
```
|
80
|
+
uur [options]
|
81
|
+
|
82
|
+
Reports:
|
83
|
+
-l, --list List entries
|
84
|
+
-s, --summary [daily|weekly|monthly|yearly|all] Print summary
|
85
|
+
|
86
|
+
Filtering:
|
87
|
+
-w, --within today|yesterday|this-week| Time period (default: today)
|
88
|
+
last-week|this-month|last-month|
|
89
|
+
this-year|last-year
|
90
|
+
|
91
|
+
Common options:
|
92
|
+
-d, --duration-format hours-minutes|hours-decimal Duration time format (default: hours-minutes)
|
93
|
+
|
94
|
+
Others:
|
95
|
+
-h, --help Show this help message
|
96
|
+
--version Show version
|
97
|
+
```
|
98
|
+
|
99
|
+
Installation
|
100
|
+
------------
|
101
|
+
|
102
|
+
```
|
103
|
+
gem install uur
|
104
|
+
mkdir ~/.uur
|
105
|
+
touch ~/.uur/entries
|
106
|
+
```
|
107
|
+
|
108
|
+
Author
|
109
|
+
------
|
110
|
+
|
111
|
+
[Jacek Mikrut](https://github.com/jacekmikrut)
|
112
|
+
|
113
|
+
License
|
114
|
+
-------
|
115
|
+
|
116
|
+
License is included in the LICENSE file.
|
data/bin/uur
ADDED
data/lib/uur.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "uur/version"
|
2
|
+
|
3
|
+
require "uur/mixins/integer_mixin"
|
4
|
+
require "uur/mixins/time_mixin"
|
5
|
+
require "uur/time_mapper"
|
6
|
+
require "uur/duration_formatters/hours_and_minutes_duration_formatter"
|
7
|
+
require "uur/duration_formatters/hours_in_decimal_notation_duration_formatter"
|
8
|
+
require "uur/entry"
|
9
|
+
require "uur/entries"
|
10
|
+
require "uur/entries_file_reader"
|
11
|
+
require "uur/entries_parser"
|
12
|
+
require "uur/table_printing/table"
|
13
|
+
require "uur/table_printing/table_printer"
|
14
|
+
require "uur/table_printing/tables_printer"
|
15
|
+
require "uur/printers/printer"
|
16
|
+
require "uur/printers/entries_printer"
|
17
|
+
require "uur/printers/summary_printer"
|
18
|
+
require "uur/printers/no_entries_message_printer"
|
19
|
+
require "uur/args_parser"
|
20
|
+
require "uur/action_executor"
|
21
|
+
require "uur/app"
|
22
|
+
|
23
|
+
module Uur
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Uur
|
2
|
+
class ActionExecutor
|
3
|
+
|
4
|
+
def initialize(options, entries)
|
5
|
+
@options = options
|
6
|
+
@entries = entries
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute
|
10
|
+
if selected_entries.none?
|
11
|
+
Printers::NoEntriesMessagePrinter.new(@options[:within]).print
|
12
|
+
|
13
|
+
elsif @options[:print_summary]
|
14
|
+
Printers::SummaryPrinter.new(duration_formatter).print(selected_entries, @options[:summary_interval])
|
15
|
+
|
16
|
+
elsif @options[:print_list]
|
17
|
+
Printers::EntriesPrinter.new(duration_formatter).print(selected_entries)
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def selected_entries
|
25
|
+
@selected_entries ||= @entries.within(@options[:since], @options[:to])
|
26
|
+
end
|
27
|
+
|
28
|
+
def duration_formatter
|
29
|
+
case @options[:duration_format]
|
30
|
+
when "hours-minutes"
|
31
|
+
DurationFormatters::HoursAndMinutesDurationFormatter.new
|
32
|
+
when "hours-decimal"
|
33
|
+
DurationFormatters::HoursInDecimalNotationDurationFormatter.new
|
34
|
+
else
|
35
|
+
DurationFormatters::HoursAndMinutesDurationFormatter.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/uur/app.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Uur
|
2
|
+
class App
|
3
|
+
|
4
|
+
def run
|
5
|
+
ActionExecutor.new(ArgsParser.new.parse(ARGV), entries).execute
|
6
|
+
rescue EntriesFileReader::CannotReadException => exception
|
7
|
+
$stdout.puts exception.message
|
8
|
+
$stdout.puts "Please make sure the file exists and is readable."
|
9
|
+
end
|
10
|
+
|
11
|
+
def data_dir
|
12
|
+
@data_dir ||= File.expand_path("~/.uur")
|
13
|
+
end
|
14
|
+
|
15
|
+
def entries_file_path
|
16
|
+
@entries_file_path ||= File.join(data_dir, "entries")
|
17
|
+
end
|
18
|
+
|
19
|
+
def entries_file_content
|
20
|
+
@entries_file_content ||= EntriesFileReader.new(entries_file_path).read
|
21
|
+
end
|
22
|
+
|
23
|
+
def entries
|
24
|
+
@entries ||= EntriesParser.new.parse(entries_file_content.lines)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "optparse"
|
2
|
+
|
3
|
+
module Uur
|
4
|
+
class ArgsParser
|
5
|
+
|
6
|
+
def parse(args)
|
7
|
+
options = {}
|
8
|
+
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: uur [options]"
|
11
|
+
|
12
|
+
opts.separator ""
|
13
|
+
opts.separator "Reports:"
|
14
|
+
|
15
|
+
opts.on "-l", "--list", "List entries" do
|
16
|
+
options[:print_list] = true
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on "-s", "--summary [daily|weekly|monthly|yearly|all]", "Print summary" do |value|
|
20
|
+
options[:print_summary] = true
|
21
|
+
options[:summary_interval] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.separator ""
|
25
|
+
opts.separator "Filtering:"
|
26
|
+
|
27
|
+
opts.on "-w", "--within today|yesterday|this-week|last-week|this-month|last-month|this-year|last-year", String, "Time period (default: today)" do |value|
|
28
|
+
options[:within] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.separator ""
|
32
|
+
opts.separator "Common options:"
|
33
|
+
|
34
|
+
opts.on "-d", "--duration-format hours-minutes|hours-decimal", String, "Duration time format (default: hours-minutes)" do |value|
|
35
|
+
options[:duration_format] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.separator ""
|
39
|
+
opts.separator "Others:"
|
40
|
+
|
41
|
+
opts.on_tail "-h", "--help", "Show this help message" do
|
42
|
+
puts opts
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on_tail "--version", "Show version" do
|
47
|
+
puts "uur version #{Uur::VERSION}"
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
|
51
|
+
end.parse!(args)
|
52
|
+
|
53
|
+
options[:within ] ||= "today" if options[:summary_interval].nil?
|
54
|
+
options[:duration_format] ||= "hours-minutes"
|
55
|
+
options[:print_list ] = true if !options[:print_list] && !options[:print_summary]
|
56
|
+
|
57
|
+
set_default_summary_interval(options) if options[:print_summary] && options[:summary_interval].nil?
|
58
|
+
|
59
|
+
if options[:within]
|
60
|
+
time_mapper = TimeMapper.new(Time.now)
|
61
|
+
options[:since] = time_mapper.since(options[:within])
|
62
|
+
options[:to ] = time_mapper.to( options[:within])
|
63
|
+
end
|
64
|
+
|
65
|
+
options
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def set_default_summary_interval(options)
|
71
|
+
options[:summary_interval] = case options[:within]
|
72
|
+
when "today", "yesterday"
|
73
|
+
"daily"
|
74
|
+
when "this-week", "last-week"
|
75
|
+
"weekly"
|
76
|
+
when "this-month", "last-month"
|
77
|
+
"monthly"
|
78
|
+
when "this-year", "last-year"
|
79
|
+
"yearly"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/uur/entries.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Uur
|
2
|
+
class Entries < Array
|
3
|
+
|
4
|
+
def within(since, to)
|
5
|
+
self.class.new(select { |entry| (since.nil? || entry.starts_at >= since) && (to.nil? || entry.ends_at <= to) })
|
6
|
+
end
|
7
|
+
|
8
|
+
def group_by(&block)
|
9
|
+
each_with_object(Hash.new { |hash, key| hash[key] = Entries.new }) { |entry, grouped| grouped[block.call(entry)] << entry }
|
10
|
+
end
|
11
|
+
|
12
|
+
def duration
|
13
|
+
inject(0) { |total, entry| total + entry.duration }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Uur
|
2
|
+
class EntriesFileReader
|
3
|
+
|
4
|
+
CannotReadException = Class.new(Exception)
|
5
|
+
|
6
|
+
def initialize(entries_file_path)
|
7
|
+
@entries_file_path = entries_file_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def read
|
11
|
+
File.read(@entries_file_path)
|
12
|
+
rescue
|
13
|
+
raise CannotReadException, "Cannot read the entries file: #{@entries_file_path}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
module Uur
|
4
|
+
class EntriesParser
|
5
|
+
|
6
|
+
REGEXP = /^\s*(?<date>[\d-]+)\s+(?<start_time>[\d:]+)\s*-\s*(?<end_time>[\d:]+)\s+(?<project_name>.*?)(?=\s\s|$)(?:\s\s+(?<description>.+)$)?/
|
7
|
+
|
8
|
+
def parse(lines)
|
9
|
+
reject_blank_or_commented_lines(lines).each_with_object(Entries.new) do |line, entries|
|
10
|
+
entries << map_data_to_entry(line.match(REGEXP))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def reject_blank_or_commented_lines(lines)
|
17
|
+
lines.reject do |line|
|
18
|
+
line.strip == "" or line =~ /^\s*#/
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def map_data_to_entry(data)
|
23
|
+
Entry.new(
|
24
|
+
starts_at: Time.strptime("#{data[:date]} #{data[:start_time]}", '%Y-%m-%d %H:%M'),
|
25
|
+
ends_at: Time.strptime("#{data[:date]} #{data[ :end_time]}", '%Y-%m-%d %H:%M'),
|
26
|
+
project_name: data[:project_name],
|
27
|
+
description: (data[:description] || "").rstrip,
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/uur/entry.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
|
3
|
+
module Uur
|
4
|
+
class Entry < OpenStruct
|
5
|
+
|
6
|
+
def day
|
7
|
+
starts_at.beginning_of_day
|
8
|
+
end
|
9
|
+
|
10
|
+
def week
|
11
|
+
starts_at.beginning_of_week
|
12
|
+
end
|
13
|
+
|
14
|
+
def month
|
15
|
+
starts_at.beginning_of_month
|
16
|
+
end
|
17
|
+
|
18
|
+
def year
|
19
|
+
starts_at.beginning_of_year
|
20
|
+
end
|
21
|
+
|
22
|
+
def duration
|
23
|
+
(ends_at - starts_at).to_i
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Uur
|
2
|
+
module Mixins
|
3
|
+
module TimeMixin
|
4
|
+
|
5
|
+
def beginning_of_day
|
6
|
+
Time.new(year, month, day)
|
7
|
+
end
|
8
|
+
|
9
|
+
def end_of_day
|
10
|
+
Time.new(year, month, day, 23, 59, 59)
|
11
|
+
end
|
12
|
+
|
13
|
+
def beginning_of_previous_day
|
14
|
+
beginning_of_day - 1.day
|
15
|
+
end
|
16
|
+
|
17
|
+
def end_of_previous_day
|
18
|
+
end_of_day - 1.day
|
19
|
+
end
|
20
|
+
|
21
|
+
def beginning_of_week
|
22
|
+
beginning_of_day - (wday == 0 ? 6 : wday - 1).days
|
23
|
+
end
|
24
|
+
|
25
|
+
def end_of_week
|
26
|
+
(beginning_of_week + 7.days) - 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def beginning_of_previous_week
|
30
|
+
beginning_of_week - 7.days
|
31
|
+
end
|
32
|
+
|
33
|
+
def end_of_previous_week
|
34
|
+
end_of_week - 7.days
|
35
|
+
end
|
36
|
+
|
37
|
+
def beginning_of_month
|
38
|
+
Time.new(year, month)
|
39
|
+
end
|
40
|
+
|
41
|
+
def end_of_month
|
42
|
+
Time.new(year + (month == 12 ? 1 : 0), month % 12 + 1) - 1
|
43
|
+
end
|
44
|
+
|
45
|
+
def beginning_of_previous_month
|
46
|
+
Time.new(end_of_previous_month.year, end_of_previous_month.month)
|
47
|
+
end
|
48
|
+
|
49
|
+
def end_of_previous_month
|
50
|
+
Time.new(year, month) - 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def beginning_of_year
|
54
|
+
Time.new(year)
|
55
|
+
end
|
56
|
+
|
57
|
+
def end_of_year
|
58
|
+
Time.new(year + 1) - 1
|
59
|
+
end
|
60
|
+
|
61
|
+
def beginning_of_previous_year
|
62
|
+
Time.new(year - 1)
|
63
|
+
end
|
64
|
+
|
65
|
+
def end_of_previous_year
|
66
|
+
Time.new(year) - 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Time.send(:include, Uur::Mixins::TimeMixin)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Uur
|
2
|
+
module Printers
|
3
|
+
class EntriesPrinter < Printer
|
4
|
+
|
5
|
+
def print(entries)
|
6
|
+
print_tables entries, :day, proc { |time| time.strftime("%a, %d %B %Y") }
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def alignments
|
12
|
+
[:left, :right, :left, :left]
|
13
|
+
end
|
14
|
+
|
15
|
+
def rows(entries)
|
16
|
+
entries.map do |entry|
|
17
|
+
["#{entry.starts_at.strftime("%H:%M")} - #{entry.ends_at.strftime("%H:%M")}", "#{duration(entry.duration)}", "#{entry.project_name}", "#{entry.description}"]
|
18
|
+
end.push([" Total:", duration(entries.duration), "", ""])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Uur
|
2
|
+
module Printers
|
3
|
+
class NoEntriesMessagePrinter
|
4
|
+
|
5
|
+
def initialize(within_option, out=$stdout)
|
6
|
+
@within_option = within_option
|
7
|
+
@out = out
|
8
|
+
end
|
9
|
+
|
10
|
+
def print
|
11
|
+
@out.puts "No entries within #{within}."
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def within
|
17
|
+
@within ||= @within_option.gsub(/-/, " ")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Uur
|
2
|
+
module Printers
|
3
|
+
class Printer
|
4
|
+
|
5
|
+
def initialize(duration_formatter, out=$stdout)
|
6
|
+
@duration_formatter = duration_formatter
|
7
|
+
@out = out
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def duration(duration)
|
13
|
+
@duration_formatter.format(duration)
|
14
|
+
end
|
15
|
+
|
16
|
+
def print_tables(entries, interval, header_proc)
|
17
|
+
Uur::TablePrinting::TablesPrinter.new(tables(entries, interval, header_proc), alignments, @out).print
|
18
|
+
end
|
19
|
+
|
20
|
+
def tables(entries, interval, header_proc)
|
21
|
+
entries.group_by(&interval).map { |date, entries| Uur::TablePrinting::Table.new(header_proc.call(date), rows(entries)) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Uur
|
2
|
+
module Printers
|
3
|
+
class SummaryPrinter < Printer
|
4
|
+
|
5
|
+
def print(entries, interval=nil)
|
6
|
+
|
7
|
+
case interval
|
8
|
+
when "daily"
|
9
|
+
print_tables entries, :day, proc { |time| time.strftime("%a, %d %B %Y") }
|
10
|
+
|
11
|
+
when "weekly"
|
12
|
+
print_tables entries, :week, proc { |time| "#{time.beginning_of_week.strftime("%d %B %Y")} - #{time.end_of_week.strftime("%d %B %Y")}" }
|
13
|
+
|
14
|
+
when "monthly"
|
15
|
+
print_tables entries, :month, proc { |time| time.strftime("%B %Y") }
|
16
|
+
|
17
|
+
when "yearly"
|
18
|
+
print_tables entries, :year, proc { |time| time.strftime("Year %Y") }
|
19
|
+
|
20
|
+
else
|
21
|
+
print_tables entries, :nil, proc { nil }
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def alignments
|
29
|
+
[:left, :right]
|
30
|
+
end
|
31
|
+
|
32
|
+
def rows(entries)
|
33
|
+
entries.group_by(&:project_name).sort.map do |project_name, project_entries|
|
34
|
+
[project_name, duration(project_entries.duration)]
|
35
|
+
end.push(["Total:", duration(entries.duration)])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Uur
|
2
|
+
module TablePrinting
|
3
|
+
class Table < Array
|
4
|
+
|
5
|
+
def initialize(header, *args)
|
6
|
+
@header = header
|
7
|
+
super(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :header
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
header == other.header && super
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
"Header: #{header.inspect}, content: #{super}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Uur
|
2
|
+
module TablePrinting
|
3
|
+
class TablePrinter
|
4
|
+
|
5
|
+
def initialize(table, widths, alignments, out=$stdout)
|
6
|
+
@table = table
|
7
|
+
@widths = widths
|
8
|
+
@alignments = alignments
|
9
|
+
@out = out
|
10
|
+
end
|
11
|
+
|
12
|
+
def print
|
13
|
+
@out.puts formatted_table.header unless formatted_table.header.nil?
|
14
|
+
@out.puts formatted_table.map { |row_cells| row_cells.join(' ').rstrip }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :table, :widths, :alignments
|
20
|
+
|
21
|
+
def formatted_table
|
22
|
+
@formatted_table ||=
|
23
|
+
Table.new(table.header, table.map do |cells_in_row|
|
24
|
+
cells_in_row.each_with_index.map do |cell, column_index|
|
25
|
+
cell.send({ :left => :ljust, :right => :rjust }[alignments[column_index] || :left], widths[column_index] || 0)
|
26
|
+
end
|
27
|
+
end)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Uur
|
2
|
+
module TablePrinting
|
3
|
+
class TablesPrinter
|
4
|
+
|
5
|
+
def initialize(tables, alignments, out=$stdout)
|
6
|
+
@tables = tables
|
7
|
+
@alignments = alignments
|
8
|
+
@out = out
|
9
|
+
end
|
10
|
+
|
11
|
+
def print
|
12
|
+
tables.each do |table|
|
13
|
+
TablePrinter.new(table, widths, alignments, @out).print
|
14
|
+
@out.puts
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :tables, :alignments
|
21
|
+
|
22
|
+
def widths
|
23
|
+
@widths ||= tables.flatten(1).transpose.map { |rows_in_column| rows_in_column.map(&:length).max }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Uur
|
2
|
+
class TimeMapper
|
3
|
+
|
4
|
+
def initialize(now)
|
5
|
+
@now = now
|
6
|
+
end
|
7
|
+
|
8
|
+
def since(time)
|
9
|
+
case time
|
10
|
+
when "today" then @now.beginning_of_day
|
11
|
+
when "yesterday" then @now.beginning_of_previous_day
|
12
|
+
when "this-week" then @now.beginning_of_week
|
13
|
+
when "last-week" then @now.beginning_of_previous_week
|
14
|
+
when "this-month" then @now.beginning_of_month
|
15
|
+
when "last-month" then @now.beginning_of_previous_month
|
16
|
+
when "this-year" then @now.beginning_of_year
|
17
|
+
when "last-year" then @now.beginning_of_previous_year
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to(time)
|
22
|
+
case time
|
23
|
+
when "today" then @now.end_of_day
|
24
|
+
when "yesterday" then @now.end_of_previous_day
|
25
|
+
when "this-week" then @now.end_of_week
|
26
|
+
when "last-week" then @now.end_of_previous_week
|
27
|
+
when "this-month" then @now.end_of_month
|
28
|
+
when "last-month" then @now.end_of_previous_month
|
29
|
+
when "this-year" then @now.end_of_year
|
30
|
+
when "last-year" then @now.end_of_previous_year
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/uur/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uur
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jacek Mikrut
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2013-07-17 00:00:00 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
prerelease: false
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: "2.0"
|
22
|
+
type: :development
|
23
|
+
version_requirements: *id001
|
24
|
+
description: Generates simple reports from time log entries stored in human-readable text file.
|
25
|
+
email: jacekmikrut.software@gmail.com
|
26
|
+
executables:
|
27
|
+
- uur
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- lib/uur/args_parser.rb
|
34
|
+
- lib/uur/entries_file_reader.rb
|
35
|
+
- lib/uur/action_executor.rb
|
36
|
+
- lib/uur/app.rb
|
37
|
+
- lib/uur/duration_formatters/hours_in_decimal_notation_duration_formatter.rb
|
38
|
+
- lib/uur/duration_formatters/hours_and_minutes_duration_formatter.rb
|
39
|
+
- lib/uur/mixins/integer_mixin.rb
|
40
|
+
- lib/uur/mixins/time_mixin.rb
|
41
|
+
- lib/uur/time_mapper.rb
|
42
|
+
- lib/uur/entries.rb
|
43
|
+
- lib/uur/entries_parser.rb
|
44
|
+
- lib/uur/entry.rb
|
45
|
+
- lib/uur/table_printing/table_printer.rb
|
46
|
+
- lib/uur/table_printing/table.rb
|
47
|
+
- lib/uur/table_printing/tables_printer.rb
|
48
|
+
- lib/uur/printers/summary_printer.rb
|
49
|
+
- lib/uur/printers/entries_printer.rb
|
50
|
+
- lib/uur/printers/no_entries_message_printer.rb
|
51
|
+
- lib/uur/printers/printer.rb
|
52
|
+
- lib/uur/version.rb
|
53
|
+
- lib/uur.rb
|
54
|
+
- README.md
|
55
|
+
- LICENSE
|
56
|
+
- Changelog
|
57
|
+
- bin/uur
|
58
|
+
homepage: http://github.com/jacekmikrut/uur
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
metadata: {}
|
62
|
+
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- &id002
|
71
|
+
- ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- *id002
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 2.0.5
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: Command line time log reporting tool.
|
84
|
+
test_files: []
|
85
|
+
|