clewy-lang 1.0.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/README.md +245 -0
- package/package.json +17 -0
- package/src/cli.js +411 -0
- package/src/compiler.js +536 -0
- package/src/parser.js +359 -0
- package/src/runtime.js +180 -0
package/README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Clewy ✨
|
|
2
|
+
|
|
3
|
+
> The magic web-building language — `.cly` files become full websites.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
page "My App"
|
|
7
|
+
title "Hello World"
|
|
8
|
+
button "Click me" => show "Hello! 👋"
|
|
9
|
+
loop 3 => text "Simple is powerful"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
That's it. That compiles to a complete, styled, interactive website.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g clewy
|
|
20
|
+
# or use without installing:
|
|
21
|
+
npx clewy init my-app
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Requirements:** Node.js 14+
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx clewy init my-app # Create a new project
|
|
32
|
+
cd my-app
|
|
33
|
+
npx clewy run # Start dev server with live reload
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Your browser opens automatically at `http://localhost:3000`.
|
|
37
|
+
Edit `main.cly` — the page updates instantly.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Commands
|
|
42
|
+
|
|
43
|
+
| Command | What it does |
|
|
44
|
+
|---------|--------------|
|
|
45
|
+
| `npx clewy init <name>` | Create a new project |
|
|
46
|
+
| `npx clewy init <name> landing` | Create with a landing page template |
|
|
47
|
+
| `npx clewy init <name> todo` | Create with a todo app template |
|
|
48
|
+
| `npx clewy run` | Start dev server with live reload |
|
|
49
|
+
| `npx clewy run --port 8080` | Run on a custom port |
|
|
50
|
+
| `npx clewy build` | Build `index.html`, `style.css`, `script.js` |
|
|
51
|
+
| `npx clewy check` | Check `.cly` syntax without building |
|
|
52
|
+
| `npx clewy --help` | Show all commands |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Clewy Language Reference
|
|
57
|
+
|
|
58
|
+
### Page title
|
|
59
|
+
```
|
|
60
|
+
page "My Awesome App"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Headings
|
|
64
|
+
```
|
|
65
|
+
title "Big Heading"
|
|
66
|
+
subtitle "Smaller heading"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Text & links
|
|
70
|
+
```
|
|
71
|
+
text "Any paragraph of text here"
|
|
72
|
+
link "Click here" "https://example.com"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Buttons
|
|
76
|
+
```
|
|
77
|
+
button "Click me" => show "Hello!" # Toggle a message
|
|
78
|
+
button "Pop" => alert "A browser popup" # Browser alert
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Input
|
|
82
|
+
```
|
|
83
|
+
input "Enter your name..."
|
|
84
|
+
input "Search..." searchQuery # Named input (accessible in JS)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Show a message
|
|
88
|
+
```
|
|
89
|
+
show "This appears immediately on the page"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Images
|
|
93
|
+
```
|
|
94
|
+
image "https://example.com/photo.jpg" "Alt text"
|
|
95
|
+
image "local-photo.jpg"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Navigation bar
|
|
99
|
+
```
|
|
100
|
+
nav "Home" "About" "Contact" "Sign Up"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Footer
|
|
104
|
+
```
|
|
105
|
+
footer "© 2025 My App"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Loops
|
|
109
|
+
```
|
|
110
|
+
loop 5 => text "Repeated!" # Inline loop
|
|
111
|
+
loop 3 # Block loop
|
|
112
|
+
text "Multiple"
|
|
113
|
+
text "lines inside"
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Sections (grouped card)
|
|
118
|
+
```
|
|
119
|
+
section "Features"
|
|
120
|
+
text "✓ Feature one"
|
|
121
|
+
text "✓ Feature two"
|
|
122
|
+
button "Learn more" => show "Details"
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Reusable components
|
|
127
|
+
```
|
|
128
|
+
component PricingCard
|
|
129
|
+
title "Pro Plan"
|
|
130
|
+
text "$29/month"
|
|
131
|
+
button "Subscribe" => show "Let's go!"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
use PricingCard
|
|
135
|
+
use PricingCard # Use it multiple times
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Variables
|
|
139
|
+
```
|
|
140
|
+
var username "Alice"
|
|
141
|
+
var version "1.0"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Comments
|
|
145
|
+
```
|
|
146
|
+
# This is a comment — ignored by the compiler
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Generated Output
|
|
152
|
+
|
|
153
|
+
Every `.cly` file compiles to three files:
|
|
154
|
+
|
|
155
|
+
| File | Contents |
|
|
156
|
+
|------|----------|
|
|
157
|
+
| `index.html` | Full HTML5 document |
|
|
158
|
+
| `style.css` | Complete design system (CSS variables, responsive) |
|
|
159
|
+
| `script.js` | All interactivity (button actions, inputs, toggles) |
|
|
160
|
+
|
|
161
|
+
The output is **zero-dependency HTML** — just open `index.html` in any browser.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Project Structure
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
clewy/ ← Clewy language engine
|
|
169
|
+
src/
|
|
170
|
+
parser.js ← Tokenizer + AST parser
|
|
171
|
+
compiler.js ← AST → HTML/CSS/JS
|
|
172
|
+
runtime.js ← Dev server + file watcher
|
|
173
|
+
cli.js ← Command-line interface
|
|
174
|
+
examples/
|
|
175
|
+
kitchen-sink.cly ← All features demonstrated
|
|
176
|
+
landing.cly ← Landing page example
|
|
177
|
+
tests/
|
|
178
|
+
test.js ← 40-test suite (parser + compiler)
|
|
179
|
+
package.json
|
|
180
|
+
|
|
181
|
+
my-app/ ← Your generated project
|
|
182
|
+
main.cly ← ← YOU EDIT THIS
|
|
183
|
+
index.html ← Generated
|
|
184
|
+
style.css ← Generated
|
|
185
|
+
script.js ← Generated
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Design Principles
|
|
191
|
+
|
|
192
|
+
1. **One command = one action** — no hidden complexity
|
|
193
|
+
2. **Beginner first** — if you can describe it, you can build it
|
|
194
|
+
3. **Scratch-inspired** — visual blocks become text commands
|
|
195
|
+
4. **Zero config** — works instantly, no setup required
|
|
196
|
+
5. **Readable output** — generated HTML/CSS/JS is clean and readable
|
|
197
|
+
6. **Magic, not magic** — simple syntax, real web output
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Examples
|
|
202
|
+
|
|
203
|
+
### Hello World
|
|
204
|
+
```
|
|
205
|
+
page "Hello"
|
|
206
|
+
title "Hello, World! 🌍"
|
|
207
|
+
text "My first Clewy page"
|
|
208
|
+
button "Greet" => show "Hello from Clewy!"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Simple Portfolio
|
|
212
|
+
```
|
|
213
|
+
page "Jane Doe — Portfolio"
|
|
214
|
+
nav "Work" "About" "Contact"
|
|
215
|
+
title "Jane Doe"
|
|
216
|
+
subtitle "Designer & Developer"
|
|
217
|
+
text "I make beautiful things for the web."
|
|
218
|
+
section "Projects"
|
|
219
|
+
text "✦ Project Alpha"
|
|
220
|
+
text "✦ Project Beta"
|
|
221
|
+
text "✦ Project Gamma"
|
|
222
|
+
end
|
|
223
|
+
footer "© 2025 Jane Doe"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Component-based layout
|
|
227
|
+
```
|
|
228
|
+
component TeamMember
|
|
229
|
+
title "Team Member"
|
|
230
|
+
text "Role at Company"
|
|
231
|
+
link "LinkedIn" "https://linkedin.com"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
section "Our Team"
|
|
235
|
+
use TeamMember
|
|
236
|
+
use TeamMember
|
|
237
|
+
use TeamMember
|
|
238
|
+
end
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
MIT — build anything, ship everything.
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clewy-lang",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The magic web-building language — write .cly files, get full websites",
|
|
5
|
+
"main": "src/compiler.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"clewy": "./src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node tests/test.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["clewy", "web", "language", "transpiler", "beginner", "no-code", "static-site"],
|
|
13
|
+
"author": "Clewy",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"engines": { "node": ">=14.0.0" },
|
|
16
|
+
"files": ["src/", "README.md"]
|
|
17
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Clewy CLI — clewy init / run / build / check / new
|
|
4
|
+
*
|
|
5
|
+
* Install globally: npm install -g clewy
|
|
6
|
+
* Or without install: npx clewy init my-app
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { execSync, exec } = require('child_process');
|
|
13
|
+
|
|
14
|
+
// ── colours ──────────────────────────────────────────────────────────────────
|
|
15
|
+
const C = {
|
|
16
|
+
reset:'\x1b[0m', bold:'\x1b[1m', dim:'\x1b[2m',
|
|
17
|
+
green:'\x1b[32m', yellow:'\x1b[33m', blue:'\x1b[34m',
|
|
18
|
+
cyan:'\x1b[36m', red:'\x1b[31m', magenta:'\x1b[35m', white:'\x1b[97m',
|
|
19
|
+
};
|
|
20
|
+
const c = (col, s) => `${C[col]}${s}${C.reset}`;
|
|
21
|
+
const ok = s => console.log(` ${c('green','✓')} ${s}`);
|
|
22
|
+
const err = s => console.log(` ${c('red','✗')} ${s}`);
|
|
23
|
+
const tip = s => console.log(` ${c('dim', s)}`);
|
|
24
|
+
const log = s => console.log(` ${s}`);
|
|
25
|
+
|
|
26
|
+
// ── banner ────────────────────────────────────────────────────────────────────
|
|
27
|
+
function banner() {
|
|
28
|
+
const v = require('../package.json').version;
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(c('cyan', ' ██████╗██╗ ███████╗██╗ ██╗██╗ ██╗'));
|
|
31
|
+
console.log(c('cyan', ' ██╔════╝██║ ██╔════╝██║ ██║╚██╗ ██╔╝'));
|
|
32
|
+
console.log(c('cyan', ' ██║ ██║ █████╗ ██║ █╗ ██║ ╚████╔╝ '));
|
|
33
|
+
console.log(c('cyan', ' ██║ ██║ ██╔══╝ ██║███╗██║ ╚██╔╝ '));
|
|
34
|
+
console.log(c('cyan', ' ╚██████╗███████╗███████╗╚███╔███╔╝ ██║ '));
|
|
35
|
+
console.log(c('cyan', ' ╚═════╝╚══════╝╚══════╝ ╚══╝╚══╝ ╚═╝ '));
|
|
36
|
+
console.log('');
|
|
37
|
+
console.log(` ${c('white','The magic web-building language')} ${c('dim','v'+v)}`);
|
|
38
|
+
console.log('');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── templates ─────────────────────────────────────────────────────────────────
|
|
42
|
+
const TEMPLATES = {
|
|
43
|
+
|
|
44
|
+
default: `# Welcome to Clewy! ✨
|
|
45
|
+
# Edit this file — your site updates live in the browser.
|
|
46
|
+
|
|
47
|
+
page "My App"
|
|
48
|
+
|
|
49
|
+
nav "Home" "About" "Contact"
|
|
50
|
+
|
|
51
|
+
title "Hello, World! 👋"
|
|
52
|
+
subtitle "You just wrote your first Clewy page"
|
|
53
|
+
|
|
54
|
+
text "Clewy turns simple commands into beautiful websites."
|
|
55
|
+
text "Edit this file and run clewy run to see your changes."
|
|
56
|
+
|
|
57
|
+
section "Try the buttons"
|
|
58
|
+
button "Say hello" => show "Hello from Clewy! 🎉"
|
|
59
|
+
button "Pop an alert" => alert "Clewy is working!"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
section "Type something"
|
|
63
|
+
input "What is your name?" userName
|
|
64
|
+
button "Greet me" => show "Hey there, friend! 👋"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
component Card
|
|
68
|
+
title "Reusable Card"
|
|
69
|
+
text "Define once — use anywhere."
|
|
70
|
+
button "Card action" => show "Component clicked ✓"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
section "Components"
|
|
74
|
+
use Card
|
|
75
|
+
use Card
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
loop 3 => text "⭐ Clewy is minimal, instant, and fun"
|
|
79
|
+
|
|
80
|
+
footer "Built with Clewy — the magic web language ✨"
|
|
81
|
+
`,
|
|
82
|
+
|
|
83
|
+
landing: `page "My SaaS"
|
|
84
|
+
|
|
85
|
+
nav "Home" "Features" "Pricing" "Login"
|
|
86
|
+
|
|
87
|
+
title "Ship in minutes, not months 🚀"
|
|
88
|
+
subtitle "The fastest way to launch your idea"
|
|
89
|
+
|
|
90
|
+
text "Stop wrestling with HTML. Write what you mean — Clewy does the rest."
|
|
91
|
+
|
|
92
|
+
button "Start for free →" => show "Welcome! Check your inbox 📬"
|
|
93
|
+
button "See a demo" => alert "Live demo coming soon!"
|
|
94
|
+
|
|
95
|
+
section "Why Clewy?"
|
|
96
|
+
text "✦ No HTML, CSS or JavaScript to learn"
|
|
97
|
+
text "✦ Live preview as you type"
|
|
98
|
+
text "✦ Production-ready output"
|
|
99
|
+
text "✦ Works on every device"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
component Feature
|
|
103
|
+
title "Magic Commands"
|
|
104
|
+
text "One line of Clewy = a full interactive element."
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
section "Features"
|
|
108
|
+
use Feature
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
loop 4 => text "★ Trusted by developers worldwide"
|
|
112
|
+
|
|
113
|
+
footer "© 2025 My SaaS — Made with Clewy"
|
|
114
|
+
`,
|
|
115
|
+
|
|
116
|
+
todo: `page "Todo App"
|
|
117
|
+
|
|
118
|
+
title "📝 My Todos"
|
|
119
|
+
subtitle "Stay on top of everything"
|
|
120
|
+
|
|
121
|
+
section "Add a task"
|
|
122
|
+
input "What needs doing?" newTask
|
|
123
|
+
button "Add ✓" => show "Task added!"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
section "Tasks"
|
|
127
|
+
text "☐ Learn Clewy"
|
|
128
|
+
text "☐ Build something cool"
|
|
129
|
+
text "☐ Share it with friends"
|
|
130
|
+
text "☑ Install Clewy ← done!"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
button "Clear all" => alert "Clear all tasks? (not wired up yet)"
|
|
134
|
+
|
|
135
|
+
footer "Keep going ⚡"
|
|
136
|
+
`,
|
|
137
|
+
|
|
138
|
+
portfolio: `page "My Portfolio"
|
|
139
|
+
|
|
140
|
+
nav "Work" "About" "Contact"
|
|
141
|
+
|
|
142
|
+
title "Hi, I'm Alex 👋"
|
|
143
|
+
subtitle "Designer & Developer"
|
|
144
|
+
|
|
145
|
+
text "I build fast, beautiful things for the web."
|
|
146
|
+
text "Currently open to new projects."
|
|
147
|
+
|
|
148
|
+
section "Selected Work"
|
|
149
|
+
text "✦ E-commerce redesign — 2× conversion rate"
|
|
150
|
+
text "✦ Mobile app — 50k downloads in 3 months"
|
|
151
|
+
text "✦ Brand identity — for a Y Combinator startup"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
section "Skills"
|
|
155
|
+
loop 3 => text "React · Node.js · Figma · Clewy"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
button "See my work" => show "Portfolio page coming soon! 👀"
|
|
159
|
+
button "Hire me" => show "Let's talk → alex@example.com 📬"
|
|
160
|
+
|
|
161
|
+
link "GitHub" "https://github.com"
|
|
162
|
+
link "LinkedIn" "https://linkedin.com"
|
|
163
|
+
|
|
164
|
+
footer "© 2025 Alex — Built with Clewy"
|
|
165
|
+
`,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// ── .gitignore content ────────────────────────────────────────────────────────
|
|
169
|
+
const GITIGNORE = `# Clewy — generated files (do NOT edit these manually)
|
|
170
|
+
index.html
|
|
171
|
+
style.css
|
|
172
|
+
script.js
|
|
173
|
+
.DS_Store
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
// ── commands ──────────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
function cmdInit(args) {
|
|
179
|
+
const appName = args[0];
|
|
180
|
+
const tmplName = args[1] || 'default';
|
|
181
|
+
|
|
182
|
+
if (!appName) {
|
|
183
|
+
err('Please provide a project name.');
|
|
184
|
+
log('');
|
|
185
|
+
log(` Example: ${c('cyan','clewy init my-app')}`);
|
|
186
|
+
log(` Example: ${c('cyan','clewy init my-app landing')}`);
|
|
187
|
+
log('');
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const targetDir = path.resolve(process.cwd(), appName);
|
|
192
|
+
|
|
193
|
+
if (fs.existsSync(targetDir)) {
|
|
194
|
+
err(`Folder "${appName}" already exists.`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!TEMPLATES[tmplName]) {
|
|
199
|
+
err(`Unknown template "${tmplName}". Available: ${Object.keys(TEMPLATES).join(', ')}`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log('');
|
|
204
|
+
log(`${c('bold','Creating')} ${c('cyan', appName)} …`);
|
|
205
|
+
console.log('');
|
|
206
|
+
|
|
207
|
+
// Create folder + files
|
|
208
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
209
|
+
fs.writeFileSync(path.join(targetDir, 'main.cly'), TEMPLATES[tmplName]);
|
|
210
|
+
fs.writeFileSync(path.join(targetDir, '.gitignore'), GITIGNORE);
|
|
211
|
+
fs.writeFileSync(path.join(targetDir, '.clewyrc'), JSON.stringify({ name: appName, version: '0.1.0', template: tmplName }, null, 2));
|
|
212
|
+
|
|
213
|
+
ok(`Created ${c('cyan', appName + '/main.cly')}`);
|
|
214
|
+
ok(`Created ${c('dim', appName + '/.gitignore')}`);
|
|
215
|
+
ok(`Created ${c('dim', appName + '/.clewyrc')}`);
|
|
216
|
+
|
|
217
|
+
console.log('');
|
|
218
|
+
log(c('bold', 'Next steps:'));
|
|
219
|
+
console.log('');
|
|
220
|
+
log(` ${c('cyan', `cd ${appName}`)}`);
|
|
221
|
+
log(` ${c('cyan', 'clewy run')}`);
|
|
222
|
+
console.log('');
|
|
223
|
+
tip(`Templates: default · landing · todo · portfolio`);
|
|
224
|
+
tip(`Example: clewy init my-site landing`);
|
|
225
|
+
console.log('');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function cmdBuild(args) {
|
|
229
|
+
const projectDir = process.cwd();
|
|
230
|
+
const { buildProject } = require('./runtime');
|
|
231
|
+
|
|
232
|
+
const clyFile = path.join(projectDir, 'main.cly');
|
|
233
|
+
if (!fs.existsSync(clyFile)) {
|
|
234
|
+
err('No main.cly found. Are you inside a Clewy project?');
|
|
235
|
+
tip('Run: clewy init my-app');
|
|
236
|
+
console.log('');
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
log('');
|
|
241
|
+
log(`${c('blue','⚙')} Building…`);
|
|
242
|
+
|
|
243
|
+
const ok2 = buildProject(projectDir);
|
|
244
|
+
if (!ok2) process.exit(1);
|
|
245
|
+
|
|
246
|
+
const sz = f => (fs.statSync(path.join(projectDir, f)).size / 1024).toFixed(1);
|
|
247
|
+
|
|
248
|
+
console.log('');
|
|
249
|
+
ok('Build complete!\n');
|
|
250
|
+
log(` ${c('cyan','index.html')} ${sz('index.html')} kB`);
|
|
251
|
+
log(` ${c('cyan','style.css')} ${sz('style.css')} kB`);
|
|
252
|
+
log(` ${c('cyan','script.js')} ${sz('script.js')} kB`);
|
|
253
|
+
console.log('');
|
|
254
|
+
tip('Open index.html in your browser, or run: clewy run');
|
|
255
|
+
console.log('');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function cmdRun(args) {
|
|
259
|
+
const projectDir = process.cwd();
|
|
260
|
+
const clyFile = path.join(projectDir, 'main.cly');
|
|
261
|
+
|
|
262
|
+
if (!fs.existsSync(clyFile)) {
|
|
263
|
+
err('No main.cly found. Are you inside a Clewy project?');
|
|
264
|
+
tip('Run: clewy init my-app');
|
|
265
|
+
console.log('');
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let port = 3000;
|
|
270
|
+
const pi = args.indexOf('--port');
|
|
271
|
+
if (pi !== -1 && args[pi + 1]) port = parseInt(args[pi + 1], 10);
|
|
272
|
+
|
|
273
|
+
const { startServer } = require('./runtime');
|
|
274
|
+
startServer(projectDir, port);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function cmdNew(args) {
|
|
278
|
+
// clewy new button / clewy new page — interactive snippet helper
|
|
279
|
+
const what = args[0];
|
|
280
|
+
const snippets = {
|
|
281
|
+
button: `button "Label" => show "Message"`,
|
|
282
|
+
input: `input "Placeholder" varName`,
|
|
283
|
+
section: `section "Title"\n text "Content here"\nend`,
|
|
284
|
+
component: `component MyCard\n title "Card Title"\n text "Card content"\nend\n\nuse MyCard`,
|
|
285
|
+
loop: `loop 4 => text "Repeated text"`,
|
|
286
|
+
nav: `nav "Home" "About" "Contact"`,
|
|
287
|
+
};
|
|
288
|
+
if (!what || !snippets[what]) {
|
|
289
|
+
log('');
|
|
290
|
+
log(c('bold', ' Snippets — copy into your main.cly:'));
|
|
291
|
+
console.log('');
|
|
292
|
+
for (const [k, v] of Object.entries(snippets)) {
|
|
293
|
+
log(` ${c('yellow', k.padEnd(12))} ${c('dim', v.split('\n')[0])}`);
|
|
294
|
+
}
|
|
295
|
+
console.log('');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
console.log('');
|
|
299
|
+
log(c('bold', `Snippet: ${what}`));
|
|
300
|
+
console.log('');
|
|
301
|
+
snippets[what].split('\n').forEach(l => log(` ${c('cyan', l)}`));
|
|
302
|
+
console.log('');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function cmdCheck(args) {
|
|
306
|
+
const projectDir = process.cwd();
|
|
307
|
+
const clyPath = path.join(projectDir, 'main.cly');
|
|
308
|
+
|
|
309
|
+
if (!fs.existsSync(clyPath)) {
|
|
310
|
+
err('No main.cly found.');
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const { parse } = require('./parser');
|
|
315
|
+
const source = fs.readFileSync(clyPath, 'utf8');
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
const ast = parse(source);
|
|
319
|
+
const counts = summarize(ast.body);
|
|
320
|
+
const total = Object.values(counts).reduce((a,b)=>a+b,0);
|
|
321
|
+
console.log('');
|
|
322
|
+
ok(`Syntax OK — ${c('bold', total + ' nodes')} parsed`);
|
|
323
|
+
console.log('');
|
|
324
|
+
for (const [t, n] of Object.entries(counts)) {
|
|
325
|
+
log(` ${c('cyan', t.padEnd(14))} ${c('dim','×')} ${n}`);
|
|
326
|
+
}
|
|
327
|
+
console.log('');
|
|
328
|
+
} catch (e) {
|
|
329
|
+
console.log('');
|
|
330
|
+
err(`Syntax error: ${e.message}`);
|
|
331
|
+
console.log('');
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function summarize(body) {
|
|
337
|
+
const m = {};
|
|
338
|
+
for (const n of body) {
|
|
339
|
+
m[n.type] = (m[n.type]||0)+1;
|
|
340
|
+
if (n.body) { const sub=summarize(n.body); for (const [k,v] of Object.entries(sub)) m[k]=(m[k]||0)+v; }
|
|
341
|
+
}
|
|
342
|
+
return m;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function cmdHelp() {
|
|
346
|
+
banner();
|
|
347
|
+
log(c('bold','Commands:'));
|
|
348
|
+
console.log('');
|
|
349
|
+
const cmds = [
|
|
350
|
+
['init <name> [template]', 'Create a new Clewy project'],
|
|
351
|
+
['run [--port N]', 'Start dev server with live reload'],
|
|
352
|
+
['build', 'Compile main.cly → HTML/CSS/JS'],
|
|
353
|
+
['check', 'Validate .cly syntax'],
|
|
354
|
+
['new [element]', 'Show code snippets'],
|
|
355
|
+
];
|
|
356
|
+
for (const [cmd, desc] of cmds) {
|
|
357
|
+
log(` ${c('cyan', ('clewy '+cmd).padEnd(32))} ${c('dim',desc)}`);
|
|
358
|
+
}
|
|
359
|
+
console.log('');
|
|
360
|
+
log(c('bold','Templates:'));
|
|
361
|
+
console.log('');
|
|
362
|
+
for (const t of Object.keys(TEMPLATES)) {
|
|
363
|
+
log(` ${c('yellow', t)}`);
|
|
364
|
+
}
|
|
365
|
+
console.log('');
|
|
366
|
+
log(c('bold','Clewy language:'));
|
|
367
|
+
console.log('');
|
|
368
|
+
const syntax = [
|
|
369
|
+
['page "Title"', 'Browser tab title'],
|
|
370
|
+
['title "Big heading"', 'h1 heading'],
|
|
371
|
+
['subtitle "Smaller"', 'h2 heading'],
|
|
372
|
+
['text "Paragraph"', 'Text block'],
|
|
373
|
+
['button "X" => show "Y"', 'Button that shows a message'],
|
|
374
|
+
['button "X" => alert "Y"', 'Button with popup'],
|
|
375
|
+
['input "Hint" varName', 'Text input field'],
|
|
376
|
+
['show "Message"', 'Always-visible message'],
|
|
377
|
+
['loop N => text "..."', 'Repeat N times'],
|
|
378
|
+
['component Name … end', 'Define a component'],
|
|
379
|
+
['use Name', 'Render a component'],
|
|
380
|
+
['section "Label" … end', 'Grouped card section'],
|
|
381
|
+
['nav "A" "B" "C"', 'Navigation bar'],
|
|
382
|
+
['link "Text" "url"', 'Hyperlink'],
|
|
383
|
+
['image "url" "alt"', 'Image'],
|
|
384
|
+
['footer "Text"', 'Page footer'],
|
|
385
|
+
['# comment', 'Ignored line'],
|
|
386
|
+
];
|
|
387
|
+
for (const [s,d] of syntax) {
|
|
388
|
+
log(` ${c('magenta', s.padEnd(36))} ${c('dim',d)}`);
|
|
389
|
+
}
|
|
390
|
+
console.log('');
|
|
391
|
+
log(` Docs & source: ${c('cyan','https://github.com/clewy-lang/clewy')}`);
|
|
392
|
+
console.log('');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ── entry point ───────────────────────────────────────────────────────────────
|
|
396
|
+
const [,, cmd, ...rest] = process.argv;
|
|
397
|
+
switch (cmd) {
|
|
398
|
+
case 'init': cmdInit(rest); break;
|
|
399
|
+
case 'run': cmdRun(rest); break;
|
|
400
|
+
case 'build': cmdBuild(rest); break;
|
|
401
|
+
case 'check': cmdCheck(rest); break;
|
|
402
|
+
case 'new': cmdNew(rest); break;
|
|
403
|
+
case 'help': case '--help': case '-h': case undefined:
|
|
404
|
+
cmdHelp(); break;
|
|
405
|
+
default:
|
|
406
|
+
console.log('');
|
|
407
|
+
err(`Unknown command: "${cmd}"`);
|
|
408
|
+
tip('Run: clewy --help');
|
|
409
|
+
console.log('');
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|