Her bir alt süreç için geçerli olacak jenerik bir worker sınıfı tanımlayıp, gelen komutlara göre hangi worker’ın oluşturulacağını veya komutun tipine göre yeni worker oluşturmanın gerekip gerekmediğini denetleyebiliriz.
Yani python3
yazıp enter tuşuna bastığımızda, program python konsolundan çıkana kadar yeni bir worker ve process oluşturmamalı. Öyle değil mi?
Aynı durum nodejs için de geçerli. Eğer yeni bir alt süreç açtıysak, bu alt süreç sonlanana kadar yeni bir süreç açmamak, zaten açılmış olan süreçten devam etmek gerekiyor.
Alt süreçleri yönetmek için SubProcess
isimli yeni bir worker sınıf tanımlıyorum. Bu sınıfı, hem python
; hem nodejs
hem de başka programlar için kullanabiliriz.
import sys
import subprocess
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit
from PyQt5.QtGui import QTextCursor
from PyQt5.QtCore import Qt, QThread, pyqtSignal
class TerminalWorker(QThread):
finished = pyqtSignal(str)
def __init__(self, command, parent=None):
super().__init__(parent)
self.command = command
def run(self):
try:
process = subprocess.Popen(
self.command,
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
)
stdout, stderr = process.communicate()
output = stdout + stderr
except Exception as e:
output = str(e)
self.finished.emit(output)
class SubWorker(QThread):
finished = pyqtSignal(str)
def __init__(self, process, parent=None):
super().__init__(parent)
self.process = process
def run(self):
while True:
output = self.process.stdout.readline()
if output:
self.finished.emit(output.replace(">>> ", ""))
else:
break
class CommandLineWindow(QMainWindow):
def __init__(self):
super().__init__()
self.console = QTextEdit(self)
self.terminal_process = None
self.terminal_worker = None
self.python_process = None
self.python_worker = None
self.node_process = None
self.node_worker = None
self.config()
def config(self):
self.console.setReadOnly(False)
self.console.setStyleSheet("background-color: black; color: white;")
self.setCentralWidget(self.console)
self.console.setTextInteractionFlags(Qt.TextEditorInteraction)
self.console.setPlaceholderText('Enter commands and press Enter...')
self.console.installEventFilter(self)
self.setWindowTitle('Terminal Interface')
self.setGeometry(100, 100, 800, 600)
def eventFilter(self, obj, event):
if obj is self.console and event.type() == event.KeyPress:
key = event.key()
if key in (Qt.Key_Enter, Qt.Key_Return):
self.process_command()
return True
return super().eventFilter(obj, event)
def process_command(self):
cursor = self.console.textCursor()
cursor.movePosition(QTextCursor.StartOfBlock)
cursor.select(QTextCursor.BlockUnderCursor)
command = cursor.selectedText().strip()
cursor.removeSelectedText()
self.console.append(f'{command}\n')
if command == "python3" and not self.python_process:
self.start_python_process()
elif command == "node" and not self.node_process:
self.start_node_process()
elif self.node_process:
self.execute_node_command(command)
elif self.python_process:
self.execute_python_command(command)
else:
self.execute_terminal_command(command)
def start_python_process(self):
self.python_process = subprocess.Popen(
["python3", "-i"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1
)
self.python_worker = SubWorker(self.python_process)
self.python_worker.finished.connect(self.subprocess_finished)
self.python_worker.start()
def start_node_process(self):
self.node_process = subprocess.Popen(
["node", "-i"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1
)
self.node_worker = SubWorker(self.node_process)
self.node_worker.finished.connect(self.subprocess_finished)
self.node_worker.start()
def execute_node_command(self, command):
if self.node_process:
self.node_process.stdin.write(f'{command}\n')
self.node_process.stdin.flush()
if command == "exit":
self.node_process = False
def execute_python_command(self, command):
if self.python_process:
self.python_process.stdin.write(f'{command}\n')
self.python_process.stdin.flush()
if command == "exit()":
self.python_process = False
def execute_terminal_command(self, command):
self.terminal_worker = TerminalWorker(command)
self.terminal_worker.finished.connect(self.terminal_process_finished)
self.terminal_worker.start()
def subprocess_finished(self, output):
self.console.append(output)
def terminal_process_finished(self, data):
self.console.append(data)
if __name__ == '__main__':
app = QApplication(sys.argv)
terminal = CommandLineWindow()
terminal.show()
sys.exit(app.exec_())
Burada artık pseudo
bir sınıf kullanmadık. Açtığımız prosesleri yönetmeye çalıştık.
Örneğin, yukarıda, üç adet worker
tanımlandığını görüyorsunuz.
İlk worker, terminal
kodlarını çalıştırmak için,
İkinci worker, python
kodlarını çalıştırmak için,
Üçüncü worker da node
kodlarını çalıştırmak için oluşturulmuş workerlar.
İkinci worker, command
, python3
olduğunda, üçüncü worker ise command
node
olduğunda çalışıyor.
def process_command(self):
cursor = self.console.textCursor()
cursor.movePosition(QTextCursor.StartOfBlock)
cursor.select(QTextCursor.BlockUnderCursor)
command = cursor.selectedText().strip()
cursor.removeSelectedText()
self.console.append(f'{command}\n')
if command == "python3" and not self.python_process:
self.start_python_process()
elif command == "node" and not self.node_process:
self.start_node_process()
elif self.node_process:
self.execute_node_command(command)
elif self.python_process:
self.execute_python_command(command)
else:
self.execute_terminal_command(command)
Yukarıdaki koşul ifadelerini inceleyelim:
İlk koşul ifadesi, eğer command
’ın değeri "python3"
ise ve bir python
süreci açılmamışsa, anlamına geliyor. Bu durumda aşağıdaki fonksiyon çağrılır:
def start_python_process(self):
self.python_process = subprocess.Popen(
["python3", "-i"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1
)
self.python_worker = SubWorker(self.python_process)
self.python_worker.finished.connect(self.subprocess_finished)
self.python_worker.start()
İkinci koşul ifadesi ise, eğer command
’ın değeri "node"
ise ve bir node
süreci açılmamışsa, anlamına geliyor. Bu durumda da aşağıdaki fonksiyon çağrılır:
def start_node_process(self):
self.node_process = subprocess.Popen(
["node", "-i"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1
)
self.node_worker = SubWorker(self.node_process)
self.node_worker.finished.connect(self.subprocess_finished)
self.node_worker.start()
elif self.node_process:
ifadesi, node
süreci açıldığında çalışır ve execute_node_command
fonksiyonu çağrılır:
def execute_node_command(self, command):
if self.node_process:
self.node_process.stdin.write(f'{command}\n')
self.node_process.stdin.flush()
if command == "exit":
self.node_process = False
elif self.python_process:
ifadesi ise, python3
süreci açıldığında çalışır ve execute_python_command
fonksiyonu çağrılır:
def execute_python_command(self, command):
if self.python_process:
self.python_process.stdin.write(f'{command}\n')
self.python_process.stdin.flush()
if command == "exit()":
self.python_process = False
Her iki execute
fonksiyonunda da yeni prosesler açılmadığını, mevcut proseslere komutların işlendiğini görüyorsunuz.
Bu arada artık python
konsolunu aktif ettikten sonra, input
fonksiyonunu kullanabilirsiniz.
Ancak hala terminal davranışının alıştığınız python konsoluna benzemesi için azı manipülasyonlar yapmanız gerekir.
Öte yandan, geri kalan diğer terminal işlemleri, şimdilik aşağıdaki fonksiyon yardımıyla çalıştırılır:
def execute_terminal_command(self, command):
self.terminal_worker = TerminalWorker(command)
self.terminal_worker.finished.connect(self.terminal_process_finished)
self.terminal_worker.start()
execute_terminal_command
fonksiyonundaki her bir terminal çağrısı, birbirinden bağımsız süreçler olarak yaratılır.