GithubHelp home page GithubHelp logo

davidchall / jinjar Goto Github PK

View Code? Open in Web Editor NEW
39.0 2.0 1.0 1.32 MB

Templating engine for R inspired by Jinja

Home Page: https://davidchall.github.io/jinjar

License: Other

R 2.33% C++ 97.67% C 0.01%
template-engine jinja r

jinjar's Introduction

jinjar

CRAN status Codecov test coverage R-CMD-check

jinjar is a templating engine for R, inspired by the Jinja Python package and powered by the inja C++ library.

Installation

You can install the released version of jinjar from CRAN with:

install.packages("jinjar")

Or you can install the development version from GitHub:

# install.packages("remotes")
remotes::install_github("davidchall/jinjar")

Usage

library(jinjar)

render("Hello {{ name }}!", name = "world")
#> [1] "Hello world!"

Here’s a more advanced example using loops and conditional statements. The full list of supported syntax is described in vignette("template-syntax").

template <- 'Humans of A New Hope

{% for person in people -%}
{% if "A New Hope" in person.films and default(person.species, "Unknown") == "Human" -%}
* {{ person.name }} ({{ person.homeworld }})
{% endif -%}
{% endfor -%}
'

template |>
  render(people = dplyr::starwars) |>
  writeLines()
#> Humans of A New Hope
#> 
#> * Luke Skywalker (Tatooine)
#> * Darth Vader (Tatooine)
#> * Leia Organa (Alderaan)
#> * Owen Lars (Tatooine)
#> * Beru Whitesun lars (Tatooine)
#> * Biggs Darklighter (Tatooine)
#> * Obi-Wan Kenobi (Stewjon)
#> * Wilhuff Tarkin (Eriadu)
#> * Han Solo (Corellia)
#> * Wedge Antilles (Corellia)
#> * Jek Tono Porkins (Bestine IV)
#> * Raymus Antilles (Alderaan)

Related work

An important characteristic of a templating engine is how much logic is supported. This spectrum ranges from logic-less templates (i.e. only variable substitution is supported) to arbitrary code execution. Generally speaking, logic-less templates are easier to maintain because their functionality is so restricted. But often the data doesn’t align with how it should be rendered – templating logic offers the flexibility to bridge this gap.

Fortunately, we already have very popular R packages that fall on opposite ends of this spectrum:

  • whisker – Implements the Mustache templating syntax. This is nearly logic-less, though some simple control flow is supported. Mustache templates are language agnostic (i.e. can be rendered by other Mustache implementations).
  • knitr and rmarkdown – Allows arbitrary code execution to be knitted together with Markdown text content. It even supports multiple language engines (e.g. R, Python, C++, SQL).

In contrast, jinjar strikes a balance inspired by the Jinja Python package. It supports more complex logic than whisker, but without the arbitrary code execution of knitr.

jinjar's People

Contributors

davidchall 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

Watchers

 avatar  avatar

Forkers

pawanramamali

jinjar's Issues

Extract metadata from template

Jinja has a Meta API that returns useful details about the parsed template.

  • List of variables expected to be provided by render()
  • List of referenced templates (includes, extends)

Class for parsed templates

If a template will be rendered multiple times, it makes sense to parse the template once and store the intermediate object.

  • parse(.x, .config) returns an S3 object with class "jinjar_template".
  • render.jinjar_template(.x, ...) renders the template like usual.
  • The class stores:
    • a cpp11::external_pointer to the inja::Template object (i.e. after parsing).
    • The jinjar_config object (needed again for rendering).

Catch invalid data variables

We don't currently perform any validation of data variables passed to render(). This leads to confusing error messages. See #24.

Use cli themes

Currently, printing a template uses hardcoded colors. But the appropriateness of these colors depends on the user's terminal theme. For example, if they use dark mode and we hardcode black text, then they can't see the text.

The solution is to use custom cli classes. Then the user can specify how they want each cli class to be rendered using options(cli.user_theme). See example. The custom cli classes should be:

  • jinjar_text
  • jinjar_variable
  • jinjar_block
  • jinjar_comment

Release jinjar 0.1.1

Prepare for release:

  • Check current CRAN check results
  • Polish NEWS
  • devtools::build_readme()
  • urlchecker::url_check()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • rhub::check(platform = 'ubuntu-rchk')
  • rhub::check_with_sanitizers()
  • revdepcheck::revdep_check(num_workers = 4)
  • Update cran-comments.md

Submit to CRAN:

  • usethis::use_version('patch')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • usethis::use_github_release()
  • usethis::use_dev_version()

String literal with single-quotes causes error "Unknown function"

Hey, thanks for this useful package :)

