Code Monkey home page Code Monkey logo

leaf's Introduction

Leaf

Documentation Team Chat MIT License Continuous Integration Swift 5.7+


leaf's People

Contributors

0xtim avatar 123flo321 avatar beaunouvelle avatar bennydebock avatar brettrtoomey avatar cellane avatar dankinsoid avatar fananek avatar glbrntt avatar grundoon avatar gwynne avatar igor-palaguta avatar jaapwijnen avatar jameslinjl avatar joannis avatar loganwright avatar mrmage avatar namolnad avatar nathanflurry avatar petrpavlik avatar rymcol avatar siemensikkema avatar tanner0101 avatar tdotclare avatar tib avatar vi4m avatar vzsg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

leaf's Issues

#() is working and #raw(){#feature} throwing an error

I am not understanding how this works or not sure it's a bug. Please advise.

<a href="#(link)#()features">Features</a>
<a href="#(link)#raw() {#features}">Features</a>
No error

Below throws error when no space between raw() and {
<a href="#(link)#raw(){#features}">Features</a>
Uncaught Error: ParseError.expectedOpenParenthesis. Use middleware to catch this error and provide a better response. Otherwise, a 500 error page will be returned in the production environment.

#if for nullability

#if(name) should run if name is present at all or true at all. Currently it only appears to be running if name is a truthy value.

load all file extensions

Allow .html, .blah, .leaf etc to be passed. All should be rendered as leaf templates. If no extension, assume .leaf

empty tag not found

When I want to use the #empty(variable) syntax, I got this error :

ParseError.tagTemplateNotFound("empty")

In the documentation, the empty tag is used to test is a variable is empty, but it doesn't work. When I go into the Constants.swift of Leaf, there is no empty...

thanks for help !

Nested embeds don't work

When embedding content that is common across some pages, inside a #set it doesn't work. Scenario is:

  • base.leaf - describes a base template
  • table.leaf - contains a common table template
  • page1.leaf - contains the template for the page. This embeds base, sets the variable expected by base, but also gets the variable set by table.

There are no errors, it just doesn't display the content, like the nested #get() doesn't return anything

Example repo is https://github.com/0xTim/LeafNestedEmbed

#equal doesn't exist

After asking for help on Slack I was told to use:

#equal(user.name, "rugaard") {
}

Instead of:

#if(user.name == "rugaard") {
}

But I keep getting the error about the tag equal doesn't exist.

Mismatched types with addition

Just trying to create a new url with pagination like so

href="/list?page=#(currentPage - 1)&size=#(pageSize)"

My Struct context declares these as Int. But when I add or subtract from the variable the result is a Double. Just spoke to Tim about it and he said to raise it.

layouts

Take the following code:

return try drop.view.make("welcome", [
    "name": "Leaf ๐Ÿƒ"
])

Current

Right now we can include leafs into a leaf using #include or #embed

welcome.leaf

#embed("header")

<h1>Hello, #(name)</h1>

#embed("footer")

Layouts

We need a way to invert this dependency with layouts by adding three new functions.

  • extend(Constant)
  • export(Constant) { Body }
  • import(Constant)

welcome.leaf

#extend("app")

#export("main") {
    <h1>Hello, #(name)!</h1>
}

app.leaf

<head>
    <title>my site</title>
</head>

#import("main")

<footer>some copyright crap</footer>

or even

app.leaf

#embed("header")

#import("main")

#embed("footer")

Should leaf render HTML suffix files?

Should leaf search for .leaf, and then .html as well? Or check for .html, and don't append suffix.

These would be rendered as 100% raw.

Workaround:

  • Just use .leaf instead of .html

Documentation Needed

I cannot find a good documentation for how to use leaf templating language. Any good resources?

nested #import tag support

Feature, Enhancement, or Optimization

nested import tags

Introduction

it is very common to use nested tags in child template, for example:

base template:

<div class="container">
    #import("container") {
    <div class="subcontainer">
         #import("subcontainer") {
              <a href="#">hello from vapor</a>
         }
    </div>
    }
</div>

child template1

    #extend("base")
    #export("container") {
        I'm template1 rewritted container!
    }

child template2

    #extend("base")
    #export("subcontainer") {
        I'm template2 rewritted subcontainer!
    }

each of the template could be rendered seperatly, after rendering the three templates render results would be
base template:

<div class="container">
    <div class="subcontainer">
         <a href="#">hello from vapor</a>
    </div>
