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()
1 Answer 1
- It is mostly PEP8 conform, nice! One thing: Use just one empty line between functions.
- The imports
datetime
,glob
andos
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 ofnice_format_for_execute()
? This could be simplified to:result = exec_bash("./" + filename + decide_ending()) return result[0]
delete()
: You could just doreturn string.replace(...)
. It would be nice to usestr.lstrip()
to just remove the chars from the beginning.get_flags()
: Duplicate variableflags
. And this could be shorter with the side effect of makingdelete()
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 likefile_title
. Please do not useglobal
, but consider wrapping your functions in a class.
-
3\$\begingroup\$ PEP8 requires two lines between functions, python.org/dev/peps/pep-0008/#id15. \$\endgroup\$ferada– ferada2014年12月02日 11:04:13 +00:00Commented 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\$Caridorc– Caridorc2014年12月02日 20:37:13 +00:00Commented Dec 2, 2014 at 20:37