diff options
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/backtick_code_block.rb | 10 | ||||
-rw-r--r-- | plugins/blockquote.rb | 8 | ||||
-rw-r--r-- | plugins/category_generator.rb | 36 | ||||
-rw-r--r-- | plugins/code_block.rb | 9 | ||||
-rw-r--r-- | plugins/compass_compiler.rb | 1 | ||||
-rw-r--r-- | plugins/date.rb | 98 | ||||
-rw-r--r-- | plugins/gist_tag.rb | 10 | ||||
-rw-r--r-- | plugins/image_tag.rb | 45 | ||||
-rw-r--r-- | plugins/include_code.rb | 2 | ||||
-rw-r--r-- | plugins/jsfiddle.rb | 40 | ||||
-rw-r--r-- | plugins/octopress_filters.rb | 77 | ||||
-rw-r--r-- | plugins/preview_unpublished.rb | 48 | ||||
-rw-r--r-- | plugins/pullquote.rb | 15 | ||||
-rw-r--r-- | plugins/pygments_code.rb | 14 | ||||
-rw-r--r-- | plugins/rubypants.rb | 489 | ||||
-rw-r--r-- | plugins/titlecase.rb | 4 |
16 files changed, 824 insertions, 82 deletions
diff --git a/plugins/backtick_code_block.rb b/plugins/backtick_code_block.rb index 7f5076df..40e7900b 100644 --- a/plugins/backtick_code_block.rb +++ b/plugins/backtick_code_block.rb @@ -10,7 +10,7 @@ module BacktickCodeBlock @lang = nil @url = nil @title = nil - input.gsub /^`{3} *([^\n]+)?\n(.+?)\n`{3}/m do + input.gsub(/^`{3} *([^\n]+)?\n(.+?)\n`{3}/m) do @options = $1 || '' str = $2 @@ -22,12 +22,12 @@ module BacktickCodeBlock @caption = "<figcaption><span>#{$2}</span></figcaption>" end - if str.match(/\A {4}/) - str = str.gsub /^ {4}/, '' + if str.match(/\A( {4}|\t)/) + str = str.gsub(/^( {4}|\t)/, '') end if @lang.nil? || @lang == 'plain' code = tableize_code(str.gsub('<','<').gsub('>','>')) - "<figure role=code>#{@caption}#{code}</figure>" + "<figure class='code'>#{@caption}#{code}</figure>" else if @lang.include? "-raw" raw = "``` #{@options.sub('-raw', '')}\n" @@ -35,7 +35,7 @@ module BacktickCodeBlock raw += "\n```\n" else code = highlight(str, @lang) - "<figure role=code>#{@caption}#{code}</figure>" + "<figure class='code'>#{@caption}#{code}</figure>" end end end diff --git a/plugins/blockquote.rb b/plugins/blockquote.rb index a0bf12cc..62e7d143 100644 --- a/plugins/blockquote.rb +++ b/plugins/blockquote.rb @@ -46,7 +46,7 @@ module Jekyll end def render(context) - quote = paragraphize(super.map(&:strip).join) + quote = paragraphize(super) author = "<strong>#{@by.strip}</strong>" if @by if @source url = @source.match(/https?:\/\/(.+)/)[1].split('/') @@ -60,9 +60,9 @@ module Jekyll source << '/…' unless source == @source end if !@source.nil? - cite = "<cite><a href='#{@source}'>#{(@title || source)}</a></cite>" + cite = " <cite><a href='#{@source}'>#{(@title || source)}</a></cite>" elsif !@title.nil? - cite = "<cite>#{@title}</cite>" + cite = " <cite>#{@title}</cite>" end blockquote = if @by.nil? quote @@ -75,7 +75,7 @@ module Jekyll end def paragraphize(input) - "<p>#{input.gsub(/\n\n/, '</p><p>').gsub(/\n/, '<br/>')}</p>" + "<p>#{input.lstrip.rstrip.gsub(/\n\n/, '</p><p>').gsub(/\n/, '<br/>')}</p>" end end end diff --git a/plugins/category_generator.rb b/plugins/category_generator.rb index d9357bc8..bb5fd329 100644 --- a/plugins/category_generator.rb +++ b/plugins/category_generator.rb @@ -48,6 +48,35 @@ module Jekyll end + # The CategoryFeed class creates an Atom feed for the specified category. + class CategoryFeed < Page + + # Initializes a new CategoryFeed. + # + # +base+ is the String path to the <source>. + # +category_dir+ is the String path between <source> and the category folder. + # +category+ is the category currently being processed. + def initialize(site, base, category_dir, category) + @site = site + @base = base + @dir = category_dir + @name = 'atom.xml' + self.process(@name) + # Read the YAML data from the layout page. + self.read_yaml(File.join(base, '_includes/custom'), 'category_feed.xml') + self.data['category'] = category + # Set the title for this page. + title_prefix = site.config['category_title_prefix'] || 'Category: ' + self.data['title'] = "#{title_prefix}#{category}" + # Set the meta-description for this page. + meta_description_prefix = site.config['category_meta_description_prefix'] || 'Category: ' + self.data['description'] = "#{meta_description_prefix}#{category}" + + # Set the correct feed URL. + self.data['feed_url'] = "#{category_dir}/#{name}" + end + + end # The Site class is a built-in Jekyll class with access to global site config information. class Site @@ -63,6 +92,13 @@ module Jekyll index.write(self.dest) # Record the fact that this page has been added, otherwise Site::cleanup will remove it. self.pages << index + + # Create an Atom-feed for each index. + feed = CategoryFeed.new(self, self.source, category_dir, category) + feed.render(self.layouts, site_payload) + feed.write(self.dest) + # Record the fact that this page has been added, otherwise Site::cleanup will remove it. + self.pages << feed end # Loops through the list of category pages and processes each one. diff --git a/plugins/code_block.rb b/plugins/code_block.rb index 00b0b438..44e34945 100644 --- a/plugins/code_block.rb +++ b/plugins/code_block.rb @@ -24,7 +24,7 @@ # # Output: # -# <figure role=code> +# <figure class='code'> # <figcaption><span>Got pain? painrelief.sh</span> <a href="http://site.com/painrelief.sh">Download it!</a> # <div class="highlight"><pre><code class="sh"> # -- nicely escaped highlighted code -- @@ -37,7 +37,7 @@ # <sarcasm>Ooooh, sarcasm... How original!</sarcasm> # {% endcodeblock %} # -# <figure role=code> +# <figure class='code'> # <pre><code><sarcasm> Ooooh, sarcasm... How original!</sarcasm></code></pre> # </figure> # @@ -79,8 +79,8 @@ module Jekyll def render(context) output = super - code = super.join - source = "<figure role=code>" + code = super + source = "<figure class='code'>" source += @caption if @caption if @filetype source += " #{highlight(code, @filetype)}</figure>" @@ -90,6 +90,7 @@ module Jekyll source = safe_wrap(source) source = context['pygments_prefix'] + source if context['pygments_prefix'] source = source + context['pygments_suffix'] if context['pygments_suffix'] + source end end end diff --git a/plugins/compass_compiler.rb b/plugins/compass_compiler.rb deleted file mode 100644 index dcec746a..00000000 --- a/plugins/compass_compiler.rb +++ /dev/null @@ -1 +0,0 @@ -system "compass compile --css-dir source/stylesheets" diff --git a/plugins/date.rb b/plugins/date.rb new file mode 100644 index 00000000..b864f3e9 --- /dev/null +++ b/plugins/date.rb @@ -0,0 +1,98 @@ +module Octopress + module Date + + # Returns a datetime if the input is a string + def datetime(date) + if date.class == String + date = Time.parse(date) + end + date + end + + # Returns an ordidinal date eg July 22 2007 -> July 22nd 2007 + def ordinalize(date) + date = datetime(date) + "#{date.strftime('%b')} #{ordinal(date.strftime('%e').to_i)}, #{date.strftime('%Y')}" + end + + # Returns an ordinal number. 13 -> 13th, 21 -> 21st etc. + def ordinal(number) + if (11..13).include?(number.to_i % 100) + "#{number}<span>th</span>" + else + case number.to_i % 10 + when 1; "#{number}<span>st</span>" + when 2; "#{number}<span>nd</span>" + when 3; "#{number}<span>rd</span>" + else "#{number}<span>th</span>" + end + end + end + + # Formats date either as ordinal or by given date format + # Adds %o as ordinal representation of the day + def format_date(date, format) + date = datetime(date) + if format.nil? || format.empty? || format == "ordinal" + date_formatted = ordinalize(date) + else + date_formatted = date.strftime(format) + date_formatted.gsub!(/%o/, ordinal(date.strftime('%e').to_i)) + end + date_formatted + end + + end +end + + +module Jekyll + + class Post + include Octopress::Date + + # Convert this post into a Hash for use in Liquid templates. + # + # Returns <Hash> + def to_liquid + date_format = self.site.config['date_format'] + self.data.deep_merge({ + "title" => self.data['title'] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '), + "url" => self.url, + "date" => self.date, + # Monkey patch + "date_formatted" => format_date(self.date, date_format), + "updated_formatted" => self.data.has_key?('updated') ? format_date(self.data['updated'], date_format) : nil, + "id" => self.id, + "categories" => self.categories, + "next" => self.next, + "previous" => self.previous, + "tags" => self.tags, + "content" => self.content }) + end + end + + class Page + include Octopress::Date + + # Initialize a new Page. + # + # site - The Site object. + # base - The String path to the source. + # dir - The String path between the source and the file. + # name - The String filename of the file. + def initialize(site, base, dir, name) + @site = site + @base = base + @dir = dir + @name = name + + self.process(name) + self.read_yaml(File.join(base, dir), name) + # Monkey patch + date_format = self.site.config['date_format'] + self.data['date_formatted'] = format_date(self.data['date'], date_format) if self.data.has_key?('date') + self.data['updated_formatted'] = format_date(self.data['updated'], date_format) if self.data.has_key?('updated') + end + end +end
\ No newline at end of file diff --git a/plugins/gist_tag.rb b/plugins/gist_tag.rb index 946ea23f..74dd3b37 100644 --- a/plugins/gist_tag.rb +++ b/plugins/gist_tag.rb @@ -16,7 +16,7 @@ module Jekyll super @text = text @cache_disabled = false - @cache_folder = File.expand_path "../_gist_cache", File.dirname(__FILE__) + @cache_folder = File.expand_path "../.gist-cache", File.dirname(__FILE__) FileUtils.mkdir_p @cache_folder end @@ -71,7 +71,13 @@ module Jekyll def get_gist_from_web(gist, file) gist_url = get_gist_url_for gist, file raw_uri = URI.parse gist_url - https = Net::HTTP.new raw_uri.host, raw_uri.port + proxy = ENV['http_proxy'] + if proxy + proxy_uri = URI.parse(proxy) + https = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port).new raw_uri.host, raw_uri.port + else + https = Net::HTTP.new raw_uri.host, raw_uri.port + end https.use_ssl = true https.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new raw_uri.request_uri diff --git a/plugins/image_tag.rb b/plugins/image_tag.rb index 25a38df5..45670007 100644 --- a/plugins/image_tag.rb +++ b/plugins/image_tag.rb @@ -1,46 +1,47 @@ # Title: Simple Image tag for Jekyll -# Author: Brandon Mathis http://brandonmathis.com -# Description: Easily output images with optional class names and title/alt attributes +# Authors: Brandon Mathis http://brandonmathis.com +# Felix Schäfer, Frederic Hemberger +# Description: Easily output images with optional class names, width, height, title and alt attributes # -# Syntax {% image [class name(s)] url [title text] %} +# Syntax {% img [class name(s)] [http[s]:/]/path/to/image [width [height]] [title text | "title text" ["alt text"]] %} # -# Example: -# {% ima left half http://site.com/images/ninja.png Ninja Attack! %} +# Examples: +# {% img /images/ninja.png Ninja Attack! %} +# {% img left half http://site.com/images/ninja.png Ninja Attack! %} +# {% img left half http://site.com/images/ninja.png 150 150 "Ninja Attack!" "Ninja in attack posture" %} # # Output: -# <image class='left' src="http://site.com/images/ninja.png" title="Ninja Attack!" alt="Ninja Attack!"> +# <img src="/images/ninja.png"> +# <img class="left half" src="http://site.com/images/ninja.png" title="Ninja Attack!" alt="Ninja Attack!"> +# <img class="left half" src="http://site.com/images/ninja.png" width="150" height="150" title="Ninja Attack!" alt="Ninja in attack posture"> # module Jekyll class ImageTag < Liquid::Tag @img = nil - @title = nil - @class = '' - @width = '' - @height = '' def initialize(tag_name, markup, tokens) - if markup =~ /(\S.*\s+)?(https?:\/\/|\/)(\S+)(\s+\d+\s+\d+)?(\s+.+)?/i - @class = $1 || '' - @img = $2 + $3 - if $5 - @title = $5.strip - end - if $4 =~ /\s*(\d+)\s+(\d+)/ - @width = $1 - @height = $2 + attributes = ['class', 'src', 'width', 'height', 'title'] + + if markup =~ /(?<class>\S.*\s+)?(?<src>(?:https?:\/\/|\/|\S+\/)\S+)(?:\s+(?<width>\d+))?(?:\s+(?<height>\d+))?(?<title>\s+.+)?/i + @img = attributes.reduce({}) { |img, attr| img[attr] = $~[attr].strip if $~[attr]; img } + if /(?:"|')(?<title>[^"']+)?(?:"|')\s+(?:"|')(?<alt>[^"']+)?(?:"|')/ =~ @img['title'] + @img['title'] = title + @img['alt'] = alt + else + @img['alt'] = @img['title'].gsub!(/"/, '"') if @img['title'] end + @img['class'].gsub!(/"/, '') if @img['class'] end super end def render(context) - output = super if @img - "<img class='#{@class}' src='#{@img}' width='#{@width}' height='#{@height}' alt='#{@title}' title='#{@title}'>" + "<img #{@img.collect {|k,v| "#{k}=\"#{v}\"" if v}.join(" ")}>" else - "Error processing input, expected syntax: {% img [class name(s)] /url/to/image [width height] [title text] %}" + "Error processing input, expected syntax: {% img [class name(s)] [http[s]:/]/path/to/image [width [height]] [title text | \"title text\" [\"alt text\"]] %}" end end end diff --git a/plugins/include_code.rb b/plugins/include_code.rb index 80951cb5..fc6daa36 100644 --- a/plugins/include_code.rb +++ b/plugins/include_code.rb @@ -61,7 +61,7 @@ module Jekyll @filetype = file.extname.sub('.','') if @filetype.nil? title = @title ? "#{@title} (#{file.basename})" : file.basename url = "/#{code_dir}/#{@file}" - source = "<figure role=code><figcaption><span>#{title}</span> <a href='#{url}'>download</a></figcaption>\n" + source = "<figure class='code'><figcaption><span>#{title}</span> <a href='#{url}'>download</a></figcaption>\n" source += " #{highlight(code, @filetype)}</figure>" safe_wrap(source) end diff --git a/plugins/jsfiddle.rb b/plugins/jsfiddle.rb new file mode 100644 index 00000000..3ae173eb --- /dev/null +++ b/plugins/jsfiddle.rb @@ -0,0 +1,40 @@ +# Title: jsFiddle tag for Jekyll +# Author: Brian Arnold (@brianarn) +# Description: +# Given a jsFiddle shortcode, outputs the jsFiddle iframe code. +# Using 'default' will preserve defaults as specified by jsFiddle. +# +# Syntax: {% jsfiddle shorttag [tabs] [skin] [height] [width] %} +# +# Examples: +# +# Input: {% jsfiddle ccWP7 %} +# Output: <iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/ccWP7/embedded/js,resources,html,css,result/light/"></iframe> +# +# Input: {% jsfiddle ccWP7 js,html,result %} +# Output: <iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/ccWP7/embedded/js,html,result/light/"></iframe> +# + +module Jekyll + class JsFiddle < Liquid::Tag + def initialize(tag_name, markup, tokens) + if /(?<fiddle>\w+)(?:\s+(?<sequence>[\w,]+))?(?:\s+(?<skin>\w+))?(?:\s+(?<height>\w+))?(?:\s+(?<width>\w+))?/ =~ markup + @fiddle = fiddle + @sequence = (sequence unless sequence == 'default') || 'js,resources,html,css,result' + @skin = (skin unless skin == 'default') || 'light' + @width = width || '100%' + @height = height || '300px' + end + end + + def render(context) + if @fiddle + "<iframe style=\"width: #{@width}; height: #{@height}\" src=\"http://jsfiddle.net/#{@fiddle}/embedded/#{@sequence}/#{@skin}/\"></iframe>" + else + "Error processing input, expected syntax: {% jsfiddle shorttag [tabs] [skin] [height] [width] %}" + end + end + end +end + +Liquid::Template.register_tag('jsfiddle', Jekyll::JsFiddle)
\ No newline at end of file diff --git a/plugins/octopress_filters.rb b/plugins/octopress_filters.rb index 1a959892..2ba93e9e 100644 --- a/plugins/octopress_filters.rb +++ b/plugins/octopress_filters.rb @@ -2,6 +2,7 @@ require './plugins/backtick_code_block' require './plugins/post_filters' require './plugins/raw' +require './plugins/date' require 'rubypants' module OctopressFilters @@ -23,16 +24,22 @@ module Jekyll class ContentFilters < PostFilter include OctopressFilters def pre_render(post) - post.content = pre_filter(post.content) + if post.ext.match('html|textile|markdown|haml|slim|xml') + post.content = pre_filter(post.content) + end end def post_render(post) - post.content = post_filter(post.content) + if post.ext.match('html|textile|markdown|haml|slim|xml') + post.content = post_filter(post.content) + end end end end module OctopressLiquidFilters + include Octopress::Date + # Used on the blog index to split posts on the <!--more--> marker def excerpt(input) if input.index(/<!--\s*more\s*-->/i) @@ -56,6 +63,18 @@ module OctopressLiquidFilters end end + # Extracts raw content DIV from template, used for page description as {{ content }} + # contains complete sub-template code on main page level + def raw_content(input) + /<div class="entry-content">(?<content>[\s\S]*?)<\/div>\s*<(footer|\/article)>/ =~ input + return (content.nil?) ? input : content + end + + # Escapes CDATA sections in post content + def cdata_escape(input) + input.gsub(/<!\[CDATA\[/, '<![CDATA[').gsub(/\]\]>/, ']]>') + end + # Replaces relative urls with full urls def expand_urls(input, url='') url ||= '/' @@ -64,6 +83,33 @@ module OctopressLiquidFilters end end + # Improved version of Liquid's truncate: + # - Doesn't cut in the middle of a word. + # - Uses typographically correct ellipsis (…) insted of '...' + def truncate(input, length) + if input.length > length && input[0..(length-1)] =~ /(.+)\b.+$/im + $1.strip + ' …' + else + input + end + end + + # Improved version of Liquid's truncatewords: + # - Uses typographically correct ellipsis (…) insted of '...' + def truncatewords(input, length) + truncate = input.split(' ') + if truncate.length > length + truncate[0..length-1].join(' ').strip + ' …' + else + input + end + end + + # Condenses multiple spaces and tabs into a single space + def condense_spaces(input) + input.gsub(/\s{2,}/, ' ') + end + # Removes trailing forward slash from a string for easily appending url segments def strip_slash(input) if input =~ /(.+)\/$|^\/$/ @@ -84,33 +130,6 @@ module OctopressLiquidFilters input.titlecase end - # Returns a datetime if the input is a string - def datetime(date) - if date.class == String - date = Time.parse(date) - end - date - end - - # Returns an ordidinal date eg July 22 2007 -> July 22nd 2007 - def ordinalize(date) - date = datetime(date) - "#{date.strftime('%b')} #{ordinal(date.strftime('%e').to_i)}, #{date.strftime('%Y')}" - end - - # Returns an ordinal number. 13 -> 13th, 21 -> 21st etc. - def ordinal(number) - if (11..13).include?(number.to_i % 100) - "#{number}<span>th</span>" - else - case number.to_i % 10 - when 1; "#{number}<span>st</span>" - when 2; "#{number}<span>nd</span>" - when 3; "#{number}<span>rd</span>" - else "#{number}<span>th</span>" - end - end - end end Liquid::Template.register_filter OctopressLiquidFilters diff --git a/plugins/preview_unpublished.rb b/plugins/preview_unpublished.rb new file mode 100644 index 00000000..321ffd6f --- /dev/null +++ b/plugins/preview_unpublished.rb @@ -0,0 +1,48 @@ +# Monkeypatch for Jekyll +# Introduce distinction between preview/productive site generation +# so posts with YAML attribute `published: false` can be previewed +# on localhost without being published to the productive environment. + +module Jekyll + + class Site + # Read all the files in <source>/<dir>/_posts and create a new Post + # object with each one. + # + # dir - The String relative path of the directory to read. + # + # Returns nothing. + def read_posts(dir) + base = File.join(self.source, dir, '_posts') + return unless File.exists?(base) + entries = Dir.chdir(base) { filter_entries(Dir['**/*']) } + + # first pass processes, but does not yet render post content + entries.each do |f| + if Post.valid?(f) + post = Post.new(self, self.source, dir, f) + + # Monkeypatch: + # On preview environment (localhost), publish all posts + if ENV.has_key?('OCTOPRESS_ENV') && ENV['OCTOPRESS_ENV'] == 'preview' && post.data.has_key?('published') && post.data['published'] == false + post.published = true + # Set preview mode flag (if necessary), `rake generate` will check for it + # to prevent pushing preview posts to productive environment + File.open(".preview-mode", "w") {} + end + + if post.published && (self.future || post.date <= self.time) + self.posts << post + post.categories.each { |c| self.categories[c] << post } + post.tags.each { |c| self.tags[c] << post } + end + end + end + + self.posts.sort! + + # limit the posts if :limit_posts option is set + self.posts = self.posts[-limit_posts, limit_posts] if limit_posts + end + end +end
\ No newline at end of file diff --git a/plugins/pullquote.rb b/plugins/pullquote.rb index 03e307a7..3c65e66e 100644 --- a/plugins/pullquote.rb +++ b/plugins/pullquote.rb @@ -1,6 +1,6 @@ # # Author: Brandon Mathis -# Based on the sematic pullquote technique by Maykel Loomans at http://miekd.com/articles/pull-quotes-with-html5-and-css/ +# Based on the semantic pullquote technique by Maykel Loomans at http://miekd.com/articles/pull-quotes-with-html5-and-css/ # # Outputs a span with a data-pullquote attribute set from the marked pullquote. Example: # @@ -13,23 +13,28 @@ # <p> # <span data-pullquote="pullquotes are merely visual in presentation and should not appear twice in the text."> # When writing longform posts, I find it helpful to include pullquotes, which help those scanning a post discern whether or not a post is helpful. -# It is important to note, pullquotes are merely visual in presentation and should not appear twice in the text. This is why a CSS only approach # for styling pullquotes is prefered. +# It is important to note, pullquotes are merely visual in presentation and should not appear twice in the text. This is why a CSS only approach +# for styling pullquotes is prefered. # </span> # </p> # +# {% pullquote left %} will create a left-aligned pullquote instead. +# +# Note: this plugin now creates pullquotes with the class of pullquote-right by default module Jekyll class PullquoteTag < Liquid::Block def initialize(tag_name, markup, tokens) + @align = (markup =~ /left/i) ? "left" : "right" super end def render(context) output = super - if output.join =~ /\{"\s*(.+)\s*"\}/ - @quote = $1 - "<span class='has-pullquote' data-pullquote='#{@quote}'>#{output.join.gsub(/\{"\s*|\s*"\}/, '')}</span>" + if output =~ /\{"\s*(.+?)\s*"\}/m + @quote = RubyPants.new($1).to_html + "<span class='pullquote-#{@align}' data-pullquote='#{@quote}'>#{output.gsub(/\{"\s*|\s*"\}/, '')}</span>" else return "Surround your pullquote like this {\" text to be quoted \"}" end diff --git a/plugins/pygments_code.rb b/plugins/pygments_code.rb index 67ce8c34..1676a3e0 100644 --- a/plugins/pygments_code.rb +++ b/plugins/pygments_code.rb @@ -2,7 +2,7 @@ require 'pygments' require 'fileutils' require 'digest/md5' -PYGMENTS_CACHE_DIR = File.expand_path('../../_code_cache', __FILE__) +PYGMENTS_CACHE_DIR = File.expand_path('../../.pygments-cache', __FILE__) FileUtils.mkdir_p(PYGMENTS_CACHE_DIR) module HighlightCode @@ -21,21 +21,21 @@ module HighlightCode if File.exist?(path) highlighted_code = File.read(path) else - highlighted_code = Pygments.highlight(code, :lexer => lang, :formatter => 'html') + highlighted_code = Pygments.highlight(code, :lexer => lang, :formatter => 'html', :options => {:encoding => 'utf-8'}) File.open(path, 'w') {|f| f.print(highlighted_code) } end else - highlighted_code = Pygments.highlight(code, :lexer => lang, :formatter => 'html') + highlighted_code = Pygments.highlight(code, :lexer => lang, :formatter => 'html', :options => {:encoding => 'utf-8'}) end highlighted_code end def tableize_code (str, lang = '') - table = '<div class="highlight"><table cellpadding="0" cellspacing="0"><tr><td class="gutter"><pre class="line-numbers">' + table = '<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers">' code = '' str.lines.each_with_index do |line,index| - table += "<span class='line'>#{index+1}</span>\n" - code += "<div class='line'>#{line}</div>" + table += "<span class='line-number'>#{index+1}</span>\n" + code += "<span class='line'>#{line}</span>" end - table += "</pre></td><td class='code' width='100%'><pre><code class='#{lang}'>#{code}</code></pre></td></tr></table></div>" + table += "</pre></td><td class='code'><pre><code class='#{lang}'>#{code}</code></pre></td></tr></table></div>" end end diff --git a/plugins/rubypants.rb b/plugins/rubypants.rb new file mode 100644 index 00000000..e4f4502f --- /dev/null +++ b/plugins/rubypants.rb @@ -0,0 +1,489 @@ +# +# = RubyPants -- SmartyPants ported to Ruby +# +# Ported by Christian Neukirchen <mailto:chneukirchen@gmail.com> +# Copyright (C) 2004 Christian Neukirchen +# +# Incooporates ideas, comments and documentation by Chad Miller +# Copyright (C) 2004 Chad Miller +# +# Original SmartyPants by John Gruber +# Copyright (C) 2003 John Gruber +# + +# +# = RubyPants -- SmartyPants ported to Ruby +# +# == Synopsis +# +# RubyPants is a Ruby port of the smart-quotes library SmartyPants. +# +# The original "SmartyPants" is a free web publishing plug-in for +# Movable Type, Blosxom, and BBEdit that easily translates plain ASCII +# punctuation characters into "smart" typographic punctuation HTML +# entities. +# +# +# == Description +# +# RubyPants can perform the following transformations: +# +# * Straight quotes (<tt>"</tt> and <tt>'</tt>) into "curly" quote +# HTML entities +# * Backticks-style quotes (<tt>``like this''</tt>) into "curly" quote +# HTML entities +# * Dashes (<tt>--</tt> and <tt>---</tt>) into en- and em-dash +# entities +# * Three consecutive dots (<tt>...</tt> or <tt>. . .</tt>) into an +# ellipsis entity +# +# This means you can write, edit, and save your posts using plain old +# ASCII straight quotes, plain dashes, and plain dots, but your +# published posts (and final HTML output) will appear with smart +# quotes, em-dashes, and proper ellipses. +# +# RubyPants does not modify characters within <tt><pre></tt>, +# <tt><code></tt>, <tt><kbd></tt>, <tt><math></tt> or +# <tt><script></tt> tag blocks. Typically, these tags are used to +# display text where smart quotes and other "smart punctuation" would +# not be appropriate, such as source code or example markup. +# +# +# == Backslash Escapes +# +# If you need to use literal straight quotes (or plain hyphens and +# periods), RubyPants accepts the following backslash escape sequences +# to force non-smart punctuation. It does so by transforming the +# escape sequence into a decimal-encoded HTML entity: +# +# \\ \" \' \. \- \` +# +# This is useful, for example, when you want to use straight quotes as +# foot and inch marks: 6'2" tall; a 17" iMac. (Use <tt>6\'2\"</tt> +# resp. <tt>17\"</tt>.) +# +# +# == Algorithmic Shortcomings +# +# One situation in which quotes will get curled the wrong way is when +# apostrophes are used at the start of leading contractions. For +# example: +# +# 'Twas the night before Christmas. +# +# In the case above, RubyPants will turn the apostrophe into an +# opening single-quote, when in fact it should be a closing one. I +# don't think this problem can be solved in the general case--every +# word processor I've tried gets this wrong as well. In such cases, +# it's best to use the proper HTML entity for closing single-quotes +# ("<tt>’</tt>") by hand. +# +# +# == Bugs +# +# To file bug reports or feature requests (except see above) please +# send email to: mailto:chneukirchen@gmail.com +# +# If the bug involves quotes being curled the wrong way, please send +# example text to illustrate. +# +# +# == Authors +# +# John Gruber did all of the hard work of writing this software in +# Perl for Movable Type and almost all of this useful documentation. +# Chad Miller ported it to Python to use with Pyblosxom. +# +# Christian Neukirchen provided the Ruby port, as a general-purpose +# library that follows the *Cloth API. +# +# +# == Copyright and License +# +# === SmartyPants license: +# +# Copyright (c) 2003 John Gruber +# (http://daringfireball.net) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# * Neither the name "SmartyPants" nor the names of its contributors +# may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# This software is provided by the copyright holders and contributors +# "as is" and any express or implied warranties, including, but not +# limited to, the implied warranties of merchantability and fitness +# for a particular purpose are disclaimed. In no event shall the +# copyright owner or contributors be liable for any direct, indirect, +# incidental, special, exemplary, or consequential damages (including, +# but not limited to, procurement of substitute goods or services; +# loss of use, data, or profits; or business interruption) however +# caused and on any theory of liability, whether in contract, strict +# liability, or tort (including negligence or otherwise) arising in +# any way out of the use of this software, even if advised of the +# possibility of such damage. +# +# === RubyPants license +# +# RubyPants is a derivative work of SmartyPants and smartypants.py. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# This software is provided by the copyright holders and contributors +# "as is" and any express or implied warranties, including, but not +# limited to, the implied warranties of merchantability and fitness +# for a particular purpose are disclaimed. In no event shall the +# copyright owner or contributors be liable for any direct, indirect, +# incidental, special, exemplary, or consequential damages (including, +# but not limited to, procurement of substitute goods or services; +# loss of use, data, or profits; or business interruption) however +# caused and on any theory of liability, whether in contract, strict +# liability, or tort (including negligence or otherwise) arising in +# any way out of the use of this software, even if advised of the +# possibility of such damage. +# +# +# == Links +# +# John Gruber:: http://daringfireball.net +# SmartyPants:: http://daringfireball.net/projects/smartypants +# +# Chad Miller:: http://web.chad.org +# +# Christian Neukirchen:: http://kronavita.de/chris +# + + +class RubyPants < String + + # Create a new RubyPants instance with the text in +string+. + # + # Allowed elements in the options array: + # + # 0 :: do nothing + # 1 :: enable all, using only em-dash shortcuts + # 2 :: enable all, using old school en- and em-dash shortcuts (*default*) + # 3 :: enable all, using inverted old school en and em-dash shortcuts + # -1 :: stupefy (translate HTML entities to their ASCII-counterparts) + # + # If you don't like any of these defaults, you can pass symbols to change + # RubyPants' behavior: + # + # <tt>:quotes</tt> :: quotes + # <tt>:backticks</tt> :: backtick quotes (``double'' only) + # <tt>:allbackticks</tt> :: backtick quotes (``double'' and `single') + # <tt>:dashes</tt> :: dashes + # <tt>:oldschool</tt> :: old school dashes + # <tt>:inverted</tt> :: inverted old school dashes + # <tt>:ellipses</tt> :: ellipses + # <tt>:convertquotes</tt> :: convert <tt>"</tt> entities to + # <tt>"</tt> for Dreamweaver users + # <tt>:stupefy</tt> :: translate RubyPants HTML entities + # to their ASCII counterparts. + # + def initialize(string, options=[2]) + super string + @options = [*options] + end + + # Apply SmartyPants transformations. + def to_html + do_quotes = do_backticks = do_dashes = do_ellipses = do_stupify = nil + convert_quotes = false + + if @options.include? 0 + # Do nothing. + return self + elsif @options.include? 1 + # Do everything, turn all options on. + do_quotes = do_backticks = do_ellipses = true + do_dashes = :normal + elsif @options.include? 2 + # Do everything, turn all options on, use old school dash shorthand. + do_quotes = do_backticks = do_ellipses = true + do_dashes = :oldschool + elsif @options.include? 3 + # Do everything, turn all options on, use inverted old school + # dash shorthand. + do_quotes = do_backticks = do_ellipses = true + do_dashes = :inverted + elsif @options.include?(-1) + do_stupefy = true + else + do_quotes = @options.include? :quotes + do_backticks = @options.include? :backticks + do_backticks = :both if @options.include? :allbackticks + do_dashes = :normal if @options.include? :dashes + do_dashes = :oldschool if @options.include? :oldschool + do_dashes = :inverted if @options.include? :inverted + do_ellipses = @options.include? :ellipses + convert_quotes = @options.include? :convertquotes + do_stupefy = @options.include? :stupefy + end + + # Parse the HTML + tokens = tokenize + + # Keep track of when we're inside <pre> or <code> tags. + in_pre = false + + # Here is the result stored in. + result = "" + + # This is a cheat, used to get some context for one-character + # tokens that consist of just a quote char. What we do is remember + # the last character of the previous text token, to use as context + # to curl single- character quote tokens correctly. + prev_token_last_char = nil + + tokens.each { |token| + if token.first == :tag + result << token[1] + if token[1] =~ %r!<(/?)(?:pre|code|kbd|script|math)[\s>]! + in_pre = ($1 != "/") # Opening or closing tag? + end + else + t = token[1] + + # Remember last char of this token before processing. + last_char = t[-1].chr + + unless in_pre + t = process_escapes t + + t.gsub!(/"/, '"') if convert_quotes + + if do_dashes + t = educate_dashes t if do_dashes == :normal + t = educate_dashes_oldschool t if do_dashes == :oldschool + t = educate_dashes_inverted t if do_dashes == :inverted + end + + t = educate_ellipses t if do_ellipses + + # Note: backticks need to be processed before quotes. + if do_backticks + t = educate_backticks t + t = educate_single_backticks t if do_backticks == :both + end + + if do_quotes + if t == "'" + # Special case: single-character ' token + if prev_token_last_char =~ /\S/ + t = "’" + else + t = "‘" + end + elsif t == '"' + # Special case: single-character " token + if prev_token_last_char =~ /\S/ + t = "”" + else + t = "“" + end + else + # Normal case: + t = educate_quotes t + end + end + + t = stupefy_entities t if do_stupefy + end + + prev_token_last_char = last_char + result << t + end + } + + # Done + result + end + + protected + + # Return the string, with after processing the following backslash + # escape sequences. This is useful if you want to force a "dumb" quote + # or other character to appear. + # + # Escaped are: + # \\ \" \' \. \- \` + # + def process_escapes(str) + str.gsub('\\\\', '\'). + gsub('\"', '"'). + gsub("\\\'", '''). + gsub('\.', '.'). + gsub('\-', '-'). + gsub('\`', '`') + end + + # The string, with each instance of "<tt>--</tt>" translated to an + # em-dash HTML entity. + # + def educate_dashes(str) + str.gsub(/--/, '—') + end + + # The string, with each instance of "<tt>--</tt>" translated to an + # en-dash HTML entity, and each "<tt>---</tt>" translated to an + # em-dash HTML entity. + # + def educate_dashes_oldschool(str) + str.gsub(/---/, '—').gsub(/--/, '–') + end + + # Return the string, with each instance of "<tt>--</tt>" translated + # to an em-dash HTML entity, and each "<tt>---</tt>" translated to + # an en-dash HTML entity. Two reasons why: First, unlike the en- and + # em-dash syntax supported by +educate_dashes_oldschool+, it's + # compatible with existing entries written before SmartyPants 1.1, + # back when "<tt>--</tt>" was only used for em-dashes. Second, + # em-dashes are more common than en-dashes, and so it sort of makes + # sense that the shortcut should be shorter to type. (Thanks to + # Aaron Swartz for the idea.) + # + def educate_dashes_inverted(str) + str.gsub(/---/, '–').gsub(/--/, '—') + end + + # Return the string, with each instance of "<tt>...</tt>" translated + # to an ellipsis HTML entity. Also converts the case where there are + # spaces between the dots. + # + def educate_ellipses(str) + str.gsub('...', '…').gsub('. . .', '…') + end + + # Return the string, with "<tt>``backticks''</tt>"-style single quotes + # translated into HTML curly quote entities. + # + def educate_backticks(str) + str.gsub("``", '“').gsub("''", '”') + end + + # Return the string, with "<tt>`backticks'</tt>"-style single quotes + # translated into HTML curly quote entities. + # + def educate_single_backticks(str) + str.gsub("`", '‘').gsub("'", '’') + end + + # Return the string, with "educated" curly quote HTML entities. + # + def educate_quotes(str) + punct_class = '[!"#\$\%\'()*+,\-.\/:;<=>?\@\[\\\\\]\^_`{|}~]' + + str = str.dup + + # Special case if the very first character is a quote followed by + # punctuation at a non-word-break. Close the quotes by brute + # force: + str.gsub!(/^'(?=#{punct_class}\B)/, '’') + str.gsub!(/^"(?=#{punct_class}\B)/, '”') + + # Special case for double sets of quotes, e.g.: + # <p>He said, "'Quoted' words in a larger quote."</p> + str.gsub!(/"'(?=\w)/, '“‘') + str.gsub!(/'"(?=\w)/, '‘“') + + # Special case for decade abbreviations (the '80s): + str.gsub!(/'(?=\d\ds)/, '’') + + close_class = %![^\ \t\r\n\\[\{\(\-]! + dec_dashes = '–|—' + + # Get most opening single quotes: + str.gsub!(/(\s| |--|&[mn]dash;|#{dec_dashes}|ȁ[34];)'(?=\w)/, + '\1‘') + # Single closing quotes: + str.gsub!(/(#{close_class})'/, '\1’') + str.gsub!(/'(\s|s\b|$)/, '’\1') + # Any remaining single quotes should be opening ones: + str.gsub!(/'/, '‘') + + # Get most opening double quotes: + str.gsub!(/(\s| |--|&[mn]dash;|#{dec_dashes}|ȁ[34];)"(?=\w)/, + '\1“') + # Double closing quotes: + str.gsub!(/(#{close_class})"/, '\1”') + str.gsub!(/"(\s|s\b|$)/, '”\1') + # Any remaining quotes should be opening ones: + str.gsub!(/"/, '“') + + str + end + + # Return the string, with each RubyPants HTML entity translated to + # its ASCII counterpart. + # + # Note: This is not reversible (but exactly the same as in SmartyPants) + # + def stupefy_entities(str) + str. + gsub(/–/, '-'). # en-dash + gsub(/—/, '--'). # em-dash + + gsub(/‘/, "'"). # open single quote + gsub(/’/, "'"). # close single quote + + gsub(/“/, '"'). # open double quote + gsub(/”/, '"'). # close double quote + + gsub(/…/, '...') # ellipsis + end + + # Return an array of the tokens comprising the string. Each token is + # either a tag (possibly with nested, tags contained therein, such + # as <tt><a href="<MTFoo>"></tt>, or a run of text between + # tags. Each element of the array is a two-element array; the first + # is either :tag or :text; the second is the actual value. + # + # Based on the <tt>_tokenize()</tt> subroutine from Brad Choate's + # MTRegex plugin. <http://www.bradchoate.com/past/mtregex.php> + # + # This is actually the easier variant using tag_soup, as used by + # Chad Miller in the Python port of SmartyPants. + # + def tokenize + tag_soup = /([^<]*)(<[^>]*>)/ + + tokens = [] + + prev_end = 0 + scan(tag_soup) { + tokens << [:text, $1] if $1 != "" + tokens << [:tag, $2] + + prev_end = $~.end(0) + } + + if prev_end < size + tokens << [:text, self[prev_end..-1]] + end + + tokens + end +end diff --git a/plugins/titlecase.rb b/plugins/titlecase.rb index 103bf702..7648932c 100644 --- a/plugins/titlecase.rb +++ b/plugins/titlecase.rb @@ -11,8 +11,8 @@ class String # capitalize first and last words x.first.to_s.smart_capitalize! x.last.to_s.smart_capitalize! - # small words after colons are capitalized - x.join(" ").gsub(/:\s?(\W*#{small_words.join("|")}\W*)\s/) { ": #{$1.smart_capitalize} " } + # small words are capitalized after colon, period, exclamation mark, question mark + x.join(" ").gsub(/(:|\.|!|\?)\s?(\W*#{small_words.join("|")}\W*)\s/) { "#{$1} #{$2.smart_capitalize} " } end def titlecase! |