</div>

child template1:

<div class="container">
        I'm template1 rewritted container!
</div>

child template2:

<div class="container">
    <div class="subcontainer">
        I'm template2 rewritted subcontainer!
    </div>
</div>

Motivation

in a big project, it's common for templates that free to choose which tag to rewrite. it will be more free and it will be very useful.

Proposed solution

since I'm migrated from python, there are a lot of template languages, such as django-template-language,jinja, mako
takes mako for example, you can see the code Using Named Blocks paragrah shows nested block in them

Impact

no vapor app will stop compiling due to this change.

Client-side rendering

I think it would be cool if we can also render (parts of) a page on the client side. This will make it easy to reload parts of the page without reloading the whole page.

Everything is rendered on the server side currently which adds the requirement of javascript to do the partially reloading...

Feature Request: With-Tag for variable renaming

Purpose

Sometimes for the purposes of a embedded templates, it would be nice if you could rename variables passed in so that your template is much less clunky. Also, encourages reuse of small chunks of templates.

The with-tag would allow you to use a deeply nested name, as a shorter name within the provided scope.

Syntax-Suggestion

#with(<complex.nested.value>, <short-name>, โ€ฆ) { /* template */ }

It accepts N pairs of parameters, where the first of each pair is the context's variable, and the second of each pair is a string of the new name you'd like to use.

Usage

<!-- Single item rename -->
#with(my.deeply.nested.thing, "myThing") {
    <!--Other columnsโ€ฆ--><td>#(myThing.prop)</td><td>#(myThing.anotherProp)</td><td>#(myThing.someOtherProp)</td>
}
<!-- Multiple variable rename -->
#with(
      clunky.obj.name, "name",
      clunky.obj.val, "val",
      clunky-too.foo, "foo"
    ) {
    <ul>#(name): #(val), #(foo)</ul>
}

Impact

  • New tag in the leaf-base library. Could conflict if users have implemented this as a custom tag. Documentation burden.
  • Shouldn't break any vanilla Leaf apps.
  • Simplifies adoption of Leaf as a templating engine.

Leaf 3 - no way to add custom tags

In Leaf 3 there is currently no easy way to add custom tags. It kinda can be done by overriding the LeafConfig in services but if there are multiple providers or instances where this happens, one will always lose. One possibility is to make the tags dictionary mutable to allow it to be extended

#loop in one line

Currently, code below:

<ul class="sidenav" id="mobile-demo">#loop(menu, "item"){<li><a href=#(item.href)>#(item.title)</a></li>}</ul>

Produces such output:

<ul class="sidenav" id="mobile-demo"><li><a href=ITEM1_HREF)>ITEM1_TITLE</a></li>
<li><a href=ITEM2_HREF)>ITEM2_TITLE</a></li>
<li><a href=ITEM3_HREF)>ITEM3_TITLE</a></li></ul>

For those who are obsessed with minifying html it would be great if it could produce:

<ul class="sidenav" id="mobile-demo"><li><a href=ITEM1_HREF)>ITEM1_TITLE</a></li><li><a href=ITEM2_HREF)>ITEM2_TITLE</a></li><li><a href=ITEM3_HREF)>ITEM3_TITLE</a></li></ul>

i.e. everything in one line

Leaf 3 seems to return true for #if(somevar) even when somevar isn't set

Leaf 2 allowed us to check whether a variable was set in the current context by writing #if(somevar), but in Leaf 3 this no longer works.

To reproduce the problem, creating the following Leaf template:

#if(haters) {
    <p>The "haters" variable is set to #(haters)</p>
}

#if(fakers) {
    <p>The "fakers" variable is set to #(fakers)</p>
}

If you render that template with the context ["haters": "hating"] you would expect the first message to be printed but not the second, but as of 3.0 alpha 4 both messages are printed.

#markdown(content) , a custom tag

final class Markdown: Tag {
    
    let name = "markdown"

    func run(stem: Stem, context: Context, tagTemplate: TagTemplate, arguments: [Argument]) throws -> Node? {
        guard let string = arguments.first?.value?.string else { return nil }
        let html = try markdownToHTML(string)
        let unescaped = html.bytes
        return .bytes(unescaped)
    }

    func shouldRender(stem: Stem, context: Context, tagTemplate: TagTemplate, arguments: [Argument], value: Node?) -> Bool {
        return true
    }
}

markdownToHTML dependen on

