-
Notifications
You must be signed in to change notification settings - Fork 299
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a87e372
commit 425efb8
Showing
11 changed files
with
299 additions
and
1 deletion.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
0028-html-dsl/HTML.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
/*: | ||
# An HTML DSK | ||
# An HTML DSL | ||
|
||
## Exercises | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
/*.xcodeproj |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: " |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
143 changes: 143 additions & 0 deletions
143
...languages/TemplatingLanguages.playground/Pages/01-episode.xcplaygroundpage/Contents.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) |
38 changes: 38 additions & 0 deletions
38
...nguages/TemplatingLanguages.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
61 changes: 61 additions & 0 deletions
61
0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Sources/Util.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
0029-dsls-vs-templating-languages/TemplatingLanguages.playground/contents.xcplayground
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
10 changes: 10 additions & 0 deletions
10
0029-dsls-vs-templating-languages/TemplatingLanguages.xcworkspace/contents.xcworkspacedata
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
...emplating-languages/TemplatingLanguages.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters