HAML filters in a helper - ruby-on-rails

Helper functions can receive a block which they yield to render the block. Sometimes I'd want that block to be spec'd with a filter. So for example:
= doc_page title: 'FAQ' do
:markdown
# Welcome to the *FAQ*
This is not so DRY as we are always writing doc_page and markdown together. Can I make the helper method accept a block and pass it through a HAML filter. Something like:
= doc_page title: 'FAQ' do
# Welcome to the *FAQ*
In this fantasy, doc_page is a helper method that does some setup stuff and then passes the content through markdown, saving us the need to declare :markdown everywhere and making the world a DRYer place.

Currently it is not possible to use filters in helpers. An alternative approach would be to use redcarpet to parse the markdown and then pass the output to a helper.
an example would be:
= doc_page title: 'FAQ', :markdown do
### my markdown
= doc_page title: 'FAQ' do
normal html
The implementation of the doc_page would be something like this:
def doc_page(title, markup=:html)
content = yield
if markup == :markdown
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML)
content = markdown.render(content)
end
content
end
This would solve your problem, as you define your markdown filter in the helper. And you don't need an extra indentation level in your haml.

You could use tilt (the api haml uses to render markdown) directly instead of through haml. Probably something like this (not tested).
markdown_template = Tilt['md'].new { "# this is markdown code" }
markdown_template.render
You can find a similar example in the Tilt docs.
But this is more of an idea than a complete implementation.

I'm afraid, but it's not possible, because haml is a preprocessor, basicly this piece of code:
= doc_page title: 'FAQ' do
# Welcome to the *FAQ*
%a href="/" link
Will be converted in runtime ruby code like:
concat(doc_page title: 'FAQ' do
# Welcome to the *FAQ*
concat('link')
end)

Related

Markdown Render Newlines

