ima-claude 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +463 -0
- package/dist/cli.js +1064 -0
- package/package.json +49 -0
- package/platforms/claude/adapter.ts +115 -0
- package/platforms/junie/adapter.ts +254 -0
- package/platforms/junie/agents-template.md +113 -0
- package/platforms/junie/hook-translations.md +84 -0
- package/platforms/shared/detector.ts +27 -0
- package/platforms/shared/installer.ts +202 -0
- package/platforms/shared/types.ts +78 -0
- package/plugins/ima-claude/.claude-plugin/plugin.json +25 -0
- package/plugins/ima-claude/agents/explorer.md +30 -0
- package/plugins/ima-claude/agents/implementer.md +30 -0
- package/plugins/ima-claude/agents/memory.md +42 -0
- package/plugins/ima-claude/agents/reviewer.md +53 -0
- package/plugins/ima-claude/agents/tester.md +33 -0
- package/plugins/ima-claude/agents/wp-developer.md +46 -0
- package/plugins/ima-claude/hooks/README.md +145 -0
- package/plugins/ima-claude/hooks/atlassian_prereqs.py +112 -0
- package/plugins/ima-claude/hooks/block_sed_edits.py +59 -0
- package/plugins/ima-claude/hooks/bootstrap.sh +90 -0
- package/plugins/ima-claude/hooks/bootstrap_utility_check.py +94 -0
- package/plugins/ima-claude/hooks/composer_autoload_check.py +70 -0
- package/plugins/ima-claude/hooks/docs_organization.py +104 -0
- package/plugins/ima-claude/hooks/enforce_rg_over_grep.py +56 -0
- package/plugins/ima-claude/hooks/fp_utility_check.py +90 -0
- package/plugins/ima-claude/hooks/hook_logger.py +69 -0
- package/plugins/ima-claude/hooks/hooks.json +239 -0
- package/plugins/ima-claude/hooks/jira_issue_fetch.py +79 -0
- package/plugins/ima-claude/hooks/jquery_in_wordpress.py +92 -0
- package/plugins/ima-claude/hooks/memory_bootstrap.py +79 -0
- package/plugins/ima-claude/hooks/memory_store_reminder.py +75 -0
- package/plugins/ima-claude/hooks/prompt_coach.py +125 -0
- package/plugins/ima-claude/hooks/prompt_coach_digest.md +48 -0
- package/plugins/ima-claude/hooks/prompt_coach_system.md +30 -0
- package/plugins/ima-claude/hooks/sequential_thinking_check.py +81 -0
- package/plugins/ima-claude/hooks/serena_over_grep.py +96 -0
- package/plugins/ima-claude/hooks/serena_over_read.py +66 -0
- package/plugins/ima-claude/hooks/serena_project_check.py +133 -0
- package/plugins/ima-claude/hooks/sql_injection_check.py +73 -0
- package/plugins/ima-claude/hooks/task_master_after_plan.py +31 -0
- package/plugins/ima-claude/hooks/task_master_before_impl.py +93 -0
- package/plugins/ima-claude/hooks/tavily_extract_advanced.py +48 -0
- package/plugins/ima-claude/hooks/vestige_before_external.py +86 -0
- package/plugins/ima-claude/hooks/webfetch_to_tavily.py +42 -0
- package/plugins/ima-claude/hooks/websearch_to_tavily.py +41 -0
- package/plugins/ima-claude/hooks/wp_security_check.py +150 -0
- package/plugins/ima-claude/personalities/README.md +45 -0
- package/plugins/ima-claude/personalities/enable-40k.md +69 -0
- package/plugins/ima-claude/personalities/enable-templars.md +69 -0
- package/plugins/ima-claude/skills/.research-summary.md +340 -0
- package/plugins/ima-claude/skills/architect/SKILL.md +304 -0
- package/plugins/ima-claude/skills/compound-bridge/SKILL.md +200 -0
- package/plugins/ima-claude/skills/discourse/SKILL.md +440 -0
- package/plugins/ima-claude/skills/discourse-admin/SKILL.md +192 -0
- package/plugins/ima-claude/skills/discourse-admin/references/api-endpoints.md +441 -0
- package/plugins/ima-claude/skills/discourse-admin/references/gotchas.md +107 -0
- package/plugins/ima-claude/skills/discourse-admin/references/staging-defaults.md +98 -0
- package/plugins/ima-claude/skills/discourse-admin/scripts/discourse-admin.py +319 -0
- package/plugins/ima-claude/skills/docs-organize/SKILL.md +254 -0
- package/plugins/ima-claude/skills/docs-organize/templates/active-README.md +50 -0
- package/plugins/ima-claude/skills/docs-organize/templates/archive-README.md +57 -0
- package/plugins/ima-claude/skills/docs-organize/templates/docs-README.md +43 -0
- package/plugins/ima-claude/skills/docs-organize/templates/phase-archive-README.md +83 -0
- package/plugins/ima-claude/skills/docs-organize/templates/section-README.md +48 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-README.md +79 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-gitignore +9 -0
- package/plugins/ima-claude/skills/ember-discourse/SKILL.md +496 -0
- package/plugins/ima-claude/skills/functional-programmer/SKILL.md +258 -0
- package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +278 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/bootstrap-patterns.md +356 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +273 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/theme-integration.md +212 -0
- package/plugins/ima-claude/skills/ima-brand/SKILL.md +108 -0
- package/plugins/ima-claude/skills/ima-brand/references/brand-identity.md +140 -0
- package/plugins/ima-claude/skills/ima-brand/references/digital-standards.md +180 -0
- package/plugins/ima-claude/skills/ima-brand/references/visual-system.md +173 -0
- package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +175 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/container-components.md +154 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/examples.md +328 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/field-components.md +298 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/form-factory.md +193 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/quick-reference.md +153 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/validation-engine.md +336 -0
- package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +178 -0
- package/plugins/ima-claude/skills/jquery/SKILL.md +413 -0
- package/plugins/ima-claude/skills/js-fp/SKILL.md +463 -0
- package/plugins/ima-claude/skills/js-fp/core-principles.md +487 -0
- package/plugins/ima-claude/skills/js-fp/examples/pure-functions.js +260 -0
- package/plugins/ima-claude/skills/js-fp/examples/tests/pure-functions.test.js +262 -0
- package/plugins/ima-claude/skills/js-fp/references/anti-patterns.md +120 -0
- package/plugins/ima-claude/skills/js-fp/references/performance-patterns.md +116 -0
- package/plugins/ima-claude/skills/js-fp/references/testing-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/SKILL.md +280 -0
- package/plugins/ima-claude/skills/js-fp-api/examples/crud-endpoint.js +258 -0
- package/plugins/ima-claude/skills/js-fp-api/references/middleware-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/references/security-sql.md +110 -0
- package/plugins/ima-claude/skills/js-fp-api/references/validation-patterns.md +165 -0
- package/plugins/ima-claude/skills/js-fp-react/SKILL.md +447 -0
- package/plugins/ima-claude/skills/js-fp-react/examples/ProductCard.tsx +65 -0
- package/plugins/ima-claude/skills/js-fp-react/references/hooks-advanced.md +136 -0
- package/plugins/ima-claude/skills/js-fp-react/references/performance-patterns.md +175 -0
- package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +322 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/complete-examples.md +397 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/composables-advanced.md +282 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/reactivity-patterns.md +348 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/testing.md +314 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +301 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/ajax-patterns.md +192 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/event-patterns.md +136 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/wp-integration.md +248 -0
- package/plugins/ima-claude/skills/livecanvas/SKILL.md +209 -0
- package/plugins/ima-claude/skills/livecanvas/references/livecanvas-features.md +311 -0
- package/plugins/ima-claude/skills/livecanvas/references/loops-and-logic.md +730 -0
- package/plugins/ima-claude/skills/livecanvas/references/picostrap.md +227 -0
- package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +339 -0
- package/plugins/ima-claude/skills/mcp-context7/SKILL.md +109 -0
- package/plugins/ima-claude/skills/mcp-memory/SKILL.md +182 -0
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +233 -0
- package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +149 -0
- package/plugins/ima-claude/skills/mcp-serena/SKILL.md +174 -0
- package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +118 -0
- package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +259 -0
- package/plugins/ima-claude/skills/php-authnet/SKILL.md +275 -0
- package/plugins/ima-claude/skills/php-authnet/references/api-reference.md +624 -0
- package/plugins/ima-claude/skills/php-authnet/references/sandbox-testing.md +424 -0
- package/plugins/ima-claude/skills/php-fp/SKILL.md +333 -0
- package/plugins/ima-claude/skills/php-fp/examples/pure-functions.php +403 -0
- package/plugins/ima-claude/skills/php-fp/examples/tests/PureFunctionsTest.php +515 -0
- package/plugins/ima-claude/skills/php-fp/references/core-principles.md +277 -0
- package/plugins/ima-claude/skills/php-fp/references/testing-patterns.md +374 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +216 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/fp-patterns.md +275 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/plugin-architecture.md +295 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/security-examples.md +203 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/testing-strategy.md +259 -0
- package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +716 -0
- package/plugins/ima-claude/skills/playwright/SKILL.md +434 -0
- package/plugins/ima-claude/skills/playwright/references/accessibility-testing.md +153 -0
- package/plugins/ima-claude/skills/playwright/references/ci-cd.md +268 -0
- package/plugins/ima-claude/skills/playwright/references/network-mocking.md +270 -0
- package/plugins/ima-claude/skills/playwright/references/visual-regression.md +215 -0
- package/plugins/ima-claude/skills/py-fp/SKILL.md +663 -0
- package/plugins/ima-claude/skills/py-fp/examples/pure-functions.py +185 -0
- package/plugins/ima-claude/skills/py-fp/examples/tests/test_pure_functions.py +244 -0
- package/plugins/ima-claude/skills/py-fp/references/core-principles.md +381 -0
- package/plugins/ima-claude/skills/py-fp/references/testing-patterns.md +283 -0
- package/plugins/ima-claude/skills/quasar-fp/SKILL.md +327 -0
- package/plugins/ima-claude/skills/quasar-fp/metadata.json +85 -0
- package/plugins/ima-claude/skills/quasar-fp/references/component-patterns.md +257 -0
- package/plugins/ima-claude/skills/quasar-fp/references/theme-integration.md +233 -0
- package/plugins/ima-claude/skills/quasar-fp/references/utility-classes.md +237 -0
- package/plugins/ima-claude/skills/quickstart/SKILL.md +129 -0
- package/plugins/ima-claude/skills/rails/SKILL.md +359 -0
- package/plugins/ima-claude/skills/resume-session/SKILL.md +68 -0
- package/plugins/ima-claude/skills/rg/SKILL.md +205 -0
- package/plugins/ima-claude/skills/ruby-fp/SKILL.md +336 -0
- package/plugins/ima-claude/skills/save-session/SKILL.md +81 -0
- package/plugins/ima-claude/skills/scorecard/SKILL.md +96 -0
- package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +127 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/advanced-checklist.md +44 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/core-checklist.md +60 -0
- package/plugins/ima-claude/skills/skill-analyzer/scripts/analyze_skill.py +418 -0
- package/plugins/ima-claude/skills/skill-creator/LICENSE.txt +202 -0
- package/plugins/ima-claude/skills/skill-creator/SKILL.md +343 -0
- package/plugins/ima-claude/skills/skill-creator/references/output-patterns.md +82 -0
- package/plugins/ima-claude/skills/skill-creator/references/workflows.md +28 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/plugins/ima-claude/skills/task-master/SKILL.md +51 -0
- package/plugins/ima-claude/skills/task-planner/SKILL.md +228 -0
- package/plugins/ima-claude/skills/task-runner/SKILL.md +192 -0
- package/plugins/ima-claude/skills/unit-testing/SKILL.md +198 -0
- package/plugins/ima-claude/skills/unit-testing/references/mock-patterns.md +181 -0
- package/plugins/ima-claude/skills/unit-testing/references/tdd-workflow.md +177 -0
- package/plugins/ima-claude/skills/unit-testing/references/test-strategy.md +126 -0
- package/plugins/ima-claude/skills/wp-local/SKILL.md +246 -0
- package/plugins/ima-claude/skills/wp-local/references/configuration.md +198 -0
- package/plugins/ima-claude/skills/wp-local/references/wp-cli-reference.md +406 -0
- package/plugins/ima-claude/skills/wp-local/scripts/wp-local.sh +61 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pure Functions in Python — Working Examples
|
|
3
|
+
|
|
4
|
+
Demonstrates core FP patterns: purity, immutability, composition,
|
|
5
|
+
and the functional core / imperative shell architecture.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field, replace
|
|
10
|
+
from functools import lru_cache, partial, reduce
|
|
11
|
+
from operator import itemgetter
|
|
12
|
+
from typing import NamedTuple
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# === Immutable Data Structures ===
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class User:
|
|
19
|
+
name: str
|
|
20
|
+
email: str
|
|
21
|
+
role: str = "user"
|
|
22
|
+
settings: tuple = () # Tuple for deep immutability
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Point(NamedTuple):
|
|
26
|
+
x: float
|
|
27
|
+
y: float
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# === Pure Functions: Calculations ===
|
|
31
|
+
|
|
32
|
+
def calculate_total(items: list[dict]) -> float:
|
|
33
|
+
return sum(item["price"] * item.get("quantity", 1) for item in items)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def apply_discount(total: float, rate: float) -> float:
|
|
37
|
+
clamped_rate = max(0.0, min(1.0, rate))
|
|
38
|
+
return round(total * (1 - clamped_rate), 2)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def calculate_tax(subtotal: float, tax_rate: float) -> float:
|
|
42
|
+
return round(subtotal * tax_rate, 2)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# === Pure Functions: Validation ===
|
|
46
|
+
|
|
47
|
+
def validate_required(value) -> bool:
|
|
48
|
+
return value is not None and value != ""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def validate_email(value: str) -> bool:
|
|
52
|
+
return isinstance(value, str) and "@" in value and "." in value.split("@")[-1]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def validate_length(min_len: int, max_len: int):
|
|
56
|
+
return lambda value: isinstance(value, str) and min_len <= len(value) <= max_len
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def validate_user_email(email: str) -> dict:
|
|
60
|
+
if not validate_required(email):
|
|
61
|
+
return {"valid": False, "error": "Required"}
|
|
62
|
+
if not validate_email(email):
|
|
63
|
+
return {"valid": False, "error": "Invalid email format"}
|
|
64
|
+
if not validate_length(5, 254)(email):
|
|
65
|
+
return {"valid": False, "error": "Email length out of range"}
|
|
66
|
+
return {"valid": True}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# === Pure Functions: Transformations ===
|
|
70
|
+
|
|
71
|
+
def normalize_name(name: str) -> str:
|
|
72
|
+
return " ".join(name.strip().split()).title()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def update_user_email(user: User, new_email: str) -> User:
|
|
76
|
+
return replace(user, email=new_email)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def add_item(items: list[dict], new_item: dict) -> list[dict]:
|
|
80
|
+
return [*items, new_item]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def remove_item(items: list[dict], item_id: int) -> list[dict]:
|
|
84
|
+
return [item for item in items if item["id"] != item_id]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def update_item(items: list[dict], item_id: int, updates: dict) -> list[dict]:
|
|
88
|
+
return [{**item, **updates} if item["id"] == item_id else item for item in items]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# === Memoization (Pure Functions Only) ===
|
|
92
|
+
|
|
93
|
+
@lru_cache(maxsize=128)
|
|
94
|
+
def fibonacci(n: int) -> int:
|
|
95
|
+
if n < 2:
|
|
96
|
+
return n
|
|
97
|
+
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# === Composition Without Utilities ===
|
|
101
|
+
|
|
102
|
+
def process_order(order: dict, discount_table: dict, tax_rate: float) -> dict:
|
|
103
|
+
"""Pure order processing — no I/O, no side effects."""
|
|
104
|
+
if not order.get("items"):
|
|
105
|
+
return {"success": False, "error": "Order must have items"}
|
|
106
|
+
|
|
107
|
+
subtotal = calculate_total(order["items"])
|
|
108
|
+
discount_rate = discount_table.get(order.get("discount_code", ""), 0.0)
|
|
109
|
+
discounted = apply_discount(subtotal, discount_rate)
|
|
110
|
+
tax = calculate_tax(discounted, tax_rate)
|
|
111
|
+
total = round(discounted + tax, 2)
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
"success": True,
|
|
115
|
+
"data": {
|
|
116
|
+
"subtotal": subtotal,
|
|
117
|
+
"discount": round(subtotal - discounted, 2),
|
|
118
|
+
"tax": tax,
|
|
119
|
+
"total": total,
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# === Higher-Order Functions ===
|
|
125
|
+
|
|
126
|
+
def create_validator(rules: list[dict]) -> callable:
|
|
127
|
+
"""Factory: creates a reusable validator from rules."""
|
|
128
|
+
def validate(value):
|
|
129
|
+
errors = [rule["message"] for rule in rules if not rule["check"](value)]
|
|
130
|
+
return {"valid": True} if not errors else {"valid": False, "errors": errors}
|
|
131
|
+
return validate
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Pre-configured validators
|
|
135
|
+
validate_username = create_validator([
|
|
136
|
+
{"check": validate_required, "message": "Username is required"},
|
|
137
|
+
{"check": validate_length(3, 50), "message": "Username must be 3-50 characters"},
|
|
138
|
+
{"check": lambda v: v.isalnum(), "message": "Username must be alphanumeric"},
|
|
139
|
+
])
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# === Partial Application ===
|
|
143
|
+
|
|
144
|
+
multiply = lambda a, b: a * b
|
|
145
|
+
double = partial(multiply, 2)
|
|
146
|
+
triple = partial(multiply, 3)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# === Grouping and Sorting (Using operator module) ===
|
|
150
|
+
|
|
151
|
+
def group_by_key(items: list[dict], key: str) -> dict[str, list[dict]]:
|
|
152
|
+
"""Group items by a key value. Pure function."""
|
|
153
|
+
sorted_items = sorted(items, key=itemgetter(key))
|
|
154
|
+
result = {}
|
|
155
|
+
for item in sorted_items:
|
|
156
|
+
group = item[key]
|
|
157
|
+
result[group] = [*result.get(group, []), item]
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# === Generator Pipeline ===
|
|
162
|
+
|
|
163
|
+
def read_lines(text: str):
|
|
164
|
+
for line in text.splitlines():
|
|
165
|
+
stripped = line.strip()
|
|
166
|
+
if stripped:
|
|
167
|
+
yield stripped
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def filter_comments(lines):
|
|
171
|
+
for line in lines:
|
|
172
|
+
if not line.startswith("#"):
|
|
173
|
+
yield line
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def parse_key_value(lines):
|
|
177
|
+
for line in lines:
|
|
178
|
+
if "=" in line:
|
|
179
|
+
key, _, value = line.partition("=")
|
|
180
|
+
yield key.strip(), value.strip()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def parse_config(text: str) -> dict:
|
|
184
|
+
"""Parse a simple key=value config from text. Pure, lazy pipeline."""
|
|
185
|
+
return dict(parse_key_value(filter_comments(read_lines(text))))
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Tests for pure function examples — no mocks needed."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
# Assume pure_functions is importable (adjust path as needed)
|
|
7
|
+
from examples.pure_functions import (
|
|
8
|
+
User,
|
|
9
|
+
add_item,
|
|
10
|
+
apply_discount,
|
|
11
|
+
calculate_tax,
|
|
12
|
+
calculate_total,
|
|
13
|
+
create_validator,
|
|
14
|
+
double,
|
|
15
|
+
fibonacci,
|
|
16
|
+
group_by_key,
|
|
17
|
+
normalize_name,
|
|
18
|
+
parse_config,
|
|
19
|
+
process_order,
|
|
20
|
+
remove_item,
|
|
21
|
+
triple,
|
|
22
|
+
update_item,
|
|
23
|
+
update_user_email,
|
|
24
|
+
validate_email,
|
|
25
|
+
validate_length,
|
|
26
|
+
validate_required,
|
|
27
|
+
validate_user_email,
|
|
28
|
+
validate_username,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# === Calculation Tests ===
|
|
33
|
+
|
|
34
|
+
class TestCalculateTotal:
|
|
35
|
+
@pytest.mark.parametrize("items,expected", [
|
|
36
|
+
([{"price": 10, "quantity": 2}, {"price": 5, "quantity": 1}], 25.0),
|
|
37
|
+
([{"price": 100}], 100.0), # Default quantity = 1
|
|
38
|
+
([], 0.0),
|
|
39
|
+
])
|
|
40
|
+
def test_valid_inputs(self, items, expected):
|
|
41
|
+
assert calculate_total(items) == expected
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TestApplyDiscount:
|
|
45
|
+
@pytest.mark.parametrize("total,rate,expected", [
|
|
46
|
+
(100.0, 0.1, 90.0),
|
|
47
|
+
(100.0, 0.0, 100.0),
|
|
48
|
+
(100.0, 1.0, 0.0),
|
|
49
|
+
(100.0, -0.1, 100.0), # Clamped to 0
|
|
50
|
+
(100.0, 1.5, 0.0), # Clamped to 1
|
|
51
|
+
(0.0, 0.5, 0.0),
|
|
52
|
+
])
|
|
53
|
+
def test_discount_calculation(self, total, rate, expected):
|
|
54
|
+
assert apply_discount(total, rate) == expected
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestCalculateTax:
|
|
58
|
+
@pytest.mark.parametrize("subtotal,rate,expected", [
|
|
59
|
+
(100.0, 0.08, 8.0),
|
|
60
|
+
(100.0, 0.0, 0.0),
|
|
61
|
+
(9.99, 0.0825, 0.82),
|
|
62
|
+
])
|
|
63
|
+
def test_tax_calculation(self, subtotal, rate, expected):
|
|
64
|
+
assert calculate_tax(subtotal, rate) == expected
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# === Validation Tests ===
|
|
68
|
+
|
|
69
|
+
class TestValidateRequired:
|
|
70
|
+
@pytest.mark.parametrize("value,expected", [
|
|
71
|
+
("hello", True),
|
|
72
|
+
("", False),
|
|
73
|
+
(None, False),
|
|
74
|
+
(0, True), # Zero is a valid value
|
|
75
|
+
(False, True), # False is a valid value
|
|
76
|
+
])
|
|
77
|
+
def test_required_validation(self, value, expected):
|
|
78
|
+
assert validate_required(value) == expected
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TestValidateEmail:
|
|
82
|
+
@pytest.mark.parametrize("value,expected", [
|
|
83
|
+
("user@example.com", True),
|
|
84
|
+
("a@b.c", True),
|
|
85
|
+
("invalid", False),
|
|
86
|
+
("@no-local.com", False),
|
|
87
|
+
("no-domain@", False),
|
|
88
|
+
("", False),
|
|
89
|
+
(123, False), # Non-string
|
|
90
|
+
])
|
|
91
|
+
def test_email_validation(self, value, expected):
|
|
92
|
+
assert validate_email(value) == expected
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TestValidateUserEmail:
|
|
96
|
+
def test_valid_email(self):
|
|
97
|
+
result = validate_user_email("user@example.com")
|
|
98
|
+
assert result["valid"] is True
|
|
99
|
+
|
|
100
|
+
def test_empty_email(self):
|
|
101
|
+
result = validate_user_email("")
|
|
102
|
+
assert result["valid"] is False
|
|
103
|
+
assert result["error"] == "Required"
|
|
104
|
+
|
|
105
|
+
def test_invalid_format(self):
|
|
106
|
+
result = validate_user_email("notanemail")
|
|
107
|
+
assert result["valid"] is False
|
|
108
|
+
assert "format" in result["error"].lower()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# === Immutability Tests ===
|
|
112
|
+
|
|
113
|
+
class TestImmutability:
|
|
114
|
+
def test_frozen_dataclass_prevents_mutation(self):
|
|
115
|
+
user = User(name="Alice", email="alice@test.com")
|
|
116
|
+
with pytest.raises(AttributeError):
|
|
117
|
+
user.name = "Bob"
|
|
118
|
+
|
|
119
|
+
def test_update_returns_new_instance(self):
|
|
120
|
+
original = User(name="Alice", email="old@test.com")
|
|
121
|
+
updated = update_user_email(original, "new@test.com")
|
|
122
|
+
assert updated.email == "new@test.com"
|
|
123
|
+
assert original.email == "old@test.com"
|
|
124
|
+
assert original is not updated
|
|
125
|
+
|
|
126
|
+
def test_add_item_does_not_mutate(self):
|
|
127
|
+
original = [{"id": 1, "name": "A"}]
|
|
128
|
+
result = add_item(original, {"id": 2, "name": "B"})
|
|
129
|
+
assert len(result) == 2
|
|
130
|
+
assert len(original) == 1 # Unchanged
|
|
131
|
+
|
|
132
|
+
def test_remove_item_does_not_mutate(self):
|
|
133
|
+
original = [{"id": 1}, {"id": 2}, {"id": 3}]
|
|
134
|
+
result = remove_item(original, 2)
|
|
135
|
+
assert len(result) == 2
|
|
136
|
+
assert len(original) == 3 # Unchanged
|
|
137
|
+
|
|
138
|
+
def test_update_item_does_not_mutate(self):
|
|
139
|
+
original = [{"id": 1, "name": "Old"}]
|
|
140
|
+
result = update_item(original, 1, {"name": "New"})
|
|
141
|
+
assert result[0]["name"] == "New"
|
|
142
|
+
assert original[0]["name"] == "Old" # Unchanged
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# === Composition Tests ===
|
|
146
|
+
|
|
147
|
+
class TestProcessOrder:
|
|
148
|
+
def test_valid_order(self):
|
|
149
|
+
order = {"items": [{"price": 100, "quantity": 1}]}
|
|
150
|
+
result = process_order(order, discount_table={}, tax_rate=0.1)
|
|
151
|
+
assert result["success"] is True
|
|
152
|
+
assert result["data"]["subtotal"] == 100.0
|
|
153
|
+
assert result["data"]["tax"] == 10.0
|
|
154
|
+
assert result["data"]["total"] == 110.0
|
|
155
|
+
|
|
156
|
+
def test_order_with_discount(self):
|
|
157
|
+
order = {
|
|
158
|
+
"items": [{"price": 100, "quantity": 1}],
|
|
159
|
+
"discount_code": "SAVE10",
|
|
160
|
+
}
|
|
161
|
+
result = process_order(order, discount_table={"SAVE10": 0.1}, tax_rate=0.0)
|
|
162
|
+
assert result["success"] is True
|
|
163
|
+
assert result["data"]["total"] == 90.0
|
|
164
|
+
|
|
165
|
+
def test_empty_order_fails(self):
|
|
166
|
+
result = process_order({"items": []}, discount_table={}, tax_rate=0.1)
|
|
167
|
+
assert result["success"] is False
|
|
168
|
+
assert "items" in result["error"].lower()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# === Higher-Order Function Tests ===
|
|
172
|
+
|
|
173
|
+
class TestCreateValidator:
|
|
174
|
+
def test_all_rules_pass(self):
|
|
175
|
+
result = validate_username("alice42")
|
|
176
|
+
assert result["valid"] is True
|
|
177
|
+
|
|
178
|
+
def test_empty_value(self):
|
|
179
|
+
result = validate_username("")
|
|
180
|
+
assert result["valid"] is False
|
|
181
|
+
assert any("required" in e.lower() for e in result["errors"])
|
|
182
|
+
|
|
183
|
+
def test_too_short(self):
|
|
184
|
+
result = validate_username("ab")
|
|
185
|
+
assert result["valid"] is False
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# === Partial Application Tests ===
|
|
189
|
+
|
|
190
|
+
class TestPartialApplication:
|
|
191
|
+
def test_double(self):
|
|
192
|
+
assert double(5) == 10
|
|
193
|
+
|
|
194
|
+
def test_triple(self):
|
|
195
|
+
assert triple(5) == 15
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# === Memoization Tests ===
|
|
199
|
+
|
|
200
|
+
class TestFibonacci:
|
|
201
|
+
@pytest.mark.parametrize("n,expected", [
|
|
202
|
+
(0, 0), (1, 1), (2, 1), (10, 55), (20, 6765),
|
|
203
|
+
])
|
|
204
|
+
def test_fibonacci(self, n, expected):
|
|
205
|
+
assert fibonacci(n) == expected
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# === Generator Pipeline Tests ===
|
|
209
|
+
|
|
210
|
+
class TestParseConfig:
|
|
211
|
+
def test_simple_config(self):
|
|
212
|
+
text = """
|
|
213
|
+
# Database settings
|
|
214
|
+
host = localhost
|
|
215
|
+
port = 5432
|
|
216
|
+
name = mydb
|
|
217
|
+
"""
|
|
218
|
+
result = parse_config(text)
|
|
219
|
+
assert result == {"host": "localhost", "port": "5432", "name": "mydb"}
|
|
220
|
+
|
|
221
|
+
def test_empty_input(self):
|
|
222
|
+
assert parse_config("") == {}
|
|
223
|
+
|
|
224
|
+
def test_comments_and_blanks_skipped(self):
|
|
225
|
+
text = "# comment\n\nkey = value\n# another comment"
|
|
226
|
+
result = parse_config(text)
|
|
227
|
+
assert result == {"key": "value"}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# === Grouping Tests ===
|
|
231
|
+
|
|
232
|
+
class TestGroupByKey:
|
|
233
|
+
def test_group_items(self):
|
|
234
|
+
items = [
|
|
235
|
+
{"name": "A", "category": "x"},
|
|
236
|
+
{"name": "B", "category": "y"},
|
|
237
|
+
{"name": "C", "category": "x"},
|
|
238
|
+
]
|
|
239
|
+
result = group_by_key(items, "category")
|
|
240
|
+
assert len(result["x"]) == 2
|
|
241
|
+
assert len(result["y"]) == 1
|
|
242
|
+
|
|
243
|
+
def test_empty_list(self):
|
|
244
|
+
assert group_by_key([], "category") == {}
|