Jekyll print custom JSON string from data - jekyll

Let's say I have an array of hashes like so;
site.foo = [
{
"foo": "bar",
"baz": ["a", "b", "c"]
},
{
"foo": "baz",
"baz": ["x", "y"]
},
...
]
I want to get all values (bar, a, b, c, baz, x, y) in an array and output it as JSON in a javascript variable in a template.
How would I do this?

I assume that you know how your datas are organized.
{% assign splitMark = "###" %}
{% assign arr = "" %}
{% for el in site.foo %}
{% assign arrSize = arr | size %}
<!-- if we already have something in our string
add the splitMark at the end -->
{% if arrSize > 0 %}
{% capture arr %}{{arr}}{{splitMark}}{%endcapture%}
{% endif %}
<!-- array + foo.foo value + splitMark -->
{% capture arr %}{{arr}}{{el.foo}}{{splitMark}}{%endcapture%}
<!-- transform an array to a string
with elements delimited by splitMark -->
{% assign bazStr = el.baz | join: splitMark %}
<!-- array + transformed array -->
{% capture arr %}{{arr}}{{bazStr}}{%endcapture%}
{% endfor %}
<!-- transform our string to an array splitting at the splitMark -->
{% assign arr = arr | split: splitMark %}
<script>
var my_var = {{ arr | jsonify }};
</script>
If you don't know your exact data structure, eg:
-
foo: "bar"
baz: ["a", "b", "c", 10]
-
foo: baz
baz: ["x", "y"]
any: other
key: ['toto','titi']
You have to go the plugin way. Create a file _plugins/flatten.rb containing :
module Jekyll
module FlattenArray
def flatten( datas )
datas.flat_map(&:values).flatten
end
end
end
Liquid::Template.register_filter(Jekyll::FlattenArray)
You can now flatten your datas like this :
var my_var = {{ site.foo | flatten | jsonify }};

Related

Not able to access dictionary value in Macro DBT

I am accessing a dictionary variable which is defined in the marco
{% macro normalize_state(column_name) -%}
{% set states_dict = {
"Alabama" : "AL",
"Alaska" : "AK",
...
....
} -%}
CASE WHEN {{column_name}} IS NOT NULL THEN '{{ states_dict.get(column_name) }}'
ELSE NULL END
{%- endmacro %}
But '{{ states_dict.get(column_name) }}' produced the output None
Macros are compiled (templated) before the query is run. That means that the data in your database doesn't run through the jinja templater.
{{ states_dict.get(column_name) }} looks up the name of the column in your dictionary, not the data it contains.
What you need to do is use your dictionary to write a case statement that performs the get operation. That looks like this:
{% macro normalize_state(column_name) -%}
{% set states_dict = {
"Alabama" : "AL",
"Alaska" : "AK",
...
} -%}
case
{% for k, v in states_dict.items() %}
when {{ column_name }} = '{{ k }}'
then '{{ v }}'
{% endfor %}
end
{%- endmacro %}
Then you need to pass in column_name as a string (quoted) when you call the macro:
select {{ normalize_state("my_column") }}

After rendering a Jinja2 template, can I get the set values?

