1
0
mirror of https://github.com/nlohmann/json.git synced 2025-07-07 23:41:10 +03:00

Clean up and document project files (#4560)

This commit is contained in:
Niels Lohmann
2025-01-17 06:53:35 +01:00
committed by GitHub
parent ad2ee18539
commit 26cfec34be
519 changed files with 3301 additions and 1332 deletions

View File

@ -5,47 +5,49 @@ import os.path
import re
import sys
import yaml
warnings = 0
def report(rule, location, description):
def report(rule, location, description) -> None:
global warnings
warnings += 1
print(f'{warnings:3}. {location}: {description} [{rule}]')
def check_structure():
def check_structure() -> None:
expected_sections = [
'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',
'Default definition',
'Notes',
'Examples',
'See also',
'Version history'
"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",
"Default definition",
"Notes",
"Examples",
"See also",
"Version history",
]
required_sections = [
'Examples',
'Version history'
"Examples",
"Version history",
]
files = sorted(glob.glob('api/**/*.md', recursive=True))
files = sorted(glob.glob("api/**/*.md", recursive=True))
for file in files:
with open(file) as file_content:
section_idx = -1 # the index of the current h2 section
@ -60,30 +62,30 @@ def check_structure():
for lineno, original_line in enumerate(file_content.readlines()):
line = original_line.strip()
if line.startswith('# '):
if line.startswith("# "):
h1sections += 1
# there should only be one top-level title
if h1sections > 1:
report('structure/unexpected_section', f'{file}:{lineno+1}', f'unexpected top-level title "{line}"')
report("structure/unexpected_section", f"{file}:{lineno+1}", f'unexpected top-level title "{line}"')
h1sections = 1
# Overview pages should have a better title
if line == '# Overview':
report('style/title', f'{file}:{lineno+1}', 'overview pages should have a better title than "Overview"')
if line == "# Overview":
report("style/title", f"{file}:{lineno+1}", 'overview pages should have a better title than "Overview"')
# lines longer than 160 characters are bad (unless they are tables)
if len(line) > 160 and '|' not in line:
report('whitespace/line_length', f'{file}:{lineno+1} ({current_section})', f'line is too long ({len(line)} vs. 160 chars)')
if len(line) > 160 and "|" not in line:
report("whitespace/line_length", f"{file}:{lineno+1} ({current_section})", f"line is too long ({len(line)} vs. 160 chars)")
# sections in `<!-- NOLINT -->` comments are treated as present
if line.startswith('<!-- NOLINT'):
current_section = line.strip('<!-- NOLINT')
current_section = current_section.strip(' -->')
if line.startswith("<!-- NOLINT"):
current_section = line.strip("<!-- NOLINT")
current_section = current_section.strip(" -->")
existing_sections.append(current_section)
# check if sections are correct
if line.startswith('## '):
if line.startswith("## "):
# 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:
@ -91,21 +93,20 @@ def check_structure():
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])}')
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])}')
report("style/numbering", f"{file}:{lineno} ({current_section})", f'unexpected overloads: {", ".join([f"({x})" for x in unexpected])}')
current_section = line.strip('## ')
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]}")')
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
else:
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')
elif "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:
@ -116,64 +117,106 @@ def check_structure():
documented_overloads[current_section].append(number)
# code example
if line == '```cpp' and section_idx == -1:
if line == "```cpp" and section_idx == -1:
in_initial_code_example = True
if in_initial_code_example and line.startswith('//') and line not in ['// since C++20', '// until C++20']:
if in_initial_code_example and line.startswith("//") and line not in ["// since C++20", "// until C++20"]:
# check numbering of overloads
if any(map(str.isdigit, line)):
number = int(re.findall(r'\d+', line)[0])
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 })')
report("style/numbering", f"{file}:{lineno+1}", f"expected number ({number}) to be ({last_overload +1 })")
last_overload = number
if any(map(str.isdigit, line)) and '(' not in line:
report('style/numbering', f'{file}:{lineno+1}', f'number should be in parentheses: {line}')
if any(map(str.isdigit, line)) and "(" not in line:
report("style/numbering", f"{file}:{lineno+1}", f"number should be in parentheses: {line}")
if line == '```' and in_initial_code_example:
if line == "```" and in_initial_code_example:
in_initial_code_example = False
# consecutive blank lines are bad
if line == '' and previous_line == '':
report('whitespace/blank_lines', f'{file}:{lineno}-{lineno+1} ({current_section})', 'consecutive blank lines')
if line == "" and previous_line == "":
report("whitespace/blank_lines", f"{file}:{lineno}-{lineno+1} ({current_section})", "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} ({current_section})', f'"{untitled_admonition.group(2)}" admonitions should have a title')
untitled_admonition = re.match(r"^(\?\?\?|!!!) ([^ ]+)$", line)
if untitled_admonition and untitled_admonition.group(2) != "example":
report("style/admonition_title", f"{file}:{lineno} ({current_section})", f'"{untitled_admonition.group(2)}" admonitions should have a title')
previous_line = line
if 'index.md' not in file: # index.md files may have a different structure
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')
report("structure/missing_section", f"{file}:{lineno+1}", f'required section "{required_section}" was not found')
def check_examples():
example_files = sorted(glob.glob('../../examples/*.cpp'))
markdown_files = sorted(glob.glob('**/*.md', recursive=True))
def check_examples() -> None:
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))
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())
content = " ".join(open(markdown_file).readlines())
if example_file in content:
found = True
break
if not found:
report('examples/missing', f'{example_file}', 'example file is not used in any documentation file')
report("examples/missing", f"{example_file}", "example file is not used in any documentation file")
if __name__ == '__main__':
print(120 * '-')
def check_links() -> None:
"""Check that every entry in the navigation (nav in mkdocs.yml) links to at most one file. If a file is linked more
than once, then the first entry is repeated. See https://github.com/nlohmann/json/issues/4564 for the issue in
this project and https://github.com/mkdocs/mkdocs/issues/3428 for the root cause.
The issue can be fixed by merging the keys, so
- 'NLOHMANN_JSON_VERSION_MAJOR': api/macros/nlohmann_json_version_major.md
- 'NLOHMANN_JSON_VERSION_MINOR': api/macros/nlohmann_json_version_major.md
would be replaced with
- 'NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR': api/macros/nlohmann_json_version_major.md
"""
file_with_path = {}
def collect_links(node, path="") -> None:
if isinstance(node, list):
for x in node:
collect_links(x, path)
elif isinstance(node, dict):
for p, x in node.items():
collect_links(x, path + "/" + p)
else:
if node not in file_with_path:
file_with_path[node] = []
file_with_path[node].append(path)
with open("../mkdocs.yml") as mkdocs_file:
# see https://github.com/yaml/pyyaml/issues/86#issuecomment-1042485535
yaml.add_multi_constructor("tag:yaml.org,2002:python/name", lambda loader, suffix, node: None, Loader=yaml.SafeLoader)
yaml.add_multi_constructor("!ENV", lambda loader, suffix, node: None, Loader=yaml.SafeLoader)
y = yaml.safe_load(mkdocs_file)
collect_links(y["nav"])
for duplicate_file in [x for x in file_with_path if len(file_with_path[x]) > 1]:
file_list = [f'"{x}"' for x in file_with_path[duplicate_file]]
file_list_str = ", ".join(file_list)
report("nav/duplicate_files", "mkdocs.yml", f'file "{duplicate_file}" is linked with multiple keys in "nav": {file_list_str}; only one is rendered properly, see #4564')
if __name__ == "__main__":
print(120 * "-")
check_structure()
check_examples()
print(120 * '-')
check_links()
print(120 * "-")
if warnings > 0:
sys.exit(1)