packages/python-packages/doc-warden/warden/cmd_entry.py (188 lines of code) (raw):
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from __future__ import print_function
from .enforce_target_file_presence import find_missing_target_files
from .enforce_readme_content import verify_readme_content
from .enforce_changelog_content import verify_changelog_content
from .index_packages import index_packages, render
from .WardenConfiguration import WardenConfiguration
from .PackageInfo import PackageInfo
import os
import logging
# CONFIGURATION. ENTRY POINT. EXECUTION.
def console_entry_point():
cfg = WardenConfiguration()
if cfg.verbose_output:
cfg.dump()
command_selector = {
'scan': all_operations,
'content': verify_content,
'presence': verify_presence,
'index': index
}
if cfg.command in command_selector:
command_selector.get(cfg.command)(cfg)
else:
print('Unrecognized command invocation {}.'.format(cfg.command))
exit(1)
# index the packages present in the repository
def index(config):
packages = index_packages(config)
render(config, packages)
if config.verbose_output:
print('Warden located the following packages: ')
for pkg in packages:
print(pkg.package_id)
# verify the content of readmes or changelogs
def verify_content(config):
packages = index_packages(config)
if config.target == 'all':
print('Only use the `all` switch when runing the `scan` command')
exit(1)
if config.target == 'readme':
content_results, ignored_content_results = verify_readme_content(config)
output_readme_content_results(content_results, config)
exit_on_readme_content_issues(content_results, config)
if config.target == 'changelog':
missing_changelog, empty_release_notes = verify_changelog_content(config, packages)
output_changelog_content_results(missing_changelog, empty_release_notes)
exit_on_changelog_content_issues(missing_changelog, empty_release_notes, config)
# verify the presence of the target_files (Readme or Changelog)
def verify_presence(config):
if config.target == 'all':
print('Only use the `all` switch when runing the `scan` command')
exit(1)
presence_results, ignored_presence_results = find_missing_target_files(config)
output_presence_results(presence_results, config)
exit_on_presence_issues(presence_results, config)
# Verify Case of readme files Present
def verify_file_case_readme(pkg_list, config):
readmes_with_wrong_case = []
if pkg_list is None:
return readmes_with_wrong_case
for pkg in pkg_list:
if pkg.relative_readme_location:
if not os.path.splitext(os.path.basename(pkg.relative_readme_location))[0].isupper():
readmes_with_wrong_case.append(os.path.normpath(os.path.join(config.target_directory, pkg.relative_readme_location)))
return readmes_with_wrong_case
# Verify Case of changelog files Present
def verify_file_case_changelog(pkg_list, config):
changelogs_with_wrong_case = []
for pkg in pkg_list:
if pkg.relative_changelog_location:
if not os.path.splitext(os.path.basename(pkg.relative_changelog_location))[0].isupper():
changelogs_with_wrong_case.append(os.path.normpath(os.path.join(config.target_directory, pkg.relative_changelog_location)))
return changelogs_with_wrong_case
# Exit if there are any presence issues
def exit_on_presence_issues(presence_results, config):
if len(presence_results) > 0:
conclusion_message()
exit(1)
# Exit if there are readme content issues
def exit_on_readme_content_issues(content_results, config):
if len(content_results) > 0:
conclusion_message()
exit(1)
# Exit if there are changelog content issues
def exit_on_changelog_content_issues(missing_changelog, empty_release_notes, config):
if len(missing_changelog) > 0:
conclusion_message()
exit(1)
if config.pipeline_stage == 'release' and len(empty_release_notes) > 0:
conclusion_message()
exit(1)
# print content results for readme
def output_readme_content_results(readmes_with_issues, config):
length = len(readmes_with_issues)
if length:
print('{0} {1} at least one missing required section.'.format(length, pluralize('readme has', 'readmes have', length)))
for readme_tuple in readmes_with_issues:
header = '{0} is missing {1} with {2}:'.format(
config.get_output_path(readme_tuple[0]),
pluralize('a header', 'headers', len(readme_tuple[1])),
pluralize('the pattern', 'patterns', len(readme_tuple[1]))
)
print(header)
for missing_pattern in readme_tuple[1]:
print(' * {0}'.format(format_header_path(missing_pattern)))
print()
def format_header_path(pattern):
return " -> ".join(pattern)
# print content results for changelog
def output_changelog_content_results(missing_changelog, empty_release_notes):
if len(missing_changelog):
print('{0} {1} missing entry{2} for the latest package version'.format(len(missing_changelog), pluralize('changelog has', 'changelogs have', len(missing_changelog)), pluralize('', 's', len(missing_changelog))))
print()
for changelog_tuple in missing_changelog:
print('MISSING CHANGELOG ENTRY: Latest Version {0} is missing in {1}. Add changelog for latest version'.format(changelog_tuple[1]['curr_pkg_version'], changelog_tuple[0]))
print()
if len(empty_release_notes):
print('{0} {1} empty release note for the latest package version'.format(len(empty_release_notes), pluralize('changelog has', 'changelogs have', len(empty_release_notes))))
print()
for changelog_tuple in empty_release_notes:
print('EMPTY CHANGELOG ENTRY: Latest Version {0} has no release notes in {1}. Consider adding release notes'.format(changelog_tuple[1]['curr_pkg_version'], changelog_tuple[0]))
print()
# print presence results
def output_presence_results(missing_target_file_paths, config):
if len(missing_target_file_paths):
print('{0} missing {1}{2} detected at:'.format(len(missing_target_file_paths), config.target_files[0], 's' if len(missing_target_file_paths) > 1 else ''))
for path in missing_target_file_paths:
print(config.get_output_path(path))
print()
# print case issues
def output_case_results(readmes_with_wrong_case, changelogs_with_wrong_case):
if readmes_with_wrong_case:
print('{0} Readme{1} are wrongly named:'.format(len(readmes_with_wrong_case), 's' if len(readmes_with_wrong_case) > 1 else ''))
for path in readmes_with_wrong_case:
print(path)
print()
if changelogs_with_wrong_case:
print('{0} Changelog{1} are wrongly named:'.format(len(changelogs_with_wrong_case), 's' if len(changelogs_with_wrong_case) > 1 else ''))
for path in changelogs_with_wrong_case:
print(path)
print()
# Run both presence and content verification on changelogs
def all_operations_readme(config, packages):
config.target_files = ['readme.rst', 'readme.md'] if config.scan_language == 'python' else ['readme.md']
if config.verbose_output:
print('Starting Readme Presence Examination')
readme_presence_results, ignored_readme_presence_results = find_missing_target_files(config)
if config.verbose_output:
print('Done with Readme Presence Examination')
print('Starting Readme Content Examination')
readme_content_results, ignored_readme_content_results = verify_readme_content(config)
if config.verbose_output:
print('Done with Readme Content Examination')
readmes_with_wrong_case = verify_file_case_readme(packages, config)
output_presence_results(readme_presence_results, config)
output_readme_content_results(readme_content_results, config)
output_case_results(readmes_with_wrong_case, None)
if len(readme_content_results) > 0 or len(readme_presence_results) > 0 or len(readmes_with_wrong_case) > 0:
return 1
else:
return 0
# Run both presence and content verification on readmes
def all_operations_changelog(config, packages):
config.target_files = ['history.rst', 'history.md'] if config.scan_language == 'python' else ['changelog.md']
if config.verbose_output:
print('Starting Changelog Presence Examination')
changelog_presence_results, ignored_changelog_presence_results = find_missing_target_files(config)
if config.verbose_output:
print('Done with Changelog Presence Examination')
print('Starting Changelog Content Examination')
missing_changelog, empty_release_notes = verify_changelog_content(config, packages)
if config.verbose_output:
print('Done with Changelog Content Examination')
changelogs_with_wrong_case = verify_file_case_changelog(packages, config)
output_presence_results(changelog_presence_results, config)
output_changelog_content_results(missing_changelog, empty_release_notes)
output_case_results(None, changelogs_with_wrong_case)
if len(missing_changelog) > 0 or len(changelog_presence_results) > 0 or len(changelogs_with_wrong_case):
return 1
elif len(empty_release_notes) > 0 and config.pipeline_stage == 'release':
return 1
else:
return 0
# execute both presence and content verification
def all_operations(config):
packages = index_packages(config)
result = 0
if config.target == 'default':
result = all_operations_readme(config, None)
elif config.target == 'readme':
result = all_operations_readme(config, packages)
elif config.target == 'changelog':
result = all_operations_changelog(config, packages)
elif config.target == 'all':
readme_result = all_operations_readme(config, packages)
changelog_result = all_operations_changelog(config, packages)
result = readme_result or changelog_result
if result == 1:
conclusion_message()
exit(1)
# return the plural form of the string given a count > 1
def pluralize(string, plural_string, count):
return plural_string if count > 1 else string
# final output. Could get longer or pull from a template in the future.
def conclusion_message():
print('For a rundown on what you need to do to resolve this breaking issue ASAP, check out aka.ms/azure-sdk-analyze-failed')