From: Agnibho Mondal Date: Wed, 23 Dec 2015 09:41:04 +0000 (+0530) Subject: Major code rearrangement X-Git-Url: https://code.agnibho.com/repo?a=commitdiff_plain;h=717bb15e56a14b4d814054c5bd5d15a9b4c9e20f;p=ddstorm.git Major code rearrangement --- diff --git a/__main__.py b/__main__.py index 1b430c2..2d7ca46 100644 --- a/__main__.py +++ b/__main__.py @@ -1,141 +1,29 @@ #! /usr/bin/python3 -# DDStorm -# ------- -# Copyright (c) 2015 Agnibho Mondal -# All rights reserved +''' +Start the application +''' +''' +Copyright (c) 2015 Agnibho Mondal +All rights reserved -# This file is part of DDStorm. +This file is part of DDStorm. -# DDStorm is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +DDStorm is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. -# DDStorm is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +DDStorm is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with DDStorm. If not, see . +You should have received a copy of the GNU General Public License +along with DDStorm. If not, see . +''' -import sys, time, subprocess -from PyQt4 import QtGui, QtCore -from _symptoms import Symptoms -from _differentials import Differentials -from ddstorm import DDStorm -from conf import Conf -from _extras import * -from const import * - -conf=False - -class Content(QtGui.QWidget): - change=QtCore.pyqtSignal() - def __init__(self): - super(Content, self).__init__() - self.dd=DDStorm(True,conf) - if(not self.dd.compiler.clean): - ret=QtGui.QMessageBox.warning(self, "Compilation Error", "Error was encountered while compiling the Knowledgebase.", "Ignore", "View Log") - if(ret==1): - x_logfile() - self.initUI() - def initUI(self): - global conf - - grid=QtGui.QGridLayout() - self.setLayout(grid) - - self.symp=Symptoms(self.dd.symptoms()) - self.symp.setFrameShape(QtGui.QFrame.StyledPanel) - self.symp.changed.connect(self.update) - - self.diff=Differentials() - self.diff.setFrameShape(QtGui.QFrame.StyledPanel) - - grid.addWidget(self.symp, 0, 0) - grid.addWidget(self.diff, 0, 1) - grid.setColumnStretch(0, 1) - grid.setColumnStretch(1, 1) - QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("Cleanlooks")) - def update(self, data): - self.diff.update(self.dd.dd(data)) - self.change.emit() - -class Window(QtGui.QMainWindow): - def __init__(self): - super(Window, self).__init__() - self.initUI() - def initUI(self): - global conf - self.con=Content() - self.sett=SettingsDialog(conf) - if(conf.get("status_message")=="on"): - self.con.change.connect(self.showStatus) - - menu=self.menuBar() - menuFile=menu.addMenu("&File") - menuFile.addAction("&Save").triggered.connect(self.savefile) - menuFile.addAction("E&xit").triggered.connect(self.close) - menuEdit=menu.addMenu("&Edit") - menuEdit.addAction("&Add").triggered.connect(self.con.symp.addItem) - menuEdit.addAction("&Browse Symptoms").triggered.connect(self.con.symp.browseSymptoms) - rmAction=QtGui.QAction("&Remove", self) - rmAction.setShortcut("Delete") - rmAction.triggered.connect(self.con.symp.remove) - menuEdit.addAction(rmAction) - menuEdit.addAction("&Clear All").triggered.connect(self.con.symp.removeAll) - menuTool=menu.addMenu("&Tools") - menuTool.addAction("&Library").triggered.connect(x_lib) - menuTool.addAction("&Settings").triggered.connect(self.settings) - menuTool.addAction("&View Log").triggered.connect(x_logfile) - menuHelp=menu.addMenu("&Help") - menuHelp.addAction("&Help").triggered.connect(x_help) - menuHelp.addAction("&About").triggered.connect(self.about) - - self.setCentralWidget(self.con) - self.status=self.statusBar() - self.setGeometry(200, 200, 600, 400) - self.setWindowTitle("D/D Storm") - self.setWindowIcon(QtGui.QIcon("icons/icon.png")) - self.showMaximized() - self.con.symp.new.setFocus() - def showStatus(self): - if(self.con.symp.getList() and self.con.diff.getList()): - self.status.showMessage(str(len(self.con.diff.getList()))+" differential diagnosis for "+str(len(self.con.symp.getList()))+" symptom(s).") - else: - self.status.showMessage("") - def savefile(self): - x_save(self, self.con.symp.getList(), self.con.diff.getList()) - def settings(self): - self.sett.exec_() - def about(self): - QtGui.QMessageBox.about(self, "About", "

DDStorm

\nBrainstorm Medicine") - -def main(): - app=QtGui.QApplication(sys.argv) - - global conf - conf=Conf() - if(conf.get("clean_log")=="yes"): - open(LOG_FILE, "w").close() - if(conf.get("splash_screen")=="yes"): - ss=True - else: - ss=False - if(ss): - splash=QtGui.QSplashScreen(QtGui.QPixmap("icons/splash.png")) - splash.show() - time.sleep(0.1) - app.processEvents() - splash.showMessage("Loading...") - - w=Window() - if(ss): - splash.finish(w) - - sys.exit(app.exec_()) +import gui if(__name__=="__main__"): - main() + gui.main() diff --git a/_differentials.py b/_differentials.py deleted file mode 100644 index 16f86d3..0000000 --- a/_differentials.py +++ /dev/null @@ -1,47 +0,0 @@ -# DDStorm -# ------- -# Copyright (c) 2015 Agnibho Mondal -# All rights reserved - -# This file is part of DDStorm. - -# DDStorm is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# DDStorm is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with DDStorm. If not, see . - -from PyQt4 import QtGui - -class Differentials(QtGui.QFrame): - data=[] - def __init__(self): - super(Differentials, self).__init__() - self.initUI() - def initUI(self): - self.label=QtGui.QLabel("Differential Diagnosis") - self.label.setStyleSheet("font-size:18px") - self.listWidget=QtGui.QListWidget(self) - self.listWidget.setStyleSheet("font-size:14px") - self.listWidget.setSelectionMode(0) - box=QtGui.QVBoxLayout() - box.addWidget(self.label) - box.addWidget(self.listWidget) - self.setLayout(box) - - def update(self, data): - self.data=data - self.listWidget.clear() - if(self.data): - for d in self.data: - QtGui.QListWidgetItem(d, self.listWidget) - - def getList(self): - return self.data diff --git a/_extras.py b/_extras.py deleted file mode 100644 index d15e627..0000000 --- a/_extras.py +++ /dev/null @@ -1,163 +0,0 @@ -# DDStorm -# ------- -# Copyright (c) 2015 Agnibho Mondal -# All rights reserved - -# This file is part of DDStorm. - -# DDStorm is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# DDStorm is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with DDStorm. If not, see . - -import subprocess, os -from PyQt4 import QtGui, QtCore -from const import * - -def x_settings(): - subprocess.Popen(["xdg-open", CONF_FILE]) - -def x_lib(): - if(os.path.isfile(CONF_FILE)): - with open(CONF_FILE) as conf: - for line in conf: - if(line.startswith("library_path=")): - library_path=line[13:-1] - if(os.path.isdir(library_path)): - subprocess.Popen(["xdg-open", library_path]) - -def x_save(w, symp, diff): - fname=QtGui.QFileDialog.getSaveFileName(w, "Save File", "~", "HTML files('*.html')") - if(not fname.endswith(".html")): - fname=fname+".html" - with open(fname, "w") as f: - print("Differential Diagnosis", file=f) - print("

