diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d35cb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +*.pyc diff --git a/report-xfstests.py b/report-xfstests.py index e4a28b5..8615d06 100755 --- a/report-xfstests.py +++ b/report-xfstests.py @@ -4,6 +4,7 @@ gi.require_version("Gtk", "4.0") import bisect import pathlib +import reporter import re import sys import xdg.BaseDirectory @@ -14,104 +15,6 @@ from gi.repository import Gio XFSTESTS = pathlib.Path(xdg.BaseDirectory.xdg_data_home) / "xfstests" / "date" -class Calendar(Gtk.Calendar): - def __init__(self): - Gtk.Calendar.__init__(self, show_day_names=True) - self.connect("next-month", self.view_changed) - self.connect("next-year", self.view_changed) - self.connect("prev-month", self.view_changed) - self.connect("prev-year", self.view_changed) - self.view_changed(self) - - def get_month_directory(self): - date = self.get_date() - month = XFSTESTS / f"{date.get_year():04}" / f"{date.get_month():02}" - return month if month.is_dir() else None - - def get_selected_day(self): - if month := self.get_month_directory(): - day = month / f"{self.get_date().get_day_of_month():02}" - return day if day.is_dir() else None - - def view_changed(self, calendar): - self.clear_marks() - if month := self.get_month_directory(): - for day in month.iterdir(): - self.mark_day(int(day.stem)) - self.emit("day-selected") - - -class ServerRow(Gtk.TreeExpander): - def __init__(self): - Gtk.TreeExpander.__init__(self) - self.set_child(Gtk.Label(xalign=0)) - self.get_child().set_state_flags(Gtk.StateFlags.CHECKED, False) - - def set_item(self, item): - self.set_list_row(item) - self.get_child().set_text(item.get_item().get_name() if item else "") - - -class ServerSorter(Gtk.Sorter): - def do_compare(self, a, b): - if a.get_name() < b.get_name(): - return Gtk.Ordering.SMALLER - elif a.get_name() > b.get_name(): - return Gtk.Ordering.LARGER - return Gtk.Ordering.EQUAL - - -class ServerWindow(Gtk.ScrolledWindow): - def __init__(self): - Gtk.ScrolledWindow.__init__(self, vexpand=True, hscrollbar_policy=Gtk.PolicyType.NEVER) - self.servers = Gtk.TreeListModel.new(Gtk.DirectoryList(), False, True, self.create_func) - self.sorter = Gtk.TreeListRowSorter.new(ServerSorter()) - self.sorted = Gtk.SortListModel.new(self.servers, self.sorter) - self.selection = Gtk.SingleSelection.new(self.sorted) - self.factory = Gtk.SignalListItemFactory() - self.view = Gtk.ListView.new(self.selection, self.factory) - - self.selection.set_autoselect(False) - - self.factory.connect("setup", self.setup) - self.factory.connect("bind", self.bind) - self.factory.connect("unbind", self.unbind) - self.factory.connect("teardown", self.teardown) - self.selection.connect("selection-changed", self.selection_changed) - self.set_child(self.view) - - def selection_changed(self, selection, position, n_items): - treeitem = self.selection.get_selected_item() - file = treeitem.get_item().get_attribute_object("standard::file") - self.emit("test-changed", file if treeitem.get_depth() > 0 else None) - - def setup(self, factory, listitem): - listitem.set_child(ServerRow()) - - def bind(self, factory, listitem): - listitem.get_child().set_item(listitem.get_item()) - listitem.set_selectable(listitem.get_item().get_depth() > 0) - - def unbind(self, factory, listitem): - listitem.get_child().set_item(None) - - def teardown(self, factory, listitem): - listitem.set_child(None) - - def create_func(self, item): - file = item.get_attribute_object("standard::file") - root = pathlib.Path(self.servers.get_model().get_file().get_path()) - if pathlib.Path(file.get_path()).parent == root: - return Gtk.DirectoryList.new(file=file) - - def set_day(self, day): - file = Gio.File.new_for_path(str(day)) if day else None - self.servers.get_model().set_file(file) - - @GObject.Signal(arg_types=(Gio.File,)) - def test_changed(self, file): pass - - class TestProperty(): def __init__(self, name): self.name = name @@ -215,7 +118,7 @@ class TestResultsModel(GObject.GObject, Gio.ListModel): self.properties.clear() self.tests.clear() - for file in sorted(path.iterdir()): + for file in sorted(path.iterdir() if path else []): self.columns.append(file.stem) root = xml.etree.ElementTree.parse(file).getroot() for prop in root.attrib.keys(): @@ -354,8 +257,7 @@ class TestWindow(Gtk.ScrolledWindow): class Window(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title="Xfstests Results") - self.calendar = Calendar() - self.servers = ServerWindow() + self.chooser = reporter.TestChooser() self.passing = Gtk.Switch(halign=Gtk.Align.CENTER, active=True) self.lpass = Gtk.Label.new("Show Passing Tests") self.skipped = Gtk.Switch(halign=Gtk.Align.CENTER, active=True) @@ -363,15 +265,13 @@ class Window(Gtk.Window): self.search = Gtk.SearchEntry(placeholder_text="Type To Filter Tests") self.popover = FilterPopover(parent=self.search) self.xfstests = TestWindow() - self.sz_group = Gtk.SizeGroup.new(Gtk.SizeGroupMode.VERTICAL) self.left = Gtk.Grid(row_spacing=5) - self.left.attach(self.calendar, 0, 0, 2, 1) - self.left.attach(self.servers, 0, 1, 2, 1) - self.left.attach(self.passing, 0, 2, 1, 1) - self.left.attach(self.lpass, 1, 2, 1, 1) - self.left.attach(self.skipped, 0, 3, 1, 1) - self.left.attach(self.lskip, 1, 3, 1, 1) + self.left.attach(self.chooser, 0, 0, 2, 1) + self.left.attach(self.passing, 0, 1, 1, 1) + self.left.attach(self.lpass, 1, 1, 1, 1) + self.left.attach(self.skipped, 0, 2, 1, 1) + self.left.attach(self.lskip, 1, 2, 1, 1) self.right = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5) self.right.append(self.xfstests.notebook) @@ -382,26 +282,20 @@ class Window(Gtk.Window): self.child.append(self.left) self.child.append(self.right) - self.sz_group.add_widget(self.calendar) - self.sz_group.add_widget(self.xfstests.notebook) + reporter.common.SizeGroup.add_widget(self.xfstests.notebook) self.lskip.set_xalign(0) self.lpass.set_xalign(0) self.set_default_size(1100, 750) self.set_child(self.child) - self.calendar.connect("day-selected", self.date_changed) - self.servers.connect("test-changed", self.test_changed) + self.chooser.connect("test-selected", self.test_changed) self.passing.connect("notify::active", self.show_passing) self.skipped.connect("notify::active", self.show_skipped) self.search.connect("search-changed", self.search_changed) - self.date_changed(self.calendar) - - def date_changed(self, calendar): - self.servers.set_day(calendar.get_selected_day()) def test_changed(self, window, file): - self.xfstests.set_tests(pathlib.Path(file.get_path())) + self.xfstests.set_tests(file.path if file else None) def show_passing(self, passing, param): self.xfstests.set_show_passing(passing.get_active()) diff --git a/reporter/__init__.py b/reporter/__init__.py new file mode 100644 index 0000000..3c71025 --- /dev/null +++ b/reporter/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/python +from . import common +from . import testchooser +from gi.repository import GObject +from gi.repository import Gtk + +class TestChooser(Gtk.Box): + def __init__(self): + Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) + self.stack = testchooser.Stack() + self.append(Gtk.StackSwitcher(stack=self.stack)) + self.append(self.stack) + self.stack.connect("test-selected", self.on_test_selected) + + def on_test_selected(self, stack, path): + self.emit("test-selected", path) + + @GObject.Signal(arg_types=(testchooser.Path,)) + def test_selected(self, path): pass diff --git a/reporter/common.py b/reporter/common.py new file mode 100644 index 0000000..1557e49 --- /dev/null +++ b/reporter/common.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +import gi +import pathlib +import xdg.BaseDirectory + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk + + +XFSTESTS_BASE = pathlib.Path(xdg.BaseDirectory.xdg_data_home) / "xfstests" +XFSTESTS_DATE = XFSTESTS_BASE / "date" +XFSTESTS_TAGS = XFSTESTS_BASE / "tags" + +SizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.VERTICAL) diff --git a/reporter/testchooser.py b/reporter/testchooser.py new file mode 100644 index 0000000..678f147 --- /dev/null +++ b/reporter/testchooser.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +import pathlib +from . import common +from gi.repository import GObject +from gi.repository import Gtk +from gi.repository import Gio + + +class Path(GObject.GObject): + def __init__(self, path): + GObject.GObject.__init__(self) + self.path = path + + def __lt__(self, rhs): return self.path < rhs.path + def get_is_test_result(self): + return False not in [ f.is_file() for f in self.path.iterdir() ] + def get_name(self): return self.path.name + def get_model(self): + if not self.get_is_test_result(): + return DirectoryModel(self.path) + + +class DirectoryModel(GObject.GObject, Gio.ListModel): + def __init__(self, path=None): + GObject.GObject.__init__(self) + self.children = [ ] + self.set_path(path) + + def do_get_item_type(self): return GObject.TYPE_PYOBJECT + def do_get_n_items(self): return len(self.children) + def do_get_item(self, n): return self.children[n] + + def set_path(self, path): + rm = len(self.children) + self.children.clear() + self.path = path if path and path.is_dir() else None + if self.path: + self.children = sorted([ Path(c) for c in path.iterdir() ]) + self.emit("items-changed", 0, rm, len(self.children)) + + +class DirectoryWindow(Gtk.ScrolledWindow): + def __init__(self, path=None, autoexpand=True): + Gtk.ScrolledWindow.__init__(self, vexpand=True, + hscrollbar_policy=Gtk.PolicyType.NEVER) + self.dirtree = Gtk.TreeListModel.new(root=DirectoryModel(path), + passthrough=False, + autoexpand=autoexpand, + create_func=self.create_func) + self.selection = Gtk.SingleSelection.new(self.dirtree) + self.selection.set_autoselect(False) + self.selection.set_can_unselect(True) + self.selection.connect("selection-changed", self.selection_changed) + + self.factory = Gtk.SignalListItemFactory() + self.factory.connect("setup", self.on_setup) + self.factory.connect("bind", self.on_bind) + self.factory.connect("unbind", self.on_unbind) + self.factory.connect("teardown", self.on_teardown) + + self.listview = Gtk.ListView.new(self.selection, self.factory) + self.set_child(self.listview) + + def create_func(self, path): + return path.get_model() + + def on_setup(self, factory, listitem): + listitem.set_child(Gtk.TreeExpander(child=Gtk.Label(xalign=0))) + + def on_bind(self, factory, listitem): + treerow = listitem.get_item() + filepath = treerow.get_item() + expander = listitem.get_child() + expander.set_list_row(treerow) + expander.get_child().set_text(filepath.get_name()) + listitem.set_selectable(filepath.get_is_test_result()) + + def on_unbind(self, factory, listitem): + expander = listitem.get_child() + expander.set_list_row(None) + expander.get_child().set_text("") + + def on_teardown(self, factory, treeitem): + treeitem.set_child(None) + + def set_directory(self, path): + self.dirtree.get_model().set_path(path) + + def selection_changed(self, selection, position, n_items): + treeitem = self.selection.get_selected_item() + self.emit("test-selected", treeitem.get_item() if treeitem else None) + + def select_none(self): + self.selection.unselect_item(self.selection.get_selected()) + + @GObject.Signal(arg_types=(Path,)) + def test_selected(self, filepath): pass + + +class Calendar(Gtk.Calendar): + def __init__(self): + Gtk.Calendar.__init__(self, show_day_names=True) + self.connect("next-month", self.view_changed) + self.connect("next-year", self.view_changed) + self.connect("prev-month", self.view_changed) + self.connect("prev-year", self.view_changed) + common.SizeGroup.add_widget(self) + self.view_changed(self) + + def get_month_directory(self): + date = self.get_date() + month = common.XFSTESTS_DATE / f"{date.get_year():04}" / f"{date.get_month():02}" + return month if month.is_dir() else None + + def get_selected_day(self): + if month := self.get_month_directory(): + day = month / f"{self.get_date().get_day_of_month():02}" + return day if day.is_dir() else None + + def view_changed(self, calendar): + self.clear_marks() + if month := self.get_month_directory(): + for day in month.iterdir(): + self.mark_day(int(day.stem)) + self.emit("day-selected") + + +class DatePage(Gtk.Box): + def __init__(self): + Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=5) + self.calendar = Calendar() + self.dirwin = DirectoryWindow(self.calendar.get_selected_day()) + self.append(self.calendar) + self.append(self.dirwin) + self.get_first_child().connect("day-selected", self.on_day_selected) + + def on_day_selected(self, calendar): + self.dirwin.set_directory(self.calendar.get_selected_day()) + + def select_none(self): + self.dirwin.select_none() + + +class Stack(Gtk.Stack): + def __init__(self): + Gtk.Stack.__init__(self, transition_type=Gtk.StackTransitionType.OVER_LEFT_RIGHT) + self.date = DatePage() + self.tags = DirectoryWindow(common.XFSTESTS_TAGS, autoexpand=False) + self.add_titled(self.date, "Date", "Date") + self.add_titled(self.tags, "Tags", "Tags") + self.date.dirwin.connect("test-selected", self.on_test_selected) + self.tags.connect("test-selected", self.on_test_selected) + self.connect("notify::visible-child", self.on_page_changed) + + def on_test_selected(self, widget, test): + self.emit("test-selected", test) + + def on_page_changed(self, stack, param): + stack.get_visible_child().select_none() + self.emit("test-selected", None) + + @GObject.Signal(arg_types=(Path,)) + def test_selected(self, path): pass