cruhon 2.10.0__tar.gz

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.
Files changed (190) hide show
  1. cruhon-2.10.0/PKG-INFO +1316 -0
  2. cruhon-2.10.0/README.md +1284 -0
  3. cruhon-2.10.0/cruhon/__init__.py +21 -0
  4. cruhon-2.10.0/cruhon/cli.py +933 -0
  5. cruhon-2.10.0/cruhon/core/__init__.py +14 -0
  6. cruhon-2.10.0/cruhon/core/ast_nodes.py +515 -0
  7. cruhon-2.10.0/cruhon/core/cache.py +250 -0
  8. cruhon-2.10.0/cruhon/core/dependency_resolver.py +114 -0
  9. cruhon-2.10.0/cruhon/core/diagnostics.py +354 -0
  10. cruhon-2.10.0/cruhon/core/lexer.py +309 -0
  11. cruhon-2.10.0/cruhon/core/libs/__init__.py +1 -0
  12. cruhon-2.10.0/cruhon/core/libs/abc_.py +40 -0
  13. cruhon-2.10.0/cruhon/core/libs/archive_.py +238 -0
  14. cruhon-2.10.0/cruhon/core/libs/argparse_.py +62 -0
  15. cruhon-2.10.0/cruhon/core/libs/array_.py +151 -0
  16. cruhon-2.10.0/cruhon/core/libs/ast_.py +106 -0
  17. cruhon-2.10.0/cruhon/core/libs/asyncio_.py +124 -0
  18. cruhon-2.10.0/cruhon/core/libs/atexit_.py +26 -0
  19. cruhon-2.10.0/cruhon/core/libs/base64_.py +38 -0
  20. cruhon-2.10.0/cruhon/core/libs/binascii_.py +72 -0
  21. cruhon-2.10.0/cruhon/core/libs/bisect_.py +36 -0
  22. cruhon-2.10.0/cruhon/core/libs/calendar_.py +153 -0
  23. cruhon-2.10.0/cruhon/core/libs/cmath_.py +102 -0
  24. cruhon-2.10.0/cruhon/core/libs/codecs_.py +76 -0
  25. cruhon-2.10.0/cruhon/core/libs/collections_.py +79 -0
  26. cruhon-2.10.0/cruhon/core/libs/color_.py +35 -0
  27. cruhon-2.10.0/cruhon/core/libs/colorsys_.py +78 -0
  28. cruhon-2.10.0/cruhon/core/libs/config_.py +185 -0
  29. cruhon-2.10.0/cruhon/core/libs/configparser_.py +97 -0
  30. cruhon-2.10.0/cruhon/core/libs/contextlib_.py +38 -0
  31. cruhon-2.10.0/cruhon/core/libs/copy_.py +17 -0
  32. cruhon-2.10.0/cruhon/core/libs/crypto_.py +165 -0
  33. cruhon-2.10.0/cruhon/core/libs/csv_.py +132 -0
  34. cruhon-2.10.0/cruhon/core/libs/ctypes_.py +134 -0
  35. cruhon-2.10.0/cruhon/core/libs/dataclasses_.py +41 -0
  36. cruhon-2.10.0/cruhon/core/libs/date_.py +211 -0
  37. cruhon-2.10.0/cruhon/core/libs/decimal_.py +103 -0
  38. cruhon-2.10.0/cruhon/core/libs/diff_.py +82 -0
  39. cruhon-2.10.0/cruhon/core/libs/dis_.py +56 -0
  40. cruhon-2.10.0/cruhon/core/libs/doctest_.py +49 -0
  41. cruhon-2.10.0/cruhon/core/libs/email_.py +242 -0
  42. cruhon-2.10.0/cruhon/core/libs/enum_.py +44 -0
  43. cruhon-2.10.0/cruhon/core/libs/env_.py +211 -0
  44. cruhon-2.10.0/cruhon/core/libs/errno_.py +39 -0
  45. cruhon-2.10.0/cruhon/core/libs/file_.py +244 -0
  46. cruhon-2.10.0/cruhon/core/libs/filecmp_.py +52 -0
  47. cruhon-2.10.0/cruhon/core/libs/fileinput_.py +113 -0
  48. cruhon-2.10.0/cruhon/core/libs/fnmatch_.py +50 -0
  49. cruhon-2.10.0/cruhon/core/libs/fraction_.py +82 -0
  50. cruhon-2.10.0/cruhon/core/libs/ftp_.py +94 -0
  51. cruhon-2.10.0/cruhon/core/libs/functools_.py +41 -0
  52. cruhon-2.10.0/cruhon/core/libs/futures_.py +96 -0
  53. cruhon-2.10.0/cruhon/core/libs/gc_.py +62 -0
  54. cruhon-2.10.0/cruhon/core/libs/getpass_.py +37 -0
  55. cruhon-2.10.0/cruhon/core/libs/gettext_.py +38 -0
  56. cruhon-2.10.0/cruhon/core/libs/glob_.py +125 -0
  57. cruhon-2.10.0/cruhon/core/libs/graphlib_.py +55 -0
  58. cruhon-2.10.0/cruhon/core/libs/heapq_.py +40 -0
  59. cruhon-2.10.0/cruhon/core/libs/html_.py +53 -0
  60. cruhon-2.10.0/cruhon/core/libs/http_.py +353 -0
  61. cruhon-2.10.0/cruhon/core/libs/httpserver_.py +67 -0
  62. cruhon-2.10.0/cruhon/core/libs/image_.py +104 -0
  63. cruhon-2.10.0/cruhon/core/libs/importlib_.py +57 -0
  64. cruhon-2.10.0/cruhon/core/libs/inspect_.py +101 -0
  65. cruhon-2.10.0/cruhon/core/libs/io_.py +65 -0
  66. cruhon-2.10.0/cruhon/core/libs/ip_.py +96 -0
  67. cruhon-2.10.0/cruhon/core/libs/itertools_.py +88 -0
  68. cruhon-2.10.0/cruhon/core/libs/json_.py +27 -0
  69. cruhon-2.10.0/cruhon/core/libs/keyword_.py +34 -0
  70. cruhon-2.10.0/cruhon/core/libs/linecache_.py +32 -0
  71. cruhon-2.10.0/cruhon/core/libs/locale_.py +58 -0
  72. cruhon-2.10.0/cruhon/core/libs/log_.py +104 -0
  73. cruhon-2.10.0/cruhon/core/libs/mail_.py +334 -0
  74. cruhon-2.10.0/cruhon/core/libs/math_.py +45 -0
  75. cruhon-2.10.0/cruhon/core/libs/mimetypes_.py +59 -0
  76. cruhon-2.10.0/cruhon/core/libs/mmap_.py +78 -0
  77. cruhon-2.10.0/cruhon/core/libs/multiprocessing_.py +108 -0
  78. cruhon-2.10.0/cruhon/core/libs/numbers_.py +31 -0
  79. cruhon-2.10.0/cruhon/core/libs/operator_.py +100 -0
  80. cruhon-2.10.0/cruhon/core/libs/pdb_.py +75 -0
  81. cruhon-2.10.0/cruhon/core/libs/pdf_.py +63 -0
  82. cruhon-2.10.0/cruhon/core/libs/pickle_.py +120 -0
  83. cruhon-2.10.0/cruhon/core/libs/platform_.py +63 -0
  84. cruhon-2.10.0/cruhon/core/libs/plist_.py +129 -0
  85. cruhon-2.10.0/cruhon/core/libs/pop3_.py +75 -0
  86. cruhon-2.10.0/cruhon/core/libs/pprint_.py +41 -0
  87. cruhon-2.10.0/cruhon/core/libs/profile_.py +41 -0
  88. cruhon-2.10.0/cruhon/core/libs/queue_.py +47 -0
  89. cruhon-2.10.0/cruhon/core/libs/random_.py +64 -0
  90. cruhon-2.10.0/cruhon/core/libs/re_.py +76 -0
  91. cruhon-2.10.0/cruhon/core/libs/reprlib_.py +38 -0
  92. cruhon-2.10.0/cruhon/core/libs/resource_.py +44 -0
  93. cruhon-2.10.0/cruhon/core/libs/runpy_.py +66 -0
  94. cruhon-2.10.0/cruhon/core/libs/sched_.py +52 -0
  95. cruhon-2.10.0/cruhon/core/libs/selectors_.py +71 -0
  96. cruhon-2.10.0/cruhon/core/libs/shell_.py +179 -0
  97. cruhon-2.10.0/cruhon/core/libs/shelve_.py +132 -0
  98. cruhon-2.10.0/cruhon/core/libs/shlex_.py +39 -0
  99. cruhon-2.10.0/cruhon/core/libs/shutil_.py +86 -0
  100. cruhon-2.10.0/cruhon/core/libs/signal_.py +65 -0
  101. cruhon-2.10.0/cruhon/core/libs/socket_.py +136 -0
  102. cruhon-2.10.0/cruhon/core/libs/sqlite_.py +438 -0
  103. cruhon-2.10.0/cruhon/core/libs/ssl_.py +75 -0
  104. cruhon-2.10.0/cruhon/core/libs/stat_.py +81 -0
  105. cruhon-2.10.0/cruhon/core/libs/statistics_.py +62 -0
  106. cruhon-2.10.0/cruhon/core/libs/store_.py +45 -0
  107. cruhon-2.10.0/cruhon/core/libs/string_.py +147 -0
  108. cruhon-2.10.0/cruhon/core/libs/struct_.py +111 -0
  109. cruhon-2.10.0/cruhon/core/libs/sys_.py +65 -0
  110. cruhon-2.10.0/cruhon/core/libs/sysconfig_.py +43 -0
  111. cruhon-2.10.0/cruhon/core/libs/tempfile_.py +81 -0
  112. cruhon-2.10.0/cruhon/core/libs/text_.py +329 -0
  113. cruhon-2.10.0/cruhon/core/libs/textwrap_.py +66 -0
  114. cruhon-2.10.0/cruhon/core/libs/threading_.py +88 -0
  115. cruhon-2.10.0/cruhon/core/libs/time_.py +27 -0
  116. cruhon-2.10.0/cruhon/core/libs/timeit_.py +36 -0
  117. cruhon-2.10.0/cruhon/core/libs/tokenize_.py +121 -0
  118. cruhon-2.10.0/cruhon/core/libs/toml_.py +46 -0
  119. cruhon-2.10.0/cruhon/core/libs/traceback_.py +58 -0
  120. cruhon-2.10.0/cruhon/core/libs/tracemalloc_.py +58 -0
  121. cruhon-2.10.0/cruhon/core/libs/types_.py +73 -0
  122. cruhon-2.10.0/cruhon/core/libs/typing_.py +68 -0
  123. cruhon-2.10.0/cruhon/core/libs/unicode_.py +93 -0
  124. cruhon-2.10.0/cruhon/core/libs/unittest_.py +82 -0
  125. cruhon-2.10.0/cruhon/core/libs/url_.py +50 -0
  126. cruhon-2.10.0/cruhon/core/libs/warnings_.py +50 -0
  127. cruhon-2.10.0/cruhon/core/libs/weakref_.py +61 -0
  128. cruhon-2.10.0/cruhon/core/libs/webbrowser_.py +30 -0
  129. cruhon-2.10.0/cruhon/core/libs/xml_.py +91 -0
  130. cruhon-2.10.0/cruhon/core/libs/xmlrpc_.py +44 -0
  131. cruhon-2.10.0/cruhon/core/libs/yaml_.py +54 -0
  132. cruhon-2.10.0/cruhon/core/libs/zipapp_.py +54 -0
  133. cruhon-2.10.0/cruhon/core/libs/zlib_.py +130 -0
  134. cruhon-2.10.0/cruhon/core/mod_loader.py +1021 -0
  135. cruhon-2.10.0/cruhon/core/namespace_runtime.py +186 -0
  136. cruhon-2.10.0/cruhon/core/parser.py +1765 -0
  137. cruhon-2.10.0/cruhon/core/registry.py +555 -0
  138. cruhon-2.10.0/cruhon/core/runner.py +733 -0
  139. cruhon-2.10.0/cruhon/core/syntax_engine.py +213 -0
  140. cruhon-2.10.0/cruhon/core/transpiler.py +1140 -0
  141. cruhon-2.10.0/cruhon/mods/cruhon-discord-example/__init__.py +36 -0
  142. cruhon-2.10.0/cruhon/tests/conftest.py +6 -0
  143. cruhon-2.10.0/cruhon/tests/test_cache.py +213 -0
  144. cruhon-2.10.0/cruhon/tests/test_cli.py +206 -0
  145. cruhon-2.10.0/cruhon/tests/test_concurrency.py +143 -0
  146. cruhon-2.10.0/cruhon/tests/test_core.py +6052 -0
  147. cruhon-2.10.0/cruhon/tests/test_crypto_log_config.py +331 -0
  148. cruhon-2.10.0/cruhon/tests/test_data.py +371 -0
  149. cruhon-2.10.0/cruhon/tests/test_db_env_migrate.py +190 -0
  150. cruhon-2.10.0/cruhon/tests/test_db_panel_bridge.py +151 -0
  151. cruhon-2.10.0/cruhon/tests/test_db_serialization.py +531 -0
  152. cruhon-2.10.0/cruhon/tests/test_db_serialization_extended.py +565 -0
  153. cruhon-2.10.0/cruhon/tests/test_db_serialization_v2.py +327 -0
  154. cruhon-2.10.0/cruhon/tests/test_dev_tools.py +209 -0
  155. cruhon-2.10.0/cruhon/tests/test_discord.py +1508 -0
  156. cruhon-2.10.0/cruhon/tests/test_dotted_block_commands.py +190 -0
  157. cruhon-2.10.0/cruhon/tests/test_expanded_methods.py +403 -0
  158. cruhon-2.10.0/cruhon/tests/test_expanded_methods2.py +203 -0
  159. cruhon-2.10.0/cruhon/tests/test_expanded_methods3.py +176 -0
  160. cruhon-2.10.0/cruhon/tests/test_expanded_methods4.py +428 -0
  161. cruhon-2.10.0/cruhon/tests/test_file_date.py +318 -0
  162. cruhon-2.10.0/cruhon/tests/test_file_mgmt_utils.py +243 -0
  163. cruhon-2.10.0/cruhon/tests/test_file_path.py +481 -0
  164. cruhon-2.10.0/cruhon/tests/test_gap_fill.py +442 -0
  165. cruhon-2.10.0/cruhon/tests/test_handlers_compile.py +67 -0
  166. cruhon-2.10.0/cruhon/tests/test_html_web.py +112 -0
  167. cruhon-2.10.0/cruhon/tests/test_language_v25.py +415 -0
  168. cruhon-2.10.0/cruhon/tests/test_language_v26.py +244 -0
  169. cruhon-2.10.0/cruhon/tests/test_namespace_isolation.py +83 -0
  170. cruhon-2.10.0/cruhon/tests/test_namespace_runtime.py +46 -0
  171. cruhon-2.10.0/cruhon/tests/test_networking.py +229 -0
  172. cruhon-2.10.0/cruhon/tests/test_os_introspect.py +219 -0
  173. cruhon-2.10.0/cruhon/tests/test_os_system.py +236 -0
  174. cruhon-2.10.0/cruhon/tests/test_panel.py +182 -0
  175. cruhon-2.10.0/cruhon/tests/test_schedule.py +140 -0
  176. cruhon-2.10.0/cruhon/tests/test_shell_archive_mail.py +363 -0
  177. cruhon-2.10.0/cruhon/tests/test_shortcuts_data.py +124 -0
  178. cruhon-2.10.0/cruhon/tests/test_shortcuts_data_v25.py +162 -0
  179. cruhon-2.10.0/cruhon/tests/test_testing_profiling.py +145 -0
  180. cruhon-2.10.0/cruhon/tests/test_text_http_csv.py +406 -0
  181. cruhon-2.10.0/cruhon/tests/test_text_math.py +211 -0
  182. cruhon-2.10.0/cruhon/tests/test_v27.py +169 -0
  183. cruhon-2.10.0/cruhon.egg-info/PKG-INFO +1316 -0
  184. cruhon-2.10.0/cruhon.egg-info/SOURCES.txt +188 -0
  185. cruhon-2.10.0/cruhon.egg-info/dependency_links.txt +1 -0
  186. cruhon-2.10.0/cruhon.egg-info/entry_points.txt +2 -0
  187. cruhon-2.10.0/cruhon.egg-info/requires.txt +14 -0
  188. cruhon-2.10.0/cruhon.egg-info/top_level.txt +1 -0
  189. cruhon-2.10.0/pyproject.toml +51 -0
  190. cruhon-2.10.0/setup.cfg +4 -0
