achoo 0.3

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.
@@ -0,0 +1,119 @@
1
+ require 'time'
2
+
3
+ class Achoo; end
4
+
5
+ class Achoo::Timespan
6
+
7
+ SECONDS_IN_A_DAY = 86400
8
+ SECONDS_IN_AN_HOUR = 3600
9
+ SECONDS_IN_A_MINUTE = 60
10
+
11
+ attr :start
12
+ attr :end
13
+
14
+ def initialize(start, end_)
15
+ raise ArgumentError.new('Nil in parameters not allowed') if start.nil? || end_.nil?
16
+
17
+ self.start = start
18
+ self.end = end_
19
+ end
20
+
21
+ def start=(timeish)
22
+ @start = to_time(timeish)
23
+ end
24
+
25
+ def end=(timeish)
26
+ @end = to_time(timeish)
27
+ end
28
+
29
+ def to_s
30
+ duration = duration_string
31
+ from_to = from_to_string
32
+
33
+ sprintf("(%s) %s", duration, from_to)
34
+ end
35
+
36
+ def contains?(timeish_or_timespan)
37
+ if timeish_or_timespan.is_a? Achoo::Timespan
38
+ timespan = timeish_or_timespan
39
+ return start <= timespan.start && self.end >= timespan.end
40
+ else
41
+ time = to_time(timeish_or_timespan)
42
+ return start <= time && self.end >= time
43
+ end
44
+ end
45
+
46
+ def overlaps?(timespan)
47
+ start <= timespan.start && self.end >= timespan.start \
48
+ || start <= timespan.end && self.end >= timespan.end \
49
+ || contains?(timespan) || timespan.contains?(self)
50
+ end
51
+
52
+ private
53
+
54
+ def to_time(timeish)
55
+ case timeish
56
+ when Time
57
+ timeish.clone
58
+ when DateTime
59
+ Time.local(timeish.year, timeish.month, timeish.day, timeish.hour,timeish.minute, timeish.second)
60
+ when Date
61
+ Time.local(timeish.year, timeish.month, timeish.day)
62
+ else
63
+ if timeish.respond_to?(:to_s)
64
+ Time.parse(timeish.to_s)
65
+ else
66
+ raise ArgumentError.new("Don't know how to convert #{timeish.class} to Time")
67
+ end
68
+ end
69
+ end
70
+
71
+
72
+ def duration_string
73
+ delta = @end - @start
74
+ d = delta.to_i / SECONDS_IN_A_DAY
75
+
76
+ delta = delta - d*SECONDS_IN_A_DAY
77
+ h = delta.to_i / SECONDS_IN_AN_HOUR
78
+
79
+ delta = delta - h*SECONDS_IN_AN_HOUR
80
+ m = delta.to_i / SECONDS_IN_A_MINUTE
81
+
82
+ sprintf "%d+%02d:%02d", d, h, m
83
+ end
84
+
85
+ def from_to_string
86
+ today = Date.today
87
+ start_date = start.send(:to_date)
88
+ end_date = self.end.send(:to_date)
89
+
90
+ format = if start_date == today
91
+ "Today"
92
+ elsif start_date.month == today.month &&
93
+ start_date.year == today.year
94
+ "%a %e."
95
+ elsif start_date.year == today.year
96
+ "%a %e. %b"
97
+ else
98
+ "%a %e. %b %Y"
99
+ end
100
+ from = start.strftime(format << " %R")
101
+
102
+ format = if end_date == start_date
103
+ "%R"
104
+ elsif end_date == today
105
+ "Today %R"
106
+ elsif end_date.month == today.month &&
107
+ end_date.year == today.year
108
+ "%a %e. %R"
109
+ elsif end_date.year == today.year
110
+ "%a %e. %b %R"
111
+ else
112
+ "%a %e. %b %Y %R"
113
+ end
114
+ to = self.end.strftime(format)
115
+
116
+ sprintf "%s - %s", from, to
117
+ end
118
+
119
+ end
@@ -0,0 +1,10 @@
1
+ class Achoo; class UI; end; end
2
+
3
+ Achoo::UI.autoload :Commands, 'achoo/ui/commands'
4
+ Achoo::UI.autoload :Common, 'achoo/ui/common'
5
+ Achoo::UI.autoload :DateChooser, 'achoo/ui/date_chooser'
6
+ Achoo::UI.autoload :DateChoosers, 'achoo/ui/date_choosers'
7
+ Achoo::UI.autoload :ExceptionHandling, 'achoo/ui/exception_handling'
8
+ Achoo::UI.autoload :MonthChooser, 'achoo/ui/month_chooser'
9
+ Achoo::UI.autoload :OptionallyRangedDateChooser, 'achoo/ui/optionally_ranged_date_chooser'
10
+ Achoo::UI.autoload :RegisterHours, 'achoo/ui/register_hours'
@@ -0,0 +1,51 @@
1
+ require 'achoo/hour_administration_form'
2
+ require 'achoo/lock_month_form'
3
+ require 'achoo/ui'
4
+
5
+ module Achoo::UI::Commands
6
+
7
+ include Achoo::UI::DateChoosers
8
+ include Achoo::UI::Common
9
+
10
+ def show_registered_hours_for_day(agent)
11
+ date = date_chooser
12
+ form = Achoo::HourAdministrationForm.new(agent)
13
+ form.show_registered_hours_for_day(date)
14
+ end
15
+
16
+ def show_registered_hours_for_week(agent)
17
+ date = date_chooser
18
+ form = Achoo::HourAdministrationForm.new(agent)
19
+ form.show_registered_hours_for_week(date)
20
+ end
21
+
22
+
23
+ def show_flexi_time(agent)
24
+ date = date_chooser
25
+ form = Achoo::HourAdministrationForm.new(agent)
26
+ balance = form.flexi_time(date)
27
+ puts "Flexi time balance: #{Achoo::Term::underline(balance)}"
28
+ end
29
+
30
+
31
+ def lock_month(agent)
32
+ month = month_chooser
33
+ form = Achoo::LockMonthForm.new(agent)
34
+ form.lock_month(month)
35
+ form.print_values
36
+ if confirm
37
+ form.submit
38
+ else
39
+ puts "Cancelled"
40
+ end
41
+ end
42
+
43
+
44
+ def show_holiday_report(agent)
45
+ page = agent.get(RC[:holiday_report_url])
46
+ page.body.match(/<b>(\d+,\d+)<\/b>/)
47
+ puts "Balance: #{Achoo::Term::underline($1)}"
48
+ end
49
+
50
+
51
+ end
@@ -0,0 +1,15 @@
1
+ require 'achoo/term'
2
+
3
+ class Achoo; class UI; end; end
4
+
5
+ module Achoo::UI::Common
6
+
7
+ def confirm
8
+ answer = Achoo::Term::ask "Submit? [Y/n]"
9
+ answer.downcase!
10
+ return answer == 'y' || answer == ''
11
+ end
12
+
13
+ end
14
+
15
+
@@ -0,0 +1,75 @@
1
+ require 'achoo/term'
2
+
3
+ class Achoo; class UI; end; end
4
+
5
+ class Achoo::UI::DateChooser
6
+
7
+ PROMPT = "Date ([today] | ?)"
8
+ FORMAT = " today | (+|-)n | [[[YY]YY]-[M]M]-[D]D"
9
+
10
+ def choose
11
+ loop do
12
+ answer = Achoo::Term::ask PROMPT
13
+ begin
14
+ date = handle_answer(answer)
15
+ return date if date
16
+ rescue ArgumentError => e
17
+ puts e
18
+ end
19
+ end
20
+ end
21
+
22
+ def parse_date(date_str, base=Date.today)
23
+ raise ArgumentError.new('Invalid date') if date_str.nil?
24
+
25
+ # Today (default)
26
+ if date_str == 'today' || date_str.empty?
27
+ return Date.today
28
+ end
29
+
30
+ # Base offset
31
+ case date_str.chars.first
32
+ when '-'
33
+ return base - Integer(date_str[1..-1])
34
+ when '+'
35
+ return base + Integer(date_str[1..-1])
36
+ end
37
+
38
+ #
39
+ date = date_str.split('-').collect {|d| d.to_i}
40
+ case date.length
41
+ when 1
42
+ return Date.civil(base.year, base.month, *date)
43
+ when 2
44
+ return Date.civil(base.year, *date)
45
+ when 3
46
+ date[0] += 2000 if date[0] < 100
47
+ return Date.civil(*date)
48
+ end
49
+
50
+ raise ArgumentError.new('Invalid date')
51
+ end
52
+
53
+ private
54
+
55
+ def handle_answer(answer)
56
+ if answer == '?'
57
+ print_help_message
58
+ return false
59
+ else
60
+ return parse_date(answer)
61
+ end
62
+ end
63
+
64
+ def print_help_message
65
+ puts "Accepted formats:"
66
+ puts date_format_help_string
67
+ puts
68
+ system 'cal -3m'
69
+ end
70
+
71
+ def date_format_help_string
72
+ FORMAT
73
+ end
74
+
75
+ end
@@ -0,0 +1,21 @@
1
+ require 'achoo/ui/date_chooser'
2
+ require 'achoo/ui/optionally_ranged_date_chooser'
3
+ require 'achoo/ui/month_chooser'
4
+
5
+ class Achoo; class UI; end; end
6
+
7
+ module Achoo::UI::DateChoosers
8
+
9
+ def date_chooser
10
+ Achoo::UI::DateChooser.new.choose
11
+ end
12
+
13
+ def optionally_ranged_date_chooser
14
+ Achoo::UI::OptionallyRangedDateChooser.new.choose
15
+ end
16
+
17
+ def month_chooser
18
+ Achoo::UI::MonthChooser.new.choose
19
+ end
20
+
21
+ end
@@ -0,0 +1,20 @@
1
+ require 'achoo/term'
2
+
3
+ class Achoo; class UI; end; end;
4
+
5
+ module Achoo::UI::ExceptionHandling
6
+
7
+ def handle_exception(user_message, e)
8
+ Achoo::Term::warn(user_message) + get_exception_reason(e)
9
+ end
10
+
11
+ def handle_fatal_exception(user_message, e)
12
+ puts Achoo::Term::fatal(user_message) + get_exception_reason(e)
13
+ exit 1
14
+ end
15
+
16
+ def get_exception_reason(e)
17
+ "\nReason: \n\t" + e.message.gsub("\n", "\n\t") + "\n---\n\t" + e.backtrace.join("\n\t")
18
+ end
19
+
20
+ end
@@ -0,0 +1,27 @@
1
+ require 'achoo/term'
2
+
3
+ class Achoo; class UI; end; end
4
+
5
+ class Achoo::UI::MonthChooser
6
+
7
+ def choose
8
+ default = one_month_ago
9
+ period = Achoo::Term::ask "Period ([#{default}] | YYYYMM)"
10
+ period = default if !period || period.empty?
11
+ # FIX validate YYYYMM
12
+ period
13
+ end
14
+
15
+ def one_month_ago
16
+ now = Time.now
17
+ year = now.year
18
+
19
+ # Use -2 + 1 to shift range from 0-11 to 1-12
20
+ month = (now.month - 2)%12 + 1
21
+ year -= 1 if month > now.month
22
+
23
+ sprintf "%d%02d", year, month
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,33 @@
1
+ require 'achoo/ui/date_chooser'
2
+
3
+ class Achoo::UI::OptionallyRangedDateChooser < Achoo::UI::DateChooser
4
+
5
+ def parse_date_range(date_range_str)
6
+ start_date_str, finish_date_str = *date_range_str.split('->')
7
+ start_date = parse_date(start_date_str.strip)
8
+ finish_date = parse_date(finish_date_str.strip, start_date)
9
+
10
+ if start_date >= finish_date
11
+ raise ArgumentError.new('Invalid date range')
12
+ end
13
+
14
+ [start_date, finish_date]
15
+ end
16
+
17
+ private
18
+
19
+ def handle_answer(answer)
20
+ if answer.include? '->'
21
+ return parse_date_range(answer)
22
+ else
23
+ return super
24
+ end
25
+ end
26
+
27
+ def date_format_help_string
28
+ return " DATE [-> DATE]\n" \
29
+ << " DATE:\n" \
30
+ << FORMAT
31
+ end
32
+
33
+ end
@@ -0,0 +1,137 @@
1
+ require 'achoo/awake'
2
+ require 'achoo/hour_registration_form'
3
+ require 'achoo/hour_registration_form_ranged'
4
+ require 'achoo/ical'
5
+ require 'achoo/term'
6
+ require 'achoo/ui'
7
+ require 'achoo/vcs'
8
+ require 'stringio'
9
+
10
+ module Achoo::UI::RegisterHours
11
+
12
+ include Achoo::UI::DateChoosers
13
+ include Achoo::UI::ExceptionHandling
14
+
15
+ def register_hours(agent)
16
+ date = optionally_ranged_date_chooser
17
+
18
+ puts "Fetching data ..."
19
+ form = if date.class == Date
20
+ Achoo::HourRegistrationForm
21
+ else
22
+ Achoo::HourRegistrationFormRanged
23
+ end.new(agent)
24
+
25
+ form.date = date
26
+ form.project = project_chooser(form)
27
+ form.phase = phase_chooser(form)
28
+ print_remark_help(date) if date.class == Date
29
+ form.remark = remark_chooser
30
+ print_hours_help(date) if date.class == Date
31
+ form.hours = hours_chooser
32
+
33
+ answer = Achoo::Term.ask("Do you want to change the defaults for worktime period and/or billing percentage? [N/y]").downcase
34
+ if answer == 'y'
35
+ form.workperiod = workperiod_chooser(form)
36
+ form.billing = billing_chooser(form)
37
+ end
38
+
39
+ form.print_values
40
+ if confirm
41
+ puts "Submitting ..."
42
+ form.submit
43
+ else
44
+ puts "Cancelled"
45
+ end
46
+ end
47
+
48
+
49
+ def phase_chooser(form)
50
+ phases = form.phases_for_selected_project
51
+ puts "Phases"
52
+ answer = Achoo::Term.choose('Phase', phases.collect {|p| p[1] })
53
+ phases[answer.to_i-1][0]
54
+ end
55
+
56
+
57
+ def workperiod_chooser(form)
58
+ periods = form.worktime_periods
59
+ puts "Worktime periods"
60
+ answer = Achoo::Term.choose('Period [1]', periods.collect {|p| p[1] }, nil, [''])
61
+ answer = '1' if answer.empty?
62
+ periods[answer.to_i-1][0]
63
+ end
64
+
65
+ def billing_chooser(form)
66
+ options = form.billing_options
67
+ puts "Billing options"
68
+ answer = Achoo::Term.choose('Billing [1]', options.collect {|p| p[1] }, nil, [''])
69
+ answer = '1' if answer.empty?
70
+ options[answer.to_i-1][0]
71
+ end
72
+
73
+
74
+ def print_hours_help(date)
75
+ puts "Awake log:"
76
+ begin
77
+ awake = Achoo::Awake.new
78
+ awake.at(date)
79
+ puts
80
+ rescue Exception => e
81
+ print handle_exception("Failed to retrieve awake log.", e)
82
+ end
83
+ end
84
+
85
+ def hours_chooser
86
+ answer = Achoo::Term::ask 'Hours [7:30]'
87
+ return answer == '' ? '7.5' : answer
88
+ end
89
+
90
+
91
+ def print_remark_help(date)
92
+ puts "VCS logs for #{date}:"
93
+ begin
94
+ Achoo::VCS.print_logs_for(date, RC[:vcs_dirs])
95
+ rescue Exception => e
96
+ puts handle_exception("Failed to retrieve VCS logs.", e)
97
+ end
98
+ puts '-' * 80
99
+ puts "Calendar events for #{date}:"
100
+ puts '---'
101
+ begin
102
+ RC[:ical].each do |config|
103
+ Achoo::ICal.from_http_request(config).print_events(date)
104
+ end
105
+ rescue Exception => e
106
+ puts handle_exception("Failed to retrieve calendar events.", e)
107
+ end
108
+ end
109
+
110
+ def remark_chooser
111
+ Achoo::Term::ask 'Remark'
112
+ end
113
+
114
+
115
+ def project_chooser(form)
116
+ puts 'Recently used projects'
117
+ projects = form.recent_projects
118
+ answer = Achoo::Term.choose('Project [1]', projects.collect { |p| p[1] },
119
+ 'Other', [''])
120
+ case answer
121
+ when ''
122
+ projects[0][0]
123
+ when '0'
124
+ return all_projects_chooser(form)
125
+ else
126
+ return projects[answer.to_i-1][0]
127
+ end
128
+ end
129
+
130
+
131
+ def all_projects_chooser(form)
132
+ projects = form.all_projects
133
+ answer = Achoo::Term.choose('Project', projects.collect { |p| p[1] })
134
+ projects[answer.to_i-1][0]
135
+ end
136
+
137
+ end