갈루아의 반서재


Haskell Yesod의 템플릿 언어 세익스피어를 활용하여 웹페이지 만들기 (1)



앞으로 2개의 포스팅을 통해 Yesod로 웹페이지를 구현해보겠습니다.

먼저, Yesod로 Web 페이지를 구현하기 위한 템플릿 언어에는 다음이 있습니다.

  • Hamlet
    • 들어쓰기를 활용한 네스트 표현
  • Cassius (Lucius)
    • Lucius 는 CSS 의 슈퍼세트로、CSS 기술
    • Lucius 는 {} 를 통해 네스트 표현
  • Julius
    • 들어쓰기로 네스트를 표현

이상을 합쳐서 Shakespeare라고 부르고, 각각은  HTMLCSSJavascript 에 대응합니다. HTML 이 Hamlet 에, 

Cassius (Lucius) 이 CSS 에, Javascript 에 Julius 가 대응하는 등 첫 글자가 동일한 것은 뭐 우연일 것입니다. 

Web페이지 작성해보기

이번에는 Bootstrap3でさくっとWebサイトを作ってみよう의 내용을 Yesod 를 이용하여 기술해보겠습니다. 

Wrapper 작성

<html><head><body>을 정의하는 것부터 시작합니다. 그리고 이를  templates/default-layout-wrapper.hamlet 에 기술합니다.

templates/default-layout-wrapper.hamlet

1
2
3
4
5
6
7
$doctype 5
<html>
  <head>
    <title>#{pageTitle pc}
    ^{pageHead pc}
  <body>
    ^{pageBody pc}
cs


Hamlet의 구문에는 닫는 태그는 기본적으로 사용하지 않습니다. 닫는 태그를 들어쓰기로 대신합니다. 

Doctype

$doctype 은 Doctype 선언을 위한 키워드입니다. 아래와 같이 종류가 있습니다.

구문

생성되는 Doctype 선언
$doctype 5<!DOCTYPE html>
$doctype html<!DOCTYPE html>
$doctype 1.1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "" class="autolink">http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
$doctype strict<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "" class="autolink">http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
!!! (非推奨 비추장)<!DOCTYPE html>

변수전개

Hamlet 은 변수의 전개를 지원합니다. 다만, 전개되는 대상에 따라 아래와 같이 약간의 차이가 있습니다. 

기법전개되는 대상
#{...}변수
@{...}형안전URL
@?{...}쿼리 파라메터에 붙는 형안전URL
^{...}

동형 템플릿

*{...}속성 또는 속성의 리스트
_{...}메시지

이번 예에서는 변수전개와 템플릿의 전개를 다뤄봅니다.


Header 구역 작성

templates/default-layout.hamlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div #header>
  <div .logo>
    <h1>LOGO
  <nav .navbar .navbar-default role="navigation">
    <div .container>
      <div .navbar-header>
        <button .navbar-toggle data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
          <span .sr-only>Toggle navigation
          <span .icon-bar>
          <span .icon-bar>
          <span .icon-bar>
        <span .navbar-brand .visible-xs href="@{HomeR}">Menu
      <div .collapse .navbar-collapse #bs-example-navbar-collapse-1>
        <ul .nav .navbar-nav>
          $forall n <- [1,2,3,4,5]
            $if n == 1
              <li .first>
                <a href=@{HomeR}>MENU#{show n}
            $else
              <li>
                <a href=@{HomeR}>MENU#{show n}
cs

형안전 URL @{HomeR} 로 링크를 지정합니다. 이것은 config/routes 파일에 기술된 내용과 대응관계로 실제의 URL 을 교환합니다. 태그의 속성으로  . 와 # 을 사용하는바, 이로부터 자동적으로 id와 class에 각각 치환됩니다. 


생성되는 HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html><head><title></title>
<link rel="stylesheet" href="http://***.**.**.***:3000/static/css/bootstrap.css?etag=PDMJ7RwX"><link rel="stylesheet" href="http://***.**.**.***:3000/static/tmp/autogen-dyDfk8nC.css"></head>
<body><div id="header"><div class="logo"><h1>LOGO</h1>
</div>
<nav class="navbar navbar-default" role="navigation"><div class="container"><div class="navbar-header"><button class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"><span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="navbar-brand visible-xs" href="http://***.**.**.***:3000/">Menu</span>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"><ul class="nav navbar-nav"><li class="first"><a href="http://***.**.**.***:3000/">MENU1</a>
</li>
<li><a href="http://***.**.**.***:3000/">MENU2</a>
</li>
<li><a href="http:/***.**.**.***:3000/">MENU3</a>
</li>
<li><a href="http://***.**.**.***:3000/">MENU4</a>
</li>
<li><a href="http://***.**.**.***:3000/">MENU5</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
</body>
</html>
 