I think I stumbled upon an issue when trying to render a SQL query, but I may be missing a piece of the puzzle. The following snippet results in Error: [inja.exception.parser_error] (at 3:34) unknown function join

library(jinjar)

sql_template <- "
    {% if isArray(cols) %}
        SELECT {{ join(cols, ', ') }}
    {% else %}
        SELECT {{ cols }}
    {% endif %}
        FROM {{ table }}
    {% if isNumber(nrows) %}
        LIMIT {{ nrows }}
    {% endif %}
"

sql <- render(sql_template, table='foo', cols="['puff', 'plaf']", nrows=10)

Some context (let me know if I missed something):

  • R version 4.1.3 (2022-03-10)
  • jinjar version 0.1.0
  • System Ubuntu 20.04

Convert C++ exceptions to R conditions

When rendering template, catch inja.exception.render_error and convert to R condition using rlang::abort(). This condition can store the reason, the missing data variable, and the location (line no, char no). From this metadata, we can construct a very helpful error message.

Behaviour of `path_loader`

I've used jinja a lot in Python projects so thought I'd try this package for R, but having some problems loading child templates with include:

I have a base template:

Include here.
{% include "me.tmpl" %}

and in the same folder the me.tmpl template:

This is me

From that location as working folder, rendering fails (as expected) because there's no loader specified to get the sub-template:

> render(fs::path("base.tmpl"))
Error: [inja.exception.render_error] (at 2:12) include 'me.tmpl' not found

Adding a path_loader with either "/", "." or "" (empty string) as the path results in a working include:

> confz = jinjar_config(loader=path_loader("/"))
> render(fs::path("base.tmpl"), .config=confz)
[1] "Include here.\nThis is me\n\n"
> confz = jinjar_config(loader=path_loader("."))
> render(fs::path("base.tmpl"), .config=confz)
[1] "Include here.\nThis is me\n\n"
> confz = jinjar_config(loader=path_loader(""))
> render(fs::path("base.tmpl"), .config=confz)
[1] "Include here.\nThis is me\n\n"

However, running from the parent folder, none of the above work (as expected) but I can't make anything work. Not the obvious relative path:

> confz = jinjar_config(loader=path_loader("./templates"))
> render(fs::path("templates/base.tmpl"), .config=confz)
Error: [inja.exception.file_error] failed accessing file at 'me.tmpl'

Note the slightly different error message to "include 'me.tmpl' not found" above.

Or even an absolute path:

> confz = jinjar_config(loader = path_loader(file.path(getwd(),"templates")))
> render(fs::path("templates/base.tmpl"), .config=confz)
Error: [inja.exception.file_error] failed accessing file at 'me.tmpl'

Although the path is correct and the template is there:

> confz
Path loader: /tmp/jinja/templates
Syntax: {% block %} {{ variable }} {# comment #}> 
> file.exists("/tmp/jinja/templates/me.tmpl")
[1] TRUE

So am I misunderstanding something about template loading here? What is the path relative to? The working directory, the parent template, or something else? Why does "/" work in the first instance?

I think I might have to set my working directory to the top level folder I'm rendering to get any of this to work, but I don't want to have to wrap every render call in a withr::with_dir or similar. Am I missing something?

Release jinjar 0.3.0

Prepare for release:

  • git pull
  • Check current CRAN check results
  • Check if any deprecation processes should be advanced, as described in Gradual deprecation
  • Polish NEWS
  • devtools::build_readme()
  • urlchecker::url_check()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • revdepcheck::revdep_check(num_workers = 4)
  • Update cran-comments.md
  • git push

Submit to CRAN:

  • usethis::use_version('minor')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • git push
  • usethis::use_github_release()
  • usethis::use_dev_version()
  • git push

How to pass length-1 vector as vector?

Hi!

R has this weird quirk that everything is a vector, even strings.

If my template uses a for loop on a vector, it succeeds if there's more than one entry but crashes if its length is just one, I guess some internal logic casts it to a string/number instead of an iterable with one element. This makes my templates crash randomly and I had to implement a helper function to manually cast vectors of length one to a list when I need to iterate over it. That's quite ugly...

I'm not sure if you can do anything about this, I guess you need to guess the data types and convert them for the C++ data types and you can't know if someone wants to iterate over a vector or use it as a string, but I thought I leave a ticket as this is an annoying caveat of R. Maybe add some documentation if you can't fix it haha

Release jinjar 0.3.1

Prepare for release:

  • git pull
  • Check current CRAN check results
  • Polish NEWS
  • urlchecker::url_check()
  • devtools::build_readme()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • revdepcheck::revdep_check(num_workers = 4)
  • Update cran-comments.md
  • git push

Submit to CRAN:

  • usethis::use_version('patch')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • usethis::use_github_release()
  • usethis::use_dev_version(push = TRUE)

Release jinjar 0.2.0

Prepare for release:

  • git pull
  • Check current CRAN check results
  • Check if any deprecation processes should be advanced, as described in Gradual deprecation
  • Polish NEWS
  • devtools::build_readme()
  • urlchecker::url_check()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • rhub::check(platform = 'ubuntu-rchk')
  • rhub::check_with_sanitizers()
  • revdepcheck::revdep_check(num_workers = 4)
  • Update cran-comments.md
  • git push

Submit to CRAN:

  • usethis::use_version('minor')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • git push
  • usethis::use_github_release()
  • usethis::use_dev_version()
  • git push

Release jinjar 0.1.0

First release:

Prepare for release:

  • devtools::build_readme()
  • urlchecker::url_check()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • rhub::check(platform = 'solaris-x86-patched')
  • rhub::check(platform = 'ubuntu-rchk')
  • rhub::check_with_sanitizers()
  • Review pkgdown reference index for, e.g., missing topics

Submit to CRAN:

  • usethis::use_version('minor')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • usethis::use_github_release()
  • usethis::use_dev_version()
  • usethis::use_news_md()

Error rendering braces

Hi,

Thanks for this excellent package. I ran into what appears to be a bug while rendering some JSON:

The following works as expected : jinjar::render('hello {{name}}!', name = 'world')

[1] "hello world!"

However the following with enclosing braces does not: jinjar::render('{hello {{name}}!}', name = 'world')

Error in glue(str, .envir = .envir, .transformer = transformer, .cli = TRUE, : Unterminated quote (')

sessionInfo()

R version 4.3.1 (2023-06-16)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/liblapack.so.3;  LAPACK version 3.9.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
 [4] LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: /UTC
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] compiler_4.3.1    magrittr_2.0.3    cli_3.6.1         tools_4.3.1       fs_1.6.3         
 [6] jinjar_0.3.0      rstudioapi_0.15.0 vctrs_0.6.4       knitr_1.44        jsonlite_1.8.7   
[11] xfun_0.40         lifecycle_1.0.3   rlang_1.1.1       purrr_1.0.2      

Add FAQ

There are quite a few gotchas where jinjar diverges from Jinja. Unfortunately, these tidbits are buried in the Template Syntax vignette which is quite a long document. I think we probably need a separate document that surfaces the most important (and confusing) gotchas.

  • String literals require double-quotes
  • String literals escaping
  • Enforcing vector variables

Passing Functions to `render`

Thank you for this awesome package.

I am trying to add functions to ... in render, but I don't believe I can. Here is a small reprex:

add_w <- function(x){paste0("w", x)}
add_w("orld") # returns "world"

render("hello {{add_w('orld')}}", add_w=add_w)

The above throws the error:

Error: [inja.exception.parser_error] (at 1:21) unknown function add_w

Is this not possible with jinjar, or should I be doing something differently?

Thanks for your help.

String literal with single-quotes causes error "Variable not found"

Variable comparison inside a template throws an error. Below is a reproducible example.

R code

library(jinjar)

render(
  fs::path("template.txt"),
  variable = 'test'
)

Template file template.txt

{# this works #}
{% if 1 == 1 %}
  {{ variable }}
{% endif %}

{# this does NOT work #}
{% if variable == 'test' %}
  {{ variable }}
{% endif %}

Error

Error in `render()`:
! Problem encountered while rendering template.
Caused by error:
! Variable 'test' not found.
ℹ Error occurred on line 7 and column 20.
Run `rlang::last_trace()` to see where the error occurred.

Session information

R version 4.3.1 (2023-06-16)
Platform: x86_64-apple-darwin20 (64-bit)
Running under: macOS Ventura 13.5

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-x86_64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/Los_Angeles
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] jinjar_0.3.1   jsonlite_1.8.7

loaded via a namespace (and not attached):
 [1] utf8_1.2.4        R6_2.5.1          xfun_0.41         tidyselect_1.2.0  magrittr_2.0.3    glue_1.6.2        tibble_3.2.1      knitr_1.45       
 [9] pkgconfig_2.0.3   dplyr_1.1.3       generics_0.1.3    lifecycle_1.0.3   cli_3.6.1         fansi_1.0.5       vctrs_0.6.4       DBI_1.1.3        
[17] compiler_4.3.1    rstudioapi_0.15.0 tools_4.3.1       pillar_1.9.0      fs_1.6.3          rlang_1.1.1      

Problem parsing double quote

I would like to check if a value is equal to this string -> "/ *" and the quotation mark is a part of the value. I tried to run {% if x != '"/ *"' %} but the rendering did not work. Is there any way to work around it so I can completely render the quotation mark?

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.