cruhon-2.10.0/PKG-INFO ADDED
@@ -0,0 +1,1316 @@
1
+ Metadata-Version: 2.4
2
+ Name: cruhon
3
+ Version: 2.10.0
4
+ Summary: A modern, extensible scripting language built on Python — by CrucibleLab
5
+ Author-email: CrucibleLab <cruciblelab@hotmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/cruciblelab/cruhon
8
+ Project-URL: Repository, https://github.com/cruciblelab/cruhon
9
+ Project-URL: Issues, https://github.com/cruciblelab/cruhon/issues
10
+ Project-URL: Discord, https://discord.gg/SPf5VZ6QPG
11
+ Keywords: language,dsl,scripting,transpiler,cruhon
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Topic :: Software Development :: Interpreters
19
+ Classifier: Topic :: Software Development :: Compilers
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ Provides-Extra: requests
23
+ Requires-Dist: requests>=2.28; extra == "requests"
24
+ Provides-Extra: discord
25
+ Requires-Dist: discord.py>=2.0; extra == "discord"
26
+ Provides-Extra: async
27
+ Requires-Dist: httpx>=0.24; extra == "async"
28
+ Provides-Extra: full
29
+ Requires-Dist: requests>=2.28; extra == "full"
30
+ Requires-Dist: discord.py>=2.0; extra == "full"
31
+ Requires-Dist: httpx>=0.24; extra == "full"
32
+
33
+ # Cruhon
34
+
35
+ **A modern, extensible scripting language built on Python.**
36
+ By [CrucibleLab](https://github.com/cruciblelab) · `.clpy` files · MIT License · **v2.10.0**
37
+
38
+ ---
39
+
40
+ ## What is Cruhon?
41
+
42
+ Cruhon is a scripting language that compiles to Python. It replaces Python's
43
+ indented block syntax with a uniform `@command[args]` syntax, making scripts
44
+ easier to read and write — especially for automation, tooling, and data tasks.
45
+
46
+ Every Cruhon program is valid Python under the hood. You can always inspect
47
+ the generated Python with `cruhon run --show-python`.
48
+
49
+ The plugin system lets you extend everything: new commands, new block types,
50
+ new value syntax, new runtime objects — all without touching the core.
51
+
52
+ **128 stdlib namespaces · 1839+ built-in commands · 4039 tests**
53
+
54
+ ---
55
+
56
+ ## Install
57
+
58
+ ```bash
59
+ pip install cruhon
60
+ ```
61
+
62
+ **Requirements:** Python 3.10+
63
+
64
+ ---
65
+
66
+ ## Quick Start
67
+
68
+ ```clpy
69
+ # hello.clpy
70
+ @var[name; "Cruhon"]
71
+ @print[Hello from {name}!]
72
+
73
+ @func[add; a; b]
74
+ @return[a + b]
75
+ @end
76
+
77
+ @var[result; add(5, 3)]
78
+ @print[5 + 3 = {result}]
79
+
80
+ @for[i; range(3)]
81
+ @print[i = {i}]
82
+ @end
83
+ ```
84
+
85
+ ```bash
86
+ cruhon run hello.clpy
87
+ ```
88
+
89
+ ---
90
+
91
+ ## CLI
92
+
93
+ | Command | Description |
94
+ |---|---|
95
+ | `cruhon run file.clpy` | Run a script |
96
+ | `cruhon run file.clpy --show-python` | Show generated Python before running |
97
+ | `cruhon run file.clpy --watch` | Re-run automatically when `.clpy` files change |
98
+ | `cruhon run file.clpy --log cruhon.log [--log-level DEBUG]` | Write diagnostics to a log file |
99
+ | `cruhon repl` | Start an interactive session (`:help`, `:vars`, `:history`, `:load`, `:type`, `:clear`, `:quit`) |
100
+ | `cruhon docs` | List plugins that ship a command reference |
101
+ | `cruhon fmt file.clpy` | Normalize indentation (writes the file) |
102
+ | `cruhon fmt file.clpy --check` | Exit non-zero if not formatted (CI-friendly) |
103
+ | `cruhon fmt file.clpy --stdout` | Print formatted result without writing |
104
+ | `cruhon build file.clpy` | Compile `.clpy` → `.py` |
105
+ | `cruhon build file.clpy -o out.py` | Compile to a specific output file |
106
+ | `cruhon check file.clpy` | Check for syntax errors without running |
107
+ | `cruhon new myproject` | Create a new project scaffold |
108
+ | `cruhon new --plugin myplugin` | Create a plugin scaffold in `mods/myplugin/` |
109
+ | `cruhon libs` | List all supported libraries |
110
+ | `cruhon mods` | Show loaded plugins, APIs, block commands, overrides |
111
+ | `cruhon cache` | Show transpile cache stats |
112
+ | `cruhon cache --clear` | Clear the transpile cache |
113
+ | `cruhon --version` | Show version |
114
+
115
+ ---
116
+
117
+ ## Language Reference
118
+
119
+ ### Syntax Rules
120
+
121
+ Every statement starts with `@`. Arguments go inside `[...]`, separated by `;`.
122
+ Blocks are opened by a command and closed by `@end`.
123
+
124
+ ```
125
+ @command[arg1; arg2; key=value]
126
+
127
+ @block[args]
128
+ # body
129
+ @end
130
+ ```
131
+
132
+ ### Variables and Constants
133
+
134
+ ```clpy
135
+ @var[x; 42] # x = 42
136
+ @var[name; "Alice"] # name = "Alice"
137
+ @var[msg; Hello world] # msg = "Hello world" (bare text → string)
138
+ @var[copy; name] # copy = name (identifier → variable reference)
139
+ @const[MAX; 100] # MAX = 100 # const (convention: uppercase)
140
+ ```
141
+
142
+ Named parameters work everywhere:
143
+
144
+ ```clpy
145
+ @http.post["https://api.example.com/ban"; reason="spam"; days=7]
146
+ ```
147
+
148
+ ### Type Annotations
149
+
150
+ Type annotations are first-class — no `@raw` required:
151
+
152
+ ```clpy
153
+ @var[score: float; 0.0]
154
+ @const[LIMIT: int; 100]
155
+ @type[Matrix; list[list[float]]]
156
+
157
+ @dataclass[Point]
158
+ x: float = 0.0
159
+ y: float = 0.0
160
+ @end
161
+
162
+ @func[distance; p: Point; q: Point; return=float]
163
+ @return[((p.x - q.x)**2 + (p.y - q.y)**2) ** 0.5]
164
+ @end
165
+ ```
166
+
167
+ | Syntax | Emits |
168
+ |---|---|
169
+ | `@var[x: int; 42]` | `x: int = 42` |
170
+ | `@var[x: int]` | `x: int` (annotation-only) |
171
+ | `@const[LIMIT: int; 100]` | `LIMIT: int = 100 # const` |
172
+ | `@func[f; a: int; b: str; return=bool]` | `def f(a: int, b: str) -> bool:` |
173
+ | `@type[Vector; list[float]]` | `Vector = list[float] # type alias` |
174
+ | `@dataclass[Point] ... @end` | `@dataclass` decorated class block |
175
+
176
+ ### Output and Input
177
+
178
+ ```clpy
179
+ @print[Hello, World!] # print("Hello, World!")
180
+ @print[Value is {x}] # print(f"Value is {x}")
181
+ @var[line; @input[Enter: ]] # line = input("Enter: ")
182
+ ```
183
+
184
+ ### String Interpolation
185
+
186
+ Use `{varname}` inside any value to embed a variable:
187
+
188
+ ```clpy
189
+ @var[name; "Alice"]
190
+ @print[Hello, {name}!] # f"Hello, {name}!"
191
+ @var[msg; "Hi, {name}!"] # f"Hi, {name}!"
192
+ @var[info; age={user.age}] # f"age={user.age}"
193
+ ```
194
+
195
+ ### Control Flow
196
+
197
+ ```clpy
198
+ @if[x > 0]
199
+ @print[positive]
200
+ @elif[x == 0]
201
+ @print[zero]
202
+ @else
203
+ @print[negative]
204
+ @end
205
+
206
+ @for[i; range(10)]
207
+ @print[{i}]
208
+ @end
209
+
210
+ @while[x > 0]
211
+ @var[x; x - 1]
212
+ @end
213
+
214
+ @repeat[5]
215
+ @print[hello]
216
+ @end
217
+
218
+ @for[i; range(10)]
219
+ @if[i == 5]
220
+ @break
221
+ @end
222
+ @if[i % 2 == 0]
223
+ @continue
224
+ @end
225
+ @print[{i}]
226
+ @end
227
+ ```
228
+
229
+ ### Pattern Matching (Python 3.10+)
230
+
231
+ ```clpy
232
+ @match[status]
233
+ @case[200]
234
+ @print[OK]
235
+ @case[404]
236
+ @print[Not Found]
237
+ @default
238
+ @print[Unknown status]
239
+ @end
240
+
241
+ @match[command.split()]
242
+ @case[["quit"]]
243
+ @print[Quitting]
244
+ @case[["go", direction]]
245
+ @print[Going {direction}]
246
+ @default
247
+ @print[Unknown command]
248
+ @end
249
+ ```
250
+
251
+ > Case patterns are raw Python match-pattern syntax passed straight through
252
+ > — use a real comma `,` for structural list/tuple patterns like
253
+ > `["go", direction]`, not Cruhon's `;` argument separator.
254
+
255
+ ### Functions
256
+
257
+ ```clpy
258
+ @func[greet; name]
259
+ @print[Hello, {name}!]
260
+ @return[name]
261
+ @end
262
+
263
+ greet("Bob") # call with Python syntax
264
+ ```
265
+
266
+ Async functions:
267
+
268
+ ```clpy
269
+ @async[main]
270
+ @var[res; @http.async_get["https://example.com"]]
271
+ @var[data; @http.json[res]]
272
+ @print[Got {len(data)} bytes]
273
+ @end
274
+
275
+ @asyncio.run[main()]
276
+ ```
277
+
278
+ Async for and async with:
279
+
280
+ ```clpy
281
+ @async[main]
282
+ @async.for[item; async_generator()]
283
+ @print[{item}]
284
+ @end
285
+
286
+ @async.with[aiofiles.open("file.txt") as f]
287
+ @var[content; await f.read()]
288
+ @print[{content}]
289
+ @end
290
+ @end
291
+ ```
292
+
293
+ ### Classes
294
+
295
+ ```clpy
296
+ @class[Animal]
297
+ @func[__init__; self; name]
298
+ @var[self.name; name]
299
+ @end
300
+ @end
301
+
302
+ @class[Dog; Animal]
303
+ @func[speak; self]
304
+ @print[Woof! I am {self.name}]
305
+ @end
306
+ @end
307
+
308
+ @var[dog; Dog("Rex")]
309
+ dog.speak()
310
+ ```
311
+
312
+ Multiple inheritance:
313
+
314
+ ```clpy
315
+ @class[Cat; Animal; Serializable; JsonMixin]
316
+ ...
317
+ @end
318
+ ```
319
+
320
+ ### Error Handling
321
+
322
+ ```clpy
323
+ @try
324
+ @var[x; int("bad")]
325
+ @catch[e]
326
+ @print[Error: {e}]
327
+ @else
328
+ @print[no error]
329
+ @finally
330
+ @print[done]
331
+ @end
332
+
333
+ @raise[ValueError; "invalid input"]
334
+
335
+ @try
336
+ risky_call()
337
+ @catch[e]
338
+ @raise
339
+ @end
340
+ ```
341
+
342
+ Retry and timeout:
343
+
344
+ ```clpy
345
+ @retry[3]
346
+ risky_api_call()
347
+ @end
348
+
349
+ @retry[5; requests.ConnectionError]
350
+ risky_api_call()
351
+ @end
352
+
353
+ @timeout[30]
354
+ slow_operation()
355
+ @end
356
+ ```
357
+
358
+ ### Context Managers
359
+
360
+ ```clpy
361
+ @with[open("data.txt") as f]
362
+ @var[content; f.read()]
363
+ @print[{content}]
364
+ @end
365
+
366
+ @with[lock]
367
+ do_work()
368
+ @end
369
+ ```
370
+
371
+ ### Templates and Pipelines
372
+
373
+ ```clpy
374
+ @template[greeting]
375
+ Hello, {name}! You have {count} messages.
376
+ @end
377
+
378
+ @var[msg; @render[greeting; name="Alice"; count=5]]
379
+ @print[{msg}]
380
+
381
+ @pipeline[normalize; str.strip; str.lower]
382
+ @var[result; @apply[normalize; " Hello "]] # "hello"
383
+ ```
384
+
385
+ ### Multi-variable assignment and spread
386
+
387
+ ```clpy
388
+ @let[x; 10; y; 20; z; 30]
389
+
390
+ @var[n; @spread[max; [3, 1, 4, 1, 5]]] # max(*[...]) → 5
391
+ @var[r; @unpack[dict; {"a": 1, "b": 2}]] # dict(**{...})
392
+ ```
393
+
394
+ ### Other Commands
395
+
396
+ ```clpy
397
+ @del[x]
398
+ @del[a; b; c]
399
+ @assert[x > 0; "x must be positive"]
400
+ @var[home; @env[HOME]]
401
+ @var[port; @env[PORT; 8080]]
402
+ @import[requests]
403
+ @import[requests; req]
404
+ @include[utils.clpy]
405
+ @swap[a; b]
406
+ @inc[counter]
407
+ @dec[counter; 5]
408
+ ```
409
+
410
+ ### Raw Python Blocks
411
+
412
+ ```clpy
413
+ @raw
414
+ import sys
415
+ from pathlib import Path
416
+ x = [i**2 for i in range(10)]
417
+ @end
418
+ ```
419
+
420
+ ### Collections
421
+
422
+ ```clpy
423
+ @var[lst; @list[1; 2; 3]]
424
+ @var[d; @dict["name"; "Alice"; "age"; 30]]
425
+ ```
426
+
427
+ ### Multi-line Expressions
428
+
429
+ ```clpy
430
+ @var[result; max(
431
+ score_a,
432
+ score_b,
433
+ score_c
434
+ )]
435
+
436
+ @var[items; [
437
+ "apple",
438
+ "banana",
439
+ "cherry"
440
+ ]]
441
+ ```
442
+
443
+ ---
444
+
445
+ ## Errors & Diagnostics
446
+
447
+ When something goes wrong, Cruhon shows the exact line, a caret under the
448
+ problem, a plain-language hint, and a spelling suggestion:
449
+
450
+ ```
451
+ ✗ NameError greet.clpy:3
452
+
453
+ name 'price' is not defined
454
+
455
+ 1 │ @var[name; "World"]
456
+ 2 │ @print[Hello]
457
+ → 3 │ @var[total; price + tax]
458
+
459
+ Hint: 'price' is not defined as a variable.
460
+ If you meant text, wrap it in quotes: "price".
461
+ ```
462
+
463
+ `cruhon check file.clpy` shows the same rich excerpt for syntax errors without
464
+ running the script. Color auto-disables for pipes and when `NO_COLOR` is set.
465
+
466
+ ### Logging
467
+
468
+ ```bash
469
+ # Cruhon engine diagnostics
470
+ CRUHON_LOG=cruhon.log cruhon run app.clpy
471
+ CRUHON_LOG_LEVEL=DEBUG CRUHON_LOG=cruhon.log cruhon run app.clpy
472
+ cruhon run app.clpy --log cruhon.log --log-level DEBUG
473
+ ```
474
+
475
+ Levels: `ERROR`, `WARNING`, `INFO` (default), `DEBUG`.
476
+
477
+ For **script-level** application logging use `@log.*`:
478
+
479
+ ```clpy
480
+ @log.setup["DEBUG"; "app.log"]
481
+ @log.info["Application started"]
482
+ @log.warning["Something looks off"]
483
+ @log.error["Critical failure"]
484
+ ```
485
+
486
+ ### Transpile Cache
487
+
488
+ Cruhon caches compiled output in `.cruhon_cache/`. Re-runs of unchanged
489
+ scripts skip parsing entirely.
490
+
491
+ ```bash
492
+ cruhon cache # show stats
493
+ cruhon cache --clear # delete cache
494
+ cruhon run app.clpy --no-cache # bypass for one run
495
+ ```
496
+
497
+ ---
498
+
499
+ ## Value Semantics
500
+
501
+ **`expr` context** — right-hand sides of `@var`, `@const`, `@return`, etc.:
502
+ - `"text"` → string literal
503
+ - `42`, `3.14` → numeric literal
504
+ - `True`, `False`, `None` → Python literal
505
+ - `[...]`, `{...}`, `(...)` → collection or expression, passed through
506
+ - Expression with operator / call / dot → passed through as Python expression
507
+ - Single identifier → Python variable reference
508
+ - Bare text → string literal
509
+
510
+ **`display` context** — `@print`, `@assert` message:
511
+ - Same as `expr`, except a **single identifier becomes a string literal**
512
+ - Use `{varname}` for variable interpolation
513
+
514
+ ---
515
+
516
+ ## Context Variables (`@ctx`)
517
+
518
+ `__ctx__` is a shared dict available throughout script execution.
519
+
520
+ ```clpy
521
+ @ctx.set["username"; "Alice"]
522
+ @var[u; @ctx.get["username"]]
523
+ @var[u; @ctx.get["username"; "guest"]]
524
+ @ctx.delete["score"]
525
+ @ctx.clear[]
526
+
527
+ # Stack-based scope
528
+ @ctx.push[]
529
+ @ctx.set["x"; "inner"]
530
+ @var[inner_x; @ctx["x"]]
531
+ @print[{inner_x}]
532
+ @ctx.pop[]
533
+ ```
534
+
535
+ ---
536
+
537
+ ## Standard Libraries — 128 Namespaces
538
+
539
+ All namespaces are available without `@import`. Just call them directly.
540
+ See [`library.md`](library.md) for the complete method reference.
541
+
542
+ ### Core (Cruhon-native)
543
+
544
+ | Namespace | What it does |
545
+ |---|---|
546
+ | `@file.*` | Read, write, copy, move, glob, mkdir, stat, symlink, chmod… |
547
+ | `@date.*` | now, format, parse, add, diff, timezone, ISO, weekday… |
548
+ | `@text.*` | upper, lower, split, replace, regex, slug, encode, partition… |
549
+ | `@http.*` | GET, POST, PUT, DELETE, upload, auth, async variants… |
550
+ | `@crypto.*` | SHA-256/512, hmac, pbkdf2, scrypt, UUID, token, base64… |
551
+ | `@log.*` | setup, info, warning, error, to_file, get, child, formatter… |
552
+ | `@config.*` | load, save, get, set, keys, dotenv, env (JSON/TOML/INI)… |
553
+ | `@shell.*` | run, output, lines, bg, kill, wait, env, cpu_count… |
554
+ | `@archive.*` | zip, unzip, tar, gzip, bzip2, lzma and all their inverses… |
555
+ | `@mail.*` | send, send_html, IMAP connect/search/fetch, SMTP login… |
556
+ | `@csv.*` | read, write, filter, to_json, append… |
557
+ | `@store.*` | set, get, all, clear, delete (in-memory key-value) |
558
+ | `@color.*` | red, green, blue, yellow, bold, dim, reset… |
559
+ | `@ctx.*` | set, get, push, pop, clear, delete |
560
+ | `@json.*` | load, dump |
561
+
562
+ ### Text & Math
563
+
564
+ | Namespace | Wraps | Highlights |
565
+ |---|---|---|
566
+ | `@math.*` | `math` | sqrt, floor, ceil, pow, log, sin/cos/tan, gcd, clamp… |
567
+ | `@random.*` | `random` | randint, choice, shuffle, sample, gauss, seed… |
568
+ | `@cmath.*` | `cmath` | Complex sqrt, exp, log, polar, rect, phase… |
569
+ | `@decimal.*` | `decimal` | Exact arithmetic: add, sub, mul, div, round, compare… |
570
+ | `@fraction.*` | `fractions` | make, add, sub, mul, div, simplify, to_float… |
571
+ | `@statistics.*` | `statistics` | mean, median, mode, stdev, variance, correlation… |
572
+ | `@textwrap.*` | `textwrap` | wrap, fill, indent, dedent, shorten, columns… |
573
+ | `@string.*` | `string` | ascii_letters, digits, punctuation, capwords, template… |
574
+ | `@unicode.*` | `unicodedata` | name, category, normalize, NFC/NFD, strip_accents… |
575
+ | `@colorsys.*` | `colorsys` | RGB↔HSV, RGB↔HLS, RGB↔YIQ, hex helpers, luminance, blend |
576
+ | `@codecs.*` | `codecs` | rot13, hex, zlib codec, encode/decode, stream wrappers… |
577
+
578
+ ### Data & Formats
579
+
580
+ | Namespace | Wraps | Highlights |
581
+ |---|---|---|
582
+ | `@collections.*` | `collections` | Counter, defaultdict, deque, namedtuple, OrderedDict… |
583
+ | `@itertools.*` | `itertools` | chain, cycle, product, combinations, groupby, flatten… |
584
+ | `@functools.*` | `functools` | reduce, partial, lru_cache, wraps, singledispatch… |
585
+ | `@operator.*` | `operator` | add, sub, mul, itemgetter, attrgetter, lt, gt, and_… |
586
+ | `@xml.*` | `xml.etree.ElementTree` | parse, find, findall, text, attr, children, to_string… |
587
+ | `@toml.*` | `tomllib` | loads, load, dumps, get, has, keys, to_dict… |
588
+ | `@yaml.*` | `pyyaml` | loads, dumps, load_file, dump_file, get, keys… |
589
+ | `@diff.*` | `difflib` | ratio, best_match, ndiff, unified_diff, sequence_matcher… |
590
+ | `@re.*` | `re` | search, match, findall, sub, split, groups, compile… |
591
+ | `@struct.*` | `struct` | pack, unpack, calcsize, pack_into, unpack_from… |
592
+ | `@binascii.*` | `binascii` | hexlify, unhexlify, b2a_base64, a2b_base64, crc32… |
593
+ | `@pickle.*` | `pickle` | dump, dumps, load, loads, to_file, from_file, copy… |
594
+ | `@shelve.*` | `shelve` | open, get, set, delete, keys, values, items, sync… |
595
+ | `@plist.*` | `plistlib` | dumps, loads, to_file, from_file, to_dict… |
596
+ | `@reprlib.*` | `reprlib` | repr, shorten, maxstring, maxlist, maxdict, maxset… |
597
+ | `@graphlib.*` | `graphlib` | sort, is_dag, ancestors, descendants, roots, leaves… |
598
+
599
+ ### File & Path
600
+
601
+ | Namespace | Wraps | Highlights |
602
+ |---|---|---|
603
+ | `@pathlib.*` | `pathlib` | path, join, name, stem, suffix, exists, is_file, mkdir… |
604
+ | `@glob.*` | `glob` | glob, iglob, recursive, escape, fnmatch… |
605
+ | `@tempfile.*` | `tempfile` | file, dir, named, spooled, mkstemp, mkdtemp… |
606
+ | `@fnmatch.*` | `fnmatch` | match, filter, translate, fnmatchcase… |
607
+ | `@fileinput.*` | `fileinput` | lines, input, filename, lineno, close… |
608
+ | `@stat.*` | `stat` | mode_str, is_dir, is_file, is_link, is_socket, permissions… |
609
+ | `@shutil.*` | `shutil` | copy, move, tree, rmtree, disk_usage, which, unpack_archive… |
610
+ | `@filecmp.*` | `filecmp` | equal, shallow, dircmp, same_files, diff_files, compare… |
611
+ | `@linecache.*` | `linecache` | line, lines, count, check, clear… |
612
+ | `@mmap.*` | `mmap` | read, slice, size, find, open, seek, put, flush, close… |
613
+ | `@zipapp.*` | `zipapp` | create, interpreter, is_archive, copy… |
614
+
615
+ ### OS & System
616
+
617
+ | Namespace | Wraps | Highlights |
618
+ |---|---|---|
619
+ | `@os.*` | `os` | env, path, listdir, getcwd, makedirs… |
620
+ | `@sys.*` | `sys` | argv, exit, path, version, platform, getsizeof, stdin… |
621
+ | `@platform.*` | `platform` | system, node, release, version, machine, python_version… |
622
+ | `@gc.*` | `gc` | collect, enable, disable, isenabled, count, threshold… |
623
+ | `@inspect.*` | `inspect` | signature, members, source, module, file, isfunction… |
624
+ | `@traceback.*` | `traceback` | format, print, format_exc, extract, lines, last… |
625
+ | `@warnings.*` | `warnings` | warn, ignore, error, once, always, simplefilter… |
626
+ | `@weakref.*` | `weakref` | ref, proxy, finalize, deref, is_alive… |
627
+ | `@types.*` | `types` | new_class, SimpleNamespace, MappingProxy, is_function… |
628
+ | `@abc.*` | `abc` | abstract, abstractmethod, isabstract, ABC, ABCMeta… |
629
+ | `@signal.*` | `signal` | handler, send, alarm, pause, set_wakeup, getsignal… |
630
+ | `@atexit.*` | `atexit` | register, unregister, handlers… |
631
+ | `@locale.*` | `locale` | setlocale, getlocale, format_number, currency, strxfrm… |
632
+ | `@gettext.*` | `gettext` | translation, gettext, ngettext, bindtextdomain… |
633
+ | `@sysconfig.*` | `sysconfig` | get_path, get_config_var, get_platform, variables… |
634
+ | `@resource.*` | `resource` | getrlimit, setrlimit, getrusage, RLIMIT_CPU, RLIMIT_AS… |
635
+ | `@errno.*` | `errno` | name, description, code, ENOENT, EEXIST, EACCES… |
636
+ | `@getpass.*` | `getpass` | password, user, terminal… |
637
+
638
+ ### Networking
639
+
640
+ | Namespace | Wraps | Highlights |
641
+ |---|---|---|
642
+ | `@http.*` | `requests` / `httpx` | GET/POST/PUT/DELETE, upload, auth, sessions, async… |
643
+ | `@httpx.*` | `httpx` | client, async_client, timeout, follow_redirects… |
644
+ | `@socket.*` | `socket` | connect, send, recv, server, bind, accept, udp, tcp… |
645
+ | `@ssl.*` | `ssl` | wrap, context, load_cert, verify_mode, check_hostname… |
646
+ | `@ftp.*` | `ftplib` | connect, login, list, download, upload, rename, mkdir… |
647
+ | `@pop3.*` | `poplib` | connect, list, retrieve, delete, stat, top, uidl… |
648
+ | `@xmlrpc.*` | `xmlrpc.client` | client, call, multi_call, fault, close… |
649
+ | `@httpserver.*` | `http.server` | serve, serve_async, threaded, stop, close, port… |
650
+ | `@selectors.*` | `selectors` | new, watch_read, watch_write, wait, count, modify… |
651
+ | `@ip.*` | `ipaddress` | address, network, is_private, is_global, hosts, netmask… |
652
+ | `@url.*` | `urllib.parse` | parse, join, quote, unquote, encode, scheme, netloc… |
653
+ | `@html.*` | `html` / `re` | escape, unescape, strip_tags, links, images, text… |
654
+ | `@webbrowser.*` | `webbrowser` | open, open_new, open_tab, get, browsers, controller… |
655
+ | `@mimetypes.*` | `mimetypes` | guess, guess_ext, guess_all, is_text, charset, known… |
656
+
657
+ ### Concurrency
658
+
659
+ | Namespace | Wraps | Highlights |
660
+ |---|---|---|
661
+ | `@asyncio.*` | `asyncio` | run, gather, wait_for, task, lock, queue, semaphore, open… |
662
+ | `@threading.*` | `threading` | Thread, Lock, RLock, Event, Semaphore, Condition, Barrier… |
663
+ | `@multiprocessing.*` | `multiprocessing` | cpus, pool, map, starmap, process, queue, pipe, event… |
664
+ | `@futures.*` | `concurrent.futures` | threads, processes, submit, result, map, wait_first… |
665
+ | `@queue.*` | `queue` | Queue, LifoQueue, PriorityQueue, put, get, empty, full… |
666
+ | `@sched.*` | `sched` | new, run, after, at, cancel, empty, queue… |
667
+
668
+ ### Serialization & Database
669
+
670
+ | Namespace | Wraps | Highlights |
671
+ |---|---|---|
672
+ | `@sqlite.*` | `sqlite3` | open, close, execute, fetchall, fetchone, commit, tables… |
673
+ | `@pickle.*` | `pickle` | dump, load, dumps, loads, to_file, from_file, copy… |
674
+ | `@shelve.*` | `shelve` | open, get, set, delete, keys, items, sync, close… |
675
+ | `@plist.*` | `plistlib` | dumps, loads, to_file, from_file… |
676
+ | `@configparser.*` | `configparser` | load, new, get, set, sections, add_section, save… |
677
+
678
+ ### Testing & Profiling
679
+
680
+ | Namespace | Wraps | Highlights |
681
+ |---|---|---|
682
+ | `@unittest.*` | `unittest` | run, discover, assert_equal, assert_true, mock, patch… |
683
+ | `@doctest.*` | `doctest` | run, testmod, testfile, globs, verbose… |
684
+ | `@timeit.*` | `timeit` | time, repeat, stmt, setup, auto… |
685
+ | `@profile.*` | `cProfile` | run, sort, stats, dump, top, cumulative, callers… |
686
+ | `@tracemalloc.*` | `tracemalloc` | start, stop, snapshot, top, compare, peak, size… |
687
+
688
+ ### Developer Tools
689
+
690
+ | Namespace | Wraps | Highlights |
691
+ |---|---|---|
692
+ | `@ast.*` | `ast` | parse, dump, unparse, walk, names, functions, is_valid… |
693
+ | `@dis.*` | `dis` | bytecode, instructions, opnames, consts, varnames… |
694
+ | `@tokenize.*` | `tokenize` | tokens, names, keywords, comments, ops, numbers, count… |
695
+ | `@keyword.*` | `keyword` | iskeyword, issoftkeyword, all, soft_all, kwlist… |
696
+ | `@importlib.*` | `importlib` | import_module, reload, find_spec, source_hash… |
697
+ | `@inspect.*` | `inspect` | signature, members, source, isfunction, isclass… |
698
+ | `@pdb.*` | `pdb` | bp, pm, run, runeval, runcall, new, set_bp… |
699
+ | `@runpy.*` | `runpy` | module, path, module_ns, path_ns, find, result… |
700
+ | `@numbers.*` | `numbers` | is_number, is_complex, is_real, is_rational, is_integral |
701
+ | `@reprlib.*` | `reprlib` | repr, shorten, maxstring, maxlist, maxdict… |
702
+
703
+ ### Encoding & Compression
704
+
705
+ | Namespace | Wraps | Highlights |
706
+ |---|---|---|
707
+ | `@base64.*` | `base64` | encode, decode, urlsafe, b32, b16, standard, pad… |
708
+ | `@codecs.*` | `codecs` | encode, decode, rot13, hex, zlib codec, reader, writer… |
709
+ | `@binascii.*` | `binascii` | hexlify, unhexlify, b2a_base64, crc32, rlecode… |
710
+ | `@zlib.*` | `zlib` | compress, decompress, crc32, adler32, crc32_hex… |
711
+ | `@struct.*` | `struct` | pack, unpack, calcsize, iter_unpack… |
712
+ | `@archive.*` | `zipfile`/`tarfile`/`gzip`/`bz2`/`lzma` | zip, unzip, tar, gzip, bzip2, lzma and inverses |
713
+
714
+ ### Foreign Function Interface
715
+
716
+ | Namespace | Wraps | Highlights |
717
+ |---|---|---|
718
+ | `@ctypes.*` | `ctypes` | load CDLL, all C scalar types, buffer, pointer, cast… |
719
+ | `@array.*` | `array` | typed compact arrays — append, extend, insert, pop, slice… |
720
+
721
+ ### Configuration & Secrets
722
+
723
+ | Namespace | Wraps | Highlights |
724
+ |---|---|---|
725
+ | `@env.*` | `os.environ` | get, require, has, typed (`int`/`float`/`bool`/`list`/`json`), set, `.env` load/parse/save, **mask** (hide secrets), expand `$VAR` |
726
+ | `@config.*` | `json`/`tomllib`/`configparser` | load, save, get, set, keys, dotenv, env |
727
+
728
+ ```clpy
729
+ # Read configuration safely from the environment / a .env file
730
+ @env.load[] # auto-load ./.env
731
+ @var[port; @env.int["PORT"; 8080]] # typed, with a default
732
+ @var[debug; @env.bool["DEBUG"]] # 1/true/yes/on → True
733
+ @var[key; @env.require["API_KEY"]] # raises if missing
734
+ @var[safe; @env.mask[key]] # "ab••••••89" — safe to log
735
+ @print[Using key {safe}]
736
+ ```
737
+
738
+ ### Utilities
739
+
740
+ | Namespace | Wraps | Highlights |
741
+ |---|---|---|
742
+ | `@argparse.*` | `argparse` | new, add, run, run_known, parse, parse_dict… |
743
+ | `@dataclasses.*` | `dataclasses` | dataclass, field, asdict, astuple, fields, replace… |
744
+ | `@typing.*` | `typing` | Optional, Union, List, Dict, Any, Callable, TypeVar… |
745
+ | `@enum.*` | `enum` | Enum, IntEnum, StrEnum, Flag, auto, create, names… |
746
+ | `@contextlib.*` | `contextlib` | contextmanager, suppress, redirect_stdout, ExitStack… |
747
+ | `@copy.*` | `copy` | copy, deepcopy, replace… |
748
+ | `@io.*` | `io` | StringIO, BytesIO, read, write, seek, getvalue… |
749
+ | `@heapq.*` | `heapq` | heappush, heappop, heapify, nlargest, nsmallest… |
750
+ | `@bisect.*` | `bisect` | bisect_left, bisect_right, insort, insort_left… |
751
+ | `@calendar.*` | `calendar` | is_leap, days_in_month, month_name, weekday… |
752
+ | `@pprint.*` | `pprint` | pformat, pprint, saferepr, isreadable, isrecursive… |
753
+ | `@shlex.*` | `shlex` | split, join, quote, lex, punctuation_chars… |
754
+ | `@colorsys.*` | `colorsys` | RGB↔HSV/HLS/YIQ, hex_to_rgb, rgb_to_hex, luminance… |
755
+ | `@zipapp.*` | `zipapp` | create, is_archive, interpreter, copy… |
756
+
757
+ ---
758
+
759
+ ## Examples
760
+
761
+ ### HTTP API script
762
+
763
+ ```clpy
764
+ @var[res; @http.get["https://jsonplaceholder.typicode.com/todos/1"]]
765
+ @var[todo; @http.json[res]]
766
+ @print[Title: {todo["title"]}]
767
+ @print[Done: {todo["completed"]}]
768
+ ```
769
+
770
+ ### File processing
771
+
772
+ ```clpy
773
+ @var[lines; @file.readlines["data.txt"]]
774
+ @for[line; lines]
775
+ @var[clean; @text.strip[line]]
776
+ @if[clean]
777
+ @file.append["output.txt"; "{clean}\n"]
778
+ @end
779
+ @end
780
+ ```
781
+
782
+ ### Async HTTP
783
+
784
+ ```clpy
785
+ @async[fetch_all; urls]
786
+ @var[tasks; [@asyncio.task[@http.async_get[u]] for u in urls]]
787
+ @var[results; @asyncio.gather[*tasks]]
788
+ @return[results]
789
+ @end
790
+
791
+ @var[urls; ["https://example.com", "https://httpbin.org/get"]]
792
+ @asyncio.run[fetch_all(urls)]
793
+ ```
794
+
795
+ ### CSV → JSON
796
+
797
+ ```clpy
798
+ @var[rows; @csv.read["sales.csv"]]
799
+ @var[filtered; [r for r in rows if float(r["amount"]) > 100]]
800
+ @file.write["big_sales.json"; @json.dump[filtered]]
801
+ @print[Exported {len(filtered)} rows]
802
+ ```
803
+
804
+ ### Parallel processing
805
+
806
+ ```clpy
807
+ @func[process; item]
808
+ @return[item * item]
809
+ @end
810
+
811
+ @var[data; list(range(1000))]
812
+ @var[pool; @futures.threads[8]]
813
+ @var[results; @futures.map[pool; process; data]]
814
+ @print[Sum: {sum(results)}]
815
+ ```
816
+
817
+ ### Color system conversion
818
+
819
+ ```clpy
820
+ @var[rgb; @colorsys.hex_to_rgb["#3498db"]]
821
+ @var[h; @colorsys.to_hls[rgb[0]; rgb[1]; rgb[2]]]
822
+ @print[Hue: {h[0]:.2f} Lightness: {h[1]:.2f}]
823
+ ```
824
+
825
+ ### Tokenize Python source
826
+
827
+ ```clpy
828
+ @var[src; "def add(a, b):\n return a + b\n"]
829
+ @var[keywords; @tokenize.keywords[src]]
830
+ @for[tok; keywords]
831
+ @print[keyword: {tok.string}]
832
+ @end
833
+ ```
834
+
835
+ ### Run code under debugger
836
+
837
+ ```clpy
838
+ @var[dbg; @pdb.new[]]
839
+ @pdb.set_bp[dbg; "mymodule.py"; 42]
840
+ @pdb.run[dbg; "mymodule.run()"]
841
+ ```
842
+
843
+ ---
844
+
845
+ ## Plugin System
846
+
847
+ Cruhon's plugin system lets you extend the language itself. Plugins can:
848
+
849
+ - Add new `@commands` and `@block ... @end` commands
850
+ - Override existing commands with middleware chains
851
+ - Inject runtime objects into scripts (database connections, config, etc.)
852
+ - Register inline expression commands (`@var[x; @uuid[]]`)
853
+ - Hook into value evaluation at transpile-time
854
+ - Manipulate the AST before code generation
855
+ - Communicate with other plugins via expose/consume
856
+ - Hook into lifecycle events (before/after run, parse, transpile)
857
+
858
+ ### Project Structure
859
+
860
+ ```
861
+ myproject/
862
+ ├── src/
863
+ │ └── main.clpy
864
+ └── mods/
865
+ └── my-plugin/
866
+ ├── mod.json
867
+ └── __init__.py
868
+ ```
869
+
870
+ ```bash
871
+ cruhon new --plugin my-plugin
872
+ ```
873
+
874
+ ### mod.json
875
+
876
+ ```json
877
+ {
878
+ "name": "my-plugin",
879
+ "version": "1.0.0",
880
+ "description": "What this plugin does",
881
+ "cruhon": ">=2.0.0"
882
+ }
883
+ ```
884
+
885
+ Any extra field is accessible via `api.config("key")` inside the plugin.
886
+
887
+ ### `register(api)` — Entry Point
888
+
889
+ Every plugin must have a `register(api)` function in `__init__.py`:
890
+
891
+ ```python
892
+ def register(api):
893
+ api.command("greet", parse_greet, visit_greet)
894
+ ```
895
+
896
+ ---
897
+
898
+ ## Plugin API Reference
899
+
900
+ ### `api.command(name, parser_fn, visitor_fn)`
901
+
902
+ ```python
903
+ from cruhon.core.ast_nodes import Node
904
+ from dataclasses import dataclass
905
+
906
+ @dataclass
907
+ class GreetNode(Node):
908
+ target: str = ""
909
+
910
+ def parse_greet(parser):
911
+ parser.advance()
912
+ args = parser.parse_args()
913
+ return GreetNode(target=args[0] if args else '"world"', line=0)
914
+
915
+ def visit_greet(transpiler, node):
916
+ return transpiler._line(f'print("Hello, " + str({node.target}))', node.line)
917
+
918
+ def register(api):
919
+ api.command("greet", parse_greet, visit_greet)
920
+ ```
921
+
922
+ ### `api.block_command(name, visitor_fn, scoped=False)`
923
+
924
+ ```python
925
+ def visit_section(transpiler, node):
926
+ title = node.args[0] if node.args else '"Untitled"'
927
+ body_code = "\n".join(r for n in node.body if (r := n.accept(transpiler)))
928
+ return (
929
+ transpiler._line(f'print("=== " + {title} + " ===")') +
930
+ "\n" + body_code
931
+ )
932
+
933
+ def register(api):
934
+ api.block_command("section", visit_section)
935
+ # scoped=True → __ctx__ snapshot, changes don't leak
936
+ api.block_command("isolated", visit_isolated, scoped=True)
937
+ ```
938
+
939
+ ### `api.override(command, fn)`
940
+
941
+ Multiple overrides form a middleware chain:
942
+
943
+ ```python
944
+ def timed_print(transpiler, node, next_fn):
945
+ before = transpiler._line('__t0__ = __import__("time").monotonic()')
946
+ result = next_fn()
947
+ after = transpiler._line('print(f"[{__import__(\"time\").monotonic()-__t0__:.3f}s]")')
948
+ return before + "\n" + result + "\n" + after
949
+
950
+ def register(api):
951
+ api.override("print", timed_print)
952
+ ```
953
+
954
+ ### `api.inject(key, value_or_factory)`
955
+
956
+ ```python
957
+ def register(api):
958
+ api.inject("APP_VERSION", "2.9.0")
959
+ api.inject("db", lambda: sqlite3.connect(":memory:"))
960
+ ```
961
+
962
+ ### `api.inject_once(key, factory)`
963
+
964
+ Factory runs once at load time; shared across all runs:
965
+
966
+ ```python
967
+ def register(api):
968
+ api.inject_once("pool", lambda: ConnectionPool(max_connections=10))
969
+ ```
970
+
971
+ ### `api.inline_command(name, handler_fn)`
972
+
973
+ ```python
974
+ def handle_uuid(parser):
975
+ parser.advance()
976
+ parser.parse_args()
977
+ return "str(__import__('uuid').uuid4())"
978
+
979
+ def register(api):
980
+ api.inline_command("uuid", handle_uuid)
981
+ ```
982
+
983
+ ### `api.eval_hook(fn)`
984
+
985
+ Hook into value evaluation at transpile-time:
986
+
987
+ ```python
988
+ def dollar_env(value, context):
989
+ if value.startswith("$") and value[1:].isidentifier():
990
+ return f'__import__("os").environ.get("{value[1:]}", "")'
991
+ return None
992
+
993
+ def register(api):
994
+ api.eval_hook(dollar_env)
995
+ ```
996
+
997
+ ### `api.ast_hook(node_type, fn)`
998
+
999
+ ```python
1000
+ def prefix_vars(node):
1001
+ if not node.name.startswith("_"):
1002
+ node.name = "safe_" + node.name
1003
+ return node
1004
+
1005
+ def register(api):
1006
+ api.ast_hook("VarNode", prefix_vars)
1007
+ ```
1008
+
1009
+ ### `api.transform(target, fn)`
1010
+
1011
+ Wrap another plugin's block output:
1012
+
1013
+ ```python
1014
+ def register(api):
1015
+ api.transform("route", time_route)
1016
+
1017
+ def time_route(transpiler, node, code):
1018
+ path = node.args[0] if node.args else '"unknown"'
1019
+ before = transpiler._line('__t0__ = __import__("time").monotonic()')
1020
+ after = transpiler._line(f'print(f"route {path} took {{__import__(\"time\").monotonic()-__t0__:.3f}}s")')
1021
+ return before + "\n" + code + "\n" + after
1022
+ ```
1023
+
1024
+ ### `api.hook(event, fn)`
1025
+
1026
+ | Event | Signature | When |
1027
+ |---|---|---|
1028
+ | `before_run` | `fn(source=str)` | Before parsing |
1029
+ | `after_run` | `fn(source=str, python_code=str)` | After exec finishes |
1030
+ | `before_parse` | `fn(source) -> source` | Modify source text |
1031
+ | `after_parse` | `fn(ast) -> ast` | Modify AST |
1032
+ | `before_transpile` | `fn(ast) -> ast` | Transpiler pre-hook |
1033
+ | `after_transpile` | `fn(code) -> code` | Modify generated Python |
1034
+ | `on_error` | `fn(error=exc)` | On any error |
1035
+
1036
+ ```python
1037
+ def register(api):
1038
+ api.hook("before_run", lambda source: print("[run start]"))
1039
+ api.hook("on_error", lambda error: print(f"[error] {error}"))
1040
+ ```
1041
+
1042
+ ### `api.expose` / `api.consume`
1043
+
1044
+ ```python
1045
+ # Plugin A
1046
+ def register(api):
1047
+ api.expose("slugify", lambda s: s.lower().replace(" ", "-"))
1048
+
1049
+ # Plugin B
1050
+ def register(api):
1051
+ slugify = api.consume("plugin-a", "slugify")
1052
+ ```
1053
+
1054
+ ### `api.namespace(name)`
1055
+
1056
+ ```python
1057
+ def register(api):
1058
+ ns = api.namespace("db")
1059
+ ns.register("query", lambda args: f'__db__.execute({args[0]}).fetchall()')
1060
+ ns.hook("init", lambda ns: print("db ready"))
1061
+ ns.hook("destroy", lambda ns: print("db closed"))
1062
+ ```
1063
+
1064
+ ### Cleanup
1065
+
1066
+ ```python
1067
+ api.unregister_command("print")
1068
+ api.remove_hook("before_run", fn)
1069
+ api.remove_inject("db_pool")
1070
+ api.remove_eval_hook(dollar_env)
1071
+ ```
1072
+
1073
+ ### Other helpers
1074
+
1075
+ ```python
1076
+ api.alias("say", "print") # @say → @print
1077
+ api.require("cruhon-utils >= 1.2.0")
1078
+ api.is_loaded("cruhon-redis")
1079
+ api.config("my_setting", default="x")
1080
+ api.block_hook("enter", on_enter) # runtime block start/end events
1081
+ api.lexer_hook(lambda src: src.replace("§", "@"))
1082
+ api.token_hook(lambda toks: [t for t in toks if t.type != "COMMENT"])
1083
+ ```
1084
+
1085
+ ---
1086
+
1087
+ ## Complete Plugin Example
1088
+
1089
+ > Pick a namespace your plugin doesn't already share with a stdlib
1090
+ > namespace (`@log.*`, `@http.*`, `@file.*`, … are all reserved — see
1091
+ > [`library.md`](library.md) for the full list). This example uses
1092
+ > `applog` to stay clear of the built-in `@log.*` namespace. Even when a
1093
+ > custom `api.block_command`/`api.command` registration *does* share a
1094
+ > prefix with a stdlib namespace, the plugin's own registration always
1095
+ > wins for that exact dotted name — but picking a distinct name avoids
1096
+ > the confusion of two different things answering to `@log.*`.
1097
+ >
1098
+ > `api.command(name, parser_fn, visitor_fn)` expects a matching AST node
1099
+ > class named `name.title() + "Node"` — for `"applog"` that's `ApplogNode`.
1100
+
1101
+ ```python
1102
+ """
1103
+ mods/cruhon-applog/__init__.py
1104
+ Adds @applog["msg"] and @applog.timed[label] ... @end
1105
+ """
1106
+ import datetime
1107
+
1108
+
1109
+ class Logger:
1110
+ def __init__(self):
1111
+ self.lines = []
1112
+
1113
+ def log(self, msg):
1114
+ ts = datetime.datetime.now().strftime("%H:%M:%S")
1115
+ line = f"[{ts}] {msg}"
1116
+ self.lines.append(line)
1117
+ print(line)
1118
+
1119
+
1120
+ _logger = Logger()
1121
+
1122
+
1123
+ def parse_log(parser):
1124
+ from cruhon.core.ast_nodes import Node
1125
+ from dataclasses import dataclass
1126
+
1127
+ @dataclass
1128
+ class ApplogNode(Node):
1129
+ msg: str = ""
1130
+
1131
+ parser.advance()
1132
+ args = parser.parse_args()
1133
+ return ApplogNode(msg=args[0] if args else '""', line=0)
1134
+
1135
+
1136
+ def visit_log(transpiler, node):
1137
+ # Route the message through Cruhon's own value evaluator so
1138
+ # {variable} interpolation works exactly like it does in @print.
1139
+ msg = transpiler._eval_value(node.msg, "display")
1140
+ return transpiler._line(f'logger.log({msg})', node.line)
1141
+
1142
+
1143
+ def visit_timed(transpiler, node):
1144
+ label = (node.args[0] if node.args else '"block"').strip('"')
1145
+ body = "\n".join(r for n in node.body if (r := n.accept(transpiler)))
1146
+ t = transpiler
1147
+ return "\n".join([
1148
+ t._line("__t0__ = __import__('time').monotonic()"),
1149
+ body or t._line("pass"),
1150
+ t._line(f'logger.log(f"{label} took {{__import__(\'time\').monotonic()-__t0__:.3f}}s")'),
1151
+ ])
1152
+
1153
+
1154
+ def register(api):
1155
+ api.inject("logger", lambda: _logger)
1156
+ api.command("applog", parse_log, visit_log)
1157
+ api.block_command("applog.timed", visit_timed)
1158
+ api.expose("get_log_lines", lambda: _logger.lines)
1159
+ ```
1160
+
1161
+ Usage:
1162
+
1163
+ ```clpy
1164
+ @applog["Script started"]
1165
+
1166
+ @applog.timed["data processing"]
1167
+ @var[data; [1, 2, 3]]
1168
+ @var[result; sum(data)]
1169
+ @applog["Processed {result} items"]
1170
+ @end
1171
+
1172
+ @applog["Done"]
1173
+ ```
1174
+
1175
+ ---
1176
+
1177
+ ## Publishing a Plugin
1178
+
1179
+ ```
1180
+ cruhon-logger/
1181
+ ├── pyproject.toml
1182
+ └── cruhon_logger/
1183
+ ├── __init__.py
1184
+ └── mod.json
1185
+ ```
1186
+
1187
+ ```toml
1188
+ # pyproject.toml
1189
+ [project]
1190
+ name = "cruhon-logger"
1191
+ version = "1.0.0"
1192
+
1193
+ [project.entry-points."cruhon.mods"]
1194
+ cruhon-logger = "cruhon_logger:register"
1195
+ ```
1196
+
1197
+ ```bash
1198
+ pip install cruhon-logger
1199
+ cruhon run script.clpy # plugin auto-loaded
1200
+ cruhon mods # see it in the list
1201
+ ```
1202
+
1203
+ **Load order:** core → stdlib → pip plugins (alphabetical) → local `mods/` (alphabetical)
1204
+
1205
+ ---
1206
+
1207
+ ## Shortcut Plugins
1208
+
1209
+ Four configurable shortcut plugins add shorter aliases and extra convenience
1210
+ methods. All four load together without conflicts:
1211
+
1212
+ ```clpy
1213
+ # cruhon-shortcuts (base)
1214
+ @var[text; @read["notes.txt"]] # @read → @file.read
1215
+ @var[stamp; @now[]] # @now → @date.now
1216
+ @var[id; @uuid[]] # @uuid → @crypto.uuid
1217
+
1218
+ # cruhon-shortcuts-pro (math / lists / dicts / text / logic)
1219
+ @var[x; @clamp[value; 0; 100]]
1220
+ @var[slug; @snake_case["Hello World"]]
1221
+ @var[g; @group_by[items; key_fn]]
1222
+
1223
+ # cruhon-shortcuts-data (xml / toml / diff / decimal / re / yaml / image / pdf)
1224
+ @var[cfg; @toml_load["port = 8080"]]
1225
+ @var[amt; @money["3.14159"]] # decimal.money → 2dp
1226
+ @var[ok; @is_private_ip["10.0.0.1"]]
1227
+ ```
1228
+
1229
+ ---
1230
+
1231
+ ## Bundled Plugins
1232
+
1233
+ These ship in `mods/` and load automatically:
1234
+
1235
+ ### `@db.*` — multi-backend database (cruhon-db)
1236
+
1237
+ SQLite, PostgreSQL, MySQL with full CRUD, transactions, and async — 170+
1238
+ commands. Also env-aware connection, migrations, seeding, and a live
1239
+ `@panel` bridge:
1240
+
1241
+ ```clpy
1242
+ @db.connect_env["DATABASE_URL"] # DSN from the environment
1243
+ @db.migrate["./migrations"] # apply *.sql files once, in order
1244
+ @db.seed["users"; "fixtures/users.json"] # bulk-load JSON or CSV
1245
+ @var[safe; @db.dsn_safe[]]
1246
+ @print[Connected to {safe}] # DSN with the password masked
1247
+
1248
+ @panel.start[8787]
1249
+ @db.attach_panel[] # every query now streams to the panel
1250
+ ```
1251
+
1252
+ ### `@panel.*` — live log-stream dashboard (cruhon-panel)
1253
+
1254
+ A zero-dependency web panel that streams logs, metrics, and events to the
1255
+ browser over Server-Sent Events — pure standard library.
1256
+
1257
+ ```clpy
1258
+ @panel.start[8787] # → http://127.0.0.1:8787
1259
+ @panel.attach_logging["INFO"] # mirror all @log.* output to the panel
1260
+ @log.info["server warming up"] # …shows up live in the browser
1261
+ @panel.metric["users online"; 42] # update a metric tile
1262
+ @panel.event["deploy"; {"version": "2.10"}]
1263
+ @db.attach_panel[] # stream every SQL query too (needs @db)
1264
+ @panel.open[] # open the dashboard in a browser
1265
+ @panel.wait[] # keep the panel alive
1266
+ ```
1267
+
1268
+ ### `@discord.*` — Discord bot toolkit (cruhon-discord)
1269
+
1270
+ ~60 commands for building bots — see `mods/cruhon-discord`.
1271
+
1272
+ ---
1273
+
1274
+ ## Creating a New Project
1275
+
1276
+ ```bash
1277
+ cruhon new myproject
1278
+ cd myproject
1279
+ cruhon run src/main.clpy
1280
+ ```
1281
+
1282
+ Creates:
1283
+
1284
+ ```
1285
+ myproject/
1286
+ ├── src/
1287
+ │ └── main.clpy
1288
+ └── mods/
1289
+ └── README.md
1290
+ ```
1291
+
1292
+ ---
1293
+
1294
+ ## Contributing
1295
+
1296
+ ```bash
1297
+ git clone https://github.com/cruciblelab/cruhon
1298
+ cd cruhon
1299
+ pip install -e .
1300
+ python -m pytest cruhon/tests/
1301
+ cruhon run cruhon/examples/hello.clpy
1302
+ ```
1303
+
1304
+ ---
1305
+
1306
+ ## Contact
1307
+
1308
+ - **Discord:** [discord.gg/SPf5VZ6QPG](https://discord.gg/SPf5VZ6QPG)
1309
+ - **Email:** [cruciblelab@hotmail.com](mailto:cruciblelab@hotmail.com)
1310
+ - **GitHub:** [github.com/cruciblelab](https://github.com/cruciblelab)
1311
+
1312
+ ---
1313
+
1314
+ ## License
1315
+
1316
+ MIT — CrucibleLab