1# This file is part of the MicroPython project, http://micropython.org/ 2# 3# The MIT License (MIT) 4# 5# Copyright (c) 2016 Rami Ali 6# 7# Permission is hereby granted, free of charge, to any person obtaining a copy 8# of this software and associated documentation files (the "Software"), to deal 9# in the Software without restriction, including without limitation the rights 10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11# copies of the Software, and to permit persons to whom the Software is 12# furnished to do so, subject to the following conditions: 13# 14# The above copyright notice and this permission notice shall be included in 15# all copies or substantial portions of the Software. 16# 17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23# THE SOFTWARE. 24 25""" gen-cpydiff generates documentation which outlines operations that differ between MicroPython 26 and CPython. This script is called by the docs Makefile for html and Latex and may be run 27 manually using the command make gen-cpydiff. """ 28 29import os 30import errno 31import subprocess 32import time 33import re 34from collections import namedtuple 35 36# MicroPython supports syntax of CPython 3.4 with some features from 3.5, and 37# such version should be used to test for differences. If your default python3 38# executable is of lower version, you can point MICROPY_CPYTHON3 environment var 39# to the correct executable. 40if os.name == "nt": 41 CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3.exe") 42 MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/windows/micropython.exe") 43else: 44 CPYTHON3 = os.getenv("MICROPY_CPYTHON3", "python3") 45 MICROPYTHON = os.getenv("MICROPY_MICROPYTHON", "../ports/unix/micropython") 46 47TESTPATH = "../tests/cpydiff/" 48DOCPATH = "../docs/genrst/" 49INDEXTEMPLATE = "../docs/differences/index_template.txt" 50INDEX = "index.rst" 51 52HEADER = ".. This document was generated by tools/gen-cpydiff.py\n\n" 53CLASSMAP = {"Core": "Core language", "Types": "Builtin types"} 54INDEXPRIORITY = ["syntax", "core_language", "builtin_types", "modules"] 55RSTCHARS = ["=", "-", "~", "`", ":"] 56SPLIT = '"""\n|categories: |description: |cause: |workaround: ' 57TAB = " " 58 59Output = namedtuple( 60 "output", 61 [ 62 "name", 63 "class_", 64 "desc", 65 "cause", 66 "workaround", 67 "code", 68 "output_cpy", 69 "output_upy", 70 "status", 71 ], 72) 73 74 75def readfiles(): 76 """Reads test files""" 77 tests = list(filter(lambda x: x.endswith(".py"), os.listdir(TESTPATH))) 78 tests.sort() 79 files = [] 80 81 for test in tests: 82 text = open(TESTPATH + test, "r").read() 83 84 try: 85 class_, desc, cause, workaround, code = [ 86 x.rstrip() for x in list(filter(None, re.split(SPLIT, text))) 87 ] 88 output = Output(test, class_, desc, cause, workaround, code, "", "", "") 89 files.append(output) 90 except IndexError: 91 print("Incorrect format in file " + TESTPATH + test) 92 93 return files 94 95 96def run_tests(tests): 97 """executes all tests""" 98 results = [] 99 for test in tests: 100 with open(TESTPATH + test.name, "rb") as f: 101 input_py = f.read() 102 103 process = subprocess.Popen( 104 CPYTHON3, 105 shell=True, 106 stdout=subprocess.PIPE, 107 stdin=subprocess.PIPE, 108 stderr=subprocess.PIPE, 109 ) 110 output_cpy = [com.decode("utf8") for com in process.communicate(input_py)] 111 112 process = subprocess.Popen( 113 MICROPYTHON, 114 shell=True, 115 stdout=subprocess.PIPE, 116 stdin=subprocess.PIPE, 117 stderr=subprocess.PIPE, 118 ) 119 output_upy = [com.decode("utf8") for com in process.communicate(input_py)] 120 121 if output_cpy[0] == output_upy[0] and output_cpy[1] == output_upy[1]: 122 status = "Supported" 123 print("Supported operation!\nFile: " + TESTPATH + test.name) 124 else: 125 status = "Unsupported" 126 127 output = Output( 128 test.name, 129 test.class_, 130 test.desc, 131 test.cause, 132 test.workaround, 133 test.code, 134 output_cpy, 135 output_upy, 136 status, 137 ) 138 results.append(output) 139 140 results.sort(key=lambda x: x.class_) 141 return results 142 143 144def indent(block, spaces): 145 """indents paragraphs of text for rst formatting""" 146 new_block = "" 147 for line in block.split("\n"): 148 new_block += spaces + line + "\n" 149 return new_block 150 151 152def gen_table(contents): 153 """creates a table given any set of columns""" 154 xlengths = [] 155 ylengths = [] 156 for column in contents: 157 col_len = 0 158 for entry in column: 159 lines = entry.split("\n") 160 for line in lines: 161 col_len = max(len(line) + 2, col_len) 162 xlengths.append(col_len) 163 for i in range(len(contents[0])): 164 ymax = 0 165 for j in range(len(contents)): 166 ymax = max(ymax, len(contents[j][i].split("\n"))) 167 ylengths.append(ymax) 168 169 table_divider = "+" + "".join(["-" * i + "+" for i in xlengths]) + "\n" 170 table = table_divider 171 for i in range(len(ylengths)): 172 row = [column[i] for column in contents] 173 row = [entry + "\n" * (ylengths[i] - len(entry.split("\n"))) for entry in row] 174 row = [entry.split("\n") for entry in row] 175 for j in range(ylengths[i]): 176 k = 0 177 for entry in row: 178 width = xlengths[k] 179 table += "".join(["| {:{}}".format(entry[j], width - 1)]) 180 k += 1 181 table += "|\n" 182 table += table_divider 183 return table + "\n" 184 185 186def gen_rst(results): 187 """creates restructured text documents to display tests""" 188 189 # make sure the destination directory exists 190 try: 191 os.mkdir(DOCPATH) 192 except OSError as e: 193 if e.args[0] != errno.EEXIST and e.args[0] != errno.EISDIR: 194 raise 195 196 toctree = [] 197 class_ = [] 198 for output in results: 199 section = output.class_.split(",") 200 for i in range(len(section)): 201 section[i] = section[i].rstrip() 202 if section[i] in CLASSMAP: 203 section[i] = CLASSMAP[section[i]] 204 if i >= len(class_) or section[i] != class_[i]: 205 if i == 0: 206 filename = section[i].replace(" ", "_").lower() 207 rst = open(DOCPATH + filename + ".rst", "w") 208 rst.write(HEADER) 209 rst.write(section[i] + "\n") 210 rst.write(RSTCHARS[0] * len(section[i])) 211 rst.write(time.strftime("\nGenerated %a %d %b %Y %X UTC\n\n", time.gmtime())) 212 toctree.append(filename) 213 else: 214 rst.write(section[i] + "\n") 215 rst.write(RSTCHARS[min(i, len(RSTCHARS) - 1)] * len(section[i])) 216 rst.write("\n\n") 217 class_ = section 218 rst.write(".. _cpydiff_%s:\n\n" % output.name.rsplit(".", 1)[0]) 219 rst.write(output.desc + "\n") 220 rst.write("~" * len(output.desc) + "\n\n") 221 if output.cause != "Unknown": 222 rst.write("**Cause:** " + output.cause + "\n\n") 223 if output.workaround != "Unknown": 224 rst.write("**Workaround:** " + output.workaround + "\n\n") 225 226 rst.write("Sample code::\n\n" + indent(output.code, TAB) + "\n") 227 output_cpy = indent("".join(output.output_cpy[0:2]), TAB).rstrip() 228 output_cpy = ("::\n\n" if output_cpy != "" else "") + output_cpy 229 output_upy = indent("".join(output.output_upy[0:2]), TAB).rstrip() 230 output_upy = ("::\n\n" if output_upy != "" else "") + output_upy 231 table = gen_table([["CPy output:", output_cpy], ["uPy output:", output_upy]]) 232 rst.write(table) 233 234 template = open(INDEXTEMPLATE, "r") 235 index = open(DOCPATH + INDEX, "w") 236 index.write(HEADER) 237 index.write(template.read()) 238 for section in INDEXPRIORITY: 239 if section in toctree: 240 index.write(indent(section + ".rst", TAB)) 241 toctree.remove(section) 242 for section in toctree: 243 index.write(indent(section + ".rst", TAB)) 244 245 246def main(): 247 """Main function""" 248 249 # set search path so that test scripts find the test modules (and no other ones) 250 os.environ["PYTHONPATH"] = TESTPATH 251 os.environ["MICROPYPATH"] = TESTPATH 252 253 files = readfiles() 254 results = run_tests(files) 255 gen_rst(results) 256 257 258main() 259