You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
6.0 KiB

  1. #!/usr/bin/env python3
  2. import re
  3. import sys
  4. anchor = '###'
  5. min_entries_per_section = 3
  6. auth_keys = ['apiKey', 'OAuth', 'X-Mashape-Key', 'No', 'User-Agent']
  7. punctuation = ['.', '?', '!']
  8. https_keys = ['Yes', 'No']
  9. cors_keys = ['Yes', 'No', 'Unknown']
  10. index_title = 0
  11. index_desc = 1
  12. index_auth = 2
  13. index_https = 3
  14. index_cors = 4
  15. index_link = 5
  16. num_segments = 5
  17. errors = []
  18. title_links = []
  19. anchor_re = re.compile(anchor + '\s(.+)')
  20. section_title_re = re.compile('\*\s\[(.*)\]')
  21. link_re = re.compile('\[(.+)\]\((http.*)\)')
  22. def add_error(line_num, message):
  23. """adds an error to the dynamic error list"""
  24. err = '(L{:03d}) {}'.format(line_num + 1, message)
  25. errors.append(err)
  26. def check_alphabetical(lines):
  27. """
  28. checks if all entries per section are in alphabetical order based in entry title
  29. """
  30. sections = {}
  31. section_line_num = {}
  32. for line_num, line in enumerate(lines):
  33. if line.startswith(anchor):
  34. category = line.split(anchor)[1].strip()
  35. sections[category] = []
  36. section_line_num[category] = line_num
  37. continue
  38. if not line.startswith('|') or line.startswith('|---'):
  39. continue
  40. raw_title = [x.strip() for x in line.split('|')[1:-1]][0]
  41. title_re_match = link_re.match(raw_title)
  42. if title_re_match:
  43. sections[category].append(title_re_match.group(1).upper())
  44. for category, entries in sections.items():
  45. if sorted(entries) != entries:
  46. add_error(section_line_num[category], "{} section is not in alphabetical order".format(category))
  47. def check_entry(line_num, segments):
  48. # START Title
  49. raw_title = segments[index_title]
  50. title_re_match = link_re.match(raw_title)
  51. # url should be wrapped in '[TITLE](LINK)' Markdown syntax
  52. if not title_re_match:
  53. add_error(line_num, 'Title syntax should be "[TITLE](LINK)"')
  54. else:
  55. # do not allow "... API" in the entry title
  56. title = title_re_match.group(1)
  57. if title.upper().endswith(' API'):
  58. add_error(line_num, 'Title should not end with "... API". Every entry is an API here!')
  59. # END Title
  60. # START Description
  61. # first character should be capitalized
  62. char = segments[index_desc][0]
  63. if char.upper() != char:
  64. add_error(line_num, "first character of description is not capitalized")
  65. # last character should not punctuation
  66. char = segments[index_desc][-1]
  67. if char in punctuation:
  68. add_error(line_num, "description should not end with {}".format(char))
  69. desc_length = len(segments[index_desc])
  70. if desc_length > 100:
  71. add_error(line_num, "description should not exceed 100 characters (currently {})".format(desc_length))
  72. # END Description
  73. # START Auth
  74. # values should conform to valid options only
  75. auth = segments[index_auth]
  76. if auth != 'No' and (not auth.startswith('`') or not auth.endswith('`')):
  77. add_error(line_num, "auth value is not enclosed with `backticks`")
  78. if auth.replace('`', '') not in auth_keys:
  79. add_error(line_num, "{} is not a valid Auth option".format(auth))
  80. # END Auth
  81. # START HTTPS
  82. # values should conform to valid options only
  83. https = segments[index_https]
  84. if https not in https_keys:
  85. add_error(line_num, "{} is not a valid HTTPS option".format(https))
  86. # END HTTPS
  87. # START CORS
  88. # values should conform to valid options only
  89. cors = segments[index_cors]
  90. if cors not in cors_keys:
  91. add_error(line_num, "{} is not a valid CORS option".format(cors))
  92. # END CORS
  93. def check_format(filename):
  94. """
  95. validates that each line is formatted correctly,
  96. appending to error list as needed
  97. """
  98. with open(filename) as fp:
  99. lines = list(line.rstrip() for line in fp)
  100. check_alphabetical(lines)
  101. # START Check Entries
  102. num_in_category = min_entries_per_section + 1
  103. category = ""
  104. category_line = 0
  105. for line_num, line in enumerate(lines):
  106. if section_title_re.match(line):
  107. title_links.append(section_title_re.match(line).group(1))
  108. # check each section for the minimum number of entries
  109. if line.startswith(anchor):
  110. match = anchor_re.match(line)
  111. if match:
  112. if match.group(1) not in title_links:
  113. add_error(line_num, "section header ({}) not added as a title link".format(match.group(1)))
  114. else:
  115. add_error(line_num, "section header is not formatted correctly")
  116. if num_in_category < min_entries_per_section:
  117. add_error(category_line, "{} section does not have the minimum {} entries (only has {})".format(
  118. category, min_entries_per_section, num_in_category))
  119. category = line.split(' ')[1]
  120. category_line = line_num
  121. num_in_category = 0
  122. continue
  123. # skips lines that we do not care about
  124. if not line.startswith('|') or line.startswith('|---'):
  125. continue
  126. num_in_category += 1
  127. segments = line.split('|')[1:-1]
  128. if len(segments) < num_segments:
  129. add_error(line_num, "entry does not have all the required sections (have {}, need {})".format(
  130. len(segments), num_segments))
  131. continue
  132. # START Global
  133. for segment in segments:
  134. # every line segment should start and end with exactly 1 space
  135. if len(segment) - len(segment.lstrip()) != 1 or len(segment) - len(segment.rstrip()) != 1:
  136. add_error(line_num, "each segment must start and end with exactly 1 space")
  137. # END Global
  138. segments = [seg.strip() for seg in segments]
  139. check_entry(line_num, segments)
  140. # END Check Entries
  141. def main():
  142. if len(sys.argv) < 2:
  143. print("No file passed (file should contain Markdown table syntax)")
  144. sys.exit(1)
  145. check_format(sys.argv[1])
  146. if len(errors) > 0:
  147. for err in errors:
  148. print(err)
  149. sys.exit(1)
  150. if __name__ == "__main__":
  151. main()