Skip to content

Commit

Permalink
Updated loop, required tags and other minor improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
RussBaz committed Mar 7, 2024
1 parent 69f8d5e commit 81db8ea
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 95 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Reparse - HTML Server Side Templating Super Powers in Swift (Experimental)

NOTE: The master branch is way ahead of the latest release as it includes many fixes and additional features. Please use it while I am preparing for the next release.

## Three Core Concepts

1. Super Powerful and Flexible Syntax
Expand All @@ -20,7 +18,7 @@ Here is an example of the `Reparse` syntax as used in the bundled `Example` proj
<main>
<h1>
Hello
<r-include name="components.world" r-if="!(context?.isEmpty ?? true)">
<r-include name="components.world" r-if="!context.isEmpty">
Ultra Heroes!
</r-include>
<r-block r-else> World?</r-block>
Expand All @@ -47,7 +45,7 @@ Here is an example of the `Reparse` syntax as used in the bundled `Example` proj
If you would like to use it in your own project, firstly, add it to your package dependencies like this:

```swift
.package(url: "https://github.com/RussBaz/experimental-reparse-html.git", from: "0.0.6"),
.package(url: "https://github.com/RussBaz/experimental-reparse-html.git", from: "0.0.7"),
```

Then add the `ReparseRuntime` as a dependency to your target like this:
Expand Down Expand Up @@ -133,6 +131,10 @@ All the options you can pass are the same except the location and destination ar

All special attributes and tags will be removed by the compilation and must not appear in the output of the render function. If they do, then it is a bug.

### String Interpolation

You can use the standard swift syntax for string interpolation `\(expression)` to insert any swift expression into a text part of the html template - inside the attribute values and the text between tags.

### Control Attributes

There are a few types of control attributes and can be separated in 3 groups:
Expand Down Expand Up @@ -166,6 +168,7 @@ Optionally, you can save the result of the condition to a different variable usi

