run_ical 0.5.0 → 0.6.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/.github/workflows/ci.yml +33 -0
- data/.rubocop.yml +34 -1
- data/Gemfile +3 -1
- data/README.md +35 -0
- data/Rakefile +5 -1
- data/exe/run_ical +13 -22
- data/lib/run_ical/data/advanced_10_k.rb +2 -2
- data/lib/run_ical/data/advanced_10_k.yaml +57 -0
- data/lib/run_ical/data/advanced_15_k.rb +2 -2
- data/lib/run_ical/data/advanced_15_k.yaml +71 -0
- data/lib/run_ical/data/advanced_5_k.rb +1 -1
- data/lib/run_ical/data/advanced_5_k.yaml +57 -0
- data/lib/run_ical/data/advanced_8_k.rb +8 -0
- data/lib/run_ical/data/advanced_8_k.yaml +57 -0
- data/lib/run_ical/data/half_marathon_advanced.rb +2 -2
- data/lib/run_ical/data/half_marathon_advanced.yaml +85 -0
- data/lib/run_ical/data/half_marathon_hm3.rb +1 -1
- data/lib/run_ical/data/half_marathon_hm3.yaml +85 -0
- data/lib/run_ical/data/half_marathon_intermediate1.rb +2 -2
- data/lib/run_ical/data/half_marathon_intermediate1.yaml +85 -0
- data/lib/run_ical/data/half_marathon_intermediate2.rb +2 -2
- data/lib/run_ical/data/half_marathon_intermediate2.yaml +85 -0
- data/lib/run_ical/data/half_marathon_novice1.rb +2 -2
- data/lib/run_ical/data/half_marathon_novice1.yaml +85 -0
- data/lib/run_ical/data/half_marathon_novice2.rb +2 -2
- data/lib/run_ical/data/half_marathon_novice2.yaml +85 -0
- data/lib/run_ical/data/half_marathon_walkers.rb +8 -0
- data/lib/run_ical/data/half_marathon_walkers.yaml +85 -0
- data/lib/run_ical/data/intermediate_10_k.rb +2 -2
- data/lib/run_ical/data/intermediate_10_k.yaml +57 -0
- data/lib/run_ical/data/intermediate_15_k.rb +2 -2
- data/lib/run_ical/data/intermediate_15_k.yaml +71 -0
- data/lib/run_ical/data/intermediate_5_k.rb +1 -1
- data/lib/run_ical/data/intermediate_5_k.yaml +57 -0
- data/lib/run_ical/data/intermediate_8_k.rb +2 -2
- data/lib/run_ical/data/intermediate_8_k.yaml +57 -0
- data/lib/run_ical/data/marathon_3.rb +8 -0
- data/lib/run_ical/data/marathon_3.yaml +169 -0
- data/lib/run_ical/data/marathon_advanced1.rb +8 -0
- data/lib/run_ical/data/marathon_advanced1.yaml +127 -0
- data/lib/run_ical/data/marathon_advanced2.rb +8 -0
- data/lib/run_ical/data/marathon_advanced2.yaml +127 -0
- data/lib/run_ical/data/marathon_boston_bound.rb +8 -0
- data/lib/run_ical/data/marathon_boston_bound.yaml +86 -0
- data/lib/run_ical/data/marathon_dopey_challenge.rb +8 -0
- data/lib/run_ical/data/marathon_dopey_challenge.yaml +127 -0
- data/lib/run_ical/data/marathon_intermediate1.rb +8 -0
- data/lib/run_ical/data/marathon_intermediate1.yaml +127 -0
- data/lib/run_ical/data/marathon_intermediate2.rb +8 -0
- data/lib/run_ical/data/marathon_intermediate2.yaml +127 -0
- data/lib/run_ical/data/marathon_novice1.rb +2 -2
- data/lib/run_ical/data/marathon_novice1.yaml +127 -0
- data/lib/run_ical/data/marathon_novice2.rb +1 -1
- data/lib/run_ical/data/marathon_novice2.yaml +90 -89
- data/lib/run_ical/data/marathon_novice_supreme.rb +8 -0
- data/lib/run_ical/data/marathon_novice_supreme.yaml +211 -0
- data/lib/run_ical/data/marathon_personal_best.rb +8 -0
- data/lib/run_ical/data/marathon_personal_best.yaml +211 -0
- data/lib/run_ical/data/marathon_senior.rb +8 -0
- data/lib/run_ical/data/marathon_senior.yaml +57 -0
- data/lib/run_ical/data/novice_10_k.rb +2 -2
- data/lib/run_ical/data/novice_10_k.yaml +57 -0
- data/lib/run_ical/data/novice_15_k.rb +2 -2
- data/lib/run_ical/data/novice_15_k.yaml +71 -0
- data/lib/run_ical/data/novice_5_k.rb +1 -1
- data/lib/run_ical/data/novice_5_k.yaml +57 -0
- data/lib/run_ical/data/novice_8_k.rb +2 -2
- data/lib/run_ical/data/novice_8_k.yaml +57 -0
- data/lib/run_ical/data/walkers_5_k.rb +2 -2
- data/lib/run_ical/data/walkers_5_k.yaml +57 -0
- data/lib/run_ical/data/walking_10_k.rb +2 -2
- data/lib/run_ical/data/walking_10_k.yaml +57 -0
- data/lib/run_ical/version.rb +3 -1
- data/lib/run_ical.rb +46 -27
- data/run_ical.gemspec +15 -10
- data/tasks/higdon_programs.yml +179 -0
- data/tasks/refresh_data.rake +156 -0
- metadata +95 -36
- data/.travis.yml +0 -5
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Source manifest for `rake data:refresh`.
|
|
2
|
+
#
|
|
3
|
+
# One entry per training program. `class` is the public API name passed to
|
|
4
|
+
# RunIcal::Generator.generate(program:) -- never rename an existing one.
|
|
5
|
+
# `weeks` is asserted against the scraped table so a silent site change fails loudly.
|
|
6
|
+
---
|
|
7
|
+
- class: Novice5K
|
|
8
|
+
file: novice_5_k
|
|
9
|
+
description: "5-K : Novice"
|
|
10
|
+
weeks: 8
|
|
11
|
+
url: https://www.halhigdon.com/training-programs/5k-training/novice-5k/
|
|
12
|
+
- class: Intermediate5K
|
|
13
|
+
file: intermediate_5_k
|
|
14
|
+
description: "5-K : Intermediate"
|
|
15
|
+
weeks: 8
|
|
16
|
+
url: https://www.halhigdon.com/training-programs/5k-training/intermediate-5k/
|
|
17
|
+
- class: Advanced5K
|
|
18
|
+
file: advanced_5_k
|
|
19
|
+
description: "5-K : Advanced"
|
|
20
|
+
weeks: 8
|
|
21
|
+
url: https://www.halhigdon.com/training-programs/5k-training/advanced-5k/
|
|
22
|
+
- class: Walkers5K
|
|
23
|
+
file: walkers_5_k
|
|
24
|
+
description: "5-K - Walkers"
|
|
25
|
+
weeks: 8
|
|
26
|
+
url: https://www.halhigdon.com/training-programs/5k-training/walkers-5k/
|
|
27
|
+
|
|
28
|
+
- class: Novice8K
|
|
29
|
+
file: novice_8_k
|
|
30
|
+
description: "8-K - Novice"
|
|
31
|
+
weeks: 8
|
|
32
|
+
url: https://www.halhigdon.com/training-programs/8k-training/novice-8k/
|
|
33
|
+
- class: Intermediate8K
|
|
34
|
+
file: intermediate_8_k
|
|
35
|
+
description: "8-K - Intermediate"
|
|
36
|
+
weeks: 8
|
|
37
|
+
url: https://www.halhigdon.com/training-programs/8k-training/intermediate-8k/
|
|
38
|
+
- class: Advanced8K
|
|
39
|
+
file: advanced_8_k
|
|
40
|
+
description: "8-K - Advanced"
|
|
41
|
+
weeks: 8
|
|
42
|
+
url: https://www.halhigdon.com/training-programs/8k-training/advanced-8k/
|
|
43
|
+
|
|
44
|
+
- class: Novice10K
|
|
45
|
+
file: novice_10_k
|
|
46
|
+
description: "10-K - Novice"
|
|
47
|
+
weeks: 8
|
|
48
|
+
url: https://www.halhigdon.com/training-programs/10k-training/novice-10k/
|
|
49
|
+
- class: Intermediate10K
|
|
50
|
+
file: intermediate_10_k
|
|
51
|
+
description: "10-K - Intermediate"
|
|
52
|
+
weeks: 8
|
|
53
|
+
url: https://www.halhigdon.com/training-programs/10k-training/intermediate-10k/
|
|
54
|
+
- class: Advanced10K
|
|
55
|
+
file: advanced_10_k
|
|
56
|
+
description: "10-K - Advanced"
|
|
57
|
+
weeks: 8
|
|
58
|
+
url: https://www.halhigdon.com/training-programs/10k-training/advanced-10k/
|
|
59
|
+
- class: Walking10K
|
|
60
|
+
file: walking_10_k
|
|
61
|
+
description: "10-K - Walking"
|
|
62
|
+
weeks: 8
|
|
63
|
+
url: https://www.halhigdon.com/training-programs/10k-training/walkers-10k/
|
|
64
|
+
|
|
65
|
+
- class: Novice15K
|
|
66
|
+
file: novice_15_k
|
|
67
|
+
description: "15-K - Novice"
|
|
68
|
+
weeks: 10
|
|
69
|
+
url: https://www.halhigdon.com/training-programs/15k-10-mile-training/novice-15k-10-mile/
|
|
70
|
+
- class: Intermediate15K
|
|
71
|
+
file: intermediate_15_k
|
|
72
|
+
description: "15-K - Intermediate"
|
|
73
|
+
weeks: 10
|
|
74
|
+
url: https://www.halhigdon.com/training-programs/15k-10-mile-training/intermediate-15k-10-mile/
|
|
75
|
+
- class: Advanced15K
|
|
76
|
+
file: advanced_15_k
|
|
77
|
+
description: "15-K - Advanced"
|
|
78
|
+
weeks: 10
|
|
79
|
+
url: https://www.halhigdon.com/training-programs/15k-10-mile-training/advanced-15k-10-mile/
|
|
80
|
+
|
|
81
|
+
- class: HalfMarathonNovice1
|
|
82
|
+
file: half_marathon_novice1
|
|
83
|
+
description: "Half Marathon - Novice 1"
|
|
84
|
+
weeks: 12
|
|
85
|
+
url: https://www.halhigdon.com/training-programs/half-marathon-training/novice-1-half-marathon/
|
|
86
|
+
- class: HalfMarathonNovice2
|
|
87
|
+
file: half_marathon_novice2
|
|
88
|
+
description: "Half Marathon - Novice 2"
|
|
89
|
+
weeks: 12
|
|
90
|
+
url: https://www.halhigdon.com/training-programs/half-marathon-training/novice-2-half-marathon/
|
|
91
|
+
- class: HalfMarathonIntermediate1
|
|
92
|
+
file: half_marathon_intermediate1
|
|
93
|
+
description: "Half Marathon - Intermediate 1"
|
|
94
|
+
weeks: 12
|
|
95
|
+
url: https://www.halhigdon.com/training-programs/half-marathon-training/intermediate-1-half-marathon/
|
|
96
|
+
- class: HalfMarathonIntermediate2
|
|
97
|
+
file: half_marathon_intermediate2
|
|
98
|
+
description: "Half Marathon - Intermediate 2"
|
|
99
|
+
weeks: 12
|
|
100
|
+
url: https://www.halhigdon.com/training-programs/half-marathon-training/intermediate-2-half-marathon/
|
|
101
|
+
- class: HalfMarathonAdvanced
|
|
102
|
+
file: half_marathon_advanced
|
|
103
|
+
description: "Half Marathon - Advanced"
|
|
104
|
+
weeks: 12
|
|
105
|
+
url: https://www.halhigdon.com/training-programs/half-marathon-training/advanced-half-marathon/
|
|
106
|
+
- class: HalfMarathonHM3
|
|
107
|
+
file: half_marathon_hm3
|
|
108
|
+
description: "Half Marathon - HM3"
|
|
109
|
+
weeks: 12
|
|
110
|
+
url: https://www.halhigdon.com/training-programs/half-marathon-training/half-marathon-3/
|
|
111
|
+
- class: HalfMarathonWalkers
|
|
112
|
+
file: half_marathon_walkers
|
|
113
|
+
description: "Half Marathon - Walkers"
|
|
114
|
+
weeks: 12
|
|
115
|
+
url: https://www.halhigdon.com/training-programs/half-marathon-training/walkers-half-marathon/
|
|
116
|
+
|
|
117
|
+
- class: MarathonNovice1
|
|
118
|
+
file: marathon_novice1
|
|
119
|
+
description: "Marathon : Novice 1"
|
|
120
|
+
weeks: 18
|
|
121
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/novice-1-marathon/
|
|
122
|
+
- class: MarathonNovice2
|
|
123
|
+
file: marathon_novice2
|
|
124
|
+
description: "Marathon : Novice 2"
|
|
125
|
+
weeks: 18
|
|
126
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/novice-2-marathon/
|
|
127
|
+
- class: MarathonIntermediate1
|
|
128
|
+
file: marathon_intermediate1
|
|
129
|
+
description: "Marathon : Intermediate 1"
|
|
130
|
+
weeks: 18
|
|
131
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/intermediate-1-marathon/
|
|
132
|
+
- class: MarathonIntermediate2
|
|
133
|
+
file: marathon_intermediate2
|
|
134
|
+
description: "Marathon : Intermediate 2"
|
|
135
|
+
weeks: 18
|
|
136
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/intermediate-2-marathon/
|
|
137
|
+
- class: MarathonAdvanced1
|
|
138
|
+
file: marathon_advanced1
|
|
139
|
+
description: "Marathon : Advanced 1"
|
|
140
|
+
weeks: 18
|
|
141
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/advanced-1-marathon/
|
|
142
|
+
- class: MarathonAdvanced2
|
|
143
|
+
file: marathon_advanced2
|
|
144
|
+
description: "Marathon : Advanced 2"
|
|
145
|
+
weeks: 18
|
|
146
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/advanced-2-marathon/
|
|
147
|
+
- class: MarathonNoviceSupreme
|
|
148
|
+
file: marathon_novice_supreme
|
|
149
|
+
description: "Marathon : Novice Supreme"
|
|
150
|
+
weeks: 30
|
|
151
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/novice-supreme/
|
|
152
|
+
- class: MarathonPersonalBest
|
|
153
|
+
file: marathon_personal_best
|
|
154
|
+
description: "Marathon : Personal Best"
|
|
155
|
+
weeks: 30
|
|
156
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/personal-best/
|
|
157
|
+
- class: MarathonSenior
|
|
158
|
+
file: marathon_senior
|
|
159
|
+
description: "Marathon : Senior"
|
|
160
|
+
weeks: 8
|
|
161
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/senior/
|
|
162
|
+
- class: Marathon3
|
|
163
|
+
file: marathon_3
|
|
164
|
+
description: "Marathon : Marathon 3"
|
|
165
|
+
weeks: 24
|
|
166
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/marathon-3/
|
|
167
|
+
- class: MarathonDopeyChallenge
|
|
168
|
+
file: marathon_dopey_challenge
|
|
169
|
+
description: "Marathon : Dopey Challenge"
|
|
170
|
+
weeks: 18
|
|
171
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/dopey-challenge/
|
|
172
|
+
# Boston is run on Patriots' Day (a Monday), so the final table row holds a single
|
|
173
|
+
# Monday cell instead of a full week. The scraper truncates to 12 full weeks + race day.
|
|
174
|
+
- class: MarathonBostonBound
|
|
175
|
+
file: marathon_boston_bound
|
|
176
|
+
description: "Marathon : Boston Bound"
|
|
177
|
+
weeks: 13
|
|
178
|
+
partial_final_week: true
|
|
179
|
+
url: https://www.halhigdon.com/training-programs/marathon-training/boston-bound/
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "yaml"
|
|
6
|
+
|
|
7
|
+
# Re-scrapes the training plans in lib/run_ical/data from halhigdon.com.
|
|
8
|
+
#
|
|
9
|
+
# rake data:refresh # every program in tasks/higdon_programs.yml
|
|
10
|
+
# rake data:refresh[Novice5K] # just one
|
|
11
|
+
#
|
|
12
|
+
# Every page renders the schedule as a static table with a `Week | Mon..Sun` header
|
|
13
|
+
# row, in miles first and kilometres second. We take the miles table, flatten it
|
|
14
|
+
# row-major into one activity per day, and write a YAML sidecar plus a loader stub.
|
|
15
|
+
module HigdonScraper
|
|
16
|
+
DATA_DIR = File.expand_path("../lib/run_ical/data", __dir__)
|
|
17
|
+
MANIFEST = File.expand_path("higdon_programs.yml", __dir__)
|
|
18
|
+
DAYS_PER_WEEK = 7
|
|
19
|
+
|
|
20
|
+
# The final cell of a plan is the race itself. Higdon isn't consistent about it:
|
|
21
|
+
# most read "<distance> Race", but Novice 10K ends on "10K Run", Walking 10K on
|
|
22
|
+
# "10K Walk", and the marathons on a bare "Marathon" / "Boston Marathon".
|
|
23
|
+
RACE = /\A(\d+K (Race|Run|Walk)|(Boston |Half )?Marathon)\z/i
|
|
24
|
+
|
|
25
|
+
class ScrapeError < StandardError; end
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# Every program in the manifest, or the single one named.
|
|
30
|
+
def programs(only = nil)
|
|
31
|
+
all = YAML.safe_load_file(MANIFEST)
|
|
32
|
+
return all unless only
|
|
33
|
+
|
|
34
|
+
selected = all.select { |program| program["class"] == only }
|
|
35
|
+
raise ScrapeError, "unknown program #{only}" if selected.empty?
|
|
36
|
+
|
|
37
|
+
selected
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def scrape(program)
|
|
41
|
+
rows = schedule_rows(Nokogiri::HTML(fetch(program["url"])), program)
|
|
42
|
+
unless rows.size == program["weeks"]
|
|
43
|
+
raise ScrapeError, "#{program['class']}: expected #{program['weeks']} weeks, got #{rows.size}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
activities = rows.flat_map { |row| week_activities(row) }
|
|
47
|
+
activities = trim_partial_final_week(activities) if program["partial_final_week"]
|
|
48
|
+
validate!(program, activities)
|
|
49
|
+
activities
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def fetch(url)
|
|
53
|
+
uri = URI(url)
|
|
54
|
+
response = Net::HTTP.get_response(uri, "User-Agent" => "run_ical data refresh")
|
|
55
|
+
raise ScrapeError, "#{url}: HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
|
56
|
+
|
|
57
|
+
response.body
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def schedule_rows(doc, program)
|
|
61
|
+
table = schedule_table(doc)
|
|
62
|
+
raise ScrapeError, "#{program['class']}: no schedule table found" unless table
|
|
63
|
+
|
|
64
|
+
table.css("tbody tr").map { |tr| tr.css("td").map { |td| cell_text(td) } }.reject(&:empty?)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The first table whose header starts "Week | Mon" -- the walkers half marathon page
|
|
68
|
+
# leads with an unrelated pace legend, and the kilometre table always follows the
|
|
69
|
+
# miles one, so first-match gives us miles.
|
|
70
|
+
def schedule_table(doc)
|
|
71
|
+
doc.css("table").find do |candidate|
|
|
72
|
+
headers = candidate.css("th").map { |th| cell_text(th) }
|
|
73
|
+
headers[0] == "Week" && headers[1] == "Mon"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Cells are taken by position, not by header: a few pages ship an empty <th> where
|
|
78
|
+
# "Wed" belongs. The 8K pages add a ninth "Miles" total column, and on the novice
|
|
79
|
+
# page the race sits in it rather than under Sunday -- pull it back onto race day.
|
|
80
|
+
def week_activities(row)
|
|
81
|
+
week = row[1, DAYS_PER_WEEK]
|
|
82
|
+
total = row[DAYS_PER_WEEK + 1]
|
|
83
|
+
week[-1] = total if total&.match?(RACE)
|
|
84
|
+
week
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Boston's last row is a lone Monday race day padded with six empty cells.
|
|
88
|
+
def trim_partial_final_week(activities)
|
|
89
|
+
race = activities.reverse.find { |activity| !activity.empty? }
|
|
90
|
+
activities.take(activities.size - DAYS_PER_WEEK) << race
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def validate!(program, activities)
|
|
94
|
+
blank = activities.index(&:empty?)
|
|
95
|
+
raise ScrapeError, "#{program['class']}: empty activity at index #{blank}" if blank
|
|
96
|
+
|
|
97
|
+
expected = expected_size(program)
|
|
98
|
+
unless activities.size == expected
|
|
99
|
+
raise ScrapeError, "#{program['class']}: expected #{expected} days, got #{activities.size}"
|
|
100
|
+
end
|
|
101
|
+
return if activities.last.match?(RACE)
|
|
102
|
+
|
|
103
|
+
raise ScrapeError, "#{program['class']}: plan ends on #{activities.last.inspect}, not a race"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def expected_size(program)
|
|
107
|
+
weeks = program["weeks"]
|
|
108
|
+
return weeks * DAYS_PER_WEEK unless program["partial_final_week"]
|
|
109
|
+
|
|
110
|
+
((weeks - 1) * DAYS_PER_WEEK) + 1
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Higdon's markup is littered with non-breaking spaces and hard line breaks.
|
|
114
|
+
def cell_text(node)
|
|
115
|
+
node.text.gsub(/[[:space:]]+/, " ").strip
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def write(program, activities)
|
|
119
|
+
write_yaml(program, activities)
|
|
120
|
+
write_loader(program)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Ruby's emitter quotes numeric-looking strings, which keeps the marathon plans'
|
|
124
|
+
# bare mileage cells ("6", "20") loading back as Strings.
|
|
125
|
+
def write_yaml(program, activities)
|
|
126
|
+
body = activities.map { |activity| { "activity" => activity } }
|
|
127
|
+
File.write(File.join(DATA_DIR, "#{program['file']}.yaml"), body.to_yaml)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def write_loader(program)
|
|
131
|
+
File.write(File.join(DATA_DIR, "#{program['file']}.rb"), <<~RUBY)
|
|
132
|
+
module RunIcal
|
|
133
|
+
module Data
|
|
134
|
+
class #{program['class']}
|
|
135
|
+
DESCRIPTION = #{program['description'].inspect}
|
|
136
|
+
DATA = YAML.safe_load_file(File.join(__dir__, '#{program['file']}.yaml'), symbolize_names: true)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
RUBY
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
namespace :data do
|
|
145
|
+
desc "Re-scrape training plan data from halhigdon.com (optionally one program)"
|
|
146
|
+
task :refresh, [:program] do |_t, args|
|
|
147
|
+
require "nokogiri"
|
|
148
|
+
|
|
149
|
+
HigdonScraper.programs(args[:program]).each do |program|
|
|
150
|
+
activities = HigdonScraper.scrape(program)
|
|
151
|
+
HigdonScraper.write(program, activities)
|
|
152
|
+
puts format("%<name>-26s %<days>3d days %<race>s",
|
|
153
|
+
name: program["class"], days: activities.size, race: activities.last)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: run_ical
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sean Cashin
|
|
@@ -10,19 +10,19 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: activesupport
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 5.0.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: 5.0.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: base64
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: icalendar
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 2.0.0
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 2.0.0
|
|
54
68
|
- !ruby/object:Gem::Dependency
|
|
55
69
|
name: logger
|
|
56
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -94,103 +108,103 @@ dependencies:
|
|
|
94
108
|
- !ruby/object:Gem::Version
|
|
95
109
|
version: '0'
|
|
96
110
|
- !ruby/object:Gem::Dependency
|
|
97
|
-
name:
|
|
111
|
+
name: yaml
|
|
98
112
|
requirement: !ruby/object:Gem::Requirement
|
|
99
113
|
requirements:
|
|
100
114
|
- - ">="
|
|
101
115
|
- !ruby/object:Gem::Version
|
|
102
|
-
version:
|
|
116
|
+
version: '0'
|
|
103
117
|
type: :runtime
|
|
104
118
|
prerelease: false
|
|
105
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
120
|
requirements:
|
|
107
121
|
- - ">="
|
|
108
122
|
- !ruby/object:Gem::Version
|
|
109
|
-
version:
|
|
123
|
+
version: '0'
|
|
110
124
|
- !ruby/object:Gem::Dependency
|
|
111
|
-
name:
|
|
125
|
+
name: irb
|
|
112
126
|
requirement: !ruby/object:Gem::Requirement
|
|
113
127
|
requirements:
|
|
114
128
|
- - ">="
|
|
115
129
|
- !ruby/object:Gem::Version
|
|
116
|
-
version:
|
|
117
|
-
type: :
|
|
130
|
+
version: '0'
|
|
131
|
+
type: :development
|
|
118
132
|
prerelease: false
|
|
119
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
120
134
|
requirements:
|
|
121
135
|
- - ">="
|
|
122
136
|
- !ruby/object:Gem::Version
|
|
123
|
-
version:
|
|
137
|
+
version: '0'
|
|
124
138
|
- !ruby/object:Gem::Dependency
|
|
125
|
-
name:
|
|
139
|
+
name: nokogiri
|
|
126
140
|
requirement: !ruby/object:Gem::Requirement
|
|
127
141
|
requirements:
|
|
128
|
-
- - "
|
|
142
|
+
- - ">="
|
|
129
143
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: '
|
|
144
|
+
version: '0'
|
|
131
145
|
type: :development
|
|
132
146
|
prerelease: false
|
|
133
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
134
148
|
requirements:
|
|
135
|
-
- - "
|
|
149
|
+
- - ">="
|
|
136
150
|
- !ruby/object:Gem::Version
|
|
137
|
-
version: '
|
|
151
|
+
version: '0'
|
|
138
152
|
- !ruby/object:Gem::Dependency
|
|
139
153
|
name: rake
|
|
140
154
|
requirement: !ruby/object:Gem::Requirement
|
|
141
155
|
requirements:
|
|
142
156
|
- - "~>"
|
|
143
157
|
- !ruby/object:Gem::Version
|
|
144
|
-
version: '
|
|
158
|
+
version: '13.0'
|
|
145
159
|
type: :development
|
|
146
160
|
prerelease: false
|
|
147
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
148
162
|
requirements:
|
|
149
163
|
- - "~>"
|
|
150
164
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: '
|
|
165
|
+
version: '13.0'
|
|
152
166
|
- !ruby/object:Gem::Dependency
|
|
153
|
-
name:
|
|
167
|
+
name: rdoc
|
|
154
168
|
requirement: !ruby/object:Gem::Requirement
|
|
155
169
|
requirements:
|
|
156
|
-
- - "
|
|
170
|
+
- - ">="
|
|
157
171
|
- !ruby/object:Gem::Version
|
|
158
|
-
version: '
|
|
172
|
+
version: '0'
|
|
159
173
|
type: :development
|
|
160
174
|
prerelease: false
|
|
161
175
|
version_requirements: !ruby/object:Gem::Requirement
|
|
162
176
|
requirements:
|
|
163
|
-
- - "
|
|
177
|
+
- - ">="
|
|
164
178
|
- !ruby/object:Gem::Version
|
|
165
|
-
version: '
|
|
179
|
+
version: '0'
|
|
166
180
|
- !ruby/object:Gem::Dependency
|
|
167
|
-
name:
|
|
181
|
+
name: rspec
|
|
168
182
|
requirement: !ruby/object:Gem::Requirement
|
|
169
183
|
requirements:
|
|
170
|
-
- - "
|
|
184
|
+
- - "~>"
|
|
171
185
|
- !ruby/object:Gem::Version
|
|
172
|
-
version: '0'
|
|
186
|
+
version: '3.0'
|
|
173
187
|
type: :development
|
|
174
188
|
prerelease: false
|
|
175
189
|
version_requirements: !ruby/object:Gem::Requirement
|
|
176
190
|
requirements:
|
|
177
|
-
- - "
|
|
191
|
+
- - "~>"
|
|
178
192
|
- !ruby/object:Gem::Version
|
|
179
|
-
version: '0'
|
|
193
|
+
version: '3.0'
|
|
180
194
|
- !ruby/object:Gem::Dependency
|
|
181
|
-
name:
|
|
195
|
+
name: rubocop
|
|
182
196
|
requirement: !ruby/object:Gem::Requirement
|
|
183
197
|
requirements:
|
|
184
|
-
- - "
|
|
198
|
+
- - "~>"
|
|
185
199
|
- !ruby/object:Gem::Version
|
|
186
|
-
version: '0'
|
|
200
|
+
version: '1.0'
|
|
187
201
|
type: :development
|
|
188
202
|
prerelease: false
|
|
189
203
|
version_requirements: !ruby/object:Gem::Requirement
|
|
190
204
|
requirements:
|
|
191
|
-
- - "
|
|
205
|
+
- - "~>"
|
|
192
206
|
- !ruby/object:Gem::Version
|
|
193
|
-
version: '0'
|
|
207
|
+
version: '1.0'
|
|
194
208
|
description: Uses Hal Higdon's plans for creating a calendar
|
|
195
209
|
email:
|
|
196
210
|
- scashin133@gmail.com
|
|
@@ -199,10 +213,10 @@ executables:
|
|
|
199
213
|
extensions: []
|
|
200
214
|
extra_rdoc_files: []
|
|
201
215
|
files:
|
|
216
|
+
- ".github/workflows/ci.yml"
|
|
202
217
|
- ".gitignore"
|
|
203
218
|
- ".rspec"
|
|
204
219
|
- ".rubocop.yml"
|
|
205
|
-
- ".travis.yml"
|
|
206
220
|
- Gemfile
|
|
207
221
|
- LICENSE.txt
|
|
208
222
|
- README.md
|
|
@@ -212,30 +226,75 @@ files:
|
|
|
212
226
|
- exe/run_ical
|
|
213
227
|
- lib/run_ical.rb
|
|
214
228
|
- lib/run_ical/data/advanced_10_k.rb
|
|
229
|
+
- lib/run_ical/data/advanced_10_k.yaml
|
|
215
230
|
- lib/run_ical/data/advanced_15_k.rb
|
|
231
|
+
- lib/run_ical/data/advanced_15_k.yaml
|
|
216
232
|
- lib/run_ical/data/advanced_5_k.rb
|
|
233
|
+
- lib/run_ical/data/advanced_5_k.yaml
|
|
217
234
|
- lib/run_ical/data/advanced_8_k.rb
|
|
235
|
+
- lib/run_ical/data/advanced_8_k.yaml
|
|
218
236
|
- lib/run_ical/data/half_marathon_advanced.rb
|
|
237
|
+
- lib/run_ical/data/half_marathon_advanced.yaml
|
|
219
238
|
- lib/run_ical/data/half_marathon_hm3.rb
|
|
239
|
+
- lib/run_ical/data/half_marathon_hm3.yaml
|
|
220
240
|
- lib/run_ical/data/half_marathon_intermediate1.rb
|
|
241
|
+
- lib/run_ical/data/half_marathon_intermediate1.yaml
|
|
221
242
|
- lib/run_ical/data/half_marathon_intermediate2.rb
|
|
243
|
+
- lib/run_ical/data/half_marathon_intermediate2.yaml
|
|
222
244
|
- lib/run_ical/data/half_marathon_novice1.rb
|
|
245
|
+
- lib/run_ical/data/half_marathon_novice1.yaml
|
|
223
246
|
- lib/run_ical/data/half_marathon_novice2.rb
|
|
247
|
+
- lib/run_ical/data/half_marathon_novice2.yaml
|
|
248
|
+
- lib/run_ical/data/half_marathon_walkers.rb
|
|
249
|
+
- lib/run_ical/data/half_marathon_walkers.yaml
|
|
224
250
|
- lib/run_ical/data/intermediate_10_k.rb
|
|
251
|
+
- lib/run_ical/data/intermediate_10_k.yaml
|
|
225
252
|
- lib/run_ical/data/intermediate_15_k.rb
|
|
253
|
+
- lib/run_ical/data/intermediate_15_k.yaml
|
|
226
254
|
- lib/run_ical/data/intermediate_5_k.rb
|
|
255
|
+
- lib/run_ical/data/intermediate_5_k.yaml
|
|
227
256
|
- lib/run_ical/data/intermediate_8_k.rb
|
|
257
|
+
- lib/run_ical/data/intermediate_8_k.yaml
|
|
258
|
+
- lib/run_ical/data/marathon_3.rb
|
|
259
|
+
- lib/run_ical/data/marathon_3.yaml
|
|
260
|
+
- lib/run_ical/data/marathon_advanced1.rb
|
|
261
|
+
- lib/run_ical/data/marathon_advanced1.yaml
|
|
262
|
+
- lib/run_ical/data/marathon_advanced2.rb
|
|
263
|
+
- lib/run_ical/data/marathon_advanced2.yaml
|
|
264
|
+
- lib/run_ical/data/marathon_boston_bound.rb
|
|
265
|
+
- lib/run_ical/data/marathon_boston_bound.yaml
|
|
266
|
+
- lib/run_ical/data/marathon_dopey_challenge.rb
|
|
267
|
+
- lib/run_ical/data/marathon_dopey_challenge.yaml
|
|
268
|
+
- lib/run_ical/data/marathon_intermediate1.rb
|
|
269
|
+
- lib/run_ical/data/marathon_intermediate1.yaml
|
|
270
|
+
- lib/run_ical/data/marathon_intermediate2.rb
|
|
271
|
+
- lib/run_ical/data/marathon_intermediate2.yaml
|
|
228
272
|
- lib/run_ical/data/marathon_novice1.rb
|
|
273
|
+
- lib/run_ical/data/marathon_novice1.yaml
|
|
229
274
|
- lib/run_ical/data/marathon_novice2.rb
|
|
230
275
|
- lib/run_ical/data/marathon_novice2.yaml
|
|
276
|
+
- lib/run_ical/data/marathon_novice_supreme.rb
|
|
277
|
+
- lib/run_ical/data/marathon_novice_supreme.yaml
|
|
278
|
+
- lib/run_ical/data/marathon_personal_best.rb
|
|
279
|
+
- lib/run_ical/data/marathon_personal_best.yaml
|
|
280
|
+
- lib/run_ical/data/marathon_senior.rb
|
|
281
|
+
- lib/run_ical/data/marathon_senior.yaml
|
|
231
282
|
- lib/run_ical/data/novice_10_k.rb
|
|
283
|
+
- lib/run_ical/data/novice_10_k.yaml
|
|
232
284
|
- lib/run_ical/data/novice_15_k.rb
|
|
285
|
+
- lib/run_ical/data/novice_15_k.yaml
|
|
233
286
|
- lib/run_ical/data/novice_5_k.rb
|
|
287
|
+
- lib/run_ical/data/novice_5_k.yaml
|
|
234
288
|
- lib/run_ical/data/novice_8_k.rb
|
|
289
|
+
- lib/run_ical/data/novice_8_k.yaml
|
|
235
290
|
- lib/run_ical/data/walkers_5_k.rb
|
|
291
|
+
- lib/run_ical/data/walkers_5_k.yaml
|
|
236
292
|
- lib/run_ical/data/walking_10_k.rb
|
|
293
|
+
- lib/run_ical/data/walking_10_k.yaml
|
|
237
294
|
- lib/run_ical/version.rb
|
|
238
295
|
- run_ical.gemspec
|
|
296
|
+
- tasks/higdon_programs.yml
|
|
297
|
+
- tasks/refresh_data.rake
|
|
239
298
|
homepage: https://github.com/scashin133/run_ical
|
|
240
299
|
licenses:
|
|
241
300
|
- MIT
|
|
@@ -247,14 +306,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
247
306
|
requirements:
|
|
248
307
|
- - ">="
|
|
249
308
|
- !ruby/object:Gem::Version
|
|
250
|
-
version: '
|
|
309
|
+
version: '3.1'
|
|
251
310
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
252
311
|
requirements:
|
|
253
312
|
- - ">="
|
|
254
313
|
- !ruby/object:Gem::Version
|
|
255
314
|
version: '0'
|
|
256
315
|
requirements: []
|
|
257
|
-
rubygems_version:
|
|
316
|
+
rubygems_version: 4.0.16
|
|
258
317
|
specification_version: 4
|
|
259
318
|
summary: Generate an ical file for a race
|
|
260
319
|
test_files: []
|