coach_zed 0.7.0 → 0.8.0
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/coach_zed/feed_writer.rb +5 -16
- data/lib/coach_zed/version.rb +1 -1
- data/lib/coach_zed.rb +101 -27
- data/sig/coach_zed.rbs +21 -13
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e97024f8b6766bd4d08c6811960bbd5d75e10ca7cbeb4032fe4167e6b1f823f
|
|
4
|
+
data.tar.gz: b0861f0e335a273a49703b1bcd17be48697ec1008c3bf2541a809e6ddb09aa02
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5ea3e7f3d8fea32340b42179590ab8e7c607d7a3e1daba4a2735246a54443b5519066c6a9bf20de1eed66745ac6279b8b7d32dfeab2edbb9984368cc810f94ea
|
|
7
|
+
data.tar.gz: e1fd57f9709a06e7a1975fab46f62e28c31998ec3834882a09a2da81578b7daced1baf9e72fd14fed26b12e1b07a3fff86fbbe5a8ee3f0daad104bcf68a9b108
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## [](https://github.com/rossta/coach_zed/compare/v0.7.0...v) (2026-06-28)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* merge schedules in json ([b8343be](https://github.com/rossta/coach_zed/commit/b8343bee863437f9313e6e0d4782aaa05a199fe5))
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
* dedupe overlapping appended feed events ([d19060e](https://github.com/rossta/coach_zed/commit/d19060ed42ee960e73fdebf151c3195978d44b51))
|
|
1
10
|
## [](https://github.com/rossta/coach_zed/compare/v0.6.0...v) (2026-06-18)
|
|
2
11
|
|
|
3
12
|
### Features
|
|
@@ -5,24 +5,18 @@ require "time"
|
|
|
5
5
|
|
|
6
6
|
class CoachZed
|
|
7
7
|
class FeedWriter
|
|
8
|
-
def initialize(schedule:,
|
|
8
|
+
def initialize(schedule:, calendar_name: nil)
|
|
9
9
|
@schedule = schedule
|
|
10
|
-
@start_date = start_date
|
|
11
|
-
@existing_feed_content = existing_feed_content
|
|
12
10
|
@calendar_name = calendar_name
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
def build
|
|
16
|
-
|
|
17
|
-
append_to_existing_feed(existing_feed_content)
|
|
18
|
-
else
|
|
19
|
-
fresh_feed
|
|
20
|
-
end
|
|
14
|
+
fresh_feed
|
|
21
15
|
end
|
|
22
16
|
|
|
23
17
|
private
|
|
24
18
|
|
|
25
|
-
attr_reader :schedule, :
|
|
19
|
+
attr_reader :schedule, :calendar_name
|
|
26
20
|
|
|
27
21
|
def fresh_feed
|
|
28
22
|
lines = header_lines
|
|
@@ -31,11 +25,6 @@ class CoachZed
|
|
|
31
25
|
lines.join("\r\n") + "\r\n"
|
|
32
26
|
end
|
|
33
27
|
|
|
34
|
-
def append_to_existing_feed(existing_feed)
|
|
35
|
-
event_block = event_lines.join("\r\n") + "\r\n"
|
|
36
|
-
existing_feed.sub(/END:VCALENDAR\s*\z/, "#{event_block}END:VCALENDAR\r\n")
|
|
37
|
-
end
|
|
38
|
-
|
|
39
28
|
def header_lines
|
|
40
29
|
[
|
|
41
30
|
"BEGIN:VCALENDAR",
|
|
@@ -50,10 +39,10 @@ class CoachZed
|
|
|
50
39
|
|
|
51
40
|
def event_lines
|
|
52
41
|
schedule.fetch("days").flat_map do |day|
|
|
53
|
-
date =
|
|
42
|
+
date = Date.strptime(day.fetch("date"), "%Y-%m-%d")
|
|
54
43
|
[
|
|
55
44
|
"BEGIN:VEVENT",
|
|
56
|
-
"UID:#{
|
|
45
|
+
"UID:#{date.strftime("%Y%m%d")}@coach_zed",
|
|
57
46
|
"DTSTAMP:#{generated_timestamp}",
|
|
58
47
|
"DTSTART;VALUE=DATE:#{date.strftime("%Y%m%d")}",
|
|
59
48
|
"DTEND;VALUE=DATE:#{(date + 1).strftime("%Y%m%d")}",
|
data/lib/coach_zed/version.rb
CHANGED
data/lib/coach_zed.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "date"
|
|
|
4
4
|
require "digest"
|
|
5
5
|
require "json"
|
|
6
6
|
require "pathname"
|
|
7
|
+
require "time"
|
|
7
8
|
require "yaml"
|
|
8
9
|
|
|
9
10
|
require_relative "coach_zed/version"
|
|
@@ -19,7 +20,7 @@ class CoachZed
|
|
|
19
20
|
Result = Data.define(:schedule_path, :ics_path, :webcal_path, :schedule)
|
|
20
21
|
|
|
21
22
|
class Config
|
|
22
|
-
attr_accessor :workout_catalog_dir, :model, :output_dir, :feed_output_basename, :feed_title, :existing_feed_path
|
|
23
|
+
attr_accessor :workout_catalog_dir, :model, :output_dir, :feed_output_basename, :feed_title, :existing_feed_path, :existing_schedule_path, :merge_policy
|
|
23
24
|
|
|
24
25
|
def initialize(
|
|
25
26
|
workout_catalog_dir: nil,
|
|
@@ -27,7 +28,9 @@ class CoachZed
|
|
|
27
28
|
output_dir: nil,
|
|
28
29
|
feed_output_basename: nil,
|
|
29
30
|
feed_title: nil,
|
|
30
|
-
existing_feed_path: nil
|
|
31
|
+
existing_feed_path: nil,
|
|
32
|
+
existing_schedule_path: nil,
|
|
33
|
+
merge_policy: nil
|
|
31
34
|
)
|
|
32
35
|
@workout_catalog_dir = workout_catalog_dir
|
|
33
36
|
@model = model
|
|
@@ -35,6 +38,8 @@ class CoachZed
|
|
|
35
38
|
@feed_output_basename = feed_output_basename
|
|
36
39
|
@feed_title = feed_title
|
|
37
40
|
@existing_feed_path = existing_feed_path
|
|
41
|
+
@existing_schedule_path = existing_schedule_path
|
|
42
|
+
@merge_policy = merge_policy
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
def apply(hash)
|
|
@@ -100,7 +105,9 @@ class CoachZed
|
|
|
100
105
|
output_dir: nil,
|
|
101
106
|
feed_output_basename: nil,
|
|
102
107
|
feed_title: nil,
|
|
103
|
-
existing_feed_path: nil
|
|
108
|
+
existing_feed_path: nil,
|
|
109
|
+
existing_schedule_path: nil,
|
|
110
|
+
merge_policy: nil
|
|
104
111
|
)
|
|
105
112
|
config = self.class.config
|
|
106
113
|
|
|
@@ -114,31 +121,43 @@ class CoachZed
|
|
|
114
121
|
@feed_title = feed_title.nil? ? config.feed_title : feed_title
|
|
115
122
|
resolved_existing_feed_path = existing_feed_path.nil? ? config.existing_feed_path : existing_feed_path
|
|
116
123
|
@existing_feed_path = resolved_existing_feed_path && Pathname(resolved_existing_feed_path)
|
|
124
|
+
resolved_existing_schedule_path = existing_schedule_path.nil? ? config.existing_schedule_path : existing_schedule_path
|
|
125
|
+
resolved_existing_schedule_path =
|
|
126
|
+
if resolved_existing_schedule_path.nil? && @existing_feed_path
|
|
127
|
+
Pathname(@existing_feed_path.to_s.sub(/\.ics\z/, ".json"))
|
|
128
|
+
else
|
|
129
|
+
resolved_existing_schedule_path
|
|
130
|
+
end
|
|
131
|
+
@existing_schedule_path = resolved_existing_schedule_path && Pathname(resolved_existing_schedule_path)
|
|
132
|
+
@merge_policy = merge_policy.nil? ? config.merge_policy : merge_policy
|
|
117
133
|
end
|
|
118
134
|
|
|
119
|
-
def generate_schedule(start_date:, consultation_prompt: nil, consultation_prompt_path: nil, generation_mode: nil)
|
|
135
|
+
def generate_schedule(start_date:, consultation_prompt: nil, consultation_prompt_path: nil, generation_mode: nil, merge_policy: nil)
|
|
120
136
|
prompt_text = resolve_prompt_text(consultation_prompt, consultation_prompt_path)
|
|
121
137
|
catalog = Catalog::Loader.new(@workout_catalog_dir).load
|
|
122
138
|
generation_mode = normalize_generation_mode(generation_mode)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
merge_policy = normalize_merge_policy(merge_policy || @merge_policy || generation_mode)
|
|
140
|
+
existing_schedule = load_existing_schedule if merge_policy == :append
|
|
141
|
+
existing_feed = load_existing_feed if existing_schedule.nil? && generation_mode != :refresh
|
|
142
|
+
start_date = generation_start_date(start_date, existing_schedule:, generation_mode:, merge_policy:)
|
|
143
|
+
generation_days = generation_days_for(start_date, generation_mode:, existing_schedule:, merge_policy:)
|
|
144
|
+
existing_context = existing_schedule ? schedule_context(existing_schedule, limit_days: 28) : existing_feed&.to_context(limit_days: 28)
|
|
145
|
+
schedule_key = schedule_key_for(prompt_text, start_date, catalog, generation_days, existing_context, merge_policy)
|
|
128
146
|
prompt = PromptBuilder.new(
|
|
129
147
|
consultation_prompt: prompt_text,
|
|
130
148
|
catalog: catalog,
|
|
131
149
|
start_date: start_date,
|
|
132
150
|
schedule_key: schedule_key,
|
|
133
151
|
generation_days: generation_days,
|
|
134
|
-
existing_feed_context:
|
|
152
|
+
existing_feed_context: existing_context
|
|
135
153
|
).build
|
|
136
154
|
raw_schedule = @ai_client.generate(prompt:)
|
|
137
155
|
schedule = ScheduleParser.parse(raw_schedule)
|
|
138
|
-
schedule = normalize_schedule(schedule, start_date:, prompt_text:, schedule_key:, catalog:, generation_days:)
|
|
156
|
+
schedule = normalize_schedule(schedule, start_date:, prompt_text:, schedule_key:, catalog:, generation_days:, merge_policy:)
|
|
157
|
+
schedule = merge_schedule(existing_schedule, schedule, merge_policy)
|
|
139
158
|
|
|
140
159
|
schedule_path = write_schedule(schedule, schedule_key)
|
|
141
|
-
feed_paths = write_feeds(schedule
|
|
160
|
+
feed_paths = write_feeds(schedule)
|
|
142
161
|
|
|
143
162
|
Result.new(
|
|
144
163
|
schedule_path: schedule_path,
|
|
@@ -150,7 +169,7 @@ class CoachZed
|
|
|
150
169
|
|
|
151
170
|
private
|
|
152
171
|
|
|
153
|
-
attr_reader :workout_catalog_dir, :ai_client, :output_dir, :schedule_output_dir, :feed_output_dir, :feed_output_basename, :feed_title, :existing_feed_path
|
|
172
|
+
attr_reader :workout_catalog_dir, :ai_client, :output_dir, :schedule_output_dir, :feed_output_dir, :feed_output_basename, :feed_title, :existing_feed_path, :existing_schedule_path, :merge_policy
|
|
154
173
|
|
|
155
174
|
def wrap_client(client, model:)
|
|
156
175
|
return client if client.is_a?(Clients::RubyOpenAI)
|
|
@@ -195,6 +214,15 @@ class CoachZed
|
|
|
195
214
|
FeedReader.load_existing(existing_feed_path)
|
|
196
215
|
end
|
|
197
216
|
|
|
217
|
+
def load_existing_schedule
|
|
218
|
+
return nil if existing_schedule_path.nil?
|
|
219
|
+
return nil unless existing_schedule_path.exist?
|
|
220
|
+
|
|
221
|
+
schedule = JSON.parse(existing_schedule_path.read)
|
|
222
|
+
ScheduleParser.validate!(schedule)
|
|
223
|
+
schedule
|
|
224
|
+
end
|
|
225
|
+
|
|
198
226
|
def normalize_generation_mode(value)
|
|
199
227
|
return nil if value.nil?
|
|
200
228
|
|
|
@@ -206,33 +234,48 @@ class CoachZed
|
|
|
206
234
|
end
|
|
207
235
|
end
|
|
208
236
|
|
|
209
|
-
def
|
|
237
|
+
def normalize_merge_policy(value)
|
|
238
|
+
return :replace if value.nil?
|
|
239
|
+
|
|
240
|
+
case value.to_sym
|
|
241
|
+
when :replace, :append
|
|
242
|
+
value.to_sym
|
|
243
|
+
else
|
|
244
|
+
raise ArgumentError, "unsupported merge policy: #{value}"
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def generation_start_date(start_date, existing_schedule:, generation_mode:, merge_policy:)
|
|
210
249
|
return normalize_date(start_date) if generation_mode == :refresh
|
|
211
250
|
|
|
212
|
-
|
|
213
|
-
|
|
251
|
+
if merge_policy == :append
|
|
252
|
+
last_date = existing_schedule&.fetch("days")&.map { |day| Date.parse(day.fetch("date")) }&.max
|
|
253
|
+
return normalize_date(start_date) if last_date.nil?
|
|
214
254
|
|
|
215
|
-
|
|
255
|
+
last_date + 1
|
|
256
|
+
else
|
|
257
|
+
normalize_date(start_date)
|
|
258
|
+
end
|
|
216
259
|
end
|
|
217
260
|
|
|
218
|
-
def generation_days_for(start_date, generation_mode:,
|
|
219
|
-
return 7 if
|
|
220
|
-
return 28 if
|
|
221
|
-
return 7 if existing_feed
|
|
261
|
+
def generation_days_for(start_date, generation_mode:, existing_schedule:, merge_policy:)
|
|
262
|
+
return 7 if merge_policy == :append && existing_schedule
|
|
263
|
+
return 28 if merge_policy == :append
|
|
222
264
|
return 28 if generation_mode.nil?
|
|
223
265
|
|
|
224
266
|
upcoming_sunday = start_date + ((7 - start_date.wday) % 7)
|
|
225
267
|
(upcoming_sunday - start_date).to_i + 29
|
|
226
268
|
end
|
|
227
269
|
|
|
228
|
-
def schedule_key_for(prompt_text, start_date, catalog, generation_days,
|
|
270
|
+
def schedule_key_for(prompt_text, start_date, catalog, generation_days, existing_context, merge_policy)
|
|
229
271
|
Digest::SHA256.hexdigest(
|
|
230
272
|
[
|
|
231
273
|
prompt_text.strip,
|
|
232
274
|
start_date.iso8601,
|
|
233
275
|
generation_days,
|
|
234
276
|
catalog_digest(catalog),
|
|
235
|
-
|
|
277
|
+
merge_policy.to_s,
|
|
278
|
+
existing_context.to_s
|
|
236
279
|
].join("\n")
|
|
237
280
|
)[0...12] || ""
|
|
238
281
|
end
|
|
@@ -241,7 +284,7 @@ class CoachZed
|
|
|
241
284
|
Digest::SHA256.hexdigest(catalog.map(&:fingerprint).join("\n"))
|
|
242
285
|
end
|
|
243
286
|
|
|
244
|
-
def normalize_schedule(schedule, start_date:, prompt_text:, schedule_key:, catalog:, generation_days:)
|
|
287
|
+
def normalize_schedule(schedule, start_date:, prompt_text:, schedule_key:, catalog:, generation_days:, merge_policy:)
|
|
245
288
|
catalog_texts = catalog.to_h { |entry| [entry.relative_path, entry.path.read] }
|
|
246
289
|
days = schedule.fetch("days")
|
|
247
290
|
normalized_days = days.each_with_index.map do |day, index|
|
|
@@ -266,10 +309,31 @@ class CoachZed
|
|
|
266
309
|
"catalog_directory" => workout_catalog_dir.to_s,
|
|
267
310
|
"catalog_count" => catalog.count,
|
|
268
311
|
"program_length_days" => schedule.fetch("program_length_days", generation_days).to_i,
|
|
312
|
+
"merge_policy" => merge_policy.to_s,
|
|
313
|
+
"generated_at" => Time.now.utc.iso8601,
|
|
269
314
|
"days" => normalized_days
|
|
270
315
|
)
|
|
271
316
|
end
|
|
272
317
|
|
|
318
|
+
def merge_schedule(existing_schedule, schedule, merge_policy)
|
|
319
|
+
return schedule if merge_policy == :replace || existing_schedule.nil?
|
|
320
|
+
|
|
321
|
+
existing_by_date = existing_schedule.fetch("days").to_h { |day| [day.fetch("date"), day] }
|
|
322
|
+
merged_by_date = existing_by_date.merge(schedule.fetch("days").to_h { |day| [day.fetch("date"), day] })
|
|
323
|
+
|
|
324
|
+
merged_days = merged_by_date.values.sort_by { |day| Date.parse(day.fetch("date")) }
|
|
325
|
+
merged_days = merged_days.each_with_index.map do |day, index|
|
|
326
|
+
day.merge("day_number" => index + 1)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
schedule.merge(
|
|
330
|
+
"merged_from_schedule_id" => existing_schedule["schedule_id"],
|
|
331
|
+
"start_date" => merged_days.first&.fetch("date"),
|
|
332
|
+
"program_length_days" => merged_days.length,
|
|
333
|
+
"days" => merged_days
|
|
334
|
+
)
|
|
335
|
+
end
|
|
336
|
+
|
|
273
337
|
def write_schedule(schedule, schedule_key)
|
|
274
338
|
schedule_output_dir.mkpath
|
|
275
339
|
path = schedule_output_dir.join(schedule_filename(schedule_key))
|
|
@@ -277,13 +341,11 @@ class CoachZed
|
|
|
277
341
|
path
|
|
278
342
|
end
|
|
279
343
|
|
|
280
|
-
def write_feeds(schedule
|
|
344
|
+
def write_feeds(schedule)
|
|
281
345
|
feed_output_dir.mkpath
|
|
282
346
|
base_path = feed_output_dir.join(feed_basename)
|
|
283
347
|
feed = FeedWriter.new(
|
|
284
348
|
schedule:,
|
|
285
|
-
start_date:,
|
|
286
|
-
existing_feed_content: existing_feed&.feed_content,
|
|
287
349
|
calendar_name: feed_title
|
|
288
350
|
).build
|
|
289
351
|
ics_path = base_path.sub_ext(".ics")
|
|
@@ -300,4 +362,16 @@ class CoachZed
|
|
|
300
362
|
def feed_basename
|
|
301
363
|
feed_output_basename || "schedule"
|
|
302
364
|
end
|
|
365
|
+
|
|
366
|
+
def schedule_context(schedule, limit_days: 28)
|
|
367
|
+
days = schedule.fetch("days")
|
|
368
|
+
recent_days = days.last(limit_days)
|
|
369
|
+
|
|
370
|
+
recent_days.map do |day|
|
|
371
|
+
pieces = [day.fetch("date")]
|
|
372
|
+
pieces << ((day["day_type"] == "workout") ? day.fetch("workout").fetch("title") : "Rest")
|
|
373
|
+
pieces << day["notes"] if day["notes"] && !day["notes"].to_s.empty?
|
|
374
|
+
pieces.join(" | ")
|
|
375
|
+
end.join("\n")
|
|
376
|
+
end
|
|
303
377
|
end
|
data/sig/coach_zed.rbs
CHANGED
|
@@ -27,6 +27,8 @@ class CoachZed
|
|
|
27
27
|
attr_accessor feed_output_basename: String?
|
|
28
28
|
attr_accessor feed_title: String?
|
|
29
29
|
attr_accessor existing_feed_path: String?
|
|
30
|
+
attr_accessor existing_schedule_path: String?
|
|
31
|
+
attr_accessor merge_policy: String?
|
|
30
32
|
|
|
31
33
|
def initialize: (
|
|
32
34
|
?workout_catalog_dir: String?,
|
|
@@ -34,7 +36,9 @@ class CoachZed
|
|
|
34
36
|
?output_dir: String?,
|
|
35
37
|
?feed_output_basename: String?,
|
|
36
38
|
?feed_title: String?,
|
|
37
|
-
?existing_feed_path: String
|
|
39
|
+
?existing_feed_path: String?,
|
|
40
|
+
?existing_schedule_path: String?,
|
|
41
|
+
?merge_policy: String?
|
|
38
42
|
) -> void
|
|
39
43
|
|
|
40
44
|
def apply: (Hash[untyped, untyped]) -> void
|
|
@@ -55,6 +59,8 @@ class CoachZed
|
|
|
55
59
|
attr_reader feed_output_basename: String?
|
|
56
60
|
attr_reader feed_title: String?
|
|
57
61
|
attr_reader existing_feed_path: Pathname?
|
|
62
|
+
attr_reader existing_schedule_path: Pathname?
|
|
63
|
+
attr_reader merge_policy: String?
|
|
58
64
|
|
|
59
65
|
def initialize: (
|
|
60
66
|
client: untyped,
|
|
@@ -63,14 +69,17 @@ class CoachZed
|
|
|
63
69
|
?output_dir: String?,
|
|
64
70
|
?feed_output_basename: String?,
|
|
65
71
|
?feed_title: String?,
|
|
66
|
-
?existing_feed_path: String
|
|
72
|
+
?existing_feed_path: String?,
|
|
73
|
+
?existing_schedule_path: String?,
|
|
74
|
+
?merge_policy: String?
|
|
67
75
|
) -> void
|
|
68
76
|
|
|
69
77
|
def generate_schedule: (
|
|
70
78
|
start_date: untyped,
|
|
71
79
|
?consultation_prompt: String?,
|
|
72
80
|
?consultation_prompt_path: String?,
|
|
73
|
-
?generation_mode: Symbol
|
|
81
|
+
?generation_mode: Symbol?,
|
|
82
|
+
?merge_policy: Symbol?
|
|
74
83
|
) -> Result
|
|
75
84
|
|
|
76
85
|
private
|
|
@@ -79,16 +88,20 @@ class CoachZed
|
|
|
79
88
|
def resolve_prompt_text: (String? consultation_prompt, String? consultation_prompt_path) -> String
|
|
80
89
|
def normalize_date: (untyped value) -> Date
|
|
81
90
|
def load_existing_feed: -> CoachZed::FeedReader?
|
|
91
|
+
def load_existing_schedule: -> Hash[String, untyped]?
|
|
82
92
|
def normalize_generation_mode: (untyped value) -> Symbol?
|
|
83
|
-
def
|
|
84
|
-
def
|
|
85
|
-
def
|
|
93
|
+
def normalize_merge_policy: (untyped value) -> Symbol?
|
|
94
|
+
def generation_start_date: (untyped start_date, existing_schedule: Hash[String, untyped]?, generation_mode: Symbol?, merge_policy: Symbol?) -> Date
|
|
95
|
+
def generation_days_for: (Date start_date, generation_mode: Symbol?, existing_schedule: Hash[String, untyped]?, merge_policy: Symbol?) -> Integer
|
|
96
|
+
def schedule_key_for: (String prompt_text, Date start_date, Array[CoachZed::Catalog::Entry] catalog, Integer generation_days, String? existing_context, Symbol? merge_policy) -> String
|
|
86
97
|
def catalog_digest: (Array[CoachZed::Catalog::Entry] catalog) -> String
|
|
87
|
-
def normalize_schedule: (Hash[String, untyped] schedule, start_date: Date, prompt_text: String, schedule_key: String, catalog: Array[CoachZed::Catalog::Entry], generation_days: Integer) -> Hash[String, untyped]
|
|
98
|
+
def normalize_schedule: (Hash[String, untyped] schedule, start_date: Date, prompt_text: String, schedule_key: String, catalog: Array[CoachZed::Catalog::Entry], generation_days: Integer, merge_policy: Symbol?) -> Hash[String, untyped]
|
|
99
|
+
def merge_schedule: (Hash[String, untyped]?, Hash[String, untyped], Symbol?) -> Hash[String, untyped]
|
|
88
100
|
def write_schedule: (Hash[String, untyped] schedule, String schedule_key) -> Pathname
|
|
89
|
-
def write_feeds: (Hash[String, untyped] schedule
|
|
101
|
+
def write_feeds: (Hash[String, untyped] schedule) -> Hash[Symbol, Pathname]
|
|
90
102
|
def schedule_filename: (String schedule_key) -> String
|
|
91
103
|
def feed_basename: -> String
|
|
104
|
+
def schedule_context: (Hash[String, untyped] schedule, ?limit_days: Integer) -> String
|
|
92
105
|
|
|
93
106
|
module Catalog
|
|
94
107
|
class Entry
|
|
@@ -196,14 +209,10 @@ class CoachZed
|
|
|
196
209
|
|
|
197
210
|
class FeedWriter
|
|
198
211
|
attr_reader schedule: Hash[String, untyped]
|
|
199
|
-
attr_reader start_date: Date
|
|
200
|
-
attr_reader existing_feed_content: String?
|
|
201
212
|
attr_reader calendar_name: String?
|
|
202
213
|
|
|
203
214
|
def initialize: (
|
|
204
215
|
schedule: Hash[String, untyped],
|
|
205
|
-
start_date: Date,
|
|
206
|
-
?existing_feed_content: String?,
|
|
207
216
|
?calendar_name: String?
|
|
208
217
|
) -> void
|
|
209
218
|
|
|
@@ -212,7 +221,6 @@ class CoachZed
|
|
|
212
221
|
private
|
|
213
222
|
|
|
214
223
|
def fresh_feed: -> String
|
|
215
|
-
def append_to_existing_feed: (String existing_feed) -> String
|
|
216
224
|
def header_lines: -> Array[String]
|
|
217
225
|
def event_lines: -> Array[String]
|
|
218
226
|
def schedule_name: -> String
|