cs



Header 영역 CSS 정의

CassiusHamlet과 같은 들여쓰기를 이용하여 기술합니다. 다만, 세미클론은 필요없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#header
  padding-top: 20px
  background-color: #eee
 
.logo
  text-align: center
  width: 100px
  margin-left: auto
  margin-right: auto
  margin-bottom: 20px
 
.logo h1
  margin: 0
  padding-top: 38px
  width: 100px
  height: 100px
  -webkit-border-radius: 50%
  -moz-border-radius: 50%
  border-radius: 50%
  font-size: 26px
  color: #7E7E7E
  background-color: #fff
 
.navbar
  border-radius: 0
 
.navbar-nav
  float: none
  width: 400px
  margin: 10px auto
 
.navbar-nav > li
  text-align: center
  float: left
  width: 80px
  border-right: 1px solid #555
 
.navbar-nav > li.first
  border-left: 1px solid #555
 
.navbar-nav > li > a
  padding-top: 5px
  padding-bottom: 5px
cs


생성되는 CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#header {
    padding-top: 20px;
    background-color: #eee;
}
.logo {
    text-align: center;
    width: 100px;
    margin-left: auto;
    margin-right: auto;
    margin-bottom: 20px;
}
.logo h1 {
    margin: 0;
    padding-top: 38px;
    width: 100px;
    height: 100px;
    -webkit-border-radius: 50%;
    -moz-border-radius: 50%;
    border-radius: 50%;
    font-size: 26px;
    color: #7E7E7E;
    background-color: #fff;
}
.navbar {
    border-radius: 0;
}
.navbar-nav {
    float: none;
    width: 400px;
    margin: 10px auto;
}
.navbar-nav > li {
    text-align: center;
    float: left;
    width: 80px;
    border-right: 1px solid #555;
}
.navbar-nav > li.first {
    border-left: 1px solid #555;
}
.navbar-nav > li > a {
    padding-top: 5px;
    padding-bottom: 5px;
}
cs


이번에 새롭게 등장한 구문이 $if $else 구문과 $forall 구문입니다.

Forall

이것은、리스트의 값을 반복해서 보여줄 때 사용됩니다.

1
2
3
<ul>
  $forall person <- people
    <li>#{person}
cs


if elseif else

Hamlet 에는 조건분기로 if, elseif, else 구문이 사용됩니다.

1
2
3
4
5
6
$if isAdmin
  <p>管理者ページへようこそ。
$elseif isLoggedIn
  <p>管理者ではありません。
$else
  <p>ログインしてください。
cs



Footer 영역의 작성

헤더 영역과 관련 방식으로  templates/default-layout.hamlet 에 기술한다.

templates/default-layout.hamlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div #footer>
  <div .logo>
    <p>LOGO
  <ul .navbar-nav .list-inline>
    $forall n <- [1,2,3,4,5]
      $if n == 1
        <li .first>
          <a href=@{HomeR}>MENU#{show n}
      $else
        <li>
          <a href=@{HomeR}>MENU#{show n}
  <div .clearfix>
  <ul .sns-icon .list-inline>
    <li><i .fa .fa-twitter .fa-2x>
    <li><i .fa .fa-facebook .fa-2x>
  <div .copy>
    <span>#{appCopyright $ appSettings master}
cs



Scaffolded 사이트의 카피라이트는 #{appCopyright $ appSettings master} 에서 기술하거나、config/Settings 에서 카피라이트의 문자열을 설정할 수 있습니다.


config/settings.yaml

1
2
3
4
5
6
7
8
9
10
# Values formatted like "_env:ENV_VAR_NAME:default_value" can be overridden by the specified environment variable.
# See https://github.com/yesodweb/yesod/wiki/Configuration#overriding-configuration-values-with-environment-variables
 
static-dir:     "_env:STATIC_DIR:static"
host:           "_env:HOST:*4" # any IPv4 host
port:           "_env:PORT:3000" # NB: The port `yesod devel` uses is distinct from this value. Set the `yesod devel` port from the command line.
ip-from-header: "_env:IP_FROM_HEADER:false"
 
copyright: Insert copyright statement here
#analytics: UA-YOURCODE
cs


