After preparing a reply to @apetryk about a post he made months ago about macros… I decided to share a lot of small findings here, it’s really raw because I wanted ways for “you” to (re)discover things by having a solid playground : meaning you won’t need much more than a demo server but beware! you may still lose a few neurons or blow a fuse in the process!
The backstory? If I always understood that curing the wiki takes time, that snippets are easier to write, they are not so easy to understand until you try… So I couldn’t help but feel, and way before I read about Macros, that a lot feels like being at your fingertips, people helping make it so easy, and so simple! The fun begins, then suddenly you realize how little you know, and turn to the forum, chat, twitter, for help (or not)…
The hardest to grasp, and I started there because we all see the “for loop” example in dev-template, and may just want to make it “grow”, not caring much “how”.
Macros are indeed a huge timesaver, but here is also my understanding of the acronym “DRY” (Do not Repeat Yourself). I didn’t need to repeat so much text in my data_templates and message: > (but it sure was easier), but I now I’m slowly replacing a lot of crap by snippets which help format many sensors inputs, I started to rewrite long or multiple scripts, because macros are so much nicer in ** picking different outcomes** : maybe you’ve already seen how a list of all states is generated, but did you also consider using it to generate some parts of your config file(s) ? World blown when I understood I didn’t need to look for the names of the fresh sensors I created to put an icon in customize: you can just have HA rewrite the damned thing and leave blanks after icon: mdi:
I still made most of my findings by RTFM at http://jinja.pocoo.org but often felt a lot of examples didn’t apply, me having no input file. Yes they’re meant to save time to HTML coders, and you’ll see - in a very n00b way - how I realized that most actually do apply to HA.
But in the last weeks, after reading these, a lot of things untangled at once :
https://www.webforefront.com/django/createreusablejinjatemplates.html
https://www.webforefront.com/django/usebuiltinjinjastatements.html
So if I felt the community inputs an amazing help, I always imagined the most advanced users are very likely busy either “building stuff”, or “fixing stuff”, only to stop-by occasionally. I knew we wouldn’t share the same understanding of “going far”, but I what I knew true is: to go fast, go alone.
The next steps will most likely bring you to tears, and surely make a few old timers cringe
I was too lazy to learn vim, but by luck ST3 helped me free some time, and winter feels a good time to gift some of it all around.
As I started and finished this post in dev-template, just copy/paste the text below to see crazy speak unfold. I had some of it ready, but it took 5h to make sure there was no error. No error… engrish much? Well, there will be a lot of typos, it started as a test (to if I learned anything) but, being all the stuff learned over months, I focused on making everything work, not the grammar, not the spelling (English is not my mother tongue, so you’ll have to excuse my French).
My goal was to make it gradually more challenging, while self-explanatory and compact. So it won’t be until you give it a try (to break the logic), that you’ll see it’s not just the sum of all parts.
# A MACRO DEFINITION, has no output in DEV TEMPLATE
# What you will never notice at first, as it only impacts outputs of dev-template, is how should be really careful about using >- or {{"{"}}%- : no logic there, but adding a '-' everywhere is often asking for more troubles.
# You forget about this after getting more confident with using snippet that worked, and don't see how and when it impacts the code from the lines above and bellow. Because even when you add a few lines before and after a snippet, it is a better way to check in dev-template, but it can (and very often it didà blow up silently, and even if script_check saying everything's ok, you will feel something is off.
So, macros, there's actually different ways to define them, depending if you want to have them 'apply some logic to something' or just 'repeat output'.
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
{% macro sni(onoff='') %}
- service: home-assistant.turn_{{onoff}}
entity_id: script.{{- caller() -}}
{% endmacro %}
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
So to quickly prepare new elements, let's say for a script sequence :
========================================
{%- call sni(onoff='off') %}yesitworks{% endcall %}
{%- call sni(onoff='off') %}toreplicate_text{% endcall %}
{%- call sni(onoff='off') %}but_what_about_running_it{% endcall %}
{%- call sni(onoff='off') %}.._these_dots_will_be_an_issue{% endcall %}
{%- set oneofseveralrules_no_regex_needed = '..._these_dots_will_be_an_issue' %}
{% set oneofseveralrules_no_regex_needed = oneofseveralrules_no_regex_needed.replace('.','').replace('will_be','are_not').replace('_these','') %}
{%- call sni(onoff='off') %}{{ oneofseveralrules_no_regex_needed }}_anymore{% endcall %}
========================================
# and now to filter strings with macros and see how they stack
# Wut? macro can be combined with another !?
{% macro cleancut(var) %}{{ var|string|trim|truncate(2,False,'') }}{% endmacro %}
{% macro getmeafterdollar(delimiter) %}{{ delimiter.split('$')[1] }}{% endmacro %}
{% macro givemeheadaches(delimiter) %}{{ delimiter.split('$')[0] }}{% endmacro %}
{%- set a=1 -%} {% macro teststrings() %} {%- set devtemplate_string = 'this_is_only_a_test' -%} {%- set devtemplate_dict = (('this','is','only','a','test')) -%} {%- set naming_convention = devtemplate_string.split('_')[0] -%}{{naming_convention}} {%- set can_help = devtemplate_string.split('_')[1:5] -%}{{can_help}} $output {%- endmacro -%}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Variables defined above, can only be used in the macro : {{teststrings() }}
Here's to testing if the variable is still available after running the macro :
{{ teststrings() }} {{ (can_help is defined ) }}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
So, getmeafterdolar() would me the first result after the $ sign
and also fails silently if you add the following code and make it run alone : {{ '{{' }} getmeafterdollar() }} (although it's only temporary in dev-template, you can now see why you want to understand the more you can : otherwise more stuff == more headaches )
------------------------------------------
You can set variables with a macro : {{teststrings() }}
And if I want what's after $ {{ getmeafterdollar(teststrings()) }}
And if I wanted {{ givemeheadaches(teststrings()) }}
Let's say you want to format 3 input_sliders to hours:minutes:seconds !
{% macro hhmmssbase(hh='12', mm='34', ss='52') -%}
{{ '{{' }} '%0.02d:%0.02d:%0.02d'|format({{hh|string|truncate(2,False,'')|int}},{{mm|string|truncate(2,False,'')|int}},{{ss|string|truncate(2,False,'')|int}}) {{ '}}' }}
{%- endmacro %}
{% macro hms(hh, mm, ss) -%}
{{ '{{' }} '%0.02d:%0.02d:%0.02d'|format({{hh|string|truncate(2,False,'')|int}},{{mm|string|truncate(2,False,'')|int}},{{ss|string|truncate(2,False,'')|int}}) {{ '}}' }}
{%- endmacro %}
{% macro runhms(hh='12', mm='34', ss='52') -%}
{{ caller }}
{% endmacro %}
Using defaults : {{ hhmmssbase() }}
Changing just the last value: {{ hhmmssbase(ss=59.000001) }}
Changing the second value another way: {{ hhmmssbase(',' , 59.000001 , "extra", ) }}
Changing the second value and last is default: {{ hhmmssbase( (( 12 , '59.000001' , ' ' )) ) }}
^^^^^^^^^^^^^^
========================================
========================================
========================================
Let's go crazy : what are loops for ? Dictionary {{ devtemplate_dict is iterable and devtemplate_dict is mapping }}
# Back to macro arguments : the quoted values will be used in case there are missing inputs but I still need want a value to be there...
{% macro hhmmss(hh='12', mm='34', ss='52', give='moar!', me='tricks') %}
--
Macro name : {{ hhmmss.name }}
---
All the macro keywords in .arguments, I can use [0:x] to extract :
{{ hhmmss.arguments }} but only the 2nd : {{ hhmmss.arguments[1] }}
All default outputs : .defaults (likewise [0:x] will extract)
{{ hhmmss.defaults }}
--
Will catch extra positional arguments (varargs) : {{ hhmmss.catch_varargs }}
Will catch extra keywords and their arguments (kwargs) : {{ hhmmss.catch_kwargs }}
---
# Wait, stop, extra positional & keyword arguments, wtf?
--
Variable/positional arguments : {{ hhmmss.varargs }}
Extracting the first argument, or more, from a dict : {{ varargs[0] }} {{ varargs[0:3] }}
Could I use for loop to get list of items ? {{ varargs is iterable }} but do I need to?
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
Extra keyword arguments: {{ kwargs }}
This looks different, how do I get the content?
I tried {{ kwargs[0] }}, and I see there's something {{ kwargs|list }}, but it doesn't help!
{{ kwargs is iterable and kwargs is mapping }}, because there's more than just one item.
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
---------------
{% for item in kwargs %}Current cycle is {{loop.index}} and {{ loop.index0 + 10//10 }} !? of {{ loop.length }} ! Yes, it is rarely mentioned, and rarely useful, but index0 can still help, and a good reminder about shell script arrays.
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
Here's how all my {{ item }} and {{ kwargs[item] }} can be extracted !
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
Wait, all this for 1 {% set itemvalues = kwargs[item] -%}{% for ionlyneedsomecharacters in itemvalues if itemvalues is defined %} {%- if loop.index in [13,14,15] -%} {{ ionlyneedsomecharacters[0] }} {%- endif -%} {% endfor %} ?
Well {{ ( '1 ' + itemvalues[12:15] ~ ' can still mean troubles and headaches' ) }} but even above, I still only went down {{loop.depth}} level... there's {{ hhmmss.defaults[3] }}
{% endfor %}
{% for value in kwargs %} {{loop.length}} {{value}} {{kwargs}} {% endfor %}
{% for item in kwargs %} {% set keywordname = item %} {% endfor %}
Extracting Keyword-Argument directly {{ kwargs.unconsumedkeyword }}
============
Result after parsing arguments :
{{ '{{' }} '%0.02d:%0.02d:%0.02d'|format({{hh|string|truncate(2,False,'')|int}},{{mm|string|truncate(2,False,'')|int}},{{ss|string|truncate(2,False,'')|int}}) {{ '}}' }}
============
{% endmacro %}
# -------------------------------------------
# CALLING THE MACRO : hhmmss
# -------------------------------------------
{{ hhmmss( '12.34567890', '10//3', '10_damnitsastring', "unconsumedargument") }}
{{ hhmmss(unconsumedkeyword='unconsomumedarg') }}
{% macro testa(testargs, a, b) %}
--
Macro name : {{ testa.name }}
Args {{ testargs, a, b }}
It's a tuple with one value, our extra argument {{ varargs }}
It was already easy to extract those as a string : {{ varargs[0]|trim }}
But now you how it's {{ hhmmss.name is defined }}, that if macros are available all around, you may also need loops to get useful feedback if things go wrong...
{% endmacro %}
# -------------------------------------------
# CALLING THE MACRO : test a
# -------------------------------------------
{{ testa('aaa', 'bbb', 'ccc', "extra argument") }}