jekyll/plugins/recently_updated_generator.rb (171 lines of code) (raw):
require 'yaml'
require 'json'
require 'kramdown'
class RecentsPage < Jekyll::Page
# Initialize a new RecentsPage.
#
# 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, content, data)
@site = site
@base = base
@dir = dir
@name = name
@content = @output = content
self.process(name)
self.data = data
end
end
class RecentsGenerator < Jekyll::Generator
# Make sure we run before the TOC generator
priority :high
def generate(site)
raise "Git is not installed" unless git_installed?
recents_output = site.config['recently_updated_output'] || 'recently_updated.md'
data = site.frontmatter_defaults.all(recents_output, :pages).clone
data['title'] = 'Recently Updated'
data['edit_on_github'] = false
pages_by_path = Hash.new
site.pages.each { |p| pages_by_path[p.path] = p }
toc = site.data['toc']
toc_by_path = Hash.new
toc_by_id = Hash.new
toc.each { |e| handle_toc_entry(toc_by_path, toc_by_id, e) }
github_repo = site.config['github_repo']
youtrack_project = site.config['youtrack_project']
toc = site.data['toc']
content = header(github_repo)
content << format_commits(commits, github_repo, youtrack_project, pages_by_path, toc_by_path, toc_by_id)
#content << commits.to_s
recents_page = RecentsPage.new(site, site.source, '/', recents_output, content, data)
site.pages << recents_page
end
def handle_toc_entry(toc_by_path, toc_by_id, entry)
toc_by_id[entry[:id]] = entry
toc_by_path[entry[:path]] = entry if entry.key?(:path)
if entry.key?(:pages) then
entry[:pages].each { |p| handle_toc_entry(toc_by_path, toc_by_id, p) }
end
end
def header(github_repo)
"See the [full changelog on GitHub](https://github.com/#{github_repo}/commits/)\n"
end
def format_commits(commits, github_repo, youtrack_project, pages_by_path, toc_by_path, toc_by_id)
content = ''
prev_date = ''
commits.each do |c|
if c[:files].any? {|f| not should_skip(f[:file]) } then
date = c[:date].strftime('%-d %B %Y')
content << "<hr>\n## #{date}\n" unless date == prev_date
content << "\n"
content << "**#{format_message(c[:subject], github_repo, youtrack_project)}** ([view diff](https://github.com/#{github_repo}/commit/#{c[:hash]}))\n"
body = c[:body].join()
content << "<br>#{format_message(body, github_repo, youtrack_project)}\n"
content << "\n"
c[:files].each do |f|
file = f[:file]
if not should_skip(file) then
case f[:type]
when 'D'
content << "* `#{file}` (deleted)\n"
when 'R'
newfile = f[:newfile]
data = format_file(newfile, pages_by_path, toc_by_path, toc_by_id)
if data then
content << data + " (renamed from `#{file}`)\n"
else
if pages_by_path.key?(file) then
data = format_file(file, pages_by_path, toc_by_path, toc_by_id)
content << data + " (renamed from `#{newfile}`)\n" if data
else
content << "* (Renamed `#{file}` to `#{newfile}`. Neither file exists now)\n"
end
end
else
data = format_file(file, pages_by_path, toc_by_path, toc_by_id)
content << data + "\n" if data
end
end
end
content << "\n"
prev_date = date
end
end
content
end
def should_skip(file)
file == '_SUMMARY.md' or file == 'README.md' or file == 'CONTRIBUTING.md' or not file.end_with?('.md')
end
def format_file(file, pages_by_path, toc_by_path, toc_by_id)
# Always add a redirect if moving or deleting a file. Cool URLs don't change
if not pages_by_path.key?(file) then
puts ""
puts "WARNING: File has been moved or deleted without adding a redirect: #{file}"
puts ""
return nil
end
raise "Page is not in ToC: #{file}" unless toc_by_path.key?(file)
page = pages_by_path[file]
title = page.data['title']
toc_entry = toc_by_path[file]
title = toc_entry[:title]
path = format_path(toc_entry, toc_by_id)
"* #{path} **[#{title}](/#{file})**"
end
def format_path(toc_entry, toc_by_id)
path = ""
parent_id = toc_entry[:parent_id]
if not parent_id.nil? then
parent_toc = toc_by_id[parent_id]
path << format_path(parent_toc, toc_by_id) if parent_toc.key?(:parent_id)
title = parent_toc[:title]
if parent_toc.key?(:path) then
path << "[#{title}](/#{parent_toc[:path]})"
else
path << title
end
path << " / "
end
path
end
def format_message(msg, github_repo, youtrack_project)
msg = msg.gsub(/(\#(\d+))/, "[\\1](https://github.com/#{github_repo}/issues/\\2)")
if youtrack_project then
msg = msg.gsub(/(#{youtrack_project}-\d+)/, "[\\1](https://youtrack.jetbrains.com/issue/\\1)")
end
msg
end
def commits
commits = []
lines = %x{ git log -n50 --no-merges --name-status --pretty=format:%H%n%an%n%aD%n%s%n%b%n%n }
lines = lines.lines
while lines.length > 0 do
commit = {
:hash => lines.shift.strip,
:author => lines.shift.strip,
:date => DateTime.parse(lines.shift.strip),
:subject => lines.shift.strip
}
body = [ lines.shift.strip ]
line = ''
begin
prev_line = line
line = lines.shift
body << line.strip
end until (line == "\n" and prev_line == "\n") or lines.length <= 0
commit[:body] = body[0..-3]
while lines[0] == "\n"
lines.shift
end
files = []
line = ''
begin
line = lines.shift
if match = line.match(/(^\w)(\d*)\t([^\s]*)(?:\s+(.*))?/)
# Similarity is for renames
type, similarity, file1, file2 = match.captures
files << { :type => type, :similarity => similarity, :file => file1, :newfile => file2 }
end
end until line == "\n" or lines.length <= 0
commit[:files] = files
commits << commit
end
commits
end
def git_installed?
null = RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw/ ? 'NUL' : '/dev/null'
system "git --version>>#{null} 2>&1"
end
end