Differential Diagnosis

", file=f) - print("", file=f) - print("", file=f) - print("
SymptomsDiffrential Diagnosis
    ", file=f) - for s in symp: - print("
  1. "+s+"
  2. ", file=f) - print("
    ", file=f) - for d in diff: - print("
  1. "+d+"
  2. ", file=f) - print("
", file=f) - -def x_help(): - if(os.path.isfile(HELP_FILE)): - subprocess.Popen(["xdg-open", HELP_FILE]) - else: - subprocess.Popen(["xdg-open", "http://www.agnibho.com"]) - -def x_logfile(): - subprocess.Popen(["xdg-open", LOG_FILE]) - -class SettingsDialog(QtGui.QDialog): - def __init__(self, conf): - super(SettingsDialog, self).__init__() - self.setWindowTitle("Settings") - self.conf=conf - self.initUI() - def initUI(self): - self.lpLabel=QtGui.QLabel("Libary Path:") - self.lpEdit=QtGui.QLineEdit(self.conf.get("library_path")) - self.lpBrowse=QtGui.QPushButton("Browse") - self.lpBrowse.clicked.connect(self.lpUpdate) - self.mpLabel=QtGui.QLabel("Module Path:") - self.mpEdit=QtGui.QLineEdit(self.conf.get("module_path")) - self.mpBrowse=QtGui.QPushButton("Browse") - self.mpBrowse.clicked.connect(self.mpUpdate) - self.splash=QtGui.QCheckBox("Show Splash Screen") - if(self.conf.get("splash_screen")=="yes"): - self.splash.setChecked(True) - self.clean=QtGui.QCheckBox("Clean Log on Exit") - if(self.conf.get("clean_log")=="yes"): - self.clean.setChecked(True) - self.status=QtGui.QCheckBox("Show Status Message") - if(self.conf.get("status_message")=="on"): - self.status.setChecked(True) - self.ok=QtGui.QPushButton("Ok") - self.ok.clicked.connect(self.save) - self.cancel=QtGui.QPushButton("Cancel") - self.cancel.clicked.connect(self.close) - self.default=QtGui.QPushButton("Default") - self.default.clicked.connect(self.reset) - - ctrl=QtGui.QHBoxLayout() - ctrl.addWidget(self.ok) - ctrl.addWidget(self.cancel) - ctrl.addWidget(self.default) - layout=QtGui.QGridLayout(self) - layout.addWidget(self.lpLabel, 0, 0) - layout.addWidget(self.lpEdit, 0, 1) - layout.addWidget(self.lpBrowse, 0, 2) - layout.addWidget(self.mpLabel, 1, 0) - layout.addWidget(self.mpEdit, 1, 1) - layout.addWidget(self.mpBrowse, 1, 2) - layout.addWidget(self.splash, 2, 0) - layout.addWidget(self.clean, 3, 0) - layout.addWidget(self.status, 4, 0) - layout.addLayout(ctrl, 5, 1) - - self.cancel.setFocus() - - def lpUpdate(self): - self.lpEdit.setText(self.getFolder()) - def cpUpdate(self): - self.cpEdit.setText(self.getFolder()) - def mpUpdate(self): - self.mpEdit.setText(self.getFolder()) - - def getFolder(self): - dn=QtGui.QFileDialog.getExistingDirectory() - if(dn.startswith(QtCore.QDir.currentPath())): - dn="."+dn[len(QtCore.QDir.currentPath()):]+"/" - else: - dn=dn+"/" - return dn - - def save(self): - self.conf.set("library_path", self.lpEdit.text()) - self.conf.set("class_path", self.cpEdit.text()) - self.conf.set("module_path", self.mpEdit.text()) - if(self.splash.isChecked()): - self.conf.set("splash_screen", "yes") - else: - self.conf.set("splash_screen", "no") - if(self.clean.isChecked()): - self.conf.set("clean_log", "yes") - else: - self.conf.set("clean_log", "no") - if(self.status.isChecked()): - self.conf.set("status_message", "on") - else: - self.conf.set("status_message", "off") - QtGui.QMessageBox.information(self, "Restart required", "Some settings takes effect only after restarting DDStorm") - self.close() - self.conf.write() - - def reset(self): - self.conf.default() - self.lpEdit.setText(self.conf.get("library_path")) - self.cpEdit.setText(self.conf.get("class_path")) - self.mpEdit.setText(self.conf.get("module_path")) - if(self.conf.get("splash_screen")=="yes"): - self.splash.setChecked(True) - else: - self.splash.setChecked(False) - if(self.conf.get("clean_log")=="yes"): - self.clean.setChecked(True) - else: - self.clean.setChecked(False) - if(self.conf.get("status_message")=="on"): - self.status.setChecked(True) - else: - self.status.setChecked(False) diff --git a/_symptoms.py b/_symptoms.py deleted file mode 100644 index 2d7d8d6..0000000 --- a/_symptoms.py +++ /dev/null @@ -1,139 +0,0 @@ -# DDStorm -# ------- -# Copyright (c) 2015 Agnibho Mondal -# All rights reserved - -# This file is part of DDStorm. - -# DDStorm is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# DDStorm is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with DDStorm. If not, see . - -from PyQt4 import QtGui, QtCore - -class Symptoms(QtGui.QFrame): - sympList=[] - changed=QtCore.pyqtSignal(list) - def __init__(self, auto): - super(Symptoms, self).__init__() - self.auto=auto - self.initUI() - - def initUI(self): - self.label=QtGui.QLabel("Symptoms") - self.label.setStyleSheet("font-size:18px") - - self.listWidget=QtGui.QListWidget(self) - self.listWidget.setStyleSheet("font-size:14px") - self.listWidget.setSelectionMode(4) - - self.rm=QtGui.QPushButton("Remove") - self.rm.clicked.connect(self.remove) - - self.cl=QtGui.QPushButton("Clear All") - self.cl.clicked.connect(self.removeAll) - - self.browse=QtGui.QPushButton("Browse Symptoms") - self.browse.clicked.connect(self.browseSymptoms) - - self.add=QtGui.QPushButton("Add") - self.add.clicked.connect(self.addItem) - - self.new=QtGui.QLineEdit(self) - self.new.returnPressed.connect(self.addItem) - self.completer=QtGui.QCompleter(self.auto) - self.completer.setCaseSensitivity(0) - self.completer.setCompletionMode(2) - self.new.setCompleter(self.completer) - - self.browser=SymptomBrowser(self.auto) - self.browser.added.connect(self.addItem) - - hboxt=QtGui.QHBoxLayout() - hboxt.addWidget(self.new) - hboxt.addWidget(self.add) - hboxb=QtGui.QHBoxLayout() - hboxb.addWidget(self.rm) - hboxb.addWidget(self.cl) - hboxb.addWidget(self.browse) - vbox=QtGui.QVBoxLayout() - vbox.addWidget(self.label) - vbox.addLayout(hboxt) - vbox.addWidget(self.listWidget) - vbox.addLayout(hboxb) - self.setLayout(vbox) - - def addItem(self, text=""): - if(not text): - text=self.new.text() - if(len(text)>0): - if(text in self.sympList): - QtGui.QMessageBox.information(self, "Duplicate Symptom", "'"+text+"' has already been added to the symptom list.") - elif(text in self.auto): - QtGui.QListWidgetItem(text, self.listWidget) - self.sympList.append(text) - self.new.clear() - self.changed.emit(list(self.sympList)) - else: - QtGui.QMessageBox.warning(self, "Symptom Unvailable", "'"+text+"' is not available in the current Library.") - - def remove(self, all=False): - if(len(self.listWidget.selectedItems())>0): - for item in self.listWidget.selectedItems(): - self.sympList.remove(item.text()) - self.listWidget.takeItem(self.listWidget.row(item)) - self.changed.emit(list(self.sympList)) - - def removeAll(self): - self.listWidget.clear() - del self.sympList[:] - self.changed.emit(list(self.sympList)) - - def browseSymptoms(self): - self.browser.exec_() - - def getList(self): - return self.sympList - -class SymptomBrowser(QtGui.QDialog): - added=QtCore.pyqtSignal(str) - def __init__(self, items): - super(SymptomBrowser, self).__init__() - self.setWindowTitle("Choose Symptom") - self.items=items - self.initUI() - - def initUI(self): - self.search=QtGui.QLineEdit() - self.search.textChanged.connect(self.refresh) - self.listItems=QtGui.QListWidget() - self.listItems.activated.connect(self.sendUp) - - layout=QtGui.QVBoxLayout(self) - layout.addWidget(self.search) - layout.addWidget(self.listItems) - - self.listItems.addItems(self.items) - self.search.setFocus() - - def refresh(self): - term=self.search.text() - buff=[] - for i in self.items: - if(term.lower() in i.lower()): - buff.append(i) - self.listItems.clear() - self.listItems.addItems(buff) - - def sendUp(self): - self.added.emit(self.listItems.currentItem().text()) - self.close() diff --git a/alias.py b/alias.py index d4e013e..a1b9d10 100644 --- a/alias.py +++ b/alias.py @@ -1,32 +1,41 @@ #! /usr/bin/python3 -# DDStorm -# ------- -# Copyright (c) 2015 Agnibho Mondal -# All rights reserved +''' This module handles the aliases of the symptoms. ''' +''' +Copyright (c) 2015 Agnibho Mondal +All rights reserved -# This file is part of DDStorm. +This file is part of DDStorm. -# DDStorm is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +DDStorm is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. -# DDStorm is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +DDStorm is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with DDStorm. If not, see . +You should have received a copy of the GNU General Public License +along with DDStorm. If not, see . +''' -import sys, os +import sys +import os from fnmatch import fnmatch + from conf import Conf from const import * class Alias: + ''' Provides the class to handle symptom aliases ''' + def __init__(self, conf=False): + ''' + Initiates the alias object + Accepts a Conf object as parameter + ''' self.data={} if(conf): self.conf=conf @@ -35,19 +44,25 @@ class Alias: self.compile() def compile(self): + ''' Compile the plaintext index files to a program usable format ''' + # Loop over the alias files for path, subdirs, files in os.walk(self.conf.get("alias_path")): for name in files: if(fnmatch(name, "*.txt")): + # Open the *.txt files with open(self.conf.get("alias_path")+name, "r") as f: for line in f: + # Ignore lines starting with # line=line.rstrip().split("#")[0] if(len(line)==0): pass else: terms=[] + # Split words separated by ; and add to the terms for i in line.split(";"): if(i.strip()): terms.append(i.strip()) + # If alias present, add terms to the data if(len(terms)==2): self.data[terms[-1]]=terms[0] elif(len(terms)>2): @@ -56,12 +71,22 @@ class Alias: self.data[i]=t def get(self, term): + ''' + Return the alias of the queried symptom + + Parameter: + term - Queried string + + Return value: + String containing the alias of the term + ''' if(term in self.data): return self.data[term] else: return term def main(): + ''' Print the alias of the command line argument ''' a=Alias() if(len(sys.argv)>1): print(a.get(sys.argv[1])) diff --git a/compile.py b/compile.py index acb5ec7..80bb4d1 100644 --- a/compile.py +++ b/compile.py @@ -57,41 +57,57 @@ class Compile: self._conf=conf else: self._conf=Conf() + self.clean=True def compile(self): ''' Compile the text files to DDStorm modules. ''' self.source=set() self.custom=set() self.alias=Alias(self._conf) - self.clean=True + + # Loop over library files and add *.txt files to source for path, subdirs, files in os.walk(self._conf.get("library_path")): for name in files: if(fnmatch(name, "*.txt")): self.source.add(os.path.join(path, name)) + + # Loop over custom files and add *.txt files to custom for path, subdirs, files in os.walk(self._conf.get("custom_path")): for name in files: if(fnmatch(name, "*.txt")): self.custom.add(os.path.join(path, name)) + + # Create module directory if not already present and delete all module files if(not os.path.isdir(self._conf.get("module_path"))): os.makedirs(self._conf.get("module_path")) for f in os.listdir(self._conf.get("module_path")): if(fnmatch(f, "*.module")): os.unlink(self._conf.get("module_path")+f) + + # Create a regex for calculating priority from filename self.priorityRegex=re.compile("(?<=\.)\d+$") + + # First sort files by priority then compile them to module for src in self._sortPriority(self.source): self._makeModule(src) for src in self._sortPriority(self.custom): self._makeModule(src) def _sortPriority(self, files): + ''' Sort data files based on their priority settings. ''' ls=[] + # Loop over the files for addr in files: + # Format the file name name=os.path.splitext(os.path.basename(addr))[0].lower().replace("_"," ").replace("-", " ") + # Search for priority tag on file name m=re.search(self.priorityRegex, name) + # Add to ls as (symptom name, priority number, file name) with default priority of 100 if(m): ls.append((name.replace("."+m.group(), ""), int(m.group()), addr)) else: ls.append((name, 100, addr)) + # Sort the file list, first by the symptom name, then by the priority number ls.sort(reverse=True) if(ls): return(list(zip(*ls))[2]) @@ -99,13 +115,19 @@ class Compile: return ls def _makeModule(self, src): + ''' Create application usable modules from data files. ''' + # Format the file name module=os.path.splitext(os.path.basename(src))[0].lower().replace("_"," ").replace("-", " ") + # Remove the priority tag from file name m=re.search(self.priorityRegex, module) if(m): module=module.replace("."+m.group(), "") + # Create the module file name modFile=self._conf.get("module_path")+module+".module" modFlag=False + # Loop over both files, the source data file and the target module file with open(src, "r") as sf, open(modFile, "a") as tf: + # Ignore lines starting with ! or #, + and - has special meaning, write other lines to module. Log the errors. for line in sf: line=line.strip().split("#")[0] if(len(line)==0): @@ -123,17 +145,20 @@ class Compile: else: self.clean=False logging.warning("Syntax error in file '"+src+"': "+line) + # Deal with special lines if(modFlag): modFlag=False with open(src, "r") as f: for line in f: line=line.strip().split("#")[0] if(line[1:].replace(" ","").replace("-","").replace("_","").replace("'","").isalnum()): + # If line starts with + add it to the module file if(line.startswith("+")): with open(modFile, "r") as fn: text=fn.read() with open(modFile, "w") as fn: print(self.alias.get(line[1:]).capitalize()+"\n"+text, file=fn) + # If line starts with - remove corresponding item from the module file elif(line.startswith("-")): with open(modFile, "r") as fn: text=fn.read() @@ -141,7 +166,12 @@ class Compile: with open(modFile, "w") as fn: print(text, file=fn) + def is_clean(self): + '''Report if compilation ended successfully''' + return self.clean + def main(): + ''' Compile the data files into formatted module files ''' c=Compile().compile() if(__name__=="__main__"): diff --git a/conf.py b/conf.py index 5b84851..03ee5e8 100644 --- a/conf.py +++ b/conf.py @@ -25,7 +25,7 @@ along with DDStorm. If not, see . import os import logging -from const import * # Import constants +from const import * logging.basicConfig(filename=LOG_FILE) @@ -40,7 +40,7 @@ class Conf: function calls. ''' - _conf={} #Initiates configuration dictionary + _conf={} def __init__(self, filename=CONF_FILE): ''' @@ -65,47 +65,59 @@ class Conf: def read(self): ''' Read the configuration file and collect the values ''' - if(os.path.isfile(self.filename)): # If file is actually present + # If file is actually present + if(os.path.isfile(self.filename)): try: - with open(self.filename) as f: # Open file + with open(self.filename) as f: for line in f: - line="".join(line.split()) # Removes any stray whitespaces - if(line.startswith("#")): # Ignores comments + # Removes any stray whitespaces + line="".join(line.split()) + # Ignores comments starting with # + if(line.startswith("#")): pass - elif(line.startswith("library_path=")): # Library files path + # Library path + elif(line.startswith("library_path=")): self._conf["library_path"]=line[13:] if(os.path.isdir(self._conf["library_path"])): if(not self._conf["library_path"].endswith("/")): self._conf["library_path"]+="/" - elif(line.startswith("custom_path=")): # Custom files path + # Custom path + elif(line.startswith("custom_path=")): self._conf["custom_path"]=line[12:] if(os.path.isdir(self._conf["custom_path"])): if(not self._conf["custom_path"].endswith("/")): self._conf["custom_path"]+="/" - elif(line.startswith("index_path=")): # Index files path + # Index path + elif(line.startswith("index_path=")): self._conf["index_path"]=line[11:] if(os.path.isdir(self._conf["index_path"])): if(not self._conf["index_path"].endswith("/")): self._conf["index_path"]+="/" - elif(line.startswith("alias_path=")): # Alias files path + # Alias path + elif(line.startswith("alias_path=")): self._conf["alias_path"]=line[11:] if(os.path.isdir(self._conf["alias_path"])): if(not self._conf["alias_path"].endswith("/")): self._conf["alias_path"]+="/" - elif(line.startswith("module_path=")): # Path to save compiled modules + # Module path + elif(line.startswith("module_path=")): self._conf["module_path"]=line[12:] if(os.path.isdir(self._conf["module_path"])): if(not self._conf["module_path"].endswith("/")): self._conf["module_path"]+="/" - elif(line.startswith("splash_screen=")): # Whether to show a splash screen + # Splash screen + elif(line.startswith("splash_screen=")): self._conf["splash_screen"]=line[14:] - elif(line.startswith("clean_log=")): # Whether to clean logs before exit + # Clean log + elif(line.startswith("clean_log=")): self._conf["clean_log"]=line[10:] - elif(line.startswith("status_message=")): # Whether to show status messages + # Status message + elif(line.startswith("status_message=")): self._conf["status_message"]=line[15:] + # Unknown option else: - logging.warning("Unrecognized configuration: "+line) # Log a warning if unrecognized option found - except: # Go with default if file could not be read and log an error + logging.warning("Unrecognized configuration: "+line) + except: logging.error("Configuration file "+self.filename+" could not be read. Using default configurations.") def get(self, key=False): diff --git a/ddstorm.py b/ddstorm.py index 7fb300a..2b6664c 100644 --- a/ddstorm.py +++ b/ddstorm.py @@ -1,53 +1,94 @@ #! /usr/bin/python3 -# DDStorm -# ------- -# Copyright (c) 2015 Agnibho Mondal -# All rights reserved +''' +DDStorm is a python application for finding differential diagnosis for +a given list of symptoms. +''' +''' +Copyright (c) 2015 Agnibho Mondal +All rights reserved -# This file is part of DDStorm. +This file is part of DDStorm. -# DDStorm is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +DDStorm is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. -# DDStorm is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +DDStorm is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with DDStorm. If not, see . +You should have received a copy of the GNU General Public License +along with DDStorm. If not, see . +''' + +import sys +import os -import sys, os from compile import Compile from conf import Conf from index import Index class DDStorm: + ''' Provides the class for finding differential diagnosis. ''' conf=False + def __init__(self, comp=False, conf=False): + ''' + Initiate the diagnosis finder. + + Parameters: + comp - Recompiles the data files if set to True + conf - Supply a Conf object + ''' if(conf): self.conf=conf else: self.conf=Conf() + self.compiler=Compile(conf) if(comp): - self.compiler=Compile(conf).compile() + self.compiler.compile() self.index=Index(conf) def dd(self, symptoms): + ''' + Find the differential diagnosis list. + + Parameter: + symptom - list of strings containing symptoms + + Return value: + List of strings containing the differential diagnosis + ''' + + # Return empty list if symptom list is empty if(not symptoms): return + + # Find DD of first symptom and discard it diff1=self._getDiff(symptoms.pop(0)) + + # Loop through the rest of the list for s in symptoms: + + # Find DD of the current item in the list diff2=self._getDiff(s) + + # List for temporary holding the DDs temp=[] + + # Make both lists the same length by appending empty strings to the end if(len(diff1)>len(diff2)): diff2+=[""]*(len(diff1)-len(diff2)) elif(len(diff2)>len(diff1)): diff1+=[""]*(len(diff2)-len(diff1)) + + # Loop over both lists for (s1, s2) in zip(diff1, diff2): + + # Add s1 to temp if s1 or any of its upstream ancestor is common to both list if((s1 not in temp) and (len(s1)>0)): if(s1 in diff2): temp.append(s1) @@ -56,6 +97,8 @@ class DDStorm: for i in us: if(i in diff2): temp.append(i) + + # Add s2 to temp if s2 or any of its upstream ancestor is common to both list if((s2 not in temp) and (len(s2)>0)): if(s2 in diff1): temp.append(s2) @@ -64,10 +107,14 @@ class DDStorm: for i in us: if(i in diff1): temp.append(i) + + # Copy temp to first list diff1=list(temp) + return diff1 def _getDiff(self, symptom): + ''' Return differential diagnosis for a single symptom ''' diff=[] symptom=symptom.lower().replace("_"," ").replace("-", " ") if(os.path.isfile(self.conf.get("module_path")+symptom+".module")): @@ -77,12 +124,26 @@ class DDStorm: return diff def symptoms(self): + ''' + Return a full list of available symptoms + + Return value: + List of string containing symptoms + ''' symp=[] for n in os.listdir(self.conf.get("module_path")): symp.append(os.path.splitext(os.path.basename(n))[0].capitalize()) return symp def main(): + ''' + Find differential diagnosis in command line mode. + Accepts symptoms as command line arguments. Prints a list of + avialable symptoms if called without any argument. + + Command line arguments: + A list of symptoms separated by space + ''' s=DDStorm() if(len(sys.argv)>1): for d in s.dd(sys.argv[1:]): diff --git a/extras.py b/extras.py new file mode 100644 index 0000000..8021012 --- /dev/null +++ b/extras.py @@ -0,0 +1,196 @@ +''' +This module provides some extra functionalities to the main window. +''' +''' +Copyright (c) 2015 Agnibho Mondal +All rights reserved + +This file is part of DDStorm. + +DDStorm is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +DDStorm is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with DDStorm. If not, see . +''' + +import subprocess +import os +from PyQt4 import QtGui, QtCore +from const import * + +def x_settings(): + ''' Open the configuration file ''' + subprocess.Popen(["xdg-open", CONF_FILE]) + +def x_lib(): + ''' Open the library path ''' + if(os.path.isfile(CONF_FILE)): + with open(CONF_FILE) as conf: + for line in conf: + if(line.startswith("library_path=")): + library_path=line[13:-1] + if(os.path.isdir(library_path)): + subprocess.Popen(["xdg-open", library_path]) + +def x_save(w, symp, diff): + ''' Save data to a file ''' + fname=QtGui.QFileDialog.getSaveFileName(w, "Save File", "~", "HTML files('*.html')") + if(not fname.endswith(".html")): + fname=fname+".html" + with open(fname, "w") as f: + print("Differential Diagnosis", file=f) + print("

