add time diff plugin

This commit is contained in:
2022-03-12 09:28:33 +01:00
parent b99421e8e7
commit 297f67b9b5
16 changed files with 239 additions and 21 deletions

View File

@@ -33,7 +33,7 @@ def sort_menu_contributions(menu_contributions: [MenuContribution]) -> [MenuCont
result = []
items = _sort_by_action_id(menu_contributions[:])
_recursive_half_order_adder(result, items, None)
_recursive_half_order_adder(result, items)
# add remaining items to the end (ordered by their action_id)
# This resolves cycles.
@@ -42,10 +42,10 @@ def sort_menu_contributions(menu_contributions: [MenuContribution]) -> [MenuCont
return result
def _recursive_half_order_adder(result: [MenuContribution], items: [MenuContribution], parent):
def _recursive_half_order_adder(result: [MenuContribution], items: [MenuContribution]):
for item in items:
mc: MenuContribution = item
if not mc.after:
result.append(mc)
items.remove(mc)
_recursive_half_order_adder(result, items, mc.action_id)
_recursive_half_order_adder(result, items)

View File

@@ -1,7 +1,7 @@
from typing import Callable
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import QMenu
from PySide6.QtWidgets import QMenu, QPushButton, QWidget
class RAction():
@@ -86,3 +86,20 @@ class RAction():
self._update_check_state()
return action
def to_qpushbutton(self, parent: QWidget) -> QPushButton:
button = QPushButton(parent)
if self.label:
button.setText(self.label)
if self.icon_from_theme:
button.setIcon(QIcon.fromTheme(self.icon_from_theme))
if self.icon_file:
button.setIcon(QIcon(self.icon_file))
if self.shortcut:
button.setShortcut(self.shortcut)
if self.action:
button.pressed.connect(self.action)
if self.checkable:
button.setChecked(self.checked)
button.setCheckable(self.checkable)
return button

View File

@@ -8,13 +8,17 @@ from typing import Optional, Callable
from PySide6.QtCore import QRunnable, QThreadPool, Signal
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QCheckBox, QPushButton, QComboBox, \
QSizePolicy, QProgressBar
QSizePolicy, QProgressBar, QMenu, QMenuBar
from src.plugins.domain.raction import RAction
from src.plugins.logfile.preprocesslineshook import PreProcessLinesHook
from src.ui.bigtext.bigtext import BigText
from src.ui.bigtext.logFileModel import LogFileModel
from src.i18n import _
from src.pluginregistry import PluginRegistry
from src.ui.hbox import HBox
from src.zonedpluginregistry import ZonedPluginRegistry
class FilterTask(QRunnable):
@@ -27,6 +31,7 @@ class FilterTask(QRunnable):
regex: re.Pattern,
lock: threading.RLock,
filter_match_found_listeners: Callable[[int], None],
pre_process_lines_hooks: [PreProcessLinesHook],
progress_handler: Callable[[float], None],
on_before: Callable[[], None],
on_finish: Callable[[], None]
@@ -36,6 +41,7 @@ class FilterTask(QRunnable):
self.filter_model = filter_model
self.regex = regex
self.progress_handler = progress_handler
self.pre_process_lines_hooks = pre_process_lines_hooks
self.on_before = on_before
self.on_finish = on_finish
self.lock = lock
@@ -71,7 +77,12 @@ class FilterTask(QRunnable):
target_line_offset = target.tell()
for listener in self.filter_match_found_listeners:
listener(target_line_offset, source_line_offset)
target.write(l)
for hook in self.pre_process_lines_hooks:
h: PreProcessLinesHook = hook
line = h.pre_process_line(line, target)
target.write(line.encode("utf8"))
# sometime buffering can hide results for a while
# We force a flush periodically.
@@ -100,9 +111,10 @@ class FilterWidget(QWidget):
search_is_running = Signal(bool)
signal_update_progress = Signal(float)
def __init__(self, source_model: LogFileModel):
def __init__(self, source_model: LogFileModel, zoned_plugin_registry: ZonedPluginRegistry):
super(FilterWidget, self).__init__()
self.source_model = source_model
self._zoned_plugin_registry = zoned_plugin_registry
self._lock = threading.RLock()
@@ -131,6 +143,8 @@ class FilterWidget(QWidget):
self.btn_bookmark.setToolTip(_("save query"))
self.btn_bookmark.pressed.connect(self._save_query)
self.menu = self._create_stuff_menu()
self.ignore_case = QCheckBox(_("ignore case"))
self.ignore_case.setChecked(True)
self.ignore_case.stateChanged.connect(self.filter_changed)
@@ -146,6 +160,7 @@ class FilterWidget(QWidget):
filter_bar.layout.addWidget(self.progress_bar)
filter_bar.layout.addWidget(self.btn_cancel_search)
filter_bar.layout.addWidget(self.btn_bookmark)
filter_bar.layout.addWidget(self.menu)
filter_bar.layout.addWidget(self.ignore_case)
filter_bar.layout.addWidget(self.is_regex)
@@ -162,6 +177,16 @@ class FilterWidget(QWidget):
def on_reveal(self):
self._reload_save_queries()
def _create_stuff_menu(self):
menu = HBox()
actions = self._zoned_plugin_registry.execute_flat_map("get_filter_widget_actions")
for action in actions:
raction: RAction = action;
button = raction.to_qpushbutton(menu)
menu.addWidget(button)
return menu
def _reload_save_queries(self):
saved_queries = PluginRegistry.execute_single("saved_queries")
for saved_query in saved_queries:
@@ -238,12 +263,15 @@ class FilterWidget(QWidget):
self.source_model.set_query_highlight(query, ignore_case, is_regex)
self.filter_model.set_query_highlight(query, ignore_case, is_regex)
pre_process_lines_hooks = self._zoned_plugin_registry.execute_remove_falsy("get_pre_process_lines_hook")
self.filter_task = FilterTask(
self.source_model,
self.filter_model,
regex,
self._lock,
self.filter_match_found_listeners,
pre_process_lines_hooks,
self.progress_handler,
lambda: self.search_is_running.emit(True),
lambda: self.search_is_running.emit(False)

View File

@@ -7,15 +7,17 @@ from src.plugins.logfile.filterwidget import FilterWidget
from src.ui.bigtext.logFileModel import LogFileModel
from src.plugins.krowlog.Tab import Tab
from src.util.conversion import humanbytes
from src.zonedpluginregistry import ZonedPluginRegistry
class FullTabWidget(Tab):
def __init__(self, model: LogFileModel, unique_id: str, title: str):
def __init__(self, model: LogFileModel, unique_id: str, title: str, zoned_plugin_registry: ZonedPluginRegistry):
super(FullTabWidget, self).__init__(unique_id, title)
self._model = model
self._zoned_plugin_registry = zoned_plugin_registry
self.file_view = BigText(model)
self.filter_hit_view = FilterWidget(self._model)
self.filter_hit_view = FilterWidget(self._model, self._zoned_plugin_registry)
self.filter_view_syncer = FilterViewSyncer(self.file_view)
self.filter_hit_view.add_line_click_listener(self.filter_view_syncer.click_listener)
self.filter_hit_view.add_filter_match_found_listener(self.filter_view_syncer.match_found)

View File

@@ -0,0 +1,11 @@
from abc import abstractmethod
from typing import List, BinaryIO
from src.ui.bigtext.line import Line
class PreProcessLinesHook:
@abstractmethod
def pre_process_line(self, line: str, file_io: BinaryIO) -> str:
return line

View File

@@ -3,6 +3,7 @@ from typing import Optional
from PySide6.QtWidgets import QMessageBox
from src.pluginregistry import PluginRegistry
from src.plugins.logfile.fulltabwidget import FullTabWidget
from src.ui.bigtext.logFileModel import LogFileModel
from src.pluginbase import PluginBase
@@ -31,7 +32,8 @@ class LogFilePlugin(PluginBase):
filename = os.path.basename(realpath)
model = LogFileModel(file, self.settings)
tab = FullTabWidget(model, unique_id=realpath, title=filename)
tab = FullTabWidget(model, unique_id=realpath, title=filename,
zoned_plugin_registry=PluginRegistry.create_zoned_plugin_registry())
return tab

View File

View File

@@ -0,0 +1,75 @@
from typing import List, Optional, BinaryIO
from src.plugins.logfile.preprocesslineshook import PreProcessLinesHook
from src.ui.bigtext.line import Line
from re import compile
from datetime import datetime, timedelta
class TimeDiffPreProcessLinesHook(PreProcessLinesHook):
def __init__(self):
super(TimeDiffPreProcessLinesHook, self).__init__()
self.date_pattern = compile(r"\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}([,.]\d{3})")
self.prev_time: Optional[datetime] = None
def pre_process_line(self, line: str, file_io: BinaryIO) -> str:
time = self._parse_time(line)
if time:
if self.prev_time:
time_diff = time - self.prev_time
if time_diff.total_seconds() > 0:
line_ending = self.parse_line_ending(line)
time_diff_str = self.time_diff_to_str(time_diff)
time_diff_line = f"{time_diff_str}{line_ending}"
file_io.write(time_diff_line.encode("utf8"))
self.prev_time = time
return line
def time_diff_to_str(self, time_diff: timedelta) -> str:
total_seconds = time_diff.total_seconds()
hours = int(total_seconds / 3600)
minutes = int((total_seconds % 3600) / 60)
seconds = int(total_seconds % 60)
milliseconds = int((total_seconds * 1000) % 1000)
if hours > 0:
result = f"{hours:3}:{minutes:02}:{seconds:02}.{milliseconds:03}"
elif minutes > 0:
result = f" {minutes:2}:{seconds:02}.{milliseconds:03}"
elif seconds:
result = f" {seconds:2}.{milliseconds:03}"
else:
result = f" 0.{milliseconds:03}"
return result
def parse_line_ending(self, line):
if line[-2:] == "\r\n":
return "\r\n"
return line[-1]
def _parse_time(self, line: str) -> Optional[datetime]:
m = self.date_pattern.match(line)
if m:
date_string = m.group(0).replace(" ", "T").replace(",", ".")
time = datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%f")
return time
return None
if __name__ == "__main__":
t = TimeDiffPreProcessLinesHook()
texts = ["2022-02-22T12:00:00,123 foo bar", "2022-02-22T12:00:00.124 baz",
"should not match 2022-02-22T12:00:00,130 other",
"2022-02-22 12:00:00,130 yet another"]
lines = []
byte_offset = 0
for text in texts:
lines.append(Line(byte_offset=byte_offset, byte_end=byte_offset + len(text.encode("utf8")), line=text))
byte_offset = byte_offset + len(text.encode("utf8"))
ls = t.pre_process_lines(lines)
for l in ls:
print(l.line())

View File

@@ -0,0 +1,34 @@
from typing import Optional
from PySide6.QtGui import QIcon
from src.pluginbase import PluginBase
from src.plugins.domain.raction import RAction
from src.plugins.logfile.preprocesslineshook import PreProcessLinesHook
from src.plugins.timediff.time_diff_pre_process_lines_hook import TimeDiffPreProcessLinesHook
from src.i18n import _
class TimeDiffPlugin(PluginBase):
def __init__(self):
super(TimeDiffPlugin, self).__init__()
self.time_diff_state = False
self.time_diff_action = RAction(_(""), lambda: self._toggle_time_diff(),
icon_file="icons/ionicons/stopwatch-outline.svg", checkable=True)
def copy(self):
return TimeDiffPlugin()
def get_filter_widget_actions(self) -> [RAction]:
return [
self.time_diff_action
]
def get_pre_process_lines_hook(self) -> Optional[PreProcessLinesHook]:
if self.time_diff_state:
return TimeDiffPreProcessLinesHook()
return None
def _toggle_time_diff(self):
self.time_diff_state = not self.time_diff_state
self.time_diff_action.set_checked(self.time_diff_state)