Skip to content

Commit

Permalink
Fixed some attributes related bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
RussBaz committed Mar 5, 2024
1 parent 8c36770 commit d8c9312
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 167 deletions.
84 changes: 42 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,32 @@ NOTE: The master branch is way ahead of the latest release as it includes many f

## Example

Here is an example of the `Reparse` syntax as used in the bundled `Example` project:
Here is an example of the `Reparse` syntax as used in the bundled `Example` project (with few lines removed for brevity):

```html
<r-require name="context" type="[String]" label="superheroes" />
<r-extend name="base" />
<r-extend name="body" />

<main>
<h1>
Hello
<r-include name="components.world" r-if="!(context?.isEmpty ?? true)">
Ultra Heroes!
</r-include>
<r-block r-else> World?</r-block>
</h1>
<ol>
<li r-for-every="context">
<p>
<r-include name="components.hello-me"><r-item /></r-include>
</p>
<p>Index: <r-index /> or +1 = <r-eval line="index+1" /></p>
</li>
<li r-else>No more heroes...</li>
</ol>

<p><r-value of="req.url.string" /></p>
<h1>
Hello
<r-include name="components.world" r-if="!(context?.isEmpty ?? true)">
Ultra Heroes!
</r-include>
<r-block r-else> World?</r-block>
</h1>
<ol>
<li r-for-every="context">
<p>
<r-include name="components.hello-me"><r-item /></r-include>
</p>
<p>Index: <r-index /> or +1 = <r-eval line="index+1" /></p>
</li>
<li r-else>No more heroes...</li>
</ol>

<p><r-value of="req.url.string" /></p>
</main>
<title r-add-to-slot="head">Hero List</title>
```
Expand All @@ -47,7 +47,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.5"),
.package(url: "https://github.com/RussBaz/experimental-reparse-html.git", from: "0.0.6"),
```

Then add the `ReparseRuntime` as a dependency to your target like this:
Expand Down Expand Up @@ -141,44 +141,44 @@ There are a few types of control attributes and can be separated in 3 groups:

Most html tags can be equipped with one of the conditional control attributes:

- **r-if="condition"**
- **r-else-if="condition"**
- **r-else**
- **r-if="condition"**
- **r-else-if="condition"**
- **r-else**

If the condition (which must be a valid Swift expression) is satisfied than the html tag and its contents are rendered.

If the condition is satisfied, then a special variable called `previousUnnamedIfTaken` will be set to true. Otherwise, it will be set to false.

Optionally, you can save the result of the condition to a different variable using an additional attribute:

- **r-tag="tag-name"**
- **r-tag="tag-name"**

#### Loops

- **r-for-every="sequence"**
- **r-for-every="sequence"**

#### Slots

- **r-add-to-slot="slot-name"**
- **r-replace-slot="slot-name"**
- **r-add-to-slot="slot-name"**
- **r-replace-slot="slot-name"**

### Control Tags

- **`<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-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.
- **`<r-set name="attr-name" value="attr-value" />`** to replace an attribute in a preceding tag (skipping other set/unset tags) or
- **`<r-set name="attr-name" value="attr-value" append />`** to append to it instead
- **`<r-unset name="attr-name" />`** to remove an atttribute from a preceding tag (skipping other set/unset tags)
- **`<r-var name="var-name" line="expression" />`** to assign the result of an expression to a variable
- **`<r-value of="name" default="optional-val" />`** to paste the value of the specified variable or the provided default value if the value was `nil`
- **`<r-eval line="expression" />`** evaluate the expression and paste its result
- **`<r-slot name="optional-name" />`** to mark an area to be filled by the incoming outer slot. If no name is provided, it will be known as 'default' or
- **`<r-slot name="optional-name"> default slot </r-slot>`** to declare a slot and provide the default contents if no matching slot found in the incoming outer slots
- **`<r-index />`** (inside the loops only) to paste the index of the current iteration of the innermost loop
- **`<r-value />`** (inside the loops only) to paste the value of the current iteration of the innermost loop
- **`<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-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.
- **`<r-set name="attr-name" value="attr-value" />`** to replace an attribute in a preceding tag (skipping other set/unset tags) or
- **`<r-set name="attr-name" value="attr-value" append />`** to append to it instead
- **`<r-unset name="attr-name" />`** to remove an atttribute from a preceding tag (skipping other set/unset tags)
- **`<r-var name="var-name" line="expression" />`** to assign the result of an expression to a variable
- **`<r-value of="name" default="optional-val" />`** to paste the value of the specified variable or the provided default value if the value was `nil`
- **`<r-eval line="expression" />`** evaluate the expression and paste its result
- **`<r-slot name="optional-name" />`** to mark an area to be filled by the incoming outer slot. If no name is provided, it will be known as 'default' or
- **`<r-slot name="optional-name"> default slot </r-slot>`** to declare a slot and provide the default contents if no matching slot found in the incoming outer slots
- **`<r-index />`** (inside the loops only) to paste the index of the current iteration of the innermost loop
- **`<r-value />`** (inside the loops only) to paste the value of the current iteration of the innermost loop

## How does it work?

Expand Down
46 changes: 27 additions & 19 deletions Resources/Pages/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,33 @@
<r-extend name="body" />

<main>
<h1>
Hello
<r-include name="components.world" r-if="!(context?.isEmpty ?? true)">
Ultra Heroes!
</r-include>
<r-block r-else> World?</r-block>
</h1>
<ol>
<li r-for-every="context">
<p>
<r-include name="components.hello-me"><r-item /></r-include>
</p>
<p>
Index: <r-index /> or +1 = <r-eval line="index+1" />
</p>
</li>
<li r-else>No more heroes...</li>
</ol>
<h1>
Hello
<r-include name="components.world" r-if="!(context?.isEmpty ?? true)">
Ultra Heroes!
</r-include>
<r-block r-else> World?</r-block>
</h1>
<ol>
<li r-for-every="context">
<p class='base'>
<r-set name="class" value=" rose" append />
<r-include name="components.hello-me"><r-item /></r-include>
</p>
<p>Index: <r-index /> or +1 = <r-eval line="index+1" /></p>
</li>
<li r-else>No more heroes...</li>
</ol>

<p><r-value of="req.url.string" /></p>
<p><r-value of="req.url.string" /></p>
<button
class="button"
hx-post="/auth/logout?next=/"
hx-target="body"
data-loading-delay
data-loading-disable
>
What's up?
</button>
</main>
<title r-add-to-slot="head">Hero List</title>
67 changes: 19 additions & 48 deletions Sources/Core/SimpleHtmlParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ public final class SimpleHtmlParser {
let content: AST.Content
}

enum AttributeValueWrapper {
case single, double, none
}

enum DataState {
case script(depth: Int)
case style
Expand All @@ -22,7 +18,7 @@ public final class SimpleHtmlParser {
case lookingForAttributeName(tag: String, attributes: SwiftAttributeStorage, key: String)
case lookineForAttributeSeparator(tag: String, attributes: SwiftAttributeStorage, key: String)
case lookingForAttributeValueStart(tag: String, attributes: SwiftAttributeStorage, key: String)
case lookingForAttributeValue(tag: String, attributes: SwiftAttributeStorage, key: String, value: String, wrapper: AttributeValueWrapper)
case lookingForAttributeValue(tag: String, attributes: SwiftAttributeStorage, key: String, value: String, wrapper: SwiftAttributeStorage.AttributeValueWrapper)
case lookingForVoidTagEnd(tag: String, attributes: SwiftAttributeStorage)
case lookingForClosingTagName(name: String)
case lookingForClosingTagEnd(name: String)
Expand Down Expand Up @@ -133,14 +129,16 @@ public final class SimpleHtmlParser {
case let .lookingForAttributeName(tag, attributes, key):
if char == " " {
state = .lookineForAttributeSeparator(tag: tag, attributes: attributes, key: key)
} else if char == "\n" {
state = .lookingForAttributeName(tag: tag, attributes: attributes, key: key)
} else if char == "=" {
state = .lookingForAttributeValueStart(tag: tag, attributes: attributes, key: key)
} else if char == ">" {
attributes.append(key: key, value: "", wrapped: false)
attributes.append(key: key, value: "", wrapper: .none)
state = .lookingForText(buffer: "")
appendTag(.openingTag(name: tag, attributes: attributes), till: index)
} else if char == "/" {
attributes.append(key: key, value: "", wrapped: false)
attributes.append(key: key, value: "", wrapper: .none)
state = .lookingForVoidTagEnd(tag: tag, attributes: attributes)
} else if !["\"", "'"].contains(char), !isControlCharacter(char) {
state = .lookingForAttributeName(tag: tag, attributes: attributes, key: key + String(char))
Expand All @@ -154,13 +152,16 @@ public final class SimpleHtmlParser {
state = .lookingForAttributeValueStart(tag: tag, attributes: attributes, key: key)
} else if char == ">" {
state = .lookingForText(buffer: "")
attributes.append(key: key, value: "", wrapped: false)
attributes.append(key: key, value: "", wrapper: .none)
appendTag(.openingTag(name: tag, attributes: attributes), till: index)
} else if char == "/" {
attributes.append(key: key, value: "", wrapped: false)
attributes.append(key: key, value: "", wrapper: .none)
state = .lookingForVoidTagEnd(tag: tag, attributes: attributes)
} else if !["\"", "'"].contains(char), char == "\n" || !isControlCharacter(char) {
attributes.append(key: key, value: "", wrapped: false)
} else if char == "\n" {
attributes.append(key: key, value: "", wrapper: .none)
state = .lookingForAttributes(tag: tag, attributes: attributes)
} else if !["\"", "'"].contains(char), !isControlCharacter(char) {
attributes.append(key: key, value: "", wrapper: .none)
state = .lookingForAttributeName(tag: tag, attributes: attributes, key: String(char))
} else {
cancelTag(till: index)
Expand All @@ -180,36 +181,36 @@ public final class SimpleHtmlParser {
case let .lookingForAttributeValue(tag, attributes, key, value, wrapper):
if char == " " {
if case .none = wrapper {
attributes.append(key: key, value: value, wrapped: false)
attributes.append(key: key, value: value, wrapper: .none)
state = .lookingForAttributes(tag: tag, attributes: attributes)
} else {
state = .lookingForAttributeValue(tag: tag, attributes: attributes, key: key, value: value + String(char), wrapper: wrapper)
}
} else if char == "\"" {
switch wrapper {
case .single:
state = .lookingForAttributeValue(tag: tag, attributes: attributes, key: key, value: value + String(char), wrapper: wrapper)
state = .lookingForAttributeValue(tag: tag, attributes: attributes, key: key, value: value + String(char), wrapper: .single)
case .double:
attributes.append(key: key, value: value, wrapped: true)
attributes.append(key: key, value: value, wrapper: .double)
state = .lookingForAttributes(tag: tag, attributes: attributes)
case .none:
cancelTag(till: index)
}
} else if char == "'" {
switch wrapper {
case .single:
attributes.append(key: key, value: value, wrapped: true)
attributes.append(key: key, value: value, wrapper: .single)
state = .lookingForAttributes(tag: tag, attributes: attributes)
case .double:
state = .lookingForAttributeValue(tag: tag, attributes: attributes, key: key, value: value + String(char), wrapper: wrapper)
state = .lookingForAttributeValue(tag: tag, attributes: attributes, key: key, value: value + String(char), wrapper: .double)
case .none:
cancelTag(till: index)
}
} else if char == ">", case .none = wrapper, !value.isEmpty {
attributes.append(key: key, value: value, wrapped: false)
attributes.append(key: key, value: value, wrapper: .none)
appendTag(.openingTag(name: tag, attributes: attributes), till: index)
} else if char == "/", case .none = wrapper, !value.isEmpty {
attributes.append(key: key, value: value, wrapped: false)
attributes.append(key: key, value: value, wrapper: .none)
state = .lookingForVoidTagEnd(tag: tag, attributes: attributes)
} else if !isControlCharacter(char) {
state = .lookingForAttributeValue(tag: tag, attributes: attributes, key: key, value: value + String(char), wrapper: wrapper)
Expand Down Expand Up @@ -453,33 +454,3 @@ public final class SimpleHtmlParser {
}
}
}

extension SimpleHtmlParser.AttributeValueWrapper {
func isWrapped() -> Bool {
if case .none = self {
false
} else {
true
}
}
}

extension SwiftAttributeStorage {
static func from(attributes: [String: (String, SimpleHtmlParser.AttributeValueWrapper)]) -> SwiftAttributeStorage {
let storage = SwiftAttributeStorage()
for (key, (value, wrapper)) in attributes {
if value.isEmpty {
if wrapper.isWrapped() {
storage[key] = .string("")
} else {
storage[key] = .flag
}

} else {
storage[key] = .string(value)
}
}

return storage
}
}
8 changes: 4 additions & 4 deletions Sources/Core/SwiftCodeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,13 @@ public final class SwiftCodeGenerator {
case .elseType:
properties.append("if !\(cn) {", at: indentation)
}
properties.append("attributes.update(key: \"\(name)\", with: \(value.codeString), replacing: false)", at: indentation + 1)
properties.append("attributes.append(to: \"\(name)\", value: \(value.codeString))", at: indentation + 1)
properties.append("\(cn) = true", at: indentation + 1)
properties.append("} else {", at: indentation)
properties.append("\(cn) = false", at: indentation + 1)
properties.append("}", at: indentation)
} else {
properties.append("attributes.update(key: \"\(name)\", with: \(value.codeString), replacing: false)", at: indentation)
properties.append("attributes.append(to: \"\(name)\", value: \(value.codeString))", at: indentation)
}
case let .replace(name: name, value: value, condition: condition):
if let condition {
Expand All @@ -264,13 +264,13 @@ public final class SwiftCodeGenerator {
case .elseType:
properties.append("if !\(cn) {", at: indentation)
}
properties.append("attributes.update(key: \"\(name)\", with: \(value.codeString), replacing: true)", at: indentation + 1)
properties.append("attributes.replace(key: \"\(name)\", with: \(value.codeString))", at: indentation + 1)
properties.append("\(cn) = true", at: indentation + 1)
properties.append("} else {", at: indentation)
properties.append("\(cn) = false", at: indentation + 1)
properties.append("}", at: indentation)
} else {
properties.append("attributes.update(key: \"\(name)\", with: \(value.codeString), replacing: true)", at: indentation)
properties.append("attributes.replace(key: \"\(name)\", with: \(value.codeString))", at: indentation)
}
case let .remove(name: name, condition: condition):
if let condition {
Expand Down
6 changes: 3 additions & 3 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.5
// reparse version: 0.0.6
// ------------------------------
// This is an auto-generated file
// ------------------------------
Expand Down Expand Up @@ -141,15 +141,15 @@ public final class SwiftOutputBuilder {
.joined(separator: "\n") + "\n\n"
}

if protocols.isEmpty {
if protocols.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return """
\(String(repeating: " ", count: indentation))enum \(page.name) {
\(buildPathFunc(for: page.path, at: indentation + 1))
\(buildRenderFunc(for: page, at: indentation + 1))
\(buildIncludeFunc(for: page))
\(String(repeating: " ", count: indentation))}
"""
} else if associatedTypes.isEmpty {
} else if associatedTypes.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return """
\(String(repeating: " ", count: indentation))struct \(page.name)\(protocols) {
\(buildPathFunc(for: page.path, at: indentation + 1))
Expand Down
Loading

0 comments on commit d8c9312

Please sign in to comment.