From eef966454f180ecfac3a978b9017004c0a34c25b Mon Sep 17 00:00:00 2001 From: bbopen <1772563+bbopen@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:44:49 -0800 Subject: [PATCH] prehex: rename package and modules to module_contracts --- README.md | 20 ++--- SPEC.md | 32 ++++---- gleam.toml | 4 +- ...contracts.gleam => module_contracts.gleam} | 8 +- .../loader.gleam | 0 .../rule.gleam | 0 .../verify.gleam | 4 +- .../violation.gleam | 2 +- ...test.gleam => module_contracts_test.gleam} | 80 ++++++++++--------- 9 files changed, 76 insertions(+), 74 deletions(-) rename src/{gleam_contracts.gleam => module_contracts.gleam} (95%) rename src/{gleam_contracts => module_contracts}/loader.gleam (100%) rename src/{gleam_contracts => module_contracts}/rule.gleam (100%) rename src/{gleam_contracts => module_contracts}/verify.gleam (99%) rename src/{gleam_contracts => module_contracts}/violation.gleam (98%) rename test/{gleam_contracts_test.gleam => module_contracts_test.gleam} (80%) diff --git a/README.md b/README.md index 71f8229..14fbf21 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# gleam_contracts +# module_contracts -[![Package Version](https://img.shields.io/hexpm/v/gleam_contracts)](https://hex.pm/packages/gleam_contracts) -[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_contracts/) +[![Package Version](https://img.shields.io/hexpm/v/module_contracts)](https://hex.pm/packages/module_contracts) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/module_contracts/) Build-time module contract verification for Gleam — enforce that paired modules stay in sync. ## Installation ```sh -gleam add gleam_contracts +gleam add module_contracts ``` ## Usage @@ -16,20 +16,20 @@ gleam add gleam_contracts Create a contract check entrypoint in your package: ```gleam -import gleam_contracts -import gleam_contracts/rule +import module_contracts +import module_contracts/rule pub fn main() { - gleam_contracts.check( + module_contracts.check( interface_path: "build/dev/docs/my_package/package-interface.json", rules: [ - gleam_contracts.mirror_rule( + module_contracts.mirror_rule( source: "my_package/headless/button", target: "my_package/button", prefix_params: [rule.Labeled(label: "context")], ) - |> gleam_contracts.with_exceptions(exceptions: ["button"]), - gleam_contracts.shared_types( + |> module_contracts.with_exceptions(exceptions: ["button"]), + module_contracts.shared_types( module_a: "my_package/headless/button", module_b: "my_package/button", type_names: ["ButtonConfig"], diff --git a/SPEC.md b/SPEC.md index aed1237..2162b59 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,4 +1,4 @@ -# gleam_contracts Specification +# module_contracts Specification Build-time module contract verification for Gleam. Reads the compiler's `package-interface` JSON and checks that paired modules (e.g. headless/styled @@ -230,38 +230,38 @@ pub fn check( ## Module Layout ``` -src/gleam_contracts.gleam — public API, re-exports -src/gleam_contracts/rule.gleam — Rule type + constructors -src/gleam_contracts/verify.gleam — verification engine -src/gleam_contracts/violation.gleam — Violation type + formatter -src/gleam_contracts/loader.gleam — package interface JSON loading +src/module_contracts.gleam — public API, re-exports +src/module_contracts/rule.gleam — Rule type + constructors +src/module_contracts/verify.gleam — verification engine +src/module_contracts/violation.gleam — Violation type + formatter +src/module_contracts/loader.gleam — package interface JSON loading ``` ## Usage Pattern -A consuming project adds `gleam_contracts` as a dev dependency, then +A consuming project adds `module_contracts` as a dev dependency, then creates a verification entry point: ```gleam // test/contract_test.gleam (or a standalone script) -import gleam_contracts -import gleam_contracts/rule +import module_contracts +import module_contracts/rule pub fn main() { - gleam_contracts.check( + module_contracts.check( interface_path: "build/dev/docs/my_package/package-interface.json", rules: [ - gleam_contracts.mirror_rule( + module_contracts.mirror_rule( source: "my_package/headless/badge", target: "my_package/badge", prefix_params: [rule.Labeled(label: "context")], ), - gleam_contracts.mirror_rule( + module_contracts.mirror_rule( source: "my_package/headless/button", target: "my_package/button", prefix_params: [rule.Labeled(label: "context")], ) - |> gleam_contracts.with_exceptions(exceptions: ["button"]), + |> module_contracts.with_exceptions(exceptions: ["button"]), ], ) } @@ -277,18 +277,18 @@ gleam run -m contract_test Or as a startest test: ```gleam -import gleam_contracts +import module_contracts import startest.{describe, it} import startest/expect pub fn contract_tests() { describe("module contracts", [ it("headless/styled modules stay in sync", fn() { - let assert Ok(interface) = gleam_contracts.load_package_interface( + let assert Ok(interface) = module_contracts.load_package_interface( path: "build/dev/docs/my_package/package-interface.json", ) - gleam_contracts.verify(interface: interface, rules: my_rules()) + module_contracts.verify(interface: interface, rules: my_rules()) |> expect.to_be_ok }), ]) diff --git a/gleam.toml b/gleam.toml index 77aa1f1..8dc395b 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,8 +1,8 @@ -name = "gleam_contracts" +name = "module_contracts" version = "0.1.0" description = "Build-time module contract verification for Gleam — enforce that paired modules stay in sync." licences = ["Apache-2.0"] -repository = { type = "github", user = "bbopen", repo = "gleam_contracts" } +repository = { type = "github", user = "bbopen", repo = "module_contracts" } gleam = ">= 1.14.0" [dependencies] diff --git a/src/gleam_contracts.gleam b/src/module_contracts.gleam similarity index 95% rename from src/gleam_contracts.gleam rename to src/module_contracts.gleam index 91a70f0..7ded035 100644 --- a/src/gleam_contracts.gleam +++ b/src/module_contracts.gleam @@ -3,10 +3,10 @@ import gleam/io import gleam/package_interface.{type Package} import gleam/result -import gleam_contracts/loader -import gleam_contracts/rule -import gleam_contracts/verify as contract_verify -import gleam_contracts/violation +import module_contracts/loader +import module_contracts/rule +import module_contracts/verify as contract_verify +import module_contracts/violation /// Decoded package-interface model exported by the compiler. pub type PackageInterface = diff --git a/src/gleam_contracts/loader.gleam b/src/module_contracts/loader.gleam similarity index 100% rename from src/gleam_contracts/loader.gleam rename to src/module_contracts/loader.gleam diff --git a/src/gleam_contracts/rule.gleam b/src/module_contracts/rule.gleam similarity index 100% rename from src/gleam_contracts/rule.gleam rename to src/module_contracts/rule.gleam diff --git a/src/gleam_contracts/verify.gleam b/src/module_contracts/verify.gleam similarity index 99% rename from src/gleam_contracts/verify.gleam rename to src/module_contracts/verify.gleam index 13b4b4f..1b41ca5 100644 --- a/src/gleam_contracts/verify.gleam +++ b/src/module_contracts/verify.gleam @@ -12,11 +12,11 @@ import gleam/package_interface.{ TypeDefinition, Variable, } import gleam/string -import gleam_contracts/rule.{ +import module_contracts/rule.{ type ExportSpec, type ParamSpec, type Rule, ExportSpec, Labeled, MirrorRule, RequireExports, SharedTypes, Unlabeled, } -import gleam_contracts/violation.{ +import module_contracts/violation.{ type Violation, MissingExport, MissingFunction, MissingType, ModuleNotFound, ParameterMismatch, TypeMismatch, } diff --git a/src/gleam_contracts/violation.gleam b/src/module_contracts/violation.gleam similarity index 98% rename from src/gleam_contracts/violation.gleam rename to src/module_contracts/violation.gleam index 31c7a0b..2e24247 100644 --- a/src/gleam_contracts/violation.gleam +++ b/src/module_contracts/violation.gleam @@ -3,7 +3,7 @@ import gleam/int import gleam/list import gleam/string -import gleam_contracts/loader.{type LoadError} +import module_contracts/loader.{type LoadError} /// A single contract violation. pub type Violation { diff --git a/test/gleam_contracts_test.gleam b/test/module_contracts_test.gleam similarity index 80% rename from test/gleam_contracts_test.gleam rename to test/module_contracts_test.gleam index 91ecce4..ebefd00 100644 --- a/test/gleam_contracts_test.gleam +++ b/test/module_contracts_test.gleam @@ -1,8 +1,8 @@ import gleam/string -import gleam_contracts -import gleam_contracts/loader -import gleam_contracts/rule -import gleam_contracts/violation +import module_contracts +import module_contracts/loader +import module_contracts/rule +import module_contracts/violation import startest.{describe, it} import startest/expect @@ -10,19 +10,19 @@ pub fn main() { startest.run(startest.default_config()) } -pub fn gleam_contracts_tests() { - describe("gleam_contracts", [ +pub fn module_contracts_tests() { + describe("module_contracts", [ describe("load_package_interface", [ it("returns Ok for valid package-interface json", fn() { let _ = - gleam_contracts.load_package_interface(path: fixture_path()) + module_contracts.load_package_interface(path: fixture_path()) |> expect.to_be_ok Nil }), it("returns ReadError for missing files", fn() { let error = - gleam_contracts.load_package_interface(path: missing_fixture_path()) + module_contracts.load_package_interface(path: missing_fixture_path()) |> expect.to_be_error case error { @@ -35,7 +35,9 @@ pub fn gleam_contracts_tests() { it("returns DecodeError for malformed json", fn() { let error = - gleam_contracts.load_package_interface(path: malformed_fixture_path()) + module_contracts.load_package_interface( + path: malformed_fixture_path(), + ) |> expect.to_be_error case error { @@ -48,7 +50,7 @@ pub fn gleam_contracts_tests() { it("returns DecodeError for invalid interface schema", fn() { let error = - gleam_contracts.load_package_interface(path: invalid_fixture_path()) + module_contracts.load_package_interface(path: invalid_fixture_path()) |> expect.to_be_error case error { @@ -64,8 +66,8 @@ pub fn gleam_contracts_tests() { it( "returns Ok when source function exists and extra target functions exist", fn() { - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.mirror_rule( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.mirror_rule( source: "fixture_pkg/headless/icon", target: "fixture_pkg/icon", prefix_params: [rule.Labeled(label: "theme")], @@ -79,13 +81,13 @@ pub fn gleam_contracts_tests() { "returns MissingFunction when source function is missing in target", fn() { let result = - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.mirror_rule( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.mirror_rule( source: "fixture_pkg/headless/button", target: "fixture_pkg/button", prefix_params: [rule.Labeled(label: "theme")], ) - |> gleam_contracts.with_exceptions(exceptions: ["button"]), + |> module_contracts.with_exceptions(exceptions: ["button"]), ]) result @@ -103,8 +105,8 @@ pub fn gleam_contracts_tests() { it("returns ParameterMismatch when labels differ", fn() { let result = - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.mirror_rule( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.mirror_rule( source: "fixture_pkg/headless/icon", target: "fixture_pkg/icon", prefix_params: [], @@ -126,13 +128,13 @@ pub fn gleam_contracts_tests() { it("exceptions skip parameter checks but still require existence", fn() { let result = - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.mirror_rule( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.mirror_rule( source: "fixture_pkg/headless/button", target: "fixture_pkg/button", prefix_params: [rule.Labeled(label: "theme")], ) - |> gleam_contracts.with_exceptions(exceptions: ["button"]), + |> module_contracts.with_exceptions(exceptions: ["button"]), ]) result @@ -149,8 +151,8 @@ pub fn gleam_contracts_tests() { it("returns ModuleNotFound when module is missing", fn() { let result = - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.mirror_rule( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.mirror_rule( source: "fixture_pkg/headless/does_not_exist", target: "fixture_pkg/icon", prefix_params: [rule.Labeled(label: "theme")], @@ -170,8 +172,8 @@ pub fn gleam_contracts_tests() { describe("verify require_exports", [ it("returns Ok when all required exports are present", fn() { - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.require_exports( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.require_exports( module: "fixture_pkg/exports", exports: [ rule.ExportSpec(name: "render", arity: 2, labels: [ @@ -186,8 +188,8 @@ pub fn gleam_contracts_tests() { it("returns MissingExport when export is missing", fn() { let result = - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.require_exports( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.require_exports( module: "fixture_pkg/exports", exports: [ rule.ExportSpec(name: "missing", arity: 1, labels: [ @@ -211,8 +213,8 @@ pub fn gleam_contracts_tests() { it("returns ParameterMismatch for arity and label mismatch", fn() { let result = - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.require_exports( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.require_exports( module: "fixture_pkg/exports", exports: [ rule.ExportSpec(name: "render", arity: 1, labels: [ @@ -238,8 +240,8 @@ pub fn gleam_contracts_tests() { describe("verify shared_types", [ it("returns Ok for matching type definitions", fn() { - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.shared_types( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.shared_types( module_a: "fixture_pkg/headless/palette", module_b: "fixture_pkg/palette", type_names: ["Tone", "ToneName"], @@ -250,8 +252,8 @@ pub fn gleam_contracts_tests() { it("returns MissingType when one module is missing a type", fn() { let result = - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.shared_types( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.shared_types( module_a: "fixture_pkg/headless/toggle", module_b: "fixture_pkg/toggle", type_names: ["ToggleState"], @@ -271,8 +273,8 @@ pub fn gleam_contracts_tests() { it("returns TypeMismatch when definitions differ", fn() { let result = - gleam_contracts.verify(interface: fixture_interface(), rules: [ - gleam_contracts.shared_types( + module_contracts.verify(interface: fixture_interface(), rules: [ + module_contracts.shared_types( module_a: "fixture_pkg/headless/toggle", module_b: "fixture_pkg/toggle", type_names: ["ToggleConfig"], @@ -297,7 +299,7 @@ pub fn gleam_contracts_tests() { describe("format_violations", [ it("produces readable output", fn() { let formatted = - gleam_contracts.format_violations(violations: [ + module_contracts.format_violations(violations: [ violation.MissingFunction( rule_source: "fixture_pkg/headless/button", rule_target: "fixture_pkg/button", @@ -327,8 +329,8 @@ pub fn gleam_contracts_tests() { describe("check_result", [ it("returns Ok when interface load and verification pass", fn() { - gleam_contracts.check_result(interface_path: fixture_path(), rules: [ - gleam_contracts.mirror_rule( + module_contracts.check_result(interface_path: fixture_path(), rules: [ + module_contracts.mirror_rule( source: "fixture_pkg/headless/icon", target: "fixture_pkg/icon", prefix_params: [rule.Labeled(label: "theme")], @@ -339,7 +341,7 @@ pub fn gleam_contracts_tests() { it("returns InterfaceLoadFailure when interface cannot be loaded", fn() { let result = - gleam_contracts.check_result( + module_contracts.check_result( interface_path: missing_fixture_path(), rules: [], ) @@ -363,7 +365,7 @@ pub fn gleam_contracts_tests() { } fn fixture_interface() { - gleam_contracts.load_package_interface(path: fixture_path()) + module_contracts.load_package_interface(path: fixture_path()) |> expect.to_be_ok }