I want to render a Jinja2 template, and after the rendering, read the values that were set by it:
import jinja2
template = jinja2.Template("""
{% set number = 42 %}
Hello {{name}} {{number}}!
""")
vars = {"name": "Ned"}
print(template.render(vars).strip())
print(vars)
This prints:
Hello Ned 42!
{'name': 'Ned'}
Is there something I can do after template.render that would give me the value of number? Also, I need to do it without knowing the name number beforehand. I want to discover whatever values have been set in the template.
Here's a way to do it without using functions that are documented, but say not to use them:
import jinja2
template = jinja2.Template("""
{% set number = 52 %}
{% set choice = "apple" %}
Hello {{name}} {{number}}!
{% if number > 50 %}
More than 40!
{% set also = 'foo' %}
{% endif %}
""")
ctx = template.new_context(vars = {"name": "Ned"})
template.render(ctx)
mod = template.module
template_vars = {n:getattr(mod, n) for n in dir(mod) if not n.startswith("_")}
print(template_vars)
This prints:
{'also': 'foo', 'choice': 'apple', 'number': 52}
I guess, this is a kinda hack but it seems you can do something with Context from jinja2.runtime like
$ cat show_vars.py
import jinja2
template = jinja2.Template("""
{% set number = 42 %}
Hello {{name}} {{number}}!
""")
vars = {"name": "Ned"}
print(template.render(vars).strip())
print(vars)
from jinja2 import Environment
from jinja2.runtime import Context
env = Environment()
ctx = Context(env, vars, '', template.blocks)
list(template.root_render_func(ctx))
print(ctx.vars)
print(ctx.get_all())
$ python show_vars.py
Hello Ned 42!
{'name': 'Ned'}
{'number': 42}
{'name': 'Ned', 'number': 42}
As #nedbat pointed out, one can also achieve the same with
import jinja2
template = jinja2.Template("""
{% set number = 42 %}
Hello {{name}} {{number}}!
{% if number > 50 %}
More than 40!
{% set also = 'foo' %}
{% endif %}
""")
ctx = template.new_context(vars = {"name": "Ned"})
list(template.root_render_func(ctx))
print("get_all:", ctx.get_all())
print("exported_vars:", ctx.exported_vars)
print("vars:", ctx.vars)
With the context provided:
import jinja2
import re
template = jinja2.Template("""
{% set number = 42 %}
Hello {{name}} {{number}}!
""")
vars = {"name": "Ned"}
rendered = template.render(vars).strip()
print(vars)
print(rendered)
changed_var_value = re.findall(r'Hello[\s\S]*\s{1}(.*)!', rendered)
print(f'var after the greeting: {changed_var_value}')
output:
{'name': 'Ned'}
Hello Ned 42!
var after the greeting: ['42']

What Jekyll syntax to filter key:value pair in Front Matter?

