check_rosdep.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. #!/usr/bin/env python
  2. from __future__ import print_function
  3. import re
  4. import yaml
  5. import argparse
  6. import sys
  7. indent_atom = ' '
  8. # pretty - A miniature library that provides a Python print and stdout
  9. # wrapper that makes colored terminal text easier to use (eg. without
  10. # having to mess around with ANSI escape sequences). This code is public
  11. # domain - there is no license except that you must leave this header.
  12. #
  13. # Copyright (C) 2008 Brian Nez <thedude at bri1 dot com>
  14. #
  15. # With modifications
  16. # (C) 2013 Paul M <pmathieu@willowgarage.com>
  17. codeCodes = {
  18. 'black': '0;30', 'bright gray': '0;37',
  19. 'blue': '0;34', 'white': '1;37',
  20. 'green': '0;32', 'bright blue': '1;34',
  21. 'cyan': '0;36', 'bright green': '1;32',
  22. 'red': '0;31', 'bright cyan': '1;36',
  23. 'purple': '0;35', 'bright red': '1;31',
  24. 'yellow': '0;33', 'bright purple': '1;35',
  25. 'dark gray': '1;30', 'bright yellow': '1;33',
  26. 'normal': '0'
  27. }
  28. def printc(text, color):
  29. """Print in color."""
  30. if sys.stdout.isatty():
  31. print("\033["+codeCodes[color]+"m"+text+"\033[0m")
  32. else:
  33. print(text)
  34. def print_test(msg):
  35. printc(msg, 'yellow')
  36. def print_err(msg):
  37. printc(' ERR: ' + msg, 'red')
  38. def no_trailing_spaces(buf):
  39. clean = True
  40. for i, l in enumerate(buf.split('\n')):
  41. if re.search(r' $', l) is not None:
  42. print_err("trailing space line %u" % (i+1))
  43. clean = False
  44. return clean
  45. def no_blank_lines(buf):
  46. clean = True
  47. for i, l in enumerate(buf.split('\n')[:-1]):
  48. if re.match(r'^\s*$', l):
  49. print_err("blank line %u" % (i+1))
  50. clean = False
  51. return clean
  52. def generic_parser(buf, cb):
  53. ilen = len(indent_atom)
  54. stringblock = False
  55. strlvl = 0
  56. lvl = 0
  57. clean = True
  58. for i, l in enumerate(buf.split('\n')):
  59. if l == '':
  60. continue
  61. if re.search(r'^\s*#', l) is not None:
  62. continue
  63. try:
  64. s = re.search(r'(?!' + indent_atom + ')[^\s]', l).start()
  65. except:
  66. print_err("line %u: %s" % (i, l))
  67. raise
  68. if stringblock:
  69. if int(s / ilen) > strlvl:
  70. continue
  71. stringblock = False
  72. lvl = int(s / ilen)
  73. opts = {'lvl': lvl, 's': s}
  74. if not cb(i, l, opts):
  75. clean = False
  76. if re.search(r'\|$|\?$|^\s*\?', l) is not None:
  77. stringblock = True
  78. strlvl = lvl
  79. return clean
  80. def correct_indent(buf):
  81. ilen = len(indent_atom)
  82. def fun(i, l, o):
  83. s = o['s']
  84. olvl = fun.lvl
  85. lvl = o['lvl']
  86. fun.lvl = lvl
  87. if s % ilen > 0:
  88. print_err("invalid indentation level line %u: %u" % (i+1, s))
  89. return False
  90. if lvl > olvl + 1:
  91. print_err("too much indentation line %u" % (i+1))
  92. return False
  93. return True
  94. fun.lvl = 0
  95. return generic_parser(buf, fun)
  96. def check_brackets(buf):
  97. excepts = ['uri', 'md5sum']
  98. def fun(i, l, o):
  99. m = re.match(r'^(?:' + indent_atom + r')*([^:]*):\s*(\w.*)$', l)
  100. if m is not None and m.groups()[0] not in excepts:
  101. if m.groups()[1] == 'null':
  102. return True
  103. print_err("list not in square brackets line %u" % (i+1))
  104. return False
  105. return True
  106. return generic_parser(buf, fun)
  107. def check_order(buf):
  108. def fun(i, l, o):
  109. lvl = o['lvl']
  110. st = fun.namestack
  111. while len(st) > lvl + 1:
  112. st.pop()
  113. if len(st) < lvl + 1:
  114. st.append('')
  115. if re.search(r'^\s*\?', l) is not None:
  116. return True
  117. m = re.match(r'^(?:' + indent_atom + r')*([^:]*):.*$', l)
  118. prev = st[lvl]
  119. try:
  120. # parse as yaml to parse `"foo bar"` as string 'foo bar' not string '"foo bar"'
  121. item = yaml.safe_load(m.groups()[0])
  122. except:
  123. print('woops line %d' % i)
  124. raise
  125. st[lvl] = item
  126. if item < prev:
  127. print_err("list out of alphabetical order line %u. '%s' should come before '%s'" % ((i+1), item, prev))
  128. return False
  129. return True
  130. fun.namestack = ['']
  131. return generic_parser(buf, fun)
  132. def main(fname):
  133. with open(fname) as f:
  134. buf = f.read()
  135. def my_assert(val):
  136. if not val:
  137. my_assert.clean = False
  138. my_assert.clean = True
  139. # here be tests.
  140. ydict = None
  141. try:
  142. ydict = yaml.safe_load(buf)
  143. except Exception:
  144. pass
  145. if ydict != {}:
  146. print_test("checking for trailing spaces...")
  147. my_assert(no_trailing_spaces(buf))
  148. print_test("checking for blank lines...")
  149. my_assert(no_blank_lines(buf))
  150. print_test("checking for incorrect indentation...")
  151. my_assert(correct_indent(buf))
  152. print_test("checking for non-bracket package lists...")
  153. my_assert(check_brackets(buf))
  154. print_test("checking for item order...")
  155. my_assert(check_order(buf))
  156. print_test("building yaml dict...")
  157. else:
  158. print_test("skipping file with empty dict contents...")
  159. try:
  160. ydict = yaml.safe_load(buf)
  161. # ensure that values don't contain whitespaces
  162. whitespace_whitelist = ["el capitan", "mountain lion"]
  163. def walk(node):
  164. if isinstance(node, dict):
  165. for key, value in node.items():
  166. walk(key)
  167. walk(value)
  168. if isinstance(node, list):
  169. for value in node:
  170. walk(value)
  171. if isinstance(node, str) and re.search(r'\s', node) and node not in whitespace_whitelist:
  172. print_err("value '%s' must not contain whitespaces" % node)
  173. my_assert(False)
  174. walk(ydict)
  175. except Exception as e:
  176. print_err("could not build the dict: %s" % (str(e)))
  177. my_assert(False)
  178. if not my_assert.clean:
  179. printc("there were errors, please correct the file", 'bright red')
  180. return False
  181. return True
  182. if __name__ == '__main__':
  183. parser = argparse.ArgumentParser(description='Checks whether yaml syntax corresponds to ROS rules')
  184. parser.add_argument('infile', help='input rosdep YAML file')
  185. args = parser.parse_args()
  186. if not main(args.infile):
  187. sys.exit(1)