diff --git a/Work_Log_Generator/README.rst b/Work_Log_Generator/README.rst new file mode 100644 index 0000000..e33320e --- /dev/null +++ b/Work_Log_Generator/README.rst @@ -0,0 +1,46 @@ +`Work log generator` +-------------------- + +📝 C'est quoi? +=============== + +Ce simple script python permet de gĂ©nĂ©rer un journal de travail au format rst Ă  partir des commits de l'un de vos dĂ©pĂŽts. + +🔘 Comment l'utiliser? +======================= + +Il est nĂ©cessaire de commencer par gĂ©nĂ©rer un jeton d'accĂšs et de l'entrer dans le premier champ de la fenĂȘtre. + +Pour le gĂ©nĂ©rer, rendez-vous `ici`_ dans la section "Personal access tokens". + +À prĂ©sent, il est possible de presser sur le bouton de connexion. DĂšs lors, une liste dĂ©roulante affiche les dĂ©pĂŽts disponibles. + +AprĂšs le choix du dĂ©pĂŽt, le dernier bouton permet d'ouvrir le log, de le modifier et de le sauvegarder au format rst. + +Il est maintenant possible de le convertir en utilisant pandoc ou n'importe quelle autre convertisseur que vous prĂ©fĂ©rez. + +Enjoy! + + +📝 What is this? +================ + +This simple python script allows you to generate a worklog in rst format based on your repo commits. + +🔘 How to use it? +================= + +Simply generate a personnal access token and enter it in the first field of the window. + +In order to generate this token, go `here`_ under "Personal access tokens". + +Then, it is possible to press on the connection button. Since then a dropdown list display the available repositories. + +After choosing the repository, the last button allows you to open the log, edit it and save it in rst format. + +You can then convert it using pandoc or any other converter you prefer. + +Enjoy! + +.. _`ici`: https://github.com/settings/tokens +.. _`here`: https://github.com/settings/tokens diff --git a/Work_Log_Generator/requirements.txt b/Work_Log_Generator/requirements.txt new file mode 100644 index 0000000..b1b1575 --- /dev/null +++ b/Work_Log_Generator/requirements.txt @@ -0,0 +1,4 @@ +# WorkLog +pygithub +PyQt5 +qdarkstyle diff --git a/Work_Log_Generator/resources/icone.ico b/Work_Log_Generator/resources/icone.ico new file mode 100644 index 0000000..fe19ec6 Binary files /dev/null and b/Work_Log_Generator/resources/icone.ico differ diff --git a/Work_Log_Generator/resources/loader.gif b/Work_Log_Generator/resources/loader.gif new file mode 100644 index 0000000..e158443 Binary files /dev/null and b/Work_Log_Generator/resources/loader.gif differ diff --git a/Work_Log_Generator/resources/save.png b/Work_Log_Generator/resources/save.png new file mode 100644 index 0000000..14e787b Binary files /dev/null and b/Work_Log_Generator/resources/save.png differ diff --git a/Work_Log_Generator/work_log.py b/Work_Log_Generator/work_log.py new file mode 100644 index 0000000..01f2994 --- /dev/null +++ b/Work_Log_Generator/work_log.py @@ -0,0 +1,358 @@ +"""Get commits for the repo specified as parameter and write work log.""" +import os +import sys +from pathlib import Path + +import qdarkstyle +from github import Github +from github.GithubException import BadCredentialsException +from PyQt5 import Qt +from PyQt5 import QtCore +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtGui import QIcon +from PyQt5.QtGui import QMovie +from PyQt5.QtWidgets import QAction +from PyQt5.QtWidgets import QApplication +from PyQt5.QtWidgets import QComboBox +from PyQt5.QtWidgets import QFileDialog +from PyQt5.QtWidgets import QHBoxLayout +from PyQt5.QtWidgets import QLabel +from PyQt5.QtWidgets import QLineEdit +from PyQt5.QtWidgets import QMainWindow +from PyQt5.QtWidgets import QMessageBox +from PyQt5.QtWidgets import QPlainTextEdit +from PyQt5.QtWidgets import QPushButton +from PyQt5.QtWidgets import QVBoxLayout +from PyQt5.QtWidgets import QWidget + +sys.settrace +APP_NAME = 'Work Log Generator' +RESOURCES_FOLDER = 'resources/' +ICON_PATH = RESOURCES_FOLDER + 'icone.ico' +LOADER_PATH = RESOURCES_FOLDER + 'loader.gif' +SAVE_ICON_PATH = RESOURCES_FOLDER + 'save.png' + + +class WorkLogPreviewer(QMainWindow): + """ + Worklog previewer class. + + Extends QMainWindow + """ + + sig = pyqtSignal(str, name='update') + + def __init__(self, parent=None, repository=None, systemtray_icon=None): + """Init window.""" + super(WorkLogPreviewer, self).__init__(parent) + + saveAct = QAction(QIcon(SAVE_ICON_PATH), '&Save', self) + saveAct.setShortcut('Ctrl+S') + saveAct.setStatusTip('Save work log') + saveAct.triggered.connect(self.save) + + menubar = self.menuBar() + fileMenu = menubar.addMenu('&File') + fileMenu.addAction(saveAct) + + self.repository = repository + self.systemtray_icon = systemtray_icon + + self.statusBar() + + widget = QWidget() + layout = QVBoxLayout() + self.te = QPlainTextEdit() + layout.addWidget(self.te) + + self.lbl = QLabel() + self.lbl.hide() + self.movie = QMovie(LOADER_PATH) + self.lbl.setMovie(self.movie) + hlayout = QHBoxLayout() + hlayout.addStretch() + hlayout.addWidget(self.lbl) + hlayout.addStretch() + layout.addLayout(hlayout) + + self.generate_log() + + widget.setLayout(layout) + widget.setFixedSize(500, 500) + self.setCentralWidget(widget) + + self.setWindowTitle(f'Work log for {repository.full_name}') + self.setWindowIcon(QIcon(ICON_PATH)) + self.setLocale(QtCore.QLocale()) + self.adjustSize() + + self.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) + + def generate_log(self): + """Launch thread used to generate log.""" + self.lbl.show() + self.movie.start() + self.sig.connect(self.display_log) + self.workThread = WorkLogThread(self, self.repository, self.sig) + self.workThread.start() + + def display_log(self, log): + """Display log received from thread.""" + self.lbl.hide() + self.movie.stop() + self.te.setPlainText(log) + btMessage = "Log generation successfull!" + self.systemtray_icon.showMessage( + APP_NAME, btMessage, QIcon(ICON_PATH), 5000) + self.statusBar().showMessage(btMessage) + + def save(self): + """Save log to file.""" + home = str(Path.home()) + self.output = "" + self.output = QFileDialog.getSaveFileName(self, + 'Save file', + home, + "ReST files (*.rst)") + + if self.output[0] is not "": + with open(self.output[0], 'w') as f: + f.write(self.te.toPlainText()) + + btMessage = f'File saved as {self.output[0]}.' + else: + btMessage = "Can't save. No file specified." + self.systemtray_icon.showMessage( + APP_NAME, btMessage, QIcon(ICON_PATH), 5000) + self.statusBar().showMessage(btMessage) + + +class MainWidget(QWidget): + """ + Worklog Generator class. + + Extends QWidget + """ + + sig = pyqtSignal(list, name='update') + + def __init__(self, parent=None): + """Init widget.""" + super(MainWidget, self).__init__(parent) + + layout = QVBoxLayout() + + self.parent = parent + + self.ght = QLineEdit() + self.ght.setPlaceholderText("Github Token") + self.ght_keyPressEvent = self.ght.keyPressEvent + self.ght_original_style = self.ght.styleSheet() + self.ght.keyPressEvent = self.line_keyPressEvent + layout.addWidget(self.ght) + + self.btn = QPushButton("Connect to github") + self.btn.clicked.connect(self.getRepos) + layout.addWidget(self.btn) + + self.combo = QComboBox() + self.combo.setDisabled(True) + layout.addWidget(self.combo) + + self.btn_save = QPushButton("Open worklog") + self.btn_save.setDisabled(True) + self.btn_save.clicked.connect(self.open_log) + layout.addWidget(self.btn_save) + + self.lbl = QLabel() + self.lbl.hide() + self.movie = QMovie(LOADER_PATH) + self.lbl.setMovie(self.movie) + hlayout = QHBoxLayout() + hlayout.addStretch() + hlayout.addWidget(self.lbl) + hlayout.addStretch() + layout.addLayout(hlayout) + + self.setLayout(layout) + + self.setLocale(QtCore.QLocale()) + + self.sig.connect(self.add_repo_to_combobox) + + def line_keyPressEvent(self, event): + """Detect enter or return key pressed.""" + if (event.key() == QtCore.Qt.Key_Enter + or event.key() == QtCore.Qt.Key_Return): + self.getRepos() + else: + self.ght_keyPressEvent(event) + + def open_log(self): + """Build rst file from repository commits and opens it.""" + if self.parent is not None: + self.parent.statusBar().showMessage('Opening preview.') + + repo = self.combo.itemData(self.combo.currentIndex()) + + self.preview = WorkLogPreviewer(None, + repo, self.parent.systemtray_icon) + self.preview.show() + + if self.parent is not None: + self.parent.statusBar().showMessage('Done.') + + def getRepos(self): + """Get repos for user and add them in a combobox.""" + if self.ght.text() is not "": + if self.parent is not None: + self.parent.statusBar().showMessage('Fetching repos...') + + self.lbl.show() + self.movie.start() + self.ght.setStyleSheet(self.ght_original_style) + token = self.ght.text() + self.g = Github(token) + + try: + if self.workThread is not None and self.workThread.isRunning(): + return + except AttributeError: + pass + finally: + self.workThread = ConnectionThread(self, self.g, self.sig) + self.workThread.start() + + else: + self.ght.setStyleSheet("QLineEdit { border: 2px solid red; }") + + def add_repo_to_combobox(self, repo_list): + """Add repos from repo_list to combobox.""" + if len(repo_list) is not 0: + for repo in repo_list: + self.combo.addItem(repo.full_name, repo) + self.combo.setDisabled(False) + self.btn_save.setDisabled(False) + + self.lbl.hide() + self.movie.stop() + + +class WorkLogThread(QtCore.QThread): + """Thread used to write work log from repository.""" + + def __init__(self, parent, repository=None, sig=None): + """Init thread.""" + super(WorkLogThread, self).__init__(parent) + self.sig = sig + self.repository = repository + self.parent = parent + + def run(self): + """Run thread.""" + message = "Journal de travail" + restTructuredText = message + os.linesep + restTructuredText += '=' * len(message) + os.linesep * 2 + for commit in self.repository.get_commits(): + com = commit.commit + date = com.author.date + restTructuredText += str(date) + os.linesep + restTructuredText += '-' * len(str(date)) + os.linesep * 2 + restTructuredText += self.format_message_for_rst(com.message) + self.sig.emit(restTructuredText) + + def format_message_for_rst(self, message): + """Format message for a nice rst print.""" + new_message = "" + split_message = message.splitlines() + for i in range(len(split_message)): + if i is not 0 and split_message[i] is not "": + new_message += "- " + new_message += split_message[i] + "\n" * 2 + return new_message + + +class ConnectionThread(QtCore.QThread): + """Thread used to connect to github.""" + + def __init__(self, parent, g=None, sig=None): + """Init thread.""" + super(ConnectionThread, self).__init__(parent) + self.g = g + self.sig = sig + self.parent = parent + + def run(self): + """Run thread.""" + repo_list = [] + try: + + for repo in self.g.get_user().get_repos(): + repo_list.append(repo) + + if self.parent is not None: + self.parent.parent.statusBar().showMessage('Done.') + sys_tray = self.parent.parent.systemtray_icon + sys_tray.showMessage( + APP_NAME, 'Connected to github', QIcon(ICON_PATH), 5000) + except BadCredentialsException as e: + if self.parent is not None: + self.parent.parent.statusBar().showMessage(str(e)) + self.sig.emit(repo_list) + + +class Window(QMainWindow): + """Personal mainWindow class.""" + + def __init__(self, systemtray_icon=None, parent=None): + """Init window.""" + super(Window, self).__init__(parent) + + self.systemtray_icon = systemtray_icon + self.statusBar() + self.widget = MainWidget(self) + self.setCentralWidget(self.widget) + + self.resize(500, 200) + self.setWindowTitle(APP_NAME) + + self.setWindowIcon(QIcon(ICON_PATH)) + + self.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) + + helpAct = QAction('&Help', self) + helpAct.setShortcut('Ctrl+H') + helpAct.setStatusTip('Help') + helpAct.triggered.connect(self.helper) + + menubar = self.menuBar() + fileMenu = menubar.addMenu('&About') + fileMenu.addAction(helpAct) + + def helper(self): + """Display help.""" + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + + msg.setText("This simple python script allows you to generate a " + "worklog in rst format based on your repo commits.") + msg.setInformativeText("You need to generated a token first.") + msg.setWindowTitle("Help") + msg.setWindowIcon(QIcon(ICON_PATH)) + msg.setDetailedText("Simply generate a personnal access token and " + "enter it in the first field of the window." + "\r\n" + "In order to generate this token, go to " + "https://github.com/settings/tokens " + "under \"Personal access tokens\".") + msg.exec_() + + +if __name__ == "__main__": + app = QApplication(list(sys.argv)) + icon = QIcon(ICON_PATH) + systemtray_icon = Qt.QSystemTrayIcon(icon, app) + systemtray_icon.show() + window = Window(systemtray_icon) + window.show() + sys.exit(app.exec_())