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
2022-08-05 19:51:39 +02:00
import sys
2022-04-25 22:40:45 +02:00
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-05-17 13:08:56 +02:00
section_idx = - 1 # the index of the current h2 section
existing_sections = [ ] # the list of h2 sections in the file
in_initial_code_example = False # whether we are inside the first code example block
previous_line = None # the previous read line
h1sections = 0 # the number of h1 sections in the file
last_overload = 0 # the last seen overload number in the code example
documented_overloads = { } # the overloads that have been documented in the current block
current_section = None # the name of the current section
for lineno , original_line in enumerate ( file_content . readlines ( ) ) :
line = original_line . strip ( )
2021-12-29 13:41:01 +01:00
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-05-17 13:08:56 +02:00
report ( ' whitespace/line_length ' , f ' { file } : { lineno + 1 } ( { current_section } ) ' , f ' line is too long ( { len ( line ) } vs. 160 chars) ' )
2021-12-29 13:41:01 +01:00
2022-08-05 19:51:39 +02:00
# sections in `<!-- NOLINT -->` comments are treated as present
if line . startswith ( ' <!-- NOLINT ' ) :
current_section = line . strip ( ' <!-- NOLINT ' )
current_section = current_section . strip ( ' --> ' )
existing_sections . append ( current_section )
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-05-17 13:08:56 +02:00
# before starting a new section, check if the previous one documented all overloads
if current_section in documented_overloads and last_overload != 0 :
if len ( documented_overloads [ current_section ] ) > 0 and len ( documented_overloads [ current_section ] ) != last_overload :
expected = list ( range ( 1 , last_overload + 1 ) )
undocumented = [ x for x in expected if x not in documented_overloads [ current_section ] ]
unexpected = [ x for x in documented_overloads [ current_section ] if x not in expected ]
if len ( undocumented ) :
report ( ' style/numbering ' , f ' { file } : { lineno } ( { current_section } ) ' , f ' undocumented overloads: { " , " . join ( [ f " ( { x } ) " for x in undocumented ] ) } ' )
if len ( unexpected ) :
report ( ' style/numbering ' , f ' { file } : { lineno } ( { current_section } ) ' , f ' unexpected overloads: { " , " . join ( [ f " ( { x } ) " for x in unexpected ] ) } ' )
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-05-17 13:08:56 +02:00
if ' index.md ' not in file : # index.md files may have a different structure
report ( ' structure/unknown_section ' , f ' { file } : { lineno + 1 } ' , f ' section " { current_section } " is not part of the expected sections ' )
# collect the numbered items of the current section to later check if they match the number of overloads
if last_overload != 0 and not in_initial_code_example :
if len ( original_line ) and original_line [ 0 ] . isdigit ( ) :
number = int ( re . findall ( r " ^( \ d+). " , original_line ) [ 0 ] )
if current_section not in documented_overloads :
documented_overloads [ current_section ] = [ ]
documented_overloads [ current_section ] . append ( number )
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
2022-07-20 18:59:04 +02:00
if in_initial_code_example and line . startswith ( ' // ' ) and line not in [ ' // since C++20 ' , ' // until C++20 ' ] :
2022-05-17 13:08:56 +02:00
# check numbering of overloads
if any ( map ( str . isdigit , line ) ) :
number = int ( re . findall ( r ' \ d+ ' , line ) [ 0 ] )
if number != last_overload + 1 :
report ( ' style/numbering ' , f ' { file } : { lineno + 1 } ' , f ' expected number ( { number } ) to be ( { last_overload + 1 } ) ' )
last_overload = number
2021-12-29 13:41:01 +01:00
if any ( map ( str . isdigit , line ) ) and ' ( ' not in line :
2022-07-20 18:59:04 +02:00
report ( ' style/numbering ' , f ' { file } : { lineno + 1 } ' , f ' 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-05-17 13:08:56 +02:00
report ( ' whitespace/blank_lines ' , f ' { file } : { lineno } - { lineno + 1 } ( { current_section } ) ' , ' consecutive blank lines ' )
2022-04-25 22:40:45 +02:00
# check that non-example admonitions have titles
untitled_admonition = re . match ( r ' ^( \ ? \ ? \ ?|!!!) ([^ ]+)$ ' , line )
if untitled_admonition and untitled_admonition . group ( 2 ) != ' example ' :
2022-05-17 13:08:56 +02:00
report ( ' style/admonition_title ' , f ' { file } : { lineno } ( { current_section } ) ' , f ' " { untitled_admonition . group ( 2 ) } " admonitions should have a title ' )
2021-12-29 13:41:01 +01:00
previous_line = line
2022-05-17 13:08:56 +02:00
if ' index.md ' not in file : # index.md files may have a different structure
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 * ' - ' )
2022-08-05 19:51:39 +02:00
if warnings > 0 :
sys . exit ( 1 )