生成されるHTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="footer"><div class="logo"><p>LOGO</p>
</div>
<ul class="navbar-nav list-inline"><li class="first"><a href="http://***.**.**.***:3000/">MENU1</a>
</li>
<li><a href="http://***.**.**.***:3000/">MENU2</a>
</li>
<li><a href="http://***.**.**.***:3000/">MENU3</a>
</li>
<li><a href="http://***.**.**.***:3000/">MENU4</a>
</li>
<li><a href="http://***.**.**.***:3000/">MENU5</a>
</li>
</ul>
<div class="clearfix"></div>
<ul class="sns-icon list-inline"><li><i .fa .fa-twitter .fa-2x></li>
<li><i .fa .fa-facebook .fa-2x></li>
</ul>
<div class="copy"><span>Insert copyright statement here</span>
</div>
</div>
</body>
</html
cs


여기에서 HTML이 기대한대로 생성되지 않은 점에 주의하세요. 이는 Hamlet의 들여쓰기 태그가 통상의 텍스트 값대로 처리되지 않았기 때문에 이런 결과가 생기게 된 것입니다. 들여쓰기 태그를 기술하는 실제에서 id와 class가 포함되는 경우에는 적절히 들여쓰기를 할 필요가 있습니다. 마지막으로font-awesome 을 사용하기 위해 Foundation.hs파일을 아래와 같이 약간 수정합니다.


1
2
3
4
5
        pc <- widgetToPageContent $ do
            addStylesheet $ StaticR css_bootstrap_css
            addStylesheetRemote "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
            $(widgetFile "default-layout")
        withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
cs





Footer 영역의 CSS 정의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#footer
  text-align: center
  padding: 40px 0
  background-color: #7E7E7E
 
#footer .logo
  margin-top: 0
 
.logo p
  margin: 0
  padding-top: 34px
  width: 100px
  height: 100px
  -webkit-border-radius: 50%
  -moz-border-radius: 50%
  border-radius: 50%
  font-size: 26px
  color: #7E7E7E
  font-weight: bold
  background-color: #fff
 
#footer .navbar-nav > li
  border-right: 1px solid #000
#footer .navbar-nav > li.first
  border-left: 1px solid #000
 
#footer .navbar-nav li a
  color: #fff
 
.sns-icon
  margin-top: 30px
 
.sns-icon li
  padding-left: 20px
.sns-icon li:hover
  color: #fff
 
.copy
  margin-top: 20px
 
.copy span
  color: #333
cs

이상으로 1편을 마칩니다. 다음 회에서는 메인 콘텐츠를 만들어 보겠습니다.

지금까지의 소스코드


templates/default-layout-wrapper.hamlet

1
2
3
4
5
6
7
$doctype 5
<html>
  <head>
    <title>#{pageTitle pc}
    ^{pageHead pc}
  <body>
    ^{pageBody pc}
cs

templates/default-layout.hamlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<div #header>
  <div .logo>
    <h1>LOGO
  <nav .navbar .navbar-default role="navigation">
    <div .container>
      <div .navbar-header>
        <button .navbar-toggle data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
          <span .sr-only>Toggle navigation
          <span .icon-bar>
          <span .icon-bar>
          <span .icon-bar>
        <span .navbar-brand .visible-xs href="@{HomeR}">Menu
      <div .collapse .navbar-collapse #bs-example-navbar-collapse-1>
        <ul .nav .navbar-nav>
          $forall n <- [1,2,3,4,5]
            $if n == 1
              <li .first>
                <a href=@{HomeR}>MENU#{show n}
            $else
              <li>
                <a href=@{HomeR}>MENU#{show n}
<div #footer>
  <div .logo>
    <p>LOGO
  <ul .navbar-nav .list-inline>
    $forall n <- [1,2,3,4,5]
      $if n == 1
        <li .first>
          <a href=@{HomeR}>MENU#{show n}
      $else
        <li>
          <a href=@{HomeR}>MENU#{show n}
  <div .clearfix>
  <ul .sns-icon .list-inline>
    <li>
      <i .fa .fa-twitter .fa-2x>
    <li>
      <i .fa .fa-facebook .fa-2x>
  <div .copy>
    <span>#{appCopyright $ appSettings master}
cs

templates/default-layout.cassius

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#header
  padding-top: 20px
  background-color: #eee
 
.logo
  text-align: center
  width: 100px
  margin-left: auto
  margin-right: auto
  margin-bottom: 20px
 
.logo h1
  margin: 0
  padding-top: 38px
  width: 100px
  height: 100px
  -webkit-border-radius: 50%
  -moz-border-radius: 50%
  border-radius: 50%
  font-size: 26px
  color: #7E7E7E
  background-color: #fff
 
.navbar
  border-radius: 0
 
.navbar-nav
  float: none
  width: 400px
  margin: 10px auto
 
