Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/grottopress/markout
Markout is an awesome Crystal DSL for HTML
https://github.com/grottopress/markout
crystal dsl html
Last synced: about 2 months ago
JSON representation
Markout is an awesome Crystal DSL for HTML
- Host: GitHub
- URL: https://github.com/grottopress/markout
- Owner: GrottoPress
- License: mit
- Created: 2018-10-02T01:01:26.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-08-03T17:33:34.000Z (5 months ago)
- Last Synced: 2024-08-03T18:40:16.674Z (5 months ago)
- Topics: crystal, dsl, html
- Language: Crystal
- Homepage:
- Size: 127 KB
- Stars: 21
- Watchers: 5
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# Markout
**Markout** is an awesome Crystal DSL for HTML. It enables calling regular HTML tags as methods to generate HTML.
*Markout* ensures type-safe HTML with valid syntax, and automatically escapes attribute values. It supports *HTML 4* and *5*, and *XHTML*.
### Examples:
```crystal
p "A paragraph"
# =>A paragraph
p do
text "A paragraph"
end
# =>A paragraph
h1 "A first-level heading", class: "heading"
# =>A first-level heading
h1 class: "heading" do
text "A first-level heading"
end
# =>A first-level heading
ul id: "a-wrapper", class: "list-wrap" do
["aa", "bb", "cc"].each do |x|
li x, class: "list-item"
end
end
# =>
- aa
- bb
- cc
#
#
#
#
input type: "checkbox", checked: nil
# => HTML 4, 5:
# => XHTML:
```
## Installation
Add this to your application's `shard.yml`:
```yaml
dependencies:
markout:
github: GrottoPress/markout
```
## Usage
### Pages
With *Markout*, pages are created using regular Crystal structs and classes. *Markout* comes with a page mixin, which child pages can `include`, and override specific methods for their own use case:
```crystal
require "markout"
# Create your own base page
abstract struct BasePage
# Include the page mixin
include Markout::Page
# Set HTML version
#
# Versions:
# `HtmlVersion::HTML_5` (default)
# `HtmlVersion::XHTML_1_1`
# `HtmlVersion::XHTML_1_0`
# `HtmlVersion::HTML_4_01`
#private def html_version : HtmlVersion
# HtmlVersion::XHTML_1_1
#end
private def body_tag_attr : NamedTuple
{class: "my-body-class"}
end
private def inside_head : Nil
meta charset: "UTF-8"
head_content
end
private def inside_body : Nil
header id: "header" do
h1 "My First Heading Level", class: "heading"
p "An awesome description", class: "description"
end
main id: main do
body_content
end
footer id: "footer" do
raw ""
end
end
private def head_content : Nil
end
private def body_content : Nil
end
end
# Now, create a page
struct MyFirstPage < BasePage
private def head_content : Nil
title "My First Page"
end
private def body_content : Nil
p "Hello from Markout!"
end
end
# SEND OUTPUT TO CONSOLE
puts MyFirstPage.new
# => \
# \
# \
# \
# My First Page\
# \
# \
# \
#
My First Heading Level
\#
An awesome description
\# \
# \
#
Hello from Markout!
\# \
# \
# \
# \
# \
#
# OR, SERVE IT TO THE BROWSER
require "http/server"
server = HTTP::Server.new do |context|
context.response.content_type = "text/html"
context.response.print MyFirstPage.new
end
puts "Listening on http://#{server.bind_tcp(8080)}"
server.listen
# Visit 'http://localhost:8080' to see Markout in action
```
### Components
You may extract out shared elements that do not exactly fit into the page inheritance structure as components, and mount them in your pages:
```crystal
require "markout"
# Create your own base component
abstract struct BaseComponent
include Markout::Component
# Set HTML version
#
# Same as for pages.
#private def html_version : HtmlVersion
# HtmlVersion::XHTML_1_1
#end
end
# Create the component
struct MyFirstComponent < BaseComponent
def initialize(@users : Array(String))
end
private def render : Nil
ul class: "users" do
@users.each do |user|
li user, class: "user"
# Same as `li class: "user" do text(user) end`
end
end
end
end
# Mount the component
struct MySecondPage < BasePage
def initialize(@users : Array(String))
end
private def head_content : Nil
title "Component Test"
end
private def body_content : Nil
div class: "users-wrap" do
mount MyFirstComponent, @users # Or `mount MyFirstComponent.new(@users)`
end
end
end
#puts MySecondPage.new(["Kofi", "Ama", "Nana"])
```
A component may accept a block:
```crystal
# Create the component
struct MyLinkComponent < BaseComponent
def initialize(@url : String, &@block : Proc(Component, Nil))
end
private def render : Nil
a href: @url, class: "link", "data-foo": "bar" do
@block.call(self)
end
end
end
# Mount the component
struct MyThirdPage < BasePage
private def body_content : Nil
div class: "link-wrap" do
mount MyLinkComponent, "http://ab.c" do |html|
html.text("Abc")
end
end
end
end
puts MyThirdPage.new
# => ...
#
# ...
```
To accept arbitrary arguments, you would have to do something different:
```crystal
# Create the component
struct MyLinkComponent < BaseComponent
def initialize(@label : String, @url : String, **opts)
render_args(**opts)
end
private def render_args(**opts)
args = opts.merge({href: @url})
args = {class: "link"}.merge(args)
a @label, **args
end
end
# Mount the component
struct MyThirdPage < BasePage
private def body_content : Nil
div class: "link-wrap" do
mount MyLinkComponent, "Abc", "http://ab.c", "data-foo": "bar"
end
end
end
puts MyThirdPage.new
# => ...
#
# ...
```
### Custom Tags
You may define arbitrary tags with `#tag`. This is particularly useful for rendering JSX or similar:
```crystal
tag :MyApp, title: "My Awesome App" do
p "My app is the best."
end
# => \
#
My app is the best.
\#
tag :MyApp, title: "My Awesome App"
# =>
tag :cuboid, width: 4, height: 3, length: 2 do
text "A cuboid"
end
# =>
# A cuboid
#
```
### Handy methods
Apart from calling regular HTML tags as methods, the following methods are available:
- `#raw(text : String)`: Use this render unescaped text
- `#text(text : String)`: Use this to render escaped text
## Contributing
1. [Fork it](https://github.com/GrottoPress/markout/fork)
1. Switch to the `master` branch: `git checkout master`
1. Create your feature branch: `git checkout -b my-new-feature`
1. Make your changes, updating changelog and documentation as appropriate.
1. Commit your changes: `git commit`
1. Push to the branch: `git push origin my-new-feature`
1. Submit a new *Pull Request* against the `GrottoPress:master` branch.