< Summary

Information
Class: Punch.CLI.DaySchedule
Assembly: punch
File(s): /home/runner/work/punch/punch/src/Punch.CLI/DaySchedule.cs
Line coverage
100%
Covered lines: 64
Uncovered lines: 0
Coverable lines: 64
Total lines: 106
Line coverage: 100%
Branch coverage
93%
Covered branches: 30
Total branches: 32
Branch coverage: 93.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
get_Blocks()100%11100%
get_Count()100%11100%
IsFree(...)100%44100%
IsOverlapping(...)100%66100%
FindAt(...)100%22100%
CanGrow(...)100%22100%
Add(...)100%11100%
Remove(...)100%22100%
Replace(...)100%22100%
FreeSlots(...)100%11100%
FillSlots(...)100%11100%
AdvanceAfterAdd(...)80%1010100%
SetOccupied(...)100%22100%

File(s)

/home/runner/work/punch/punch/src/Punch.CLI/DaySchedule.cs

#LineLine coverage
 1namespace Punch.CLI;
 2
 3// Owns a day's booked blocks together with the 96-slot occupancy mask, keeping
 4// the two in sync so callers never manipulate the mask directly. Slots are
 5// quarter-hour indices 0..95.
 6internal sealed class DaySchedule
 7{
 8    private readonly List<TimeBlock> _blocks;
 1079    private readonly bool[] _occupied = new bool[96];
 10
 10711    public DaySchedule(IEnumerable<TimeBlock> blocks)
 10712    {
 10713        _blocks = blocks.ToList();
 48714        foreach (var block in _blocks)
 8315            SetOccupied(block, true);
 10716    }
 17
 10118    public IReadOnlyList<TimeBlock> Blocks => _blocks;
 19
 17420    public int Count => _blocks.Count;
 21
 3522    public bool IsFree(int slot) => slot is >= 0 and < 96 && !_occupied[slot];
 23
 24    public bool IsOverlapping(int start, int length)
 1225    {
 8026        for (var i = start; i < start + length && i < 96; i++)
 3127            if (_occupied[i])
 328                return true;
 929        return false;
 1230    }
 31
 32    public TimeBlock? FindAt(int slot) =>
 11533        _blocks.FirstOrDefault(b => slot >= b.StartSlot && slot < b.StartSlot + b.Length);
 34
 35    // True when the slot immediately after the block's current span is free,
 36    // i.e. the block can grow by one slot.
 37    public bool CanGrow(int startSlot, int currentLength)
 538    {
 539        var next = startSlot + currentLength;
 540        return next < 96 && !_occupied[next];
 541    }
 42
 43    public void Add(TimeBlock block)
 344    {
 345        _blocks.Add(block);
 346        SetOccupied(block, true);
 347    }
 48
 49    public void Remove(TimeBlock block)
 350    {
 351        if (_blocks.Remove(block))
 252            SetOccupied(block, false);
 353    }
 54
 55    // Swaps oldBlock for newBlock in place, re-deriving occupancy from both spans.
 56    // Returns newBlock for convenient reassignment of the caller's reference.
 57    public TimeBlock Replace(TimeBlock oldBlock, TimeBlock newBlock)
 558    {
 559        var idx = _blocks.IndexOf(oldBlock);
 560        if (idx < 0)
 161            return oldBlock;
 462        SetOccupied(oldBlock, false);
 463        _blocks[idx] = newBlock;
 464        SetOccupied(newBlock, true);
 465        return newBlock;
 566    }
 67
 68    // Temporarily clears a block's occupancy without removing it from the list,
 69    // so an in-progress edit can preview a shrink/grow in the timeline. Pair with
 70    // FillSlots to restore (or call Replace when committing the edit).
 1071    public void FreeSlots(TimeBlock block) => SetOccupied(block, false);
 72
 273    public void FillSlots(TimeBlock block) => SetOccupied(block, true);
 74
 75    // Finds where the cursor should land after booking a block at cursorSlot:
 76    // the next free slot, or slot 95 (selecting the block there) when the day is full.
 77    public (int cursorSlot, int selectionLength, TimeBlock? selectedBlock) AdvanceAfterAdd(int cursorSlot)
 678    {
 679        var selectionLength = 1;
 680        TimeBlock? selectedBlock = null;
 81
 2682        while (cursorSlot < 96 && _occupied[cursorSlot])
 2083            cursorSlot++;
 684        if (cursorSlot >= 96)
 285        {
 286            cursorSlot = 95;
 287            if (_occupied[95])
 288            {
 289                var adj = FindAt(95);
 290                if (adj != null)
 291                {
 292                    selectionLength = adj.Length;
 293                    selectedBlock = adj;
 294                }
 295            }
 296        }
 97
 698        return (cursorSlot, selectionLength, selectedBlock);
 699    }
 100
 101    private void SetOccupied(TimeBlock block, bool value)
 108102    {
 684103        for (var s = block.StartSlot; s < block.StartSlot + block.Length; s++)
 234104            _occupied[s] = value;
 108105    }
 106}