PageRenderTime 52ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/etc/extract-tests.py

https://github.com/veddan/rust
Python | 217 lines | 147 code | 34 blank | 36 comment | 10 complexity | 6ac697f8919ca217dead51ced2491ee9 MD5 | raw file
Possible License(s): JSON, Apache-2.0, MIT, AGPL-1.0
  1. # Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
  2. # file at the top-level directory of this distribution and at
  3. # http://rust-lang.org/COPYRIGHT.
  4. #
  5. # Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
  6. # http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
  7. # <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
  8. # option. This file may not be copied, modified, or distributed
  9. # except according to those terms.
  10. """
  11. Script for extracting compilable fragments from markdown documentation. See
  12. prep.js for a description of the format recognized by this tool. Expects
  13. a directory fragments/ to exist under the current directory, and writes the
  14. fragments in there as individual .rs files.
  15. """
  16. from __future__ import print_function
  17. from codecs import open
  18. from collections import deque
  19. from itertools import imap
  20. import os
  21. import re
  22. import sys
  23. # regexes
  24. CHAPTER_NAME_REGEX = re.compile(r'# (.*)')
  25. CODE_BLOCK_DELIM_REGEX = re.compile(r'~~~')
  26. COMMENT_REGEX = re.compile(r'^# ')
  27. COMPILER_DIRECTIVE_REGEX = re.compile(r'\#\[(.*)\];')
  28. ELLIPSES_REGEX = re.compile(r'\.\.\.')
  29. EXTERN_MOD_REGEX = re.compile(r'\bextern mod extra\b')
  30. MAIN_FUNCTION_REGEX = re.compile(r'\bfn main\b')
  31. TAGS_REGEX = re.compile(r'\.([\w-]*)')
  32. # tags to ignore
  33. IGNORE_TAGS = \
  34. frozenset(["abnf", "ebnf", "field", "keyword", "notrust", "precedence"])
  35. # header for code snippet files
  36. OUTPUT_BLOCK_HEADER = '\n'.join((
  37. "#[ deny(warnings) ];",
  38. "#[ allow(unused_variable) ];",
  39. "#[ allow(dead_assignment) ];",
  40. "#[ allow(unused_mut) ];",
  41. "#[ allow(attribute_usage) ];",
  42. "#[ allow(dead_code) ];",
  43. "#[ feature(macro_rules, globs, struct_variant, managed_boxes) ];\n",))
  44. def add_extern_mod(block):
  45. if not has_extern_mod(block):
  46. # add `extern mod extra;` after compiler directives
  47. directives = []
  48. while len(block) and is_compiler_directive(block[0]):
  49. directives.append(block.popleft())
  50. block.appendleft("\nextern mod extra;\n\n")
  51. block.extendleft(reversed(directives))
  52. return block
  53. def add_main_function(block):
  54. if not has_main_function(block):
  55. prepend_spaces = lambda x: ' ' + x
  56. block = deque(imap(prepend_spaces, block))
  57. block.appendleft("\nfn main() {\n")
  58. block.append("\n}\n")
  59. return block
  60. def extract_code_fragments(dest_dir, lines):
  61. """
  62. Extracts all the code fragments from a file that do not have ignored tags
  63. writing them to the following file:
  64. [dest dir]/[chapter name]_[chapter_index].rs
  65. """
  66. chapter_name = None
  67. chapter_index = 0
  68. for line in lines:
  69. if is_chapter_title(line):
  70. chapter_name = get_chapter_name(line)
  71. chapter_index = 1
  72. continue
  73. if not is_code_block_delim(line):
  74. continue
  75. assert chapter_name, "Chapter name missing for code block."
  76. tags = get_tags(line)
  77. block = get_code_block(lines)
  78. if tags & IGNORE_TAGS:
  79. continue
  80. block = add_extern_mod(add_main_function(block))
  81. block.appendleft(OUTPUT_BLOCK_HEADER)
  82. if "ignore" in tags:
  83. block.appendleft("//xfail-test\n")
  84. elif "should_fail" in tags:
  85. block.appendleft("//should-fail\n")
  86. output_filename = os.path.join(
  87. dest_dir,
  88. chapter_name + '_' + str(chapter_index) + '.rs')
  89. write_file(output_filename, block)
  90. chapter_index += 1
  91. def has_extern_mod(block):
  92. """Checks if a code block has the line `extern mod extra`."""
  93. find_extern_mod = lambda x: re.search(EXTERN_MOD_REGEX, x)
  94. return any(imap(find_extern_mod, block))
  95. def has_main_function(block):
  96. """Checks if a code block has a main function."""
  97. find_main_fn = lambda x: re.search(MAIN_FUNCTION_REGEX, x)
  98. return any(imap(find_main_fn, block))
  99. def is_chapter_title(line):
  100. return re.match(CHAPTER_NAME_REGEX, line)
  101. def is_code_block_delim(line):
  102. return re.match(CODE_BLOCK_DELIM_REGEX, line)
  103. def is_compiler_directive(line):
  104. return re.match(COMPILER_DIRECTIVE_REGEX, line)
  105. def get_chapter_name(line):
  106. """Get the chapter name from a `# Containers` line."""
  107. return re.sub(
  108. r'\W',
  109. '_',
  110. re.match(CHAPTER_NAME_REGEX, line).group(1)).lower()
  111. def get_code_block(lines):
  112. """
  113. Get a code block surrounded by ~~~, for example:
  114. 1: ~~~ { .tag }
  115. 2: let u: ~[u32] = ~[0, 1, 2];
  116. 3: let v: &[u32] = &[0, 1, 2, 3];
  117. 4: let w: [u32, .. 5] = [0, 1, 2, 3, 4];
  118. 5:
  119. 6: println!("u: {}, v: {}, w: {}", u.len(), v.len(), w.len());
  120. 7: ~~~
  121. Returns lines 2-6. Assumes line 1 has been consumed by the caller.
  122. """
  123. strip_comments = lambda x: re.sub(COMMENT_REGEX, '', x)
  124. strip_ellipses = lambda x: re.sub(ELLIPSES_REGEX, '', x)
  125. result = deque()
  126. for line in lines:
  127. if is_code_block_delim(line):
  128. break
  129. result.append(strip_comments(strip_ellipses(line)))
  130. return result
  131. def get_lines(filename):
  132. with open(filename) as f:
  133. for line in f:
  134. yield line
  135. def get_tags(line):
  136. """
  137. Retrieves all tags from the line format:
  138. ~~~ { .tag1 .tag2 .tag3 }
  139. """
  140. return set(re.findall(TAGS_REGEX, line))
  141. def write_file(filename, lines):
  142. with open(filename, 'w', encoding='utf-8') as f:
  143. for line in lines:
  144. f.write(unicode(line, encoding='utf-8', errors='replace'))
  145. def main(argv=None):
  146. if not argv:
  147. argv = sys.argv
  148. if len(sys.argv) < 2:
  149. sys.stderr.write("Please provide an input filename.")
  150. sys.exit(1)
  151. elif len(sys.argv) < 3:
  152. sys.stderr.write("Please provide a destination directory.")
  153. sys.exit(1)
  154. input_file = sys.argv[1]
  155. dest_dir = sys.argv[2]
  156. if not os.path.exists(input_file):
  157. sys.stderr.write("Input file does not exist.")
  158. sys.exit(1)
  159. if not os.path.exists(dest_dir):
  160. os.mkdir(dest_dir)
  161. extract_code_fragments(dest_dir, get_lines(input_file))
  162. if __name__ == "__main__":
  163. sys.exit(main())