-
Notifications
You must be signed in to change notification settings - Fork 4
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
Showing
1 changed file
with
26 additions
and
179 deletions.
There are no files selected for viewing
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,218 +1,65 @@ | ||
# SwiftHttp | ||
|
||
An awesome Swift HTTP library to rapidly setup the communication layer with API endpoints. | ||
|
||
```swift | ||
import SwiftHttp | ||
|
||
print(html) | ||
``` | ||
|
||
An awesome Swift HTTP library to rapidly create communication layers with API endpoints. | ||
|
||
## Install | ||
|
||
You can simply use `SwiftHtml` as a dependency via the Swift Package Manager: | ||
|
||
```swift | ||
.package(url: "https://github.com/binarybirds/swift-html", from: "1.6.0"), | ||
.package(url: "https://github.com/binarybirds/swift-http", from: "1.0.0"), | ||
``` | ||
|
||
Add the `SwiftHtml` product from the `swift-html` package as a dependency to your target: | ||
Add the `SwiftHttp` product from the `swift-http` package as a dependency to your target: | ||
|
||
```swift | ||
.product(name: "SwiftHtml", package: "swift-html"), | ||
.product(name: "SwiftHttp", package: "swift-http"), | ||
``` | ||
|
||
Import the framework: | ||
|
||
```swift | ||
import SwiftHtml | ||
import SwiftHttp | ||
``` | ||
|
||
That's it. | ||
|
||
|
||
## Creating custom tags | ||
|
||
You can define your own custom tags by subclassing the `Tag` or `EmptyTag` class. | ||
|
||
You can follow the same pattern if you take a look at the core tags. | ||
|
||
```swift | ||
open class Div: Tag { | ||
|
||
} | ||
|
||
// <div></div> - standard tag | ||
|
||
open class Br: EmptyTag { | ||
|
||
} | ||
// <br> - no closing tag | ||
|
||
``` | ||
|
||
By default the name of the tag is automatically derived from the class name (lowercased), but you can also create your own tag type & name by overriding the `createNode()` class function. | ||
|
||
```swift | ||
open class LastBuildDate: Tag { | ||
|
||
open override class func createNode() -> Node { | ||
Node(type: .standard, name: "lastBuildDate") | ||
} | ||
} | ||
|
||
// <lastBuildDate></lastBuildDate> - standard tag with custom name | ||
``` | ||
|
||
It is also possible to create tags with altered content or default attributes. | ||
|
||
```swift | ||
open class Description: Tag { | ||
|
||
public init(_ contents: String) { | ||
super.init() | ||
setContents("<![CDATA[" + contents + "]]>") | ||
} | ||
} | ||
// <description><![CDATA[lorem ipsum]]></description> - content wrapped in CDATA | ||
|
||
open class Rss: Tag { | ||
|
||
public init(@TagBuilder _ builder: () -> [Tag]) { | ||
super.init(builder()) | ||
setAttributes([ | ||
.init(key: "version", value: "2.0"), | ||
]) | ||
} | ||
} | ||
// <rss version="2.0">...</rss> - tag with a default attribute | ||
``` | ||
|
||
## Attribute management | ||
## Basic usage | ||
|
||
You can set, add or delete the attributes of a given tag. | ||
It is really easy to setup a communication layer with an API endpoint. | ||
|
||
```swift | ||
Leaf("example") | ||
// set (override) the current attributes | ||
.setAttributes([ | ||
.init(key: "a", value: "foo"), | ||
.init(key: "b", value: "bar"), | ||
.init(key: "c", value: "baz"), | ||
]) | ||
// add a new attribute using a key & value | ||
.attribute("foo", "example") | ||
// add a new flag attribute (without a value) | ||
.flagAttribute("bar") | ||
// delete an attribute by using a key | ||
.deleteAttribute("b") | ||
|
||
// <leaf a="foo" c="baz" foo="example" bar></leaf> | ||
``` | ||
|
||
You can also manage the class atrribute through helper methods. | ||
|
||
```swift | ||
Span("foo") | ||
// set (override) class values | ||
.class("a", "b", "c") | ||
// add new class values | ||
.class(add: ["d", "e", "f"]) | ||
// add new class value if the condition is true | ||
.class(add: "b", true) | ||
/// remove multiple class values | ||
.class(remove: ["b", "c", "d"]) | ||
/// remove a class value if the condition is true | ||
.class(remove: "e", true) | ||
|
||
// <span class="a f"></span> | ||
``` | ||
|
||
You can create your own attribute modifier via an extension. | ||
|
||
```swift | ||
public extension Guid { | ||
|
||
func isPermalink(_ value: Bool = true) -> Self { | ||
attribute("isPermalink", String(value)) | ||
} | ||
} | ||
``` | ||
|
||
There are other built-in type-safe attribute modifiers available on tags. | ||
|
||
|
||
## Composing tags | ||
|
||
You can come up with your own `Tag` composition system by introducing a new protocol. | ||
|
||
```swift | ||
protocol TagRepresentable { | ||
|
||
func build() -> Tag | ||
} | ||
|
||
struct ListComponent: TagRepresentable { | ||
import SwiftHttp | ||
|
||
let items: [String] | ||
|
||
init(_ items: [String]) { | ||
self.items = items | ||
} | ||
|
||
@TagBuilder | ||
func build() -> Tag { | ||
Ul { | ||
items.map { Li($0) } | ||
} | ||
} | ||
struct Todo: Codable { | ||
let id: Int | ||
let title: String | ||
let completed: Bool | ||
} | ||
|
||
let tag = ListComponent(["a", "b", "c"]).build() | ||
``` | ||
|
||
This way it is also possible to extend the `TagBuilder` to support the new protocol. | ||
struct TodoApi: HttpCodablePipelineCollection { | ||
|
||
```swift | ||
extension TagBuilder { | ||
let client: HttpClient = UrlSessionHttpClient(log: true) | ||
let apiBaseUrl = HttpUrl(host: "jsonplaceholder.typicode.com") | ||
|
||
static func buildExpression(_ expression: TagRepresentable) -> Tag { | ||
expression.build() | ||
} | ||
|
||
static func buildExpression(_ expression: TagRepresentable) -> [Tag] { | ||
[expression.build()] | ||
} | ||
|
||
static func buildExpression(_ expression: [TagRepresentable]) -> [Tag] { | ||
expression.map { $0.build() } | ||
} | ||
|
||
static func buildExpression(_ expression: [TagRepresentable]) -> Tag { | ||
GroupTag { | ||
expression.map { $0.build() } | ||
} | ||
} | ||
func list() async throws -> [Todo] { | ||
try await decodableRequest(executor: client.dataTask, | ||
url: apiBaseUrl.path("todos"), | ||
method: .get) | ||
} | ||
} | ||
``` | ||
|
||
Sometimes you'll need extra parameters for the build function, so you have to call the build method by hand. | ||
// api usage | ||
let todos = try await api.list() | ||
|
||
In those cases it is recommended to introduce a `render` function instead of using build. | ||
// curl log | ||
// curl "https://jsonplaceholder.typicode.com/todos/" | ||
|
||
```swift | ||
|
||
let tag = WebIndexTemplate(ctx) { | ||
ListComponent(["a", "b", "c"]) | ||
.render(req) | ||
} | ||
.render(req) | ||
``` | ||
|
||
If you want to create a lightweight template engine for the [Vapor](https://vapor.codes/) web framework using SwiftHtml, you can see a working example inside the [Feather CMS core](https://github.com/FeatherCMS/feather-core) repository. | ||
|
||
The HttpClient provides the executors to perform data, download or upload tasks. | ||
|
||
## Credits & references | ||
You can create decodable, encodable, codable or raw request when using a codable pipeline collection. | ||
|
||
- [HTML Reference](https://www.w3schools.com/tags/default.asp) |