Differential Diagnosis

", file=f) + print("", file=f) + print("", file=f) + print("
SymptomsDiffrential Diagnosis
    ", file=f) + for s in symp: + print("
  1. "+s+"
  2. ", file=f) + print("
    ", file=f) + for d in diff: + print("
  1. "+d+"
  2. ", file=f) + print("
", file=f) + +def x_help(): + ''' Show help ''' + if(os.path.isfile(HELP_FILE)): + subprocess.Popen(["xdg-open", HELP_FILE]) + else: + subprocess.Popen(["xdg-open", "http://www.agnibho.com"]) + +def x_logfile(): + ''' Open log file ''' + subprocess.Popen(["xdg-open", LOG_FILE]) + + +class SettingsDialog(QtGui.QDialog): + ''' + Provides a dialog box to configure application settings with a + graphical user interface. + ''' + + def __init__(self, conf): + ''' Initiate the dialog ''' + super(SettingsDialog, self).__init__() + self.setWindowTitle("Settings") + self.conf=conf + self.initUI() + + def initUI(self): + ''' Initiate the user interface ''' + self.lpLabel=QtGui.QLabel("Libary Path:") + self.lpEdit=QtGui.QLineEdit(self.conf.get("library_path")) + self.lpBrowse=QtGui.QPushButton("Browse") + self.lpBrowse.clicked.connect(self.lpUpdate) + self.cpLabel=QtGui.QLabel("Custom Path:") + self.cpEdit=QtGui.QLineEdit(self.conf.get("custom_path")) + self.cpBrowse=QtGui.QPushButton("Browse") + self.cpBrowse.clicked.connect(self.cpUpdate) + self.mpLabel=QtGui.QLabel("Module Path:") + self.mpEdit=QtGui.QLineEdit(self.conf.get("module_path")) + self.mpBrowse=QtGui.QPushButton("Browse") + self.mpBrowse.clicked.connect(self.mpUpdate) + self.splash=QtGui.QCheckBox("Show Splash Screen") + if(self.conf.get("splash_screen")=="yes"): + self.splash.setChecked(True) + self.clean=QtGui.QCheckBox("Clean Log on Exit") + if(self.conf.get("clean_log")=="yes"): + self.clean.setChecked(True) + self.status=QtGui.QCheckBox("Show Status Message") + if(self.conf.get("status_message")=="on"): + self.status.setChecked(True) + self.ok=QtGui.QPushButton("Ok") + self.ok.clicked.connect(self.save) + self.cancel=QtGui.QPushButton("Cancel") + self.cancel.clicked.connect(self.close) + self.default=QtGui.QPushButton("Default") + self.default.clicked.connect(self.reset) + + ctrl=QtGui.QHBoxLayout() + ctrl.addWidget(self.ok) + ctrl.addWidget(self.cancel) + ctrl.addWidget(self.default) + layout=QtGui.QGridLayout(self) + layout.addWidget(self.lpLabel, 0, 0) + layout.addWidget(self.lpEdit, 0, 1) + layout.addWidget(self.lpBrowse, 0, 2) + layout.addWidget(self.cpLabel, 1, 0) + layout.addWidget(self.cpEdit, 1, 1) + layout.addWidget(self.cpBrowse, 1, 2) + layout.addWidget(self.mpLabel, 2, 0) + layout.addWidget(self.mpEdit, 2, 1) + layout.addWidget(self.mpBrowse, 2, 2) + layout.addWidget(self.splash, 3, 0) + layout.addWidget(self.clean, 4, 0) + layout.addWidget(self.status, 5, 0) + layout.addLayout(ctrl, 6, 1) + + self.cancel.setFocus() + + def lpUpdate(self): + ''' Updates the library path ''' + self.lpEdit.setText(self.getFolder()) + + def cpUpdate(self): + ''' Updates the custom path ''' + self.cpEdit.setText(self.getFolder()) + + def mpUpdate(self): + ''' Updates the module path ''' + self.mpEdit.setText(self.getFolder()) + + def getFolder(self): + ''' Returns the selected directory ''' + dn=QtGui.QFileDialog.getExistingDirectory() + if(dn.startswith(QtCore.QDir.currentPath())): + dn="."+dn[len(QtCore.QDir.currentPath()):]+"/" + else: + dn=dn+"/" + return dn + + def save(self): + ''' Saves the configuration to disk ''' + self.conf.set("library_path", self.lpEdit.text()) + self.conf.set("custom_path", self.cpEdit.text()) + self.conf.set("module_path", self.mpEdit.text()) + if(self.splash.isChecked()): + self.conf.set("splash_screen", "yes") + else: + self.conf.set("splash_screen", "no") + if(self.clean.isChecked()): + self.conf.set("clean_log", "yes") + else: + self.conf.set("clean_log", "no") + if(self.status.isChecked()): + self.conf.set("status_message", "on") + else: + self.conf.set("status_message", "off") + QtGui.QMessageBox.information(self, "Restart required", "Some settings takes effect only after restarting DDStorm") + self.close() + self.conf.write() + + def reset(self): + ''' Resests the settings to the default factory value ''' + self.conf.default() + self.lpEdit.setText(self.conf.get("library_path")) + self.cpEdit.setText(self.conf.get("class_path")) + self.mpEdit.setText(self.conf.get("module_path")) + if(self.conf.get("splash_screen")=="yes"): + self.splash.setChecked(True) + else: + self.splash.setChecked(False) + if(self.conf.get("clean_log")=="yes"): + self.clean.setChecked(True) + else: + self.clean.setChecked(False) + if(self.conf.get("status_message")=="on"): + self.status.setChecked(True) + else: + self.status.setChecked(False) diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..55320cb --- /dev/null +++ b/gui.py @@ -0,0 +1,192 @@ +#! /usr/bin/python3 + +''' +This module provides the main graphical user interface for DDStorm +''' +''' +Copyright (c) 2015 Agnibho Mondal +All rights reserved + +This file is part of DDStorm. + +DDStorm is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +DDStorm is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with DDStorm. If not, see . +''' + +import sys +import time +import subprocess + +from PyQt4 import QtGui, QtCore + +from panes import Symptoms, Differentials +from ddstorm import DDStorm +from conf import Conf +from extras import * +from const import * + +conf=False + +class Content(QtGui.QWidget): + ''' + Provides the main content widget. Contains the sysmptoms and + the diagnosis panes. Also creates the DDStorm object and performs + the main operation. + ''' + + # Signal to detect when + change=QtCore.pyqtSignal() + + def __init__(self): + ''' Initiate the content widget ''' + super(Content, self).__init__() + + # Create DDStorm object with the global configuration + self.dd=DDStorm(True, conf) + + # Show warning if any error happened during data compilation + if(not self.dd.compiler.is_clean()): + ret=QtGui.QMessageBox.warning(self, "Compilation Error", "Error was encountered while compiling the Knowledgebase.", "Ignore", "View Log") + if(ret==1): + x_logfile() + + self.initUI() + + def initUI(self): + ''' Create the user interface of the widget ''' + + global conf + + grid=QtGui.QGridLayout() + self.setLayout(grid) + + self.symp=Symptoms(self.dd.symptoms()) + self.symp.setFrameShape(QtGui.QFrame.StyledPanel) + self.symp.changed.connect(self.update) + + self.diff=Differentials() + self.diff.setFrameShape(QtGui.QFrame.StyledPanel) + + grid.addWidget(self.symp, 0, 0) + grid.addWidget(self.diff, 0, 1) + grid.setColumnStretch(0, 1) + grid.setColumnStretch(1, 1) + QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("Cleanlooks")) + + def update(self, data): + ''' Update the inteface with refreshed information ''' + self.diff.update(self.dd.dd(data)) + self.change.emit() + + +class Window(QtGui.QMainWindow): + ''' + Provides main application window. Acts as a container for the + content widget. Also contains the menubar and the status bar. + ''' + + def __init__(self): + ''' Initiate the main window ''' + super(Window, self).__init__() + self.initUI() + + def initUI(self): + ''' Create the user interface ''' + global conf + self.con=Content() + self.sett=SettingsDialog(conf) + if(conf.get("status_message")=="on"): + self.con.change.connect(self.showStatus) + + menu=self.menuBar() + menuFile=menu.addMenu("&File") + menuFile.addAction("&Save").triggered.connect(self.savefile) + menuFile.addAction("E&xit").triggered.connect(self.close) + menuEdit=menu.addMenu("&Edit") + menuEdit.addAction("&Add").triggered.connect(self.con.symp.addItem) + menuEdit.addAction("&Browse Symptoms").triggered.connect(self.con.symp.browseSymptoms) + rmAction=QtGui.QAction("&Remove", self) + rmAction.setShortcut("Delete") + rmAction.triggered.connect(self.con.symp.remove) + menuEdit.addAction(rmAction) + menuEdit.addAction("&Clear All").triggered.connect(self.con.symp.removeAll) + menuTool=menu.addMenu("&Tools") + menuTool.addAction("&Library").triggered.connect(x_lib) + menuTool.addAction("&Settings").triggered.connect(self.settings) + menuTool.addAction("&View Log").triggered.connect(x_logfile) + menuHelp=menu.addMenu("&Help") + menuHelp.addAction("&Help").triggered.connect(x_help) + menuHelp.addAction("&About").triggered.connect(self.about) + + self.setCentralWidget(self.con) + self.status=self.statusBar() + self.setGeometry(200, 200, 600, 400) + self.setWindowTitle("D/D Storm") + self.setWindowIcon(QtGui.QIcon("icons/icon.png")) + self.showMaximized() + self.con.symp.new.setFocus() + + def showStatus(self): + ''' Show status message ''' + if(self.con.symp.getList() and self.con.diff.getList()): + self.status.showMessage(str(len(self.con.diff.getList()))+" differential diagnosis for "+str(len(self.con.symp.getList()))+" symptom(s).") + else: + self.status.showMessage("") + + def savefile(self): + ''' Save data to a file ''' + x_save(self, self.con.symp.getList(), self.con.diff.getList()) + + def settings(self): + ''' Open the settings dialog ''' + self.sett.exec_() + + def about(self): + ''' Show information about this application ''' + QtGui.QMessageBox.about(self, "About", "

