Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ jobs:
- name: Run tests (Linux)
if: runner.os == 'Linux'
run: |
xvfb-run -a python -m pytest tests/ -v --cov=. --cov-report=xml --cov-report=term
xvfb-run -a python -m pytest tests/ -v --cov=programver --cov-report=xml --cov-report=term

- name: Run tests (Windows/macOS)
if: runner.os != 'Linux'
run: |
python -m pytest tests/ -v --cov=. --cov-report=xml --cov-report=term
python -m pytest tests/ -v --cov=programver --cov-report=xml --cov-report=term

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v7
Expand Down
90 changes: 61 additions & 29 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

ProgramVer is a Python/tkinter GUI app that replicates Microsoft's `winver` — it displays a customizable window with program version info, copyright notices, and buttons to open a License or EULA file in a secondary window. It is published to PyPI as `programver` and is designed to be forked and customized per-program. Current version: **1.9.0**.
ProgramVer is a Python/tkinter library that displays a customizable `winver`-style version/copyright
dialog — program name, version, copyright notice, and buttons to open a License or EULA file in a
secondary window. It is published to PyPI as `programver`. As of **2.0.0**, it is a real importable
package (`programver.VersionDialog`) rather than a single file meant to be copied into another
project. `main.py` at the repo root is now a runnable demo of the package, not the library itself.

## Commands

Expand Down Expand Up @@ -33,60 +37,86 @@ python -m pytest tests/test_main.py::TestClassName::test_name -v

### Run tests with coverage
```bash
xvfb-run -a python -m pytest tests/ --cov=. --cov-report=term-missing
xvfb-run -a python -m pytest tests/ --cov=programver --cov-report=term-missing
```

### Lint
```bash
pylint $(git ls-files '*.py')
```

## Architecture
### Run the demo
```bash
python main.py
# or, once installed:
python -m programver
```

All application logic lives in a single module: **`main.py`**. It exposes four functions:
## Architecture

- `get_resource_path(filename)` — resolves paths relative to the module file (needed for PyPI installs where the CWD may differ from the package location).
- `ProgramVer()` — builds and runs the main tkinter window: logo images, version/copyright labels, and two buttons. Calls `window.mainloop()` so it blocks until the window is closed.
- `openLicense()` — opens `LICENSE.txt` in a new `Tk()` window.
- `openEULA()` — opens `EULA.txt` in a new `Tk()` window.
The library lives in the `programver/` package:

- `programver/dialog.py` — `VersionDialog`, the public class. Construct it with `app_name`,
`version`, `copyright_text`, and optional `license_path`, `eula_path`, `license_blurb`,
`logo_path`, `show_python_powered`, and `window_title`. Call `.show()` to display it.
If a Tk root already exists, `.show()` opens a `Toplevel` instead of taking over the app with
its own `mainloop()` — see `_utils.get_or_create_root`.
- `programver/_utils.py` — `get_or_create_root()` (standalone vs. embedded detection) and
`get_bundled_image_path()` (resolves paths inside `programver/imgs/`, needed for PyPI installs
where the CWD may differ from the package location).
- `programver/_text_viewer.py` — `TextViewer`, the read-only scrollable `Toplevel` window used to
display license/EULA text when their buttons are clicked.
- `programver/imgs/` — bundled image assets (currently `pythonpoweredlengthgif.gif`, the
Python-Powered badge). A consuming project supplies its own logo via `logo_path`.

### Entry points

- `__main__.py` — calls `ProgramVer()`, enabling `python -m programver`.
- `__init__.py` — declares `__all__ = ["main"]` for PyPI packaging.
- `setup.cfg` / `pyproject.toml` / `setup.py` — all register the `programver` console script pointing at `main:ProgramVer`.
- `programver/__init__.py` — exposes `VersionDialog` and `__version__`.
- `programver/__main__.py` — `main()`, enabling `python -m programver` (runs a demo dialog).
- `setup.cfg` / `pyproject.toml` / `setup.py` — all register the `programver` console script
pointing at `programver.__main__:main`.
- `main.py` (repo root) — a second, standalone demo showing how a consuming project would wire up
`VersionDialog` with its own copyright/license/EULA text. Not imported by the package itself.

### Key files

