Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI

on:
push:
pull_request:

jobs:
ci:
name: Tests on Python ${{ matrix.python-version }}
runs-on: windows-latest

strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync

- name: Run type checks
run: uv run poe typecheck

- name: Run tests
run: uv run pytest
41 changes: 41 additions & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Contributing

Thank you for your interest in contributing to construct-editor!

## Project Setup

This project uses [uv](https://docs.astral.sh/uv/) for dependency management. Make sure you have `uv` installed before proceeding.

Clone the repository and install all dependencies (including dev dependencies) with:

```bash
git clone https://github.com/timrid/construct-editor.git
cd construct-editor
uv sync
```

This will create a virtual environment and install all required packages automatically.

## Running the Example GUI

To launch the example GUI, run:

```bash
uv run construct-editor
```

## Running Tests

To run the test suite with [pytest](https://docs.pytest.org/):

```bash
uv run pytest
```

## Running Type Checks Locally

Type checks are run via [poethepoet](https://github.com/nat-n/poethepoet) and include [ruff](https://docs.astral.sh/ruff/), [ty](https://github.com/astral-sh/ty), and [pyright](https://github.com/microsoft/pyright):

```bash
uv run poe typecheck
```
51 changes: 23 additions & 28 deletions construct_editor/core/entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,7 @@
import construct_typed as cst

import construct_editor.core.model as model
from construct_editor.core.context_menu import (
ButtonMenuItem,
CheckboxMenuItem,
ContextMenu,
SeparatorMenuItem,
)
import construct_editor.core.context_menu as context_menu
from construct_editor.core.preprocessor import (
GuiMetaData,
IncludeGuiMetaData,
Expand Down Expand Up @@ -344,7 +339,7 @@ def obj_view_settings(self) -> ObjViewSettings:
return ObjViewSettings_Default(self)

# default "modify_context_menu" ###########################################
def modify_context_menu(self, menu: ContextMenu):
def modify_context_menu(self, menu: "context_menu.ContextMenu"):
"""This method is called, when the user right clicks an entry and a ContextMenu is created"""
pass

Expand Down Expand Up @@ -444,7 +439,7 @@ def obj_view_settings(self) -> ObjViewSettings:
return self.subentry.obj_view_settings

# pass throught "modify_context_menu" to subentry #########################
def modify_context_menu(self, menu: ContextMenu):
def modify_context_menu(self, menu: "context_menu.ContextMenu"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you certain that this enhances readability?

return self.subentry.modify_context_menu(menu)


Expand Down Expand Up @@ -486,24 +481,24 @@ def obj_str(self) -> str:
def obj_view_settings(self) -> ObjViewSettings:
return ObjViewSettings_Default(self) # TODO: create panel for cs.Struct

def modify_context_menu(self, menu: ContextMenu):
def modify_context_menu(self, menu: "context_menu.ContextMenu"):
def on_expand_children_clicked():
menu.parent.expand_children(self)

def on_collapse_children_clicked():
menu.parent.collapse_children(self)

menu.add_menu_item(SeparatorMenuItem())
menu.add_menu_item(context_menu.SeparatorMenuItem())
menu.add_menu_item(
ButtonMenuItem(
context_menu.ButtonMenuItem(
"Expand Children",
None,
True,
on_expand_children_clicked,
)
)
menu.add_menu_item(
ButtonMenuItem(
context_menu.ButtonMenuItem(
"Collapse Children",
None,
True,
Expand Down Expand Up @@ -579,24 +574,24 @@ def obj_str(self) -> str:
def obj_view_settings(self) -> ObjViewSettings:
return ObjViewSettings_Default(self) # TODO: create panel for cs.Array

def modify_context_menu(self, menu: ContextMenu):
def modify_context_menu(self, menu: "context_menu.ContextMenu"):
def on_expand_children_clicked():
menu.parent.expand_children(self)

def on_collapse_children_clicked():
menu.parent.collapse_children(self)

menu.add_menu_item(SeparatorMenuItem())
menu.add_menu_item(context_menu.SeparatorMenuItem())
menu.add_menu_item(
ButtonMenuItem(
context_menu.ButtonMenuItem(
"Expand Children",
None,
True,
on_expand_children_clicked,
)
)
menu.add_menu_item(
ButtonMenuItem(
context_menu.ButtonMenuItem(
"Collapse Children",
None,
True,
Expand All @@ -617,9 +612,9 @@ def on_menu_item_clicked(checked: bool):
else:
menu.parent.enable_list_view(self)

menu.add_menu_item(SeparatorMenuItem())
menu.add_menu_item(context_menu.SeparatorMenuItem())
menu.add_menu_item(
CheckboxMenuItem(
context_menu.CheckboxMenuItem(
"Enable List View",
None,
True,
Expand Down Expand Up @@ -714,7 +709,7 @@ def obj_view_settings(self) -> ObjViewSettings:
else:
return subentry.obj_view_settings

def modify_context_menu(self, menu: ContextMenu):
def modify_context_menu(self, menu: "context_menu.ContextMenu"):
subentry = self._get_subentry()
if subentry is None:
return
Expand Down Expand Up @@ -810,7 +805,7 @@ def obj_view_settings(self) -> ObjViewSettings:
else:
return subentry.obj_view_settings

def modify_context_menu(self, menu: ContextMenu):
def modify_context_menu(self, menu: "context_menu.ContextMenu"):
subentry = self._get_subentry()
if subentry is None:
return
Expand Down Expand Up @@ -1062,14 +1057,14 @@ def typ_str(self) -> str:
def obj_view_settings(self) -> ObjViewSettings:
return ObjViewSettings_Bytes(self)

def modify_context_menu(self, menu: ContextMenu):
def modify_context_menu(self, menu: "context_menu.ContextMenu"):
def on_ascii_view_clicked(checked: bool):
self.ascii_view = not self.ascii_view
menu.parent.reload()

menu.add_menu_item(SeparatorMenuItem())
menu.add_menu_item(context_menu.SeparatorMenuItem())
menu.add_menu_item(
CheckboxMenuItem(
context_menu.CheckboxMenuItem(
"ASCII View",
None,
True,
Expand Down Expand Up @@ -1193,15 +1188,15 @@ def __init__(
):
super().__init__(model, parent, construct, name, docs)

def modify_context_menu(self, menu: ContextMenu):
def modify_context_menu(self, menu: "context_menu.ContextMenu"):
def on_default_clicked():
# TODO: This is not working correctly...
self.obj = None
menu.parent.reload()

menu.add_menu_item(SeparatorMenuItem())
menu.add_menu_item(context_menu.SeparatorMenuItem())
menu.add_menu_item(
ButtonMenuItem(
context_menu.ButtonMenuItem(
"Set to default",
None,
True,
Expand Down Expand Up @@ -1294,7 +1289,7 @@ def obj_view_settings(self) -> ObjViewSettings:
else:
return subentry.obj_view_settings

def modify_context_menu(self, menu: ContextMenu):
def modify_context_menu(self, menu: "context_menu.ContextMenu"):
subentry = self._get_subentry()
if subentry is None:
return
Expand Down Expand Up @@ -1382,7 +1377,7 @@ def obj_view_settings(self) -> ObjViewSettings:
else:
return subentry.obj_view_settings

def modify_context_menu(self, menu: ContextMenu):
def modify_context_menu(self, menu: "context_menu.ContextMenu"):
subentry = self._get_subentry()
if subentry is None:
return
Expand Down
4 changes: 2 additions & 2 deletions construct_editor/wx_widgets/wx_construct_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def GetMode(self) -> int:
# this always works.)

dvc: "dv.DataViewCtrl" = self.GetView()
editor: "WxConstructEditor" = dvc.GetParent()
editor = t.cast("WxConstructEditor", dvc.GetParent())
selected_entry = editor.get_selected_entry()
if selected_entry is None:
mode = dv.DATAVIEW_CELL_INERT
Expand Down Expand Up @@ -127,7 +127,7 @@ def CreateEditorCtrl(
view_settings = value.obj_view_settings
editor: WxObjEditor = create_obj_editor(parent, view_settings)
editor.SetPosition(labelRect.Position)
editor.SetSize(labelRect.Size)
editor.SetSize(labelRect.Size) # type: ignore[call-overload]
return editor

def GetValueFromEditorCtrl(self, editor: WxObjEditor):
Expand Down
10 changes: 5 additions & 5 deletions construct_editor/wx_widgets/wx_hex_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def __init__(self, editor: "WxHexEditor", binary_data: HexEditorBinaryData):

self._attr_default = Grid.GridCellAttr()
self._attr_default.SetFont(self.font)
self._attr_default.SetBackgroundColour("white")
self._attr_default.SetBackgroundColour(wx.WHITE)

self._attr_selected = Grid.GridCellAttr()
self._attr_selected.SetFont(self.font)
Expand Down Expand Up @@ -381,7 +381,7 @@ def on_key_down(self, evt):
key = evt.GetKeyCode()

if key == wx.WXK_BACK or key == wx.WXK_DELETE:
self.SetValue(self.startValue)
self.SetValue(self.startValue or "")
self.Clear()

if key == wx.WXK_TAB:
Expand All @@ -394,7 +394,7 @@ def on_key_down(self, evt):
or key == wx.WXK_LEFT
or key == wx.WXK_RIGHT
):
self.SetValue(self.startValue)
self.SetValue(self.startValue or "")
wx.CallAfter(self.parentgrid._abort_edit)
return
elif self.mode == "hex":
Expand Down Expand Up @@ -650,7 +650,7 @@ def __init__(
self.SetRowLabelSize(50)
self.SetColLabelSize(20)

self.RegisterDataType(Grid.GRID_VALUE_STRING, None, None)
self.RegisterDataType(Grid.GRID_VALUE_STRING, None, None) # type: ignore
self.SetDefaultEditor(HexCellEditor(self))

self.ShowScrollbars(wx.SHOW_SB_ALWAYS, wx.SHOW_SB_ALWAYS)
Expand Down Expand Up @@ -1315,7 +1315,7 @@ class MyFrame(wx.Frame):
"""We simply derive a new class of Frame."""

def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(420, 800))
wx.Frame.__init__(self, parent, title=title, size=wx.Size(420, 800))

# Create an instance of our model...
self.hex_editor = WxHexEditor(self)
Expand Down
12 changes: 8 additions & 4 deletions construct_editor/wx_widgets/wx_obj_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class WxObjEditor_Default(wx.TextCtrl):
def __init__(self, parent, settings: ObjViewSettings):
self.entry = settings.entry

super(wx.TextCtrl, self).__init__(
wx.TextCtrl.__init__(
self,
parent,
wx.ID_ANY,
self.entry.obj_str,
Expand All @@ -48,7 +49,8 @@ class WxObjEditor_String(wx.TextCtrl):
def __init__(self, parent, settings: ObjViewSettings_String):
self.entry = settings.entry

super(wx.TextCtrl, self).__init__(
wx.TextCtrl.__init__(
self,
parent,
wx.ID_ANY,
self.entry.obj_str,
Expand All @@ -66,7 +68,8 @@ class WxObjEditor_Integer(wx.TextCtrl):
def __init__(self, parent, settings: ObjViewSettings_Integer):
self.entry = settings.entry

super(wx.TextCtrl, self).__init__(
wx.TextCtrl.__init__(
self,
parent,
wx.ID_ANY,
self.entry.obj_str,
Expand All @@ -91,7 +94,8 @@ class WxObjEditor_Bytes(wx.TextCtrl):
def __init__(self, parent, settings: ObjViewSettings_Bytes):
self.entry = settings.entry

super(wx.TextCtrl, self).__init__(
wx.TextCtrl.__init__(
self,
parent,
wx.ID_ANY,
settings.entry.obj_str,
Expand Down
2 changes: 1 addition & 1 deletion construct_editor/wx_widgets/wx_python_code_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ def SetUpEditor(self):
# Caret color
self.SetCaretForeground("BLUE")
# Selection background
self.SetSelBackground(1, "#66CCFF")
self.SetSelBackground(True, "#66CCFF")

# Attempt to set caret blink rate.
try:
Expand Down
9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ construct-editor = "construct_editor.main:main"
dev = [
"poethepoet>=0.46.0",
"pyright>=1.1.410",
"pytest>=8.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"pytest>=8.0.0",
"pytest>=9.0.3",

"pytest-mock>=3.14.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add pytest_cov as well and have the CI do a coverage check?

"ruff>=0.15.16",
"ty>=0.0.46",
"cryptography", # optional "extra" from construct that the user may or may not have installed
Expand All @@ -85,6 +87,10 @@ include = [
[tool.setuptools.package-data]
construct_editor = ["py.typed"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v"

[tool.pyright]
typeCheckingMode = "strict"
exclude = [
Expand Down Expand Up @@ -167,6 +173,9 @@ ignore = [
"F841", # Local variable `...` is assigned to but never used
]

[tool.uv]
system-certs = true

[tool.poe.tasks.typecheck]
sequence = [
{ cmd = "ruff check --fix --show-fixes" },
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-
1 change: 1 addition & 0 deletions tests/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is Python default. Why add the explicit markers?

Loading
Loading