- **`<r-extend name="template-name" />`** (must be before any tag other than r-require) to wrap the current template into a default slot of the specified template
- **`<r-require label="optional-label" name="var-name" type="var-type" default="optional-default" />`** (must be before any tag other than r-extend) to define a variable that must be passed into the template from the caller
- **`<r-require label="optional-label" name="var-name" type="var-type" default="optional-default" mutable />`** or if you need to remap it to a mutable variable of the same name
- **`<r-include name="template-name" />`** to include another template or
- **`<r-include name="template-name"> default slot </r-include>`** to include a template with a default slot provided
- **`<r-block> some data </r-block>`** to group some part of template, e.g. wrap some text with it and now you can apply control attributes to it.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/AST.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public indirect enum AST {
case conditional(name: String?, check: String, type: ConditionType, contents: ASTStorage)
case loop(forEvery: String, name: String?, contents: ASTStorage)
case modifiers(applying: [AttributeModifier], tag: TagType)
case requirement(name: String, type: String, label: String?, value: String?)
case requirement(name: String, type: String, label: String?, value: String?, mutable: Bool)
case eval(line: String)
case value(of: String, defaultValue: String?)
case assignment(name: String, line: String)
Expand Down
11 changes: 10 additions & 1 deletion Sources/Core/PageProperties.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,22 @@ public final class PageProperties {
}

let name: String
let rootPath: String
let fileExtension: String
let enumName: String
var lines: [LineDef] = []
var conditionTags: [String] = []
var defaultValues: [String: String] = [:]
var mutableParameters: [String] = []
var modifiersPresent = false
var protocols: [ProtocolCompliance] = []

init(name: String, fileExtension: String, enumName: String, protocols: [ProtocolCompliance]) {
init(name: String, rootPath: String, fileExtension: String, enumName: String, protocols: [ProtocolCompliance]) {
self.name = name
self.fileExtension = fileExtension
self.enumName = enumName
self.protocols = protocols
self.rootPath = rootPath
}

func clear() {
Expand Down Expand Up @@ -78,6 +81,12 @@ public final class PageProperties {
defaultValues[name] = value
}

func appendMutable(name: String) {
guard !mutableParameters.contains(name) else { return }

mutableParameters.append(name)
}

func asText(at indenation: Int = 0) -> String {
asLines(at: indenation).joined(separator: "\n")
}
Expand Down
14 changes: 8 additions & 6 deletions Sources/Core/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -284,14 +284,15 @@ extension Parser {
}

func isConditional(_ attrs: ControlAttrs) -> AST? {
switch (attrs.ifLine, attrs.ifElseLine, attrs.elseLine, attrs.tagLine) {
case let (.some(line), _, _, tag):
let tag = attrs.tagLine

return if let line = attrs.ifLine {
.conditional(name: tag, check: line, type: .ifType, contents: ASTStorage())
case let (.none, .some(line), _, tag):
} else if let line = attrs.ifElseLine {
.conditional(name: tag, check: line, type: .elseIfType, contents: ASTStorage())
case let (.none, .none, true, tag):
} else if attrs.elseLine {
.conditional(name: tag, check: "", type: .elseType, contents: ASTStorage())
case (.none, .none, false, _):
} else {
nil
}
}
Expand Down Expand Up @@ -387,8 +388,9 @@ extension Parser {

let label = attributes.find("label")
let defaultValue = attributes.find("default")
let mutable = attributes.find("mutable") != nil

ast.append(node: .requirement(name: name, type: type, label: label, value: defaultValue))
ast.append(node: .requirement(name: name, type: type, label: label, value: defaultValue, mutable: mutable))
}

func openSetTag(_ tag: AST.TagType, at depth: Int) {
Expand Down
10 changes: 6 additions & 4 deletions Sources/Core/ReparseCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import Foundation

public struct PageDef {
let path: String
let root: String
let name: [String]

public init(path: String, name: [String]) {
public init(path: String, name: [String], root: String) {
self.path = path
self.name = name
self.root = root
}
}

Expand Down Expand Up @@ -83,9 +85,9 @@ public enum ReparseCore {

for i in paths {
let url = URL(fileURLWithPath: "\(path)/\(i)")
let path = url.path
if path.hasSuffix(".\(ext)") {
htmls.append(PageDef(path: path, name: splitFilenameIntoComponents(i, dropping: ext).reversed()))
let resultPath = url.path
if resultPath.hasSuffix(".\(ext)") {
htmls.append(PageDef(path: i, name: splitFilenameIntoComponents(i, dropping: ext).reversed(), root: path))
}
}

Expand Down
43 changes: 17 additions & 26 deletions Sources/Core/SwiftCodeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public final class SwiftCodeGenerator {
}
properties.append(at: indentation + 1) {
var result = [String]()
for value in self.properties.mutableParameters {
result.append("var \(value) = \(value)")
}
for (assignment, value) in self.properties.defaultValues {
result.append("let \(assignment) = \(assignment) ?? \(value)")
}
Expand Down Expand Up @@ -210,26 +213,15 @@ public final class SwiftCodeGenerator {
let name = name ?? "previousUnnamedIfTaken"
properties.append(condition: name)
let innerGenerator = SwiftCodeGenerator(ast: contents, signatures: signatures, page: properties)

// Currently the generator assumes that if the variable is not in defaultValues map,
// then it must be an optional variable.
// This is not true but it will require parsing the signatures first
// This code must be run a later stage

if let _ = properties.defaultValues[forEvery] {
properties.append("for (index, item) in \(forEvery).enumerated() {", at: indentation)
innerGenerator.generateBody(at: indentation + 1)
properties.append("}", at: indentation)
} else {
properties.append("if let \(forEvery) {", at: indentation)
properties.append("for (index, item) in \(forEvery).enumerated() {", at: indentation + 1)
innerGenerator.generateBody(at: indentation + 2)
properties.append("}", at: indentation + 1)
properties.append("\(name) = if \(forEvery).isEmpty { false } else { true }", at: indentation + 1)
properties.append("} else {", at: indentation)
properties.append("\(name) = false", at: indentation + 1)
properties.append("}", at: indentation)
}

properties.append("if \(forEvery).isEmpty {", at: indentation)
properties.append("\(name) = false", at: indentation + 1)
properties.append("} else {", at: indentation)
properties.append("for (index, item) in \(forEvery).enumerated() {", at: indentation + 1)
innerGenerator.generateBody(at: indentation + 2)
properties.append("}", at: indentation + 1)
properties.append("\(name) = true", at: indentation + 1)
properties.append("}", at: indentation)
case let .modifiers(applying: modifiers, tag: tag):
guard !modifiers.isEmpty else { return }
let attributes = (tag.attributes ?? SwiftAttributeStorage()).codeString
Expand Down Expand Up @@ -306,13 +298,12 @@ public final class SwiftCodeGenerator {
case .closingTag:
properties.append("// Error: Impossible tag type", at: indentation)
}
case let .requirement(name, type, label, value):
case let .requirement(name, type, label, value, mutable):
signatures.append(parameter: .init(type: type, name: name, label: label, defaultValue: value, canBeOverriden: false), to: properties.name)
// Skipping the following block for now.
// TODO: find a way to reintroduce this syntax
// if let value {
// properties.appendDefault(name: name, value: value)
// }

if mutable {
properties.appendMutable(name: name)
}
case let .eval(line):
properties.append("lines.append(\"\\(\(line.trimmingCharacters(in: .whitespacesAndNewlines)))\")", at: indentation)
case let .value(of: name, defaultValue):
Expand Down
12 changes: 4 additions & 8 deletions Sources/Core/SwiftOutputBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public final class SwiftOutputBuilder {
let topLine = """
//
// ------------------------------
// reparse version: 0.0.6
// reparse version: 0.0.7
// ------------------------------
// This is an auto-generated file
// ------------------------------
Expand Down Expand Up @@ -122,11 +122,7 @@ public final class SwiftOutputBuilder {

func buildPathFunc(for path: String, at indentation: Int = 0) -> String {
"""
\(String(repeating: " ", count: indentation))static var path: String {
\(String(repeating: " ", count: indentation)) \"\"\"
\(String(repeating: " ", count: indentation)) \(path)
\(String(repeating: " ", count: indentation)) \"\"\"
\(String(repeating: " ", count: indentation))}
\(String(repeating: " ", count: indentation))// Template: ./\(path)
"""
}

Expand Down Expand Up @@ -171,12 +167,12 @@ public final class SwiftOutputBuilder {

public extension SwiftOutputBuilder.RendererDef {
init?(page: PageDef, signatures: SwiftPageSignatures, protocols: [PageProperties.ProtocolCompliance], fileExtension ext: String, enumName: String, at indentation: Int) {
guard let contents = try? String(contentsOfFile: page.path) else { return nil }
guard let contents = try? String(contentsOfFile: "\(page.root)/\(page.path)") else { return nil }
guard let storage = Parser.parse(html: contents) else { return nil }
guard let fragmentName = page.name.first else { return nil }
let fullName = page.name.reversed().joined(separator: ".")

let properties = PageProperties(name: fullName, fileExtension: ext, enumName: enumName, protocols: protocols)
let properties = PageProperties(name: fullName, rootPath: page.root, fileExtension: ext, enumName: enumName, protocols: protocols)

let generator = SwiftCodeGenerator(ast: storage, signatures: signatures, page: properties)

Expand Down
2 changes: 1 addition & 1 deletion Sources/Example/Controllers/SampleController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ struct SampleController: RouteCollection {

func hello(req: Request) throws -> View {
let queries = try req.query.decode(HeroQuery.self)
return View(data: ByteBuffer(string: Pages.Index.render(superheroes: queries.heroes, value: queries.flag, req: req)))
return View(data: ByteBuffer(string: Pages.Index.render(superheroes: queries.heroes ?? [], value: queries.flag ?? false, req: req)))
}
}
Loading

0 comments on commit 81db8ea

Please sign in to comment.