2021-12-29 13:41:01 +01:00
#!/usr/bin/env python
import glob
import os . path
2022-04-25 22:40:45 +02:00
import re
warnings = 0
def report ( rule , location , description ) :
global warnings
warnings + = 1
print ( f ' { warnings : 3 } . { location } : { description } [ { rule } ] ' )
2021-12-29 13:41:01 +01:00
def check_structure ( ) :
2022-04-25 22:40:45 +02:00
expected_sections = [
2021-12-29 13:41:01 +01:00
' Template parameters ' ,
' Specializations ' ,
' Iterator invalidation ' ,
' Requirements ' ,
' Member types ' ,
' Member functions ' ,
' Member variables ' ,
' Static functions ' ,
' Non-member functions ' ,
' Literals ' ,
' Helper classes ' ,
' Parameters ' ,
' Return value ' ,
' Exception safety ' ,
' Exceptions ' ,
' Complexity ' ,
' Possible implementation ' ,
2022-04-25 22:40:45 +02:00
' Default definition ' ,
2021-12-29 13:41:01 +01:00
' Notes ' ,
' Examples ' ,
' See also ' ,
' Version history '
]
2022-04-25 22:40:45 +02:00
required_sections = [
2021-12-29 13:41:01 +01:00
' Examples ' ,
' Version history '
]
files = sorted ( glob . glob ( ' api/**/*.md ' , recursive = True ) )
for file in files :
with open ( file ) as file_content :
2022-04-25 22:40:45 +02:00
section_idx = - 1
existing_sections = [ ]
2021-12-29 13:41:01 +01:00
in_initial_code_example = False
previous_line = None
h1sections = 0
for lineno , line in enumerate ( file_content . readlines ( ) ) :
line = line . strip ( )
if line . startswith ( ' # ' ) :
h1sections + = 1
# there should only be one top-level title
if h1sections > 1 :
2022-04-25 22:40:45 +02:00
report ( ' structure/unexpected_section ' , f ' { file } : { lineno + 1 } ' , f ' unexpected top-level title " { line } " ' )
2021-12-29 13:41:01 +01:00
h1sections = 1
# Overview pages should have a better title
if line == ' # Overview ' :
2022-04-25 22:40:45 +02:00
report ( ' style/title ' , f ' { file } : { lineno + 1 } ' , ' overview pages should have a better title than " Overview " ' )
2021-12-29 13:41:01 +01:00
# lines longer than 160 characters are bad (unless they are tables)
if len ( line ) > 160 and ' | ' not in line :
2022-04-25 22:40:45 +02:00
report ( ' whitespace/line_length ' , f ' { file } : { lineno + 1 } ' , f ' line is too long ( { len ( line ) } vs. 160 chars) ' )
2021-12-29 13:41:01 +01:00
2022-04-25 22:40:45 +02:00
# check if sections are correct
2021-12-29 13:41:01 +01:00
if line . startswith ( ' ## ' ) :
2022-04-25 22:40:45 +02:00
current_section = line . strip ( ' ## ' )
existing_sections . append ( current_section )
if current_section in expected_sections :
idx = expected_sections . index ( current_section )
if idx < = section_idx :
report ( ' structure/section_order ' , f ' { file } : { lineno + 1 } ' , f ' section " { current_section } " is in an unexpected order (should be before " { expected_sections [ section_idx ] } " ) ' )
section_idx = idx
2021-12-29 13:41:01 +01:00
else :
2022-04-25 22:40:45 +02:00
report ( ' structure/unknown_section ' , f ' { file } : { lineno + 1 } ' , f ' section " { current_section } " is not part of the expected sections ' )
2021-12-29 13:41:01 +01:00
# code example
2022-04-25 22:40:45 +02:00
if line == ' ```cpp ' and section_idx == - 1 :
2021-12-29 13:41:01 +01:00
in_initial_code_example = True
if in_initial_code_example and line . startswith ( ' // ' ) :
if any ( map ( str . isdigit , line ) ) and ' ( ' not in line :
2022-04-25 22:40:45 +02:00
report ( ' style/numbering ' , f ' { file } : { lineno + 1 } ' , ' number should be in parentheses: {line} ' )
2021-12-29 13:41:01 +01:00
if line == ' ``` ' and in_initial_code_example :
in_initial_code_example = False
# consecutive blank lines are bad
if line == ' ' and previous_line == ' ' :
2022-04-25 22:40:45 +02:00
report ( ' whitespace/blank_lines ' , f ' { file } : { lineno } - { lineno + 1 } ' , ' consecutive blank lines ' )
# check that non-example admonitions have titles
untitled_admonition = re . match ( r ' ^( \ ? \ ? \ ?|!!!) ([^ ]+)$ ' , line )
if untitled_admonition and untitled_admonition . group ( 2 ) != ' example ' :
report ( ' style/admonition_title ' , f ' { file } : { lineno } ' , f ' " { untitled_admonition . group ( 2 ) } " admonitions should have a title ' )
2021-12-29 13:41:01 +01:00
previous_line = line
2022-04-25 22:40:45 +02:00
for required_section in required_sections :
if required_section not in existing_sections :
report ( ' structure/missing_section ' , f ' { file } : { lineno + 1 } ' , f ' required section " { required_section } " was not found ' )
2021-12-29 13:41:01 +01:00
def check_examples ( ) :
example_files = sorted ( glob . glob ( ' ../../examples/*.cpp ' ) )
markdown_files = sorted ( glob . glob ( ' **/*.md ' , recursive = True ) )
# check if every example file is used in at least one markdown file
for example_file in example_files :
example_file = os . path . join ( ' examples ' , os . path . basename ( example_file ) )
found = False
for markdown_file in markdown_files :
content = ' ' . join ( open ( markdown_file ) . readlines ( ) )
if example_file in content :
found = True
break
if not found :
2022-04-25 22:40:45 +02:00
report ( ' examples/missing ' , f ' { example_file } ' , ' example file is not used in any documentation file ' )
2021-12-29 13:41:01 +01:00
if __name__ == ' __main__ ' :
2022-04-25 22:40:45 +02:00
print ( 120 * ' - ' )
2021-12-29 13:41:01 +01:00
check_structure ( )
check_examples ( )
2022-04-25 22:40:45 +02:00
print ( 120 * ' - ' )