Skip to content

Commit 6c39b80

Browse files
Refactor dsl_to_html function in stdlib/src/web.rs by extracting logical blocks into smaller helper functions (render_website, render_page, render_hero, render_section, render_card, render_children) to improve maintainability and readability.
Co-authored-by: Tcode-Motion <188012755+Tcode-Motion@users.noreply.github.com>
1 parent 514e6c0 commit 6c39b80

1 file changed

Lines changed: 146 additions & 161 deletions

File tree

‎stdlib/src/web.rs‎

Lines changed: 146 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -92,152 +92,161 @@ use techscript_runtime::{
9292
static SERVER_RUNNING: AtomicBool = AtomicBool::new(false);
9393
static PAGE_CONTENT: Mutex<String> = Mutex::new(String::new());
9494

95+
fn render_children(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue) {
96+
for child in &dsl.children {
97+
html.push_str(&dsl_to_html(
98+
&techscript_runtime::value::RuntimeValue::DslBlock(std::rc::Rc::new(child.clone())),
99+
));
100+
}
101+
}
102+
103+
fn render_website(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue) {
104+
html.push_str("<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
105+
for prop in &dsl.properties {
106+
match prop.name.as_str() {
107+
"title" => {
108+
if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value {
109+
html.push_str(&format!("<title>{}</title>", t));
110+
}
111+
}
112+
_ => {}
113+
}
114+
}
115+
html.push_str("<style>\
116+
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap');\
117+
body { background: #0f172a; color: #f8fafc; font-family: 'Outfit', sans-serif; margin: 0; padding: 0; min-height: 100vh; display: flex; flex-direction: column; }\
118+
header { display: flex; justify-content: space-between; align-items: center; padding: 1.5rem 2rem; background: rgba(15, 23, 42, 0.8); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(255,255,255,0.05); position: sticky; top: 0; z-index: 100; }\
119+
header h1 { font-size: 1.5rem; margin: 0; font-weight: 800; background: linear-gradient(135deg, #a855f7 0%, #3b82f6 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }\
120+
nav { display: flex; gap: 1.5rem; }\
121+
nav a { color: #94a3b8; text-decoration: none; font-weight: 500; transition: color 0.2s; }\
122+
nav a:hover { color: #f8fafc; }\
123+
.page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem; flex: 1; }\
124+
.hero { text-align: center; padding: 5rem 1rem; position: relative; }\
125+
.hero h1 { font-size: 4rem; font-weight: 800; margin: 0 0 1.5rem 0; background: linear-gradient(135deg, #c084fc 0%, #60a5fa 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }\
126+
.hero .subtitle { font-size: 1.5rem; color: #94a3b8; max-width: 800px; margin: 0 auto 3rem auto; line-height: 1.6; }\
127+
.hero a.start-button { background: #3b82f6; color: white; padding: 0.8rem 2rem; border-radius: 9999px; text-decoration: none; font-weight: 600; transition: all 0.2s; box-shadow: 0 4px 14px rgba(59, 130, 246, 0.4); margin-right: 1rem; display: inline-block; }\
128+
.hero a.start-button:hover { background: #2563eb; transform: translateY(-2px); }\
129+
.hero button { background: rgba(255,255,255,0.05); color: #f8fafc; border: 1px solid rgba(255,255,255,0.1); padding: 0.8rem 2rem; border-radius: 9999px; font-weight: 600; cursor: pointer; transition: all 0.2s; }\
130+
.hero button:hover { background: rgba(255,255,255,0.1); transform: translateY(-2px); }\
131+
.content-section { margin-top: 5rem; }\
132+
.content-section h2 { font-size: 2.5rem; text-align: center; margin-bottom: 3rem; background: linear-gradient(135deg, #f8fafc 0%, #94a3b8 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }\
133+
.grid-layout { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; margin-top: 2rem; }\
134+
.card { background: rgba(30, 41, 59, 0.4); border: 1px solid rgba(255,255,255,0.05); padding: 2rem; border-radius: 16px; transition: all 0.3s; backdrop-filter: blur(8px); }\
135+
.card:hover { transform: translateY(-5px); border-color: rgba(168, 85, 247, 0.4); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); }\
136+
.card h3 { font-size: 1.5rem; margin: 0 0 1rem 0; color: #c084fc; }\
137+
.card p { color: #94a3b8; line-height: 1.6; margin: 0; }\
138+
footer { text-align: center; padding: 3rem 2rem; background: #0b0f19; border-top: 1px solid rgba(255,255,255,0.05); color: #64748b; font-size: 0.9rem; margin-top: auto; }\
139+
</style>");
140+
html.push_str("</head><body>");
141+
render_children(html, dsl);
142+
html.push_str("</body></html>");
143+
}
144+
145+
fn render_page(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue) {
146+
html.push_str("<div class=\"page\">");
147+
if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = dsl.args.first() {
148+
html.push_str(&format!("<h1>{}</h1>", t));
149+
}
150+
for prop in &dsl.properties {
151+
if prop.name == "title" {
152+
if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value {
153+
html.push_str(&format!("<h2>{}</h2>", t));
154+
}
155+
}
156+
}
157+
render_children(html, dsl);
158+
html.push_str("</div>");
159+
}
160+
161+
fn render_hero(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue) {
162+
html.push_str("<section class=\"hero\">");
163+
for prop in &dsl.properties {
164+
match prop.name.as_str() {
165+
"title" => {
166+
if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value {
167+
html.push_str(&format!("<h1>{}</h1>", t));
168+
}
169+
}
170+
"subtitle" => {
171+
if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value {
172+
html.push_str(&format!("<p class=\"subtitle\">{}</p>", t));
173+
}
174+
}
175+
_ => {}
176+
}
177+
}
178+
render_children(html, dsl);
179+
html.push_str("</section>");
180+
}
181+
182+
fn render_section(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue) {
183+
html.push_str("<section class=\"content-section\">");
184+
for prop in &dsl.properties {
185+
match prop.name.as_str() {
186+
"title" => {
187+
if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value {
188+
html.push_str(&format!("<h2>{}</h2>", t));
189+
}
190+
}
191+
"id" => {
192+
if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value {
193+
html.push_str(&format!("<!-- id: {} -->", t));
194+
}
195+
}
196+
_ => {}
197+
}
198+
}
199+
html.push_str("<div class=\"grid-layout\">");
200+
render_children(html, dsl);
201+
html.push_str("</div>");
202+
html.push_str("</section>");
203+
}
204+
205+
fn render_card(html: &mut String, dsl: &techscript_runtime::value::DslBlockValue) {
206+
html.push_str("<div class=\"card\">");
207+
for prop in &dsl.properties {
208+
match prop.name.as_str() {
209+
"title" => {
210+
if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value {
211+
html.push_str(&format!("<h3>{}</h3>", t));
212+
}
213+
}
214+
"text" => {
215+
if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value {
216+
html.push_str(&format!("<p>{}</p>", t));
217+
}
218+
}
219+
"image" => {
220+
if let Some(techscript_runtime::value::RuntimeValue::Str(t)) = &prop.value {
221+
html.push_str(&format!("<img src=\"{}\" alt=\"card image\">", t));
222+
}
223+
}
224+
_ => {}
225+
}
226+
}
227+
html.push_str("</div>");
228+
}
229+
95230
/// Convert a DslBlockValue tree to HTML string.
96231
fn dsl_to_html(val: &RuntimeValue) -> String {
97232
match val {
98233
RuntimeValue::DslBlock(dsl) => {
99234
let mut html = String::new();
100235
match dsl.kind.as_str() {
101236
"website" => {
102-
html.push_str("<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
103-
for prop in &dsl.properties {
104-
match prop.name.as_str() {
105-
"title" => {
106-
if let Some(RuntimeValue::Str(t)) = &prop.value {
107-
html.push_str(&format!("<title>{}</title>", t));
108-
}
109-
}
110-
_ => {}
111-
}
112-
}
113-
html.push_str("<style>\
114-
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap');\
115-
body { background: #0f172a; color: #f8fafc; font-family: 'Outfit', sans-serif; margin: 0; padding: 0; min-height: 100vh; display: flex; flex-direction: column; }\
116-
header { display: flex; justify-content: space-between; align-items: center; padding: 1.5rem 2rem; background: rgba(15, 23, 42, 0.8); backdrop-filter: blur(12px); border-bottom: 1px solid rgba(255,255,255,0.05); position: sticky; top: 0; z-index: 100; }\
117-
header h1 { font-size: 1.5rem; margin: 0; font-weight: 800; background: linear-gradient(135deg, #a855f7 0%, #3b82f6 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }\
118-
nav { display: flex; gap: 1.5rem; }\
119-
nav a { color: #94a3b8; text-decoration: none; font-weight: 500; transition: color 0.2s; }\
120-
nav a:hover { color: #f8fafc; }\
121-
.page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem; flex: 1; }\
122-
.hero { text-align: center; padding: 5rem 1rem; position: relative; }\
123-
.hero h1 { font-size: 4rem; font-weight: 800; margin: 0 0 1.5rem 0; background: linear-gradient(135deg, #c084fc 0%, #60a5fa 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }\
124-
.hero .subtitle { font-size: 1.5rem; color: #94a3b8; max-width: 800px; margin: 0 auto 3rem auto; line-height: 1.6; }\
125-
.hero a.start-button { background: #3b82f6; color: white; padding: 0.8rem 2rem; border-radius: 9999px; text-decoration: none; font-weight: 600; transition: all 0.2s; box-shadow: 0 4px 14px rgba(59, 130, 246, 0.4); margin-right: 1rem; display: inline-block; }\
126-
.hero a.start-button:hover { background: #2563eb; transform: translateY(-2px); }\
127-
.hero button { background: rgba(255,255,255,0.05); color: #f8fafc; border: 1px solid rgba(255,255,255,0.1); padding: 0.8rem 2rem; border-radius: 9999px; font-weight: 600; cursor: pointer; transition: all 0.2s; }\
128-
.hero button:hover { background: rgba(255,255,255,0.1); transform: translateY(-2px); }\
129-
.content-section { margin-top: 5rem; }\
130-
.content-section h2 { font-size: 2.5rem; text-align: center; margin-bottom: 3rem; background: linear-gradient(135deg, #f8fafc 0%, #94a3b8 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }\
131-
.grid-layout { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem; margin-top: 2rem; }\
132-
.card { background: rgba(30, 41, 59, 0.4); border: 1px solid rgba(255,255,255,0.05); padding: 2rem; border-radius: 16px; transition: all 0.3s; backdrop-filter: blur(8px); }\
133-
.card:hover { transform: translateY(-5px); border-color: rgba(168, 85, 247, 0.4); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); }\
134-
.card h3 { font-size: 1.5rem; margin: 0 0 1rem 0; color: #c084fc; }\
135-
.card p { color: #94a3b8; line-height: 1.6; margin: 0; }\
136-
footer { text-align: center; padding: 3rem 2rem; background: #0b0f19; border-top: 1px solid rgba(255,255,255,0.05); color: #64748b; font-size: 0.9rem; margin-top: auto; }\
137-
</style>");
138-
html.push_str("</head><body>");
139-
for child in &dsl.children {
140-
html.push_str(&dsl_to_html(&RuntimeValue::DslBlock(Rc::new(
141-
child.clone(),
142-
))));
143-
}
144-
html.push_str("</body></html>");
237+
render_website(&mut html, dsl);
145238
}
146239
"page" => {
147-
html.push_str("<div class=\"page\">");
148-
if let Some(RuntimeValue::Str(t)) = dsl.args.first() {
149-
html.push_str(&format!("<h1>{}</h1>", t));
150-
}
151-
for prop in &dsl.properties {
152-
if prop.name == "title" {
153-
if let Some(RuntimeValue::Str(t)) = &prop.value {
154-
html.push_str(&format!("<h2>{}</h2>", t));
155-
}
156-
}
157-
}
158-
for child in &dsl.children {
159-
html.push_str(&dsl_to_html(&RuntimeValue::DslBlock(Rc::new(
160-
child.clone(),
161-
))));
162-
}
163-
html.push_str("</div>");
240+
render_page(&mut html, dsl);
164241
}
165242
"hero" => {
166-
html.push_str("<section class=\"hero\">");
167-
for prop in &dsl.properties {
168-
match prop.name.as_str() {
169-
"title" => {
170-
if let Some(RuntimeValue::Str(t)) = &prop.value {
171-
html.push_str(&format!("<h1>{}</h1>", t));
172-
}
173-
}
174-
"subtitle" => {
175-
if let Some(RuntimeValue::Str(t)) = &prop.value {
176-
html.push_str(&format!("<p class=\"subtitle\">{}</p>", t));
177-
}
178-
}
179-
_ => {}
180-
}
181-
}
182-
for child in &dsl.children {
183-
html.push_str(&dsl_to_html(&RuntimeValue::DslBlock(Rc::new(
184-
child.clone(),
185-
))));
186-
}
187-
html.push_str("</section>");
243+
render_hero(&mut html, dsl);
188244
}
189245
"section" => {
190-
html.push_str("<section class=\"content-section\">");
191-
for prop in &dsl.properties {
192-
match prop.name.as_str() {
193-
"title" => {
194-
if let Some(RuntimeValue::Str(t)) = &prop.value {
195-
html.push_str(&format!("<h2>{}</h2>", t));
196-
}
197-
}
198-
"id" => {
199-
if let Some(RuntimeValue::Str(t)) = &prop.value {
200-
html.push_str(&format!("<!-- id: {} -->", t));
201-
}
202-
}
203-
_ => {}
204-
}
205-
}
206-
html.push_str("<div class=\"grid-layout\">");
207-
for child in &dsl.children {
208-
html.push_str(&dsl_to_html(&RuntimeValue::DslBlock(Rc::new(
209-
child.clone(),
210-
))));
211-
}
212-
html.push_str("</div>");
213-
html.push_str("</section>");
246+
render_section(&mut html, dsl);
214247
}
215248
"card" => {
216-
html.push_str("<div class=\"card\">");
217-
for prop in &dsl.properties {
218-
match prop.name.as_str() {
219-
"title" => {
220-
if let Some(RuntimeValue::Str(t)) = &prop.value {
221-
html.push_str(&format!("<h3>{}</h3>", t));
222-
}
223-
}
224-
"text" => {
225-
if let Some(RuntimeValue::Str(t)) = &prop.value {
226-
html.push_str(&format!("<p>{}</p>", t));
227-
}
228-
}
229-
"image" => {
230-
if let Some(RuntimeValue::Str(t)) = &prop.value {
231-
html.push_str(&format!(
232-
"<img src=\"{}\" alt=\"card image\">",
233-
t
234-
));
235-
}
236-
}
237-
_ => {}
238-
}
239-
}
240-
html.push_str("</div>");
249+
render_card(&mut html, dsl);
241250
}
242251
"button" => {
243252
let label = dsl
@@ -271,11 +280,7 @@ fn dsl_to_html(val: &RuntimeValue) -> String {
271280
}
272281
"nav" => {
273282
html.push_str("<nav>");
274-
for child in &dsl.children {
275-
html.push_str(&dsl_to_html(&RuntimeValue::DslBlock(Rc::new(
276-
child.clone(),
277-
))));
278-
}
283+
render_children(&mut html, dsl);
279284
html.push_str("</nav>");
280285
}
281286
"header" => {
@@ -287,11 +292,7 @@ fn dsl_to_html(val: &RuntimeValue) -> String {
287292
}
288293
}
289294
}
290-
for child in &dsl.children {
291-
html.push_str(&dsl_to_html(&RuntimeValue::DslBlock(Rc::new(
292-
child.clone(),
293-
))));
294-
}
295+
render_children(&mut html, dsl);
295296
html.push_str("</header>");
296297
}
297298
"footer" => {
@@ -303,11 +304,7 @@ fn dsl_to_html(val: &RuntimeValue) -> String {
303304
}
304305
}
305306
}
306-
for child in &dsl.children {
307-
html.push_str(&dsl_to_html(&RuntimeValue::DslBlock(Rc::new(
308-
child.clone(),
309-
))));
310-
}
307+
render_children(&mut html, dsl);
311308
html.push_str("</footer>");
312309
}
313310
"input" => {
@@ -325,29 +322,17 @@ fn dsl_to_html(val: &RuntimeValue) -> String {
325322
}
326323
"form" => {
327324
html.push_str("<form>");
328-
for child in &dsl.children {
329-
html.push_str(&dsl_to_html(&RuntimeValue::DslBlock(Rc::new(
330-
child.clone(),
331-
))));
332-
}
325+
render_children(&mut html, dsl);
333326
html.push_str("</form>");
334327
}
335328
"main" => {
336329
html.push_str("<main>");
337-
for child in &dsl.children {
338-
html.push_str(&dsl_to_html(&RuntimeValue::DslBlock(Rc::new(
339-
child.clone(),
340-
))));
341-
}
330+
render_children(&mut html, dsl);
342331
html.push_str("</main>");
343332
}
344333
"aside" => {
345334
html.push_str("<aside>");
346-
for child in &dsl.children {
347-
html.push_str(&dsl_to_html(&RuntimeValue::DslBlock(Rc::new(
348-
child.clone(),
349-
))));
350-
}
335+
render_children(&mut html, dsl);
351336
html.push_str("</aside>");
352337
}
353338
"start" => {

0 commit comments

Comments
 (0)