Extending Middleman to handle a new filetype (.JADE) - ruby

I'd like to add .jade support to Middleman. I don't need to use any of jade's dynamic features, but I'd like to compile my app in middleman rather than with my own messy compile script.
What is the simplest way to add a new file type to Middleman?

Middleman's templating is built on Tilt, so using the tilt-jade gem it should be pretty straightforward.
Here's some code for adding Mustache templates to Middleman:
require 'tilt-mustache'
# Mustache Renderer
module Middleman::Renderers::Mustache
class << self
def registered(app)
# Mustache is not included in the default gems,
# but we'll support it if available.
begin
# Require Gem
require "mustache"
# After config, setup mustache partial paths
app.after_configuration do
Mustache.template_path = source_dir
# Convert data object into a hash for mustache
provides_metadata %r{\.mustache$} do |path|
{ :locals => { :data => data.to_h } }
end
end
rescue LoadError
end
end
alias :included :registered
end
end
Middleman::Base.register Middleman::Renderers::Mustache
that should be quite easy to adapt to work with Jade.

Related

Builder's XmlMarkup object loses constants?

I try to upgrade a legacy application from Ruby 1.8.7 to 2.2.3. Afterwards the rendering of builder templates raises errors about unknown classes.
uninitialized constant Builder::XmlMarkup::BigDecimal (NameError)
It seem that within the Builder::XmlMarkup constants like classes disappear.
### example.xml.builder (template) ###
BigDecimal.new('23') # no error
class << xml
def some
data(BigDecimal.new('23')) # raises an error in 2.2.3
end
end
xml.test { xml.some }
### main script ###
require 'rubygems'
require 'builder'
require 'bigdecimal'
def eval_script(file)
xml = Builder::XmlMarkup.new
binding.eval(File.read(file), file)
xml.target!
end
template = File.join(File.dirname(__FILE__), 'example.xml.builder')
puts eval_script(template)
# Ruby 1.8.7 / builder 3.2.0 => <test><data>0.23E2</data></test>
# Ruby 2.2.3 / builder 3.2.2 => ./eval_script.rb:5:in `some': uninitialized constant Builder::XmlMarkup::BigDecimal (NameError)
I found no reason for the behavior. How can I fix the problem?
BTW: I have the same problem with the method lookup, e.g format('%d', 42) which returns the full XML document but doesn't call Kernel.format in Ruby 2.2.3.
I found a workaround overriding const_missing which has to be applied to every template file. It works so far for the legacy application.
### example.xml.builder (template) ###
class << xml
def self.const_missing(name)
super rescue ::Object.const_get(name)
end
def some
data(BigDecimal.new('23'))
end
end
xml.test { xml.some }
But every time the constant BigDecimal is used, it triggers const_missing and then raises a NameError and calls the Object method.

How to use Slim directly in ruby

