Comments Field Template - dev questions

I’m working on a plugin that allows for a template to be used to define how the comments field will be filled. For example a template such as [$rating] %upper{%left{$context, 3}} $tags ::: $comments would lead to a comments field like [4] CHI #acid,#vocal ::: blah blah blah. The reason is outside of beets in software for DJing I obviously can’t see these flexible attributes and I think the comments field made the most sense to add these other details. If there is a better way do let me know.

It’s my first time digging through beets so I’m having a bit of trouble following all the logic but I’ve learned a few things too. I’ve also realized my initial configuration concept is not going to work or would be more complex than necessary. I have two main questions at this point that could help me.

First, I want to re-use some of the classes in beets for path configuration or for the format_item configuration for my purpose to construct a comments field. I have already found beets.utils.functemplate.Template which has the following odd behaviour I would like to understand.

tmpl('$rating %upper{$title}')
s1 = template.substitute(item)
s2 = template.substitute(item, item._template_funcs())
assert s1 == '3 %upper{My Title}'
assert s2 == '3 MY TITLE'

What is the deal with _template_funcs and why does it need to be called for functions to be substituted in the template when _template_funcs is not an implemented function as seen in beets.dbcore.db.py:264?

Second, is there not a hook in beets to see what fields are being modified prior to the actual modification of the item class? So I could examine the changes to the item and if the item makes a change to the comments or a field I defined in my comments template I would have the comments field rewritten to follow the template instead of rewriting the comments field every time an item is modified.

Thank you.

Woohoo; looks line an awesome plugin!

The idea is that the templating library itself has no built-in fields or functions. So it contains the logic for invoking functions but doesn’t know anything about %upper per se, nor any other function. _template_funcs is implemented here:

And provides the list of template functions defined here:

But template functions can also come from plugins.

There’s no event exactly like that (intercept all field updates on a model object), but there is a write object that lets you change an Item before it gets written to disk.

Okay so using the write event is working for me. Infact I have a working plugin now but it needs clean up.

Anyways, I dug further into the code and got stuck trying to understanding the use of AST for formatting code. Could you explain a bit of the history that lead to using AST and is there anything currently available that may replace the use of AST in the future? Perhaps it is necessary but I feel like it is overkill for what it does but there must be a reason that lead to its use.

Second, I want to use the if formatter but I’m failing to do something I thought would be a regular use case. For example, I want to format a string if an attribute is defined and if not put a default string value. Could you point to examples of the %if formatter use?

The function template formatter you’re referring to:

Has both an interpreter (the evaluate methods) and a compiler (the translate method). There are two ASTs involved:

  • Both use an internal AST to represent the structure of a given template expression. Using an AST may seem esoteric, but it is definitely the simplest way to make sure that template processing is correct. It would be very complicated to try to evaluate a template without first parsing it into an AST.
  • The compiler emits a Python AST for evaluation. This is purely a performance optimization and it is optional in the sense that it produces the same result as the interpreter. But it does make template evaluation a lot faster.

The %if function is actually not part of all that; it’s defined as a “normal” function like any other, here:

As you can see there, %if only checks whether the first part (the condition) is, like, 0 or false or the empty string. If you use an undefined field, that will expand to $some_field, which is neither 0 nor false nor empty. For what you want, I think you are interested in %ifdef instead, which is in the docs but also discussed here:

Okay so thank you for the prompt response.

With %if or %ifdef is it possible to ask %if{‘if “vocal” in tags’, ‘V’, ‘-’}? I have not been able to do that and digging into the code I got lost trying to navigate the AST code in pdb.

If it is not possible I will be interested in making it, but I’ll probably be back with more questions to complete it.

Hi! Yes, %ifdef should probably work for that. Can you describe what you have tried so far using %ifdef and what happened with it?

What I have been trying is something like the following:

%if{comments == "test",+,-}

or something like this

%if{"test" in comments, +, -}

Am I missing something or just doing it wrong?

Ah, I see—I had misunderstood! No, %if does not support Python expressions like == or in. As I mentioned above:

%if only checks whether the first part (the condition) is, like, 0 or false or the empty string.

This FAQ has some insight into how to accomplish what you want using the inline plugin:
https://beets.readthedocs.io/en/stable/faq.html#create-disc-n-directories-for-multi-disc-albums

Of course there’s already a plugin for what I want. Sorry I didn’t read everything thoroughly enough.

I guess I can go back to finishing my plugin now. :slight_smile:

  • I’m assuming inline plugin will make these variables available everywhere including my plugin and not only available to the path: template strings as written in the documentation.