Define and compute custom global variables (in front matter or `_config.yml`) #5868

Closed
alecive opened this Issue Feb 7, 2017 · 9 comments

Comments

Projects
None yet
5 participants
@alecive

alecive commented Feb 7, 2017

  • I believe this to be a bug, not a question about using Jekyll.
  • I updated to the latest Jekyll (or) if on GitHub Pages to the latest github-pages
  • I read the CONTRIBUTION file at https://jekyllrb.com/docs/contributing/
  • This is a feature request.

  • I am on (or have tested on) macOS 10+
  • I am on (or have tested on) Debian/Ubuntu GNU/Linux
  • I am on (or have tested on) Fedora GNU/Linux
  • I am on (or have tested on) Arch GNU/Linux
  • I am on (or have tested on) Other GNU/Linux
  • I am on (or have tested on) Windows 10+

  • I was trying to install.
  • There is a broken Plugin API.
  • I had an error on GitHub Pages, and I have reproduced it locally.
  • I had an error on GitHub Pages, and GitHub Support said it was a Jekyll Bug.
  • I had an error on GitHub Pages and I did not test it locally.
  • I was trying to build.
  • It was another bug.

My Problem / Feature Request

Hi, I failed to get around this problem on my own so I decided to create an issue here. My problem is that I would like to be able to compute and define custom global variables to use throughout my code. It would be great to have a central location where to compute them, and a standard way to use them in my code.

To make an example, I need to know how many years passed since 2010, both in forms of an integer (i.e. 7) and its corresponding string (i.e. seven).

This is what I did (please tell me if there are better ways):

{% assign yearDiffInt =    site.time | date: '%Y' | minus: 2010 %}
{% if    yearDiffInt ==  0 %}{% assign yearDiffStr =     "zero" %}
{% elsif yearDiffInt ==  1 %}{% assign yearDiffStr =      "one" %}
...
{% elsif yearDiffInt ==  5 %}{% assign yearDiffStr =     "five" %}
{% elsif yearDiffInt ==  6 %}{% assign yearDiffStr =      "six" %}
{% elsif yearDiffInt ==  7 %}{% assign yearDiffStr =    "seven" %}
{% elsif yearDiffInt ==  8 %}{% assign yearDiffStr =    "eight" %}
{% elsif yearDiffInt ==  9 %}{% assign yearDiffStr =     "nine" %}
{% elsif yearDiffInt == 10 %}{% assign yearDiffStr =      "ten" %}
{% endif %}

As of today, this code snippet would assign 7 to yearDiffInt and seven to yearDiffStr. Problem is that I need these two variables in multiple files in my _include folder, and the only workaround I found to this issue is to copy paste this snippet every time I need it, which is kind of cumbersome and counter-intuitive.

Now, I am aware that I can use Jekyll _include as custom liquid functions, but this would not be sufficient for my needs since I need some return arguments. Furthermore, similar solutions would only alleviate the problem, that is I still would need to compute yearDiffInt and yearDiffStr multiple times in my code.

Is there a better way to do this? Being able to define global variables (computed once, used everywhere) would be probably the best way. Ideally, I would like to be able to define them in the _config.yml and compute them in the beginning of the compilation. But maybe there is some other Jekyll / liquid trick I am not aware of.

Thank you!

@ashmaroli

This comment has been minimized.

Show comment
Hide comment
@ashmaroli

ashmaroli Feb 7, 2017

Member

