run_ical 0.4.3 → 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 +5 -5
- data/.github/workflows/ci.yml +33 -0
- data/.gitignore +1 -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 +8 -0
- data/lib/run_ical/data/marathon_novice2.yaml +127 -0
- 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 +50 -28
- data/run_ical.gemspec +20 -7
- data/tasks/higdon_programs.yml +179 -0
- data/tasks/refresh_data.rake +156 -0
- metadata +188 -20
- data/.travis.yml +0 -5
- data/Gemfile.lock +0 -52
|
@@ -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,15 +1,56 @@
|
|
|
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
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 5.0.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 5.0.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: base64
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: bigdecimal
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
13
54
|
- !ruby/object:Gem::Dependency
|
|
14
55
|
name: icalendar
|
|
15
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -25,47 +66,117 @@ dependencies:
|
|
|
25
66
|
- !ruby/object:Gem::Version
|
|
26
67
|
version: 2.0.0
|
|
27
68
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
69
|
+
name: logger
|
|
29
70
|
requirement: !ruby/object:Gem::Requirement
|
|
30
71
|
requirements:
|
|
31
72
|
- - ">="
|
|
32
73
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
74
|
+
version: '0'
|
|
34
75
|
type: :runtime
|
|
35
76
|
prerelease: false
|
|
36
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
78
|
requirements:
|
|
38
79
|
- - ">="
|
|
39
80
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
81
|
+
version: '0'
|
|
41
82
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
83
|
+
name: mutex_m
|
|
43
84
|
requirement: !ruby/object:Gem::Requirement
|
|
44
85
|
requirements:
|
|
45
|
-
- - "
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: ostruct
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: yaml
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0'
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
46
122
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
123
|
+
version: '0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: irb
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
48
131
|
type: :development
|
|
49
132
|
prerelease: false
|
|
50
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
134
|
requirements:
|
|
52
|
-
- - "
|
|
135
|
+
- - ">="
|
|
53
136
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
137
|
+
version: '0'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: nokogiri
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - ">="
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '0'
|
|
55
152
|
- !ruby/object:Gem::Dependency
|
|
56
153
|
name: rake
|
|
57
154
|
requirement: !ruby/object:Gem::Requirement
|
|
58
155
|
requirements:
|
|
59
156
|
- - "~>"
|
|
60
157
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
158
|
+
version: '13.0'
|
|
62
159
|
type: :development
|
|
63
160
|
prerelease: false
|
|
64
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
162
|
requirements:
|
|
66
163
|
- - "~>"
|
|
67
164
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
165
|
+
version: '13.0'
|
|
166
|
+
- !ruby/object:Gem::Dependency
|
|
167
|
+
name: rdoc
|
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - ">="
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '0'
|
|
173
|
+
type: :development
|
|
174
|
+
prerelease: false
|
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
176
|
+
requirements:
|
|
177
|
+
- - ">="
|
|
178
|
+
- !ruby/object:Gem::Version
|
|
179
|
+
version: '0'
|
|
69
180
|
- !ruby/object:Gem::Dependency
|
|
70
181
|
name: rspec
|
|
71
182
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,6 +191,20 @@ dependencies:
|
|
|
80
191
|
- - "~>"
|
|
81
192
|
- !ruby/object:Gem::Version
|
|
82
193
|
version: '3.0'
|
|
194
|
+
- !ruby/object:Gem::Dependency
|
|
195
|
+
name: rubocop
|
|
196
|
+
requirement: !ruby/object:Gem::Requirement
|
|
197
|
+
requirements:
|
|
198
|
+
- - "~>"
|
|
199
|
+
- !ruby/object:Gem::Version
|
|
200
|
+
version: '1.0'
|
|
201
|
+
type: :development
|
|
202
|
+
prerelease: false
|
|
203
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
204
|
+
requirements:
|
|
205
|
+
- - "~>"
|
|
206
|
+
- !ruby/object:Gem::Version
|
|
207
|
+
version: '1.0'
|
|
83
208
|
description: Uses Hal Higdon's plans for creating a calendar
|
|
84
209
|
email:
|
|
85
210
|
- scashin133@gmail.com
|
|
@@ -88,12 +213,11 @@ executables:
|
|
|
88
213
|
extensions: []
|
|
89
214
|
extra_rdoc_files: []
|
|
90
215
|
files:
|
|
216
|
+
- ".github/workflows/ci.yml"
|
|
91
217
|
- ".gitignore"
|
|
92
218
|
- ".rspec"
|
|
93
219
|
- ".rubocop.yml"
|
|
94
|
-
- ".travis.yml"
|
|
95
220
|
- Gemfile
|
|
96
|
-
- Gemfile.lock
|
|
97
221
|
- LICENSE.txt
|
|
98
222
|
- README.md
|
|
99
223
|
- Rakefile
|
|
@@ -102,33 +226,79 @@ files:
|
|
|
102
226
|
- exe/run_ical
|
|
103
227
|
- lib/run_ical.rb
|
|
104
228
|
- lib/run_ical/data/advanced_10_k.rb
|
|
229
|
+
- lib/run_ical/data/advanced_10_k.yaml
|
|
105
230
|
- lib/run_ical/data/advanced_15_k.rb
|
|
231
|
+
- lib/run_ical/data/advanced_15_k.yaml
|
|
106
232
|
- lib/run_ical/data/advanced_5_k.rb
|
|
233
|
+
- lib/run_ical/data/advanced_5_k.yaml
|
|
107
234
|
- lib/run_ical/data/advanced_8_k.rb
|
|
235
|
+
- lib/run_ical/data/advanced_8_k.yaml
|
|
108
236
|
- lib/run_ical/data/half_marathon_advanced.rb
|
|
237
|
+
- lib/run_ical/data/half_marathon_advanced.yaml
|
|
109
238
|
- lib/run_ical/data/half_marathon_hm3.rb
|
|
239
|
+
- lib/run_ical/data/half_marathon_hm3.yaml
|
|
110
240
|
- lib/run_ical/data/half_marathon_intermediate1.rb
|
|
241
|
+
- lib/run_ical/data/half_marathon_intermediate1.yaml
|
|
111
242
|
- lib/run_ical/data/half_marathon_intermediate2.rb
|
|
243
|
+
- lib/run_ical/data/half_marathon_intermediate2.yaml
|
|
112
244
|
- lib/run_ical/data/half_marathon_novice1.rb
|
|
245
|
+
- lib/run_ical/data/half_marathon_novice1.yaml
|
|
113
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
|
|
114
250
|
- lib/run_ical/data/intermediate_10_k.rb
|
|
251
|
+
- lib/run_ical/data/intermediate_10_k.yaml
|
|
115
252
|
- lib/run_ical/data/intermediate_15_k.rb
|
|
253
|
+
- lib/run_ical/data/intermediate_15_k.yaml
|
|
116
254
|
- lib/run_ical/data/intermediate_5_k.rb
|
|
255
|
+
- lib/run_ical/data/intermediate_5_k.yaml
|
|
117
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
|
|
118
272
|
- lib/run_ical/data/marathon_novice1.rb
|
|
273
|
+
- lib/run_ical/data/marathon_novice1.yaml
|
|
274
|
+
- lib/run_ical/data/marathon_novice2.rb
|
|
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
|
|
119
282
|
- lib/run_ical/data/novice_10_k.rb
|
|
283
|
+
- lib/run_ical/data/novice_10_k.yaml
|
|
120
284
|
- lib/run_ical/data/novice_15_k.rb
|
|
285
|
+
- lib/run_ical/data/novice_15_k.yaml
|
|
121
286
|
- lib/run_ical/data/novice_5_k.rb
|
|
287
|
+
- lib/run_ical/data/novice_5_k.yaml
|
|
122
288
|
- lib/run_ical/data/novice_8_k.rb
|
|
289
|
+
- lib/run_ical/data/novice_8_k.yaml
|
|
123
290
|
- lib/run_ical/data/walkers_5_k.rb
|
|
291
|
+
- lib/run_ical/data/walkers_5_k.yaml
|
|
124
292
|
- lib/run_ical/data/walking_10_k.rb
|
|
293
|
+
- lib/run_ical/data/walking_10_k.yaml
|
|
125
294
|
- lib/run_ical/version.rb
|
|
126
295
|
- run_ical.gemspec
|
|
296
|
+
- tasks/higdon_programs.yml
|
|
297
|
+
- tasks/refresh_data.rake
|
|
127
298
|
homepage: https://github.com/scashin133/run_ical
|
|
128
299
|
licenses:
|
|
129
300
|
- MIT
|
|
130
301
|
metadata: {}
|
|
131
|
-
post_install_message:
|
|
132
302
|
rdoc_options: []
|
|
133
303
|
require_paths:
|
|
134
304
|
- lib
|
|
@@ -136,16 +306,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
136
306
|
requirements:
|
|
137
307
|
- - ">="
|
|
138
308
|
- !ruby/object:Gem::Version
|
|
139
|
-
version: '
|
|
309
|
+
version: '3.1'
|
|
140
310
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
311
|
requirements:
|
|
142
312
|
- - ">="
|
|
143
313
|
- !ruby/object:Gem::Version
|
|
144
314
|
version: '0'
|
|
145
315
|
requirements: []
|
|
146
|
-
|
|
147
|
-
rubygems_version: 2.5.2.1
|
|
148
|
-
signing_key:
|
|
316
|
+
rubygems_version: 4.0.16
|
|
149
317
|
specification_version: 4
|
|
150
318
|
summary: Generate an ical file for a race
|
|
151
319
|
test_files: []
|