#{person}
```
## Case
模式匹配是Haskell的利器之一。总和类型(Sum types)让你能够清晰的模拟世界上的类型,`case`语法能够让你安全的匹配,如果你丢掉了一种情况,编译器会警告你。Hamlet也有同样的能力。
```haskell
$case foo
$of Left bar
It was left: #{bar}
$of Right baz
It was right: #{baz}
```
## With
完善一些,我们有了`with`。它在为一个长表达式声明一个别名的时候非常便捷。
```haskell
$with foo <- some very (long ugly) expression that $ should only $ happen once
But I'm going to use #{foo} multiple times. #{foo}
```
## Doctype
最后一点语法糖: doctype语句。我们已经支持了许多不同版本的`doctype`,即使我们建议使用`$doctype 5`,这样会生成``。
```haskell
$doctype 5
Hamlet is Awesome
All done.
```
> 这是一种非常老但是仍然被支持的语法:三个感叹号`(!!!)`。你可能在别的地方代码中,仍然可以看到这个。我们没有计划将其移除,但是通常来说`$doctype` 趋于易读。
# Lucius 语法
Lucius 是Shakespeare中两种CSS模板语言之一。它是CSS的一个超集,将来会在此基础上添加更多特性。
- 类似Hamlet, 我们支持变量和URL插值。
- CSS块支持嵌套
- 支持在模板中声明变量
- 一个作为混入(mixin)的CSS集可以在多个声明中复用。
第二点:你想给`article`中的一些标签指定样式。在纯CSS中,你可能必须这样写:
```css
article code { background-color: grey; }
article p { text-indent: 2em; }
article a { text-decoration: none; }
```
这种情况,每行都需要写出article,这样还是有些臃肿的。想象一下如果有十几个或者更多的情况。这样虽然并不是世界上最糟糕的事情,但还是有些烦人的。Lucius帮助你从中解脱出来:
```haskell
article {
code { background-color: grey; }
p { text-indent: 2em; }
a { text-decoration: none; }
> h1 { color: green; }
}
```
使用 Lucius 变量能够避免重复。一个简单的例子,定义一个公用的颜色:
```haskell
@textcolor: #ccc; /* just because we hate our users */
body { color: #{textcolor} }
a:link, a:visited { color: #{textcolor} }
```
Mixin 是Lucius比较新的特性。我们声明一个mixin来提供一个属性集,然后将mixin使用`(^)`插入到模板中。举个例子演示下我们怎样使用mixin来处理一个供应商前缀问题。
```haskell
{-# LANGUAGE QuasiQuotes #-}
import Text.Lucius
import qualified Data.Text.Lazy.IO as TLIO
-- Dummy render function.
render = undefined
-- Our mixin, which provides a number of vendor prefixes for transitions.
transition val =
[luciusMixin|
-webkit-transition: #{val};
-moz-transition: #{val};
-ms-transition: #{val};
-o-transition: #{val};
transition: #{val};
|]
-- Our actual Lucius template, which uses the mixin.
myCSS =
[lucius|
.some-class {
^{transition "all 4s ease"}
}
|]
main = TLIO.putStrLn $ renderCss $ myCSS render
```
## Cassius 语法
除了Lucius,Cassius 是一个空白字符敏感的备选方案。就像摘要中提到的,它们使用相同的处理引擎,但Cassius 预处理时,将插入括号闭合子代码块和分号来结束本行。这就意味着你可以使用所有Lucius的特性,举个简单的例子:
```haskell
#banner
border: 1px solid #{bannerColor}
background-image: url(@{BannerImageR})
```
## Julius 语法
Julius 是我们讨论过最为简单的语言,实际上,它几乎可以认为它就是JavaScript。Julia 支持我们提到的三种格式的插值。除此之外对内容不做任何的转换。
# 调用 Shakespeare
问题来了:我该怎么使用这东西呢?这里有三种不同的方式在你的Haskell代码中调用Shakespeare:
## Quasiquotes
Quasiquotes 支持在Haskell代码中嵌入任意内容,并在编译时,将其转换成Haskell代码。
## 外部文件
这种情况中,模板代码在一个独立的文件中,通过Template Haskell引用。
## 刷新模式
以上两种模式在有任何变动的情况下,都需要完全重新编译。在刷新模式中,你的模板在一个独立的文件中,通过Template Haskell中引用。但是在运行时,外部文件每次都是从零开始重新解析。
> 刷新模式不支持Hamlet,仅仅支持Cassius,Lucius和Julius。在Hamlet中有太多的复杂特性直接依赖于Haskell编译器,在运行时重新实现是不切合实际的。
前两种途径的任何一种都能应用于生产环境。他们都将整个模板嵌入到最后的可执行文件中,简化了发布而且提高了性能。quasiquoter的优点是简单:所有东西都在一个单独的文件中。对于一个简短的模板,会非常适用。然而,通常上,建议使用外部文件方式:
- 逻辑和展示分开
- 容易更换外部文件和调试一些简单的CPP宏,这意味着你可以保持快速发展的同时,仍能获得高性能
因为这些特殊的QuasiQuoters和Template Haskell 函数,你需要保证使能这些语言扩展并且使用正确的语法。例子如下:
## Quasiquoter
```haskell
{-# LANGUAGE OverloadedStrings #-} -- we're using Text below
{-# LANGUAGE QuasiQuotes #-}
import Text.Hamlet (HtmlUrl, hamlet)
import Data.Text (Text)
import Text.Blaze.Html.Renderer.String (renderHtml)
data MyRoute = Home | Time | Stylesheet
render :: MyRoute -> [(Text, Text)] -> Text
render Home _ = "/home"
render Time _ = "/time"
render Stylesheet _ = "/style.css"
template :: Text -> HtmlUrl MyRoute
template title = [hamlet|
$doctype 5
#{title}
#{title}
|]
main :: IO ()
main = putStrLn $ renderHtml $ template "My Title" render
```
## Extenal file
```haskell
{-# LANGUAGE OverloadedStrings #-} -- we're using Text below
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE CPP #-} -- to control production versus debug
import Text.Lucius (CssUrl, luciusFile, luciusFileReload, renderCss)
import Data.Text (Text)
import qualified Data.Text.Lazy.IO as TLIO
data MyRoute = Home | Time | Stylesheet
render :: MyRoute -> [(Text, Text)] -> Text
render Home _ = "/home"
render Time _ = "/time"
render Stylesheet _ = "/style.css"
template :: CssUrl MyRoute
#if PRODUCTION
template = $(luciusFile "template.lucius")
#else
template = $(luciusFileReload "template.lucius")
#endif
main :: IO ()
main = TLIO.putStrLn $ renderCss $ template render
```
```haskell
-- @template.lucius
foo { bar: baz }
```
该函数的命名方案是非常一致的。
| 语言 | Quasiquoter | External file | Reload |
| --- | --- | --- | --- |
| Hamlet | hamlet | hamletFile | N/A |
| Cassius | cassius | cassiusFile | cassiusFileReload |
| Lucius | lucius | luciusFile | luciusFileReload |
| Julius | julius | juliusFile | juliusFileReload |
# 备选Hamlet类型
到目前为止,我们已经看到怎样从Hamlet产生一个`HtmlUrl`值,也就是一段嵌入类型安全的URL的HTML。我们可以使用Hamlet生成三种其他值:纯HTML,带URL的HTML、国际化消息和组件。最后一个会在组件一章进行介绍。
生成不带URL的纯HTML,我们使用“simplified Hamlet”。这里有一些变化:
- 我们使用一个不同的函数集,它们前缀带‘s’。所以quasiquoter是`shamlet`,外部文件函数是`shamletFile`。怎样发音还有争议。
- 不允许URL插值,否则会产生一个编译时错误
- 不允许嵌入任意的`HtmlUrl`值。这个规则是说,嵌入的值必须和模板本身的类型一致,所以必须是`Html`。这意味着,对于shamlet,嵌入可以完全取代常规的变量插值(用哈希)。
在Hamlet中处理国际化(i18n)有一些复杂。Hamlet通过一个消息数据类型支持i18n,在概念和实现上于类型安全的URL非常相似。作为一个激进的例子,我们需要一个应用,向你问好并且告诉你,你已经吃了多少个苹果。我们可以使用一个数据类型展示出这些消息。
```haskell
data Msg = Hello | Apples Int
```
然后,我们将其转换成一些人类能够读懂的语言,所以我们定一些转化函数:
```haskell
renderEnglish :: Msg -> Text
renderEnglish Hello = "Hello"
renderEnglish (Apples 0) = "You did not buy any apples."
renderEnglish (Apples 1) = "You bought 1 apple."
renderEnglish (Apples i) = T.concat ["You bought ", T.pack $ show i, " apples."]
```
我们现在想要将这些Msg值直接插入到模板中。我们使用下划线插值。
```haskell
$doctype 5
i18n
_{Hello}
_{Apples count}
```
这种方式的模板需要一些方式将这些值转换成HTML。所以就想类型安全URL一样,我们传入一个渲染函数。为了表现这些,我们定义一个别名:
```haskell
type Render url = url -> [(Text, Text)] -> Text
type Translate msg = msg -> Html
type HtmlUrlI18n msg url = Translate msg -> Render url -> Html
```
在此时,你可以传入`renderEnglish`、`renderSpanish`、或者`renderKlingon`到这个模板,然后它会产生很好的转换输出。完整版的程序是:
```haskell
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Text (Text)
import qualified Data.Text as T
import Text.Hamlet (HtmlUrlI18n, ihamlet)
import Text.Blaze.Html (toHtml)
import Text.Blaze.Html.Renderer.String (renderHtml)
data MyRoute = Home | Time | Stylesheet
renderUrl :: MyRoute -> [(Text, Text)] -> Text
renderUrl Home _ = "/home"
renderUrl Time _ = "/time"
renderUrl Stylesheet _ = "/style.css"
data Msg = Hello | Apples Int
renderEnglish :: Msg -> Text
renderEnglish Hello = "Hello"
renderEnglish (Apples 0) = "You did not buy any apples."
renderEnglish (Apples 1) = "You bought 1 apple."
renderEnglish (Apples i) = T.concat ["You bought ", T.pack $ show i, " apples."]
template :: Int -> HtmlUrlI18n Msg MyRoute
template count = [ihamlet|
$doctype 5
i18n
_{Hello}
_{Apples count}
|]
main :: IO ()
main = putStrLn $ renderHtml
$ (template 5) (toHtml . renderEnglish) renderUrl
```
# 其他 Shakespeare
除了HTML,CSS和Javascript帮助,还有一些通用目的的Shakespeare 可用。shakespeare-text提供了一个简单的方式创建插值字符串,就像如Ruby和Python等脚本语言的习惯。这个包的作用不限于Yesod。
```haskell
{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
import Text.Shakespeare.Text
import qualified Data.Text.Lazy.IO as TLIO
import Data.Text (Text)
import Control.Monad (forM_)
data Item = Item
{ itemName :: Text
, itemQty :: Int
}
items :: [Item]
items =
[ Item "apples" 5
, Item "bananas" 10
]
main :: IO ()
main = forM_ items $ \item -> TLIO.putStrLn
[lt|You have #{show $ itemQty item} #{itemName item}.|]
```
这个例子的几个点:
- 请注意,我们有涉及三个不同的文本数据类型(`String`,strict `Text`和lazy `Text`)。都可以同时使用。
- 我们使用一个叫`lt`的quasiquoter,来生成lazy text,这里还有`st`,生成strict text。
- 同样,这里有长一些的名字(`ltext`和`stext`)。
# 通常建议
这里有一些来自Yesod社区的通用提示,关于如何获取大多数Shakespeare。
- 对于实际网页,使用外部文件,对于库,不是很长的文本,使用quasiquoters也是没问题的。
- Patrick Brisbin 加入了[Vim 代码高亮](https://github.com/pbrisbin/html-template-syntax),这个很有用。
- 你不应该在已存在的标签后嵌入开始、结束标签。唯一的例外是一个长文本内的``或者``标签