ascii-tracker 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/.document +5 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/ascii-tracker.gemspec +36 -0
- data/bin/atracker.rb +48 -0
- data/lib/asciitracker/app.rb +189 -0
- data/lib/asciitracker/controller.rb +62 -0
- data/lib/asciitracker/hash_ext.rb +15 -0
- data/lib/asciitracker/hhmm.rb +58 -0
- data/lib/asciitracker/model.rb +37 -0
- data/lib/asciitracker/parser.rb +86 -0
- data/lib/asciitracker/rdparser.rb +168 -0
- data/lib/asciitracker/record.rb +27 -0
- data/lib/asciitracker/slot.rb +109 -0
- data/lib/asciitracker/version.rb +3 -0
- data/lib/asciitracker.rb +16 -0
- data/spec/model/controller_spec.rb +130 -0
- data/spec/model/hhmm_spec.rb +85 -0
- data/spec/model/model_spec.rb +71 -0
- data/spec/model/record_spec.rb +33 -0
- data/spec/model/slot_spec.rb +207 -0
- data/spec/spec_helper.rb +12 -0
- metadata +149 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
=begin
|
2
|
+
Subject: ***Re: [SOLUTION] Dice Roller (#61)*
|
3
|
+
From: *Dennis Ranke *<mail exoticorn.de>
|
4
|
+
Date: Mon, 9 Jan 2006 06:53:10 +0900
|
5
|
+
References: 174521 </cgi-bin/scat.rb/ruby/ruby-talk/174521> 174811
|
6
|
+
</cgi-bin/scat.rb/ruby/ruby-talk/174811>
|
7
|
+
In-reply-to: 174811 </cgi-bin/scat.rb/ruby/ruby-talk/174811>
|
8
|
+
|
9
|
+
Hi,
|
10
|
+
|
11
|
+
here is my second solution. Quite a bit longer, but a lot nicer.
|
12
|
+
For this I implemented a simple recursive descent parser class that
|
13
|
+
allows the tokens and the grammar to be defined in a very clean ruby
|
14
|
+
syntax. I think I'd really like to see a production quality
|
15
|
+
parser(generator) using something like this grammar format.
|
16
|
+
=end
|
17
|
+
|
18
|
+
class RDParser
|
19
|
+
attr_accessor :pos
|
20
|
+
attr_reader :rules
|
21
|
+
|
22
|
+
def initialize(&block)
|
23
|
+
@lex_tokens = []
|
24
|
+
@rules = {}
|
25
|
+
@start = nil
|
26
|
+
instance_eval(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse(string)
|
30
|
+
@tokens = []
|
31
|
+
until string.empty?
|
32
|
+
raise "unable to lex '#{string}" unless @lex_tokens.any? do |tok|
|
33
|
+
#puts "match(#{string}) <- (#{tok})"
|
34
|
+
match = tok.pattern.match(string)
|
35
|
+
if match
|
36
|
+
s_tok = match.to_s
|
37
|
+
puts "(#{s_tok})" unless /^\s+$/.match(s_tok)
|
38
|
+
#puts "<<< #{s_tok} | #{tok.pattern} >>>"
|
39
|
+
@tokens << tok.block.call(s_tok) if tok.block
|
40
|
+
string = match.post_match
|
41
|
+
#puts "<<<#{s_tok}|||#{match.post_match}>>>"
|
42
|
+
true
|
43
|
+
else
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@pos = 0
|
49
|
+
@max_pos = 0
|
50
|
+
@expected = []
|
51
|
+
result = @start.parse
|
52
|
+
if @pos != @tokens.size
|
53
|
+
raise "Parse error. expected: '#{@expected.join(', ')}', found
|
54
|
+
'#{@tokens[@max_pos]}'"
|
55
|
+
end
|
56
|
+
return result
|
57
|
+
end
|
58
|
+
|
59
|
+
def next_token
|
60
|
+
@pos += 1
|
61
|
+
return @tokens[@pos - 1]
|
62
|
+
end
|
63
|
+
|
64
|
+
def expect(tok)
|
65
|
+
t = next_token
|
66
|
+
if @pos - 1 > @max_pos
|
67
|
+
@max_pos = @pos - 1
|
68
|
+
@expected = []
|
69
|
+
end
|
70
|
+
return t if tok === t
|
71
|
+
@expected << tok if @max_pos == @pos - 1 && !@expected.include?(tok)
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
LexToken = Struct.new(:pattern, :block)
|
78
|
+
|
79
|
+
def token(pattern, &block)
|
80
|
+
@lex_tokens << LexToken.new(Regexp.new('\\A' + pattern.source), block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def start(name, &block)
|
84
|
+
rule(name, &block)
|
85
|
+
@start = @rules[name]
|
86
|
+
end
|
87
|
+
|
88
|
+
def rule(name)
|
89
|
+
@current_rule = Rule.new(name, self)
|
90
|
+
@rules[name] = @current_rule
|
91
|
+
yield
|
92
|
+
@current_rule = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def match(*pattern, &block)
|
96
|
+
@current_rule.add_match(pattern, block)
|
97
|
+
end
|
98
|
+
|
99
|
+
class Rule
|
100
|
+
Match = Struct.new :pattern, :block
|
101
|
+
|
102
|
+
def initialize(name, parser)
|
103
|
+
@name = name
|
104
|
+
@parser = parser
|
105
|
+
@matches = []
|
106
|
+
@lrmatches = []
|
107
|
+
end
|
108
|
+
|
109
|
+
def add_match(pattern, block)
|
110
|
+
match = Match.new(pattern, block)
|
111
|
+
if pattern[0] == @name
|
112
|
+
pattern.shift
|
113
|
+
@lrmatches << match
|
114
|
+
else
|
115
|
+
@matches << match
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def parse
|
120
|
+
match_result = try_matches(@matches)
|
121
|
+
return nil unless match_result
|
122
|
+
loop do
|
123
|
+
result = try_matches(@lrmatches, match_result)
|
124
|
+
return match_result unless result
|
125
|
+
match_result = result
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def try_matches(matches, pre_result = nil)
|
132
|
+
match_result = nil
|
133
|
+
start = @parser.pos
|
134
|
+
matches.each do |match|
|
135
|
+
r = pre_result ? [pre_result] : []
|
136
|
+
match.pattern.each do |token|
|
137
|
+
if @parser.rules[token]
|
138
|
+
r << @parser.rules[token].parse
|
139
|
+
unless r.last
|
140
|
+
r = nil
|
141
|
+
break
|
142
|
+
end
|
143
|
+
else
|
144
|
+
nt = @parser.expect(token)
|
145
|
+
if nt
|
146
|
+
r << nt
|
147
|
+
else
|
148
|
+
r = nil
|
149
|
+
break
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
if r
|
154
|
+
if match.block
|
155
|
+
match_result = match.block.call(*r)
|
156
|
+
else
|
157
|
+
match_result = r[0]
|
158
|
+
end
|
159
|
+
break
|
160
|
+
else
|
161
|
+
@parser.pos = start
|
162
|
+
end
|
163
|
+
end
|
164
|
+
return match_result
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module AsciiTracker
|
2
|
+
class Record
|
3
|
+
|
4
|
+
attr_reader :date, :span, :desc
|
5
|
+
|
6
|
+
def to_s; "#{date} #{HHMM.new(span)} #{desc}" end
|
7
|
+
|
8
|
+
Defaults = { :date => Date.today, :span => 0.0, :desc => nil }
|
9
|
+
|
10
|
+
# span may be any valid HHMM format
|
11
|
+
# value keys: :date, :span, and :desc
|
12
|
+
def initialize values = {} #date, span, desc = nil
|
13
|
+
values = Defaults.merge(values)
|
14
|
+
@date = values[:date]
|
15
|
+
@span = HHMM.new(values[:span]).to_f
|
16
|
+
@desc = values[:desc]
|
17
|
+
end
|
18
|
+
|
19
|
+
# 35.25 -> [1, 11, 15]
|
20
|
+
def self.hours_to_dhm(hours)
|
21
|
+
d = hours.to_i / 8
|
22
|
+
h = (hours - 8*d).to_i
|
23
|
+
m = ((60 * (hours - 8*d - h)) + 0.5).to_i
|
24
|
+
[d, h, m]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module AsciiTracker
|
2
|
+
class Slot < Record
|
3
|
+
|
4
|
+
attr_reader :t_start, :t_end
|
5
|
+
|
6
|
+
def to_s; "#{date} #{t_start}-#{t_end} #{desc}" end
|
7
|
+
|
8
|
+
# supports Record values keys plus:
|
9
|
+
# :start and :end
|
10
|
+
# for interval definition
|
11
|
+
def initialize values = {}
|
12
|
+
values = Defaults.merge(values)
|
13
|
+
@t_start = HHMM.new(values[:start])
|
14
|
+
@t_end = HHMM.new(values[:end])
|
15
|
+
|
16
|
+
@duration = (@t_end - @t_start)
|
17
|
+
super values.merge(:span => @duration.to_f)
|
18
|
+
|
19
|
+
@interrupts = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# returns copy only, not suited to add/delelte interrupts
|
23
|
+
def interrupts; @interrupts.clone end
|
24
|
+
|
25
|
+
# gross length is full slot length without interruptions being subtracted
|
26
|
+
def gross_length
|
27
|
+
@duration.to_f
|
28
|
+
end
|
29
|
+
|
30
|
+
# [-----] -+
|
31
|
+
# [----------------] |
|
32
|
+
# [---] | <-- overlaping
|
33
|
+
# [------] |
|
34
|
+
# [------] |
|
35
|
+
# [----------] -+
|
36
|
+
#
|
37
|
+
# self: [---------]
|
38
|
+
#
|
39
|
+
# [---] -+
|
40
|
+
# [---] | <-- not overlaping
|
41
|
+
# [----] |
|
42
|
+
# [-----] -+
|
43
|
+
#
|
44
|
+
def _24(a,b)
|
45
|
+
[a.to_f, b < a ? b.to_f + 24 : b.to_f]
|
46
|
+
end
|
47
|
+
|
48
|
+
def overlaps? slr
|
49
|
+
return false unless slr.respond_to?(:t_end)
|
50
|
+
a, b = _24(t_start, t_end)
|
51
|
+
c, d = _24(slr.t_start, slr.t_end)
|
52
|
+
#puts "..a, b, c, d: #{a}, #{b} -> #{c}, #{d}"
|
53
|
+
if d < a
|
54
|
+
#puts "..upgrade: #{a}, #{b} -> #{c}, #{d}"
|
55
|
+
c = c + 24; d = d + 24
|
56
|
+
end
|
57
|
+
#puts "..a, b, c, d: #{a}, #{b} -> #{c}, #{d} | #{c < b && a < d}"
|
58
|
+
c < b && a < d
|
59
|
+
#!slr.nil? && !(slr.t_end <= t_start || t_end <= slr.t_start)
|
60
|
+
#not(slr.t_end <= t_start || t_end <= slr.t_start)
|
61
|
+
end
|
62
|
+
|
63
|
+
# checks for a pure technical fit which does not take into account the
|
64
|
+
# already existing interruptions!
|
65
|
+
def covers? slot_or_span
|
66
|
+
slr = slot_or_span # shortcut for shorter lines below
|
67
|
+
begin
|
68
|
+
a, b = _24(t_start, t_end)
|
69
|
+
c, d = _24(slr.t_start, slr.t_end)
|
70
|
+
if d < a
|
71
|
+
#puts "..upgrade: #{a}, #{b} -> #{c}, #{d}"
|
72
|
+
c = c + 24; d = d + 24
|
73
|
+
end
|
74
|
+
a <= c && d <= b
|
75
|
+
rescue NoMethodError # not a slot?
|
76
|
+
slr.kind_of?(Record) and slr.span <= gross_length
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_interrupt slot_or_span
|
81
|
+
slr = slot_or_span # shortcut
|
82
|
+
unless covers? slr
|
83
|
+
raise TimecardException, "interrupt not covered! #{slr}"
|
84
|
+
end
|
85
|
+
|
86
|
+
unless slr.span <= span
|
87
|
+
raise TimecardException, "'#{self}' overload(#{span}): #{slr}"
|
88
|
+
end
|
89
|
+
#raise TimecardException, "overload: #{slr}" unless slr.span <= span
|
90
|
+
|
91
|
+
# new interrupts may not overlap with existing ones!
|
92
|
+
if slr.respond_to?(:t_start)
|
93
|
+
#raise TimecardException, "overlap: #{slr}" if @interrupts.any? do |i|
|
94
|
+
# slr.overlaps? i
|
95
|
+
#end
|
96
|
+
if @interrupts.any? { |rec| slr.overlaps? rec }
|
97
|
+
raise TimecardException, "overlap: #{slr}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
@interrupts.push(slr)
|
102
|
+
|
103
|
+
# subtract interrupts from span time
|
104
|
+
#dt_lost = @interrupts.inject(0.0) { |sum, rec| sum + rec.span }
|
105
|
+
dt_lost = @interrupts.inject(0.0) { |sum, rec| sum + (rec.gross_length rescue rec.span) }
|
106
|
+
@span = gross_length - dt_lost
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/asciitracker.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "asciitracker/version"
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'asciitracker/hash_ext'
|
5
|
+
require 'asciitracker/hhmm'
|
6
|
+
require 'asciitracker/record'
|
7
|
+
require 'asciitracker/slot'
|
8
|
+
require 'asciitracker/model'
|
9
|
+
require 'asciitracker/controller'
|
10
|
+
require 'asciitracker/parser'
|
11
|
+
require 'asciitracker/app'
|
12
|
+
|
13
|
+
module AsciiTracker
|
14
|
+
# Your code goes here...
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include AsciiTracker
|
4
|
+
describe "AsciiTracker::Controller" do
|
5
|
+
it "finds the class" do
|
6
|
+
Controller.should_not == nil
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "with a controller" do
|
10
|
+
before :each do
|
11
|
+
@c = Controller.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
__END__
|
17
|
+
|
18
|
+
it "should have a #new_slot method" do
|
19
|
+
@c.new_day Date.today
|
20
|
+
slot = @c.new_slot(HHMM("10:00"), HHMM("12:00"), "foo bar")
|
21
|
+
@c.new_span "1.75", "some task"
|
22
|
+
@c.new_txt "some additional text"
|
23
|
+
end
|
24
|
+
|
25
|
+
class ControllerTest < Test::Unit::TestCase
|
26
|
+
|
27
|
+
include Timecard
|
28
|
+
|
29
|
+
def test_expoeasy
|
30
|
+
puts "\n--> #{self}"
|
31
|
+
c = Controller.new
|
32
|
+
Parser.parse c, <<-EOT
|
33
|
+
@expoeasy expoeasy:
|
34
|
+
@wallcms wallcms:
|
35
|
+
@private private:
|
36
|
+
2008-07-10 18:15-04:15 expoeasy: yui data tables for visitors
|
37
|
+
0:15 wallcms: commit reviews
|
38
|
+
0:30 private: family & food
|
39
|
+
20:15-22:15 private: halma
|
40
|
+
23:45-03:45 private: @jussie's weeding
|
41
|
+
EOT
|
42
|
+
assert_equal 5, c.model.records.size
|
43
|
+
assert_equal 3, c.model.projects.size
|
44
|
+
assert_not_nil ee = c.model.records.first
|
45
|
+
assert_equal "expoeasy: yui data tables for visitors", ee.desc
|
46
|
+
assert_equal 4, ee.interrupts.size
|
47
|
+
|
48
|
+
assert_equal 10.00, ee.gross_length
|
49
|
+
assert_equal 3.25, ee.span
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_day_spans
|
53
|
+
puts "\n--> #{self}"
|
54
|
+
c = Controller.new
|
55
|
+
c.new_day Date.today
|
56
|
+
c.new_span 0, "ueberstundenabbau"
|
57
|
+
assert_equal 1, c.model.records.length
|
58
|
+
rec = c.model.records.first
|
59
|
+
assert_equal 0, rec.span
|
60
|
+
assert_equal Date.today, rec.date
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_slot_span_slot
|
64
|
+
puts "\n--> #{self}"
|
65
|
+
c = Controller.new
|
66
|
+
c.new_day Date.today
|
67
|
+
c.new_slot(HHMM("10:00"), HHMM("14:00"), "slot 1")
|
68
|
+
c.new_span 2.5, "span"
|
69
|
+
c.new_slot(HHMM("14:00"), HHMM("16:00"), "slot 2")
|
70
|
+
assert_equal 3, c.model.records.length
|
71
|
+
assert_equal [1.5,2.5,2], c.model.records.map { |e| e.span }
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_interrupted_slot
|
75
|
+
puts "\n--> #{self}"
|
76
|
+
c = Controller.new
|
77
|
+
c.new_day Date.today
|
78
|
+
c.new_slot HHMM("10:00"), HHMM("14:00"), "office works"
|
79
|
+
c.new_span "0:15", "annoyance"
|
80
|
+
c.new_slot HHMM("10:15"), HHMM("10:30"), "another annoyance"
|
81
|
+
assert_equal 3, c.model.records.length
|
82
|
+
assert_equal 3.5, c.model.records.first.span
|
83
|
+
|
84
|
+
telco = c.new_slot(HHMM("10:30"), HHMM("11:30"), "sysadm")
|
85
|
+
assert_equal 4, c.model.records.length
|
86
|
+
assert_equal 2.5, c.model.records.first.span
|
87
|
+
|
88
|
+
c.new_span 0.25, "telco" # interrupt the sysadm task only!
|
89
|
+
assert_equal 5, c.model.records.length
|
90
|
+
assert_equal 2.5, c.model.records.first.span
|
91
|
+
|
92
|
+
assert_raise(TimecardException) do
|
93
|
+
c.new_slot HHMM("09:15"), HHMM("10:15"), "overlap"
|
94
|
+
end
|
95
|
+
assert_raise(TimecardException) do
|
96
|
+
c.new_slot HHMM("10:00"), HHMM("11:00"), "overlap"
|
97
|
+
end
|
98
|
+
assert_raise(TimecardException) do
|
99
|
+
c.new_slot HHMM("11:00"), HHMM("12:00"), "overlap"
|
100
|
+
end
|
101
|
+
assert_raise(TimecardException) do
|
102
|
+
c.new_slot HHMM("13:55"), HHMM("14:05"), "overlap"
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_equal 5, c.model.by_date(Date.today).length
|
106
|
+
#records = c.model.by_date[Date.today]
|
107
|
+
#assert_equal 4, records.length
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_disjunct_slots
|
111
|
+
puts "\n--> #{self}"
|
112
|
+
c = Controller.new
|
113
|
+
c.new_day Date.today
|
114
|
+
c.new_slot(HHMM("10:00"), HHMM("11:00"), "foo bar")
|
115
|
+
c.new_slot(HHMM("11:00"), HHMM("13:00"), "foo bar")
|
116
|
+
c.new_slot(HHMM("13:00"), HHMM("16:00"), "foo bar")
|
117
|
+
assert_equal 3, c.model.records.length
|
118
|
+
assert_equal [1,2,3], c.model.records.map { |e| e.span }
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_new_record_api
|
122
|
+
puts "\n--> #{self}"
|
123
|
+
c = Controller.new
|
124
|
+
c.new_day Date.today
|
125
|
+
slot = c.new_slot(HHMM("10:00"), HHMM("12:00"), "foo bar")
|
126
|
+
c.new_span "1.75", "some task"
|
127
|
+
c.new_txt "some additional text"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include AsciiTracker
|
4
|
+
describe "AsciiTracker::HHMM" do
|
5
|
+
it "finds the class" do
|
6
|
+
HHMM.should_not == nil
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "HHMM calculus" do
|
10
|
+
it "should calc differences" do
|
11
|
+
dt = (HHMM '12:00') - (HHMM '10:00')
|
12
|
+
dt.hours.should == 2.0
|
13
|
+
dt.minutes.should == 0
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should calc midnight overlaps" do
|
17
|
+
hhmm = (HHMM('00:01') - HHMM('23:59'))
|
18
|
+
hhmm.hours.should == 0
|
19
|
+
hhmm.minutes.should == 2
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should calc zero length slots" do
|
23
|
+
hhmm = (HHMM('12:34') - HHMM('12:34'))
|
24
|
+
hhmm.hours.should == 0
|
25
|
+
hhmm.minutes.should == 0
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should calc 24 hour slots" do
|
29
|
+
hhmm = (HHMM('24:00') - HHMM('00:00'))
|
30
|
+
hhmm.hours.should == 24
|
31
|
+
hhmm.minutes.should == 0
|
32
|
+
|
33
|
+
# 0/end - 24/start ist actually 0 minutes long!
|
34
|
+
hhmm = (HHMM('00:00') - HHMM('24:00'))
|
35
|
+
hhmm.hours.should == 0
|
36
|
+
hhmm.minutes.should == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should calc long overlaps" do
|
40
|
+
hhmm = (HHMM('02:00') - HHMM('14:00'))
|
41
|
+
hhmm.hours.should == 12
|
42
|
+
hhmm.minutes.should == 0
|
43
|
+
hhmm = (HHMM('02:00') - HHMM('10:00'))
|
44
|
+
hhmm.hours.should == 16
|
45
|
+
hhmm.minutes.should == 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "HHMM parsing" do
|
50
|
+
it "should parse '1:30'" do
|
51
|
+
hhmm = (HHMM '1:30')
|
52
|
+
hhmm.to_a.should == [1, 30]
|
53
|
+
hhmm.to_s.should == "01:30"
|
54
|
+
hhmm.to_f.should == 1.5
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should parse '1.5'" do
|
58
|
+
hhmm = (HHMM '1.5')
|
59
|
+
hhmm.to_a.should == [1, 30]
|
60
|
+
hhmm.to_s.should == "01:30"
|
61
|
+
hhmm.to_f.should == 1.5
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should parse 1.5" do
|
65
|
+
hhmm = (HHMM 1.5)
|
66
|
+
hhmm.to_a.should == [1, 30]
|
67
|
+
hhmm.to_s.should == "01:30"
|
68
|
+
hhmm.to_f.should == 1.5
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should parse (1, 30)" do
|
72
|
+
hhmm = (HHMM 1, 30)
|
73
|
+
hhmm.to_a.should == [1, 30]
|
74
|
+
hhmm.to_s.should == "01:30"
|
75
|
+
hhmm.to_f.should == 1.5
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should parse ('1', '30')" do
|
79
|
+
hhmm = (HHMM '1', '30')
|
80
|
+
hhmm.to_a.should == [1, 30]
|
81
|
+
hhmm.to_s.should == "01:30"
|
82
|
+
hhmm.to_f.should == 1.5
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include AsciiTracker
|
4
|
+
describe "AsciiTracker::Model" do
|
5
|
+
it "finds the class" do
|
6
|
+
Model.should_not == nil
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "with a model" do
|
10
|
+
before :each do
|
11
|
+
@model = Model.new
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be initially empty" do
|
15
|
+
@model.by_date(Date.today).should == []
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should add slot records" do
|
19
|
+
@model.add_record(Slot.new :start=>"10:45", :end=>"12:15")
|
20
|
+
(records = @model.by_date(Date.today)).should_not == []
|
21
|
+
records.first.span.should == 1.5
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should find best cascaded cover" do
|
25
|
+
@model.add_record Slot.new(:start=>"10:00", :end=>"20:00")
|
26
|
+
@model.add_record Slot.new(:start=>"11:00", :end=>"19:00")
|
27
|
+
@model.add_record Slot.new(:start=>"12:00", :end=>"17:00")
|
28
|
+
slot = Slot.new(:start=>"16:00", :end=>"17:00")
|
29
|
+
(cover = @model.find_best_cover slot).should_not == nil
|
30
|
+
cover.t_start.to_s.should == "12:00"
|
31
|
+
cover.t_end.to_s.should == "17:00"
|
32
|
+
|
33
|
+
slot = Slot.new(:start=>"11:10", :end=>"11:20")
|
34
|
+
(cover = @model.find_best_cover slot).should_not == nil
|
35
|
+
cover.t_start.to_s.should == "11:00"
|
36
|
+
cover.t_end.to_s.should == "19:00"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should find best cover" do
|
40
|
+
@model.add_record Slot.new(:start=>"10:00", :end=>"15:00")
|
41
|
+
@model.add_record Slot.new(:start=>"15:00", :end=>"20:00")
|
42
|
+
slot = Slot.new(:start=>"16:00", :end=>"17:00")
|
43
|
+
(overlaps = @model.find_overlaps slot).size.should == 1
|
44
|
+
overlap = overlaps.first
|
45
|
+
overlap.t_start.to_s.should == "15:00"
|
46
|
+
overlap.t_end.to_s.should == "20:00"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should find overlaps" do
|
50
|
+
@model.add_record Slot.new(:start=>"10:00", :end=>"20:00")
|
51
|
+
slot = Slot.new(:start=>"11:00", :end=>"13:00")
|
52
|
+
(overlaps = @model.find_overlaps slot).size.should == 1
|
53
|
+
overlap = overlaps.first
|
54
|
+
overlap.t_start.to_s.should == "10:00"
|
55
|
+
overlap.t_end.to_s.should == "20:00"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should add non overlapping slot records" do
|
59
|
+
@model.add_record(Slot.new :start=>"10:00", :end=>"11:00")
|
60
|
+
@model.add_record(Slot.new :start=>"11:00", :end=>"13:00")
|
61
|
+
(records = @model.by_date(Date.today)).should_not == []
|
62
|
+
records.first.span.should == 1
|
63
|
+
records.last.span.should == 2
|
64
|
+
end
|
65
|
+
|
66
|
+
# @rec.covers?(Slot.new(:start=>"10:45", :end=>"12:15")).should == true
|
67
|
+
# @rec.covers?(Slot.new(:start=>"10:00", :end=>"12:15")).should == false
|
68
|
+
# @rec.covers?(Slot.new(:start=>"10:45", :end=>"13:00")).should == false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include AsciiTracker
|
4
|
+
describe "AsciiTracker::Record" do
|
5
|
+
it "finds the class" do
|
6
|
+
Record.should_not == nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should calc days from hours and minutes" do
|
10
|
+
Record.hours_to_dhm(0.25).should == [0, 0, 15]
|
11
|
+
Record.hours_to_dhm(1).should == [0, 1, 0]
|
12
|
+
Record.hours_to_dhm(24).should == [3, 0, 0]
|
13
|
+
Record.hours_to_dhm(35.25).should == [4, 3, 15]
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should init with fixnum span" do
|
17
|
+
rec = Record.new :span => 1, :desc => "foo bar"
|
18
|
+
rec.span.should == 1.0
|
19
|
+
rec.desc.should == "foo bar"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should init with string duration span" do
|
23
|
+
rec = Record.new :span => "2.75", :desc => "bar foo"
|
24
|
+
rec.span.should == 2.75
|
25
|
+
rec.desc.should == "bar foo"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should init with without description text" do
|
29
|
+
rec = Record.new :span => "00:30"
|
30
|
+
rec.span.should == 0.5
|
31
|
+
rec.desc.should == nil
|
32
|
+
end
|
33
|
+
end
|