.navbar-nav > li
  text-align: center
  float: left
  width: 80px
  border-right: 1px solid #555
 
.navbar-nav > li.first
  border-left: 1px solid #555
 
.navbar-nav > li > a
  padding-top: 5px
  padding-bottom: 5px
 
#footer
  text-align: center
  padding: 40px 0
  background-color: #7E7E7E
 
#footer .logo
  margin-top: 0
 
.logo p
  margin: 0
  padding-top: 34px
  width: 100px
  height: 100px
  -webkit-border-radius: 50%
  -moz-border-radius: 50%
  border-radius: 50%
  font-size: 26px
  color: #7E7E7E
  font-weight: bold
  background-color: #fff
 
#footer .navbar-nav > li
  border-right: 1px solid #000
#footer .navbar-nav > li.first
  border-left: 1px solid #000
 
#footer .navbar-nav li a
  color: #fff
 
.sns-icon
  margin-top: 30px
 
.sns-icon li
  padding-left: 20px
.sns-icon li:hover
  color: #fff
 
.copy
  margin-top: 20px
 
.copy span
  color: #333
cs

templates/homepage.hamlet

1
-- 빈 파일
cs

config/settings.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Values formatted like "_env:ENV_VAR_NAME:default_value" can be overridden by the specified environment variable.
# See https://github.com/yesodweb/yesod/wiki/Configuration#overriding-configuration-values-with-environment-variables
 
static-dir:     "_env:STATIC_DIR:static"
host:           "_env:HOST:*4" # any IPv4 host
port:           "_env:PORT:3000" # NB: The port `yesod devel` uses is distinct from this value. Set the `yesod devel` port from the command line.
ip-from-header: "_env:IP_FROM_HEADER:false"
 
# Default behavior: determine the application root from the request headers.
# Uncomment to set an explicit approot
#approot:        "_env:APPROOT:http://localhost:3000"
 
# Optional values with the following production defaults.
# In development, they default to the inverse.
#
# detailed-logging: false
# should-log-all: false
# reload-templates: false
# mutable-static: false
# skip-combining: false
# auth-dummy-login : false
 
# NB: If you need a numeric value (e.g. 123) to parse as a String, wrap it in single quotes (e.g. "_env:PGPASS:'123'")
# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings
 
copyright: Copyright © 2018 Haskell Core Developer Group.com All Rights Reserved.
#analytics: UA-YOURCODE
cs


Foundation.hs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE RankNTypes #-}
 
module Foundation where
 
import Import.NoFoundation
import Text.Hamlet                 (hamletFile)
import Text.Jasmine                (minifym)
import Yesod.Core.Types            (Logger)
import Yesod.Default.Util          (addStaticContentExternal)
import qualified Yesod.Core.Unsafe as Unsafe
import qualified Data.CaseInsensitive as CI
import qualified Data.Text.Encoding as TE
 
-- | The foundation datatype for your application. This can be a good place to
-- keep settings and values requiring initialization before your application
-- starts running, such as database connections. Every handler will have
-- access to the data present here.
data App = App
    { appSettings    :: AppSettings
    , appStatic      :: Static -- ^ Settings for static file serving.
    , appHttpManager :: Manager
    , appLogger      :: Logger
    }
 
data MenuItem = MenuItem
    { menuItemLabel :: Text
    , menuItemRoute :: Route App
    , menuItemAccessCallback :: Bool
    }
 
data MenuTypes
    = NavbarLeft MenuItem
    | NavbarRight MenuItem
 
-- This is where we define all of the routes in our application. For a full
-- explanation of the syntax, please see:
-- http://www.yesodweb.com/book/routing-and-handlers
--
-- Note that this is really half the story; in Application.hs, mkYesodDispatch
-- generates the rest of the code. Please see the following documentation
-- for an explanation for this split:
-- http://www.yesodweb.com/book/scaffolding-and-the-site-template#scaffolding-and-the-site-template_foundation_and_application_modules
--
-- This function also generates the following type synonyms:
-- type Handler = HandlerT App IO
-- type Widget = WidgetT App IO ()
mkYesodData "App" $(parseRoutesFile "config/routes")
 
-- | A convenient synonym for creating forms.
type Form x = Html -> MForm (HandlerT App IO) (FormResult x, Widget)
 
