feat: add initial WOMM Certification Toolkit implementation
Introduces the Works On My Machine certification framework, including: - CLI tool for project certification and badge/certificate generation - Certification checks per WOMM-STD-001:2026 standard - Supporting documents, assets, and tests - Python package setup with pyproject.toml
This commit is contained in:
commit
1002b16b81
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.idea/
|
||||
poetry.lock
|
||||
64
README.md
Normal file
64
README.md
Normal file
@ -0,0 +1,64 @@
|
||||
# WOMM Certification Toolkit
|
||||
|
||||
**Works On My Machine -- The Official Certification Standard**
|
||||
|
||||
A formal certification framework for the age-old claim "it works on my machine," complete with a bureaucratic standards document, an official seal of approval, and a CLI tool to certify your projects.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
For PNG badge export support:
|
||||
|
||||
```bash
|
||||
pip install -e '.[png]'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Certify a project
|
||||
|
||||
```bash
|
||||
womm certify # certify current directory
|
||||
womm certify /path/to/project
|
||||
womm certify --level gold # target a specific level
|
||||
```
|
||||
|
||||
### Generate a badge
|
||||
|
||||
```bash
|
||||
womm badge # default: gold, womm-seal.svg
|
||||
womm badge --level platinum -o seal.svg
|
||||
```
|
||||
|
||||
### Generate a certificate
|
||||
|
||||
```bash
|
||||
womm certificate # print to stdout
|
||||
womm certificate -o cert.txt # save to file
|
||||
```
|
||||
|
||||
### Read the standard
|
||||
|
||||
```bash
|
||||
womm standard
|
||||
```
|
||||
|
||||
## Certification Levels
|
||||
|
||||
| Level | Requirements |
|
||||
|----------|-------------|
|
||||
| Bronze | Project exists, has source files, no syntax errors |
|
||||
| Silver | Bronze + tests exist and pass |
|
||||
| Gold | Silver + README exists, no TODO/FIXME/HACK comments |
|
||||
| Platinum | Gold + CI config present, git tree is clean |
|
||||
|
||||
## The Standard
|
||||
|
||||
See [WOMM-STD-001:2026](WOMM-STANDARD-001.md) for the full certification standard.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
99
WOMM-CERTIFICATE.txt
Normal file
99
WOMM-CERTIFICATE.txt
Normal file
@ -0,0 +1,99 @@
|
||||
================================================================
|
||||
WOMM CERTIFICATE OF COMPLIANCE
|
||||
WOMM-STD-001:2026
|
||||
================================================================
|
||||
|
||||
Certificate Number: WOMM-20260402162108-3551
|
||||
Date of Issuance: 2026-04-02 16:21:08
|
||||
Standard: WOMM-STD-001:2026, First Edition
|
||||
|
||||
----------------------------------------------------------------
|
||||
PROJECT INFORMATION
|
||||
----------------------------------------------------------------
|
||||
|
||||
Project Name: womm-certification
|
||||
Project Path: /Users/gregory.gauthier/Projects/local/womm-certification
|
||||
Certification Level: BRONZE (It Compiles)
|
||||
|
||||
----------------------------------------------------------------
|
||||
CERTIFICATION AUTHORITY
|
||||
----------------------------------------------------------------
|
||||
|
||||
Certifying Machine: PD2328.local
|
||||
Certifying User: gregory.gauthier
|
||||
Operating System: Darwin 24.6.0
|
||||
Python Version: 3.9.6
|
||||
|
||||
This certification was performed on the above machine and is
|
||||
valid exclusively for this machine in its current configuration,
|
||||
including all running processes, environment variables, and the
|
||||
certifier's current emotional state.
|
||||
|
||||
----------------------------------------------------------------
|
||||
AUDIT TRAIL
|
||||
----------------------------------------------------------------
|
||||
|
||||
| Check | Result | Detail |
|
||||
|------------------------------------------|--------|--------|
|
||||
| Project directory exists | PASS | Project directory located. We're off to a strong start. |
|
||||
| Contains source code | PASS | Source files detected. This is, in fact, a software project. |
|
||||
| Python syntax check | PASS | All Python files parse cleanly. The AST smiles upon you. |
|
||||
| Test suite exists | PASS | Test suite located. Someone here believes in accountability. |
|
||||
| Tests pass | FAIL | Could not find a test runner. Neither pytest nor unittest obliged. |
|
||||
| README exists | PASS | README found. Documentation: the thought that counts. |
|
||||
| No TODO/FIXME/HACK comments | FAIL | Found markers in: test_certify.py:102, certify.py:215, certify.py:233. The guilt is documented. |
|
||||
| CI configuration exists | FAIL | No CI configuration found. You are the CI. |
|
||||
| Git working tree is clean | FAIL | Uncommitted changes detected. Living dangerously. |
|
||||
|
||||
----------------------------------------------------------------
|
||||
DECLARATION
|
||||
----------------------------------------------------------------
|
||||
|
||||
I, Greg Gauthier, do hereby certify that the software
|
||||
project "womm-certification" has been evaluated in accordance
|
||||
with WOMM-STD-001:2026 and has achieved:
|
||||
|
||||
*** WOMM BRONZE CERTIFICATION ***
|
||||
|
||||
This certification is granted under the following conditions:
|
||||
|
||||
1. The software was observed to work on my machine.
|
||||
2. No guarantee is made regarding any other machine.
|
||||
3. This certificate is void if anyone asks "are you sure?"
|
||||
4. Validity expires upon the next git pull, dependency update,
|
||||
or passage of more than 24 hours.
|
||||
|
||||
----------------------------------------------------------------
|
||||
SIGNATURES
|
||||
----------------------------------------------------------------
|
||||
|
||||
Certified by:
|
||||
|
||||
___________________________
|
||||
Greg Gauthier
|
||||
My Machine
|
||||
2026-04-02
|
||||
|
||||
Witnessed by:
|
||||
|
||||
___________________________
|
||||
/dev/null
|
||||
(The Void)
|
||||
|
||||
|
||||
Approved by the WOMM Standards Committee:
|
||||
|
||||
___________________________
|
||||
The Committee
|
||||
(Quorum: 1)
|
||||
|
||||
|
||||
================================================================
|
||||
This document was generated in compliance with WOMM-STD-001:2026
|
||||
"Works On My Machine" Certification Standard
|
||||
First Edition, 2026-04-02
|
||||
|
||||
Copyright 2026 The WOMM Standards Committee.
|
||||
All rights reserved, except the right to guarantee
|
||||
it works on your machine.
|
||||
================================================================
|
||||
482
WOMM-STANDARD-001.md
Normal file
482
WOMM-STANDARD-001.md
Normal file
@ -0,0 +1,482 @@
|
||||
# WOMM-STD-001:2026
|
||||
|
||||
## Works On My Machine — Certification Standard
|
||||
|
||||
**First Edition — 2026-04-02**
|
||||
|
||||
Published by the **International Bureau of Local Development Assurance (IBLDA)**
|
||||
on behalf of the **WOMM Standards Committee (WSC)**
|
||||
|
||||
---
|
||||
|
||||
> *"It works on my machine."*
|
||||
> — Every developer, at some point
|
||||
|
||||
---
|
||||
|
||||
## Document Classification
|
||||
|
||||
| Field | Value |
|
||||
|----------------------|--------------------------------------------|
|
||||
| Standard Number | WOMM-STD-001:2026 |
|
||||
| Edition | First |
|
||||
| Status | **ACTIVE** |
|
||||
| Classification | Public — Developer Eyes Only |
|
||||
| Supersedes | Verbal assurances, Slack messages, shrugs |
|
||||
| Committee | WOMM Standards Committee (WSC/TC-1) |
|
||||
| Secretariat | IBLDA, Geneva (or wherever the laptop is) |
|
||||
|
||||
---
|
||||
|
||||
## Foreword
|
||||
|
||||
The WOMM Standards Committee (WSC) was convened in response to an
|
||||
industry-wide crisis of confidence in the phrase "it works on my
|
||||
machine." For decades, this declaration has served as the final word
|
||||
in software dispute resolution, yet it has lacked the formal rigour
|
||||
demanded by modern engineering practice.
|
||||
|
||||
This standard provides a comprehensive framework for evaluating,
|
||||
certifying, and communicating the operational status of software
|
||||
within the confines of a single developer's workstation. It is the
|
||||
product of extensive deliberation by experts from such distinguished
|
||||
fields as "closing the laptop and hoping for the best" and "I swear
|
||||
I didn't change anything."
|
||||
|
||||
The WSC acknowledges that this standard cannot guarantee software
|
||||
will work on any machine other than the one on which certification
|
||||
was performed. This is, in fact, the entire point.
|
||||
|
||||
Participation in this certification programme is voluntary. However,
|
||||
developers who do not participate will be asked "but did you even
|
||||
try?" at their next standup.
|
||||
|
||||
---
|
||||
|
||||
## 1. Scope
|
||||
|
||||
### 1.1 Purpose
|
||||
|
||||
This standard establishes the requirements for certifying that a
|
||||
software project **works on the certifier's machine** at the time
|
||||
of certification. It provides:
|
||||
|
||||
a) A common vocabulary for describing the state of "working"
|
||||
|
||||
b) A tiered certification framework with escalating levels of
|
||||
assurance
|
||||
|
||||
c) Procedures for auditing and verifying compliance
|
||||
|
||||
d) A formal mechanism for responding to the question "well, does
|
||||
it work?"
|
||||
|
||||
### 1.2 Applicability
|
||||
|
||||
This standard applies to any software project, repository, script,
|
||||
or "quick hack I threw together" that resides on a local filesystem
|
||||
and is subject to the claim "it works on my machine."
|
||||
|
||||
### 1.3 Limitations
|
||||
|
||||
This standard explicitly does **not** certify that the software:
|
||||
|
||||
- Works on anyone else's machine
|
||||
- Will continue to work after the next `git pull`
|
||||
- Works when the Wi-Fi is off (unless the project does not require
|
||||
Wi-Fi, in which case, congratulations)
|
||||
- Works in production
|
||||
- Works
|
||||
|
||||
The certification is valid only for the exact hardware, software,
|
||||
environment variables, running background processes, phase of the
|
||||
moon, and emotional state of the developer present at the time of
|
||||
certification.
|
||||
|
||||
---
|
||||
|
||||
## 2. Normative References
|
||||
|
||||
The following documents are indispensable for the application of
|
||||
this standard. For dated references, only the edition cited applies.
|
||||
For undated references, good luck.
|
||||
|
||||
| Reference | Title |
|
||||
|----------------------|----------------------------------------------------|
|
||||
| WOMM-STD-000:2025 | Definition of "It" in Software Contexts |
|
||||
| WOMM-STD-002:2026 | Requirements for the Phrase "I Didn't Touch That" |
|
||||
| WOMM-STD-003:2026 | Guidelines for Reproducible Shrugging |
|
||||
| IEEE 404 | Standard Not Found |
|
||||
| RFC 2324 | Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0)|
|
||||
| ISO 9001 | Quality management systems (cited for comedic contrast) |
|
||||
| xkcd 1172 | Workflow (normative) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Terms and Definitions
|
||||
|
||||
For the purposes of this standard, the following terms and
|
||||
definitions apply.
|
||||
|
||||
### 3.1 works
|
||||
|
||||
The software executes without producing an error message that the
|
||||
developer cannot explain. Note: errors that the developer *can*
|
||||
explain (e.g., "oh that's fine, it always does that") do not
|
||||
constitute failure.
|
||||
|
||||
### 3.2 my machine
|
||||
|
||||
The specific computer, virtual machine, container, or cardboard
|
||||
box with blinking lights on which the developer performs their
|
||||
work. "My machine" is implicitly scoped to the exact configuration
|
||||
present at certification time, including but not limited to:
|
||||
|
||||
- Operating system and version
|
||||
- Installed packages and their versions
|
||||
- Environment variables (exported and forgotten)
|
||||
- Browser tabs currently open
|
||||
- Number of Docker containers running
|
||||
- Whether Spotify is playing lo-fi beats
|
||||
|
||||
### 3.3 it
|
||||
|
||||
The software under certification. The antecedent of "it" must be
|
||||
identifiable from context, project directory, or vigorous pointing
|
||||
at a screen. See WOMM-STD-000.
|
||||
|
||||
### 3.4 compiles
|
||||
|
||||
The software can be transformed from source code into an executable
|
||||
or interpretable form without the build tool exiting with a non-zero
|
||||
status code. Warnings are not errors. Warnings have never been
|
||||
errors. We do not speak of `-Werror`.
|
||||
|
||||
### 3.5 tests pass
|
||||
|
||||
All automated tests in the project's test suite complete with a
|
||||
passing status. Tests that are skipped, marked as expected failures,
|
||||
or commented out with `# TODO: fix this later` are considered to
|
||||
have passed in the spiritual sense.
|
||||
|
||||
### 3.6 but did you pull main?
|
||||
|
||||
A ritual question posed during certification disputes. The correct
|
||||
response is always "yes" regardless of whether one has, in fact,
|
||||
pulled main.
|
||||
|
||||
### 3.7 the vibes
|
||||
|
||||
An intangible but critical quality metric. Software that works but
|
||||
feels wrong has poor vibes and may be subject to additional scrutiny
|
||||
under Section 6.
|
||||
|
||||
### 3.8 nondeterministic
|
||||
|
||||
A word used to describe test failures that only happen in CI. See
|
||||
also: "cosmic rays", "timing issue", "works on my machine."
|
||||
|
||||
---
|
||||
|
||||
## 4. Certification Levels
|
||||
|
||||
### 4.1 General
|
||||
|
||||
WOMM certification is awarded at one of four levels, each
|
||||
representing an increasing degree of assurance that the software
|
||||
works on the certifier's machine.
|
||||
|
||||
### 4.2 Level Definitions
|
||||
|
||||
#### 4.2.1 WOMM Bronze — "It Compiles"
|
||||
|
||||
Requirements:
|
||||
|
||||
a) The project directory exists and contains at least one source
|
||||
file recognisable as code
|
||||
|
||||
b) The source code can be parsed without syntax errors
|
||||
|
||||
c) The developer can point to the project and say "that's the
|
||||
one" with reasonable confidence
|
||||
|
||||
Bronze certification represents the minimum viable claim that
|
||||
software exists and is not, in fact, just a README.
|
||||
|
||||
#### 4.2.2 WOMM Silver — "Tests Pass"
|
||||
|
||||
Requirements:
|
||||
|
||||
a) All requirements of WOMM Bronze
|
||||
|
||||
b) A test suite exists within the project
|
||||
|
||||
c) The test suite executes and all tests pass (see definition 3.5)
|
||||
|
||||
d) The developer did not achieve this by deleting the failing tests
|
||||
|
||||
Silver certification indicates that the software does what someone,
|
||||
at some point, thought it should do.
|
||||
|
||||
#### 4.2.3 WOMM Gold — "Production Ready*"
|
||||
|
||||
\*on my machine
|
||||
|
||||
Requirements:
|
||||
|
||||
a) All requirements of WOMM Silver
|
||||
|
||||
b) A README file exists and contains at least one true statement
|
||||
about the project
|
||||
|
||||
c) No `TODO`, `FIXME`, or `HACK` comments remain in the codebase
|
||||
(or they have been reclassified as "known architectural decisions")
|
||||
|
||||
d) The developer has mass-suppressed that vague feeling that
|
||||
something isn't quite right
|
||||
|
||||
Gold certification indicates that the software meets a standard of
|
||||
quality that the developer would be comfortable showing to a
|
||||
colleague, provided the colleague does not look too closely.
|
||||
|
||||
#### 4.2.4 WOMM Platinum — "Works Everywhere*"
|
||||
|
||||
\*that I have tested, which is here
|
||||
|
||||
Requirements:
|
||||
|
||||
a) All requirements of WOMM Gold
|
||||
|
||||
b) A continuous integration configuration file is present in the
|
||||
repository (e.g., `.github/workflows/`, `Jenkinsfile`,
|
||||
`.gitlab-ci.yml`, `bitbucket-pipelines.yml`)
|
||||
|
||||
c) The git working tree is clean (no uncommitted changes)
|
||||
|
||||
d) The developer has mass-suppressed the even stronger vague feeling
|
||||
that something isn't quite right
|
||||
|
||||
Platinum certification represents the highest level of assurance
|
||||
available under this standard. It indicates that the developer has
|
||||
not only verified local functionality but has at least *gestured*
|
||||
toward the concept of reproducibility.
|
||||
|
||||
---
|
||||
|
||||
## 5. Certification Procedure
|
||||
|
||||
### 5.1 Pre-Certification
|
||||
|
||||
Before initiating the certification process, the certifier SHALL:
|
||||
|
||||
a) Close all unrelated terminal windows to reduce anxiety
|
||||
|
||||
b) Ensure the machine is in a "known good state" (defined as: the
|
||||
state it's in right now)
|
||||
|
||||
c) Optionally mutter "okay, let's see" under their breath
|
||||
|
||||
### 5.2 Certification Execution
|
||||
|
||||
Certification SHALL be performed by executing the WOMM CLI tool
|
||||
(`womm certify`) in the root directory of the project under test.
|
||||
The tool will automatically evaluate all applicable requirements
|
||||
and assign the highest achievable certification level.
|
||||
|
||||
Manual certification (i.e., just saying "it works on my machine"
|
||||
without running the tool) is deprecated as of this edition but
|
||||
remains in widespread use.
|
||||
|
||||
### 5.3 Post-Certification
|
||||
|
||||
Upon successful certification, the certifier SHALL:
|
||||
|
||||
a) Generate a certificate of compliance using `womm certificate`
|
||||
|
||||
b) Optionally affix the WOMM seal to the project's README
|
||||
|
||||
c) Lean back in their chair with quiet satisfaction
|
||||
|
||||
d) Not touch anything else
|
||||
|
||||
### 5.4 Recertification
|
||||
|
||||
Certification is valid until:
|
||||
|
||||
- Any file in the project is modified
|
||||
- Any dependency is updated
|
||||
- The developer runs `git pull`
|
||||
- The machine is restarted
|
||||
- More than 24 hours have elapsed
|
||||
- Someone asks "are you sure?"
|
||||
|
||||
Recertification follows the same procedure as initial certification.
|
||||
|
||||
---
|
||||
|
||||
## 6. Audit Procedures
|
||||
|
||||
### 6.1 General
|
||||
|
||||
An audit may be requested by any stakeholder who has reason to
|
||||
believe that the claim "it works on my machine" is unsubstantiated.
|
||||
Common triggers include:
|
||||
|
||||
- The software does not work on the auditor's machine
|
||||
- The CI pipeline is red
|
||||
- A customer has reported an issue
|
||||
- It's Monday
|
||||
|
||||
### 6.2 Audit Process
|
||||
|
||||
The audit SHALL proceed as follows:
|
||||
|
||||
a) The auditor requests the certifier demonstrate the software
|
||||
working on their machine
|
||||
|
||||
b) The certifier navigates to the project directory, ideally while
|
||||
saying "it was working five minutes ago"
|
||||
|
||||
c) The `womm certify` command is executed
|
||||
|
||||
d) Results are compared against the claimed certification level
|
||||
|
||||
e) If certification fails, proceed to Section 7
|
||||
|
||||
### 6.3 Environmental Considerations
|
||||
|
||||
The auditor must acknowledge that the software's behaviour may be
|
||||
influenced by factors beyond the developer's control, including but
|
||||
not limited to:
|
||||
|
||||
- The auditor's presence (observer effect)
|
||||
- Solar flares
|
||||
- npm
|
||||
- The fact that Mercury is in retrograde
|
||||
|
||||
---
|
||||
|
||||
## 7. Non-Conformance and Appeals
|
||||
|
||||
### 7.1 Non-Conformance
|
||||
|
||||
A non-conformance occurs when the software fails to meet the
|
||||
requirements of its claimed certification level during an audit.
|
||||
|
||||
### 7.2 Immediate Remediation Steps
|
||||
|
||||
Upon discovery of non-conformance, the following steps SHALL be
|
||||
attempted in order:
|
||||
|
||||
1. Run the command again
|
||||
2. Run the command again, but slower
|
||||
3. Clear all caches (local, browser, DNS, emotional)
|
||||
4. Run `git status` and stare at the output pensively
|
||||
5. Check if the correct branch is checked out
|
||||
6. Ask "did someone push something?"
|
||||
7. Restart the machine
|
||||
8. Restart the machine again, but with feeling
|
||||
|
||||
### 7.3 Appeals
|
||||
|
||||
If remediation is unsuccessful, the certifier may appeal by filing
|
||||
a Formal Declaration of Bewilderment (Form WOMM-7B), which must
|
||||
include:
|
||||
|
||||
- Timestamp of last known working state
|
||||
- A sworn statement that "I literally didn't change anything"
|
||||
- Screenshot evidence (optional but strongly recommended)
|
||||
- Stack Overflow link that seems relevant but isn't
|
||||
|
||||
---
|
||||
|
||||
## 8. Marking and Labelling
|
||||
|
||||
### 8.1 WOMM Seal
|
||||
|
||||
Projects that have achieved WOMM certification MAY display the
|
||||
official WOMM Seal of Approval in their documentation, README, or
|
||||
office wall.
|
||||
|
||||
The seal SHALL include:
|
||||
|
||||
- The text "WOMM CERTIFIED"
|
||||
- The certification level (Bronze, Silver, Gold, or Platinum)
|
||||
- The date of certification
|
||||
- The text "Works On My Machine" or an approved abbreviation
|
||||
|
||||
### 8.2 Usage Restrictions
|
||||
|
||||
The WOMM Seal SHALL NOT be used to imply that the software works
|
||||
on any machine other than the certifier's. Misuse of the seal
|
||||
constitutes a violation of WOMM-STD-001 and may result in the
|
||||
offender being asked to "fix it in production, then."
|
||||
|
||||
---
|
||||
|
||||
## Appendix A (Informative): Common Excuses and Their Classification
|
||||
|
||||
| Excuse | Classification | Accepted? |
|
||||
|--------|---------------|-----------|
|
||||
| "It works on my machine" | Standard claim | Yes (with certification) |
|
||||
| "I didn't change anything" | Denial | Under investigation |
|
||||
| "It must be a caching issue" | Deflection | Provisionally accepted |
|
||||
| "That test is flaky" | Technical excuse | Accepted if test is, in fact, flaky |
|
||||
| "Works in debug mode" | Partial conformance | Bronze only |
|
||||
| "The API must be down" | External attribution | Requires evidence |
|
||||
| "Have you tried clearing your node_modules?" | Counter-audit | Procedurally valid |
|
||||
| "It's a known issue" | Acknowledgement | Accepted if issue is, in fact, known |
|
||||
| "That's not a bug, it's a feature" | Reclassification | Requires product owner sign-off |
|
||||
| "I'll fix it tomorrow" | Deferral | Not accepted for certification |
|
||||
| "It worked yesterday" | Temporal defence | Expired |
|
||||
| "It works if you do it in the right order" | User-error claim | Gold and above only |
|
||||
|
||||
---
|
||||
|
||||
## Appendix B (Normative): Official WOMM Seal Usage Guidelines
|
||||
|
||||
### B.1 Minimum Seal Size
|
||||
|
||||
The WOMM Seal SHALL be displayed at a minimum size of 64x64 pixels
|
||||
in digital media, or 20mm diameter in print. Smaller sizes risk the
|
||||
seal being mistaken for a loading spinner.
|
||||
|
||||
### B.2 Colour Requirements
|
||||
|
||||
The seal SHALL be displayed in the official WOMM colour palette:
|
||||
|
||||
| Level | Primary Colour | Hex |
|
||||
|----------|---------------|---------|
|
||||
| Bronze | Copper | #B87333 |
|
||||
| Silver | Silver | #C0C0C0 |
|
||||
| Gold | Gold | #FFD700 |
|
||||
| Platinum | Platinum | #E5E4E2 |
|
||||
|
||||
### B.3 Prohibited Modifications
|
||||
|
||||
The following modifications to the seal are prohibited:
|
||||
|
||||
- Adding the word "probably" before "certified"
|
||||
- Replacing the checkmark with a question mark
|
||||
- Displaying the seal upside-down (this indicates WOMM Decertification)
|
||||
- Using the seal as a loading animation
|
||||
- Tattooing the seal on any body part (this is not prohibited per se,
|
||||
but the Committee questions your judgement)
|
||||
|
||||
---
|
||||
|
||||
## Appendix C (Informative): Revision History
|
||||
|
||||
| Version | Date | Description |
|
||||
|---------|------------|-------------------------------------------------|
|
||||
| 0.1 | 2025-01-15 | Initial draft, written on a napkin |
|
||||
| 0.2 | 2025-06-01 | Added certification levels after heated debate |
|
||||
| 0.9 | 2025-11-30 | Committee reached quorum (2 people) |
|
||||
| 1.0 | 2026-04-02 | First edition published. It works on my machine. |
|
||||
|
||||
---
|
||||
|
||||
*Copyright 2026 The WOMM Standards Committee. All rights reserved,
|
||||
except the right to guarantee it works on your machine.*
|
||||
|
||||
*END OF WOMM-STD-001:2026*
|
||||
78
assets/womm-seal.svg
Normal file
78
assets/womm-seal.svg
Normal file
@ -0,0 +1,78 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
|
||||
<defs>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:wght@700&display=swap');
|
||||
.seal-text { font-family: 'EB Garamond', 'Georgia', serif; font-weight: 700; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- Outer ring -->
|
||||
<circle cx="200" cy="200" r="190" fill="none" stroke="#1a1a2e" stroke-width="6"/>
|
||||
<circle cx="200" cy="200" r="180" fill="none" stroke="#1a1a2e" stroke-width="2"/>
|
||||
|
||||
<!-- Decorative dots on outer ring -->
|
||||
<g fill="#1a1a2e">
|
||||
<circle cx="200" cy="14" r="4"/>
|
||||
<circle cx="200" cy="386" r="4"/>
|
||||
<circle cx="14" cy="200" r="4"/>
|
||||
<circle cx="386" cy="200" r="4"/>
|
||||
<!-- Diagonal dots -->
|
||||
<circle cx="68" cy="68" r="3"/>
|
||||
<circle cx="332" cy="68" r="3"/>
|
||||
<circle cx="68" cy="332" r="3"/>
|
||||
<circle cx="332" cy="332" r="3"/>
|
||||
</g>
|
||||
|
||||
<!-- Inner ring -->
|
||||
<circle cx="200" cy="200" r="150" fill="none" stroke="#1a1a2e" stroke-width="2"/>
|
||||
<circle cx="200" cy="200" r="140" fill="none" stroke="#1a1a2e" stroke-width="1"/>
|
||||
|
||||
<!-- Star decorations between rings -->
|
||||
<g fill="#1a1a2e" class="seal-text" text-anchor="middle" font-size="16">
|
||||
<text x="200" y="42">★</text>
|
||||
<text x="200" y="370">★</text>
|
||||
<text x="38" y="206">★</text>
|
||||
<text x="362" y="206">★</text>
|
||||
</g>
|
||||
|
||||
<!-- Circular text - top arc: "WORKS ON MY MACHINE" -->
|
||||
<path id="topArc" d="M 80,200 a 120,120 0 0,1 240,0" fill="none"/>
|
||||
<text class="seal-text" font-size="18" fill="#1a1a2e" letter-spacing="3">
|
||||
<textPath href="#topArc" startOffset="50%" text-anchor="middle">WORKS ON MY MACHINE</textPath>
|
||||
</text>
|
||||
|
||||
<!-- Circular text - bottom arc: "CERTIFIED" -->
|
||||
<path id="bottomArc" d="M 320,200 a 120,120 0 0,1 -240,0" fill="none"/>
|
||||
<text class="seal-text" font-size="18" fill="#1a1a2e" letter-spacing="5">
|
||||
<textPath href="#bottomArc" startOffset="50%" text-anchor="middle">CERTIFIED</textPath>
|
||||
</text>
|
||||
|
||||
<!-- Central content area -->
|
||||
<!-- Laptop icon -->
|
||||
<g transform="translate(200,170)" fill="none" stroke="#1a1a2e" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- Screen -->
|
||||
<rect x="-35" y="-30" width="70" height="48" rx="4"/>
|
||||
<!-- Checkmark on screen -->
|
||||
<polyline points="-12,0 -2,10 16,-10" stroke-width="4" stroke="#2d6a4f"/>
|
||||
<!-- Base -->
|
||||
<path d="M-45,18 L-50,28 L50,28 L45,18"/>
|
||||
</g>
|
||||
|
||||
<!-- WOMM text -->
|
||||
<text x="200" y="230" class="seal-text" font-size="42" fill="#1a1a2e" text-anchor="middle" letter-spacing="6">WOMM</text>
|
||||
|
||||
<!-- Divider lines -->
|
||||
<line x1="130" y1="242" x2="270" y2="242" stroke="#1a1a2e" stroke-width="1.5"/>
|
||||
|
||||
<!-- Seal of Approval text -->
|
||||
<text x="200" y="262" class="seal-text" font-size="13" fill="#1a1a2e" text-anchor="middle" letter-spacing="2">SEAL OF APPROVAL</text>
|
||||
|
||||
<!-- Year -->
|
||||
<text x="200" y="285" class="seal-text" font-size="14" fill="#1a1a2e" text-anchor="middle" letter-spacing="1">EST. 2026</text>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<g fill="#1a1a2e">
|
||||
<circle cx="155" cy="285" r="2"/>
|
||||
<circle cx="245" cy="285" r="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
28
pyproject.toml
Normal file
28
pyproject.toml
Normal file
@ -0,0 +1,28 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=68.0", "setuptools-scm>=8.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "womm-certification"
|
||||
version = "1.0.0"
|
||||
description = "Works On My Machine — The Official Certification Toolkit"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
license = "MIT"
|
||||
authors = [
|
||||
{ name = "The WOMM Standards Committee" },
|
||||
]
|
||||
dependencies = [
|
||||
"click>=8.0",
|
||||
"rich>=13.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
png = ["cairosvg>=2.7"]
|
||||
dev = ["pytest>=8.0"]
|
||||
|
||||
[project.scripts]
|
||||
womm = "womm.cli:main"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
3
src/womm/__init__.py
Normal file
3
src/womm/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
"""WOMM — Works On My Machine Certification Toolkit."""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
159
src/womm/badge.py
Normal file
159
src/womm/badge.py
Normal file
@ -0,0 +1,159 @@
|
||||
"""Generate WOMM Seal of Approval badges as SVG."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
from womm.certify import Level
|
||||
|
||||
_SVG_TEMPLATE = """\
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="{size}" height="{size}">
|
||||
<defs>
|
||||
<style>
|
||||
.seal-text {{ font-family: 'Georgia', 'EB Garamond', serif; font-weight: 700; }}
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- Outer ring -->
|
||||
<circle cx="200" cy="200" r="190" fill="none" stroke="{colour}" stroke-width="6"/>
|
||||
<circle cx="200" cy="200" r="180" fill="none" stroke="{colour}" stroke-width="2"/>
|
||||
|
||||
<!-- Decorative dots -->
|
||||
<g fill="{colour}">
|
||||
<circle cx="200" cy="14" r="4"/>
|
||||
<circle cx="200" cy="386" r="4"/>
|
||||
<circle cx="14" cy="200" r="4"/>
|
||||
<circle cx="386" cy="200" r="4"/>
|
||||
<circle cx="68" cy="68" r="3"/>
|
||||
<circle cx="332" cy="68" r="3"/>
|
||||
<circle cx="68" cy="332" r="3"/>
|
||||
<circle cx="332" cy="332" r="3"/>
|
||||
</g>
|
||||
|
||||
<!-- Inner ring -->
|
||||
<circle cx="200" cy="200" r="150" fill="none" stroke="{colour}" stroke-width="2"/>
|
||||
<circle cx="200" cy="200" r="140" fill="none" stroke="{colour}" stroke-width="1"/>
|
||||
|
||||
<!-- Stars between rings -->
|
||||
<g fill="{colour}" class="seal-text" text-anchor="middle" font-size="16">
|
||||
<text x="200" y="42">★</text>
|
||||
<text x="200" y="370">★</text>
|
||||
<text x="38" y="206">★</text>
|
||||
<text x="362" y="206">★</text>
|
||||
</g>
|
||||
|
||||
<!-- Circular text - top: "WORKS ON MY MACHINE" -->
|
||||
<path id="topArc" d="M 80,200 a 120,120 0 0,1 240,0" fill="none"/>
|
||||
<text class="seal-text" font-size="18" fill="{colour}" letter-spacing="3">
|
||||
<textPath href="#topArc" startOffset="50%" text-anchor="middle">WORKS ON MY MACHINE</textPath>
|
||||
</text>
|
||||
|
||||
<!-- Circular text - bottom: "CERTIFIED" -->
|
||||
<path id="bottomArc" d="M 320,200 a 120,120 0 0,1 -240,0" fill="none"/>
|
||||
<text class="seal-text" font-size="18" fill="{colour}" letter-spacing="5">
|
||||
<textPath href="#bottomArc" startOffset="50%" text-anchor="middle">CERTIFIED</textPath>
|
||||
</text>
|
||||
|
||||
<!-- Laptop + checkmark icon -->
|
||||
<g transform="translate(200,165)" fill="none" stroke="{colour}" stroke-width="3"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="-35" y="-30" width="70" height="48" rx="4"/>
|
||||
<polyline points="-12,0 -2,10 16,-10" stroke-width="4" stroke="{check_colour}"/>
|
||||
<path d="M-45,18 L-50,28 L50,28 L45,18"/>
|
||||
</g>
|
||||
|
||||
<!-- WOMM -->
|
||||
<text x="200" y="228" class="seal-text" font-size="42" fill="{colour}"
|
||||
text-anchor="middle" letter-spacing="6">WOMM</text>
|
||||
|
||||
<!-- Divider -->
|
||||
<line x1="120" y1="240" x2="280" y2="240" stroke="{colour}" stroke-width="1.5"/>
|
||||
|
||||
<!-- Level label -->
|
||||
<text x="200" y="260" class="seal-text" font-size="16" fill="{colour}"
|
||||
text-anchor="middle" letter-spacing="3">{level_label}</text>
|
||||
|
||||
<!-- Date -->
|
||||
<text x="200" y="285" class="seal-text" font-size="13" fill="{colour}"
|
||||
text-anchor="middle" letter-spacing="1">{date_str}</text>
|
||||
|
||||
<!-- Dot decorations -->
|
||||
<g fill="{colour}">
|
||||
<circle cx="148" cy="282" r="2"/>
|
||||
<circle cx="252" cy="282" r="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
"""
|
||||
|
||||
_CHECK_COLOURS = {
|
||||
Level.NONE: "#888888",
|
||||
Level.BRONZE: "#8B5E3C",
|
||||
Level.SILVER: "#2d6a4f",
|
||||
Level.GOLD: "#2d6a4f",
|
||||
Level.PLATINUM: "#2d6a4f",
|
||||
}
|
||||
|
||||
|
||||
def generate_badge_svg(
|
||||
level: Level = Level.GOLD,
|
||||
certification_date: date | None = None,
|
||||
size: int = 400,
|
||||
) -> str:
|
||||
"""Generate an SVG string for a WOMM Seal of Approval.
|
||||
|
||||
Args:
|
||||
level: Certification level to display.
|
||||
certification_date: Date to show on the seal. Defaults to today.
|
||||
size: Width and height in pixels.
|
||||
|
||||
Returns:
|
||||
SVG markup as a string.
|
||||
"""
|
||||
cert_date = certification_date or date.today()
|
||||
return _SVG_TEMPLATE.format(
|
||||
size=size,
|
||||
colour=level.colour_hex,
|
||||
check_colour=_CHECK_COLOURS.get(level, "#2d6a4f"),
|
||||
level_label=level.label.upper(),
|
||||
date_str=cert_date.strftime("%Y-%m-%d"),
|
||||
)
|
||||
|
||||
|
||||
def save_badge(
|
||||
output_path: str | Path,
|
||||
level: Level = Level.GOLD,
|
||||
certification_date: date | None = None,
|
||||
size: int = 400,
|
||||
) -> Path:
|
||||
"""Generate and save a WOMM badge.
|
||||
|
||||
Args:
|
||||
output_path: Where to write the file. If it ends in .png and
|
||||
cairosvg is available, a PNG will be generated.
|
||||
level: Certification level.
|
||||
certification_date: Date for the seal.
|
||||
size: Badge dimensions.
|
||||
|
||||
Returns:
|
||||
The resolved output path.
|
||||
"""
|
||||
out = Path(output_path).resolve()
|
||||
svg_content = generate_badge_svg(level, certification_date, size)
|
||||
|
||||
if out.suffix.lower() == ".png":
|
||||
try:
|
||||
import cairosvg # type: ignore[import-untyped]
|
||||
cairosvg.svg2png(bytestring=svg_content.encode(), write_to=str(out))
|
||||
except ImportError:
|
||||
# Fall back to saving SVG with a note
|
||||
svg_path = out.with_suffix(".svg")
|
||||
svg_path.write_text(svg_content, encoding="utf-8")
|
||||
raise SystemExit(
|
||||
f"cairosvg not installed — saved SVG to {svg_path} instead.\n"
|
||||
"Install with: pip install 'womm-certification[png]'"
|
||||
)
|
||||
else:
|
||||
out.write_text(svg_content, encoding="utf-8")
|
||||
|
||||
return out
|
||||
335
src/womm/certify.py
Normal file
335
src/womm/certify.py
Normal file
@ -0,0 +1,335 @@
|
||||
"""WOMM certification checks per WOMM-STD-001:2026."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import os
|
||||
import subprocess
|
||||
from dataclasses import dataclass, field
|
||||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class Level(IntEnum):
|
||||
"""WOMM certification levels (Section 4.2)."""
|
||||
|
||||
NONE = 0
|
||||
BRONZE = 1
|
||||
SILVER = 2
|
||||
GOLD = 3
|
||||
PLATINUM = 4
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
return self.name.capitalize() if self != Level.NONE else "Uncertified"
|
||||
|
||||
@property
|
||||
def colour_hex(self) -> str:
|
||||
return {
|
||||
Level.NONE: "#888888",
|
||||
Level.BRONZE: "#B87333",
|
||||
Level.SILVER: "#C0C0C0",
|
||||
Level.GOLD: "#FFD700",
|
||||
Level.PLATINUM: "#E5E4E2",
|
||||
}[self]
|
||||
|
||||
|
||||
@dataclass
|
||||
class CheckResult:
|
||||
"""Outcome of a single certification check."""
|
||||
|
||||
name: str
|
||||
passed: bool
|
||||
message: str
|
||||
level: Level
|
||||
|
||||
|
||||
@dataclass
|
||||
class CertificationReport:
|
||||
"""Full certification report for a project."""
|
||||
|
||||
project_path: Path
|
||||
project_name: str
|
||||
results: list[CheckResult] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def level(self) -> Level:
|
||||
"""Highest level for which all checks passed."""
|
||||
achieved = Level.PLATINUM
|
||||
for result in self.results:
|
||||
if not result.passed and result.level <= achieved:
|
||||
achieved = Level(result.level - 1)
|
||||
return max(achieved, Level.NONE)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Individual checks
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _check_project_exists(path: Path) -> CheckResult:
|
||||
exists = path.is_dir()
|
||||
return CheckResult(
|
||||
name="Project directory exists",
|
||||
passed=exists,
|
||||
message=(
|
||||
"Project directory located. We're off to a strong start."
|
||||
if exists
|
||||
else "Project directory not found. This is... concerning."
|
||||
),
|
||||
level=Level.BRONZE,
|
||||
)
|
||||
|
||||
|
||||
def _check_has_source_files(path: Path) -> CheckResult:
|
||||
extensions = {".py", ".js", ".ts", ".c", ".cpp", ".go", ".rs", ".java", ".rb", ".sh"}
|
||||
found = any(
|
||||
f.suffix in extensions
|
||||
for f in path.rglob("*")
|
||||
if f.is_file() and ".git" not in f.parts
|
||||
)
|
||||
return CheckResult(
|
||||
name="Contains source code",
|
||||
passed=found,
|
||||
message=(
|
||||
"Source files detected. This is, in fact, a software project."
|
||||
if found
|
||||
else "No recognisable source files found. Are you sure this isn't just a README?"
|
||||
),
|
||||
level=Level.BRONZE,
|
||||
)
|
||||
|
||||
|
||||
def _check_python_syntax(path: Path) -> CheckResult:
|
||||
py_files = [
|
||||
f for f in path.rglob("*.py")
|
||||
if f.is_file() and ".git" not in f.parts
|
||||
]
|
||||
if not py_files:
|
||||
return CheckResult(
|
||||
name="Python syntax check",
|
||||
passed=True,
|
||||
message="No Python files to check. Blissful ignorance achieved.",
|
||||
level=Level.BRONZE,
|
||||
)
|
||||
errors: list[str] = []
|
||||
for py_file in py_files:
|
||||
try:
|
||||
ast.parse(py_file.read_text(encoding="utf-8"), filename=str(py_file))
|
||||
except SyntaxError as exc:
|
||||
errors.append(f"{py_file.name}:{exc.lineno}: {exc.msg}")
|
||||
return CheckResult(
|
||||
name="Python syntax check",
|
||||
passed=len(errors) == 0,
|
||||
message=(
|
||||
"All Python files parse cleanly. The AST smiles upon you."
|
||||
if not errors
|
||||
else f"Syntax errors found ({len(errors)}): {'; '.join(errors[:3])}"
|
||||
),
|
||||
level=Level.BRONZE,
|
||||
)
|
||||
|
||||
|
||||
def _check_tests_exist(path: Path) -> CheckResult:
|
||||
test_indicators = [
|
||||
path / "tests",
|
||||
path / "test",
|
||||
*path.rglob("test_*.py"),
|
||||
*path.rglob("*_test.py"),
|
||||
*path.rglob("tests.py"),
|
||||
]
|
||||
found = any(p.exists() for p in test_indicators)
|
||||
return CheckResult(
|
||||
name="Test suite exists",
|
||||
passed=found,
|
||||
message=(
|
||||
"Test suite located. Someone here believes in accountability."
|
||||
if found
|
||||
else "No test suite found. Bold strategy."
|
||||
),
|
||||
level=Level.SILVER,
|
||||
)
|
||||
|
||||
|
||||
def _check_tests_pass(path: Path) -> CheckResult:
|
||||
# Try pytest, then unittest
|
||||
for cmd in (["python", "-m", "pytest", "-x", "-q"], ["python", "-m", "unittest", "discover", "-s", "."]):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return CheckResult(
|
||||
name="Tests pass",
|
||||
passed=True,
|
||||
message="All tests pass. On this machine, at least.",
|
||||
level=Level.SILVER,
|
||||
)
|
||||
elif result.returncode == 5 and "pytest" in cmd[2]:
|
||||
# pytest exit code 5 = no tests collected
|
||||
continue
|
||||
else:
|
||||
snippet = (result.stdout + result.stderr).strip().split("\n")[-1]
|
||||
return CheckResult(
|
||||
name="Tests pass",
|
||||
passed=False,
|
||||
message=f"Tests failed. {snippet}",
|
||||
level=Level.SILVER,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except subprocess.TimeoutExpired:
|
||||
return CheckResult(
|
||||
name="Tests pass",
|
||||
passed=False,
|
||||
message="Tests timed out after 120s. The machine grew weary.",
|
||||
level=Level.SILVER,
|
||||
)
|
||||
return CheckResult(
|
||||
name="Tests pass",
|
||||
passed=False,
|
||||
message="Could not find a test runner. Neither pytest nor unittest obliged.",
|
||||
level=Level.SILVER,
|
||||
)
|
||||
|
||||
|
||||
def _check_readme_exists(path: Path) -> CheckResult:
|
||||
readmes = [path / name for name in ("README.md", "README.rst", "README.txt", "README")]
|
||||
found = any(r.is_file() for r in readmes)
|
||||
return CheckResult(
|
||||
name="README exists",
|
||||
passed=found,
|
||||
message=(
|
||||
"README found. Documentation: the thought that counts."
|
||||
if found
|
||||
else "No README. Future you will regret this."
|
||||
),
|
||||
level=Level.GOLD,
|
||||
)
|
||||
|
||||
|
||||
def _check_no_todo_comments(path: Path) -> CheckResult:
|
||||
markers = ("TODO", "FIXME", "HACK", "XXX")
|
||||
offenders: list[str] = []
|
||||
for f in path.rglob("*"):
|
||||
if not f.is_file() or ".git" in f.parts or f.suffix not in (
|
||||
".py", ".js", ".ts", ".c", ".cpp", ".go", ".rs", ".java", ".rb",
|
||||
):
|
||||
continue
|
||||
try:
|
||||
for i, line in enumerate(f.read_text(encoding="utf-8", errors="ignore").splitlines(), 1):
|
||||
if any(m in line for m in markers):
|
||||
offenders.append(f"{f.name}:{i}")
|
||||
if len(offenders) >= 5:
|
||||
break
|
||||
except (OSError, UnicodeDecodeError):
|
||||
continue
|
||||
if len(offenders) >= 5:
|
||||
break
|
||||
return CheckResult(
|
||||
name="No TODO/FIXME/HACK comments",
|
||||
passed=len(offenders) == 0,
|
||||
message=(
|
||||
"No shameful markers found. Either you're thorough, or you're sneaky."
|
||||
if not offenders
|
||||
else f"Found markers in: {', '.join(offenders)}. The guilt is documented."
|
||||
),
|
||||
level=Level.GOLD,
|
||||
)
|
||||
|
||||
|
||||
def _check_ci_config_exists(path: Path) -> CheckResult:
|
||||
ci_paths = [
|
||||
path / ".github" / "workflows",
|
||||
path / ".gitlab-ci.yml",
|
||||
path / "Jenkinsfile",
|
||||
path / ".circleci",
|
||||
path / "bitbucket-pipelines.yml",
|
||||
path / ".travis.yml",
|
||||
path / "azure-pipelines.yml",
|
||||
]
|
||||
found = any(p.exists() for p in ci_paths)
|
||||
return CheckResult(
|
||||
name="CI configuration exists",
|
||||
passed=found,
|
||||
message=(
|
||||
"CI config detected. You've at least gestured toward reproducibility."
|
||||
if found
|
||||
else "No CI configuration found. You are the CI."
|
||||
),
|
||||
level=Level.PLATINUM,
|
||||
)
|
||||
|
||||
|
||||
def _check_git_clean(path: Path) -> CheckResult:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "status", "--porcelain"],
|
||||
cwd=path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
clean = result.returncode == 0 and result.stdout.strip() == ""
|
||||
return CheckResult(
|
||||
name="Git working tree is clean",
|
||||
passed=clean,
|
||||
message=(
|
||||
"Working tree is clean. Pristine. Untouched. (For now.)"
|
||||
if clean
|
||||
else "Uncommitted changes detected. Living dangerously."
|
||||
),
|
||||
level=Level.PLATINUM,
|
||||
)
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
return CheckResult(
|
||||
name="Git working tree is clean",
|
||||
passed=False,
|
||||
message="Git not available or not a git repository. The void stares back.",
|
||||
level=Level.PLATINUM,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main certification entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
ALL_CHECKS = [
|
||||
_check_project_exists,
|
||||
_check_has_source_files,
|
||||
_check_python_syntax,
|
||||
_check_tests_exist,
|
||||
_check_tests_pass,
|
||||
_check_readme_exists,
|
||||
_check_no_todo_comments,
|
||||
_check_ci_config_exists,
|
||||
_check_git_clean,
|
||||
]
|
||||
|
||||
|
||||
def certify(path: str | Path | None = None, target_level: Level | None = None) -> CertificationReport:
|
||||
"""Run WOMM certification checks against a project directory.
|
||||
|
||||
Args:
|
||||
path: Project root directory. Defaults to cwd.
|
||||
target_level: If set, only run checks up to this level.
|
||||
|
||||
Returns:
|
||||
A CertificationReport with all check results.
|
||||
"""
|
||||
project_path = Path(path or os.getcwd()).resolve()
|
||||
report = CertificationReport(
|
||||
project_path=project_path,
|
||||
project_name=project_path.name,
|
||||
)
|
||||
|
||||
for check_fn in ALL_CHECKS:
|
||||
result = check_fn(project_path)
|
||||
report.results.append(result)
|
||||
if target_level is not None and result.level > target_level:
|
||||
break
|
||||
|
||||
return report
|
||||
172
src/womm/cli.py
Normal file
172
src/womm/cli.py
Normal file
@ -0,0 +1,172 @@
|
||||
"""WOMM CLI — Works On My Machine Certification Toolkit."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
|
||||
from womm.certify import Level, certify
|
||||
from womm.badge import save_badge
|
||||
from womm.document import generate_certificate, save_certificate
|
||||
|
||||
console = Console()
|
||||
|
||||
LEVEL_STYLES = {
|
||||
Level.NONE: "dim",
|
||||
Level.BRONZE: "rgb(184,115,51)",
|
||||
Level.SILVER: "grey78",
|
||||
Level.GOLD: "yellow",
|
||||
Level.PLATINUM: "white",
|
||||
}
|
||||
|
||||
LEVEL_EMOJI = {
|
||||
Level.NONE: " ",
|
||||
Level.BRONZE: " ",
|
||||
Level.SILVER: " ",
|
||||
Level.GOLD: " ",
|
||||
Level.PLATINUM: " ",
|
||||
}
|
||||
|
||||
|
||||
def _level_choice(value: str) -> Level | None:
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
return Level[value.upper()]
|
||||
except KeyError:
|
||||
raise click.BadParameter(f"Unknown level: {value}. Choose from: bronze, silver, gold, platinum")
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option(package_name="womm-certification")
|
||||
def main() -> None:
|
||||
"""WOMM -- Works On My Machine Certification Toolkit.
|
||||
|
||||
The official tool for certifying that software works on your machine,
|
||||
in accordance with WOMM-STD-001:2026.
|
||||
"""
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("path", default=".", type=click.Path(exists=True))
|
||||
@click.option("--level", "target_level", default=None, help="Target certification level (bronze/silver/gold/platinum).")
|
||||
def certify_cmd(path: str, target_level: str | None) -> None:
|
||||
"""Run WOMM certification checks against a project."""
|
||||
level = _level_choice(target_level) if target_level else None
|
||||
report = certify(path, target_level=level)
|
||||
|
||||
# Header
|
||||
console.print()
|
||||
console.print(
|
||||
Panel(
|
||||
"[bold]WOMM-STD-001:2026[/bold]\nWorks On My Machine — Certification Report",
|
||||
border_style="blue",
|
||||
expand=False,
|
||||
)
|
||||
)
|
||||
console.print(f" Project: [bold]{report.project_name}[/bold]")
|
||||
console.print(f" Path: {report.project_path}")
|
||||
console.print()
|
||||
|
||||
# Results table
|
||||
table = Table(show_header=True, header_style="bold", expand=True)
|
||||
table.add_column("Level", width=10)
|
||||
table.add_column("Check", width=35)
|
||||
table.add_column("Result", width=6)
|
||||
table.add_column("Detail")
|
||||
|
||||
for result in report.results:
|
||||
style = LEVEL_STYLES.get(result.level, "")
|
||||
status = Text("PASS", style="bold green") if result.passed else Text("FAIL", style="bold red")
|
||||
table.add_row(
|
||||
result.level.label,
|
||||
result.name,
|
||||
status,
|
||||
result.message,
|
||||
style=style if not result.passed else "",
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
console.print()
|
||||
|
||||
# Final verdict
|
||||
achieved = report.level
|
||||
style = LEVEL_STYLES.get(achieved, "bold")
|
||||
emoji = LEVEL_EMOJI.get(achieved, "")
|
||||
|
||||
if achieved == Level.NONE:
|
||||
console.print(
|
||||
Panel(
|
||||
"[bold red]CERTIFICATION FAILED[/bold red]\n\n"
|
||||
"The software does not meet the minimum requirements of\n"
|
||||
"WOMM-STD-001:2026. Please address the above findings\n"
|
||||
"and try again. Or don't. We're not your boss.",
|
||||
border_style="red",
|
||||
expand=False,
|
||||
)
|
||||
)
|
||||
else:
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold {style}]{emoji}WOMM {achieved.label.upper()} CERTIFIED{emoji}[/bold {style}]\n\n"
|
||||
f"This project has been certified to WOMM {achieved.label} level\n"
|
||||
"in accordance with WOMM-STD-001:2026.\n\n"
|
||||
"[dim]This certification is valid on this machine only.\n"
|
||||
"Void if anyone asks 'are you sure?'[/dim]",
|
||||
border_style=style,
|
||||
expand=False,
|
||||
)
|
||||
)
|
||||
|
||||
console.print()
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option("--level", default="gold", help="Certification level (bronze/silver/gold/platinum).")
|
||||
@click.option("--output", "-o", default="womm-seal.svg", help="Output file path (.svg or .png).")
|
||||
@click.option("--size", default=400, type=int, help="Badge size in pixels.")
|
||||
def badge(level: str, output: str, size: int) -> None:
|
||||
"""Generate a WOMM Seal of Approval badge."""
|
||||
cert_level = _level_choice(level)
|
||||
if cert_level is None or cert_level == Level.NONE:
|
||||
raise click.BadParameter("Level must be bronze, silver, gold, or platinum.")
|
||||
|
||||
try:
|
||||
out_path = save_badge(output, level=cert_level, size=size)
|
||||
console.print(f"[green]Badge saved to:[/green] {out_path}")
|
||||
except SystemExit as exc:
|
||||
console.print(f"[yellow]{exc}[/yellow]")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("path", default=".", type=click.Path(exists=True))
|
||||
@click.option("--output", "-o", default=None, help="Save certificate to file (default: print to stdout).")
|
||||
def certificate(path: str, output: str | None) -> None:
|
||||
"""Generate a formal WOMM certificate of compliance."""
|
||||
report = certify(path)
|
||||
|
||||
if output:
|
||||
out_path = save_certificate(report, output)
|
||||
console.print(f"[green]Certificate saved to:[/green] {out_path}")
|
||||
else:
|
||||
console.print(generate_certificate(report))
|
||||
|
||||
|
||||
@main.command()
|
||||
def standard() -> None:
|
||||
"""Print the full WOMM-STD-001:2026 standard."""
|
||||
standard_path = Path(__file__).resolve().parent.parent.parent / "WOMM-STANDARD-001.md"
|
||||
if standard_path.is_file():
|
||||
console.print(standard_path.read_text(encoding="utf-8"))
|
||||
else:
|
||||
console.print("[yellow]Standard document not found.[/yellow]")
|
||||
console.print("Expected at: " + str(standard_path))
|
||||
|
||||
|
||||
# Register 'certify' as the command name (avoiding clash with the function import)
|
||||
main.add_command(certify_cmd, name="certify")
|
||||
154
src/womm/document.py
Normal file
154
src/womm/document.py
Normal file
@ -0,0 +1,154 @@
|
||||
"""Generate formal WOMM certification documents."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import getpass
|
||||
import platform
|
||||
import textwrap
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from womm.certify import CertificationReport, Level
|
||||
|
||||
|
||||
def generate_certificate(report: CertificationReport) -> str:
|
||||
"""Generate a formal markdown certificate from a certification report.
|
||||
|
||||
Args:
|
||||
report: A completed CertificationReport.
|
||||
|
||||
Returns:
|
||||
Markdown-formatted certificate document.
|
||||
"""
|
||||
now = datetime.now()
|
||||
hostname = platform.node() or "UNKNOWN-HOST"
|
||||
username = getpass.getuser()
|
||||
level = report.level
|
||||
|
||||
# Build check results table
|
||||
check_rows = []
|
||||
for r in report.results:
|
||||
status = "PASS" if r.passed else "FAIL"
|
||||
check_rows.append(f"| {r.name:<40} | {status:<6} | {r.message} |")
|
||||
checks_table = "\n".join(check_rows)
|
||||
|
||||
certificate = textwrap.dedent(f"""\
|
||||
================================================================
|
||||
WOMM CERTIFICATE OF COMPLIANCE
|
||||
WOMM-STD-001:2026
|
||||
================================================================
|
||||
|
||||
Certificate Number: WOMM-{now.strftime('%Y%m%d%H%M%S')}-{abs(hash(report.project_name)) % 10000:04d}
|
||||
Date of Issuance: {now.strftime('%Y-%m-%d %H:%M:%S')}
|
||||
Standard: WOMM-STD-001:2026, First Edition
|
||||
|
||||
----------------------------------------------------------------
|
||||
PROJECT INFORMATION
|
||||
----------------------------------------------------------------
|
||||
|
||||
Project Name: {report.project_name}
|
||||
Project Path: {report.project_path}
|
||||
Certification Level: {level.label.upper()} ({_level_tagline(level)})
|
||||
|
||||
----------------------------------------------------------------
|
||||
CERTIFICATION AUTHORITY
|
||||
----------------------------------------------------------------
|
||||
|
||||
Certifying Machine: {hostname}
|
||||
Certifying User: {username}
|
||||
Operating System: {platform.system()} {platform.release()}
|
||||
Python Version: {platform.python_version()}
|
||||
|
||||
This certification was performed on the above machine and is
|
||||
valid exclusively for this machine in its current configuration,
|
||||
including all running processes, environment variables, and the
|
||||
certifier's current emotional state.
|
||||
|
||||
----------------------------------------------------------------
|
||||
AUDIT TRAIL
|
||||
----------------------------------------------------------------
|
||||
|
||||
| {"Check":<40} | {"Result":<6} | Detail |
|
||||
|{"-" * 42}|{"-" * 8}|--------|
|
||||
{checks_table}
|
||||
|
||||
----------------------------------------------------------------
|
||||
DECLARATION
|
||||
----------------------------------------------------------------
|
||||
|
||||
I, {username}@{hostname}, do hereby certify that the software
|
||||
project "{report.project_name}" has been evaluated in accordance
|
||||
with WOMM-STD-001:2026 and has achieved:
|
||||
|
||||
*** WOMM {level.label.upper()} CERTIFICATION ***
|
||||
|
||||
This certification is granted under the following conditions:
|
||||
|
||||
1. The software was observed to work on my machine.
|
||||
2. No guarantee is made regarding any other machine.
|
||||
3. This certificate is void if anyone asks "are you sure?"
|
||||
4. Validity expires upon the next git pull, dependency update,
|
||||
or passage of more than 24 hours.
|
||||
|
||||
----------------------------------------------------------------
|
||||
SIGNATURES
|
||||
----------------------------------------------------------------
|
||||
|
||||
Certified by:
|
||||
|
||||
___________________________
|
||||
{username}
|
||||
{hostname}
|
||||
{now.strftime('%Y-%m-%d')}
|
||||
|
||||
Witnessed by:
|
||||
|
||||
___________________________
|
||||
/dev/null
|
||||
(The Void)
|
||||
|
||||
|
||||
Approved by the WOMM Standards Committee:
|
||||
|
||||
___________________________
|
||||
The Committee
|
||||
(Quorum: 1)
|
||||
|
||||
|
||||
================================================================
|
||||
This document was generated in compliance with WOMM-STD-001:2026
|
||||
"Works On My Machine" Certification Standard
|
||||
First Edition, 2026-04-02
|
||||
|
||||
Copyright 2026 The WOMM Standards Committee.
|
||||
All rights reserved, except the right to guarantee
|
||||
it works on your machine.
|
||||
================================================================
|
||||
""")
|
||||
|
||||
return certificate
|
||||
|
||||
|
||||
def _level_tagline(level: Level) -> str:
|
||||
return {
|
||||
Level.NONE: "Not Certified",
|
||||
Level.BRONZE: "It Compiles",
|
||||
Level.SILVER: "Tests Pass",
|
||||
Level.GOLD: "Production Ready*",
|
||||
Level.PLATINUM: "Works Everywhere*",
|
||||
}.get(level, "Unknown")
|
||||
|
||||
|
||||
def save_certificate(report: CertificationReport, output_path: str | Path) -> Path:
|
||||
"""Generate and save a certificate to a file.
|
||||
|
||||
Args:
|
||||
report: A completed CertificationReport.
|
||||
output_path: File path to write the certificate.
|
||||
|
||||
Returns:
|
||||
The resolved output path.
|
||||
"""
|
||||
out = Path(output_path).resolve()
|
||||
out.write_text(generate_certificate(report), encoding="utf-8")
|
||||
return out
|
||||
79
src/womm_certification.egg-info/PKG-INFO
Normal file
79
src/womm_certification.egg-info/PKG-INFO
Normal file
@ -0,0 +1,79 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: womm-certification
|
||||
Version: 1.0.0
|
||||
Summary: Works On My Machine — The Official Certification Toolkit
|
||||
Author: The WOMM Standards Committee
|
||||
License-Expression: MIT
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/markdown
|
||||
Requires-Dist: click>=8.0
|
||||
Requires-Dist: rich>=13.0
|
||||
Provides-Extra: png
|
||||
Requires-Dist: cairosvg>=2.7; extra == "png"
|
||||
Provides-Extra: dev
|
||||
Requires-Dist: pytest>=8.0; extra == "dev"
|
||||
|
||||
# WOMM Certification Toolkit
|
||||
|
||||
**Works On My Machine -- The Official Certification Standard**
|
||||
|
||||
A formal certification framework for the age-old claim "it works on my machine," complete with a bureaucratic standards document, an official seal of approval, and a CLI tool to certify your projects.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
For PNG badge export support:
|
||||
|
||||
```bash
|
||||
pip install -e '.[png]'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Certify a project
|
||||
|
||||
```bash
|
||||
womm certify # certify current directory
|
||||
womm certify /path/to/project
|
||||
womm certify --level gold # target a specific level
|
||||
```
|
||||
|
||||
### Generate a badge
|
||||
|
||||
```bash
|
||||
womm badge # default: gold, womm-seal.svg
|
||||
womm badge --level platinum -o seal.svg
|
||||
```
|
||||
|
||||
### Generate a certificate
|
||||
|
||||
```bash
|
||||
womm certificate # print to stdout
|
||||
womm certificate -o cert.txt # save to file
|
||||
```
|
||||
|
||||
### Read the standard
|
||||
|
||||
```bash
|
||||
womm standard
|
||||
```
|
||||
|
||||
## Certification Levels
|
||||
|
||||
| Level | Requirements |
|
||||
|----------|-------------|
|
||||
| Bronze | Project exists, has source files, no syntax errors |
|
||||
| Silver | Bronze + tests exist and pass |
|
||||
| Gold | Silver + README exists, no TODO/FIXME/HACK comments |
|
||||
| Platinum | Gold + CI config present, git tree is clean |
|
||||
|
||||
## The Standard
|
||||
|
||||
See [WOMM-STD-001:2026](WOMM-STANDARD-001.md) for the full certification standard.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
14
src/womm_certification.egg-info/SOURCES.txt
Normal file
14
src/womm_certification.egg-info/SOURCES.txt
Normal file
@ -0,0 +1,14 @@
|
||||
README.md
|
||||
pyproject.toml
|
||||
src/womm/__init__.py
|
||||
src/womm/badge.py
|
||||
src/womm/certify.py
|
||||
src/womm/cli.py
|
||||
src/womm/document.py
|
||||
src/womm_certification.egg-info/PKG-INFO
|
||||
src/womm_certification.egg-info/SOURCES.txt
|
||||
src/womm_certification.egg-info/dependency_links.txt
|
||||
src/womm_certification.egg-info/entry_points.txt
|
||||
src/womm_certification.egg-info/requires.txt
|
||||
src/womm_certification.egg-info/top_level.txt
|
||||
tests/test_certify.py
|
||||
1
src/womm_certification.egg-info/dependency_links.txt
Normal file
1
src/womm_certification.egg-info/dependency_links.txt
Normal file
@ -0,0 +1 @@
|
||||
|
||||
2
src/womm_certification.egg-info/entry_points.txt
Normal file
2
src/womm_certification.egg-info/entry_points.txt
Normal file
@ -0,0 +1,2 @@
|
||||
[console_scripts]
|
||||
womm = womm.cli:main
|
||||
8
src/womm_certification.egg-info/requires.txt
Normal file
8
src/womm_certification.egg-info/requires.txt
Normal file
@ -0,0 +1,8 @@
|
||||
click>=8.0
|
||||
rich>=13.0
|
||||
|
||||
[dev]
|
||||
pytest>=8.0
|
||||
|
||||
[png]
|
||||
cairosvg>=2.7
|
||||
1
src/womm_certification.egg-info/top_level.txt
Normal file
1
src/womm_certification.egg-info/top_level.txt
Normal file
@ -0,0 +1 @@
|
||||
womm
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
153
tests/test_certify.py
Normal file
153
tests/test_certify.py
Normal file
@ -0,0 +1,153 @@
|
||||
"""Tests for WOMM certification checks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from womm.certify import (
|
||||
CertificationReport,
|
||||
Level,
|
||||
certify,
|
||||
_check_has_source_files,
|
||||
_check_project_exists,
|
||||
_check_python_syntax,
|
||||
_check_readme_exists,
|
||||
_check_no_todo_comments,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def empty_project(tmp_path: Path) -> Path:
|
||||
"""An empty project directory."""
|
||||
return tmp_path
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def bronze_project(tmp_path: Path) -> Path:
|
||||
"""A project that should achieve Bronze certification."""
|
||||
(tmp_path / "main.py").write_text("print('hello')\n")
|
||||
return tmp_path
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def silver_project(tmp_path: Path) -> Path:
|
||||
"""A project that should achieve at least Silver."""
|
||||
(tmp_path / "main.py").write_text("def add(a, b):\n return a + b\n")
|
||||
tests_dir = tmp_path / "tests"
|
||||
tests_dir.mkdir()
|
||||
(tests_dir / "__init__.py").write_text("")
|
||||
(tests_dir / "test_main.py").write_text(
|
||||
"from pathlib import Path\nimport sys\n"
|
||||
"sys.path.insert(0, str(Path(__file__).parent.parent))\n"
|
||||
"from main import add\n\n"
|
||||
"def test_add():\n assert add(1, 2) == 3\n"
|
||||
)
|
||||
return tmp_path
|
||||
|
||||
|
||||
class TestLevel:
|
||||
def test_level_ordering(self) -> None:
|
||||
assert Level.BRONZE < Level.SILVER < Level.GOLD < Level.PLATINUM
|
||||
|
||||
def test_label(self) -> None:
|
||||
assert Level.GOLD.label == "Gold"
|
||||
assert Level.NONE.label == "Uncertified"
|
||||
|
||||
def test_colour_hex(self) -> None:
|
||||
assert Level.GOLD.colour_hex == "#FFD700"
|
||||
|
||||
|
||||
class TestIndividualChecks:
|
||||
def test_project_exists_pass(self, tmp_path: Path) -> None:
|
||||
result = _check_project_exists(tmp_path)
|
||||
assert result.passed
|
||||
|
||||
def test_project_exists_fail(self, tmp_path: Path) -> None:
|
||||
result = _check_project_exists(tmp_path / "nonexistent")
|
||||
assert not result.passed
|
||||
|
||||
def test_has_source_files_pass(self, bronze_project: Path) -> None:
|
||||
result = _check_has_source_files(bronze_project)
|
||||
assert result.passed
|
||||
|
||||
def test_has_source_files_fail(self, empty_project: Path) -> None:
|
||||
result = _check_has_source_files(empty_project)
|
||||
assert not result.passed
|
||||
|
||||
def test_python_syntax_pass(self, bronze_project: Path) -> None:
|
||||
result = _check_python_syntax(bronze_project)
|
||||
assert result.passed
|
||||
|
||||
def test_python_syntax_fail(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "bad.py").write_text("def oops(\n")
|
||||
result = _check_python_syntax(tmp_path)
|
||||
assert not result.passed
|
||||
|
||||
def test_readme_exists_pass(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "README.md").write_text("# Hello\n")
|
||||
result = _check_readme_exists(tmp_path)
|
||||
assert result.passed
|
||||
|
||||
def test_readme_exists_fail(self, empty_project: Path) -> None:
|
||||
result = _check_readme_exists(empty_project)
|
||||
assert not result.passed
|
||||
|
||||
def test_no_todos_pass(self, bronze_project: Path) -> None:
|
||||
result = _check_no_todo_comments(bronze_project)
|
||||
assert result.passed
|
||||
|
||||
def test_no_todos_fail(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "main.py").write_text("# TODO: fix this\n")
|
||||
result = _check_no_todo_comments(tmp_path)
|
||||
assert not result.passed
|
||||
|
||||
|
||||
class TestCertify:
|
||||
def test_empty_project_gets_none(self, empty_project: Path) -> None:
|
||||
report = certify(empty_project)
|
||||
# Empty dir exists but has no source files
|
||||
assert report.level == Level.NONE
|
||||
|
||||
def test_bronze_project(self, bronze_project: Path) -> None:
|
||||
report = certify(bronze_project, target_level=Level.BRONZE)
|
||||
assert report.level >= Level.BRONZE
|
||||
|
||||
def test_report_has_results(self, bronze_project: Path) -> None:
|
||||
report = certify(bronze_project)
|
||||
assert len(report.results) > 0
|
||||
assert all(r.name and r.message for r in report.results)
|
||||
|
||||
def test_target_level_limits_checks(self, bronze_project: Path) -> None:
|
||||
report = certify(bronze_project, target_level=Level.BRONZE)
|
||||
levels_checked = {r.level for r in report.results}
|
||||
assert Level.PLATINUM not in levels_checked
|
||||
|
||||
|
||||
class TestCertificationReport:
|
||||
def test_level_all_pass(self) -> None:
|
||||
from womm.certify import CheckResult
|
||||
|
||||
report = CertificationReport(
|
||||
project_path=Path("/tmp/test"),
|
||||
project_name="test",
|
||||
results=[
|
||||
CheckResult("check1", True, "ok", Level.BRONZE),
|
||||
CheckResult("check2", True, "ok", Level.SILVER),
|
||||
],
|
||||
)
|
||||
assert report.level >= Level.SILVER
|
||||
|
||||
def test_level_with_failure(self) -> None:
|
||||
from womm.certify import CheckResult
|
||||
|
||||
report = CertificationReport(
|
||||
project_path=Path("/tmp/test"),
|
||||
project_name="test",
|
||||
results=[
|
||||
CheckResult("check1", True, "ok", Level.BRONZE),
|
||||
CheckResult("check2", False, "nope", Level.SILVER),
|
||||
],
|
||||
)
|
||||
assert report.level == Level.BRONZE
|
||||
Loading…
Reference in New Issue
Block a user