achoo 0.3 → 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/CHANGES +12 -0
- data/README.rdoc +26 -30
- data/Rakefile +3 -0
- data/bin/achoo +2 -2
- data/lib/achoo.rb +1 -139
- data/lib/achoo/achievo.rb +13 -0
- data/lib/achoo/achievo/form.rb +22 -0
- data/lib/achoo/achievo/hour_administration_form.rb +91 -0
- data/lib/achoo/achievo/hour_registration_form.rb +230 -0
- data/lib/achoo/achievo/hour_registration_form_ranged.rb +49 -0
- data/lib/achoo/achievo/lock_month_form.rb +44 -0
- data/lib/achoo/achievo/login_form.rb +27 -0
- data/lib/achoo/achievo/table.rb +30 -0
- data/lib/achoo/app.rb +153 -0
- data/lib/achoo/awake.rb +86 -100
- data/lib/achoo/extensions.rb +24 -0
- data/lib/achoo/ical.rb +47 -40
- data/lib/achoo/rc_loader.rb +42 -42
- data/lib/achoo/system.rb +8 -3
- data/lib/achoo/system/cstruct.rb +67 -0
- data/lib/achoo/system/log_entry.rb +24 -0
- data/lib/achoo/system/pm_suspend.rb +17 -20
- data/lib/achoo/system/utmp_record.rb +64 -0
- data/lib/achoo/system/wtmp.rb +14 -10
- data/lib/achoo/temporal.rb +8 -0
- data/lib/achoo/temporal/open_timespan.rb +16 -0
- data/lib/achoo/temporal/timespan.rb +122 -0
- data/lib/achoo/term.rb +58 -56
- data/lib/achoo/term/menu.rb +2 -2
- data/lib/achoo/term/table.rb +59 -60
- data/lib/achoo/ui.rb +13 -9
- data/lib/achoo/ui/commands.rb +60 -38
- data/lib/achoo/ui/common.rb +10 -7
- data/lib/achoo/ui/date_chooser.rb +69 -65
- data/lib/achoo/ui/date_choosers.rb +15 -14
- data/lib/achoo/ui/exception_handling.rb +14 -12
- data/lib/achoo/ui/month_chooser.rb +37 -24
- data/lib/achoo/ui/optionally_ranged_date_chooser.rb +29 -25
- data/lib/achoo/ui/register_hours.rb +116 -114
- data/lib/achoo/vcs.rb +32 -30
- data/lib/achoo/vcs/git.rb +18 -14
- data/lib/achoo/vcs/subversion.rb +25 -23
- metadata +30 -24
- data/lib/achoo/binary.rb +0 -7
- data/lib/achoo/binary/cstruct.rb +0 -60
- data/lib/achoo/binary/utmp_record.rb +0 -59
- data/lib/achoo/form.rb +0 -18
- data/lib/achoo/hour_administration_form.rb +0 -131
- data/lib/achoo/hour_registration_form.rb +0 -227
- data/lib/achoo/hour_registration_form_ranged.rb +0 -45
- data/lib/achoo/lock_month_form.rb +0 -40
- data/lib/achoo/open_timespan.rb +0 -13
- data/lib/achoo/timespan.rb +0 -119
data/lib/achoo/system/wtmp.rb
CHANGED
@@ -1,18 +1,22 @@
|
|
1
|
-
require 'achoo/binary'
|
2
1
|
require 'achoo/system'
|
3
2
|
|
4
|
-
|
3
|
+
module Achoo
|
4
|
+
module System
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
class Wtmp < Array
|
7
|
+
|
8
|
+
def initialize(glob='/var/log/wtmp*')
|
9
|
+
super()
|
10
|
+
chunk_size = UTMPRecord.bin_size
|
11
|
+
Dir.glob(glob).sort.reverse.each do |file|
|
12
|
+
File.open(file, 'r') do |io|
|
13
|
+
while (bytes = io.read(chunk_size))
|
14
|
+
self << UTMPRecord.new(bytes)
|
15
|
+
end
|
16
|
+
end
|
13
17
|
end
|
14
18
|
end
|
19
|
+
|
15
20
|
end
|
16
21
|
end
|
17
|
-
|
18
22
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'achoo/temporal'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Achoo
|
5
|
+
module Temporal
|
6
|
+
class Timespan
|
7
|
+
|
8
|
+
SECONDS_IN_A_DAY = 86400
|
9
|
+
SECONDS_IN_AN_HOUR = 3600
|
10
|
+
SECONDS_IN_A_MINUTE = 60
|
11
|
+
|
12
|
+
attr :start
|
13
|
+
attr :end
|
14
|
+
|
15
|
+
def initialize(start, end_)
|
16
|
+
raise ArgumentError.new('Nil in parameters not allowed') if start.nil? || end_.nil?
|
17
|
+
|
18
|
+
self.start = start
|
19
|
+
self.end = end_
|
20
|
+
end
|
21
|
+
|
22
|
+
def start=(timeish)
|
23
|
+
@start = to_time(timeish)
|
24
|
+
end
|
25
|
+
|
26
|
+
def end=(timeish)
|
27
|
+
@end = to_time(timeish)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
duration = duration_string
|
32
|
+
from_to = from_to_string
|
33
|
+
|
34
|
+
sprintf("(%s) %s", duration, from_to)
|
35
|
+
end
|
36
|
+
|
37
|
+
def contains?(timeish_or_timespan)
|
38
|
+
if timeish_or_timespan.is_a? Timespan
|
39
|
+
timespan = timeish_or_timespan
|
40
|
+
return start <= timespan.start && self.end >= timespan.end
|
41
|
+
else
|
42
|
+
time = to_time(timeish_or_timespan)
|
43
|
+
return start <= time && self.end >= time
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def overlaps?(timespan)
|
48
|
+
start <= timespan.start && self.end >= timespan.start \
|
49
|
+
|| start <= timespan.end && self.end >= timespan.end \
|
50
|
+
|| contains?(timespan) || timespan.contains?(self)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def to_time(timeish)
|
56
|
+
case timeish
|
57
|
+
when Time
|
58
|
+
timeish.clone
|
59
|
+
when DateTime
|
60
|
+
Time.local(timeish.year, timeish.month, timeish.day, timeish.hour,timeish.minute, timeish.second)
|
61
|
+
when Date
|
62
|
+
Time.local(timeish.year, timeish.month, timeish.day)
|
63
|
+
else
|
64
|
+
if timeish.respond_to?(:to_s)
|
65
|
+
Time.parse(timeish.to_s)
|
66
|
+
else
|
67
|
+
raise ArgumentError.new("Don't know how to convert #{timeish.class} to Time")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def duration_string
|
74
|
+
delta = @end - @start
|
75
|
+
d = delta.to_i / SECONDS_IN_A_DAY
|
76
|
+
|
77
|
+
delta = delta - d*SECONDS_IN_A_DAY
|
78
|
+
h = delta.to_i / SECONDS_IN_AN_HOUR
|
79
|
+
|
80
|
+
delta = delta - h*SECONDS_IN_AN_HOUR
|
81
|
+
m = delta.to_i / SECONDS_IN_A_MINUTE
|
82
|
+
|
83
|
+
sprintf "%d+%02d:%02d", d, h, m
|
84
|
+
end
|
85
|
+
|
86
|
+
def from_to_string
|
87
|
+
today = Date.today
|
88
|
+
start_date = start.send(:to_date)
|
89
|
+
end_date = self.end.send(:to_date)
|
90
|
+
|
91
|
+
format = if start_date == today
|
92
|
+
"Today"
|
93
|
+
elsif start_date.month == today.month &&
|
94
|
+
start_date.year == today.year
|
95
|
+
"%a %e."
|
96
|
+
elsif start_date.year == today.year
|
97
|
+
"%a %e. %b"
|
98
|
+
else
|
99
|
+
"%a %e. %b %Y"
|
100
|
+
end
|
101
|
+
from = start.strftime(format << " %R")
|
102
|
+
|
103
|
+
format = if end_date == start_date
|
104
|
+
"%R"
|
105
|
+
elsif end_date == today
|
106
|
+
"Today %R"
|
107
|
+
elsif end_date.month == today.month &&
|
108
|
+
end_date.year == today.year
|
109
|
+
"%a %e. %R"
|
110
|
+
elsif end_date.year == today.year
|
111
|
+
"%a %e. %b %R"
|
112
|
+
else
|
113
|
+
"%a %e. %b %Y %R"
|
114
|
+
end
|
115
|
+
to = self.end.strftime(format)
|
116
|
+
|
117
|
+
sprintf "%s - %s", from, to
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/achoo/term.rb
CHANGED
@@ -1,76 +1,78 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
3
|
+
require 'achoo'
|
4
4
|
|
5
|
-
Achoo
|
6
|
-
|
5
|
+
module Achoo
|
6
|
+
class Term
|
7
7
|
|
8
|
-
|
8
|
+
autoload :Menu, 'achoo/term/menu'
|
9
|
+
autoload :Table, 'achoo/term/table'
|
9
10
|
|
10
|
-
|
11
|
+
def self.bold(text); "\e[1m#{text}\e[0m"; end
|
12
|
+
|
13
|
+
def self.underline(text); "\e[4m#{text}\e[0m"; end
|
11
14
|
|
12
|
-
|
15
|
+
def self.warn(text); "\e[1;33m#{text}\e[0m"; end
|
13
16
|
|
14
|
-
|
17
|
+
def self.fatal(text); "\e[1;31m#{text}\e[0m"; end
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
pas
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.ask(question='')
|
26
|
-
answer = nil
|
27
|
-
loop do
|
28
|
-
print bold("#{question}> ")
|
29
|
-
$stdout.flush
|
30
|
-
answer = gets
|
19
|
+
def self.password
|
20
|
+
`stty -echo`
|
21
|
+
pas = ask('Password')
|
22
|
+
`stty echo`
|
23
|
+
pas
|
24
|
+
end
|
31
25
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
def self.ask(question='')
|
27
|
+
answer = nil
|
28
|
+
loop do
|
29
|
+
print bold("#{question}> ")
|
30
|
+
$stdout.flush
|
31
|
+
answer = gets
|
32
|
+
|
33
|
+
# Answer is nil if user hits C-d on an empty input
|
34
|
+
if answer.nil?
|
35
|
+
puts
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
|
39
|
+
answer.strip! unless answer.nil?
|
40
|
+
|
41
|
+
# FIX move this to achoo.rb?
|
42
|
+
unless $stdin.tty?
|
43
|
+
puts answer
|
44
|
+
end
|
45
|
+
break unless a_little_something(answer)
|
36
46
|
end
|
37
|
-
|
38
|
-
|
47
|
+
answer
|
48
|
+
end
|
39
49
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
break unless a_little_something(answer)
|
50
|
+
def self.choose(question, entries, special=nil, additional_valid_answers=[])
|
51
|
+
menu = Menu.new(question, entries, special, additional_valid_answers)
|
52
|
+
menu.print_ask_and_validate
|
45
53
|
end
|
46
|
-
answer
|
47
|
-
end
|
48
54
|
|
49
|
-
|
50
|
-
menu = Achoo::Term::Menu.new(question, entries, special, additional_valid_answers)
|
51
|
-
menu.print_ask_and_validate
|
52
|
-
end
|
55
|
+
private
|
53
56
|
|
54
|
-
|
57
|
+
def self.a_little_something(answer)
|
58
|
+
return false if answer.nil?
|
55
59
|
|
56
|
-
|
57
|
-
|
60
|
+
case answer.downcase
|
61
|
+
when 'bless you!', 'gesundheit!'
|
62
|
+
puts "Thank you!"
|
63
|
+
return true
|
64
|
+
else
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
end
|
58
68
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
69
|
+
def self.shadowbox(text)
|
70
|
+
x = "┌──────────────────────────────────────────┐ \n"
|
71
|
+
x << "│ #{text.center(40)} " << "│▒\n"
|
72
|
+
x << "└──────────────────────────────────────────┘▒\n"
|
73
|
+
x << " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\n"
|
74
|
+
x
|
65
75
|
end
|
66
|
-
end
|
67
76
|
|
68
|
-
def self.shadowbox(text)
|
69
|
-
x = "┌──────────────────────────────────────────┐ \n"
|
70
|
-
x << "│ #{text.center(40)} " << "│▒\n"
|
71
|
-
x << "└──────────────────────────────────────────┘▒\n"
|
72
|
-
x << " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\n"
|
73
|
-
x
|
74
77
|
end
|
75
|
-
|
76
78
|
end
|
data/lib/achoo/term/menu.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'achoo/term'
|
2
2
|
|
3
|
-
|
3
|
+
module Achoo
|
4
4
|
class Term
|
5
5
|
class Menu
|
6
6
|
|
@@ -22,7 +22,7 @@ class Achoo
|
|
22
22
|
return '1' if only_one_option?
|
23
23
|
|
24
24
|
loop do
|
25
|
-
answer =
|
25
|
+
answer = Term.ask(@question)
|
26
26
|
if @valid[answer]
|
27
27
|
return answer
|
28
28
|
else
|
data/lib/achoo/term/table.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
require 'jcode'
|
6
|
-
end
|
3
|
+
require 'achoo/term'
|
4
|
+
|
7
5
|
|
8
6
|
# Unicode box drawing characters
|
9
7
|
#
|
@@ -28,74 +26,75 @@ end
|
|
28
26
|
#
|
29
27
|
# 2570 ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
|
30
28
|
|
31
|
-
|
32
|
-
|
33
|
-
class
|
29
|
+
module Achoo
|
30
|
+
class Term
|
31
|
+
class Table
|
34
32
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
def initialize(headers, data_rows, summaries=nil)
|
34
|
+
@headers = headers
|
35
|
+
@data_rows = data_rows
|
36
|
+
@summaries = summaries
|
37
|
+
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
39
|
+
def print(io=$stdout)
|
40
|
+
lengths = calculate_table_cell_widths
|
41
|
+
format = build_format(lengths)
|
42
|
+
headers = center_table_headers(lengths)
|
43
|
+
separator = lengths.map {|length| '─'*(length+2)}
|
46
44
|
|
47
45
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
46
|
+
io.print '┌' << separator.join('┬') << "┐\n"
|
47
|
+
io.print '│ ' << headers.join(' │ ') << " │\n"
|
48
|
+
io.print '├' << separator.join('┼') << "┤\n"
|
49
|
+
@data_rows.each {|r| io.printf format, *r }
|
50
|
+
unless @summaries.nil? || @data_rows.length == 1
|
51
|
+
io.print '├' << separator.join('┼') << "┤\n"
|
52
|
+
io.printf format, *@summaries
|
53
|
+
end
|
54
|
+
io.print '└' << separator.join('┴') << "┘\n"
|
55
|
+
end
|
58
56
|
|
59
|
-
|
57
|
+
private
|
60
58
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
59
|
+
def center_table_headers(lengths)
|
60
|
+
headers = @headers.dup
|
61
|
+
lengths.each_with_index do |len,i|
|
62
|
+
headers[i] = headers[i].center(len)
|
63
|
+
end
|
64
|
+
headers
|
65
|
+
end
|
68
66
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
67
|
+
def calculate_table_cell_widths
|
68
|
+
lengths = []
|
69
|
+
@headers.each_with_index do |h, i|
|
70
|
+
lengths[i] = h.length
|
71
|
+
end
|
72
|
+
@data_rows.each do |r|
|
73
|
+
r.each_with_index do |d, i|
|
74
|
+
lengths[i] = [d.length, lengths[i]].max
|
75
|
+
end
|
76
|
+
end
|
77
|
+
lengths
|
78
78
|
end
|
79
|
-
end
|
80
|
-
lengths
|
81
|
-
end
|
82
79
|
|
83
|
-
|
84
|
-
|
85
|
-
|
80
|
+
def build_format(lengths)
|
81
|
+
is_column_left_justified = Array.new(lengths.count)
|
82
|
+
is_column_left_justified.fill(false)
|
86
83
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
84
|
+
@data_rows.each do |r|
|
85
|
+
r.each_index do |c|
|
86
|
+
if !r[c].strip.empty? && !r[c].match(/^\d+[:.,]?\d*$/)
|
87
|
+
is_column_left_justified[c] = true
|
88
|
+
end
|
89
|
+
end
|
91
90
|
end
|
91
|
+
|
92
|
+
lengths.reduce('│') do |f, l|
|
93
|
+
justify = is_column_left_justified.shift ? '-' : ''
|
94
|
+
f + " %#{justify}#{l}s │"
|
95
|
+
end + "\n"
|
92
96
|
end
|
97
|
+
|
93
98
|
end
|
94
|
-
|
95
|
-
lengths.reduce('│') do |f, l|
|
96
|
-
justify = is_column_left_justified.shift ? '-' : ''
|
97
|
-
f + " %#{justify}#{l}s │"
|
98
|
-
end + "\n"
|
99
99
|
end
|
100
|
-
|
101
100
|
end
|