├── 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 |
--------------------------------------------------------------------------------