leaf 3 docs

This commit is contained in:
tanner0101 2018-08-08 18:08:47 -04:00
parent 35f2686f6f
commit cb374e0994
14 changed files with 237 additions and 197 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/.couscous
.sass-cache
.DS_Store
leaf-pygment/dist

View File

@ -1,54 +1,3 @@
# Custom Tags
You can extend Leaf to provide your own tags that add custom functionality. To demonstrate this, let's look at a basic example by recreating `#uppercase` together. This tag will take one argument, which is the string to uppercase.
When working with custom tags, there are four important things to know:
1. You should call `requireParameterCount()` with the number of parameters you expect to receive. This will throw an error if your tag is used incorrectly.
2. If you do or do not require a body, you should use either `requireBody()` or `requireNoBody()`. Again, this will throw an error if your tag is used incorrectly.
3. You can read individual parameters using the `parameters` array. Each parameter will be of type `LeafData`, which you can convert to concrete data types using properties such as `.string`, `.dictionary`, and so on.
4. You must return a `Future<LeafData?>` containing what should be rendered. In the example below we wrap the resulting uppercase string in a `LeafData` string, then send that back wrapped in a future.
Heres example code for a `CustomUppercase` Leaf tag:
```swift
import Async
import Leaf
public final class CustomUppercase: Leaf.LeafTag {
public init() {}
public func render(parsed: ParsedTag, context: LeafContext, renderer: LeafRenderer) throws -> Future<LeafData?> {
// ensure we receive precisely one parameter
try parsed.requireParameterCount(1)
// pull out our lone parameter as a string then uppercase it, or use an empty string
let string = parsed.parameters[0].string?.uppercased() ?? ""
// send it back wrapped in a LeafData
return Future(.string(string))
}
}
```
We can now register this Tag in our `configure.swift` file with:
```swift
services.register { container -> LeafConfig in
// take a copy of Leaf's default tags
var tags = defaultTags
// add our custom tag
tags["customuppercase"] = CustomUppercase()
// find the location of our Resources/Views directory
let directoryConfig = try container.make(DirectoryConfig.self, for: LeafRenderer.self)
let viewsDirectory = directoryConfig.workDir + "Resources/Views"
// put all that into a new Leaf configuration and return it
return LeafConfig(tags: tags, viewsDir: viewsDirectory)
}
```
Once that is complete, you can use `#customuppercase(some_variable)` to run your custom code.
> Note: Use of non-alphanumeric characters in tag names is **strongly discouraged** and may be disallowed in future versions of Leaf.
Coming soon.

View File

