Module: Flycal::SlotTemplates

Defined in:
lib/flycal/slot_templates.rb

Overview

Named and custom slot windows. YAML/JSON shape matches ~/.flycal/config.yml:

days: [1, 2, 3, 4, 5]
hours:
- 9:30-13:00
- 14:00-18:30

Constant Summary collapse

BUILTIN =
{
  "work" => {
    "days" => [ 1, 2, 3, 4, 5 ],
    "hours" => [ "9:30-13:00", "14:00-18:30" ]
  },
  "dinner" => {
    "days" => [ 1, 2, 3, 4, 5, 6 ],
    "hours" => [ "19-23" ]
  }
}.freeze

Class Method Summary collapse

Class Method Details

.custom_present?(custom) ⇒ Boolean

Returns:

  • (Boolean)


108
109
110
111
112
113
114
# File 'lib/flycal/slot_templates.rb', line 108

def custom_present?(custom)
  return false if custom.nil?
  return false if custom.respond_to?(:blank?) && custom.blank?
  return false if custom.respond_to?(:empty?) && custom.empty?

  true
end

.indifferent(value) ⇒ Object



122
123
124
125
126
127
# File 'lib/flycal/slot_templates.rb', line 122

def indifferent(value)
  return {} if value.nil?
  return value.with_indifferent_access if value.respond_to?(:with_indifferent_access)

  value
end

.names(catalog = nil) ⇒ Object



24
25
26
# File 'lib/flycal/slot_templates.rb', line 24

def names(catalog = nil)
  normalize_catalog(catalog || DefaultSlots.templates).keys
end

.normalize_catalog(catalog) ⇒ Object



116
117
118
119
120
# File 'lib/flycal/slot_templates.rb', line 116

def normalize_catalog(catalog)
  indifferent(catalog).each_with_object({}) do |(key, value), out|
    out[key.to_s] = value
  end
end

.parse(definition) ⇒ Object



48
49
50
51
52
53
# File 'lib/flycal/slot_templates.rb', line 48

def parse(definition)
  data = indifferent(definition)
  days = parse_days(data[:days])
  hours = parse_hours(data[:hours])
  { days: days, hours: hours }
end

.parse_clock(value) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/flycal/slot_templates.rb', line 89

def parse_clock(value)
  str = value.to_s.strip
  if str.match?(/\A\d{1,2}\z/)
    hour = str.to_i
    raise Error, "Invalid hour #{value.inspect}. Must be 0-23." unless hour.between?(0, 23)

    return [ hour, 0 ]
  end

  hour_s, minute_s = str.split(":", 2)
  hour = hour_s.to_i
  minute = minute_s.to_i
  unless hour.between?(0, 23) && minute.between?(0, 59)
    raise Error, "Invalid time #{value.inspect}. Use HH:MM (e.g. 9:00, 18:30)."
  end

  [ hour, minute ]
end

.parse_days(values) ⇒ Object

Raises:



55
56
57
58
59
60
61
62
63
# File 'lib/flycal/slot_templates.rb', line 55

def parse_days(values)
  days = Array(values).map(&:to_i)
  raise Error, "slots template days cannot be empty." if days.empty?

  invalid = days.reject { |d| d.between?(1, 7) }
  raise Error, "Invalid template days #{invalid.inspect}. Use 1 (Mon) .. 7 (Sun)." unless invalid.empty?

  days.uniq
end

.parse_hour_range(item) ⇒ Object

Raises:



74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/flycal/slot_templates.rb', line 74

def parse_hour_range(item)
  start_str, end_str = item.to_s.strip.split("-", 2)
  if start_str.nil? || end_str.nil? || start_str.empty? || end_str.empty?
    raise Error, "Invalid hours item #{item.inspect}. Use format like '9-13' or '14:30-18:00'."
  end

  sh, sm = parse_clock(start_str)
  eh, em = parse_clock(end_str)
  start_minutes = (sh * 60) + sm
  end_minutes = (eh * 60) + em
  raise Error, "Invalid hours range #{item.inspect}: end must be after start." if end_minutes <= start_minutes

  [ sh, sm, eh, em ]
end

.parse_hours(values) ⇒ Object

Raises:



65
66
67
68
69
70
71
72
# File 'lib/flycal/slot_templates.rb', line 65

def parse_hours(values)
  return values if values.is_a?(Array) && values.first.is_a?(Array)

  ranges = Array(values).map { |item| parse_hour_range(item) }
  raise Error, "template hours cannot be empty." if ranges.empty?

  ranges.sort_by { |sh, sm, _eh, _em| (sh * 60) + sm }
end

.resolve(name: nil, custom: nil, catalog: nil) ⇒ Object

Resolve a named template, or a custom YAML-shaped hash. custom wins over name when present.

Raises:



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/flycal/slot_templates.rb', line 30

def resolve(name: nil, custom: nil, catalog: nil)
  if custom_present?(custom)
    parsed = parse(custom)
    return parsed.merge(name: "custom")
  end

  source = normalize_catalog(catalog || DefaultSlots.templates)
  raise Error, "slots.templates is missing." if source.empty?
  key = name.to_s.strip
  key = source.keys.first.to_s if key.empty?
  definition = source[key]
  unless definition
    raise Error, "Unknown slots template #{key.inspect}. Available: #{source.keys.join(", ")}"
  end

  parse(definition).merge(name: key)
end