I would like to create a basic ruby script that renders Slim templates into html (This would eventually be part of a larger project). Ideally I would like to use the HTML produced within the script.
I understand this is possible using TILT (as shown in the SLIM README) where it says the following:
Slim uses Tilt to compile the generated code. If you want to use the Slim template directly, you can use the Tilt interface.
Tilt.new['template.slim'].render(scope)
Slim::Template.new('template.slim', optional_option_hash).render(scope)
Slim::Template.new(optional_option_hash) { source }.render(scope)
The optional option hash can have to options which were documented in the section above. The scope is the object in which the template code is executed.
However, I'm still unable to successfully run this. Therefore, I was wondering if someone could help me by producing a working example.
EDIT (this has recently been edited further ):
I have played about with the code quite a bit but I keep on getting the following error:
undefined local variable or method `source' for main:Object (NameError)
This is what i'm running:
require 'slim'
# I'm not sure about the next two lines...
optional_option_hash = {}
scope = Object.new
Tilt.new('template.slim').render(scope)
Slim::Template.new('template.slim', optional_option_hash).render(scope)
Slim::Template.new(optional_option_hash) { source }.render(scope)
Many Thanks for all your help.
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.
Quick essentials of
module SlimRender
def slim(template, variables = {})
template = template.to_s
template += '.slim' unless template.end_with? '.slim'
template = File.read("#{ROOT}/app/views/#{template}", encoding: 'UTF-8')
Slim::Template.new { template }.render OpenStruct.new(variables)
end
end
Include SlimRender to your class and:
def render_something
slim 'streams/scoreboard', scores: '1-2'
end

cattr_accessor outside of rails

I'm trying to use the google_search ruby library (code follows) but it complains that 'cattr_accessor is an undefined method' - any ideas why this might be or how I could fix it?
require 'rubygems'
require 'google_search'
GoogleSearch.web :q => "pink floyd"
cattr_accessor seems to be a Rails extension that acts like attr_accessor, but is accessible on both the class and its instances.
If you want to copy the source of the cattr_accessor method, check out this documentation:
# File vendor/rails/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb, line 46
def cattr_accessor(*syms)
cattr_reader(*syms)
cattr_writer(*syms)
end
# File vendor/rails/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb, line 4
def cattr_reader(*syms)
syms.flatten.each do |sym|
next if sym.is_a?(Hash)
class_eval("unless defined? ##\#{sym}\n##\#{sym} = nil\nend\n\ndef self.\#{sym}\n##\#{sym}\nend\n\ndef \#{sym}\n##\#{sym}\nend\n", __FILE__, __LINE__)
end
end
# File vendor/rails/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb, line 24
def cattr_writer(*syms)
options = syms.extract_options!
syms.flatten.each do |sym|
class_eval("unless defined? ##\#{sym}\n##\#{sym} = nil\nend\n\ndef self.\#{sym}=(obj)\n##\#{sym} = obj\nend\n\n\#{\"\ndef \#{sym}=(obj)\n##\#{sym} = obj\nend\n\" unless options[:instance_writer] == false }\n", __FILE__, __LINE__)
end
end
You can get this functionality by including the Ruby Facets gem. Reference the source here:
https://github.com/rubyworks/facets/blob/master/lib/core/facets/cattr.rb
You generally don't need to require all code from the gem. You can selectively require what you want. There are quite a few useful extensions in the gem though.

How can I test helpers blocks in Sinatra, using Rspec?

I'm writing a sinatra app and testing it with rspec and rack/test (as described on sinatrarb.com).
It's been great so far, until I moved some rather procedural code from my domain objects to
sinatra helpers.
Since then, I've been trying to figure out how to test these in isolation ?
I test my sinatra helpers in isolation by putting the helper methods within its own module.
Since my sinatra application is a little bit bigger than the usual hello world example, I need to split it up into smaller parts. A module for the common helpers suits my use case well.
If you write a quick demo, and you define your helper methods within the helpers { ... } block, I don't think testing it is absolutely necessary. Any sinatra app in production, may require more modularity anyways.
# in helpers.rb
module Helpers
def safe_json(string)
string.to_s.gsub(/[&><']/) { |special| {'&' => '\u0026', '>' => '\u003E', '<' => '\u003C', "'" => '\u0027'}[special] }
end
end
# in app.rb
helpers do
include Helpers
end
# in spec/helpers_spec.rb
class TestHelper
include Helpers
end
describe 'Sinatra helpers' do
let(:helpers) { TestHelper.new }
it "should escape json to inject it as a html attribute"
helpers.safe_json("&><'").should eql('\u0026\u003E\u003C\u0027')
end
end
Actually you don't need to do:
helpers do
include FooBar
end
Since you can just call
helpers FooBar
The helpers method takes a list of modules to mix-in and an optional block which is class-eval'd in: https://github.com/sinatra/sinatra/blob/75d74a413a36ca2b29beb3723826f48b8f227ea4/lib/sinatra/base.rb#L920-L923
maybe this can help you some way http://japhr.blogspot.com/2009/03/sinatra-innards-deletgator.html
I've also tried this (which needs to be cleaned up a bit to be reusable) to isolate each helper in its own environment to be tested:
class SinatraSim
def initialize
...set up object here...
end
end
def helpers(&block)
SinatraSim.class_eval(&block)
end
require 'my/helper/definition' # defines my_helper
describe SinatraSim do
subject { SinatraSim.new(setup) }
it "should do something"
subject.expects(:erb).with(:a_template_to_render) # mocha mocking
subject.my_helper(something).should == "something else"
end
end

Call Sinatra erb from another class

I need to render a Sinatra erb template inside a class in my controller. I'm having issues calling this though. I've looked in the Sinatra rdocs and have come up with this:
Sinatra::Templates.erb :template_to_render
When I do this, I get the following error:
undefined method `erb' for Sinatra::Templates:Module
Is there a way to call this from another class?
To imitate rendering behavior of Sinatra controller in some other class (not controller) you can create module like this:
module ErbRender
include Sinatra::Templates
include Sinatra::Helpers
include Sinatra::ContentFor
def settings
#settings ||= begin
settings = Sinatra::Application.settings
settings.root = "#{ROOT}/app"
settings
end
end
def template_cache
#template_cache ||= Tilt::Cache.new
end
end
Here you may need to tune settings.root
Usage example:
class ArticleIndexingPostBody
include ErbRender
def get_body
erb :'amp/articles/show', layout: :'amp/layout'
end
end
This will properly render templates with layouts including content_for
why you don't require 'erb' and after use only erb
## You'll need to require erb in your app
require 'erb'
get '/' do
erb :index
end
You could have your class return the template name and render it in the main app.
Of course that's not exactly an answer (I don't have enough rep to add a comment with this account) and you're probably doing just that by now anyway...

Resources