Interesting problem...
DISCLAIMER: I'm not entirely sure if what I'm proposing will work..

  • Defining a variable in the main layout may be accessed in all child-level templates. Ergo if you have a variable string = "Hello World" in say default.html, calling it {{ string }} elsewhere (layouts inheriting from default.html or an include, may return the value "Hello World"

Tip:

  • Simplify the snippet by favoring {% capture %}--{% endcapture %} over multiple {% assign -- %} and {% case -- %} {% when -- %} {% endcase %} over multiple {% elsif -- %}
<!-- in main layout before `{{ content }}` -->

{% assign yearDiffInt = site.time | date: '%Y' | minus: 2010 %}

{% capture yearDiffStr %}
  {% case yearDiffInt %}
    {% when 1 %}
      "one"
    {% when 2 %}
      "two"
    ...
    {% when 9 %}
      "nine"
    {% when 10 %}
      "ten"
  {% endcase %}
{% endcapture %}
Member

ashmaroli commented Feb 7, 2017

Interesting problem...
DISCLAIMER: I'm not entirely sure if what I'm proposing will work..

  • Defining a variable in the main layout may be accessed in all child-level templates. Ergo if you have a variable string = "Hello World" in say default.html, calling it {{ string }} elsewhere (layouts inheriting from default.html or an include, may return the value "Hello World"

Tip:

  • Simplify the snippet by favoring {% capture %}--{% endcapture %} over multiple {% assign -- %} and {% case -- %} {% when -- %} {% endcase %} over multiple {% elsif -- %}
<!-- in main layout before `{{ content }}` -->

{% assign yearDiffInt = site.time | date: '%Y' | minus: 2010 %}

{% capture yearDiffStr %}
  {% case yearDiffInt %}
    {% when 1 %}
      "one"
    {% when 2 %}
      "two"
    ...
    {% when 9 %}
      "nine"
    {% when 10 %}
      "ten"
  {% endcase %}
{% endcapture %}
@alecive

This comment has been minimized.

Show comment
Hide comment
@alecive

alecive Feb 7, 2017

Thank you @ashmaroli for the reply.

I tried to assign the variable as you suggested, but apparently it is not working. Correct me if I am wrong, but this is my default layout:

<!DOCTYPE html>
<html>

    {% include head.html %}

    <body id="page-top" class="index">

    {% include header.html home="yes" %}
    {{ content }}
    {% include about.html %}
    {% include footer.html %}
    {% include js.html %}

    </body>
</html>

And I would have expected to be able to assign the variable in head.html and use it e.g. in about.html, but this does not seem to be working. That is, I have to re-assign the variable in about.html in order to be able to use it there.

Regarding your tip, I tried to use it in my code. Although much more elegant, the problem is that now every time I use yearDiffStr , I get a line break alongside the variable. That is, every time I use:

..{{yearDiffStr}} years...

I get:

... seven
years...

Any idea why? For the record, this is the code snippet:

{% assign yearDiffInt = site.time | date: '%Y' | minus: 2010 %}
{% capture yearDiffStr %}
  {% case yearDiffInt %}
    {% when  0 %}     zero
    {% when  1 %}      one
    {% when  2 %}      two
    {% when  3 %}    three
    {% when  4 %}     four
    {% when  5 %}     five
    {% when  6 %}      six
    {% when  7 %}    seven
    {% when  8 %}    eight
    {% when  9 %}     nine
    {% when 10 %}      ten
    {% when 11 %}   eleven
    {% when 12 %}   twelve
    {% when 13 %} thirteen
    {% when 14 %} fourteen
    {% when 15 %}  fifteen
  {% endcase %}
{% endcapture %}

alecive commented Feb 7, 2017

Thank you @ashmaroli for the reply.

I tried to assign the variable as you suggested, but apparently it is not working. Correct me if I am wrong, but this is my default layout:

<!DOCTYPE html>
<html>

    {% include head.html %}

    <body id="page-top" class="index">

    {% include header.html home="yes" %}
    {{ content }}
    {% include about.html %}
    {% include footer.html %}
    {% include js.html %}

    </body>
</html>

And I would have expected to be able to assign the variable in head.html and use it e.g. in about.html, but this does not seem to be working. That is, I have to re-assign the variable in about.html in order to be able to use it there.

Regarding your tip, I tried to use it in my code. Although much more elegant, the problem is that now every time I use yearDiffStr , I get a line break alongside the variable. That is, every time I use:

..{{yearDiffStr}} years...

I get:

... seven
years...

Any idea why? For the record, this is the code snippet:

{% assign yearDiffInt = site.time | date: '%Y' | minus: 2010 %}
{% capture yearDiffStr %}
  {% case yearDiffInt %}
    {% when  0 %}     zero
    {% when  1 %}      one
    {% when  2 %}      two
    {% when  3 %}    three
    {% when  4 %}     four
    {% when  5 %}     five
    {% when  6 %}      six
    {% when  7 %}    seven
    {% when  8 %}    eight
    {% when  9 %}     nine
    {% when 10 %}      ten
    {% when 11 %}   eleven
    {% when 12 %}   twelve
    {% when 13 %} thirteen
    {% when 14 %} fourteen
    {% when 15 %}  fifteen
  {% endcase %}
{% endcapture %}
@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Feb 7, 2017

Try {{ yearDiffStr | strip_newlines }}

And I would have expected to be able to assign the variable in head.html and use it e.g. in about.html, but this does not seem to be working. That is, I have to re-assign the variable in about.html in order to be able to use it there.

Yes, as I understand it, variables need to be defined within a specific post, page or document (in this case about.html) and not within theme files. Theme defaults for includes and layouts can be set in config.yml or in data files.

ghost commented Feb 7, 2017

Try {{ yearDiffStr | strip_newlines }}

And I would have expected to be able to assign the variable in head.html and use it e.g. in about.html, but this does not seem to be working. That is, I have to re-assign the variable in about.html in order to be able to use it there.

Yes, as I understand it, variables need to be defined within a specific post, page or document (in this case about.html) and not within theme files. Theme defaults for includes and layouts can be set in config.yml or in data files.

@alecive

This comment has been minimized.

Show comment
Hide comment
@alecive

alecive Feb 7, 2017

Try {{ yearDiffStr | strip_newlines }}

That works, thank you!

Theme defaults for includes and layouts can be set in config.yml or in data files.

But still, this allows for the creation of static variables that can be used everywhere, but not the computation of variable whose value dynamically changes, am I right?

alecive commented Feb 7, 2017

Try {{ yearDiffStr | strip_newlines }}

That works, thank you!

Theme defaults for includes and layouts can be set in config.yml or in data files.

But still, this allows for the creation of static variables that can be used everywhere, but not the computation of variable whose value dynamically changes, am I right?

alecive added a commit to alecive/alecive.github.io that referenced this issue Feb 7, 2017

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Feb 7, 2017

But still, this allows for the creation of static variables that can be used everywhere, but not the computation of variable whose value dynamically changes, am I right?

If the data files are unchanging from build to build then the variables are static, but if you generated the data files dynamically for each build then the variables update on each build. You can use Rake or Gulp to generate data files.

Or if you want dynamic variables you could try a Jekyll plugin which writes directly to the site config. Here's something to get you started: http://stackoverflow.com/questions/10674846 and http://stackoverflow.com/questions/10750755

One last option which I have not tried is putting Liquid code into data files, to see if Jekyll processes the Liquid and produce a new variable each build. I don't know if Liquid code in _data is processed or not.

ghost commented Feb 7, 2017

But still, this allows for the creation of static variables that can be used everywhere, but not the computation of variable whose value dynamically changes, am I right?

If the data files are unchanging from build to build then the variables are static, but if you generated the data files dynamically for each build then the variables update on each build. You can use Rake or Gulp to generate data files.

Or if you want dynamic variables you could try a Jekyll plugin which writes directly to the site config. Here's something to get you started: http://stackoverflow.com/questions/10674846 and http://stackoverflow.com/questions/10750755

One last option which I have not tried is putting Liquid code into data files, to see if Jekyll processes the Liquid and produce a new variable each build. I don't know if Liquid code in _data is processed or not.

@pathawks

This comment has been minimized.

Show comment
Hide comment
@pathawks

pathawks Feb 7, 2017

Member

I don't know if Liquid code in _data is processed or not.

Nope.

Member

pathawks commented Feb 7, 2017

I don't know if Liquid code in _data is processed or not.

Nope.

@ghost ghost referenced this issue in alecive/alecive.github.io Feb 7, 2017

Closed

Global include #12

@parkr

This comment has been minimized.

Show comment
Hide comment
@parkr

parkr Feb 11, 2017

Member

If you find that you're writing something like this over and over, you can use layouts as well to ensure that you're generating the correct info. We have discussed bringing in include_cache which would allow you to write this in a single include and save the time of recomputing it, but we have not yet integrated that (if you'd like to, feel free to submit a PR!) – I believe what you're asking for is an initialization file or something that allows for

{% assign site.yearsSinceIStartedMyBlog = site.time | date:"%Y" | to_integer | minus: 2010 %}

then you'd just use

{{ site.yearsSinceIStartedMyBlog }}

Alas, we don't have that kind of setup with Jekyll: it simply iterates through all your files in essentially alphabetical order and renders them one-by-one. If you want the field to be dynamic, I would really recommend using Liquid as-is and generating the value when you need it.

In terms of turning a 1 into one, that's a completely different story. For that, we'd be in plugin territory, as you'd want a third-party library (I don't believe Ruby stdlib has a function that does that for you) and we try to stay away from adding further third-party libraries wherever possible.

I'm going to close this for now, but we'll all see your responses here so feel free to reply. If you want to submit a PR to add some kind of global Liquid variable cache, I'd love to see a PR!

Member

parkr commented Feb 11, 2017

If you find that you're writing something like this over and over, you can use layouts as well to ensure that you're generating the correct info. We have discussed bringing in include_cache which would allow you to write this in a single include and save the time of recomputing it, but we have not yet integrated that (if you'd like to, feel free to submit a PR!) – I believe what you're asking for is an initialization file or something that allows for

{% assign site.yearsSinceIStartedMyBlog = site.time | date:"%Y" | to_integer | minus: 2010 %}

then you'd just use

{{ site.yearsSinceIStartedMyBlog }}

Alas, we don't have that kind of setup with Jekyll: it simply iterates through all your files in essentially alphabetical order and renders them one-by-one. If you want the field to be dynamic, I would really recommend using Liquid as-is and generating the value when you need it.

In terms of turning a 1 into one, that's a completely different story. For that, we'd be in plugin territory, as you'd want a third-party library (I don't believe Ruby stdlib has a function that does that for you) and we try to stay away from adding further third-party libraries wherever possible.

I'm going to close this for now, but we'll all see your responses here so feel free to reply. If you want to submit a PR to add some kind of global Liquid variable cache, I'd love to see a PR!

@parkr parkr closed this Feb 11, 2017

@alecive

This comment has been minimized.

Show comment
Hide comment
@alecive

alecive Feb 13, 2017

Thank you for the clarification @parkr . Thanks to @xhn35rq , I now moved to using a layout (see alecive/alecive.github.io#13). I didn't know I could use one to assign variables outside of its scope. Whilst this may save some lines of code, it does not save the need of recomputing the variable multiple times. To this end, include-cache would be super helpful!

What does prevent you to include it in the Jekyll codebase?

alecive commented Feb 13, 2017

Thank you for the clarification @parkr . Thanks to @xhn35rq , I now moved to using a layout (see alecive/alecive.github.io#13). I didn't know I could use one to assign variables outside of its scope. Whilst this may save some lines of code, it does not save the need of recomputing the variable multiple times. To this end, include-cache would be super helpful!

What does prevent you to include it in the Jekyll codebase?

@DirtyF

This comment has been minimized.

Show comment
Hide comment
@DirtyF

DirtyF Feb 13, 2017

Member

@alecive Jekyll could embed a lot of features, but its philosophy is to keep the core as simple as possible, minimize dependencies and provide extra features through plugins. Jekyll aims at simplicity, but let you add the features you need. Call it modular, extensible, whatever. It is as powerful but way more easier to maintain that way🔧

Member

DirtyF commented Feb 13, 2017

@alecive Jekyll could embed a lot of features, but its philosophy is to keep the core as simple as possible, minimize dependencies and provide extra features through plugins. Jekyll aims at simplicity, but let you add the features you need. Call it modular, extensible, whatever. It is as powerful but way more easier to maintain that way🔧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment