Skip to content

Commit

Permalink
Add 29
Browse files Browse the repository at this point in the history
  • Loading branch information
stephencelis committed Sep 17, 2018
1 parent a87e372 commit 425efb8
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*:
# An HTML DSK
# An HTML DSL

## Exercises

Expand Down
4 changes: 4 additions & 0 deletions 0029-dsls-vs-templating-languages/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
12 changes: 12 additions & 0 deletions 0029-dsls-vs-templating-languages/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
init:
git submodule update --init
xcrun swift package --package-path Vendor/Stencil generate-xcodeproj

install-mm-xcodeproj:
@ls MyApp.xcodeproj/GeneratedModuleMap | xargs -n1 -I '{}' $(SUDO) mkdir -p "$(FRAMEWORKS_PATH)/{}.framework"
@ls MyApp.xcodeproj/GeneratedModuleMap | xargs -n1 -I '{}' $(SUDO) cp "./MyApp.xcodeproj/GeneratedModuleMap/{}/module.modulemap" "$(FRAMEWORKS_PATH)/{}.framework/module.map"

SDK_PATH = $(shell xcrun --show-sdk-path 2>/dev/null)
FRAMEWORKS_PATH = $(SDK_PATH)/System/Library/Frameworks
SUDO = sudo --prompt=$(SUDO_PROMPT)
SUDO_PROMPT = " 🔒 Please enter your password: "
14 changes: 14 additions & 0 deletions 0029-dsls-vs-templating-languages/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## [Point-Free](https://www.pointfree.co)

> #### This directory contains code from Point-Free Episode: [DSLs vs. Templating Languages](https://www.pointfree.co/episodes/ep29-dsls-vs-templating-languages)
>
> Templating languages are the most common way to render HTML in web frameworks, but we don’t think they are the best way. We compare templating languages to the DSL we previously built, and show that the DSL fixes many problems that templates have, while also revealing amazing compositions that were previously hidden.
### Getting Started

* Clone repo
* `cd` into this directory
* Run `make`
* Open `TemplatingLanguages.xcworkspace`
* Build `cmd+B`
* Open the playground
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@

import Stencil

struct _User {
let _id: Int
}

try Template(templateString: "{{ user.id | uppercase }}").render(["user": _User(_id: 42)])


let template = Template.init(stringLiteral: """
<ul>
{% for user in users %}
<li>{{ user.name }}</li>
{% endfor %}
</ul>
""")

//print(try template.render(["users": [["name": "Blob"], ["name": "Blob Jr."], ["name": "Blob Sr."]]]))

let name = "Blob"
//print(
// render(
// header([
// h1([.text(name)])
// ])
// )
//)

func greet(name: String) -> Node {
return header([
h1([.text(name.uppercased())])
])
}

print(render(greet(name: "Blob")))

struct User {
let name: String
let isAdmin: Bool
}

func adminDetail(user: User) -> [Node] {
guard user.isAdmin else { return [] }
return [
header([
h1([.text("Welcome admin: \(user.name)")])
])
]
}

func render(_ nodes: [Node]) -> String {
return nodes.map(render).joined()
}

print(render(adminDetail(user: User(name: "Blob Jr.", isAdmin: false))))

print(render(adminDetail(user: User(name: "Blob Sr.", isAdmin: true))))

func users(_ names: [String]) -> Node {
return ul(names.map(userItem))
}

func userItem(_ name: String) -> Node {
return li([.text(name)])
}

print(render(users(["Blob", "Blob Jr."])))

let template1 = """
<ul>
{% for user in users %}
{% include "userItem" user %}
{% endfor %}
</ul>
"""


class MemoryTemplateLoader: Loader {
func loadTemplate(name: String, environment: Environment) throws -> Template {
if name == "userItem" {
return Template(templateString: """
<li>{{ user }}</li>
""", environment: environment)
}

throw TemplateDoesNotExist(templateNames: [name], loader: self)
}
}

let environment = Environment(loader: MemoryTemplateLoader())

//print(try template1.render(["users": ["Blob", "Blob Jr."]]))

print(
try environment
.renderTemplate(
string: template1,
context: ["users": ["Blob", "Blob Jr."]]
)
)

func redacted(_ node: Node) -> Node {
switch node {
case let .el("img", attrs, children):
return .el(
"img",
attrs.filter { attrName, _ in attrName != "src" }
+ [("style", "background: black")],
children
)
case let .el(tag, attrs, children):
return .el(tag, attrs, children.map(redacted))
case let .text(string):
return .text(
string
.split(separator: " ")
.map { String.init(repeating: "", count: $0.count )}
.joined(separator: " ")
)
}
}


import WebKit
import PlaygroundSupport

let doc = header([
h1(["Point-Free"]),
p([id("blurb")], [
"Functional programming in Swift. ",
a([href("/about")], ["Learn more"]),
"!"
]),
img([src("https://pbs.twimg.com/profile_images/907799692339269634/wQEf0_2N_400x400.jpg"), width(64), height(64)])
])

