Skip to content

Commit

Permalink
add prettify()
Browse files Browse the repository at this point in the history
  • Loading branch information
Helmut Hänsel committed Apr 19, 2023
1 parent 92fe96a commit 0e874c8
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 86 deletions.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Colors = "0.12"
DataFrames = "1"
Genie = "5"
OrderedCollections = "1"
SnoopPrecompile = "1"
Stipple = "0.26"
julia = "1.6"

Expand Down
90 changes: 50 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# StippleUI [![Docs](https://img.shields.io/badge/stippleui-docs-greenyellow)](https://www.genieframework.com/docs/)
# StippleUI.jl [![Docs](https://img.shields.io/badge/stippleui-docs-greenyellow)](https://www.genieframework.com/docs/)

StippleUI is a library of reactive UI elements for [Stipple.jl](https://github.com/GenieFramework/Stipple.jl).

`StippleUI.jl`, together with [Stipple.jl](https://github.com/GenieFramework/Stipple.jl),
[StippleCharts.jl](https://github.com/GenieFramework/StippleCharts.jl) and
[Genie.jl](https://github.com/GenieFramework/Genie.jl) provide a powerful and complete solution for building
together with
- [Stipple.jl](https://github.com/GenieFramework/Stipple.jl),
- [StipplePlotly.jl](https://github.com/GenieFramework/StipplePlotly.jl) and
- [Genie.jl](https://github.com/GenieFramework/Genie.jl)

it is part of the [GenieFramework](https://github.com/GenieFramework/GenieFramework.jl), a powerful and complete solution for building
beautiful, responsive, reactive, high performance interactive data dashboards in pure Julia.

`StippleUI` provides over 30 UI elements, including forms and form inputs (button, slider, checkbox, radio, toggle, range), lists, data tables,
higher level components (badges, banners, cards, dialogs, chips, icons), and layout elements (row, col, dashboard, heading, space) from the [Quasar Framework](https://quasar.dev).

**New**: [StippleUIParser](#stippleuiparser)
- conversion of html code to julia code
- pretty-printing of html
## Installation

```julia
Expand Down Expand Up @@ -193,67 +199,71 @@ Genie.isrunning(:webserver) || up()
<img src="docs/content/img/Example.png">

## StippleUIParser
### Tools
- `parse_vue_html`
- `test_vue_parsing`
- `prettify`

A very new tool is StippleUIParser. It converts html code to the respective Julian code and prettifies html code. This is meant as a helper tool to port demo code from the internet into Stipple/Genie apps.

A very new tool is StippleUIParser. It converts html code to the respective Julian code. This is meant as a helper tool to port demo code from the internet into Stipple/Genie apps.
#### Parse vue html code to julia code
```julia
julia> using StippleUI.StippleUIParser
julia> doc_string = """
<template>
<div class="q-pa-md">
<q-scroll-area style="height: 230px; max-width: 300px;">
<div class="row no-wrap">
<div v-for="n in 10" :key="n" style="width: 150px" class="q-pa-sm">
Lorem @ipsum \$dolor sit amet consectetur adipisicing elit. Architecto fuga quae veritatis blanditiis sequi id expedita amet esse aspernatur! Iure, doloribus!
Lorem @ipsum \$dolor sit amet consectetur adipisicing elit.
</div>
<q-btn color=\"primary\" label=\"`Animate to \${position}px`\" @click=\"scroll = true\"></q-btn>
<q-input hint=\"Please enter some words\" v-on:keyup.enter=\"process = true\" label=\"Input\" v-model=\"input\" class=\"q-my-md\"></q-input>
<q-input hint=\"Please enter some words\" v-on:keyup.enter=\"process = true\" label=\"Input\" v-model=\"input\"></q-input>
<q-input hint=\"Please enter a number\" label=\"Input\" v-model.number=\"numberinput\" class=\"q-my-md\"></q-input>
</div>
</q-scroll-area>
</div>
</template>
""";

julia> parse_vue_html(doc_string) |> println
julia> parse_vue_html(html_string, indent = 2) |> println
template(
Stipple.Html.div(class = "q-pa-md",
scrollarea(style = "height: 230px; max-width: 300px;",
Stipple.Html.div(class = "row no-wrap", [
Stipple.Html.div(var"v-for" = "n in 10", key! = "n", style = "width: 150px", class = "q-pa-sm",
raw"Lorem @ipsum $dolor sit amet consectetur adipisicing elit. Architecto fuga quae veritatis blanditiis sequi id expedita amet esse aspernatur! Iure, doloribus!"
)
btn("`Animate to \${position}px`", color = "primary", var"v-on:click" = "scroll = true")
textfield("Input", :input, hint = "Please enter some words", var"v-on:keyup.enter" = "process = true", class = "q-my-md")
numberfield("Input", :numberinput, hint = "Please enter a number", class = "q-my-md")
])
Stipple.Html.div(class = "q-pa-md",
scrollarea(style = "height: 230px; max-width: 300px;",
Stipple.Html.div(class = "row no-wrap", [
Stipple.Html.div(var"v-for" = "n in 10", key! = "n", style = "width: 150px", class = "q-pa-sm",
"Lorem @ipsum dolor sit amet consectetur adipisicing elit."
)
btn(raw"`Animate to ${position}px`", color = "primary", var"v-on:click" = "scroll = true")
textfield("Input", :input, hint = "Please enter some words", var"v-on:keyup.enter" = "process = true")
numberfield("Input", :numberinput, hint = "Please enter a number", class = "q-my-md")
])
)
)
)
```
There is also a testing tool `test_vue_parsing()` whether the parsing was successful:
```julia
julia> test_vue_parsing(raw"""<a :hello-world="I need $$$"></a>""")

Original HTML string:
<a :hello-world="I need $$$"></a>

Julia code:
a(var"hello-world!" = raw"I need $$$")
#### Test parsing result

Produced HTML:
<a :hello-world="I need $$$"></a>
```

```jc
julia> test_vue_parsing(raw"""<q-test :hello-world="I need $$$"></q-test>""")
Original HTML string:
<q-test :hello-world="I need $$$"></q-test>
There is also a testing tool `test_vue_parsing()` whether the parsing was successful:

Julia code:
quasar(:test, var"hello-world" = R"I need $$$")
<div style="background-color:#f6f8fa;"><pre>
<DIV STYLE="font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:10pt;"><SPAN STYLE="color:#98C379;">julia&gt; </SPAN><SPAN STYLE="color:#383A42;">test_vue_parsing(raw"""&lt;a :hello-world="I need $$$"&gt;asap&lt;/a&gt;""")<BR><BR>Original HTML string:<BR></SPAN><SPAN STYLE="color:#DF6C75;">&lt;a :hello-world="I need $$$"&gt;asap&lt;/a&gt;<BR><BR></SPAN><SPAN STYLE="color:#383A42;">Julia code:<BR></SPAN><SPAN STYLE="color:#0184BC;">a(var"hello-world!" = raw"I need $$$",<BR> "asap"<BR>)<BR><BR></SPAN><SPAN STYLE="color:#383A42;">Produced HTML:<BR></SPAN><SPAN STYLE="color:#50A14F;">&lt;a :hello-world="I need $$$"&gt;<BR> asap<BR>&lt;/a&gt;</SPAN></DIV>
</pre></div>

Produced HTML:
<q-test :hello-world="I need $$$"></q-test>
<div style="background-color:#f6f8fa;"><pre>
<DIV STYLE="font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:10pt;"><SPAN STYLE="color:#98C379;background-color:#FAFAFA;">julia&gt; </SPAN><SPAN STYLE="color:#383A42;background-color:#FAFAFA;">test_vue_parsing(raw"""&lt;q-test :hello-world="I need $$$"&gt;asap&lt;/q-test&gt;"""; indent = 2)<BR><BR>Original HTML string:<BR></SPAN><SPAN STYLE="color:#DF6C75;background-color:#FAFAFA;">&lt;q-test :hello-world="I need $$$"&gt;asap&lt;/q-test&gt;<BR><BR></SPAN><SPAN STYLE="color:#383A42;background-color:#FAFAFA;">Julia code:<BR></SPAN><SPAN STYLE="color:#0184BC;background-color:#FAFAFA;">quasar(:test, var"hello-world" = R"I need $$$",<BR> "asap"<BR>)<BR><BR></SPAN><SPAN STYLE="color:#383A42;background-color:#FAFAFA;">Produced HTML:<BR></SPAN><SPAN STYLE="color:#50A14F;background-color:#FAFAFA;">&lt;q-test :hello-world="I need $$$"&gt;<BR> asap<BR>&lt;/q-test&gt;</SPAN></DIV>
</pre></div>
#### Prettify html code
The new prettifier is already used in `test_vue_parsing()` by default
```julia
julia> prettify("""<div class="first">single line<div> more\nlines</div></div>"""; indent = 5) |> println
<div class="first">
single line
<div>
more
lines
</div>
</div>
```

## Demos
Expand Down
136 changes: 90 additions & 46 deletions src/StippleUIParser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ module StippleUIParser

using SnoopPrecompile

export parse_vue_html, test_vue_parsing

export parse_vue_html, test_vue_parsing, prettify

using Stipple
using StippleUI
Expand Down Expand Up @@ -38,11 +37,12 @@ function rawrepr(x)
end

function symrepr(x)
# if x is already a Symbol representation return x
startswith(x, "\":") || startswith(x, "\"R\"") && return string(x)
# if x is surrounded by quotes strip quotes
startswith(x, '"') && endswith(x, '"') && length(x) > 1 && (x = x[2:end-1])

# if x is already a Symbol representation return x
startswith(x, ":") || startswith(x, "R\"") && return string(x)

v_sym = repr(Symbol(x))
if startswith(v_sym, ":")
v_sym
Expand Down Expand Up @@ -155,56 +155,102 @@ function function_parser(tag, attrs, context = @__MODULE__)
fn_str, arg_str, attrs
end

function attr_tostring(attr::Pair)
"$(attr[1]) = $(attr[2])"
function attr_to_kwargstring(attr::Pair)
"$(attr[1]) = $(attr[2])"
end

function parse_elem(el::EzXML.Node, level = 1)
if ! iselement(el)
content = strip(el.content)
content == "" && return ""
quotes = occursin('"', content) ? "\"\"\"" : "\""
endswith(content, '"') && (content = content[1:end-1] * "\\\"")
return string(occursin('$', content) ? "raw" : "", quotes, content, quotes)
end
indent = repeat(' ', level * 4)
arg_str = ""
attrs = attr_dict(stipple_attr, el)
function attr_to_paramstring(attr::Pair)
"$(attr[1])=\"$(attr[2])\""
end

fn_str, arg_str, new_attrs = function_parser(Val(Symbol(el.name)), attrs)
function parse_to_stipple(el::EzXML.Node, level = 1; indent = 4)
if ! iselement(el)
content = strip(el.content)
content == "" && return ""
quotes = occursin('"', content) ? "\"\"\"" : "\""
endswith(content, '"') && (content = content[1:end-1] * "\\\"")
return string(occursin('$', content) ? "raw" : "", quotes, content, quotes)
end
indent_1 = repeat(' ', level * indent)
arg_str = ""
attrs = attr_dict(stipple_attr, el)

attr_str = join(attr_tostring.(collect(new_attrs)), ", ")
fn_str, arg_str, new_attrs = function_parser(Val(Symbol(el.name)), attrs)

children = parse_elem.(nodes(el), level + 1)
children = children[length.(children) .> 0]
children_str = join(children, "\n$indent")

no = length(children)
sep1 = (length(arg_str) + length(attr_str) == 0) ? "" : ", "
indent2 = no == 0 ? "" : repeat(' ', (level-1) * 4)
sep2, sep3 = if no == 0
("", "")
elseif no == 1
("$sep1\n$indent", "\n$indent2")
else
("$sep1[\n$indent", "\n$indent2]")
end

sep0 = length(arg_str) > 0 && length(attr_str) > 0 ? ", " : ""
"""$fn_str$arg_str$sep0$attr_str$sep2$children_str$sep3)"""
attr_str = join(attr_to_kwargstring.(collect(new_attrs)), ", ")

children = parse_to_stipple.(nodes(el), level + 1; indent)
children = children[length.(children) .> 0]
children_str = join(children, "\n$indent_1")

no = length(children)
sep1 = (length(arg_str) + length(attr_str) == 0) ? "" : ", "
indent_2 = no == 0 ? "" : repeat(' ', (level - 1) * indent)
sep2, sep3 = if no == 0
("", "")
elseif no == 1
("$sep1\n$indent_1", "\n$indent_2")
else
("$sep1[\n$indent_1", "\n$indent_2]")
end

sep0 = length(arg_str) > 0 && length(attr_str) > 0 ? ", " : ""
"""$fn_str$arg_str$sep0$attr_str$sep2$children_str$sep3)"""
end

function parse_to_html(el::EzXML.Node, level = 1; indent = 4)
if ! iselement(el)
indent_1 = repeat(' ', (level - 1) * indent)
return replace(strip(el.content), r"\n\s*" =>"\n$indent_1")
end
indent_1 = repeat(' ', level * indent)
attrs = attr_dict(el)

attr_str = join(attr_to_paramstring.(collect(attrs)), ", ")

children = parse_to_html.(nodes(el), level + 1; indent)
children = children[length.(children) .> 0]
children_str = join(children, "\n$indent_1")
no = length(children)
sep1 = (length(attr_str) == 0) ? "" : " "
indent_2 = no == 0 ? "" : repeat(' ', (level - 1) * indent)
sep2, sep3 = if no == 0
("", "")
else
("$sep1\n$indent_1", "\n$indent_2")
end
tag = el.name
"""<$tag$sep1$attr_str>$sep2$children_str$sep3</$tag>"""
end

function parse_vue_html(html)
function parse_vue_html(html; indent = 4)
html_string = replace(html, "@"=>"__vue-on__")
empty!(EzXML.XML_GLOBAL_ERROR_STACK)
doc = Logging.with_logger(Logging.SimpleLogger(stdout, Logging.Error)) do
EzXML.parsehtml(html_string).root
end
# remove the html -> body levels
replace(parse_elem(first(eachelement(first(eachelement(doc))))), "__vue-on__" => "@")
replace(parse_to_stipple(first(eachelement(first(eachelement(doc)))); indent), "__vue-on__" => "@")
end

function prettify(html::AbstractString; indent = 4)
html_string = replace(html, "@"=>"__vue-on__")
empty!(EzXML.XML_GLOBAL_ERROR_STACK)
doc = Logging.with_logger(Logging.SimpleLogger(stdout, Logging.Error)) do
EzXML.parsehtml(html_string).root
end
# remove the html -> body levels
replace(parse_to_html(first(eachelement(first(eachelement(doc)))); indent), "__vue-on__" => "@") |> ParsedHTMLString
end

function prettify(el::EzXML.Node; indent = 4)
replace(parse_to_html(el; indent), "__vue-on__" => "@")
end

prettify(doc::EzXML.Document; indent = 4) = prettify(doc.root; indent)

prettify(v::Vector) = prettify(join(v))

function function_parser(tag::Val{Symbol("q-input")}, attrs, context = @__MODULE__)
kk = String.(collect(keys(attrs)))
pos = findfirst(startswith(r"fieldname$|var\"v-model."), kk)
Expand All @@ -214,9 +260,6 @@ function function_parser(tag::Val{Symbol("q-input")}, attrs, context = @__MODULE
haskey(attrs, "label") || (attrs["label"] = "\"\"")
k = kk[pos]
v = attrs[k]
v_symbol = repr(Symbol(strip(v, '"')))
startswith(v_symbol, ":") && (v = v_symbol)

k == "fieldname" || delete!(attrs, k)
attrs["fieldname"] = v

Expand All @@ -228,17 +271,18 @@ function function_parser(tag::Val{Symbol("q-input")}, attrs, context = @__MODULE
end
end

function test_vue_parsing(html_string)
function test_vue_parsing(html_string; prettify::Bool = true, indent = 4)
println("\nOriginal HTML string:")
printstyled(html_string, "\n\n", color = :light_red)

julia_code = parse_vue_html(html_string)
julia_code = parse_vue_html(html_string; indent)

println("Julia code:")
printstyled(julia_code, "\n\n", color = :blue)

println("Produced HTML:")
printstyled(eval(Meta.parse(julia_code)), "\n", color = :green)
new_html = eval(Meta.parse(julia_code))
printstyled(prettify ? StippleUIParser.prettify(new_html; indent) : new_html, "\n", color = :green)
end

# precompilation ...
Expand All @@ -254,10 +298,10 @@ end
<q-scroll-area style="height: 230px; max-width: 300px;">
<div class="row no-wrap">
<div v-for="n in 10" :key="n" style="width: 150px" class="q-pa-sm">
Lorem @ipsum dolor sit amet consectetur adipisicing elit. Architecto fuga quae veritatis blanditiis sequi id expedita amet esse aspernatur! Iure, doloribus!
Lorem @ipsum dolor sit amet consectetur adipisicing elit.
</div>
<q-btn color=\"primary\" label=\"`Animate to \${position}px`\" @click=\"scroll = true\"></q-btn>
<q-input hint=\"Please enter some words\" v-on:keyup.enter=\"process = true\" label=\"Input\" v-model=\"input\" class=\"q-my-md\"></q-input>
<q-input hint=\"Please enter some words\" v-on:keyup.enter=\"process = true\" label=\"Input\" v-model=\"input\"></q-input>
<q-input hint=\"Please enter a number\" label=\"Input\" v-model.number=\"numberinput\" class=\"q-my-md\"></q-input>
</div>
</q-scroll-area>
Expand Down

0 comments on commit 0e874c8

Please sign in to comment.