Specifying a Layout and a Template in a Standalone (Not Rails) Ruby App, Using Slim or Haml

Specifying a layout and a template in a standalone (not rails) ruby app, using slim or haml

The layout.slim file looks like:

h1 Hello
.content
== yield

The contents.slim file looks like:

= name

This can be shortened, but I separated to individual steps for explanation purposes.

require 'slim'

# Simple class to represent an environment
class Env
attr_accessor :name
end

# Intialize it
env = Env.new
# Set the variable we reference in contents.slim
env.name = "test this layout"

# Read the layout file in as a string
layout = File.open("layout.slim", "rb").read

# Read the contents file in as a string
contents = File.open("contents.slim", "rb").read

# Create new template object with the layout
l = Slim::Template.new { layout }

# Render the contents passing in the environment: env
# so that it can resolve: = name
c = Slim::Template.new { contents }.render(env)

# Render the layout passing it the rendered contents
# as the block. This is what yield in layout.slim will get
puts l.render{ c }

This will output:

<h1>Hello</h1><div class="content">test this layout</div>

How to use HAML to generate standalone HTML files via a layout template

I'd look at using Jekyll with a HAML workflow something like this http://mikeferrier.com/2011/04/29/blogging-with-jekyll-haml-sass-and-jammit/

How to use Slim directly in ruby

See Specifying a layout and a template in a standalone (not rails) ruby app, using slim or haml

This is what I ended up using:

require 'slim'

# Simple class to represent an environment
class Env
attr_accessor :name
end

scope = Env.new
scope.name = "test this layout"

layout =<<EOS
h1 Hello
.content
= yield
EOS

contents =<<EOS
= name
EOS

layout = Slim::Template.new { layout }
content = Slim::Template.new { contents }.render(scope)

puts layout.render{ content }

For the scope, you can put in modules/classes or even self.

Rails + Slim Source Output

There's nothing really wrong with the output, Slim is just compressing it for maximum performance. The browser doesn't care how it looks, as long as it works.

However during development it's understandable you may want a more readable version for debugging purposes.

You can configure Slim to produce pretty output like so:

# Indent html for pretty debugging and do not sort attributes
Slim::Engine.set_default_options :pretty => true, :sort_attrs => false

Just put it in config/environments/development.rb for example and you're set.

Use SLIM/HAML etc. in a Ruby script?

Here's a starting point:

require 'haml'

