aboutsummaryrefslogtreecommitdiff
path: root/site/src/main/jekyll/_plugins
diff options
context:
space:
mode:
Diffstat (limited to 'site/src/main/jekyll/_plugins')
-rw-r--r--site/src/main/jekyll/_plugins/include_code.rb95
-rw-r--r--site/src/main/jekyll/_plugins/pygments_code.rb201
-rw-r--r--site/src/main/jekyll/_plugins/raw.rb40
-rw-r--r--site/src/main/jekyll/_plugins/sitemap_generator.rb297
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(/{{/, '&#x7b;&#x7b;').gsub(/{%/, '&#x7b;&#x25;')
+ 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('<','&lt;')
+ 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