| Path | Purpose |
|------|---------|
| `main.py` | All application logic |
| `programver/dialog.py` | `VersionDialog` — the public API |
| `programver/_utils.py` | Root/Toplevel detection, bundled image path resolution |
| `programver/_text_viewer.py` | `TextViewer` — license/EULA text window |
| `programver/imgs/` | Bundled image assets (Python-Powered badge) |
| `main.py` | Standalone demo entry point |
| `tests/test_main.py` | Unit tests (mocked tkinter) |
| `imgs/` | Image assets (`dfdlogo.gif`, `pythonpoweredlengthgif.gif`) |
| `LICENSE.txt` | License text displayed at runtime by `openLicense()` |
| `EULA.txt` | EULA text displayed at runtime by `openEULA()` |
| `LICENSE.md` | License text; `main.py`'s demo points its `license_path` here |
| `EULA.md` | EULA text; `main.py`'s demo points its `eula_path` here |
| `pytest.ini` | Pytest configuration (testpaths, addopts) |
| `.deepsource.toml` | DeepSource static analysis config (uses `black` formatter) |

**Customization intent:** The strings inside `ProgramVer()` (window title, version label, trademark text, license blurb) and the image files in `imgs/` are expected to be replaced when the project is forked. `LICENSE.txt` and `EULA.txt` in the repo root are the files opened at runtime.
**Customization intent:** `VersionDialog` is now a real, parameterized class — consuming projects
construct it with their own name, version, copyright text, and file paths rather than editing
literals in a copied file. `main.py` demonstrates this usage and is a reasonable starting point to
adapt, but is not itself imported by `programver`.

## Testing

Tests are in `tests/test_main.py` using `unittest.TestCase` with five test classes:
Tests are in `tests/test_main.py` using `unittest.TestCase`:

- `TestGetResourcePath` — path resolution helper
- `TestOpenLicense` — license window creation and content display
- `TestOpenEULA` — EULA window creation and content display
- `TestProgramVer` — main window components (images, labels, buttons, commands)
- `TestModuleIntegration` — import and callable checks
- `TestVersionDialogInit` — constructor parameter storage and defaults
- `TestVersionDialogShow` — `.show()` behavior: standalone vs. embedded, window title, labels,
conditional license/EULA buttons, Python-Powered badge, logo
- `TestTextViewer` — the license/EULA viewer window (Toplevel, title, content, read-only state, scrollbar)
- `TestGetOrCreateRoot` — standalone-vs-embedded root detection
- `TestModuleIntegration` — package imports, `VersionDialog` is a class, `__version__` is set,
the demo `main.py` module imports cleanly

All tkinter calls are mocked with `unittest.mock.patch` so tests run headlessly.

### CI Workflows (`.github/workflows/`)

| Workflow | Trigger | What it does |
|----------|---------|--------------|
| `tests.yml` | push/PR to `master` | Runs pytest across Ubuntu/Windows/macOS x Python 3.9-3.12; uploads coverage to Codecov |
| `tests.yml` | push/PR to `master` | Runs pytest across Ubuntu/Windows/macOS x Python 3.10-3.12; uploads coverage to Codecov |
| `pylint.yml` | any push | Runs pylint on all `.py` files (Python 3.9) |
| `codeql-analysis.yml` | push/PR to `master`, weekly schedule | CodeQL security scanning |
| `push-to-pypi.yml` | GitHub release published | Builds and publishes to PyPI |
Expand All @@ -98,10 +128,12 @@ The default branch is `master`.
- 4-space indentation (no tabs).
- Semantic Versioning for releases.
- Version number appears in **four places** — update all on a version bump:
1. `main.py` (the `info` label text)
2. `pyproject.toml` (`[project] version`)
3. `setup.cfg` (`[metadata] version`)
4. `setup.py` (`version` kwarg)
- The `# pylint: disable=import-error, invalid-name` comments at the top of `main.py`, `__main__.py`, `__init__.py`, and `test_main.py` are intentional — do not remove them.
- `test_main.py` also disables `wrong-import-position`, `import-outside-toplevel`, and `unused-argument` — do not remove these either.
1. `pyproject.toml` (`[project] version`)
2. `setup.cfg` (`[metadata] version`)
3. `setup.py` (`version` kwarg)
4. `programver/__init__.py` (`__version__`)
- The `# pylint: disable=import-error, invalid-name` comments at the top of `main.py` and
`test_main.py` are intentional — do not remove them.
- `test_main.py` also disables `wrong-import-position`, `import-outside-toplevel`, and
`unused-argument` — do not remove these either.
- Black is configured as the code formatter via `.deepsource.toml`.
32 changes: 32 additions & 0 deletions EULA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# End-User License Agreement (EULA)

