ascii-tracker 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|