.Package(url: "https://github.com/czechboy0/cmark.swift.git", majorVersion: 0, minor: 1)

it could work !

Packages with views

When building packages with views. You don't want users to copy all the views into their project.
Some of the views would be just fine to have in the package
With the current system this cannot be done

In laravel there was a option to write path as fx
AdminPanel:Login/login

this means it would check if there was a folder in Resources/Views which fitted for /AdminPanel/Login/login.leaf

if this was not found it would take the same path but in the package

I made a small example of how it could work, but the initialisation with workdir kills it a bit.

 public func makeWithFallback(_ path: String) throws -> View {
        do {
            return try make(Droplet().workDir + "/Resources/Views/" + path)
        } catch {
             return try make(Droplet().workDir + "Packages/AdminPanel-0.1.1/Sources/AdminPanel/Resources/Views/" + path + ".leaf")
        }
        
    }

Is this something for next update? It's very handy

Boolean Custom Tag always behaves as though result is true

I have registered a custom tag to return true if the index is odd and false if even as follows:

class OddEvenTag: BasicTag {
    let name = "OddEven"

    func run(arguments: ArgumentList) throws -> Node? {
        guard
            arguments.count == 1,
            let index = arguments[0]?.int
            else { return Node(nil) }
        print(index, index & 1)
        return Node((index & 1) == 1)
    }
}

The print statement produces satisfyingly good output:

0 0
1 1
2 0
3 1
...

However, when I use the custom tag inside a #loop in a leaf file, such as

#OddEven(index){hello}##else(){bye}

It always instantiates the hello. I've tried hard-coding false into the return statement and it doesn't change the outcome. I have used a (more complex) custom tag before, so I know they can work.

In case you're wondering, I really want to use the tag to alternate the row background colours of a grid!

Leaf should respect spacing in rendering

I have a leaf file that does this:

#if(group.admin) { #(group.admin.firstName) #(group.admin.lastName) }

However, this renders as FirstLast instead of First Last. Leaf should respect the spacing between the two objects

Leaf requires an #ifnot()

Enhancement

#ifnot()

Introduction

Whilst in Leaf there is an #if() there isn't an #ifnot().

Motivation

I would like to have a readonly field in a form when viewing, but when editing, I would like to remove the readonly attribute. When I show the form I send "update": true or "update": false and then in the html I have:

<input name="fname" type="text" value="#(member.fname)" pattern="alpha" #ifnot(update) {readonly}>

Proposed solution

copy the code for #if() as proposed by @Oxtim and use it for the tag #ifnot

Code snippets

I duplicated the If.swift file and created an IfNot.swift file and changed the following:
guard let value = arguments.first?.value else { return false }
// Existence of bool, evaluate bool.
if let bool = value.bool { return !bool }
// Empty string value returns true.
if value.string == "" { return true }
// Otherwise, not-nil returns false.
return false
`

Impact

There should no impact.

Alternatives considered

The alternative is to invert the logic and make update equal false, which will cause confusion. #if(update) {readonly}

consistent whitespace in `#for` tag

when I runโ€ฆ

#for( name in names ) {

instead ofโ€ฆ

#for(name in names) {

I get:
[ ERROR ] TemplateKit.TemplateError.parse: Expected 105 got 110 (names.leaf:11)

I'm not super hung-up on my own personal whitespace style, but Leaf should be smarter than this.

#equal(...){...}#else(){...} unreliable

Hi,

Leaf is pretty incredible, but sometimes #equal(...){...}#else(){...} always instantiates the 'else' content, regardless of the value of the 'equal' condition. For example, this:

#equal(user.id,"0"){Add New Person} #else(){Edit #(user.forename) #(user.surname)'s Record}

gives

Add New Person Edit 's Record

when user.id is equal to zero and:

Edit Joe Soap's Record

when it isn't

Whereas:

