In "normal" code one can use klass.is_a?(Reference.class) to check whether or not a class was implemented with class or struct:
Array.is_a?(Reference.class) #=> true
Tuple.is_a?(Reference.class) #=> false
However, I can't seem to figure out how to do the same in a macro to reopen said class/struct.
For example: if i pass the Array class to my macro, I need to generate code starting with class Array, whereas for a Tuple it needs to be struct Tuple.
I read the docs for Crystal::Macros over and over, but I'm not able to come up with code that does what I need and compiles.
You can use < to check this:
class MyClass
end
struct MyStruct
end
p {{ MyClass < Reference }} # => true
p {{ MyClass < Struct }} # => false
p {{ MyStruct < Struct }} # => true
p {{ MyStruct < Reference }} # => false
However, I would recommend requiring users to use the macro inside said type. This way you don't need to reopen a class/struct because you are already inside it.
This is how many things in the standard library and the language are implemented. For example:
class Foo
# it's not "include Foo, Bar" where "include" reopens the type
include Bar
# It's not "JSON.mapping Foo, ..." where JSON.mapping reopens the type
JSON.mapping(...)
end
cjgajard on Gitter pointed out the following solution:
{% if Reference.all_subclasses.includes?(klass.resolve) %}
Related
When running Flask normally, everything described below works absolutely fine.
When running a build in Frozen-Flask however, I run into the following issue:
In my Jinja2 template I tried this at first, where I'm having to create the route strings of many url_for() functions from multiple sources:
{% macro my_macro(foo) %}
Link text
{% endmacro %}
Although this runs fine in Flask, when I do a build with Frozen-Flask I get the following error:
werkzeug.routing.BuildError: Could not build url for endpoint 'index_'. Did you mean...
As you can see, the value of foo is missing.
So I thought that's annoying, but maybe the string concatenation of 'index_' ~ foo happens after the url_for() is parsed. So to test my theory, I tried this instead:
{% macro my_macro(foo) %}
{% set route = 'index_' ~ foo %}
Link text
{% endmacro %}
But I get the exact same error. Exactly. So, as if it's setting the value of set route before concatenating the value of the foo variable onto the end of 'index_', and passing that incomplete value through to url_for().
However, if I substitute foo for 'foo' (so now foo is a string not a variable) then the concatenation works fine. So I can't concatenate string with string variables in Frozen-Flask?
Is there any way around this or is this maybe a possible bug in Frozen-Flask?
Update: It appears to be an issue with data coming from routes.py where I have return render_template('my_template.html', foo='bar'). This foo variable, although it is being passed through to the base template where in turn it is passed through to the macro (it can be output to prove the data present and correct) this data however cannot, it seems, be used to dynamically create a route for use by url_for().
i think will help you a simple line code
Link Text
considering foo as string only will be concatenate, no with string1+strin2, only using html printing.
Is it possible to check if a global constant (e.g. a TypeNode of Class, Struct, Module) has been defined by using a macro?
{% if defined?(::My_Class) %}
puts "My_Class has been defined."
{% end %}
I know it's possible to get use TypeNode#has_constant?, but the question has to do whether or not a constant has been defined in the global namespace.
(The reason I asked is because during development and refactoring, I found this would be a convenient placeholder for a few circumstances until I can get the rest of the codebase re-written.)
You could write a macro that simulates that functionality.
Use TypeNode#resolve? to check if the constant has been defined and then conditionally insert the code you want to execute:
macro if_defined(path, &blok)
{% if path.resolve? %}
{{blok.body}}
{% else %}
puts "Not defined: {{path.id}} "
{% end %}
end # === macro if_defined
if_defined(Kemal::Session::My_Constant) do
Kemal.listen
end
In the top level scope, the macro variable #type refers to the program. You can use #type.has_constant?(:Foo) to determine if a top level constant Foo is defined.
For some reason my template is not working, and I can't tell why. The value of . is a map[string]UpFile where UpFile is a struct with the method Path() which takes no arguments. Here is the relevant part of the template:
{{ range $key, $value := . }}
{{ $key }}
{{ end }}
The template works without the call to Path() on the variable $value. I've also tested the call to Path when the value of . was UpFile and it worked. The go doc on templates says calls to methods on variables is fine. The template compiles and is served however nothing in the range is outputted. When I omit the call to Path() I get a string of characters. Thanks for taking a look.
edit: Using a field from UpFile rather than the Path method provides expected output. Still don't understand why calling Path doesn't work.
The Path method is on the pointer receiver:
func (f *UpFile) Path() string { return f.path }
The value in $path is a Path. The method Path() cannot be called on a Path because pointer receiver methods are not in the value type's method set.
There are two ways to fix the problem. The first is to declare the method with a value receiver:
func (f UpFile) Path() string { return f.path }
The second way to fix the problem is to use *Path values instead of Path values. Change the map to:
var m map[string]*UpFile
Just omit the parentheses and it should be fine. Example:
{{ range $key, $value := . }}
{{ $key }}
{{ end }}
We can define template name via {{define "home"}}, and then load it in other (parent) template via {{template "home"}}.
How I can load template via variable value {{template .TemplateName}}. Or it's impossible?
Unfortunately you can't.
The syntax of the {{template}} action:
{{template "name"}}
The template with the specified name is executed with nil data.
{{template "name" pipeline}}
The template with the specified name is executed with dot set
to the value of the pipeline.
The name of the template to be included is a constant string, it is not a pipeline which could vary during execution based on parameters.
If the allowed syntax would be:
{{template pipeline}}
then you could use something like {{template .TemplName}} but since the syntax only allows a constant string, you can't.
Reasoning from Rob why dynamic template invocation is not allowed (source):
We want the template language to be statically analyzable so the context of a template's invocation is clear, checkable, and lockdownable. If an invocation point is totally dynamic, this can't be done. Similarly, if a template can belong to multiple sets, its context can differ between sets in a way that would require all sets to be analyzed simultaneously. Since both these constraints are easy to work around if you want to, at the cost of losing those static checks in a higher-level package, it seemed wise to control the situation in the base template implementation. A higher-level package, such as a hypothetical HTML-only wrapper, can guarantee no workarounds more easily if the constraints are clear.
Alternative #1: Execute Includable Template First
What you can do is execute the template you would want to include first, and insert the result where you want to include it. You can use special types not to escape the result of the inner template when inserting, for example html.HTML in case of HTML templates.
See this example:
func main() {
t := template.Must(template.New("t").Parse(t))
template.Must(t.New("t1").Parse(t1))
params := struct {
Name string
Value interface{}
}{"t1", nil}
b := bytes.Buffer{}
t.ExecuteTemplate(&b, params.Name, nil)
params.Value = template.HTML(b.String())
t.Execute(os.Stdout, params)
}
const t = `<html><body>
Now I will include template with name: {{.Name}}
{{.Value}}
</body>/html>`
const t1 = `I'm template <b>t1</b>.`
Output:
<html><body>
Now I will include template with name: t1
I'm template <b>t1</b>.
</body>/html>
Try it on the Go Playground.
The result of template t1 was inserted unescaped. If you leave out template.HTML:
params.Value = b.String()
t1 would be inserted escaped, like this:
<html><body>
Now I will include template with name: t1
I'm template <b>t1</b>.
</body>/html>
Alternative #2: Restructure Templates
You can restructure your templates not to be in situations where you would want to include a template with varying names.
Example: you might want to create pages where you have a page template something like this:
<html><body>
Title, headers etc.
{{template .Page}}
Footers
</body></html>
You can restructure it to be something like this:
header template:
<html><body>
Title, headers, etc.
footer template:
Footers
</body></html
And your page templates would include header and footer like this:
{{template "header" .}}
Page content comes here.
{{template "footer" .}}
Alternative #3: Use {{if}} action and predefined names
If you know the template names prior and it is not an exhausting list, you can use the {{if}} template action to include the desired template. Example:
{{if eq .Name "page1"}}
{{template "page1" .}}
{{else if eq .Name "page2"}}
{{template "page2" .}}
...
{{end}}
Alternative #4: Modifying the static template text
The idea here is that you could modify the static text of the outer template manually and insert the name of the inner template you want to include.
The downside of this method is that after inserting the name of the inner template, you have to re-parse the template, so I don't recommend this.
I have a function that returns a non-current user's first and last name as a tuple, using the following custom tag, where foo is a username from active directory:
{% name foo %}
I would like to access the first element of the tuple, and the only syntax I could think of is:
{{ {% name foo %}.0 }}
which is incorrect. How can I accomplish this?
You can't do that. If your tag is simply returning a value, there's no way to get the first part of it.
Instead, write an assignment tag, which sets a variable in the context. Then you can do this:
{% name foo as bar %}
{{ bar.0 }}