-- Please see the documentation for the Yesod typeclass. There are a number
-- of settings which can be configured by overriding methods here.
instance Yesod App where
    -- Controls the base of generated URLs. For more information on modifying,
    -- see: https://github.com/yesodweb/yesod/wiki/Overriding-approot
    approot = ApprootRequest $ \app req ->
        case appRoot $ appSettings app of
            Nothing -> getApprootText guessApproot app req
            Just root -> root
 
    -- Store session data on the client in encrypted cookies,
    -- default session idle timeout is 120 minutes
    makeSessionBackend _ = Just <$> defaultClientSessionBackend
        120    -- timeout in minutes
        "config/client_session_key.aes"
 
    -- Yesod Middleware allows you to run code before and after each handler function.
    -- The defaultYesodMiddleware adds the response header "Vary: Accept, Accept-Language" and performs authorization checks.
    -- Some users may also want to add the defaultCsrfMiddleware, which:
    --   a) Sets a cookie with a CSRF token in it.
    --   b) Validates that incoming write requests include that token in either a header or POST parameter.
    -- To add it, chain it together with the defaultMiddleware: yesodMiddleware = defaultYesodMiddleware . defaultCsrfMiddleware
    -- For details, see the CSRF documentation in the Yesod.Core.Handler module of the yesod-core package.
    yesodMiddleware = defaultYesodMiddleware
 
    defaultLayout widget = do
        master <- getYesod
        mmsg <- getMessage
 
        mcurrentRoute <- getCurrentRoute
 
        -- Get the breadcrumbs, as defined in the YesodBreadcrumbs instance.
        (title, parents) <- breadcrumbs
 
        -- Define the menu items of the header.
        let menuItems =
                [ NavbarLeft $ MenuItem
                    { menuItemLabel = "Home"
                    , menuItemRoute = HomeR
                    , menuItemAccessCallback = True
                    }
                ]
 
        let navbarLeftMenuItems = [x | NavbarLeft x <- menuItems]
        let navbarRightMenuItems = [x | NavbarRight x <- menuItems]
 
        let navbarLeftFilteredMenuItems = [x | x <- navbarLeftMenuItems, menuItemAccessCallback x]
        let navbarRightFilteredMenuItems = [x | x <- navbarRightMenuItems, menuItemAccessCallback x]
 
        -- We break up the default layout into two components:
        -- default-layout is the contents of the body tag, and
        -- default-layout-wrapper is the entire page. Since the final
        -- value passed to hamletToRepHtml cannot be a widget, this allows
        -- you to use normal widget features in default-layout.
 
        pc <- widgetToPageContent $ do
            addStylesheet $ StaticR css_bootstrap_css
            addStylesheetRemote "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
            $(widgetFile "default-layout")
        withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
 
    -- Routes not requiring authenitcation.
    isAuthorized FaviconR _ = return Authorized
    isAuthorized RobotsR _ = return Authorized
    -- Default to Authorized for now.
    isAuthorized _ _ = return Authorized
 
    -- This function creates static content files in the static folder
    -- and names them based on a hash of their content. This allows
    -- expiration dates to be set far in the future without worry of
    -- users receiving stale content.
    addStaticContent ext mime content = do
        master <- getYesod
        let staticDir = appStaticDir $ appSettings master
        addStaticContentExternal
            minifym
            genFileName
            staticDir
            (StaticR . flip StaticRoute [])
            ext
            mime
            content
      where
        -- Generate a unique filename based on the content itself
        genFileName lbs = "autogen-" ++ base64md5 lbs
 
    -- What messages should be logged. The following includes all messages when
    -- in development, and warnings and errors in production.
    shouldLog app _source level =
        appShouldLogAll (appSettings app)
            || level == LevelWarn
            || level == LevelError
 
    makeLogger = return . appLogger
 
-- Define breadcrumbs.
instance YesodBreadcrumbs App where
  breadcrumb HomeR = return ("Home", Nothing)
  breadcrumb  _ = return ("home", Nothing)
 
-- This instance is required to use forms. You can modify renderMessage to
-- achieve customized and internationalized form validation messages.
instance RenderMessage App FormMessage where
    renderMessage _ _ = defaultFormMessage
 
-- Useful when writing code that is re-usable outside of the Handler context.
-- An example is background jobs that send email.
-- This can also be useful for writing code that works across multiple Yesod applications.
instance HasHttpManager App where
    getHttpManager = appHttpManager
 
unsafeHandler :: App -> Handler a -> IO a
unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
 
-- Note: Some functionality previously present in the scaffolding has been
-- moved to documentation in the Wiki. Following are some hopefully helpful
-- links:
--
-- https://github.com/yesodweb/yesod/wiki/Sending-email
-- https://github.com/yesodweb/yesod/wiki/Serve-static-files-from-a-separate-domain
-- https://github.com/yesodweb/yesod/wiki/i18n-messages-in-the-scaffolding
cs