diff options
Diffstat (limited to 'site/src/main/jekyll/_plugins')
-rw-r--r-- | site/src/main/jekyll/_plugins/include_code.rb | 95 | ||||
-rw-r--r-- | site/src/main/jekyll/_plugins/pygments_code.rb | 201 | ||||
-rw-r--r-- | site/src/main/jekyll/_plugins/raw.rb | 40 | ||||
-rw-r--r-- | site/src/main/jekyll/_plugins/sitemap_generator.rb | 297 |
4 files changed, 633 insertions, 0 deletions
diff --git a/site/src/main/jekyll/_plugins/include_code.rb b/site/src/main/jekyll/_plugins/include_code.rb new file mode 100644 index 00000000..5d62064d --- /dev/null +++ b/site/src/main/jekyll/_plugins/include_code.rb @@ -0,0 +1,95 @@ +# 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 1: +# {% 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 +# +# Example 2: +# You can also include an optional title for the <figcaption> +# +# {% include_code javascripts/test.js Example 2 %} +# +# will output a figcaption with the title: Example 2 (test.js) +# +#encoding: utf-8 + +#require 'colorator' +require 'pathname' +require './_plugins/pygments_code' + +module Jekyll + + class IncludeCodeTag < Liquid::Tag + def initialize(tag_name, markup, tokens) + @file = nil + @title_old = nil + @original_markup = markup + + opts = Octopress::Pygments.parse_markup(markup) + @options = opts.merge({ + link_text: opts[:link_text] || 'view raw', + start: opts[:start] || 1 + }) + markup = Octopress::Pygments.clean_markup(markup) + + if markup.strip =~ /(^\S*\.\S+) *(.+)?/i + @file = $1 + @options[:title] ||= $2 + elsif markup.strip =~ /(.*?)(\S*\.\S+)\Z/i # Title before file is deprecated in 2.1 + @title_old = $1 + @file = $2 + end + super + end + + def render(context) + code_dir = (context.registers[:site].config['code_dir'].sub(/^\//,'') || 'downloads/code') + code_path = (Pathname.new(context.registers[:site].source) + code_dir).expand_path + filepath = code_path + @file + + unless @title_old.nil? + @options[:title] ||= @title_old + puts "### ------------ WARNING ------------ ###".yellow + puts "This include_code syntax is deprecated ".yellow + puts "Correct syntax: path/to/file.ext [title]".yellow + puts "Update include for #{filepath}".yellow + puts "### --------------------------------- ###".yellow + end + + if File.symlink?(code_path) + puts "Code directory '#{code_path}' cannot be a symlink".yellow + return "Code directory '#{code_path}' cannot be a symlink".yellow + end + + unless filepath.file? + puts "File #{filepath} could not be found".yellow + return "File #{filepath} could not be found".yellow + end + + Dir.chdir(code_path) do + @options[:lang] ||= filepath.extname.sub('.','') + @options[:title] = @options[:title] ? "#{@options[:title]} (#{filepath.basename})" : filepath.basename + @options[:url] ||= "/#{code_dir}/#{@file}" + + code = filepath.read + code = Octopress::Pygments.get_range(code, @options[:start], @options[:end]) + begin + Octopress::Pygments.highlight(code, @options) + rescue MentosError => e + markup = "{% include_code #{@original_markup} %}" + Octopress::Pygments.highlight_failed(e, "{% include_code [title] [lang:language] path/to/file [start:#] [end:#] [range:#-#] [mark:#,#-#] [linenos:false] %}", markup, code, filepath) + end + end + end + end +end + +Liquid::Template.register_tag('include_code', Jekyll::IncludeCodeTag) diff --git a/site/src/main/jekyll/_plugins/pygments_code.rb b/site/src/main/jekyll/_plugins/pygments_code.rb new file mode 100644 index 00000000..c83ab474 --- /dev/null +++ b/site/src/main/jekyll/_plugins/pygments_code.rb @@ -0,0 +1,201 @@ +require 'pygments' +require 'fileutils' +require 'digest/md5' +#require 'colorator' + +PYGMENTS_CACHE_DIR = File.expand_path('../../.pygments-cache', __FILE__) +FileUtils.mkdir_p(PYGMENTS_CACHE_DIR) + +module Octopress + module Pygments + + def self.render_pygments(code, lang) + highlighted_code = ::Pygments.highlight(code, :lexer => lang, :formatter => 'html', :options => {:encoding => 'utf-8'}) + highlighted_code = highlighted_code.gsub(/{{/, '{{').gsub(/{%/, '{%') + highlighted_code.to_s + end + + def self.highlight(code, options = {}) + lang = options[:lang] + lang = 'ruby' if lang == 'ru' + lang = 'objc' if lang == 'm' + lang = 'perl' if lang == 'pl' + lang = 'yaml' if lang == 'yml' + lang = 'coffeescript' if lang == 'coffee' + lang = 'csharp' if lang == 'cs' + lang = 'plain' if lang == '' or lang.nil? or !lang + + options[:lang] = lang + options[:title] ||= ' ' if options[:url] + options[:no_cache] = true + + # Attempt to retrieve cached code + cache = nil + unless options[:no_cache] + path = options[:cache_path] || get_cache_path(PYGMENTS_CACHE_DIR, options[:lang], options.to_s + code) + cache = read_cache(path) + end + + unless cache + if options[:lang] == 'plain' + # Escape html tags + code = code.gsub('<','<') + else + code = render_pygments(code, options[:lang]).match(/<pre>(.+)<\/pre>/m)[1].gsub(/ *$/, '') #strip out divs <div class="highlight"> + end + code = tableize_code(code, options[:lang], {linenos: options[:linenos], start: options[:start], marks: options[:marks]}) + code = "<figure class='code'>#{code}</figure>" + File.open(path, 'w') {|f| f.print(code) } unless options[:no_cache] + end + cache || code + end + + def self.read_cache (path) + File.exist?(path) ? File.read(path) : nil unless path.nil? + end + + def self.get_cache_path (dir, name, str) + File.join(dir, "#{name}-#{Digest::MD5.hexdigest(str)}.html") + end + + def self.captionize (caption, url, link_text) + figcaption = "<figcaption>#{caption}" + figcaption += "<a href='#{url}'>#{(link_text || 'link').strip}</a>" if url + figcaption += "</figcaption>" + end + + def self.tableize_code (code, lang, options = {}) + start = options[:start] || 1 + lines = options[:linenos] || true + marks = options[:marks] || [] + table = "<div class='highlight'><table><tr>" + + if lines == true + table += number_lines(start, code.lines.count, marks) if lines + end + + table += "<td class='main #{'unnumbered' unless lines} #{lang}'><pre>" + code.lines.each_with_index do |line,index| + classes = 'line' + if marks.include? index + start + classes += ' bg-warning' + classes += ' start' unless marks.include? index - 1 + start + classes += ' end' unless marks.include? index + 1 + start + end + line = line.strip.empty? ? ' ' : line + table += "<div class='#{classes}'>#{line}</div>" + end + table +="</pre></td></tr></table></div>" + end + + def self.number_lines (start, count, marks) + start ||= 1 + lines = "<td class='line-numbers' aria-hidden='true'><pre>" + count.times do |index| + classes = 'line-number' + if marks.include? index + start + classes += ' marked' + classes += ' start' unless marks.include? index - 1 + start + classes += ' end' unless marks.include? index + 1 + start + end + lines += "<div data-line='#{index + start}' class='#{classes}'></div>" + end + lines += "</pre></td>" + end + + def self.parse_markup (input, defaults={}) + lang = input.match(/\s*lang:\s*(\S+)/i) + lang = (lang.nil? ? nil : lang[1]) + + url = input.match(/\s*url:\s*(("(.+?)")|('(.+?)')|(\S+))/i) + url = (url.nil? ? nil : url[3] || url[5] || url[6]) + + title = input.match(/\s*title:\s*(("(.+?)")|('(.+?)')|(\S+))/i) + title = (title.nil? ? nil : title[3] || title[5] || title[6]) + title ||= ' ' if url + + linenos = input.match(/\s*linenos:\s*(\w+)/i) + linenos = (linenos.nil? ? nil : linenos[1]) + + marks = get_marks(input) + + link_text = input.match(/\s*link[-_]text:\s*(("(.+?)")|('(.+?)')|(\S+))/i) + link_text = (link_text.nil? ? 'link' : link_text[3] || link_text[5] || link_text[6]) + + start = input.match(/\s*start:\s*(\d+)/i) + start = (start.nil? ? nil : start[1].to_i) + + endline = input.match(/\s*end:\s*(\d+)/i) + endline = (endline.nil? ? nil : endline[1].to_i) + + if input =~ / *range:(\d+)-(\d+)/i + start = $1.to_i + endline = $2.to_i + end + + options = { + lang: lang, + url: url, + title: title, + linenos: linenos, + marks: marks, + link_text: link_text, + start: start, + end: endline + } + + #defaults.each { |k,v| options[k] ||= defaults[k] } + options.each { |k,v| options[k] ||= defaults[k] } + end + + def self.clean_markup (input) + input.sub(/\s*lang:\s*\S+/i,'' + ).sub(/\s*title:\s*(("(.+?)")|('(.+?)')|(\S+))/i,'' + ).sub(/\s*url:\s*(\S+)/i,'' + ).sub(/\s*link_text:\s*(("(.+?)")|('(.+?)')|(\S+))/i,'' + ).sub(/\s*mark:\s*\d\S*/i,'' + ).sub(/\s*linenos:\s*\w+/i,'' + ).sub(/\s*start:\s*\d+/i,'' + ).sub(/\s*end:\s*\d+/i,'' + ).sub(/\s*range:\s*\d+-\d+/i,'') + end + + def self.get_marks (input) + # Matches pattern for line marks and returns array of line numbers to mark + # Example input mark:1,5-10,2 + # Outputs: [1,2,5,6,7,8,9,10] + marks = [] + if input =~ / *mark:(\d\S*)/i + marks = $1.gsub /(\d+)-(\d+)/ do + ($1.to_i..$2.to_i).to_a.join(',') + end + marks = marks.split(',').collect {|s| s.to_i}.sort + end + marks + end + + def self.get_range (code, start, endline) + length = code.lines.count + start ||= 1 + endline ||= length + if start > 1 or endline < length + raise "#{filepath} is #{length} lines long, cannot begin at line #{start}" if start > length + raise "#{filepath} is #{length} lines long, cannot read beyond line #{endline}" if endline > length + code = code.split(/\n/).slice(start - 1, endline + 1 - start).join("\n") + end + code + end + + def self.highlight_failed(error, syntax, markup, code, file = nil) + code_snippet = code.split("\n")[0..9].map{|l| " #{l}" }.join("\n") + fail_message = "\nPygments Error while parsing the following markup#{" in #{file}" if file}:\n\n".red + fail_message += " #{markup}\n#{code_snippet}\n" + fail_message += "#{" ..." if code.split("\n").size > 10}\n" + fail_message += "\nValid Syntax:\n\n#{syntax}\n".yellow + fail_message += "\nPygments Error:\n\n#{error.message}".red + $stderr.puts fail_message.chomp + raise ArgumentError + end + end +end + diff --git a/site/src/main/jekyll/_plugins/raw.rb b/site/src/main/jekyll/_plugins/raw.rb new file mode 100644 index 00000000..069469ae --- /dev/null +++ b/site/src/main/jekyll/_plugins/raw.rb @@ -0,0 +1,40 @@ +# Author: Brandon Mathis +# Description: Provides plugins with a method for wrapping and unwrapping input to prevent Markdown and Textile from parsing it. +# Purpose: This is useful for preventing Markdown and Textile from being too aggressive and incorrectly parsing in-line HTML. +module TemplateWrapper + # Wrap input with a <div> + def safe_wrap(input) + "<div class='bogus-wrapper'><notextile>#{input}</notextile></div>" + end + # This must be applied after the + def unwrap(input) + input.gsub /<div class='bogus-wrapper'><notextile>(.+?)<\/notextile><\/div>/m do + $1 + end + end +end + +# Author: phaer, https://github.com/phaer +# Source: https://gist.github.com/1020852 +# Description: Raw tag for jekyll. Keeps liquid from parsing text betweeen {% raw %} and {% endraw %} + +module Jekyll + class RawTag < Liquid::Block + def parse(tokens) + @nodelist ||= [] + @nodelist.clear + + while token = tokens.shift + if token =~ FullToken + if block_delimiter == $1 + end_tag + return + end + end + @nodelist << token if not token.empty? + end + end + end +end + +Liquid::Template.register_tag('raw', Jekyll::RawTag)
\ No newline at end of file diff --git a/site/src/main/jekyll/_plugins/sitemap_generator.rb b/site/src/main/jekyll/_plugins/sitemap_generator.rb new file mode 100644 index 00000000..258d0eb7 --- /dev/null +++ b/site/src/main/jekyll/_plugins/sitemap_generator.rb @@ -0,0 +1,297 @@ +# Sitemap.xml Generator is a Jekyll plugin that generates a sitemap.xml file by +# traversing all of the available posts and pages. +# +# See readme file for documenation +# +# Updated to use config file for settings by Daniel Groves +# Site: http://danielgroves.net +# +# Author: Michael Levin +# Site: http://www.kinnetica.com +# Distributed Under A Creative Commons License +# - http://creativecommons.org/licenses/by/3.0/ + +require 'rexml/document' + +module Jekyll + + class Post + attr_accessor :name + + def full_path_to_source + File.join(@base, @name) + end + + def path_to_source + File.join(@name) + end + + def location_on_server(my_url) + "#{my_url}#{url}" + end + end + + class Page + attr_accessor :name + + def full_path_to_source + File.join(@base, @dir, @name) + end + + def path_to_source + File.join(@dir, @name) + end + + def location_on_server(my_url) + location = "#{my_url}#{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 + + # Config defaults + SITEMAP_FILE_NAME = "/sitemap.xml" + EXCLUDE = ["/atom.xml", "/feed.xml", "/feed/index.xml"] + INCLUDE_POSTS = ["/index.html"] + CHANGE_FREQUENCY_NAME = "change_frequency" + PRIORITY_NAME = "priority" + + # 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) + # Configuration + sitemap_config = site.config['sitemap'] || {} + @config = {} + @config['filename'] = sitemap_config['filename'] || SITEMAP_FILE_NAME + @config['change_frequency_name'] = sitemap_config['change_frequency_name'] || CHANGE_FREQUENCY_NAME + @config['priority_name'] = sitemap_config['priority_name'] || PRIORITY_NAME + @config['exclude'] = sitemap_config['exclude'] || EXCLUDE + @config['include_posts'] = sitemap_config['include_posts'] || INCLUDE_POSTS + + 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) + + # Create destination directory if it doesn't exist yet. Otherwise, we cannot write our file there. + Dir::mkdir(site.dest) if !File.directory? site.dest + + # File I/O: create sitemap.xml file and write out pretty-printed XML + filename = @config['filename'] + file = File.new(File.join(site.dest, filename), "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, "/", filename) + 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?(site, 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?(site, page.path_to_source) + 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(site, 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[@config['change_frequency_name']]) + change_frequency = + page_or_post.data[@config['change_frequency_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[@config['priority_name']]) + priority_value = page_or_post.data[@config['priority_name']] + if valid_priority?(priority_value) + priority = REXML::Element.new "priority" + priority.text = page_or_post.data[@config['priority_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(site, page_or_post) + loc = REXML::Element.new "loc" + url = site.config['url'] + loc.text = page_or_post.location_on_server(url) + + 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?(site, page_or_post.path_to_source) + # 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?(site, name) + @config['exclude'].include? name + end + + def posts_included?(site, name) + @config['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 |