5
\$\begingroup\$

I have decided to code an IDE for C using Python with Tkinter. I tried to use my best functional style and keep functions small. This programme works perfectly in Linux with Python 2.7, but it should be compatible with both Windows and Python 3.x.

# -*- coding: utf-8 -*-
import sys
from subprocess import Popen, PIPE, STDOUT
import datetime
import glob
import os
import platform
if sys.version_info.major == 2:
 import Tkinter as tk
 import tkMessageBox as pop_up
 import tkFileDialog
else:
 import Tkinter as tk
 import tkinter.tkMessageBox as pop_up
 import tkinter.tkFileDialog as tkFileDialog
TITLE = "C ide"
WINDOWS_ENDING = ".exe"
LINUX_ENDING = ".out"
EMPTY_TITLE_ERROR_MESSAGE_SAVE = "Please write the name of the file you want to save in the given field."
EMPTY_TITLE_ERROR_MESSAGE_OPEN = "Please write the name of the file you want to open in the given field."
INVALID_CHARACTERS_MESSAGE = "Unicode does not allow accented letters, please replace them in the following way: è -> e', à -> a'."
SAVING_SUCCESS_MESSAGE = "Your text is now stored in the {filename} file"
NO_ERROR = ('', None)
def replace_old_title(new_title):
 """
 Replace the old content of the widget
 file_title with a new title.
 """
 file_title.delete(0, tk.END)
 file_title.insert(tk.INSERT, new_title)
def replace_old_text(new_text):
 """
 Replace the old content of the widget
 main_text with a new title.
 """
 main_text.delete("1.0", tk.END)
 main_text.insert(tk.INSERT, new_text, "a")
def open_():
 """
 Opens a file using the built-in file explorer,
 and displays its text in the main text field.
 """
 filename = tkFileDialog.askopenfilename()
 replace_old_title(filename)
 with open(filename) as f:
 replace_old_text(f.read())
def title_is_empty():
 """
 Return True if the tite is empty.
 """
 return not file_title.get()
def invalid_characters_in_title():
 """
 Handles invalid characters in the
 title widget. Mainly accented letters.
 """
 try:
 __ = file_title.get()
 except UnicodeEncodeError:
 return True
def invalid_characters_in_body():
 """
 Handles invalid characters in the
 title widget. Mainly accented letters.
 """
 try:
 f.write(main_text.get(1.0, tk.END))
 except UnicodeEncodeError:
 return True
def save(alert=True):
 """
 Saves the content of the main text widget into a file,
 handles any kind of error that may happen.
 If alert is True: showes a pop up message to inform the user if
 the file is saved successfuly.
 """
 if title_is_empty():
 pop_up.showerror("No title.", EMPTY_TITLE_ERROR_MESSAGE_SAVE)
 return False
 if invalid_characters_in_title():
 pop_up.showerror("Invalid characters", INVALID_CHARACTERS_MESSAGE)
 return False
 filename = file_title.get()
 with open(filename, "w+") as f:
 try:
 f.write(main_text.get(1.0, tk.END))
 except UnicodeEncodeError:
 pop_up.showerror("Invalid characters", INVALID_CHARACTERS_MESSAGE)
 return False
 if alert:
 try:
 pop_up.showinfo("File saved succesfully.",
 SAVING_SUCCESS_MESSAGE.format(filename=filename))
 except UnicodeEncodeError:
 pop_up.showerror(
 "Invalid characters",
 INVALID_CHARACTERS_MESSAGE)
def exec_bash(shell_command):
 """
 Runs a shell_command.
 Taken from http://stackoverflow.com/questions/4256107/running-bash-commands-in-python
 User contributions are licensed under cc by-sa 3.0 with attribution required.
 """
 event = Popen(shell_command, shell=True, stdin=PIPE, stdout=PIPE,
 stderr=STDOUT)
 return event.communicate()
def compile_(filename, flags):
 """
 Uses the gcc compiler to compile the file.
 """
 command = "gcc " + filename + " " + flags
 return exec_bash(command)
def system_is(name):
 """
 Returns True if the os is equal to the given name.
 >>> system_is("Linux")
 True # If you are on linux
 False # If you are on Windows or Mac
 """
 operating_system = platform.system()
 if operating_system == name:
 return True
def decide_ending():
 """
 Decides the correct ending of the executable file
 basing on the os.
 """
 if system_is("Windows"):
 return WINDOWS_ENDING
 elif system_is("Linux"):
 return LINUX_ENDING