I'm working on a Project and give an user the possibility to create a Post.
With loading the Post, i'm calling the markdown method, to extract links and format the text.
Now i got a Problem.
By writing "1. Example" the Output in the Post is a list.
By just writing "1.Example"_ without the whitespace between the point and the text, it'working fine.
My markdown method:
#preview = nil
options = {
autolink: true,
hard_wrap: true
}
begin
URI.extract(text, ['http', 'https', 'www']).each do |uri|
unless text.include?("<a")
text = text.gsub( uri, "#{uri}" )
#preview = LinkThumbnailer.generate(uri)
end
end
rescue OpenSSL::SSL::SSLError => e
end
renderer = Redcarpet::Render::HTML.new(options)
markdown = Redcarpet::Markdown.new(renderer)
markdown.render(text).html_safe
May you know, how to fix it.. I don't want the list, i just want the Output to be the same like the Input!
Thank you, for your time!
EDIT Added a photo to show the output.
You want to use a backslash escape in your Markdown source. As the rules explain:
Markdown allows you to use backslash escapes to generate literal characters which would otherwise have special meaning in Markdown’s formatting syntax.
Among the characters which backlash escaping supports is the dot (.). Therefore your source text should look like this:
1\. Example
Which results in this HTML:
<p>1. Example</p>
And renders as:
1. Example
By default you're going to get the list. Markdown is after all looking for syntax that it recognises in order to generate mark up.
In order to skip particular markdown features I think you're going to need to provide your own custom renderer.
If you define a new renderer:
class NoListRenderer < Redcarpet::Render::HTML
def list(contents, list_type)
contents
end
def list_item(text, list_type)
text
end
end
and use an instance of that instead of the default renderer class when you create your markdown instance it should skip the default list processing. (NB. I haven't tested this code):
renderer = NoListRenderer.new(options)
markdown = Redcarpet::Markdown.new(renderer)

Output slim to variable

I'm using Base64.encode64(val) to convert html to base64.
Example:
- val = link_to 'Link', link_path
= Base64.encode64(val)
But how can I get slim markup to variable? Like so:
.class = link_to 'Link', link_path # <- this output with slim div
Or even multiple lines
div
span
.another_div
There is a way by putting slim code into partial and do this:
- var = render 'partial'
= Base64.encode64(var) # Convert into base64
How to do this without partial?
Slim exposes its templating through the Tilt interface, like so:
# Render a template file:
Slim::Template.new("template.slim", options).render(scope)
# Render a string:
Slim::Template.new(options) { "b slim markup" }.render(scope)
Where options is an optional hash of options for slim and scope is the object in which the template code is executed.
So the following:
slim_markup = <<-SLIM
div
span
.another_div
SLIM
# The options hash and scope have been omitted for the sake of simplicity
html_output = Slim::Template.new { slim_markup }.render
Sets the value of html_output to:
<div></div>
<span></span>
<div class="another_div"></div>
But for your example with the url helper link_path, you must provide slim a scope in which the url helpers are available e.g. a controller.
This is an old question, but I have wondered about this many times, and I always spend a lot of time researching it.
Using Slim 4 you can use capture directly:
- val = capture
div
span
.another_div
This will put the rendered slim into your variable.
Another way from the box using capture method. From the docs:
Using the Binding you can capture to local variables as follows:
module Helpers
def capture_to_local(var, &block)
set_var = block.binding.eval("lambda {|x| #{var} = x }")
# In Rails we have to use capture!
# If we are using Slim without a framework (Plain Tilt),
# you can just yield to get the captured block.
set_var.call(defined?(::Rails) ? capture(&block) : yield)
end
end
The helper can then be used in the Slim template as follows
/ The captured_content variable must be known by the Binding beforehand.
= capture_to_local captured_content=:captured_content
p This will be captured in the variable captured_content
= captured_content
Read more https://github.com/slim-template/slim#capturing-to-local-variables

How can I include an arbitrary file into a HAML template?

I want to have independent .markdown files that I then include in my haml templates. So I want to somehow include -- not render -- an external file into the template. I want the parent file to have :markdown in it, with the inclusion directly below that, and then the .markdown file to just be pure markdown.
Or: Is there a way to just use markdown as a rails template language (same way i can write templates or partials in erb or haml and rails just figures it out)?
This is similar to your solution, but using the :markdown filter. Haml does string interpolation on any filtered text, so you can read the markdown file like this.
:markdown
#{File.read(File.join(File.dirname(__FILE__), "foo.markdown"))}
You could put this into a helper, but you'd have to be careful with the file paths.
The simplest way I could think of is to create a custom template handler for Markdown. That you get to use Markdown code as partials (also getting support for locals for free).
module Markdown
class Template < ActionView::Template::Handler
include ActionView::Template::Handlers::Compilable
self.default_format = Mime::HTML
def compile(template)
'"' + Maruku.new(template.source).to_html + '".html_safe'
end
end
end
And then register it with markdown extension (in application.rb or custom initializer):
ActionView::Template.register_template_handler(:md, Markdown::Template)
And then user render like you would for any partial :)
# for file foo.md
= render 'foo'
Here's the best I can come up with (no haml filter involved at all):
=raw Maruku.new(File.read(File.dirname(__FILE__)+'/foo.markdown')).to_html
This is something I asked the HAML developers a while back. I suggested we needed an :include filter for HAML. Their response was we should load the file into a variable and then use the variable like we would any other.
Extending ActionView::Template::Handler is deprecated in at least Rails 3.1.0. Instead the following worked for me:
In lib/markdown_views.rb:
require "rdiscount"
class MarkdownViews
def call template
'md = ERB.new(<<\'EOF\'%s
EOF
).result( binding)
RDiscount.new( md).to_html.html_safe'% template.source
end
end
In config/application.rb:
require "markdown_views"
ActionView::Template.register_template_handler :markdown, MarkdownViews.new
In views/public/home.html.markdown:
# H1
+ Bullets.
+ screaming.
+ from out of nowhere
<%= "Embedded Ruby" %>

Rails: Is it possible to write view helpers with HAML syntax?