`#equal(role.role,name.role){}#else(){selected="selected"}

works reliably and sets the appropriate option in a drop-down.

The only difference I can see is that the working one is inside a #loop tag and so role.role is a changing value. I have tried making a fixed field user.zero = 0 and putting:

#equal(user.id,user.zero)...

to see if I was doing something wrong with the literal, but it didn't help. I've also tried putting a lot more white space around the tokens, but to no effect.

#repeat tag

#repeat(5) {
    hello
}

output:

hello
hello
hello
hello
hello

Could be nice to include something like i to indicate position.

#repeat(99) {
    Hello for the #(i)th time.
}

Index in for loop shows up as a decimal when adding an int

When adding an int to the index for loops it displays as a float instead of an int.

Example

let index = 0
index + 1 should equal and show up as 1 . However, it shows up as 1.0, 2.0, 3.0, etc

#for(player in players) { <tr><td>#(index+1)</td><td>#(player.name)</td></td></tr> }

Expected rendering:

1 Bryan
2 Jessica
3 Chris

How it actually renders:

1.0 Bryan
2.0 Jessica
3.0 Chris

Note:

It could be nice to get a number formatting tag to display it to how the user wants it displayed

Declaring variables

Maybe this is out of Leaf's scope, but coming from ejs and erb, I thought it would be nice to be able to declare variables (I looked everywhere, and if it's already possible, I missed it every time).

If this isn't possible / is out of scope, what would be a proper way to create and pass a variable to all leafs? It's something for the footer, and the footer is #imported everywhere. It would be desirable to not having to create and pass it every time I view.make, though ๐Ÿ˜

Cheers!

#index does not work as described in documentation

As described in documentation (https://docs.vapor.codes/2.0/leaf/leaf/#index-indexobject-_-index-intstring)

Index: #index(object, _ index: Int|String)ยถ

Hello, #index(friends, 0)!
Hello, #index(friends, "best")!

Currently it has following issues:

  1. it searches just in arrays
  2. it requires numeric index in quotes #index(friends, "0"). Probably it is leaf limitation, as everything that is not inside quotes is variable name
  3. it does not support for constructions like #index(#index(friends, "0"), "name")

I added similar tag, that resolves 1) and 3), 2)

public class KeypathTag: Tag {
   public let name = "keypath"

   public func run(tagTemplate: TagTemplate, arguments: ArgumentList) throws -> Node? {
      guard arguments.count >= 2 else {
         return nil
      }

      var value: Node? = arguments[0]

      for i in 1..<arguments.count {
         if let array = value?.array, let index = arguments[i]?.int {
            value = array[index]
         } else if let object = value?.object, let key = arguments[i]?.string {
            value = object[key]
         } else {
            return nil
         }
      }

      return value
   }
}

It can search in array and in dictionary, and also has ability to index inside previous result #index(#index(friends, "0"), "name"), like #keypath(friends, "0", "name")

Hashtag (#) breaks standard HTML/CSS

I'm not sure using the # as a token is wise. Trying it out I hit quite a few cases where it completely standard HTML/CSS was interpreted as Leaf methods.


Two quick examples

A common way to "pop" modals is using an <a> where the href is just a #.

<a href="#" class="btn btn-primary" data-toggle="modal">

(I know you could just use <button> instead of <a> but not everybody do that)

You also won't be able to use inline hex-colors, which is also something a lot of people are doing (at least the lazy ones).

<h1 style="color: #ff0000">This heading is red</h1>

There are probably a lot more cases, where the # would make Leaf conflict with normal standard HTML/CSS. So maybe it would be better/wiser to use a different token?

Or maybe change it to a double ##? I don't know. All I know is a single # unfortunately causes quite the trouble :(

Leaf requires #notequal

#notequal()

Author: @willianpinho

Introduction

Tag needed to compare different data. Today we only have #equal ()

Motivation

It is not always possible to do all the checks with #if () and #else ().

For example, I get a dictionary of projects and projects have milestones, I wish only the first one is active and others inactive.

#loop (project.milestones, "milestone") {
    #equal (milestone.id, "1") {
        Active Milestone: # (milestone.name)
    }
    #notequal (milestone.id, "1") {
        Not Active Milestone # (milestone.name)
    }
}

Propose

Add #notequal() to leaf

Code snippets

I duplicated the Equal.swift file and created an NotEqual.swift file and changed the following:
Add in Constants.swift
"notequal": NotEqual(),
Create NotEqual.swift

public final class NotEqual: BasicTag {
    public enum Error: LeafError {
        case expected2Arguments
    }
    
    public let name = "NotEqual"
    
    public func run(arguments: ArgumentList) throws -> Node? {
        guard arguments.count == 2 else { throw Error.expected2Arguments }
        return nil
    }
    
    public func shouldRender(
        tagTemplate: TagTemplate,
        arguments: ArgumentList,
        value: Node?
        ) -> Bool {
        return fuzzyEquals(
            arguments.first,
            arguments.last
        )
    }
}

fileprivate func fuzzyEquals(_ lhs: Node?, _ rhs: Node?) -> Bool {
    let lhs = lhs ?? .null
    let rhs = rhs ?? .null
    
    switch lhs.wrapped {
    case let .array(lhs):
        guard let rhs = rhs.array else { return false }
        guard lhs.count != rhs.count else { return false }
        for (l, r) in zip(lhs, rhs) where !fuzzyEquals(Node(l), r) { return false }
        return true
    case let .bool(bool):
        return bool != rhs.bool
    case let .bytes(bytes):
        guard case let .bytes(rhs) = rhs.wrapped else { return false }
        return bytes != rhs
    case .null:
        return rhs.isNull
    case let .number(number):
        switch number {
        case let .double(double):
            return double != rhs.double
        case let .int(int):
            return int != rhs.int
        case let .uint(uint):
            return uint != rhs.uint
        }
    case let .object(lhs):
        guard let rhs = rhs.object else { return false }
        guard lhs.count != rhs.count else { return false }
        for (k, v) in lhs where !fuzzyEquals(Node(v), rhs[k]) { return false }
        return true
    case let .string(string):
        return string != rhs.string
    case let .date(date):
        // FIXME: Add fuzzy date access and equality?
        guard case let .date(right) = rhs.wrapped else { return false }
        return date != right
    }
}

Impact

There should no impact.

Considerations

Need more tests

Changelog

  • Change name Unequal to NotEqual, suggestion of @vzsg

"Illegal Instruction" crash on Linux when printing text with CRLF inside

Consider the following code (replacing the "It works" text of the basic template):

let drop = Droplet()

drop.get { req in
    return try drop.view.make("welcome", [
    	"content": "\r\n\""
    ])
}

This code works fine on macOS, but causes the process to crash on Linux, both on a physical Ubuntu installation and in Docker. The only output is "Illegal Instruction".

It seems that strings that match the following pattern: \r\n.*["|'] โ€“ in other words, strings that contain a Windows newline and a single or double quote โ€“ trigger the crash when printed into the output using the #() tag.

Ability to access session

How to access the session in a template file?

There is no option to add Flash messages and loop them in the template

Nested loops cause data carrying forward

I found when I have a loop inside another, the data will be carried forward from one item to another. For example, I have a bunch of data: [
{'title': 'Swift', 'opt': [1, 2, 3], 'option': true}
{'title': 'Vapor', 'image': 'Vapor.png'}
]

If I do #loop(data, "each"), and inside the loop, I have #loop(each.opt, "opt"). It works for the first one, obviously. But it also works for the second one, because the opt from the first is carried forward to the second one for some reason... It has been bugging me for a while, and I am pretty sure my data is alright when I call drop.view.make().

Is there anyone who met the same or similar problem?

access the index in a loop

loop(friends, "friend") {
  Hello, #(friend.name) you are number xxxx in my list of friends!
}

I would want the output to be
Hello, John you are number 0 in my list of friends!
Hello, Jane you are number 1 in my list of friends!
Hello, Jack you are number 2 in my list of friends!

'#export()' depends on '{' locaiton

Here's a bit of inconsistency in .leaf parsing. You can still get the job done but still it feels like it should not be like that.

This works fine, and renders expected 'Hey'

#export("body") {
    <h1>Hey</h1>
}

And this renders nothing:

#export("body")
{
    <h1>Hey</h1>
}

Prettier html

I noticed two things about the html output of Leaf (there might be others that I have not encountered).

  1. there is an extra newline after a loop tag.
  2. there does not seem to be a concept of indentation level

This results in html that is perfectly valid but not as pretty to the eye which does not help with debugging.

I have figured out how to solve the first point and I can create a PR if this is helpful. As for the second, I would like to know if this is something people besides me would be interested in. Ideally, any indentation should be able to be turned off as well.

syntax highlighting

We need to work on creating a universal syntax highlighter for leaf that we can make a Sublime package for and PR into GitHub.

IntelliJ plugin

Would be nice to have syntax highlighting for leaf files in IntelliJ/AppCode

two consecutive strings join together

When I do Hi #(user.firstname) #(user.lastname) I get Hi LemmyKilmister instead of Hi Lemmy Kilmister

using:

let leaf = try req.make(LeafRenderer.self)
let context = model ?? [String: String]()
let output = leaf.render(template: data, context)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.