diff options
author | Brandon Mathis <brandon@imathis.com> | 2011-07-19 09:06:54 -0400 |
---|---|---|
committer | Brandon Mathis <brandon@imathis.com> | 2011-07-19 09:06:54 -0400 |
commit | 17c59fb1d1bf3e0c05137af4b4bd09ae271a2d31 (patch) | |
tree | a4b3b5d43173f9b02ec4b6401cb6e14f6e716a35 /plugins | |
parent | 873a604e144c53cfc5465a790e43db5b7ebb429e (diff) | |
download | my_new_personal_website-17c59fb1d1bf3e0c05137af4b4bd09ae271a2d31.tar.xz my_new_personal_website-17c59fb1d1bf3e0c05137af4b4bd09ae271a2d31.zip |
Moved plugins to root directory. I'm ditching the idea of shipping plugins with themes until it's more obviously necessary. This way it's easier to merge and update plugins.
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/blockquote.rb | 74 | ||||
-rw-r--r-- | plugins/category_generator.rb | 163 | ||||
-rw-r--r-- | plugins/code_block.rb | 80 | ||||
-rw-r--r-- | plugins/compass_compiler.rb | 1 | ||||
-rw-r--r-- | plugins/custom_filters.rb | 75 | ||||
-rw-r--r-- | plugins/figure_tag.rb | 69 | ||||
-rw-r--r-- | plugins/gist_tag.rb | 94 | ||||
-rw-r--r-- | plugins/haml.rb | 24 | ||||
-rw-r--r-- | plugins/include_code.rb | 55 | ||||
-rw-r--r-- | plugins/pullquote.rb | 40 | ||||
-rw-r--r-- | plugins/pygments_cache_patch.rb | 30 | ||||
-rw-r--r-- | plugins/render_partial.rb | 52 | ||||
-rw-r--r-- | plugins/sitemap_generator.rb | 308 | ||||
-rw-r--r-- | plugins/titlecase.rb | 36 |
14 files changed, 1101 insertions, 0 deletions
diff --git a/plugins/blockquote.rb b/plugins/blockquote.rb new file mode 100644 index 00000000..d292ce8e --- /dev/null +++ b/plugins/blockquote.rb @@ -0,0 +1,74 @@ +# +# Author: Brandon Mathis +# A full rewrite based on the work of: Josediaz Gonzalez - https://github.com/josegonzalez/josediazgonzalez.com/blob/master/_plugins/blockquote.rb +# +# Outputs a string with a given attribution as a quote +# +# {% blockquote Bobby Willis http://google.com/search?q=pants the search for bobby's pants %} +# Wheeee! +# {% endblockquote %} +# ... +# <blockquote> +# <p>Wheeee!</p> +# <footer> +# <strong>Bobby Willis</strong><cite><a href="http://google.com/search?q=pants">The Search For Bobby's Pants</a> +# </blockquote> +# +require './plugins/titlecase.rb' + +module Jekyll + + class Blockquote < Liquid::Block + FullCiteWithTitle = /(\S[\S\s]*)\s+(https?:\/\/)(\S+)\s+(.+)/i + FullCite = /(\S[\S\s]*)\s+(https?:\/\/)(\S+)/i + Author = /(\S[\S\s]*)/ + + def initialize(tag_name, markup, tokens) + @by = nil + @source = nil + @title = nil + if markup =~ FullCiteWithTitle + @by = $1 + @source = $2 + $3 + @title = $4.titlecase + elsif markup =~ FullCite + @by = $1 + @source = $2 + $3 + elsif markup =~ Author + @by = $1 + end + super + end + + def render(context) + output = paragraphize(super.map(&:strip).join) + author = "<strong>#{@by.strip}</strong>" + if @source + url = @source.match(/https?:\/\/(.+)/)[1].split('/') + parts = [] + url.each do |part| + if (parts + [part]).join('/').length < 32 + parts << part + end + end + source = parts.join('/') + source << '/…' unless source == @source + end + cite = "<cite><a href='#{@source}'>#{(@title || source)}</a></cite>" + result = if @by.nil? + output + elsif !@source.nil? + "#{output}<footer>#{author + cite}</footer>" + else + "#{output}<footer>#{author}</footer>" + end + "<blockquote>#{result}</blockquote>" + end + + def paragraphize(input) + "<p>#{input.gsub(/\n\n/, '</p><p>').gsub(/\n/, '<br/>')}</p>" + end + end +end + +Liquid::Template.register_tag('blockquote', Jekyll::Blockquote) diff --git a/plugins/category_generator.rb b/plugins/category_generator.rb new file mode 100644 index 00000000..c2e9a46e --- /dev/null +++ b/plugins/category_generator.rb @@ -0,0 +1,163 @@ +# Jekyll category page generator. +# http://recursive-design.com/projects/jekyll-plugins/ +# +# Version: 0.1.4 (201101061053) +# +# Copyright (c) 2010 Dave Perrett, http://recursive-design.com/ +# Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php) +# +# A generator that creates category pages for jekyll sites. +# +# To use it, simply drop this script into the _plugins directory of your Jekyll site. You should +# also create a file called 'category_index.html' in the _layouts directory of your jekyll site +# with the following contents (note: you should remove the leading '# ' characters): +# +# ================================== COPY BELOW THIS LINE ================================== +# --- +# layout: default +# --- +# +# <h1 class="category">{{ page.title }}</h1> +# <ul class="posts"> +# {% for post in site.categories[page.category] %} +# <div>{{ post.date | date_to_html_string }}</div> +# <h2><a href="{{ post.url }}">{{ post.title }}</a></h2> +# <div class="categories">Filed under {{ post.categories | category_links }}</div> +# {% endfor %} +# </ul> +# ================================== COPY ABOVE THIS LINE ================================== +# +# You can alter the _layout_ setting if you wish to use an alternate layout, and obviously you +# can change the HTML above as you see fit. +# +# When you compile your jekyll site, this plugin will loop through the list of categories in your +# site, and use the layout above to generate a page for each one with a list of links to the +# individual posts. +# +# Included filters : +# - category_links: Outputs the list of categories as comma-separated <a> links. +# - date_to_html_string: Outputs the post.date as formatted html, with hooks for CSS styling. +# +# Available _config.yml settings : +# - category_dir: The subfolder to build category pages in (default is 'categories'). +# - category_title_prefix: The string used before the category name in the page title (default is +# 'Category: '). +module Jekyll + + + # The CategoryIndex class creates a single category page for the specified category. + class CategoryIndex < Page + + # Initializes a new CategoryIndex. + # + # +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 = 'index.html' + self.process(@name) + # Read the YAML data from the layout page. + self.read_yaml(File.join(base, '_layouts'), 'category_index.html') + 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}" + end + + end + + + # The Site class is a built-in Jekyll class with access to global site config information. + class Site + + # Creates an instance of CategoryIndex for each category page, renders it, and + # writes the output to a file. + # + # +category_dir+ is the String path to the category folder. + # +category+ is the category currently being processed. + def write_category_index(category_dir, category) + index = CategoryIndex.new(self, self.source, category_dir, category) + index.render(self.layouts, site_payload) + index.write(self.dest) + # Record the fact that this page has been added, otherwise Site::cleanup will remove it. + self.pages << index + end + + # Loops through the list of category pages and processes each one. + def write_category_indexes + if self.layouts.key? 'category_index' + dir = self.config['category_dir'] || 'categories' + self.categories.keys.each do |category| + self.write_category_index(File.join(dir, category.gsub(/_|\W/, '-')), category) + end + + # Throw an exception if the layout couldn't be found. + else + throw "No 'category_index' layout found." + end + end + + end + + + # Jekyll hook - the generate method is called by jekyll, and generates all of the category pages. + class GenerateCategories < Generator + safe true + priority :low + + def generate(site) + site.write_category_indexes + end + + end + + + # Adds some extra filters used during the category creation process. + module Filters + + # Outputs a list of categories as comma-separated <a> links. This is used + # to output the category list for each post on a category page. + # + # +categories+ is the list of categories to format. + # + # Returns string + # + def category_links(categories) + dir = @context.registers[:site].config['category_dir'] + root_url = @context.registers[:site].config['root'] + categories = categories.sort!.map do |item| + "<a class='category' href='#{root_url}/#{dir}/#{item.gsub(/_|\W/, '-')}/'>#{item}</a>" + end + + case categories.length + when 0 + "" + when 1 + categories[0].to_s + else + "#{categories[0...-1].join(', ')}, #{categories[-1]}" + end + end + + # Outputs the post.date as formatted html, with hooks for CSS styling. + # + # +date+ is the date object to format as HTML. + # + # Returns string + def date_to_html_string(date) + result = '<span class="month">' + date.strftime('%b').upcase + '</span> ' + result += date.strftime('<span class="day">%d</span> ') + result += date.strftime('<span class="year">%Y</span> ') + result + end + + end + +end + diff --git a/plugins/code_block.rb b/plugins/code_block.rb new file mode 100644 index 00000000..00762d8a --- /dev/null +++ b/plugins/code_block.rb @@ -0,0 +1,80 @@ +# Title: Simple Code Blocks for Jekyll +# Author: Brandon Mathis http://brandonmathis.com +# Description: Write codeblocks with semantic HTML5 <figure> and <figcaption> elements and optional syntax highlighting — all with a simple, intuitive interface. +# +# Syntax: {% codeblock [title] [url] [link text] %} +# +# For syntax highlighting, put a file extension somewhere in the title. examples: +# {% codeblock file.sh %} +# {% codeblock Time to be Awesome! (awesome.rb) %} +# +# Example: +# +# {% codeblock Got pain? painreleif.sh http://site.com/painreleief.sh Download it! %} +# $ rm -rf ~/PAIN +# {% endcodeblock %} +# +# Output: +# +# <figure role=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 -- +# </code></pre></div> +# </figure> +# +# Example 2 (no syntax highlighting): +# +# {% codeblock %} +# <sarcasm>Ooooh, sarcasm... How original!</sarcasm> +# {% endcodeblock %} +# +# <figure role=code> +# <pre><code><sarcasm> Ooooh, sarcasm... How original!</sarcasm></code></pre> +# </figure> +# +module Jekyll + + class CodeBlock < Liquid::Block + CaptionUrlTitle = /(\S[\S\s]*)\s+(https?:\/\/)(\S+)\s+(.+)/i + CaptionUrl = /(\S[\S\s]*)\s+(https?:\/\/)(\S+)/i + Caption = /(\S[\S\s]*)/ + def initialize(tag_name, markup, tokens) + @title = nil + @caption = nil + @highlight = true + if markup =~ CaptionUrlTitle + @file = $1 + @caption = "<figcaption><span>#{$1}</span><a href='#{$2 + $3}'>#{$4}</a</figcaption>" + elsif markup =~ CaptionUrl + @file = $1 + @caption = "<figcaption><span>#{$1}</span><a href='#{$2 + $3}'>link</a</figcaption>" + elsif markup =~ Caption + @file = $1 + @caption = "<figcaption><span>#{$1}</span></figcaption>\n" + end + if @file =~ /\S[\S\s]*\.(\w+)/ + @filetype = $1 + end + super + end + + def render(context) + output = super + code = super.join + source = "<figure role=code>\n" + source += @caption if @caption + if @filetype + source += "{% highlight #{@filetype} %}\n" + code + "\n{% endhighlight %}\n</figure>" + else + source += "<pre><code>" + code.gsub!(/</,'<') + "</code></pre>\n</figure>" + end + partial = Liquid::Template.parse(source) + context.stack do + partial.render(context) + end + end + end +end + +Liquid::Template.register_tag('codeblock', Jekyll::CodeBlock) diff --git a/plugins/compass_compiler.rb b/plugins/compass_compiler.rb new file mode 100644 index 00000000..dcec746a --- /dev/null +++ b/plugins/compass_compiler.rb @@ -0,0 +1 @@ +system "compass compile --css-dir source/stylesheets" diff --git a/plugins/custom_filters.rb b/plugins/custom_filters.rb new file mode 100644 index 00000000..f0db30ee --- /dev/null +++ b/plugins/custom_filters.rb @@ -0,0 +1,75 @@ +#custom filters for Octopress + +module OctopressFilters + # Used on the blog index to split posts on the <!--more--> marker + def exerpt(input) + if input.index(/<!--\s*more\s*-->/i) + input.split(/<!--\s*more\s*-->/i)[0] + else + input + end + end + + # Summary is used on the Archive pages to return the first block of content from a post. + def summary(input) + if input.index(/\n\n/) + input.split(/\n\n/)[0] + else + input + end + end + + # Replaces relative urls with full urls + def full_urls(input, url='') + input.gsub /(\s+(href|src)\s*=\s*["|']{1})(\/[^\"'>]+)/ do + $1+url+$3 + end + end + + # Returns a url without the http:// for use in as a search modifier eg. 'search terms site:website.com' + def search_url(input) + input.gsub /(https?:\/\/)(\S+)/ do + $2 + end + end + + # replaces primes with smartquotes using RubyPants + def smart_quotes(input) + require 'rubypants' + RubyPants.new(input).to_html + end + + # Returns a title cased string based on John Gruber's title case http://daringfireball.net/2008/08/title_case_update + def titlecase(input) + 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 OctopressFilters diff --git a/plugins/figure_tag.rb b/plugins/figure_tag.rb new file mode 100644 index 00000000..550e677f --- /dev/null +++ b/plugins/figure_tag.rb @@ -0,0 +1,69 @@ +# Title: Simple Image Figure tag for Jekyll +# Author: Brandon Mathis http://brandonmathis.com +# Description: Easily output images in <figure> with an optional <figcaption> and class names. +# +# Syntax {% figure [class name(s)] url [caption text] %} +# +# Example: +# {% figure left half http://site.com/images/ninja.png Ninja Attack! %} +# +# Output: +# <figure class='left half'><img src="http://site.com/images/ninja.png"><figcaption>Ninja Attack!</figcaption></figure> +# +# Example 2 (image with caption) +# {% figure /images/ninja.png Ninja Attack! %} +# +# Output: +# <figure><img src="/images/ninja.png"><figcaption>Ninja Attack!</figcaption></figure> +# +# Example 3 (just an image with classes) +# {% figure right /images/ninja.png %} +# +# Output: +# <figure><img class="right" src="/images/ninja.png"></figure> +# + +module Jekyll + + class FigureImageTag < Liquid::Tag + ClassImgCaption = /(\S[\S\s]*)\s+(https?:\/\/|\/)(\S+)\s+(.+)/i + ClassImg = /(\S[\S\s]*)\s+(https?:\/\/|\/)(\S+)/i + ImgCaption = /^\s*(https?:\/\/|\/)(\S+)\s+(.+)/i + Img = /^\s*(https?:\/\/|\/)(\S+\s)/i + + @img = nil + @caption = nil + @class = '' + + def initialize(tag_name, markup, tokens) + if markup =~ ClassImgCaption + @class = $1 + @img = $2 + $3 + @caption = $4 + elsif markup =~ ClassImg + @class = $1 + @img = $2 + $3 + elsif markup =~ ImgCaption + @img = $1 + $2 + @caption = $3 + elsif markup =~ Img + @img = $1 + $2 + end + super + end + + def render(context) + output = super + if @img + figure = "<figure class='#{@class}'>" + figure += "<img src='#{@img}'>" + figure += "<figcaption>#{@caption}</figcaption>" if @caption + figure += "</figure>" + else + "Error processing input, expected syntax: {% figure [class name(s)] /url/to/image [caption] %}" + end + end + end +end + +Liquid::Template.register_tag('figure', Jekyll::FigureImageTag) diff --git a/plugins/gist_tag.rb b/plugins/gist_tag.rb new file mode 100644 index 00000000..0a8797f8 --- /dev/null +++ b/plugins/gist_tag.rb @@ -0,0 +1,94 @@ +# A Liquid tag for Jekyll sites that allows embedding Gists and showing code for non-JavaScript enabled browsers and readers. +# by: Brandon Tilly +# Source URL: https://gist.github.com/1027674 +# Post http://brandontilley.com/2011/01/31/gist-tag-for-jekyll.html +# +# Example usage: {% gist 1027674 gist_tag.rb %} //embeds a gist for this plugin + +require 'cgi' +require 'digest/md5' +require 'net/https' +require 'uri' + +module Jekyll + class GistTag < Liquid::Tag + def initialize(tag_name, text, token) + super + @text = text + @cache_disabled = false + @cache_folder = File.expand_path "../_gist_cache", File.dirname(__FILE__) + FileUtils.mkdir_p @cache_folder + end + + def render(context) + if parts = @text.match(/([\d]*) (.*)/) + gist, file = parts[1].strip, parts[2].strip + script_url = script_url_for gist, file + code = get_cached_gist(gist, file) || get_gist_from_web(gist, file) + html_output_for script_url, code + else + "" + end + end + + def html_output_for(script_url, code) + code = CGI.escapeHTML code + <<-HTML +<script src='#{script_url}'></script> +<noscript><pre><code>#{code}</code></pre></noscript> + HTML + end + + def script_url_for(gist_id, filename) + "https://gist.github.com/#{gist_id}.js?file=#{filename}" + end + + def get_gist_url_for(gist, file) + "https://raw.github.com/gist/#{gist}/#{file}" + end + + def cache(gist, file, data) + cache_file = get_cache_file_for gist, file + File.open(cache_file, "w") do |io| + io.write data + end + end + + def get_cached_gist(gist, file) + return nil if @cache_disabled + cache_file = get_cache_file_for gist, file + File.read cache_file if File.exist? cache_file + end + + def get_cache_file_for(gist, file) + bad_chars = /[^a-zA-Z0-9\-_.]/ + gist = gist.gsub bad_chars, '' + file = file.gsub bad_chars, '' + md5 = Digest::MD5.hexdigest "#{gist}-#{file}" + File.join @cache_folder, "#{gist}-#{file}-#{md5}.cache" + end + + 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 + https.use_ssl = true + https.verify_mode = OpenSSL::SSL::VERIFY_NONE + request = Net::HTTP::Get.new raw_uri.request_uri + data = https.request request + data = data.body + cache gist, file, data unless @cache_disabled + data + end + end + + class GistTagNoCache < GistTag + def initialize(tag_name, text, token) + super + @cache_disabled = true + end + end +end + +Liquid::Template.register_tag('gist', Jekyll::GistTag) +Liquid::Template.register_tag('gistnocache', Jekyll::GistTagNoCache) diff --git a/plugins/haml.rb b/plugins/haml.rb new file mode 100644 index 00000000..7e548dec --- /dev/null +++ b/plugins/haml.rb @@ -0,0 +1,24 @@ +module Jekyll + require 'haml' + class HamlConverter < Converter + safe true + priority :low + + def matches(ext) + ext =~ /haml/i + end + + def output_ext(ext) + ".html" + end + + def convert(content) + begin + engine = Haml::Engine.new(content) + engine.render + rescue StandardError => e + puts "!!! HAML Error: " + e.message + end + end + end +end diff --git a/plugins/include_code.rb b/plugins/include_code.rb new file mode 100644 index 00000000..b063f14f --- /dev/null +++ b/plugins/include_code.rb @@ -0,0 +1,55 @@ +# Title: Include Code Tag for Jekyll +# Author: Brandon Mathis http://brandonmathis.com +# Description: Import files on your filesystem into any blog post as embedded code snippets with syntax highlighting and a download link. +# Configuration: You can set default import path in _config.yml (defaults to code_dir: downloads/code) +# +# Syntax {% include_code path/to/file %} +# +# Example: +# {% include_code javascripts/test.js %} +# +# This will import test.js from source/downloads/code/javascripts/test.js +# and output the contents in a syntax highlighted code block inside a figure, +# with a figcaption listing the file name and download link +# + +require 'pathname' + +module Jekyll + + class IncludeCodeTag < Liquid::Tag + def initialize(tag_name, file, tokens) + super + @file = file.strip + end + + def render(context) + code_dir = (context.registers[:site].config['code_dir'] || 'downloads/code') + code_path = (Pathname.new(context.registers[:site].source) + code_dir).expand_path + file = code_path + @file + + if File.symlink?(code_path) + return "Code directory '#{code_path}' cannot be a symlink" + end + + unless file.file? + return "File #{file} could not be found" + end + + Dir.chdir(code_path) do + code = file.read + file_type = file.extname + url = "#{context.registers[:site].config['url']}/#{code_dir}/#{@file}" + source = "<figure role=code><figcaption><span>#{file.basename}</span> <a href='#{url}'>download</a></figcaption>\n" + source += "{% highlight #{file_type} %}\n" + code + "\n{% endhighlight %}</figure>" + partial = Liquid::Template.parse(source) + context.stack do + partial.render(context) + end + end + end + end + +end + +Liquid::Template.register_tag('include_code', Jekyll::IncludeCodeTag) diff --git a/plugins/pullquote.rb b/plugins/pullquote.rb new file mode 100644 index 00000000..ff576fa0 --- /dev/null +++ b/plugins/pullquote.rb @@ -0,0 +1,40 @@ +# +# Author: Brandon Mathis +# Based on the sematic 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: +# +# {% pullquote %} +# 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. "} That is why it is prefered +# to use a CSS only technique for styling pullquotes. +# {% endpullquote %} +# ...will output... +# <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. +# </span> +# </p> +# + +module Jekyll + + class PullquoteTag < Liquid::Block + def initialize(tag_name, markup, tokens) + 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>" + else + return "Surround your pullquote like this {! text to be quoted !}" + end + end + end +end + +Liquid::Template.register_tag('pullquote', Jekyll::PullquoteTag) diff --git a/plugins/pygments_cache_patch.rb b/plugins/pygments_cache_patch.rb new file mode 100644 index 00000000..09c09840 --- /dev/null +++ b/plugins/pygments_cache_patch.rb @@ -0,0 +1,30 @@ +# +# Author: Raimonds Simanovskis, http://blog.rayapps.com/ +# Source URL: https://github.com/rsim/blog.rayapps.com/blob/master/_plugins/pygments_cache_patch.rb +# + +require 'fileutils' +require 'digest/md5' + +PYGMENTS_CACHE_DIR = File.expand_path('../../_code_cache', __FILE__) +FileUtils.mkdir_p(PYGMENTS_CACHE_DIR) + +Jekyll::HighlightBlock.class_eval do + def render_pygments(context, code) + if defined?(PYGMENTS_CACHE_DIR) + path = File.join(PYGMENTS_CACHE_DIR, "#{@lang}-#{Digest::MD5.hexdigest(code)}.html") + if File.exist?(path) + highlighted_code = File.read(path) + else + highlighted_code = Albino.new(code, @lang).to_s(@options) + File.open(path, 'w') {|f| f.print(highlighted_code) } + end + else + highlighted_code = Albino.new(code, @lang).to_s(@options) + end + output = add_code_tags(highlighted_code, @lang) + output = context["pygments_prefix"] + output if context["pygments_prefix"] + output = output + context["pygments_suffix"] if context["pygments_suffix"] + output + end +end diff --git a/plugins/render_partial.rb b/plugins/render_partial.rb new file mode 100644 index 00000000..96de97ea --- /dev/null +++ b/plugins/render_partial.rb @@ -0,0 +1,52 @@ +# Title: Render Partial Tag for Jekyll +# Author: Brandon Mathis http://brandonmathis.com +# Description: Import files on your filesystem into any blog post and render them inline. +# Note: Paths are relative to the source directory +# +# Syntax {% render_partial path/to/file %} +# +# Example 1: +# {% render_partial about/_bio.markdown %} +# +# This will import source/about/_bio.markdown and render it inline. +# In this example I used an underscore at the beginning of the filename to prevent Jekyll +# from generating an about/bio.html (Jekyll doesn't convert files beginning with underscores) +# +# Example 2: +# {% render_partial ../README.markdown %} +# +# You can use relative pathnames, to include files outside of the source directory. +# This might be useful if you want to have a page for a project's README without having +# to duplicated the contents +# + +require 'pathname' + +module Jekyll + + class RenderPartialTag < Liquid::Tag + def initialize(tag_name, file, tokens) + super + @file = file.strip + end + + def render(context) + file_dir = (context.registers[:site].source || 'source') + file_path = Pathname.new(file_dir).expand_path + file = file_path + @file + + unless file.file? + return "File #{file} could not be found" + end + + Dir.chdir(file_path) do + partial = Liquid::Template.parse(file.read) + context.stack do + partial.render(context) + end + end + end + end +end + +Liquid::Template.register_tag('render_partial', Jekyll::RenderPartialTag) diff --git a/plugins/sitemap_generator.rb b/plugins/sitemap_generator.rb new file mode 100644 index 00000000..8b6cf78c --- /dev/null +++ b/plugins/sitemap_generator.rb @@ -0,0 +1,308 @@ +# Sitemap.xml Generator is a Jekyll plugin that generates a sitemap.xml file by +# traversing all of the available posts and pages. +# +# How To Use: +# 1) Copy source file into your _plugins folder within your Jekyll project. +# 2) Change modify the url variable in _config.yml to reflect your domain name. +# 3) Run Jekyll: jekyll --server to re-generate your site. +# +# Variables: +# * Change SITEMAP_FILE_NAME if you want your sitemap to be called something +# other than sitemap.xml. +# * Change the PAGES_INCLUDE_POSTS list to include any pages that are looping +# through your posts (e.g. "index.html", "archive.html", etc.). This will +# ensure that right after you make a new post, the last modified date will +# be updated to reflect the new post. +# * A sitemap.xml should be included in your _site folder. +# * If there are any files you don't want included in the sitemap, add them +# to the EXCLUDED_FILES list. The name should match the name of the source +# file. +# * If you want to include the optional changefreq and priority attributes, +# simply include custom variables in the YAML Front Matter of that file. +# The names of these custom variables are defined below in the +# CHANGE_FREQUENCY_CUSTOM_VARIABLE_NAME and PRIORITY_CUSTOM_VARIABLE_NAME +# constants. +# +# Notes: +# * The last modified date is determined by the latest from the following: +# system modified date of the page or post, system modified date of +# included layout, system modified date of included layout within that +# layout, ... +# +# Author: Michael Levin +# Site: http://www.kinnetica.com +# Distributed Under A Creative Commons License +# - http://creativecommons.org/licenses/by/3.0/ +# +# Modified for Octopress by John W. Long +# +require 'rexml/document' + +module Jekyll + + # Change SITEMAP_FILE_NAME if you would like your sitemap file + # to be called something else + SITEMAP_FILE_NAME = "sitemap.xml" + + # Any files to exclude from being included in the sitemap.xml + EXCLUDED_FILES = ["atom.xml"] + + # Any files that include posts, so that when a new post is added, the last + # modified date of these pages should take that into account + PAGES_INCLUDE_POSTS = ["index.html"] + + # Custom variable names for changefreq and priority elements + # These names are used within the YAML Front Matter of pages or posts + # for which you want to include these properties + CHANGE_FREQUENCY_CUSTOM_VARIABLE_NAME = "change_frequency" + PRIORITY_CUSTOM_VARIABLE_NAME = "priority" + + class Post + attr_accessor :name + + def full_path_to_source + File.join(@base, @name) + end + + def location_on_server + "#{site.config['url']}#{url}" + end + end + + class Page + attr_accessor :name + + def full_path_to_source + File.join(@base, @dir, @name) + end + + def location_on_server + location = "#{site.config['url']}#{@dir}#{url}" + location.gsub(/index.html$/, "") + end + end + + class Layout + def full_path_to_source + File.join(@base, @name) + end + end + + # Recover from strange exception when starting server without --auto + class SitemapFile < StaticFile + def write(dest) + begin + super(dest) + rescue + end + + true + end + end + + class SitemapGenerator < Generator + + # Valid values allowed by sitemap.xml spec for change frequencies + VALID_CHANGE_FREQUENCY_VALUES = ["always", "hourly", "daily", "weekly", + "monthly", "yearly", "never"] + + # Goes through pages and posts and generates sitemap.xml file + # + # Returns nothing + def generate(site) + sitemap = REXML::Document.new << REXML::XMLDecl.new("1.0", "UTF-8") + + urlset = REXML::Element.new "urlset" + urlset.add_attribute("xmlns", + "http://www.sitemaps.org/schemas/sitemap/0.9") + + @last_modified_post_date = fill_posts(site, urlset) + fill_pages(site, urlset) + + sitemap.add_element(urlset) + + # File I/O: create sitemap.xml file and write out pretty-printed XML + file = File.new(File.join(site.dest, SITEMAP_FILE_NAME), "w") + formatter = REXML::Formatters::Pretty.new(4) + formatter.compact = true + formatter.write(sitemap, file) + file.close + + # Keep the sitemap.xml file from being cleaned by Jekyll + site.static_files << Jekyll::SitemapFile.new(site, site.dest, "/", SITEMAP_FILE_NAME) + end + + # Create url elements for all the posts and find the date of the latest one + # + # Returns last_modified_date of latest post + def fill_posts(site, urlset) + last_modified_date = nil + site.posts.each do |post| + if !excluded?(post.name) + url = fill_url(site, post) + urlset.add_element(url) + end + + path = post.full_path_to_source + date = File.mtime(path) + last_modified_date = date if last_modified_date == nil or date > last_modified_date + end + + last_modified_date + end + + # Create url elements for all the normal pages and find the date of the + # index to use with the pagination pages + # + # Returns last_modified_date of index page + def fill_pages(site, urlset) + site.pages.each do |page| + if !excluded?(page.name) + path = page.full_path_to_source + if File.exists?(path) + url = fill_url(site, page) + urlset.add_element(url) + end + end + end + end + + # Fill data of each URL element: location, last modified, + # change frequency (optional), and priority. + # + # Returns url REXML::Element + def fill_url(site, page_or_post) + url = REXML::Element.new "url" + + loc = fill_location(page_or_post) + url.add_element(loc) + + lastmod = fill_last_modified(site, page_or_post) + url.add_element(lastmod) if lastmod + + if (page_or_post.data[CHANGE_FREQUENCY_CUSTOM_VARIABLE_NAME]) + change_frequency = + page_or_post.data[CHANGE_FREQUENCY_CUSTOM_VARIABLE_NAME].downcase + + if (valid_change_frequency?(change_frequency)) + changefreq = REXML::Element.new "changefreq" + changefreq.text = change_frequency + url.add_element(changefreq) + else + puts "ERROR: Invalid Change Frequency In #{page_or_post.name}" + end + end + + if (page_or_post.data[PRIORITY_CUSTOM_VARIABLE_NAME]) + priority_value = page_or_post.data[PRIORITY_CUSTOM_VARIABLE_NAME] + if valid_priority?(priority_value) + priority = REXML::Element.new "priority" + priority.text = page_or_post.data[PRIORITY_CUSTOM_VARIABLE_NAME] + url.add_element(priority) + else + puts "ERROR: Invalid Priority In #{page_or_post.name}" + end + end + + url + end + + # Get URL location of page or post + # + # Returns the location of the page or post + def fill_location(page_or_post) + loc = REXML::Element.new "loc" + loc.text = page_or_post.location_on_server + + loc + end + + # Fill lastmod XML element with the last modified date for the page or post. + # + # Returns lastmod REXML::Element or nil + def fill_last_modified(site, page_or_post) + path = page_or_post.full_path_to_source + + lastmod = REXML::Element.new "lastmod" + date = File.mtime(path) + latest_date = find_latest_date(date, site, page_or_post) + + if @last_modified_post_date == nil + # This is a post + lastmod.text = latest_date.iso8601 + else + # This is a page + if posts_included?(page_or_post.name) + # We want to take into account the last post date + final_date = greater_date(latest_date, @last_modified_post_date) + lastmod.text = final_date.iso8601 + else + lastmod.text = latest_date.iso8601 + end + end + lastmod + end + + # Go through the page/post and any implemented layouts and get the latest + # modified date + # + # Returns formatted output of latest date of page/post and any used layouts + def find_latest_date(latest_date, site, page_or_post) + layouts = site.layouts + layout = layouts[page_or_post.data["layout"]] + while layout + path = layout.full_path_to_source + date = File.mtime(path) + + latest_date = date if (date > latest_date) + + layout = layouts[layout.data["layout"]] + end + + latest_date + end + + # Which of the two dates is later + # + # Returns latest of two dates + def greater_date(date1, date2) + if (date1 >= date2) + date1 + else + date2 + end + end + + # Is the page or post listed as something we want to exclude? + # + # Returns boolean + def excluded?(name) + EXCLUDED_FILES.include? name + end + + def posts_included?(name) + PAGES_INCLUDE_POSTS.include? name + end + + # Is the change frequency value provided valid according to the spec + # + # Returns boolean + def valid_change_frequency?(change_frequency) + VALID_CHANGE_FREQUENCY_VALUES.include? change_frequency + end + + # Is the priority value provided valid according to the spec + # + # Returns boolean + def valid_priority?(priority) + begin + priority_val = Float(priority) + return true if priority_val >= 0.0 and priority_val <= 1.0 + rescue ArgumentError + end + + false + end + end +end + diff --git a/plugins/titlecase.rb b/plugins/titlecase.rb new file mode 100644 index 00000000..103bf702 --- /dev/null +++ b/plugins/titlecase.rb @@ -0,0 +1,36 @@ +class String + def titlecase + small_words = %w(a an and as at but by en for if in of on or the to v v. via vs vs.) + + x = split(" ").map do |word| + # note: word could contain non-word characters! + # downcase all small_words, capitalize the rest + small_words.include?(word.gsub(/\W/, "").downcase) ? word.downcase! : word.smart_capitalize! + word + end + # 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} " } + end + + def titlecase! + replace(titlecase) + end + + def smart_capitalize + # ignore any leading crazy characters and capitalize the first real character + if self =~ /^['"\(\[']*([a-z])/ + i = index($1) + x = self[i,self.length] + # word with capitals and periods mid-word are left alone + self[i,1] = self[i,1].upcase unless x =~ /[A-Z]/ or x =~ /\.\w+/ + end + self + end + + def smart_capitalize! + replace(smart_capitalize) + end +end |