diff --git a/Project.toml b/Project.toml index 7772ab1..2f1cd61 100644 --- a/Project.toml +++ b/Project.toml @@ -12,6 +12,7 @@ Scratch = "6c6a2e73-6563-6170-7368-637461726353" [compat] DefaultApplication = "1" +OrderedCollections = "1.5" Scratch = "1.1" julia = "1" diff --git a/README.md b/README.md index 2599d95..0b14aa5 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ - Open any `"text/html"`-representable object in your browser with `Page(x)` or `Tab(x)`. - Nice syntax for writing HTML: `Cobweb.h.(children...; attributes...)`. - - `Cobweb.h.` also autocompletes HTML5 tags. -- Lightweight, simple, and hackable. + - `Cobweb.h.` autocompletes HTML5 tags for you. +- Great for templating/building your own `text/html` representations of Julia objects.

@@ -86,40 +86,58 @@ h.div( ## `Cobweb.h` Syntax Summary: -- `h.` creates a `Cobweb.Node`: +- `h(tag::String)` creates a `Cobweb.Node` +- `h.` is simplified syntax for `h(tag)` and you can tab-autocomplete HTML5 tags. ```julia -julia> h.div +julia> node = Cobweb.h.div #
``` -- `Node`s are callable! - - Positional arguments add children: +- Calling a `Node` creates a copy with the specified changes. + - Positional arguments add *children*: ```julia - julia> h.div("child") + julia> node = node("child") #
child
``` - - Keyword arguments add attributes: + - Keyword arguments add *attributes*: ```julia - julia> node = h.div(; id = "myid", class="myclass") + julia> node = node(; id = "myid", class="myclass") #
``` - There's convenient syntax for appending classes as well: ```julia -julia> node."append classes" -#
+julia> node = node."append classes" +#
child
``` -- `Bool`s are special-cased: +### Attributes +- `Node`s act like a mutable NamedTuple when it comes to attributes: ```julia -julia> h.div(hidden=true) -# +node = Cobweb.h.div -julia> h.div(hidden=false) -#
+node.id = "my_id" + +node +#
+``` + + +### Children + +- `Node`s act like a `Vector` when it comes to children: + +```julia +node = Cobweb.h.div + +push!(node, Cobweb.h.h1("Hello!")) + +node[:] +# 1-element Vector{Any}: +#

Hello!

```
diff --git a/src/Cobweb.jl b/src/Cobweb.jl index 1ece717..e04bbee 100644 --- a/src/Cobweb.jl +++ b/src/Cobweb.jl @@ -23,7 +23,7 @@ Should not often be used directly. See `?Cobweb.h`. struct Node tag::String attrs::OrderedDict{String,String} - children::Vector + children::Vector{Any} function Node(tag::AbstractString, attrs::AbstractDict, children::AbstractVector) new(string(tag), OrderedDict(string(k) => string(v) for (k,v) in pairs(attrs)), collect(children)) end @@ -84,7 +84,9 @@ Base.getproperty(::typeof(h), tag::Symbol) = h(string(tag)) Base.propertynames(::typeof(h)) = HTML5_TAGS #-----------------------------------------------------------------------------# @h -HTML5_TAGS = [:a,:abbr,:address,:area,:article,:aside,:audio,:b,:base,:bdi,:bdo,:blockquote,:body,:br,:button,:canvas,:caption,:cite,:code,:col,:colgroup,:data,:datalist,:dd,:del,:details,:dfn,:dialog,:div,:dl,:dt,:em,:embed,:fieldset,:figcaption,:figure,:footer,:form,:h1,:h2,:h3,:h4,:h5,:h6,:head,:header,:hgroup,:hr,:html,:i,:iframe,:img,:input,:ins,:kbd,:label,:legend,:li,:link,:main,:map,:mark,:math,:menu,:menuitem,:meta,:meter,:nav,:noscript,:object,:ol,:optgroup,:option,:output,:p,:param,:picture,:pre,:progress,:q,:rb,:rp,:rt,:rtc,:ruby,:s,:samp,:script,:section,:select,:slot,:small,:source,:span,:strong,:style,:sub,:summary,:sup,:svg,:table,:tbody,:td,:template,:textarea,:tfoot,:th,:thead,:time,:title,:tr,:track,:u,:ul,:var,:video,:wbr] +const HTML5_TAGS = [:a,:abbr,:address,:area,:article,:aside,:audio,:b,:base,:bdi,:bdo,:blockquote,:body,:br,:button,:canvas,:caption,:cite,:code,:col,:colgroup,:data,:datalist,:dd,:del,:details,:dfn,:dialog,:div,:dl,:dt,:em,:embed,:fieldset,:figcaption,:figure,:footer,:form,:h1,:h2,:h3,:h4,:h5,:h6,:head,:header,:hgroup,:hr,:html,:i,:iframe,:img,:input,:ins,:kbd,:label,:legend,:li,:link,:main,:map,:mark,:math,:menu,:menuitem,:meta,:meter,:nav,:noscript,:object,:ol,:optgroup,:option,:output,:p,:param,:picture,:pre,:progress,:q,:rb,:rp,:rt,:rtc,:ruby,:s,:samp,:script,:section,:select,:slot,:small,:source,:span,:strong,:style,:sub,:summary,:sup,:svg,:table,:tbody,:td,:template,:textarea,:tfoot,:th,:thead,:time,:title,:tr,:track,:u,:ul,:var,:video,:wbr] + +const VOID_ELEMENTS = [:area,:base,:br,:col,:command,:embed,:hr,:img,:input,:keygen,:link,:meta,:param,:source,:track,:wbr] macro h(ex) esc(_h(ex)) @@ -109,23 +111,57 @@ escape(x) = replace(string(x), escape_chars...) unescape(x::AbstractString) = replace(x, reverse.(escape_chars)...) #-----------------------------------------------------------------------------# show (html) -function Base.show(io::IO, node::Node) - p(args...) = print(io, args...) - p('<', tag(node)) - for (k,v) in attrs(node) - v == "true" ? p(' ', k) : v != "false" && p(' ', k, '=', '"', v, '"') +function print_opening_tag(io::IO, o::Node; self_close::Bool = false) + print(io, '<', tag(o)) + for (k,v) in attrs(o) + v == "true" ? print(io, ' ', k) : v != "false" && print(io, ' ', k, '=', '"', v, '"') end - p('>') - for child in children(node) - showable("text/html", child) ? show(io, MIME("text/html"), child) : p(child) - end - p("') + self_close && Symbol(tag(o)) ∉ VOID_ELEMENTS && length(children(o)) == 0 ? + print(io, " />") : + print(io, '>') +end + +function Base.show(io::IO, o::Node) + p(args...) = print(io, args...) + print_opening_tag(io, o) + foreach(x -> showable("text/html", x) ? show(io, MIME("text/html"), x) : p(x), children(o)) + p("') end Base.show(io::IO, ::MIME"text/html", node::Node) = show(io, node) Base.show(io::IO, ::MIME"text/xml", node::Node) = show(io, node) Base.show(io::IO, ::MIME"application/xml", node::Node) = show(io, node) +function pretty(io::IO, o::Node; depth=get(io, :depth, 0), indent=get(io, :indent, " "), self_close = get(io, :self_close, true)) + p(args...) = print(io, args...) + p(indent ^ depth) + print_opening_tag(io, o; self_close) + if length(children(o)) == 1 && !(only(o) isa Node) + x = only(o) + txt = showable("text/html", x) ? repr("text/html", x) : string(x) + if occursin('\n', txt) + println(io) + foreach(line -> p(indent ^ (depth+1), line, '\n'), lstrip.(split(txt, '\n'))) + p(indent ^ depth, "') + else + p(txt) + p("') + end + elseif length(children(o)) > 1 + child_io = IOContext(io, :depth => depth + 1, :indent => indent) + for child in children(o) + println(io) + pretty(child_io, child) + end + p('\n', indent ^ depth, "') + end +end +function pretty(io::IO, x; depth=get(io, :depth, 0), indent=get(io, :indent, " ")) + print(io, indent ^ depth) + showable("text/html", x) ? show(io, MIME("text/html"), x) : print(io, x) +end +pretty(x; kw...) = (io = IOBuffer(); pretty(io, x; kw...); String(take!(io))) + #-----------------------------------------------------------------------------# show (javascript) struct Javascript x::String