let webView = WKWebView(frame: .init(x: 0, y: 0, width: 360, height: 480))
webView.loadHTMLString(render(redacted(doc)), baseURL: nil)
PlaygroundPage.current.liveView = webView


print(render(redacted(doc)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*:
# DSLs vs. Templating Languages

## Exercises

1.) In this episode we expressed a lot of HTML “views” as just plain functions from some data type into the Node type. In past episodes we saw that functions `(A) -> B` have both a `map` and `contramap` defined, the former corresponding to post-composition and the latter pre-composition. What does `map` and `contramap` represent in the context of an HTML view `(A) -> Node`?
*/
// TODO
/*:
2.) When building a website you often realize that you want to be able to reuse an outer “shell” of a view, and plug smaller views into it. For example, the header, nav and footer would consist of the “shell”, and then the content of your homepage, about page, contact page, etc. make up the inside. This is a kind of “view composition”, and most templating languages provide something like it (Rails calls it layouts, Stencil calls it inheritance).

Formulate what this form of view composition looks like when you think of views as just functions of the form `(A) -> Node`.
*/
// TODO
/*:
3.) In previous episodes on this series we have discussed the `<>` (diamond) operator. We have remarked that this operator comes up anytime we have a nice way of combining two values of the same type together into a third value of the same type, i.e. functions of the form `(A, A) -> A`.

Given two views of the form `v, w: (A) -> [Node]`, it is possible to combine them into one view. Define the diamond operator that performs this operation: `<>: ((A) -> [Node], (A) -> [Node]) -> (A) -> [Node]`.
*/
// TODO
/*:
4.) Right now any node is allowed to be embedded inside any other node, even though certain HTML semantics forbid that. For example, the list item tag `<li>` is only allowed to be embedded in unordered lists `<ul>` and ordered lists `<ol>`. We can’t enforce this property through the `Node` type, but we can do it through the functions we define for constructing tags. The technique uses something known as phantom types, and it’s similar to what we did in our Tagged episode. Here is a series of exercises to show how it works:

4a.) First define a new `ChildOf` type. It’s a struct that simply wraps a `Node` value, but most importantly it has a generic `<T>`. We will use this generic to control when certain nodes are allowed to be embedded inside other nodes.
*/
// TODO
/*:
4b.) Define two new types, `Ol` and `Ul`, that will act as the phantom types for `ChildOf`. Since we do not care about the contents of these types, they can just be simple empty enums.
*/
// TODO
/*:
4c.) Define a new protocol, `ContainsLi`, and make both `Ol` and `Ul` conform to it. Again, we don’t care about the contents of this protocol, it is only a means to tag `Ol` and `Ul` as having the property that they are allowed to contain `<li>` elements.
*/
// TODO
/*:
4d.) Finally, define three new tag functions `ol`, `ul` and `li` that allow you to nest `<li>`s inside `<ol>`s and `<ul>`s but prohibit you from putting `li`s in any other tags. You will need to use the types `ChildOf<Ol>`, `ChildOf<Ul>` and `ContainsLi` to accomplish this.
*/
// TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

public enum Node {
indirect case el(String, [(String, String)], [Node])
case text(String)
}

extension Node: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = .text(value)
}
}
public func header(_ children: [Node]) -> Node {
return .el("header", [], children)
}
public func h1(_ children: [Node]) -> Node {
return .el("h1", [], children)
}
public func ul(_ children: [Node]) -> Node {
return .el("ul", [], children)
}
public func li(_ children: [Node]) -> Node {
return .el("li", [], children)
}
public func p(_ attrs: [(String, String)], _ children: [Node]) -> Node {
return .el("p", attrs, children)
}
public func a(_ attrs: [(String, String)], _ children: [Node]) -> Node {
return .el("a", attrs, children)
}
public func img(_ attrs: [(String, String)]) -> Node {
return .el("img", attrs, [])
}

public func id(_ value: String) -> (String, String) {
return ("id", value)
}
public func href(_ value: String) -> (String, String) {
return ("href", value)
}
public func src(_ value: String) -> (String, String) {
return ("src", value)
}
public func width(_ value: Int) -> (String, String) {
return ("width", "\(value)")
}
public func height(_ value: Int) -> (String, String) {
return ("height", "\(value)")
}

public func render(_ node: Node) -> String {
switch node {
case let .el(tag, attrs, children):
let formattedAttrs = attrs
.map { key, value in "\(key)=\"\(value)\"" }
.joined(separator: " ")
let formattedChildren = children.map(render).joined()
return "<\(tag) \(formattedAttrs)>\(formattedChildren)</\(tag)>"
case let .text(string):
return string
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='macos' display-mode='rendered' executeOnSourceChanges='false'>
<pages>
<page name='01-episode'/>
<page name='02-exercises'/>
</pages>
</playground>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ This repository is the home of code written on episodes of
1. [Domain Specific Languages: Part 1](0026-edsls-pt1)
1. [Domain Specific Languages: Part 2](0027-edsls-pt2)
1. [An HTML DSL](0028-html-dsl)
1. [DSLs vs. Templating Languages](0029-dsls-vs-templating-languages)

0 comments on commit 425efb8

Please sign in to comment.