**ProgramVer**
**Effective Date: 2026-01-01**

By installing, copying, or otherwise using ProgramVer, you agree to the terms of this End-User License Agreement.

## 1. License Grant

Subject to the terms of this agreement and the MIT License, you are granted a non-exclusive, worldwide, royalty-free license to use, copy, modify, and distribute ProgramVer.

## 2. Restrictions

You may not:
- Remove or alter any copyright notices or license text included with the software.
- Misrepresent the origin of the software or claim authorship of the original work.

## 3. Disclaimer of Warranty

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. THE AUTHORS ARE NOT LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.

## 4. Termination

This agreement is effective until terminated. Your rights under this agreement will terminate automatically if you fail to comply with any of its terms.

## 5. Governing Law

This agreement shall be governed by the laws of Canada.

---

Copyright (C) 2017-2026 Dog Face Development Co.
3 changes: 1 addition & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
include imgs/dfdlogo.gif
include imgs/pythonpoweredlengthgif.gif
recursive-include programver/imgs *.gif *.png
43 changes: 31 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,39 +40,58 @@

## Status

**Currently broken as shipped.** `ProgramVer()` loads `imgs/dfdlogo.gif`, which is not in the repository, so the window fails before it appears. The two buttons read `LICENSE.txt` and `EULA.txt`, neither of which exists either.
**2.0.0 — rewritten as a real package.** ProgramVer is now `programver`, an importable package
built around a `VersionDialog` class, instead of a single file meant to be copied into your
project. The issues that made 1.9.0 unable to start (missing `imgs/dfdlogo.gif`, missing
`LICENSE.txt`/`EULA.txt`) are resolved — see [`docs/internal/known-issues.md`](docs/internal/known-issues.md)
for the detailed before/after on each one.

The test suite passes — it mocks every file access and every widget — so CI is green and the program still cannot start. Details and suggested fixes are in [`docs/internal/known-issues.md`](docs/internal/known-issues.md).

The template is sound and the customisation points are real; it needs its assets back.
The rest of `docs/` still describes the pre-2.0 flat `main.py` layout and is being updated
incrementally; treat it as historical until noted otherwise on each page.

## Key Features

- A `winver`-style window: logo, program name and version, trademark notice, licence blurb.
- **Open License** and **Open EULA** buttons that display the full text in their own windows.
- Importable as a function, so you can wire it to your own program's About menu.
- A `VersionDialog` class you construct with your own name, version, and file paths — no
editing library internals.
- Works standalone (creates its own window) or embedded in an existing Tkinter app (opens a
`Toplevel` instead of taking over the event loop).
- Python-Powered badge included.
- Pure standard library — Tkinter only.
- Cross-platform.

## Installation

```bash
pip install programver
```

Or from source:

```bash
git clone https://github.com/willtheorangeguy/ProgramVer
cd ProgramVer
python main.py
python main.py # runs the bundled demo
```

See [`docs/installation.md`](docs/installation.md), including what you need to supply before it runs.

## Usage

```python
from main import ProgramVer
ProgramVer()
from programver import VersionDialog

dialog = VersionDialog(
app_name="YourApp",
version="1.0.0",
copyright_text="Copyright (C) 2026 You. All rights reserved.",
license_path="LICENSE.md",
eula_path="EULA.md",
)
dialog.show()
```

Every string in the window is meant to be edited for your project — see [`docs/configuration.md`](docs/configuration.md).
See `main.py` in this repository for a complete, runnable example, including an optional logo
and license blurb.

## Documentation

