forked from uwdub/web-dub
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtasks.py
296 lines (240 loc) · 10.7 KB
/
tasks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import hashlib
import invoke
import jinja2
import re
import os
import sys
import yaml
VERBOSE = False
def check_result(result, description):
if result.failed:
print('========================================')
print('Failed to {}'.format(description))
print('')
print('========================================')
print('STDOUT:')
print('========================================')
print(result.stdout)
print('========================================')
print('STDERR:')
print('========================================')
print(result.stderr)
print('========================================')
raise Exception('Failed to {}'.format(description))
@invoke.task
def verbose():
global VERBOSE
VERBOSE = True
@invoke.task
def update_dependencies():
# Parameters to keep everything silent
params_silent = {
'encoding': sys.stdout.encoding,
'hide': 'both' if VERBOSE is False else None,
'warn': True
}
# Parse our compile config
with open('_compile-config.yml') as f:
compile_config_yaml = yaml.safe_load(f)
# The npm install command sometimes outputs characters that cause Unicode
# errors on Windows, so we set the encoding parameter on our commands
print('Checking Node.js dependencies')
result = invoke.run('npm install', **params_silent)
check_result(result, 'install Node.js dependencies')
# Python dependencies
print('Checking Python dependencies')
# Ensure we have a current version of pip, as needed by pip-tools
pip_version_desired = compile_config_yaml['config']['local']['python']['pip_version']
result = invoke.run('pip --disable-pip-version-check show pip', **params_silent)
check_result(result, 'check pip version')
pip_version_current = re.search('^Version: ([\d\.]+)', result.stdout, re.MULTILINE).group(1)
if pip_version_current != pip_version_desired:
result = invoke.run(
'python -m pip install --upgrade pip=={}'.format(
pip_version_desired
),
**params_silent
)
check_result(result, 'update pip version')
# Ensures we have pip-tools, which will be in our requirements file
# pip-sync also does not respect options in the requirements file,
# so installing the entire file ensures pip-sync only needs to delete
result = invoke.run('pip --disable-pip-version-check install -r requirements3.txt', **params_silent)
check_result(result, 'install pip requirements')
# Ensure we have exactly our dependencies
result = invoke.run('pip-sync requirements3.txt', **params_silent)
check_result(result, 'sync pip requirements')
# Ruby dependencies
print('Checking Ruby dependencies')
# Check we have the correct Bundler version
bundler_version_desired = compile_config_yaml['config']['local']['ruby']['bundler_version']
result = invoke.run('gem list -i bundler -v {}'.format(bundler_version_desired), **params_silent)
# expected to fail if the desired version is not installed
if result.failed:
result = invoke.run('gem install bundler -v {}'.format(bundler_version_desired), **params_silent)
check_result(result, 'install bundler')
# And only the correct Bundler version
invoke.run('gem uninstall bundler -v "!={}"'.format(bundler_version_desired), **params_silent)
# expected to fail if no other versions of bundler are installed
# Check we have our Ruby dependencies
result = invoke.run('bundle check', **params_silent)
# expected to fail if we are missing dependencies
if result.failed:
result = invoke.run('bundle install', **params_silent)
check_result(result, 'bundle install')
@invoke.task(pre=[update_dependencies])
def build_production():
invoke.run(
'bundle exec jekyll build -t --config _config.yml,_config-production.yml',
encoding=sys.stdout.encoding
)
@invoke.task(pre=[update_dependencies])
def build_test():
invoke.run(
'bundle exec jekyll build -t --config _config.yml,_config-test.yml',
encoding=sys.stdout.encoding
)
@invoke.task()
def compile_config():
# Parse our compile config
with open('_compile-config.yml') as f:
compile_config_yaml = yaml.safe_load(f)
# Compile each jinja2 file
for jinja2_entry in compile_config_yaml['jinja2']['entries']:
jinja2_environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(searchpath='.'),
undefined=jinja2.StrictUndefined
)
template = jinja2_environment.get_template(jinja2_entry['in'])
with open(jinja2_entry['out'], 'w') as f:
f.write(template.render(compile_config_yaml['config']))
@invoke.task()
def compile_calendar():
# Obtain our stored sequences
with open('_compile-calendar-sequences.yml') as f:
seminar_calendar_sequences = yaml.safe_load(f)['sequences']
# Iterate over all our seminar files
seminar_paths = [
os.path.normpath(seminar_file_entry.path)
for seminar_file_entry
in os.scandir('_seminars')
if seminar_file_entry.is_file() and
os.path.normpath(seminar_file_entry.path) != os.path.normpath('_seminars/_template.md')
]
for seminar_path_current in seminar_paths:
# Get the hash and sequence from the file, to compare against our stored data
with open(seminar_path_current, 'rb') as f:
seminar_hash_current = hashlib.md5(f.read()).hexdigest()
with open(seminar_path_current) as f:
seminar_sequence_current = list(yaml.safe_load_all(f))[0]['sequence']
if seminar_path_current not in seminar_calendar_sequences:
# This is a seminar that is new to our sequence tracking
seminar_calendar_sequences[seminar_path_current] = {
'hash': 'invalid_hash_to_force_update',
'sequence': seminar_sequence_current
}
seminar_hash_stored = seminar_calendar_sequences[seminar_path_current]['hash']
seminar_sequence_stored = seminar_calendar_sequences[seminar_path_current]['sequence']
if seminar_hash_current != seminar_hash_stored:
# Change detected, we need to bump the sequence
seminar_sequence_current = max(seminar_sequence_current, seminar_sequence_stored) + 1
# pyyaml does not preserve comments, so we brute force modification of the seminar yml file
with open(seminar_path_current) as f:
seminar_contents = f.read()
seminar_contents = re.sub(
'sequence: {}'.format(seminar_sequence_stored),
'sequence: {}'.format(seminar_sequence_current),
seminar_contents
)
with open(seminar_path_current, 'w') as f:
f.write(seminar_contents)
# That changed the file, so update our hash, then store the updated sequence
with open(seminar_path_current, 'rb') as f:
seminar_hash_current = hashlib.md5(f.read()).hexdigest()
seminar_calendar_sequences[seminar_path_current] = {
'hash': seminar_hash_current,
'sequence': seminar_sequence_current
}
# Store our updated sequences
data = {'sequences': seminar_calendar_sequences }
with open('_compile-calendar-sequences.yml', 'w') as f:
yaml.dump(
data,
stream=f,
default_flow_style=False
)
@invoke.task()
def compile_calendar_increment_all_sequences():
# Obtain our stored sequences
with open('_compile-calendar-sequences.yml') as f:
seminar_calendar_sequences = yaml.safe_load(f)['sequences']
# Iterate over all our seminar files
seminar_paths = [
os.path.normpath(seminar_file_entry.path)
for seminar_file_entry
in os.scandir('_seminars')
if seminar_file_entry.is_file() and
os.path.normpath(seminar_file_entry.path) != os.path.normpath('_seminars/_template.md')
]
for seminar_path_current in seminar_paths:
# Get the hash and sequence from the file
with open(seminar_path_current, 'rb') as f:
seminar_hash_current = hashlib.md5(f.read()).hexdigest()
with open(seminar_path_current) as f:
seminar_sequence_current = list(yaml.safe_load_all(f))[0]['sequence']
if seminar_path_current not in seminar_calendar_sequences:
seminar_calendar_sequences[seminar_path_current] = {
'hash': seminar_hash_current,
'sequence': seminar_sequence_current
}
seminar_hash_stored = seminar_calendar_sequences[seminar_path_current]['hash']
seminar_sequence_stored = seminar_calendar_sequences[seminar_path_current]['sequence']
# Bump the sequence
seminar_sequence_current = max(seminar_sequence_current, seminar_sequence_stored) + 1
# pyyaml does not preserve comments, so we brute force modification of the seminar yml file
with open(seminar_path_current) as f:
seminar_contents = f.read()
seminar_contents = re.sub(
'sequence: {}'.format(seminar_sequence_stored),
'sequence: {}'.format(seminar_sequence_current),
seminar_contents
)
with open(seminar_path_current, 'w') as f:
f.write(seminar_contents)
# That changed the file, so update our hash, then store the updated sequence
with open(seminar_path_current, 'rb') as f:
seminar_hash_current = hashlib.md5(f.read()).hexdigest()
seminar_calendar_sequences[seminar_path_current] = {
'hash': seminar_hash_current,
'sequence': seminar_sequence_current
}
# Store our updated sequences
data = {'sequences': seminar_calendar_sequences }
with open('_compile-calendar-sequences.yml', 'w') as f:
yaml.dump(
data,
stream=f,
default_flow_style=False
)
@invoke.task()
def compile_requirements():
# Compile the requirements file
invoke.run(
'pip-compile --upgrade --output-file requirements3.txt requirements3.in',
encoding=sys.stdout.encoding
)
@invoke.task(pre=[update_dependencies])
def serve_production():
invoke.run(
'bundle exec jekyll serve -t --config _config.yml,_config-production.yml -H 0.0.0.0',
encoding=sys.stdout.encoding
)
@invoke.task(pre=[update_dependencies])
def serve_test():
invoke.run(
'bundle exec jekyll serve -t --config _config.yml,_config-test.yml --watch --force_polling',
encoding=sys.stdout.encoding
)
@invoke.task
def update_base():
invoke.run('git pull https://github.com/fogies/web-jekyll-base.git master', encoding=sys.stdout.encoding)