diff --git a/0028-html-dsl/HTML.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift b/0028-html-dsl/HTML.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift index a084f3f6..2697cb14 100644 --- a/0028-html-dsl/HTML.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift +++ b/0028-html-dsl/HTML.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift @@ -1,5 +1,5 @@ /*: - # An HTML DSK + # An HTML DSL ## Exercises diff --git a/0029-dsls-vs-templating-languages/.gitignore b/0029-dsls-vs-templating-languages/.gitignore new file mode 100644 index 00000000..02c08753 --- /dev/null +++ b/0029-dsls-vs-templating-languages/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj diff --git a/0029-dsls-vs-templating-languages/Makefile b/0029-dsls-vs-templating-languages/Makefile new file mode 100644 index 00000000..7a65bed3 --- /dev/null +++ b/0029-dsls-vs-templating-languages/Makefile @@ -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: " diff --git a/0029-dsls-vs-templating-languages/README.md b/0029-dsls-vs-templating-languages/README.md new file mode 100644 index 00000000..f62a396e --- /dev/null +++ b/0029-dsls-vs-templating-languages/README.md @@ -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 diff --git a/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/01-episode.xcplaygroundpage/Contents.swift b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/01-episode.xcplaygroundpage/Contents.swift new file mode 100644 index 00000000..0d88fd9f --- /dev/null +++ b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/01-episode.xcplaygroundpage/Contents.swift @@ -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: """ + +""") + +//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 = """ + +""" + + +class MemoryTemplateLoader: Loader { + func loadTemplate(name: String, environment: Environment) throws -> Template { + if name == "userItem" { + return Template(templateString: """ +
  • {{ user }}
  • +""", 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))) diff --git a/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift new file mode 100644 index 00000000..8ad14e5f --- /dev/null +++ b/0029-dsls-vs-templating-languages/TemplatingLanguages.playground/Pages/02-exercises.xcplaygroundpage/Contents.swift @@ -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 `
  • ` is only allowed to be embedded in unordered lists `