Expand Down Expand Up @@ -118,4 +137,4 @@ Sponsor [@willtheorangeguy](https://github.com/willtheorangeguy) on [PayPal](htt

MIT — see [`LICENSE.md`](LICENSE.md).

> Note the window itself currently displays a GPL blurb and a different copyright holder. That text is placeholder content meant to be replaced per project, but it does not match this repository's own licence — see [`docs/internal/known-issues.md`](docs/internal/known-issues.md).
> Note `main.py`'s demo window displays a GPL blurb and a different copyright holder on purpose, to show that this text is meant to be replaced per project it does not describe this repository's own licence. `python -m programver` shows this repository's actual MIT notice. See [`docs/internal/known-issues.md`](docs/internal/known-issues.md).
5 changes: 0 additions & 5 deletions __init__.py

This file was deleted.

8 changes: 0 additions & 8 deletions __main__.py

This file was deleted.

7 changes: 7 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# ProgramVer — Architecture

!!! warning "Pre-2.0 layout"
This page describes the pre-2.0 flat `main.py` layout. As of 2.0.0 the library is the
`programver` package (`VersionDialog`, `programver/dialog.py`) and `main.py` is a demo,
not the module itself — see [Known Issues](internal/known-issues.md) for what changed and
the [README](https://github.com/willtheorangeguy/ProgramVer#readme) for current usage. This
page is pending a rewrite for 2.0.

One module, three functions, no dependencies.

```text
Expand Down
7 changes: 7 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# ProgramVer — Configuration

!!! warning "Pre-2.0 layout"
This page describes the pre-2.0 flat `main.py` layout. As of 2.0.0 the library is the
`programver` package (`VersionDialog`, `programver/dialog.py`) and `main.py` is a demo,
not the module itself — see [Known Issues](internal/known-issues.md) for what changed and
the [README](https://github.com/willtheorangeguy/ProgramVer#readme) for current usage. This
page is pending a rewrite for 2.0.

ProgramVer is a template. There is no config file — you edit `main.py`, and every editable
string carries a `# change as needed` comment.

Expand Down
7 changes: 7 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# ProgramVer — Development

!!! warning "Pre-2.0 layout"
This page describes the pre-2.0 flat `main.py` layout. As of 2.0.0 the library is the
`programver` package (`VersionDialog`, `programver/dialog.py`) and `main.py` is a demo,
not the module itself — see [Known Issues](internal/known-issues.md) for what changed and
the [README](https://github.com/willtheorangeguy/ProgramVer#readme) for current usage. This
page is pending a rewrite for 2.0.

## Setup

```bash
Expand Down
7 changes: 7 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# ProgramVer — FAQ

!!! warning "Pre-2.0 layout"
This page describes the pre-2.0 flat `main.py` layout. As of 2.0.0 the library is the
`programver` package (`VersionDialog`, `programver/dialog.py`) and `main.py` is a demo,
not the module itself — see [Known Issues](internal/known-issues.md) for what changed and
the [README](https://github.com/willtheorangeguy/ProgramVer#readme) for current usage. This
page is pending a rewrite for 2.0.

## It crashes on startup

`ProgramVer()` loads `imgs/dfdlogo.gif`, which is not in the repository, so Tkinter raises a
Expand Down
7 changes: 7 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# ProgramVer — Documentation

!!! warning "Pre-2.0 layout"
This page describes the pre-2.0 flat `main.py` layout. As of 2.0.0 the library is the
`programver` package (`VersionDialog`, `programver/dialog.py`) and `main.py` is a demo,
not the module itself — see [Known Issues](internal/known-issues.md) for what changed and
the [README](https://github.com/willtheorangeguy/ProgramVer#readme) for current usage. This
page is pending a rewrite for 2.0.

A `winver`-style copyright and version window for your own Python programs: a logo, a version
line, a trademark notice, a licence blurb, and buttons that open the full licence and EULA.

Expand Down
7 changes: 7 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# ProgramVer — Installation

!!! warning "Pre-2.0 layout"
This page describes the pre-2.0 flat `main.py` layout. As of 2.0.0 the library is the
`programver` package (`VersionDialog`, `programver/dialog.py`) and `main.py` is a demo,
not the module itself — see [Known Issues](internal/known-issues.md) for what changed and
the [README](https://github.com/willtheorangeguy/ProgramVer#readme) for current usage. This
page is pending a rewrite for 2.0.

## Requirements

| | |
Expand Down
Loading
Loading