During refactoring it would be quite handy just to copy part of HAML template and paste it to helper's code. Currently in such cases 1) I have to rewrite that part of view from scratch 2) I have to use that verbose syntax like content_tag or haml_tag.
I know that it's possible to define partials with HAML systax that will serve as helper. Though 1) as for me it's inconvinient to create a separate file for each small tiny function 2) invocation syntax for partial is quite verbose.
Ideally i'd like my *_helper class to look like this:
- def some_helper(*its_args)
.some_class
= some_ruby_expression
%some_tag#some_id
- another_expression do
etc
or at least like this:
define_haml_helper :some_helper, [:arg1, :arg2], %{
.some_class
= some_ruby_expression
%some_tag#some_id
- another_expression do
etc
}
Is there a plugin that solves my issue?
Alternatively, maybe you can describe how do you refactor HAML snippets to reusable elements (helpers/functions/partials/builders/etc)?
From the reference:
def render_haml(code)
engine = Haml::Engine.new(code)
engine.render
end
This initiates a new Haml engine and renders it.
If all you are after is a method for small reusable snippets, how about partials with local variables? http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials
Haml now has a capture_haml method that you can use to accomplish this.
def some_helper
capture_haml do
.some_class
= yield
#some-code-after
end
end
some_helper do
%h1 Hello World
end
=> <div class="some_class">
<h1>Hello World</h1>
</div>
<div id="some-code-after"></div>
Here is a link with more info on capture_haml:
http://haml.info/docs/yardoc/Haml/Helpers.html#capture_haml-instance_method
I used heredoc for such purposes:
def view_helper
Haml::Engine.new(<<~HAML).render
.example
#id ID
.desc Description
HAML
end
This way has a lot of issues with a scope of variables, so, as mentioned above, the much more correct way is to use partials for this.
UPD1: here is a solution on how to solve issues with scope:
def view_helper
Haml::Engine.new(<<~HAML).render(self)
.form
= form_tag root_path do
= submit_tag :submit
HAML
end
UPD2: even better solution(founded on the internet):
def render_haml(haml, locals = {})
Haml::Engine.new(haml.strip_heredoc, format: :html5).render(self, locals)
end
def greeting
render_haml <<-HAML
.greeting
Welcome to
%span.greeting--location
= Rails.env
HAML
end

Evaluating string templates

I have a string template as shown below
template = '<p class="foo">#{content}</p>'
I want to evaluate the template based on current value of the variable called content.
html = my_eval(template, "Hello World")
This is my current approach for this problem:
def my_eval template, content
"\"#{template.gsub('"', '\"')}\"" # gsub to escape the quotes
end
Is there a better approach to solving this problem?
EDIT
I used HTML fragment in the sample code above to demonstrate my scenario. My real scenario has set of XPATH templates in a configuration file. The bind variables in the template are substituted to get a valid XPATH string.
I have thought about using ERB, but decided against as it might be a overkill.
You can do what you want with String's native method '%':
> template = "<p class='foo'>%s</p>"
> content = 'value of content'
> output = template % content
> puts output
=> "<p class='foo'>value of content</p>"
See http://ruby-doc.org/core/classes/String.html#M000770
You can render a string as if it were an erb template. Seeing that you're using this in a rake task you're better off using Erb.new.
template = '<p class="foo"><%=content%></p>'
html = Erb.new(template).result(binding)
Using the ActionController methods originally suggested, involves instantiating an ActionController::Base object and sending render or render_to_string.
I can't say I really recommend either of these approaches. This is what libraries like erb are for, and they've been throughly tested for all the edge cases you haven't thought of yet. And everyone else who has to touch your code will thank you. However, if you really don't want to use an external library, I've included some recommendations.
The my_eval method you included didn't work for me. Try something like this instead:
template = '<p class="foo">#{content}</p>'
def my_eval( template, content )
eval %Q{"#{template.gsub(/"/, '\"')}"}
end
If you want to generalize this this so you can use templates that have variables other than content, you could expand it to something like this:
def my_eval( template, locals )
locals.each_pair{ |var, value| eval "#{var} = #{value.inspect}" }
eval %Q{"#{template.gsub(/"/, '\"')}"}
end
That method would be called like this
my_eval( '<p class="foo">#{content}</p>', :content => 'value of content' )
But again, I'd advise against rolling your own in this instance.
This is also a nice one:
template = "Price of the %s is Rs. %f."
# %s - string, %f - float and %d - integer
p template % ["apple", 70.00]
# prints Price of the apple is Rs. 70.000000.
more here
To late but I think a better way is like ruby-style-guide:
template = '<p class="foo">%<content>s</p>'
content_text = 'Text inside p'
output = format( template , content: content_text )

Resources