Open Frohlix opened 5 years ago
The ECR files are compiled. You can’t read a string and compile it directly.
Alternatives: 1. use another template interpreted language (check web frameworks for alternatives). 2. Compile the ecr file using the crystal compiler and run the program to get the output.
I recommend 1 since 2 is not safe and will require a crystal compiler on runtime.
As a recommendation for option 1
Crinja might be what you're looking for https://github.com/straight-shoota/crinja (p.s it's created by @straight-shoota one of the core members of Crystal)
I think I made my original post more confusing than it needed to be. I understand that ECR files are compiled and I agree that if one were to do some replacement at runtime, some kind of templating engine would be a good fit. I'm thinking the JSON part was a false lead. However: What I was actually trying to say is that I think that an equivalent of the "render" macro which takes a string directly, instead of a filename, could be very useful. And in order to show how I mean that, I threw together a bare-bones demonstration at https://github.com/Frohlix/crystal/tree/ecr-string-test The only problem with that is that I absolutely cannot get it to build, with or without my modifications. I will continue trying, but in the meantime, I will just sum the changes up here: (I basically added "_string" to everything new, I know some things like "to_s_string" are nonsensical)
crystal/src/ecr/process_string.cr (new)
require "ecr/processor"
string = ARGV[0]
buffer_name = ARGV[1]
begin
puts ECR.process_string(string, "String", buffer_name)
rescue ex : Errno
if {Errno::ENOENT, Errno::EISDIR}.includes?(ex.errno)
STDERR.puts ex.message
exit 1
else
raise ex
end
end
crystal/src/ecr/macros.cr (added)
macro def_to_s_string(string)
def to_s_string(__io__)
ECR.embed_string {{string}}, "__io__"
end
end
macro embed_string(string, io_name)
\{{ run("ecr/process_string", {{string}}, {{io_name.id.stringify}}) }}
end
macro render_string(string)
::String.build do |%io|
::ECR.embed_string({{string}}, %io)
end
end
And, to demonstrate, demonstration.cr
require "ecr"
class Greeter
def initialize(@name : String)
end
ECR.def_to_s_string "Hello <%= @name %>!"
end
puts Greeter.new("John").to_s_string
othername = "Jack"
puts ECR.render_string("Hello <%= othername %>!")
I hope this helps :)
I don't see a real use case for this. Why would you want to use ECR.render_string("Hello <%= othername %>!")
in the first place? A simple "Hello #{othername}"
is much simpler.
There might be a point to reading templates from an external source instead of the file system, but when the template is embedded directly into the Crystal code, ECR doesn't make much sense at all.
I actually think it's not something bad to have. Maybe if the code is simple you can do:
require "ecr"
class Greeter
def initialize(@names : Array(String))
end
ECR.def_to_s_string <<-HTML
<html>
<body>
<ul>
<% @names.each do |name| %>
<%= name %>
<% end %>
</ul>
</body>
</html>
HTML
end
puts Greeter.new(["John", "Jack"]).to_s_string
Doing it with ECR syntax instead of string interpolation is simpler because one can use <% ... %>
.
Same goes with ECR.render_string
, it could be useful. That way you could theoretically have all your templates directly inside a same file. The example above generates HTML but ECR can generate anything, and maybe ECR is simpler to read than using String.build
or an external file.
That said, I'm not super convinced either that this is super necessary.
This would be handy for defining formats for things, main thing I can think of ATM would be log formats. This would allow you to have an ECR template string that should be used to render the object. For example:
require "ecr"
class Greeter
property template : String = "Hello <%= @name %>!"
def initialize(@name : String)
end
ECR.def_to_s @template
end
g1 = Greeter.new("John")
g1.to_s # => Hello John!
t2 = Gretter.new("Tim")
t2.template = "Wie gehts <%= @name %>!"
g2.to_s # => Wie gehts Tim!
Granted this is a simple example but you get the idea. Using ECR would make more complex formats be much easier to create/maintain than String.build
.
@Blacksmoke16 That can't work, the string must be known at compile-time. It seems you are changing the string at runtime.
I'd be okay if you could define the format using a constant and a HEREDOC. Then it would be known.
I know I'm a bit late to this conversation, but I wanted to add that I did have an application that would have benefitted from this. All the reason that @asterite gave for preferring this over string interpolation were why I didn't go for that, and as @Blacksmoke16 pointed out, a HEREDOC constant would have worked.
I don't think this feature is a necessity, but I do think that it would be useful.
I have a project where I would like to read ECR code from a JSON (aka "as a string"), and then use it with Kemal, but the current implementation only supports rendering files. Basically, I want to render an ECR string. Now, I could write this string out to a temporary file and then render that (however silly that seems), but while reading the code I noticed that the actual process used in ecr/processor.cr is to read the file into a string and then process this string!
So not only would the ability to directly render a string be very useful in my and other cases, it's actually already there, just not accessible via the API.