In my Jekyll site, I have a page that stores an array of data in front matter, like this:
---
layout: page
title: MyTitle
array:
- key1: value1
- key2: value2
---
What I want to do in my template: given a keyX, obtain valueX from the array.
I figured out a way to access the array:
{% assign subpage = site.pages | where: 'title', 'MyTitle' %}
{% assign array = subpage[0].array %}
Now the query that I need to write is: "from the array, extract the value that matches keyX".
Is there a way to search through the array, without the need for looping? All the examples I can find are based on one-dimensional arrays...
Your array is an array of non standardized objects (they don't have the same keys).
{{ page.array | inspect }}
returns
[{"key1"=>"value1"}, {"key2"=>"value2"}]
Here the only way to search is to loop over all array's items.
If you refactor your array to become an object, you can then get value from a key.
---
[...]
object:
key1: value1
key2: value2
...
Example :
{% assign searched = "key1" %}
{{ page.object[searched] }}
I found this workaround in the meantime:
{% for valueList in array %}
{% for valuePair in valueList %}
{% if valuePair[0] == "key1" %}
{% assign value = valuePair[1] %}
{% endif %}
{% endfor %}
{% endfor %}

Block content for passing variables from python to HTML

I'm trying to send some data from my python code to the HTML script.
This is my python code. I use lists to send the data.
def extractMetaData(request):
pdfDir = "C:/PythonPrograms/pdf/"
if pdfDir == "": pdfDir = os.getcwd() + "\\"
pdf_title = []
pdf_author = []
pdf_creationdate = []
pdf_creator = []
pdf_Keywords = []
pdf_producer = []
for pdf in os.listdir(pdfDir):
fileExtension = pdf.split(".")[-1]
if fileExtension == "pdf":
pdfFilename = pdfDir + pdf
pdf_toread = PdfFileReader(open(pdfFilename, "rb"))
pdf_title.append(pdf_toread.getDocumentInfo().title)
pdf_author.append(pdf_toread.getDocumentInfo().author)
pdf_creationdate.append(pdf_toread.getDocumentInfo()['/CreationDate'])
pdf_creator.append(pdf_toread.getDocumentInfo()['/Creator'])
pdf_Keywords.append(pdf_toread.getDocumentInfo()['/Keywords'])
pdf_producer.append(pdf_toread.getDocumentInfo().producer)
return render(request,'personal/extract.html',{'content':[str(pdf_title),str(pdf_author),str(pdf_creationdate), str(pdf_creator),str(pdf_Keywords), str(pdf_producer)]})
In HTML I use the following code.
{% block content %}
{% for c in content%}
<p>{{c}}</p>
{% endfor%}
{% endblock %}
But this prints all the items of the first list and then all items of second list and so on.. I want it to print the first item of the first list, first item of the second list and so on. Then start with second item of every list.. how can do this in jinja?
I would do it like this :
def extractMetaData(request):
pdfDir = "C:/PythonPrograms/pdf/"
if pdfDir == "":
pdfDir = os.getcwd() + "\\"
pdf_files = []
for pdf in os.listdir(pdfDir):
pdf_file = {} # Create a dictionnary to store values
fileExtension = pdf.split(".")[-1]
if fileExtension == "pdf":
pdfFilename = pdfDir + pdf
pdf_toread = PdfFileReader(open(pdfFilename, "rb"))
pdf_file['title'] = pdf_toread.getDocumentInfo().title
pdf_file['author'] = pdf_toread.getDocumentInfo().author
pdf_file['creationdate'] = pdf_toread.getDocumentInfo()['/CreationDate']
pdf_file['creator'] = pdf_toread.getDocumentInfo()['/Creator']
pdf_file['keywords'] = pdf_toread.getDocumentInfo()['/Keywords']
pdf_file['producer'] = pdf_toread.getDocumentInfo().producer
pdf_files.append(pdf_file)
return render(request,'personal/extract.html', {'pdf_files': pdf_files})
And then in the template :
{% block content %}
{% for pdf in pdf_files %}
{% for item_name, item in pdf_file.items %}
<p>{{ item_name }} : {{ item }}</p>
{% endfor %}
{% endfor%}
<hr>
{% endblock %}
I didn't test it so it might have some errors..

How to limit number of iterations when looping through object in nunjucks

I have a js object like this:
var data = [
{ src: "src1", name: "name 1" },
{ src: "src2", name: "name 2" },
{ src: "src3", name: "name 3" }
]
I am looping through it with Nunjucks:
{% for object in data %}
{{object.src}}
{% endfor %}
But I want to limit the number of iterations to 2.
How do I do that with Nunjucks?
I know there is a range option but I couldn't find how to use it in this case.
You could accomplish this a couple different ways:
A) Use loop.index0 special variable (Demo in Codepen)
Inside a for loop, you can use loop.index0 instead limit-var
{% for obj in data %}
{% if loop.index0 < 2 %}
{{obj.src}}: {{obj.name}}
{% endif %}
{% endfor %}
B) Add Custom Filter (Demo in Codepen)
But more readable is add custom filter limit and use it
var nunjucks = require('nunjucks');
var env = nunjucks.configure();
var data = [
{src: "src1", name: "name 1"},
{src: "src2", name: "name 2"},
{src: "src3", name: "name 3"}
];
env.addFilter('limit', function(arr, limit) {
return arr.slice(0, limit);
});
var res = nunjucks.renderString(`
{% for obj in data | limit(2) %}
{{obj.src}}: {{obj.name}}
{% endfor %}`,
{data}
);
console.log(res);
C) Use native slice() function (Demo in Codepen)
{% for obj in data.slice(0, 2) %}
{{obj.src}}: {{obj.name}}
{% endfor %}