1
- #!/usr/bin/env python3
2
-
3
1
"""
4
2
Check lesson files and their contents.
5
3
"""
32
30
# specially. This list must include all the Markdown files listed in the
33
31
# 'bin/initialize' script.
34
32
REQUIRED_FILES = {
35
- '%/ CODE_OF_CONDUCT.md' : True ,
36
- '%/ CONTRIBUTING.md' : False ,
37
- '%/ LICENSE.md' : True ,
38
- '%/ MAINTENANCE.md' : False ,
39
- '%/ README.md' : False ,
40
- '%/ _extras/ discuss.md' : True ,
41
- '%/ _extras/ guide.md' : True ,
42
- '%/ index.md' : True ,
43
- '%/ reference.md' : True ,
44
- '%/ setup.md' : True ,
33
+ 'CODE_OF_CONDUCT.md' : True ,
34
+ 'CONTRIBUTING.md' : False ,
35
+ 'LICENSE.md' : True ,
36
+ 'MAINTENANCE.md' : False ,
37
+ 'README.md' : False ,
38
+ os . path . join ( ' _extras' , ' discuss.md') : True ,
39
+ os . path . join ( ' _extras' , ' guide.md') : True ,
40
+ 'index.md' : True ,
41
+ 'reference.md' : True ,
42
+ 'setup.md' : True ,
45
43
}
46
44
47
45
# Episode filename pattern.
48
- P_EPISODE_FILENAME = re .compile (r'/_episodes/ (\d\d)-[-\w]+.md$' )
46
+ P_EPISODE_FILENAME = re .compile (r'(\d\d)-[-\w]+.md$' )
49
47
50
48
# Pattern to match lines ending with whitespace.
51
49
P_TRAILING_WHITESPACE = re .compile (r'\s+$' )
79
77
}
80
78
81
79
# What kinds of code fragments are allowed?
80
+ # We allow all 'language-*' code blocks below
82
81
KNOWN_CODEBLOCKS = {
83
- 'bash' ,
84
82
'error' ,
85
- 'html' ,
86
- 'language-bash' ,
87
- 'language-c' ,
88
- 'language-chapel' ,
89
- 'language-cpp' ,
90
- 'language-d' ,
91
- 'language-fortran' ,
92
- 'language-go' ,
93
- 'language-java' ,
94
- 'language-julia' ,
95
- 'language-lua' ,
96
- 'language-make' ,
97
- 'language-matlab' ,
98
- 'language-perl' ,
99
- 'language-python' ,
100
- 'language-r' ,
101
- 'language-rust' ,
102
- 'language-scala' ,
103
- 'language-shell' ,
104
- 'language-sql' ,
105
83
'output' ,
106
- 'python ' ,
107
- 'source '
84
+ 'source ' ,
85
+ 'warning '
108
86
}
109
87
110
88
# What fields are required in teaching episode metadata?
@@ -134,9 +112,15 @@ def main():
134
112
135
113
args = parse_args ()
136
114
args .reporter = Reporter ()
137
- check_config (args .reporter , args .source_dir )
115
+ life_cycle = check_config (args .reporter , args .source_dir )
116
+ # pre-alpha lessons should report without error
117
+ if life_cycle == "pre-alpha" :
118
+ args .permissive = True
138
119
check_source_rmd (args .reporter , args .source_dir , args .parser )
139
- args .references = read_references (args .reporter , args .reference_path )
120
+
121
+ args .references = {}
122
+ if not using_remote_theme (args .source_dir ):
123
+ args .references = read_references (args .reporter , args .reference_path )
140
124
141
125
docs = read_all_markdown (args .source_dir , args .parser )
142
126
check_fileset (args .source_dir , args .reporter , list (docs .keys ()))
@@ -180,7 +164,7 @@ def parse_args():
180
164
default = False ,
181
165
action = "store_true" ,
182
166
dest = 'permissive' ,
183
- help = 'Do not raise an error even if there are issues ' )
167
+ help = 'Do not raise an error even if issues are detected ' )
184
168
185
169
args , extras = parser .parse_known_args ()
186
170
require (args .parser is not None ,
@@ -190,6 +174,10 @@ def parse_args():
190
174
191
175
return args
192
176
177
+ def using_remote_theme (source_dir ):
178
+ config_file = os .path .join (source_dir , '_config.yml' )
179
+ config = load_yaml (config_file )
180
+ return 'remote_theme' in config
193
181
194
182
def check_config (reporter , source_dir ):
195
183
"""Check configuration file."""
@@ -211,6 +199,9 @@ def check_config(reporter, source_dir):
211
199
reporter .check (defaults in config .get ('defaults' , []),
212
200
'configuration' ,
213
201
'"root" not set to "." in configuration' )
202
+ if 'life_cycle' not in config :
203
+ config ['life_cycle' ] = None
204
+ return config ['life_cycle' ]
214
205
215
206
def check_source_rmd (reporter , source_dir , parser ):
216
207
"""Check that Rmd episode files include `source: Rmd`"""
@@ -237,7 +228,7 @@ def read_references(reporter, ref_path):
237
228
result = {}
238
229
urls_seen = set ()
239
230
240
- with open (ref_path , 'r' ) as reader :
231
+ with open (ref_path , 'r' , encoding = 'utf-8' ) as reader :
241
232
for (num , line ) in enumerate (reader , 1 ):
242
233
243
234
if P_INTERNAL_INCLUDE_LINK .search (line ): continue
@@ -299,17 +290,20 @@ def check_fileset(source_dir, reporter, filenames_present):
299
290
"""Are all required files present? Are extraneous files present?"""
300
291
301
292
# Check files with predictable names.
302
- required = [p . replace ( '%' , source_dir ) for p in REQUIRED_FILES ]
293
+ required = [os . path . join ( source_dir , p ) for p in REQUIRED_FILES ]
303
294
missing = set (required ) - set (filenames_present )
304
295
for m in missing :
305
296
reporter .add (None , 'Missing required file {0}' , m )
306
297
307
- # Check episode files' names .
298
+ # Check names of episode files.
308
299
seen = []
309
300
for filename in filenames_present :
310
301
if '_episodes' not in filename :
311
302
continue
312
- m = P_EPISODE_FILENAME .search (filename )
303
+
304
+ # split path to check episode name
305
+ base_name = os .path .basename (filename )
306
+ m = P_EPISODE_FILENAME .search (base_name )
313
307
if m and m .group (1 ):
314
308
seen .append (m .group (1 ))
315
309
else :
@@ -421,7 +415,8 @@ def check_codeblock_classes(self):
421
415
422
416
for node in self .find_all (self .doc , {'type' : 'codeblock' }):
423
417
cls = self .get_val (node , 'attr' , 'class' )
424
- self .reporter .check (cls in KNOWN_CODEBLOCKS ,
418
+ self .reporter .check (cls is not None and (cls in KNOWN_CODEBLOCKS
419
+ or cls .startswith ('language-' )),
425
420
(self .filename , self .get_loc (node )),
426
421
'Unknown or missing code block type {0}' ,
427
422
cls )
@@ -493,8 +488,6 @@ def get_loc(self, node):
493
488
494
489
class CheckNonJekyll (CheckBase ):
495
490
"""Check a file that isn't translated by Jekyll."""
496
- def __init__ (self , args , filename , metadata , metadata_len , text , lines , doc ):
497
- super ().__init__ (args , filename , metadata , metadata_len , text , lines , doc )
498
491
499
492
def check_metadata (self ):
500
493
self .reporter .check (self .metadata is None ,
@@ -523,7 +516,8 @@ def check(self):
523
516
"""Run extra tests."""
524
517
525
518
super ().check ()
526
- self .check_reference_inclusion ()
519
+ if not using_remote_theme ():
520
+ self .check_reference_inclusion ()
527
521
528
522
def check_metadata (self ):
529
523
super ().check_metadata ()
@@ -563,6 +557,11 @@ def check_reference_inclusion(self):
563
557
require (last_line ,
564
558
'No non-empty lines in {0}' .format (self .filename ))
565
559
560
+ include_filename = os .path .split (self .args .reference_path )[- 1 ]
561
+ if include_filename not in last_line :
562
+ self .reporter .add (self .filename ,
563
+ 'episode does not include "{0}"' ,
564
+ include_filename )
566
565
567
566
568
567
class CheckReference (CheckBase ):
@@ -586,7 +585,7 @@ def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
586
585
(re .compile (r'README\.md' ), CheckNonJekyll ),
587
586
(re .compile (r'index\.md' ), CheckIndex ),
588
587
(re .compile (r'reference\.md' ), CheckReference ),
589
- (re .compile (r '_episodes/. *\.md' ), CheckEpisode ),
588
+ (re .compile (os . path . join ( '_episodes' , ' *\.md') ), CheckEpisode ),
590
589
(re .compile (r'.*\.md' ), CheckGeneric ),
591
590
(re .compile (r'.*\.snip' ), CheckNonJekyll )
592
591
]
0 commit comments