DDStorm

\nBrainstorm Medicine") + + +def main(): + ''' Start the main application interface ''' + app=QtGui.QApplication(sys.argv) + + # Initiate the global configuration + global conf + conf=Conf() + + # Clean the log file + if(conf.get("clean_log")=="yes"): + open(LOG_FILE, "w").close() + + # Show splash-screen + if(conf.get("splash_screen")=="yes"): + ss=True + else: + ss=False + if(ss): + splash=QtGui.QSplashScreen(QtGui.QPixmap("icons/splash.png")) + splash.show() + time.sleep(0.1) + app.processEvents() + splash.showMessage("Loading...") + + # Create main window + w=Window() + if(ss): + splash.finish(w) + + # Start application + sys.exit(app.exec_()) + +if(__name__=="__main__"): + main() diff --git a/index.py b/index.py index f1da749..d7e7b67 100644 --- a/index.py +++ b/index.py @@ -1,32 +1,51 @@ #! /usr/bin/python3 -# DDStorm -# ------- -# Copyright (c) 2015 Agnibho Mondal -# All rights reserved +''' +This module handles the upstream ancestors of symptoms. -# This file is part of DDStorm. +The symptoms can be classified in different levels, with the more +generalized symtoms at upstream and the more specialized symptoms at +the downstream. -# DDStorm is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +For example: +'Acute peritonitis' is a special type of 'peritonitis' +Hence 'peritionitis' will be at upstream and 'acute peritonitis' will be +at downstream. +''' +''' +Copyright (c) 2015 Agnibho Mondal +All rights reserved -# DDStorm is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +This file is part of DDStorm. -# You should have received a copy of the GNU General Public License -# along with DDStorm. If not, see . +DDStorm is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. -import sys, os +DDStorm is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with DDStorm. If not, see . +''' + +import sys +import os from fnmatch import fnmatch + from conf import Conf from const import * class Index: + ''' Provides an index of the upstream ancestors of symptoms ''' def __init__(self, conf=False): + ''' + Initiate the index object. Accepts a Conf object as an + optional parameter. + ''' if(conf): self.conf=conf else: @@ -35,55 +54,101 @@ class Index: self.compile() def compile(self): + ''' + Compile the text index files to a application usable format. + ''' + # Loop over all files under index_path for path, subdirs, files in os.walk(self.conf.get("index_path")): for name in files: + # Only files with *.txt suffix if(fnmatch(name, "*.txt")): with open(self.conf.get("index_path")+name, "r") as f: + # Two dimensional list buffer to hold the index buff=[] buff.append([]) buff.append([]) + # Loop over all lines as separate entries for line in f: + # Ignore commnents starting with # line=line.rstrip().split("#")[0] if(len(line)==0): pass else: + # Find number of leading whitespaces ws=len(line)-len(line.lstrip()) + # Format the entry line=line.lstrip().capitalize() + + # No leading whitespace means a top level entry i.e. no upstream ancestor if(ws==0): + # Empty the buffer and add the entry del buff[0][:] buff[0].append(line.lstrip()) + # Reset the whitespace index del buff[1][:] buff[1].append(0) + # If leading whitespace > indexed whitespace, the entry is a subcategory of previous entry elif(ws>buff[1][-1]): + # Append entry to buffer as new item buff[0].append(line.lstrip()) + # Record leading whitespace to buffer buff[1].append(ws) + # Copy buffer to data list self.data[buff[0][-1]]=list(reversed(buff[0])) + # If leading whitespace == indexed whitespace, the entry is at the same level with the previous entry elif(ws==buff[1][-1]): + # Append entry to last item of buffer buff[0][-1]=line.lstrip() buff[1][-1]=ws + # Copy buffer to data list self.data[buff[0][-1]]=list(reversed(buff[0])) + # If leading whitespace < indexed whitespace, the entry is at an upper category than the previous entry elif(ws=ws): buff[0].pop() buff[1].pop() + #Append the entry to buffer else: buff[0].append(line.lstrip()) buff[1].append(ws) break + # Copy buffer to data list self.data[buff[0][-1]]=list(reversed(buff[0])) def upstream(self, name): + ''' + Return the upstream list of a symptom + + Parameter: + name - the name of the symptom as string + + Return value: + List of strings containing the upstream items + ''' if(len(name)>0): if name in self.data: return self.data[name] else: return [] + def names(self): + ''' Return all indexed symptoms name ''' + return list(self.data.keys()) + def main(): + ''' + Prints upstream items of a symptom provided as command line + argument. + If no argument provided, returns a list of all indexed symptoms. + ''' i=Index() if(len(sys.argv)>1): print(i.upstream(sys.argv[1])) + else: + print(i.names()) if(__name__=="__main__"): main() diff --git a/panes.py b/panes.py new file mode 100644 index 0000000..79636a4 --- /dev/null +++ b/panes.py @@ -0,0 +1,214 @@ +''' +This module provides the application panes containing essential +functionalities. +''' +''' +Copyright (c) 2015 Agnibho Mondal +All rights reserved + +This file is part of DDStorm. + +DDStorm is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +DDStorm is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with DDStorm. If not, see . +''' + +from PyQt4 import QtGui, QtCore + +class Symptoms(QtGui.QFrame): + ''' Provides the widget for symptoms input ''' + + # List to hold the symptoms + sympList=[] + + # Signal to notify any change in input + changed=QtCore.pyqtSignal(list) + + def __init__(self, auto): + ''' + Initiate the symptom input pane. Takes a list of string as + argument. The list is used as the auto-complete list during + user input. + + Parameter: + auto - A list of string containing all available symptoms for + autocomplete. + ''' + super(Symptoms, self).__init__() + self.auto=auto + self.initUI() + + def initUI(self): + ''' Initiate the user interface ''' + self.label=QtGui.QLabel("Symptoms") + self.label.setStyleSheet("font-size:18px") + + self.listWidget=QtGui.QListWidget(self) + self.listWidget.setStyleSheet("font-size:14px") + self.listWidget.setSelectionMode(4) + + self.rm=QtGui.QPushButton("Remove") + self.rm.clicked.connect(self.remove) + + self.cl=QtGui.QPushButton("Clear All") + self.cl.clicked.connect(self.removeAll) + + self.browse=QtGui.QPushButton("Browse Symptoms") + self.browse.clicked.connect(self.browseSymptoms) + + self.add=QtGui.QPushButton("Add") + self.add.clicked.connect(self.addItem) + + self.new=QtGui.QLineEdit(self) + self.new.returnPressed.connect(self.addItem) + self.completer=QtGui.QCompleter(self.auto) + self.completer.setCaseSensitivity(0) + self.completer.setCompletionMode(2) + self.new.setCompleter(self.completer) + + self.browser=SymptomBrowser(self.auto) + self.browser.added.connect(self.addItem) + + hboxt=QtGui.QHBoxLayout() + hboxt.addWidget(self.new) + hboxt.addWidget(self.add) + hboxb=QtGui.QHBoxLayout() + hboxb.addWidget(self.rm) + hboxb.addWidget(self.cl) + hboxb.addWidget(self.browse) + vbox=QtGui.QVBoxLayout() + vbox.addWidget(self.label) + vbox.addLayout(hboxt) + vbox.addWidget(self.listWidget) + vbox.addLayout(hboxb) + self.setLayout(vbox) + + def addItem(self, text=""): + ''' Add a new symptom ''' + if(not text): + text=self.new.text() + if(len(text)>0): + if(text in self.sympList): + QtGui.QMessageBox.information(self, "Duplicate Symptom", "'"+text+"' has already been added to the symptom list.") + elif(text in self.auto): + QtGui.QListWidgetItem(text, self.listWidget) + self.sympList.append(text) + self.new.clear() + self.changed.emit(list(self.sympList)) + else: + QtGui.QMessageBox.warning(self, "Symptom Unvailable", "'"+text+"' is not available in the current Library.") + + def remove(self, all=False): + ''' Remove selected symptoms ''' + if(len(self.listWidget.selectedItems())>0): + for item in self.listWidget.selectedItems(): + self.sympList.remove(item.text()) + self.listWidget.takeItem(self.listWidget.row(item)) + self.changed.emit(list(self.sympList)) + + def removeAll(self): + ''' Clear all symptoms ''' + self.listWidget.clear() + del self.sympList[:] + self.changed.emit(list(self.sympList)) + + def browseSymptoms(self): + ''' Open the symptom browser ''' + self.browser.exec_() + + def getList(self): + ''' Return a list of all symptoms ''' + return self.sympList + + +class SymptomBrowser(QtGui.QDialog): + ''' + Provides a dialog with a list of symptoms for alternative user + input. + ''' + added=QtCore.pyqtSignal(str) + + def __init__(self, items): + ''' Initiate the input dialog ''' + super(SymptomBrowser, self).__init__() + self.setWindowTitle("Choose Symptom") + self.items=items + self.initUI() + + def initUI(self): + ''' Initiate the dialog interface ''' + self.search=QtGui.QLineEdit() + self.search.textChanged.connect(self.refresh) + self.listItems=QtGui.QListWidget() + self.listItems.activated.connect(self.sendUp) + + layout=QtGui.QVBoxLayout(self) + layout.addWidget(self.search) + layout.addWidget(self.listItems) + + self.listItems.addItems(self.items) + self.search.setFocus() + + def refresh(self): + ''' Refresh the symptom list based on search term ''' + term=self.search.text() + buff=[] + for i in self.items: + if(term.lower() in i.lower()): + buff.append(i) + self.listItems.clear() + self.listItems.addItems(buff) + + def sendUp(self): + ''' Emit signal and close when a symptom is selected ''' + self.added.emit(self.listItems.currentItem().text()) + self.close() + + +class Differentials(QtGui.QFrame): + ''' Provides the widget for differential diagnosis output ''' + data=[] + + def __init__(self): + ''' Initiate the diagnosis output pane ''' + super(Differentials, self).__init__() + self.initUI() + + def initUI(self): + ''' Initiate the user interface ''' + self.label=QtGui.QLabel("Differential Diagnosis") + self.label.setStyleSheet("font-size:18px") + self.listWidget=QtGui.QListWidget(self) + self.listWidget.setStyleSheet("font-size:14px") + self.listWidget.setSelectionMode(0) + box=QtGui.QVBoxLayout() + box.addWidget(self.label) + box.addWidget(self.listWidget) + self.setLayout(box) + + def update(self, data): + ''' + Update the outut pane with updated diagnosis list. + + Parameter: + data - List of strings containing new list of differential + diagnosis + ''' + self.data=data + self.listWidget.clear() + if(self.data): + for d in self.data: + QtGui.QListWidgetItem(d, self.listWidget) + + def getList(self): + ''' Return a list of current differential diagnosis ''' + return self.data