diff --git a/README.md b/README.md index 32f692b83e01dd3989fe97ac994d07a48f718a0a..5abde24ededc26891550f89aa9664f7ee9f19f30 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,19 @@ deactivate ## Linting -Sjekk av kodekvalitet gjøres med scriptet "CodeQualityCheck" i script-mappa, og er skrevet i Powershell(.ps1) og Bash(.sh). Scriptet kjører Pylint med .pylintrc-fil, Flake8, Bandit og pydocstyle. Man kan gi scriptet spesifikke .py-filer, hvis ikke sjekker den alle .py-filer. +Sjekk av kodekvalitet gjøres med scriptet "CodeQualityCheck" i script-mappa, og er skrevet i Powershell(.ps1) og Bash(.sh). Scriptet kjører Pylint og Flake8 med tilhørende konfigurasjonsfiler, Pydocstyle med numpy-konvensjon og Bandit på Python-filer. -Terminal kjøres fra root: +Scriptet godtar kommandolinjeargumenter: .py-fil(er), mappe(r ) eller en blanding av disse. Uten argumenter vil scriptet sjekke alle .py-filer. -* Sjekk alle .py: `.\scripts\CodeQualityCheck.ps1` eller `./scripts/CodeQualityCheck.sh` +Terminal kjøres fra root. Sjekk: -* Sekk spesifikk(e) .py: `.\scripts\CodeQualityCheck.ps1 filEn.py filTo.py` +* Alle .py-filer: `.\scripts\CodeQualityCheck.ps1` eller `./scripts/CodeQualityCheck.sh` + +* Spesifikk(e) .py: `.\scripts\CodeQualityCheck.ps1 filEn.py filTo.py` + +* Alle .py i mappe(r ): `.\scripts\CodeQualityCheck.ps1 mappeEn mappeTo` + +* Blanding: `.\scripts\CodeQualityCheck.ps1 mappeEn mappeTo\fil.py` ## Testing diff --git a/scripts/.pylintrc b/scripts/.pylintrc index 33b7a3368cc96aae954df72b4310d207348941ab..687b3068dd56f50726927545445e5430949ee7da 100644 --- a/scripts/.pylintrc +++ b/scripts/.pylintrc @@ -412,7 +412,9 @@ good-names=app, k, ex, Run, - _ + _, + x, + y # Include a hint for the correct naming format with invalid-name. include-naming-hint=no diff --git a/scripts/CodeQualityCheck.ps1 b/scripts/CodeQualityCheck.ps1 index 7e5021fa42af906c9517997851afcde34e009cd4..e2894fa734a9e2054caca683ea971f57eb0119d5 100644 --- a/scripts/CodeQualityCheck.ps1 +++ b/scripts/CodeQualityCheck.ps1 @@ -1,13 +1,24 @@ - if ($args.Count -eq 0){ $files=Get-ChildItem -recurse | - Where-Object {$_.name -match "[A-Za-z0-9_]+\.py" -And $_.Name -notmatch "pyc"} | + Where-Object {$_.name -match "[A-Za-z0-9_]+\.py" -And $_.Name -notmatch ".pyc"} | % { $_.FullName } Write-Output "`nChecking all .py-files" } else{ - $files=$args + $files = @() + + for($i=0; $i -lt $args.Length; $i++){ + + if($args[$i] -match "[A-Za-z0-9_]+\.py"){ + $files += $args[$i] + } + else{ + $files += Get-ChildItem -recurse $args[$i] | + Where-Object {$_.Name -match "[A-Za-z0-9_]+\.py" -And $_.Name -notmatch ".pyc"} | + % { $_.FullName } + } + } } for ($i=0; $i -lt $files.Length; $i++){ diff --git a/scripts/CodeQualityCheck.sh b/scripts/CodeQualityCheck.sh index 965530d50304173c177533c918d537ed4e3120d4..899f9a49d7074d873a2e6ded71a2db4e8efd049c 100644 --- a/scripts/CodeQualityCheck.sh +++ b/scripts/CodeQualityCheck.sh @@ -1,19 +1,27 @@ - if [[ $# -eq 0 ]]; then files=$(find -type f -name '*.py' ) echo "\nChecking all .py-files" else - files=$@ -fi + args=$@ + + for file in "${args[@]}"; do - for fileName in $files; do - printf "============================$fileName============================\n" - printf "\n===PYLINT===\n" - pylint --rcfile=./scripts/.pylintrc $fileName - printf "===FLAKE8===\n" - flake8 --config ./scripts/.flake8 $fileName - printf "\n===BANDIT===\n" - bandit $fileName - printf "\n===PYDOCSTYLE===\n" - pydocstyle --convention=numpy $fileName + if [[ $file =~ ^[A-Za-z0-9_/]+.py ]] && ! [[ $file =~ .pyc ]]; then + files+=( "$file" ) + else + files+=$(find $file -type f -name '*.py') + fi done +fi + +for file in $files; do + printf "========================$(basename -- $file)========================\n" + printf "\n===PYLINT===\n" + pylint --rcfile=./scripts/.pylintrc $file + printf "===FLAKE8===\n" + flake8 --config ./scripts/.flake8 $file + printf "\n===BANDIT===\n" + bandit $file + printf "\n===PYDOCSTYLE===\n" + pydocstyle --convention=numpy $file +done diff --git a/soitool/modules/__init__.py b/soitool/modules/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2927ebf2095d5e9fbff2ced1bfcb3f550716ff91 --- /dev/null +++ b/soitool/modules/__init__.py @@ -0,0 +1 @@ +"""All modules and moduleBase (interface).""" diff --git a/soitool/modules/module_base.py b/soitool/modules/module_base.py new file mode 100644 index 0000000000000000000000000000000000000000..92e7e6bc6145a1a1921cb5580c7b8384d77cad51 --- /dev/null +++ b/soitool/modules/module_base.py @@ -0,0 +1,14 @@ +"""Base/interface of each module.""" +from abc import ABC + + +class ModuleBase(ABC): + """Interface for SOI-modules.""" + + def get_size(self): + """Abstract method, should be implemented by derived class.""" + raise NotImplementedError + + def render_onto_pdf(self): + """Abstract method, should be implemented by derived class.""" + raise NotImplementedError diff --git a/soitool/modules/module_table.py b/soitool/modules/module_table.py new file mode 100644 index 0000000000000000000000000000000000000000..5931f44367ab453627696db75da18337498496c9 --- /dev/null +++ b/soitool/modules/module_table.py @@ -0,0 +1,158 @@ +"""Module containing subclassed SOIModule (QTableWidget, ModuleBase).""" +from PySide2.QtWidgets import QTableWidget, QTableWidgetItem, QShortcut +from PySide2 import QtGui, QtCore +from soitool.modules.module_base import ModuleBase + +HEADER_FONT = QtGui.QFont() +HEADER_FONT.setFamily('Arial') +HEADER_FONT.setPointSize(12) +HEADER_FONT.setWeight(100) + +START_COLUMNS = 2 +START_ROWS = 2 + + +class Meta(type(ModuleBase), type(QTableWidget)): + """Used as a metaclass to enable multiple inheritance.""" + + +class TableModule(ModuleBase, QTableWidget, metaclass=Meta): + """Modified QTableWidget. + + Has shortcuts for adding and removing rows and columns. + Inherits from ModuleBase and QTableWidget. + Functions inherited from ModuleBase are overridden by this class. + + Has header-styled first row. + Functionality for adding and removing columns and rows is implemented. + """ + + def __init__(self): + """Initialize QTableWidget.""" + QTableWidget.__init__(self) + super(QTableWidget) + + # Remove headers + self.horizontalHeader().hide() + self.verticalHeader().hide() + + # Set number of columns and rows + self.setColumnCount(START_COLUMNS) + self.setRowCount(START_ROWS) + + # Resize width and height of columns and rows, and set size of window + self.resize() + self.setFixedWidth(START_COLUMNS * self.columnWidth(0) + 2) + self.setFixedHeight(START_ROWS * self.rowHeight(0) + 5) + + # Remove scrollbars + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + # Set headers + for i in range(self.columnCount()): + self.set_header_item(i, "") + + self.cellChanged.connect(self.resize) + + self.set_shortcuts() + + def set_header_item(self, column, text): + """Insert item with header-style. + + Item will always be set on header (top) row. + + Parameters + ---------- + column : int + What column index for inserting item. + text : String + What text the item should contain. + """ + item = QTableWidgetItem(text) + item.setBackground(QtGui.QBrush(QtGui.QColor(220, 220, 220))) + item.setFont(HEADER_FONT) + self.setItem(0, column, item) + + def set_shortcuts(self): + """Set shortcuts for adding and removing rows and columns.""" + # Create shortcuts + shortcut_add_col = QShortcut(QtGui.QKeySequence("Shift++"), self) + shortcut_rem_col = QShortcut(QtGui.QKeySequence("Shift+-"), self) + shortcut_add_row = QShortcut(QtGui.QKeySequence("Ctrl++"), self) + shortcut_rem_row = QShortcut(QtGui.QKeySequence("Ctrl+-"), self) + + # Connect shortcuts to functions + shortcut_add_col.activated.connect(self.add_column) + shortcut_rem_col.activated.connect(self.remove_column) + shortcut_add_row.activated.connect(self.add_row) + shortcut_rem_row.activated.connect(self.remove_row) + + def add_column(self): + """Add column to the right of selected column.""" + self.insertColumn(self.currentColumn() + 1) + self.set_header_item(self.currentColumn() + 1, "") + self.resize() + + def remove_column(self): + """Remove selected column if two or more columns exist.""" + if self.columnCount() > 1: + self.removeColumn(self.currentColumn()) + self.resize() + + def add_row(self): + """Add row below selected row.""" + self.insertRow(self.currentRow() + 1) + self.resize() + + def remove_row(self): + """Remove selected row if two or more rows exist (including header).""" + if self.rowCount() > 2 and self.currentRow() != 0: + self.removeRow(self.currentRow()) + self.resize() + + def resize(self): + """Resize widget, rows and columns. + + Resize widget size to total width and height of rows and columns. + Resize rows and columns to contents. + """ + self.resizeColumnsToContents() + self.resizeRowsToContents() + + # Calculate total width and height of columns and rows + width = 0 + height = 0 + + for x in range(self.columnCount()): + width += self.columnWidth(x) + 0.5 + + for y in range(self.rowCount()): + height += self.rowHeight(y) + 0.5 + + # Set total width and height + self.setFixedWidth(width) + self.setFixedHeight(height) + + def get_size(self): + """Get size of widget. + + Returns + ------- + Tuple + (width, height) (total) + """ + # Calculate total width and height of columns and rows + width = 0 + height = 0 + + for i in range(self.columnCount()): + width += self.columnWidth(i) + 0.5 + + for i in range(self.columnCount()): + height += self.rowHeight(i) + 0.5 + + return width, height + + def render_onto_pdf(self): + """Render onto pdf."""