Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial refactor of the format module. Mainly focussing on turning th…

…e Renderer class into Transformer and implementing functionality as class methods. Thus avoiding the nasty instancing.
  • Loading branch information...
commit 648653c5c8039aa660492a0e53920b7584845f96 1 parent 3113163
Luke Sutton authored
View
4 lib/pekky/context.rb
@@ -11,8 +11,8 @@ def config
Config
end
- def render
- Format::Renderers
+ def format
+ Format::Formatters
end
def filter
View
189 lib/pekky/format.rb
@@ -8,54 +8,53 @@ module Format
# error message when a renderer can't find the library it needs. This is
# to prevent users being presented with a potentially confusing message.
class LibaryMissingError < PekkyError
- def initialize(error)
- @message = ""
+ def initialize(klass, error)
+ @message = "While trying to use the #{klass.to_s} transformer, we got this error:\n #{error.message}"
end
end
# The wrapper class acts as a container for a set of renderers or filters.
# It's most essential job is to allow syntax like this:
#
- # Format::Renderers.textile("h1. blah")
+ # Format::Formatters.textile("h1. blah")
#
# This class should never be instanced, or accessed directly. Instead, we
# use the Renderers and Filters subclasses.
class Wrapper
-
# Ahh, the good old class instance attribute accessors. This is a bit of
# voodoo which is better explained by more patient people.
class << self
attr_accessor :klasses, :call_with
end
- # Adds a renderer to the existing collection.
+ # Adds a transformer to the existing collection.
def self.add(name, klass)
(self.klasses ||= {})[name] = klass
end
- # Shortcut for accessing a renderer class by name.
+ # Shortcut for accessing a transformer class by name.
def self.[](name)
self.klasses[name]
end
- # Returns all the names of the renderers
+ # Returns all the names of the transformers.
def self.keys
self.klasses.keys
end
- # Returns the actual renderers themselves.
+ # Returns the actual transformers themselves.
def self.values
self.klasses.values
end
- # An iterator for looping over the renderers. The block will recieve the
- # name and the actual class.
+ # An iterator for looping over the transformer. The block will recieve
+ # the name and the actual class.
def self.each
self.klasses.each { |k, v| yield k, v }
end
# Method missing is used here to provide nice syntax for accessing
- # renderers as methods.
+ # transformer as methods.
def self.method_missing(name, *args)
if self.klasses[name]
self.klasses[name].send(self.call_with, *args)
@@ -65,16 +64,16 @@ def self.method_missing(name, *args)
end
end
- # The renderer wrapper. Adds a few additional methods for defaults etc.
- class Renderers < Wrapper
+ # The formatter wrapper. Adds a few additional methods for defaults etc.
+ class Formatters < Wrapper
self.call_with = :_format
- # Returns the default text renderer -- textile
+ # Returns the default text formatter -- textile
def self.defaults
- Config[:text_renderer] || :textile
+ Config[:text_formatter]
end
- # This is the default renderer. It'll use the setting set in the site
+ # This is the default formatter. It'll use the setting set in the site
# config, or the built in default -- textile.
def self.text(text, opts = {})
self.klasses[defaults]._format(text, opts)
@@ -98,34 +97,42 @@ def self.defaults
end
end
- # Defines a new renderer. It does this by subclassing then instancing a
- # renderer. The idea there is to allow people to define methods in the
- # block without having to prefix the name with 'self.'
- def self.define(name, &blk)
- r = Class.new(Renderer)
- r.class_eval(&blk)
- instance = r.new
-
- Renderers.add(name, instance) if instance.respond_to?(:format)
- Filters.add(name, instance) if instance.respond_to?(:filter)
- end
-
- # Base class for the renderers and filters. A subclass can actually be both
- # a renderer -- takes text and makes HTML -- or a filter -- takes HTML and
- # modifies it in some way. The reason for this is that the logic for doing
- # both is often shared. For example in the code formatting, we sometimes
- # want to render code directly from text, or we want to modify existing
- # markup -- like some embedded in textile output.
- class Renderer
+ # Transformer class for the formatters and filters. A subclass can actually
+ # be both a formatter -- takes text and makes HTML -- or a filter -- takes
+ # HTML and modifies it in some way. The reason for this is that the logic
+ # for doing both is often shared. For example in the code formatting, we
+ # sometimes want to render code directly from text, or we want to modify
+ # existing markup -- like some embedded in textile output.
+ class Transformer
+ # Class attribute reader bo!
+ class << self
+ attr_reader :loaded
+ end
+
# The loaded variable is used to keep track if the dependencies of the
# renderer has been loaded. The loading is handled by the
# #check_for_requirements method.
@loaded = false
+
+ # This hook is being used to see if the subclasses define either the
+ # #format or #filter methods and thus need to be added to the relevant
+ # wrappers.
+ def self.singleton_method_added(method_name)
+ case method_name
+ when :format then Formatters.add(name, self)
+ when :filter then Filters.add(name, self)
+ end
+ end
+
+ # Returns the snake case version of this class name.
+ def self.name
+ @name ||= self.to_s.match(/::(\w+)$/)[1].downcase.to_sym
+ end
# This method wraps the actual #format method defined by the subclass.
# This is so we can make sure the dependencies are loaded and then run
# any of the specified filters -- or the defaults.
- def _format(text, opts = {})
+ def self._format(text, opts = {})
check_for_requirements
output = if method(:format).arity > 1
@@ -147,10 +154,10 @@ def _format(text, opts = {})
end
end
- # Wraps around the #format method defined in the subclasses. It does some
+ # Wraps around the #filter method defined in the subclasses. It does some
# preflight stuff like loading requirements and setting up the Nokogiri
# document.
- def _filter(text)
+ def self._filter(text)
check_for_requirements
doc = Nokogiri::HTML.fragment(text)
filter(doc, text)
@@ -159,81 +166,121 @@ def _filter(text)
private
- def check_for_requirements
- if respond_to?(:requirements) and !@loaded
+ # Checks to see if the Renderer has any requirements, or if they have
+ # been run previously. It also catches errors and re-raises them as a
+ # PekkyError so we can present the user with a nicer error message.
+ def self.check_for_requirements
+ if !@loaded and respond_to?(:requirements)
requirements
@loaded = true
end
rescue LoadError => error
- raise LibraryMissingError.new(error)
+ raise LibraryMissingError.new(self, error)
end
end
- define :textile do
- def requirements
- require 'RedCloth'
+ # Formats textile markup into HTML.
+ class Textile < Transformer
+ def self.format(text)
+ Tilt::RedClothTemplate.new {text}.render
end
-
- def format(text)
- RedCloth.new(text).to_html
+ end
+
+ # Formats markdown into HTML.
+ class Markdown < Transformer
+ def self.format(text)
+ Tilt::RDiscountTemplate.new {text}.render
end
end
- define :markdown do
- def requirements
- require 'Rdiscount'
+ # Ahhh, the venerable ERB templates.
+ class Erb < Transformer
+ def self.format(text)
+ Tilt::ERBTemplate.new {text}.render
end
-
- def format(text)
- Markdown.new(text, :smart).to_html
+ end
+
+ # HAML! I really like Haml. Best template language ever.
+ class Haml < Transformer
+ def self.format(text)
+ Tilt::HamlTemplate.new {text}.render
end
end
- define :ultraviolet do
- def requirements
- require 'ultraviolet'
+ # I know nothing about Liquid, but you can use it if you want to.
+ class Liquid < Transformer
+ def self.format(text)
+ Tilt::LiquidTemplate.new {text}.render
end
-
- def format(text)
-
+ end
+
+ # Same deal with Radius. It looks interesting, but not really my bag.
+ class Radius < Transformer
+ def self.format(text)
+ Tilt::RadiusTemplate.new {text}.render
end
-
- def filter(doc, text)
-
+ end
+
+ # Would you really want to render RDoc? Maybe. I dunno.
+ class Rdoc < Transformer
+ def self.format(text)
+ Tilt::RDocTemplate.new {text}.render
end
end
- define :coderay do
- def requirements
+ # Transformer which will create syntax highlighted HTML from a text fragment
+ # or it can filter an existing bit of HTML and replace each <code> tag with
+ # a highlighted version.
+ class Coderay < Transformer
+ def self.requirements
require 'coderay'
end
- def format(text)
-
+ def self.format(text)
+ highlight(text)
end
- def filter(doc, text)
+ def self.filter(doc, text)
+ doc.css("pre code").each do |el|
+ el.value = highlight(el.value)
+ end
+ end
+ def self.highlight(text)
+ lines = text.split("\n")
+ match = lines[0].match(/^#\! (\S+)/)
+ if match
+ lang = match[1]
+ lines.shift
+ CodeRay.scan(lines.join("\n"), lang.to_sym).div(:line_numbers => :table)
+ end
end
end
- define :links do
- def filter(doc, text)
+ # Utility transformer which filters a nokogiri document fragment and
+ # replaces all instances of an anchor using the pekky:// protocol with the
+ # actual path to the page. This allows users to link to pages by name,
+ # without having to remember the path.
+ class Links < Transformer
+ def self.filter(doc, text)
doc.css("a[href^=pekky]").each do |a|
a.attributes["href"].value = Util.look_up_page(a.attributes["href"].value)
end
end
end
+ # Util contains helper methods that don't fit anywhere else.
module Util
+ # Looks up a page based on a pekky URL.
def self.look_up_page(url)
match = url.match(/^pekky:\/\/(\S+)/)
- Pekky.tree[match[1].to_sym].path
+ qualify_path(Pekky.tree[match[1].to_sym].path)
end
+ # Qualify path adds a prefix to URLs on export.
def self.qualify_path(path)
if !path.match(/^http/) and Pekky.exporting? and Config[:path_prefix]
- Config[:path_prefix] + path
+ File.join(Config[:path_prefix], path)
else
path
end
View
2  spec/example/views/news.html.haml
@@ -1,2 +1,2 @@
%h1= content.title
-= render.textile(content.text)
+= format.textile(content.text)
View
8 spec/unit/format_spec.rb
@@ -10,16 +10,16 @@
end
it "should format textile" do
- Pekky::Format::Renderers.textile('h1. Weee').should.equal "<h1>Weee</h1>"
+ Pekky::Format::Formatters.textile('h1. Weee').should.equal "<h1>Weee</h1>"
end
it "should format markdown" do
- Pekky::Format::Renderers.textile('Weee').should.equal "<p>Weee</p>"
+ Pekky::Format::Formatters.textile('Weee').should.equal "<p>Weee</p>"
end
it "should replace instances of pekky:// with the page path" do
- Pekky::Format::Renderers.textile('"link":pekky://about').should.equal '<p><a href="/about">link</a></p>'
- Pekky::Format::Renderers.markdown('[link](pekky://about)').should.equal '<p><a href="/about">link</a></p>'
+ Pekky::Format::Formatters.textile('"link":pekky://about').should.equal '<p><a href="/about">link</a></p>'
+ Pekky::Format::Formatters.markdown('[link](pekky://about)').should.equal '<p><a href="/about">link</a></p>'
end
it "should qualify a path if we are exporting" do
Please sign in to comment.
Something went wrong with that request. Please try again.