@ -1,73 +1,72 @@
!!! warning
Leaf 3.0 is still in beta. Some documentation may be missing or out of date.
# Leaf
Leaf is a templating language that integrates with Futures, Reactive Streams and Codable. This section outlines how to import the Leaf package into a Vapor project.
Leaf is a powerful templating language with Swift-inspired syntax. You can use it to generate dynamic HTML pages for a front-end website or generate rich emails to send from an API.
## Example Folder Structure
## Package
```
Hello
├── Package.resolved
├── Package.swift
├── Public
├── Resources
│   ├── Views
│   │   └── hello.leaf
├── Public
│   ├── images (images resources)
│   ├── styles (css resources)
├── Sources
│   ├── App
│   │   ├── boot.swift
│   │   ├── configure.swift
│   │   └── routes.swift
│   └── Run
│   └── main.swift
├── Tests
│   ├── AppTests
│   │   └── AppTests.swift
│   └── LinuxMain.swift
└── LICENSE
```
## Adding Leaf to your project
The easiest way to use Leaf with Vapor is to include the Leaf repository as a dependency in Package.swift:
The first step to using Leaf is adding it as a dependency to your project in your SPM package manifest file.
```swift
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "project1",
name: "MyApp",
dependencies: [
// 💧 A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", .branch("beta")),
.package(url: "https://github.com/vapor/leaf.git", .branch("beta")),
/// Any other dependencies ...
.package(url: "https://github.com/vapor/leaf.git", from: "3.0.0"),
],
targets: [
.target(
name: "App",
dependencies: ["Vapor", "Leaf"]
),
.target(name: "App", dependencies: ["Leaf", ...]),
.target(name: "Run", dependencies: ["App"]),
.testTarget(name: "AppTests", dependencies: ["App"]),
]
)
```
The Leaf package adds Leaf to your project, but to configure it for use you must modify configure.swift:
## Configure
1. Add `import Leaf` to the top of the file so that Leaf is available to use. You will also need to add this to any file that will render templates.
2. Add `try services.register(LeafProvider())` to the `configure()` function so that routes may render Leaf templates as needed.
Once you have added the package to your project, you can configure Vapor to use it. This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift).
```swift
import Leaf
try services.register(LeafProvider())
```
If your application supports multiple view renderers, you may need to specify that you would like to use Leaf.
```swift
config.prefer(LeafRenderer.self, for: ViewRenderer.self)
```
## Folder Structure
Once you have configured Leaf, you will need to ensure you have a `Views` folder to store your `.leaf` files in. By default, Leaf expects the views folder to be a `./Resources/Views` relative to your project's root.
You will also likely want to enable Vapor's [`FileMiddleware`](https://api.vapor.codes/vapor/latest/Vapor/Classes/FileMiddleware.html) to serve files from your `/Public` folder.
```
VaporApp
├── Package.swift
├── Resources
│   ├── Views
│   │   └── hello.leaf
├── Public
│   ├── images (images resources)
│   ├── styles (css resources)
└── Sources
   └── ...
```
## Syntax Highlighting
You may also wish to install one of these third-party packages that provide support for syntax highlighting in Leaf templates.
### Sublime
Install the package [Leaf](https://packagecontrol.io/packages/Leaf) from package control.
### Atom
[language-leaf](https://atom.io/packages/language-leaf) by ButkiewiczP
@ -85,3 +84,23 @@ There appears to be a way to [make Xcode file associations persist](http://stack
### CLion & AppCode
Some preliminary work has been done to implement a Leaf Plugin for CLion & AppCode but lack of skill and interest in Java has slowed progress! If you have IntelliJ SDK experience and want to help with this, message Tom Holland on [Vapor Slack](http://vapor.team)
## Rendering a View
Now that Leaf is configured, let's render your first template. Inside of the `Resources/Views` folder, create a new file called `hello.leaf` with the following contents:
```leaf
Hello, #(name)!
```
Then, register a route (usually done in `routes.swift` or a controller) to render the view.
```swift
import Leaf
router.get("hello") { req -> Future<View> in
return try req.view().render("hello", ["name": "Leaf"])
}
```
Open your browser and visit `/hello`. You should see `Hello, Leaf!`. Congratulations on rendering your first Leaf view!

View File

@ -1,110 +1,76 @@
# Basics
# Leaf Overview
Welcome to Leaf. Leaf's goal is to be a simple templating language that can make generating views easier. There are plenty of great templating languages, so use what's best for you maybe that's Leaf! The goals of Leaf are:
Leaf is a powerful templating language with Swift-inspired syntax. You can use it to generate dynamic HTML pages for a front-end website or generate rich emails to send from an API.
- Small set of strictly enforced rules
- Consistency
- Parser first mentality
- Extensibility
- Asynchronous and reactive
## Rendering a template
Once you have Leaf installed, you should create a directory called “Resources” inside your project folder, and inside that create another directory called “Views”. This Resources/Views directory is the default location for Leaf templates, although you can change it if you want.
Firstly, import Leaf to routes.swift
```swift
import Leaf
```
Then, to render a basic Leaf template from a route, add this code:
```swift
router.get { req -> Future<View> in
let leaf = try req.make(LeafRenderer.self)
let context = [String: String]()
return try leaf.render("home", context)
}
```
That will load home.leaf in the Resources/Views directory and render it. The `context` dictionary is there to let you provide custom data to render inside the template, but you might find it easier to use codable structs instead because they provide extra type safety. For example:
```swift
struct HomePage: Codable {
var title: String
var content: String
}
```
### Async
Leaf's engine is completely reactive, supporting both streams and futures. One of the only ones of its kind.
When working with Future results, simply pass the `Future` in your template context.
Streams that carry an encodable type need to be encoded before they're usable within Leaf.
```swift
struct Profile: Encodable {
var friends: EncodableStream
var currentUser: Future<User>
}
```
In the above context, the `currentUser` variable in Leaf will behave as being a `User` type. Leaf will not read the user Future if it's not used during rendering.
`EncodableStream` will behave as an array of LeafData, only with lower memory impact and better performance. It is recommended to use `EncodableStream` for (large) database queries.
```
Your name is #(currentUser.name).
#for(friend in friends) {
#(friend.name) is a friend of you.
}
```
This guide will give you an overview of Leaf's syntax and the available tags.
## Template syntax
### Structure
Here is an example of a basic Leaf tag usage.
```leaf
There are #count(users) users.
```
Leaf tags are made up of four elements:
- Token: `#` is the token
- Name: A `string` that identifies the tag
- Parameter List: `()` May accept 0 or more arguments
- Body (optional): `{}` Must be separated from the parameter list by a space
- Token (`#`): This signals the leaf parser to begin looking for a tag.
- Name (`count`): that identifies the tag.
- Parameter List (`(users)`): May accept zero or more arguments.
- Body (none): An optional body can be supplied to some tags. This is similar to Swift's trailing-closure syntax.
There can be many different usages of these four elements depending on the tag's implementation. Let's look at a few examples of how Leaf's built-in tags might be used:
- `#()`
- `#(variable)`
- `#embed("template")`
- `#set("title") { Welcome to Vapor }`
- `#count(friends)`
- `#for(friend in friends) { <li>#(friend.name)</li> }`
```leaf
#(variable)
#embed("template")
#set("title") { Welcome to Vapor }
#count(friends)
#for(friend in friends) { <li>#(friend.name)</li> }
```
Leaf also supports many expressions you are familiar with in Swift.
### Working with context
- `+`
- `>`
- `==`
- `||`
- etc.
In our Swift example from earlier, we used an empty `[String: String]` dictionary for context, which passes no custom data to Leaf. To try rendering content, use this code instead:
```leaf
#if(1 + 1 == 2) {
Hello!
}
```
## Context
In the example from [Getting Started](./getting-started.md), we used a `[String: String]` dictionary to pass data to Leaf. However, you can pass anything that conforms to `Encodable`. It's actually preferred to use `Encodable` structs since `[String: Any]` is not supported.
```swift
let context = ["title": "Welcome", "message": "Vapor and Leaf work hand in hand"]
return try leaf.make("home", context)
struct WelcomeContext: Encodable {
var title: String
var number: Int
}
return try req.view().make("home", WelcomeContext(title: "Hello!", number: 42))
```
That will expose `title` and `message` to our Leaf template, which can then be used inside tags. For example:
```
```leaf
<h1>#(title)</h1>
<p>#(message)</p>
<p>#(number)</p>
```
### Checking conditions
## Usage
Here are some common Leaf usage examples.
### Conditions
Leaf is able to evaluate a range of conditions using its `#if` tag. For example, if you provide a variable it will check that variable exists in its context:
```
```leaf
#if(title) {
The title is #(title)
} else {
@ -114,7 +80,7 @@ Leaf is able to evaluate a range of conditions using its `#if` tag. For example,
You can also write comparisons, for example:
```
```leaf
#if(title == "Welcome") {
This is a friendly web page.
} else {
@ -124,7 +90,7 @@ You can also write comparisons, for example:
If you want to use another tag as part of your condition, you should omit the `#` for the inner tag. For example:
```
```leaf
#if(lowercase(title) == "welcome") {
This is a friendly web page.
} else {
@ -132,6 +98,17 @@ If you want to use another tag as part of your condition, you should omit the `#
}
```
Just like in Swift, you can also use `else if` statement.s
```leaf
#if(title == "Welcome") {
This is a friendly web page.
} else if (1 == 2) {
What?
} else {
No strangers allowed!
}
```
### Loops
@ -143,7 +120,7 @@ let context = ["team": ["Malcolm", "Kaylee", "Jayne"]]
We could then loop over them in Leaf like this:
```
```leaf
#for(name in team) {
<p>#(name) is in the team.</p>
}
@ -157,7 +134,7 @@ Leaf provides some extra variables inside a `#for` loop to give you more informa
Here's how we could use a loop variable to print just the first name in our array:
```
```leaf
#for(name in team) {
#if(isFirst) { <p>#(name) is first!</p> }
}
@ -169,7 +146,7 @@ Leafs `#embed` tag allows you to copy the contents of one template into anoth
Embedding is useful for copying in a standard piece of content, for example a page footer or advert code:
```
```leaf
#embed("footer")
```
@ -179,9 +156,9 @@ Using this approach, you would construct a child template that fills in its uniq
For example, you might create a child.leaf template like this:
```
```leaf
#set("body") {
<p>Welcome to Vapor!</p>
<p>Welcome to Vapor!</p>
}
#embed("master")
@ -189,29 +166,51 @@ For example, you might create a child.leaf template like this:
That configures one item of context, `body`, but doesnt display it directly. Instead, it embeds master.leaf, which can render `body` along with any other context variables passed in from Swift. For example, master.leaf might look like this:
```
```leaf
<html>
<head><title>#(title)</title></head>
<body>#get(body)</body>
<head>
<title>#(title)</title>
</head>
<body>#get(body)</body>
</html>
```
When given the context `["title": "Hi there!"]`, child.leaf will render as follows:
```
```html
<html>
<head><title>Hi there!</title></head>
<body><p>Welcome to Vapor!</p></body>
<head>
<title>Hi there!</title>
</head>
<body><p>Welcome to Vapor!</p></body>
</html>
```
### Comments
You can write single or multiline comments with Leaf. They will be discarded when rendering the view.
```leaf
#// Say hello to the user
Hello, #(name)!
```
Multi-line comments are opened with `#/*` and closed with `*/`.
```leaf
#/*
Say hello to the user
*/
Hello, #(name)!
```
### Other tags
#### `#capitalize`
The `#capitalize` tag uppercases the first letter of any string. For example, “taylor” will become “Taylor”.
```
```leaf
#capitalize(name)
```
@ -219,7 +218,7 @@ The `#capitalize` tag uppercases the first letter of any string. For example,
The `#contains` tag accepts an array and a value as its two parameters, and returns true if the array in parameter one contains the value in parameter two. For example, given the array `team`:
```
```leaf
#if(contains(team, "Jayne")) {
You're all set!
} else {
@ -231,7 +230,7 @@ The `#contains` tag accepts an array and a value as its two parameters, and retu
The `#count` tag returns the number of items in an array. For example:
```
```leaf
Your search matched #count(matches) pages.
```
@ -239,7 +238,7 @@ Your search matched #count(matches) pages.
The `#lowercase` tag lowercases all letters in a string. For example, “Taylor” will become “taylor”.
```
```leaf
#lowercase(name)
```
@ -247,6 +246,6 @@ The `#lowercase` tag lowercases all letters in a string. For example, “Taylor
The `#uppercase` tag uppercases all letters in a string. For example, “Taylor” will become “TAYLOR”.
```
```leaf
#uppercase(name)
```

View File

@ -52,7 +52,7 @@ pages:
- 'Message': 'http/message.md'
- 'Leaf':
- 'Getting Started': 'leaf/getting-started.md'
- 'Basics': 'leaf/basics.md'
- 'Overview': 'leaf/overview.md'
- 'Custom tags': 'leaf/custom-tags.md'
- 'Logging':
- 'Getting Started': 'logging/getting-started.md'

View File

@ -5,6 +5,8 @@ COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt && rm -rf $HOME/.cache/pip
RUN cd leaf-pygment && ./compile.sh
RUN pip install leaf-pygment/dist/leaf-0.1.0-dev.tar.gz
RUN cd 3.0 && mkdocs build
FROM nginx:1.13.12-alpine as production-stage

5
leaf-pygment/MANIFEST Normal file
View File

@ -0,0 +1,5 @@
# file GENERATED by distutils, do NOT edit
README
setup.py
leaf/__init__.py
leaf/lexer.py

1
leaf-pygment/README Normal file
View File

@ -0,0 +1 @@
Provides Leaf syntax highlighting for Pygment.

1
leaf-pygment/compile.sh Executable file
View File

@ -0,0 +1 @@
python setup.py sdist

View File

Binary file not shown.

View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
"""
pygments.lexers.leaf
~~~~~~~~~~~~~~~~~~~~
Lexer for Leaf markup.
"""
import re
from pygments.lexer import RegexLexer, ExtendedRegexLexer, include, bygroups, default, using
from pygments.token import Text, Comment, Operator, Keyword, Name, String, Punctuation, Number
__all__ = ['LeafLexer']
class LeafLexer(RegexLexer):
name = 'Leaf'
aliases = ['leaf']
filenames = ['*.leaf']
mimetypes = ['text/leaf', 'application/leaf']
tokens = {
'root': [
(r'\n', Comment),
(r'\s+', Comment),
(r'else', Keyword),
(r'#\/\/.*', String),
(r'#\/\*[^\*]*\*\/', String),
(r'if\(|if\ \(', Keyword, 'expression'),
(r'(\#)([^\(]*)(\()', bygroups(Keyword, Keyword, Punctuation), 'expression'),
(r'\{', Name.Builtin.Pseudo),
(r'\}', Name.Builtin.Pseudo),
(r'[^\#\}]+', Comment),
],
'expression': [
(r'\s+', Text),
(r'(")([^"]+)(")', String),
(r'[\d]+', Number.Int),
(r'in', Keyword),
(r',', Text),
(r'[\=\!\|\&\+\-\*\%]+', Operator),
(r'[\w]+(?=\()', Name.Builtin.Pseudo),
(r'[\w]+', Text),
(r'\(', Text, '#push'),
(r'\)', Text, '#pop'),
]
}

BIN
leaf-pygment/leaf/lexer.pyc Normal file

Binary file not shown.

16
leaf-pygment/setup.py Normal file
View File

@ -0,0 +1,16 @@
from distutils.core import setup
setup (
name='leaf',
version='0.1.0-dev',
url='https://github.com/vapor/leaf',
author='tanner0101',
author_email='me@tanner.xyz',
packages=['leaf'],
entry_points =
"""
[pygments.lexers]
leaf = leaf.lexer:LeafLexer
""",
)