def nice_format_for_execute(result):
 """
 The second argument is always going to be None,
 I only care about the first one
 """
 return result[0]
def execute(filename="a"):
 """
 Executes the "a" executable taking care that
 the ending is correct.
 Format the result before outputting it.
 """
 filename += decide_ending()
 result = exec_bash("./" + filename)
 result = nice_format_for_execute(result)
 return result
def delete(string, sub_string):
 """
 Returns the string without the sub_string chars.
 >>> delete("Hello, how are you?","Hello, ")
 how are you?
 """
 string = string.replace(sub_string, "")
 return string
def get_flags():
 """
 Gets eventual compiler flags.
 Flags must be in the first line of the file,
 in the following format:
 // FLAGS -myflag1 -myflag2
 """
 flags = ""
 text = main_text.get(1.0, tk.END)
 flags = ""
 lines = text.splitlines()
 first_line = lines[0]
 if "// FLAGS" in first_line:
 flags += delete(first_line, "// FLAGS")
 return flags
def run():
 """
 Runs the file.
 If there is a compile time error it is shown. #TODO format compile errrors
 Otherwise, if the compilation is successful the result
 is shown in a pop up.
 """
 save(alert=False)
 filename = file_title.get()
 flags = get_flags()
 result = compile_(filename, flags)
 if result == NO_ERROR:
 pop_up.showinfo("The output is: ", execute())
 else:
 pop_up.showinfo("Error found when compiling", result)
# Here the GUI code starts.
root = tk.Tk()
root.wm_title(TITLE)
menubar = tk.Menu(root)
menubar.add_command(label="Open", command=open_)
menubar.add_command(label="Save", command=save)
menubar.add_command(label="Run", command=run)
root.config(menu=menubar)
top = tk.Frame(root)
tk.Label(root, text="Title:").pack(in_=top, side=tk.LEFT)
file_title = tk.Entry(root)
file_title.pack(in_=top, side=tk.RIGHT)
top.pack()
main_text = tk.Text(root)
main_text.pack(expand=True, fill='both')
tk.mainloop()
asked Nov 29, 2014 at 17:55
\$\endgroup\$

1 Answer 1

6
+50
\$\begingroup\$
  • It is mostly PEP8 conform, nice! One thing: Use just one empty line between functions.
  • The imports datetime, glob and os are not used.
  • I needed to change the tkinter-imports for python3 on linux:

    if sys.version_info.major == 2:
     import Tkinter as tk
     import tkMessageBox as pop_up
     import tkFileDialog
    else:
     import tkinter as tk
     import tkinter.messagebox as pop_up
     import tkinter.filedialog as tkFileDialog
    
  • exec_bash(): Please read https://docs.python.org/3/library/subprocess.html#security-considerations. You pass in data gathered from the document the user edits and could be provided by someone. A possible attack vector would be something like this example:

    // FLAGS; echo $USER;
    int main( int argc, const char* argv[] ) {
    }
    
  • system_is()/decide_ending(): This could be simplified using a constant dict with a mapping from os-name to file-ending.

  • execute(): Why the use of nice_format_for_execute()? This could be simplified to:

    result = exec_bash("./" + filename + decide_ending())
    return result[0]
    
  • delete(): You could just do return string.replace(...). It would be nice to use str.lstrip() to just remove the chars from the beginning.

  • get_flags(): Duplicate variable flags. And this could be shorter with the side effect of making delete() redundant:

    lines = main_text.get(1.0, tk.END).splitlines()
    first_line = lines[0]
    if first_line.startswith("// FLAGS"):
     return first_line.lstrip("// FLAGS")
    return ""
    
  • open_(): The with statement can fail and would throw an exception:

    >>> with open("doesnotexist") as fob:
    ... pass
    Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
    FileNotFoundError: [Errno 2] No such file or directory: 'doesnotexist'
    
  • Please wrap the code at the end of the file in a main() function. This creates the problem of variables like file_title. Please do not use global, but consider wrapping your functions in a class.

answered Dec 1, 2014 at 23:36
\$\endgroup\$
2
  • 3
    \$\begingroup\$ PEP8 requires two lines between functions, python.org/dev/peps/pep-0008/#id15. \$\endgroup\$ Commented Dec 2, 2014 at 11:04
  • \$\begingroup\$ @lummax You say that exec_bash() is potentially dangerous, still this is a toll for developers and they should know what they are doing. If they really want to damage their computers, they can just run the dangerous shell script directly. \$\endgroup\$ Commented Dec 2, 2014 at 20:37

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.