check_rosdistro.py 5.0 KB

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