├── README.md └── convert.py /README.md: -------------------------------------------------------------------------------- 1 | csharp-to-python 2 | ================ 3 | 4 | Python script for converting C# code to Python. 5 | 6 | 5-1-2012 NSC 7 | 8 | When tasked with redeveloping an existing (and very robust) ASP.NET application 9 | using Python, a search for converters yielded minimal useful results. Several 10 | commercial tools exists, as well as a handful of online services (one of which has been 11 | down for at least six months as of today), but none were really working for the 12 | task at hand. 13 | 14 | While prototyping and familiarizing myself with Python, several pages 15 | and webmethods were converted by hand. In doing so a repeatable process emerged. 16 | 17 | Under the hood Python and C# have more differences than can be discussed, 18 | however the pure syntax of a source file, line by line, is moderately translatable. 19 | 20 | This conversion script was created to speed up the repetitive refactoring operations 21 | for each function in C#. It greatly reduced overall conversion time. 22 | 23 | Keep in mind, since C# and Python are both OO languages, this process is by no means foolproof. 24 | 25 | It should be noted, as you would expect, that language specific classes and types are not 26 | easily converted, and this script does not attempt to do so. 27 | 28 | What it does is simple - find and replace operations on a block of source code, line by line. 29 | Converting common C# language keywords to their Python equivalent. 30 | 31 | 32 | Use is simple: 33 | 1) create a file called convert.in, and place the block of C# code in it. 34 | (This utility works best one function at a time.) 35 | 2) run this script (./convert.py) 36 | 3) the results of the conversion will be written to convert.out. 37 | 38 | NOTE: there is a mode that makes a fair attempt at converting .aspx files to standard html. 39 | To try that: 40 | 1) place your .aspx markup in convert.in 41 | 2) run this script with an argument (./convert.py aspx) 42 | 3) results are in convert.out. 43 | 44 | FINAL NOTE: 45 | Every programmer has different habits, naming conventions, etc. Certain parts of this 46 | process were specifically intended for converting *my own naming conventions* in the C# code. 47 | 48 | Those lines are commented out, but left here as a reference/guide as you create your own 49 | customized replacements. -------------------------------------------------------------------------------- /convert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ######################################################################### 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | ######################################################################### 16 | 17 | """ 18 | Conversion Utility for C#.NET to Python. 19 | 20 | 5-1-2012 NSC 21 | 22 | Use is simple: 23 | 1) create a file called convert.in, and place the block of C# code in it. 24 | (This utility works best one function at a time.) 25 | 2) run this script (./convert.py) 26 | 3) the results of the conversion will be written to convert.out. 27 | 28 | NOTE: there is a mode that makes a fair attempt at converting .aspx files to standard html. 29 | To try that: 30 | 1) place your .aspx markup in convert.in 31 | 2) run this script with an argument (./convert.py aspx) 32 | 3) results are in convert.out. 33 | """ 34 | 35 | import re 36 | import sys 37 | 38 | out = "" 39 | 40 | with open("convert.in", 'r') as f_in: 41 | if not f_in: 42 | print "ERROR: convert.in not found." 43 | 44 | for line in f_in: 45 | # FIRST THINGS FIRST ... for Python, we wanna fix tabs into spaces... 46 | # we want all our output to have spaces instead of tabs 47 | line = line.replace("\t", " ") 48 | 49 | 50 | # we look for a command line arg to tell us if the input file might be .aspx (html) 51 | # if it is it's this special block. 52 | # the default is below 53 | if (len(sys.argv) > 1): 54 | if( sys.argv[1] == 'aspx' ): 55 | # this is the aspx -> html conversion 56 | 57 | line = line.replace(" runat=\"server\"", "") 58 | line = line.replace("CssClass", "class") 59 | 60 | # this before the TextBox replace 61 | if "TextMode=\"MultiLine\"" in line: 62 | line = line.replace("", ">") 63 | line = line.replace(" TextMode=\"MultiLine\"", "").replace("Rows", "rows") 64 | 65 | line = line.replace("", " />") 66 | line = line.replace("", ">") 67 | line = line.replace("", " />") 68 | line = line.replace("", "") 69 | line = line.replace("", "") 70 | 71 | # not all placeholders or literals convert to divs... 72 | line = line.replace(" Python conversion 78 | INDENT = 0 79 | sINDENTp4 = " " 80 | 81 | 82 | # now that the tabs are fixed, 83 | # we wanna count the whitespace at the beginning of the line 84 | # might use it later for indent validation, etc. 85 | p = re.compile(r"^\s+") 86 | m = p.match(line) 87 | if m: 88 | INDENT = len(m.group()) 89 | 90 | #if INDENT > 0 and INDENT < 4: 91 | # line = "SHORT INDENT\n" + line 92 | 93 | #if INDENT >= 4: 94 | # if INDENT % 4: 95 | # line = "!! BAD INDENT\n" + line 96 | 97 | sINDENT = " " * INDENT 98 | # this string global contains the current indent level + 4 99 | sINDENTp4 = " " * (INDENT+4) 100 | # + 8 101 | sINDENTp8 = " " * (INDENT+8) 102 | 103 | # braces are a tricky, but lets first just remove any lines that only contain an open/close brace 104 | if line.strip() == "{": continue 105 | if line.strip() == "}": continue 106 | 107 | # if the brace appears at the end of a line (like after an "if") 108 | if len(line.strip()) > 1: 109 | s = line.strip() 110 | if s[-1] == "{": 111 | line = s[:-1] 112 | 113 | # comments 114 | if line.strip()[:2] == "//": 115 | line = line.replace("//", "# ") 116 | line = line.replace("/*", "\"\"\"").replace("*/", "\"\"\"") 117 | # some comments are at the end of a line 118 | line = line.replace("; //", " # ") 119 | 120 | 121 | 122 | # Fixing semicolon line endings (not otherwise, may be in a sql statement or something) 123 | line = line.replace(";\n", "\n") 124 | 125 | # Fixing line wrapped string concatenations 126 | line = line.replace(") +\n", ") + \\\n") # lines that had an inline if still need the + 127 | line = line.replace("+\n", "\\\n") 128 | 129 | 130 | # Fixing function declarations... 131 | line = line.replace("public ", "def ") 132 | line = line.replace("private ", "def ") 133 | if line.strip()[:3] == "def": 134 | line = line.replace(")", "):") 135 | 136 | # Fixing variable declarations... 137 | # doing "string" and "int" again down below with a regex, because they could be a line starter, or part of a bigger word 138 | line = line.replace(" int ", " ").replace(" string ", " ").replace(" bool ", " ").replace(" void ", " ") 139 | line = line.replace("(int ", "(").replace("(string ", "(").replace("(bool ", "(") 140 | 141 | # common C# functions and keywords 142 | line = line.replace(".ToString()", "") # no equivalent, not necessary 143 | line = line.replace(".ToLower()", ".lower()") 144 | line = line.replace(".IndexOf(", ".find(") 145 | line = line.replace(".Replace", ".replace") 146 | line = line.replace(".Split", ".split") 147 | line = line.replace(".Trim()", ".strip()") 148 | line = line.replace("else if", "elif") 149 | line = line.replace("!string.IsNullOrEmpty(", "") 150 | line = line.replace("string.IsNullOrEmpty(", "not ") 151 | line = line.replace("this.", "self.") 152 | 153 | # Try/Catch blocks 154 | line = line.replace("try", "try:") 155 | line = line.replace("catch (Exception ex)", "except Exception:") 156 | # I often threw "new exceptions" - python doesn't need the extra stuff 157 | line = line.replace("new Exception", "Exception") 158 | line = line.replace("throw", "raise") 159 | 160 | # NULL testing 161 | line = line.replace("== null", "is None") 162 | line = line.replace("!= null", "is not None") 163 | 164 | 165 | # ##### CUSTOM REPLACEMENTS ##### 166 | # line = line.replace("\" + Environment.NewLine", "\\n\"") 167 | # line = line.replace("HttpContext.Current.Server.MapPath(", "") # this will leave a trailing paren ! 168 | # line = line.replace(").Value", ", \"\") # WAS A .Value - confirm") #should work most of the time for Cato code 169 | # line = line.replace(".Length", ".__LENGTH") 170 | # 171 | # #these commonly appear on a line alone, and we aren't using them any more 172 | # if line.strip() == "dataAccess dc = new dataAccess()": continue 173 | # if line.strip() == "acUI.acUI ui = new acUI.acUI()": continue 174 | # if line.strip() == "sErr = \"\"": continue 175 | # if line.lower().strip() == "ssql = \"\"": continue # there's mixed case usage of "sSql" 176 | # if "dataAccess.acTransaction" in line: continue 177 | # if line.strip() == "DataRow dr = null": continue 178 | # if line.strip() == "DataTable dt = new DataTable()": continue 179 | # if "FunctionTemplates.HTMLTemplates ft" in line: continue 180 | # 181 | # 182 | # # a whole bunch of common phrases from Cato C# code 183 | # line = line.replace("Globals.acObjectTypes", "uiGlobals.CatoObjectTypes") 184 | # line = line.replace("ui.", "uiCommon.") 185 | # line = line.replace("ft.", "ST.") # "FunctionTemplates ft is now import stepTemplates as sST" 186 | # line = line.replace("dc.IsTrue", "uiCommon.IsTrue") 187 | # line = line.replace("../images", "static/images") 188 | # line = line.replace("dc.EnCrypt", "catocommon.cato_encrypt") 189 | # 190 | # #99% of the time we won't want a None return, but an empty string instead 191 | # line = line.replace("return\n", "return \"\"\n") 192 | # 193 | # 194 | # 195 | # # this will *usually work 196 | # line = line.replace("if (!dc.sqlExecuteUpdate(sSQL, ref sErr))", "if not uiGlobals.request.db.exec_db_noexcep(sSQL):") 197 | # if "!dc.sqlGetSingleString" in line: 198 | # line = sINDENT + "00000 = uiGlobals.request.db.select_col_noexcep(sSQL)\n" + sINDENT + "if uiGlobals.request.db.error:\n" 199 | # 200 | # if "!dc.sqlGetDataTable" in line: 201 | # line = sINDENT + "00000 = uiGlobals.request.db.select_all_dict(sSQL)\n" + sINDENT + "if uiGlobals.request.db.error:\n" 202 | # 203 | # line = line.replace("+ sErr", "+ uiGlobals.request.db.error") # + sErr is *usually used for displaying a db error. Just switch it. 204 | # line = line.replace("(sErr)", "(uiGlobals.request.db.error)") # sometimes it's in a log message as the only arg 205 | # line = line.replace("if (!oTrans.ExecUpdate(ref sErr))", "if not uiGlobals.request.db.tran_exec_noexcep(sSQL):") 206 | # line = line.replace("oTrans.Command.CommandText", "sSQL") # transactions are different, regular sSQL variable 207 | # line = line.replace("oTrans.Commit()", "uiGlobals.request.db.tran_commit()") 208 | # line = line.replace("oTrans.RollBack()", "uiGlobals.request.db.tran_rollback()") 209 | # line = line.replace("DataRow dr in dt.Rows", "dr in dt") 210 | # line = line.replace("dt.Rows.Count > 0", "dt") 211 | # line = line.replace("Step oStep", "oStep") 212 | # line = line.replace("+ i +", "+ str(i) +") 213 | # line = line.replace("i+\\", "i += 1") 214 | # 215 | # # this will be helpful 216 | # if " CommonAttribs" in line: 217 | # line = "### CommonAttribsWithID ????\nuiCommon.NewGUID()\n" + line 218 | # 219 | # 220 | # # random stuff that may or may not work 221 | # if line.strip() == "if (!dc.sqlGetDataRow(ref dr, sSQL, ref sErr))": 222 | # line = sINDENT + "dr = uiGlobals.request.db.select_row_dict(sSQL)\n" + sINDENT + "if uiGlobals.request.db.error:\n" + sINDENTp4 + "raise Exception(uiGlobals.request.db.error)\n" 223 | # if line.strip() == "if (!dc.sqlGetDataTable(ref dt, sSQL, ref sErr))": 224 | # line = sINDENT + "dt = uiGlobals.request.db.select_all_dict(sSQL)\n" + sINDENT + "if uiGlobals.request.db.error:\n" + sINDENTp4 + "raise Exception(uiGlobals.request.db.error)\n" 225 | # 226 | # # true/false may be problematic, but these should be ok 227 | # line = line.replace(", true", ", True").replace(", false", ", False") 228 | # 229 | # ##### END CUSTOM ##### 230 | 231 | 232 | # xml/Linq stuff 233 | # the following lines were useful, but NOT foolproof, in converting some Linq XDocument/XElement stuff 234 | # to Python's ElementTree module. 235 | line = line.replace(".Attribute(", ".get(") 236 | line = line.replace(".Element(", ".find(") 237 | 238 | line = line.replace("XDocument.Load", "ET.parse") 239 | line = line.replace("XDocument ", "").replace("XElement ", "") 240 | line = line.replace("XDocument.Parse", "ET.fromstring") 241 | line = line.replace("IEnumerable ", "") 242 | 243 | # note the order of the following 244 | line = line.replace(".XPathSelectElements", ".findall").replace(".XPathSelectElement", ".find") 245 | line = line.replace(".SetValue", ".text") 246 | 247 | line = line.replace("ex.Message", "traceback.format_exc()") 248 | 249 | 250 | # ##### CUSTOM ##### 251 | # #!!! this has to be done after the database stuff, because they all use a "ref sErr" and we're matching on that! 252 | # # passing arguments by "ref" doesn't work in python, mark that code obviously 253 | # # because it need attention 254 | # line = line.replace("ref ", "0000BYREF_ARG0000") 255 | # 256 | # # the new way of doing exceptions - not raising them, appending them to an output object 257 | # line = line.replace("raise ex", "uiGlobals.request.Messages.append(traceback.format_exc())") 258 | # line = line.replace("raise Exception(", "uiGlobals.request.Messages.append(") 259 | # 260 | # 261 | # # if this is a function declaration and it's a "wm" web method, 262 | # # throw the new argument getter line on there 263 | # if "def wm" in line: 264 | # s = "" 265 | # p = re.compile("\(.*\)") 266 | # m = p.search(line) 267 | # if m: 268 | # args = m.group().replace("(","").replace(")","") 269 | # for arg in args.split(","): 270 | # s = s + sINDENTp4 + "%s = uiCommon.getAjaxArg(\"%s\")\n" % (arg.strip(), arg.strip()) 271 | # 272 | # line = line.replace(args, "self") + s 273 | # 274 | # ##### END CUSTOM ##### 275 | 276 | # else statements on their own line 277 | if line.strip() == "else": 278 | line = line.replace("else", "else:") 279 | 280 | 281 | # let's try some stuff with regular expressions 282 | # string and int declarations 283 | p = re.compile("^int ") 284 | m = p.match(line) 285 | if m: 286 | line = line.replace("int ", "") 287 | p = re.compile("^string ") 288 | m = p.match(line) 289 | if m: 290 | line = line.replace("string ", "") 291 | 292 | # if statements 293 | p = re.compile(".*if \(.*\)") 294 | m = p.match(line) 295 | if m: 296 | line = line.replace("if (", "if ") 297 | line = line[:-2] + ":\n" 298 | line = line.replace("):", ":") 299 | 300 | # foreach statements (also marking them because type declarations may need fixing) 301 | p = re.compile(".*foreach \(.*\)") 302 | m = p.match(line) 303 | if m: 304 | line = line.replace("foreach (", "for ") 305 | line = line[:-2] + ":\n" 306 | line = "### CHECK NEXT LINE for type declarations !!!\n" + line 307 | 308 | p = re.compile(".*while \(.*\)") 309 | m = p.match(line) 310 | if m: 311 | line = line.replace("while (", "while ") 312 | line = line[:-2] + ":\n" 313 | line = "### CHECK NEXT LINE for type declarations !!!\n" + line 314 | 315 | # this is a crazy one. Trying to convert inline 'if' statements 316 | #first, does it look like a C# inline if? 317 | p = re.compile("\(.*\?.*:.*\)") 318 | m = p.search(line) 319 | if m: 320 | pre_munge = m.group() 321 | 322 | # ok, let's pick apart the pieces 323 | p = re.compile("\(.*\?") 324 | m = p.search(line) 325 | if_part = m.group().replace("(", "").replace("?", "").replace(")", "").strip() 326 | 327 | p = re.compile("\?.*:") 328 | m = p.search(line) 329 | then_part = m.group().replace(":", "").replace("?", "").strip() 330 | 331 | p = re.compile(":.*\)") 332 | m = p.search(line) 333 | else_part = m.group().replace(":", "").replace("?", "").replace(")", "").strip() 334 | 335 | #now reconstitute it (don't forget the rest of the line 336 | post_munge = "(%s if %s else %s)" % (then_part, if_part, else_part) 337 | line = line.replace(pre_munge, post_munge) 338 | 339 | # && and || comparison operators in an "if" statement 340 | p = re.compile("^.*if.*&&") 341 | m = p.search(line) 342 | if m: 343 | line = line.replace("&&", "and") 344 | # line = "### VERIFY 'ANDs' !!!\n" + line 345 | 346 | p = re.compile("^.*if.*\|\|") 347 | m = p.search(line) 348 | if m: 349 | line = line.replace("||", "or") 350 | # line = "### VERIFY 'ORs' !!!\n" + line 351 | 352 | 353 | # ALL DONE! 354 | out += line 355 | 356 | with open("convert.out", 'w') as f_out: 357 | if not f_out: 358 | print "ERROR: unable to create convert.out." 359 | 360 | f_out.write(out) 361 | --------------------------------------------------------------------------------