haml_doc = <<EOT
%html
%head
:css
.id {font-weight: bold;}
.signalp {color:#000099; font-weight: bold;}
.motif {color:#FF3300; font-weight: bold;}
h3 {word-wrap: break-word;}
p {word-wrap: break-word; font-family:Courier New, Courier, Mono;}
%body
EOT

engine = Haml::Engine.new(haml_doc)
puts engine.render

Which outputs this when run:

<html>
<head>
<style>
.id {font-weight: bold;}
.signalp {color:#000099; font-weight: bold;}
.motif {color:#FF3300; font-weight: bold;}
h3 {word-wrap: break-word;}
p {word-wrap: break-word; font-family:Courier New, Courier, Mono;}
</style>
</head>
<body></body>
</html>

From there, you can easily write to a file using:

File.write(output, engine.render)

instead of using puts to output it to the console.

To use this, you need to flesh out the haml_doc with additional Haml to loop over your input data and massage it into an array or hash that you can iterate over cleanly, without embedding all sorts of scan and conditional logic. A view should be primarily used to output content, not manipulate data.

Just above the engine = Haml... line you'd want to read your input data and massage it, and store it in an instance variable that Haml can iterate over. You have the basic idea in your original code but instead of trying to output HTML, create an object or sub-hash that you can pass to Haml.

Normally this would all be separated into separate files for the model, the view and the controller, like in Rails or big Sinatra apps, but this really isn't a big app, so you can put it all in one file. Keep your logic clean and it'll be fine.

Without sample input data and an expected output it's hard to do more, but that'll give you a starting point.


Based on the data samples, here's something that gets in you the ballpark. I won't polish it because, after all, you have to do some of it, but this is a reasonable start. The first part is mocking up something reasonably like the Bio you reference in your code, but which I've never seen. You don't need this part, but might want to look through it:

module Bio

FastaFormat = 1

SAMPLE_DATA = <<-EOT
>isotig00001_f4_14 - Signal P Cleavage Site => 11:12
MMHLLCIVLLL-KWWLLL
>isotig00001_f4_15 - Signal P Cleavage Site => 10:11
MHLLCIVLLL-KWWLLL
>isotig00003_f6_8 - Signal P Cleavage Site => 11:12
MMHLLCIVLLL-KWWLLL
>isotig00003_f6_9 - Signal P Cleavage Site => 10:11
MHLLCIVLLL-KWWLLL
>isotig00004_f6_8 - Signal P Cleavage Site => 11:12
MMHLLCIVLLL-KWWLLL
>isotig00004_f6_9 - Signal P Cleavage Site => 10:11
MHLLCIVLLL-KWWLLL
>isotig00009_f2_3 - Signal P Cleavage Site => 22:23
MLKCFSIIMGLILLLEIGGGCA-IYFYRAQIQAQFQKSLTDVTITDYRENADFQDLIDALQSGLSCCGVNSYEDWDNNIYFNCSGPANNPEALWCAFLLLYTGSSKRSSQHPVRLWSSFPRTTKYFPHKDLHHWLCGYVYNVD
>isotig00009_f3_9 - Signal P Cleavage Site => 16:17
MKTGIIIFISTVVVLP-ITLKPCGVPFSCCIPDQASGVANTQCGYGVRSPEQQNTFHTKIYTTGCADMFTMWINRYLYYIAGIAGVIVLVELFGFCFAHSLINDIKRQKARWAHR
>isotig00009_f6_13 - Signal P Cleavage Site => 11:12
MMHLLCIVLLL-KWWLLL
>isotig00009_f6_14 - Signal P Cleavage Site => 10:11
MHLLCIVLLL-KWWLLL
EOT

class FlatFile

class Entry
attr_reader :definition, :aaseq

def initialize(definition, aaseq)
@definition = definition
@aaseq = aaseq
end
end

def initialize
end

def self.open(filetype, filename)
SAMPLE_DATA.split("\n").each_slice(2).map{ |seq_id, sequence| Entry.new(seq_id, sequence) }
end

def each_entry
@sample_data.each do |_entry|
yield _entry
end
end

end
end

Here's where the fun begins. I modified your get_hash routine to parse the strings how I'd do it. Instead of a hash, it returns an array of hashes. Each sub-hash is ready to be used, in other words, the data is parsed and ready to be output:

include Bio

def make_array_of_hashes(input_file)
Bio::FlatFile.open(
Bio::FastaFormat,
input_file
).map { |entry|

id_start, id_end = entry.definition.split('-').map(&:strip)
signalp, seq_end = entry.aaseq.split('-')
motif = seq_end.scan(/(?:WL|KK|RR|KR|R..R|R....R)/)

{
:id_start => id_start,
:id_end => id_end,
:signalp => signalp,
:motif => motif
}
}
end

This is a simple way to define the HAML document inside the body of a script. I only output, there's no logic in the template except to loop. Everything else was handled prior to the view being processed:

haml_doc = <<EOT
!!!
%html
%head
:css
.id {font-weight: bold;}
.signalp {color:#000099; font-weight: bold;}
.motif {color:#FF3300; font-weight: bold;}
h3 {word-wrap: break-word;}
p {word-wrap: break-word; font-family:Courier New, Courier, Mono;}
%body
- data.each do |d|
%p
%span.id= d[:id_start]
%span= d[:id_end]
%br/
%span.signalp= d[:signalp]
- d[:motif].each do |m|
%span= m
EOT

And here's how to use it:

require 'haml'

data = make_array_of_hashes('sample.txt')

engine = Haml::Engine.new(haml_doc)
puts engine.render(Object.new, :data => data)

Which, when run outputs:

<!DOCTYPE html>
<html>
<head>
<style>
.id {font-weight: bold;}
.signalp {color:#000099; font-weight: bold;}
.motif {color:#FF3300; font-weight: bold;}
h3 {word-wrap: break-word;}
p {word-wrap: break-word; font-family:Courier New, Courier, Mono;}
</style>
</head>
<body></body>
<p>
<span class='id'>>isotig00001_f4_14</span>
<span>Signal P Cleavage Site => 11:12</span>
<br>
<span class='signalp'>MMHLLCIVLLL</span>
<span>WL</span>
</p>
<p>
<span class='id'>>isotig00001_f4_15</span>
<span>Signal P Cleavage Site => 10:11</span>
<br>
<span class='signalp'>MHLLCIVLLL</span>
<span>WL</span>
</p>
<p>
<span class='id'>>isotig00003_f6_8</span>
<span>Signal P Cleavage Site => 11:12</span>
<br>
<span class='signalp'>MMHLLCIVLLL</span>
<span>WL</span>
</p>
<p>
<span class='id'>>isotig00003_f6_9</span>
<span>Signal P Cleavage Site => 10:11</span>
<br>
<span class='signalp'>MHLLCIVLLL</span>
<span>WL</span>
</p>
<p>
<span class='id'>>isotig00004_f6_8</span>
<span>Signal P Cleavage Site => 11:12</span>
<br>
<span class='signalp'>MMHLLCIVLLL</span>
<span>WL</span>
</p>
<p>
<span class='id'>>isotig00004_f6_9</span>
<span>Signal P Cleavage Site => 10:11</span>
<br>
<span class='signalp'>MHLLCIVLLL</span>
<span>WL</span>
</p>
<p>
<span class='id'>>isotig00009_f2_3</span>
<span>Signal P Cleavage Site => 22:23</span>
<br>
<span class='signalp'>MLKCFSIIMGLILLLEIGGGCA</span>
<span>KR</span>
<span>WL</span>
</p>
<p>
<span class='id'>>isotig00009_f3_9</span>
<span>Signal P Cleavage Site => 16:17</span>
<br>
<span class='signalp'>MKTGIIIFISTVVVLP</span>
<span>KR</span>
</p>
<p>
<span class='id'>>isotig00009_f6_13</span>
<span>Signal P Cleavage Site => 11:12</span>
<br>
<span class='signalp'>MMHLLCIVLLL</span>
<span>WL</span>
</p>
<p>
<span class='id'>>isotig00009_f6_14</span>
<span>Signal P Cleavage Site => 10:11</span>
<br>
<span class='signalp'>MHLLCIVLLL</span>
<span>WL</span>
</p>
</html>

How to add a stylesheet to the layout's head of a HAML template in Sinatra?

There are 2 ways you can go about it. One is to use Sinatra's own content_for gem, or bundle ActionView, which will give you access to Rails' content_for method.

The second option is to do a manual check in the layout, and include the CSS there:

# in your HAML template:
- if request.path_info == '/hello-world'
%link{:rel => :stylesheet, :type => :"text/css", :href => "/assets/css/my_stylesheet"}

How to render partial only on production build?

...
- if build?
= partial 'analytics'
...


Related Topics



Leave a reply



Submit