From 24415920ff7ee31b294d785e776a2a7dd2ad6367 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 24 Dec 2010 00:24:59 +0000 Subject: wrongdoc 1.0.0 - initial release Welcome to hell --- lib/wrongdoc/changelog.rb | 25 +++++++++++ lib/wrongdoc/final.rb | 105 +++++++++++++++++++++++++++++++++++++++++++ lib/wrongdoc/gemspec.rb | 18 ++++++++ lib/wrongdoc/history.rb | 50 +++++++++++++++++++++ lib/wrongdoc/merge.rb | 20 +++++++++ lib/wrongdoc/news_atom.rb | 46 +++++++++++++++++++ lib/wrongdoc/news_rdoc.rb | 30 +++++++++++++ lib/wrongdoc/parse_xml.rb | 11 +++++ lib/wrongdoc/prepare.rb | 17 +++++++ lib/wrongdoc/rdoc.rb | 68 ++++++++++++++++++++++++++++ lib/wrongdoc/rdoc_options.rb | 11 +++++ lib/wrongdoc/readme.rb | 20 +++++++++ lib/wrongdoc/release.rb | 44 ++++++++++++++++++ 13 files changed, 465 insertions(+) create mode 100644 lib/wrongdoc/changelog.rb create mode 100644 lib/wrongdoc/final.rb create mode 100644 lib/wrongdoc/gemspec.rb create mode 100644 lib/wrongdoc/history.rb create mode 100644 lib/wrongdoc/merge.rb create mode 100644 lib/wrongdoc/news_atom.rb create mode 100644 lib/wrongdoc/news_rdoc.rb create mode 100644 lib/wrongdoc/parse_xml.rb create mode 100644 lib/wrongdoc/prepare.rb create mode 100644 lib/wrongdoc/rdoc.rb create mode 100644 lib/wrongdoc/rdoc_options.rb create mode 100644 lib/wrongdoc/readme.rb create mode 100644 lib/wrongdoc/release.rb (limited to 'lib/wrongdoc') diff --git a/lib/wrongdoc/changelog.rb b/lib/wrongdoc/changelog.rb new file mode 100644 index 0000000..f16bfbf --- /dev/null +++ b/lib/wrongdoc/changelog.rb @@ -0,0 +1,25 @@ +# helper method for generating the ChangeLog in RDoc format atomically +module Wrongdoc::Changelog + include Wrongdoc::History + + def changelog + fp = Tempfile.new('ChangeLog', '.') + fp.write "ChangeLog from #@cgit_uri" + cmd = %w(git log) + if @changelog_start && tags[0] + range = "#@changelog_start..#{tags[0][:tag]}" + fp.write(" (#{range})") + cmd << range + end + fp.write("\n\n") + prefix = " " + IO.popen(cmd.join(' ')) do |io| + io.each { |line| + fp.write prefix + fp.write line + } + end + File.rename(fp.path, 'ChangeLog') + fp.close! + end +end diff --git a/lib/wrongdoc/final.rb b/lib/wrongdoc/final.rb new file mode 100644 index 0000000..7070de8 --- /dev/null +++ b/lib/wrongdoc/final.rb @@ -0,0 +1,105 @@ +require 'find' +require 'fileutils' + +class Wrongdoc::Final + include Wrongdoc::ParseXML + include Wrongdoc::NewsAtom + + def run + Find.find('doc') { |path| /\.html\z/ =~ path and fix(path) } + FileUtils.rm_rf('doc/js') + news_atom + end + + def initialize(opts, git_tag = nil) + @cgit_uri = URI.parse(opts[:cgit_url]) + @rdoc_uri = URI.parse(opts[:rdoc_url]) + @git_tag = git_tag + end + + # returns a cgit URI for the given +path+ and +lineno+ + def path_uri(path, lineno) + uri = @cgit_uri.dup + uri.path += "/tree/#{URI.escape(path)}" + uri.fragment = "n#{lineno}" + uri.query = "id=#{URI.escape(@git_tag)}" if @git_tag + uri + end + + # delete all the stuff that offends us + def killkillkill!(doc) + unlink = proc { |node| node.unlink } + + # JavaScript is dangerous + doc.search("script").each(&unlink) + + # if your project's big enough to need JS search, it's too bloated + doc.search('span.search-toggle').each(&unlink) + doc.search('form').each(&unlink) + + # remove W3C validator link, we use tidy instead + doc.search('div#validator-badges p').each { |x| + /Validate/i =~ x.content and x.unlink + } + end + + # since we killed off JavaScript, viewing source isn't possible with + # RDoc anymore, so link people to the web source viewer + def source_linkify!(doc) + doc.search('div.method-detail').each { |mdetail| + path = lineno = nil + mdetail.search('div.method-source-code').each { |src| + src.search('span.ruby-comment').each { |x| + if x.content =~ /File\s+(\S+),\s+line\s+(\d+)/s + path, lineno = $1, $2 + end + } + src.unlink if path && lineno + } + if path && lineno + mdetail.search('span.method-click-advice').each { |x| + x.content = '' + a = Nokogiri::XML::Node.new('a', doc) + a['href'] = path_uri(path, lineno).to_s + a.content = 'view method source' + x.add_child(a) + } + end + } + end + + # Don't give the original Darkfish a bad name, and advertise ourselves :) + def advertise!(doc) + doc.search('div#validator-badges p small').each { |x| + if /\AGenerated/ =~ x.content + first = x.children.first + first.content = first.content.gsub /\AG/, "Originally g" + last = x.children.last + last.content = "#{last.content}, modified by " + + a = Nokogiri::XML::Node.new('a', doc) + a["href"] = "http://bogomips.org/wrongdoc/" + a.content = "wrongdoc" + last.add_next_sibling(a) + end + } + end + + def fix(file) + File.open(file, "a+") do |fp| + buf = process(fp.read) + fp.truncate 0 + fp.write buf + end + end + + # the main entry point, this does all the require processing on any + # given String buffer. + def process(str) + doc = parse_xml(str) + killkillkill!(doc) + source_linkify!(doc) + advertise!(doc) + doc.to_xhtml(:indent => 0) + end +end diff --git a/lib/wrongdoc/gemspec.rb b/lib/wrongdoc/gemspec.rb new file mode 100644 index 0000000..0d31ffd --- /dev/null +++ b/lib/wrongdoc/gemspec.rb @@ -0,0 +1,18 @@ +# helper methods for gemspecs +module Wrongdoc::Gemspec + include Wrongdoc::Readme + include Wrongdoc::RdocOptions + + def extra_rdoc_files(manifest) + File.readlines('.document').map! do |x| + x.chomp! + if File.directory?(x) + manifest.grep(%r{\A#{x}/}) + elsif File.file?(x) + x + else + nil + end + end.flatten.compact + end +end diff --git a/lib/wrongdoc/history.rb b/lib/wrongdoc/history.rb new file mode 100644 index 0000000..3443ca3 --- /dev/null +++ b/lib/wrongdoc/history.rb @@ -0,0 +1,50 @@ +module Wrongdoc::History + def initialize_history + @tags = @old_summaries = nil + end + + # returns a cgit URI for a given +tag_name+ + def tag_uri(tag_name) + uri = @cgit_uri.dup + uri.path = "/tag/" + uri.query = "id=#{tag_name}" + uri + end + + # TODO: investigate Ruby git libraries + def tags + timefmt = '%Y-%m-%dT%H:%M:%SZ' + @tags ||= `git tag -l`.split(/\n/).map do |tag| + next if tag == "v0.0.0" + if %r{\Av[\d\.]+} =~ tag + header, subject, body = `git cat-file tag #{tag}`.split(/\n\n/, 3) + header = header.split(/\n/) + tagger = header.grep(/\Atagger /).first + body ||= "initial" + time = Time.at(tagger.split(/ /)[-2].to_i).utc + { + :time => time.strftime(timefmt), + :ruby_time => time, + :tagger_name => %r{^tagger ([^<]+)}.match(tagger)[1].strip, + :tagger_email => %r{<([^>]+)>}.match(tagger)[1].strip, + :id => `git rev-parse refs/tags/#{tag}`.chomp!, + :tag => tag, + :subject => subject, + :body => (old = old_summaries[tag]) ? "#{old}\n#{body}" : body, + } + end + end.compact.sort { |a,b| b[:time] <=> a[:time] } + end + + def old_summaries + @old_summaries ||= if File.exist?(".CHANGELOG.old") + File.readlines(".CHANGELOG.old").inject({}) do |hash, line| + version, summary = line.split(/ - /, 2) + hash[version] = summary + hash + end + else + {} + end + end +end diff --git a/lib/wrongdoc/merge.rb b/lib/wrongdoc/merge.rb new file mode 100644 index 0000000..b51ee5a --- /dev/null +++ b/lib/wrongdoc/merge.rb @@ -0,0 +1,20 @@ +class Wrongdoc::Merge + include Wrongdoc::ParseXML + + def initialize(opts) + @merge_html = opts[:merge_html] + end + + def run + @merge_html.each do |file, source| + rdoc_html = "doc/#{file}.html" + src = Nokogiri::XML(File.read(source)) + File.open(rdoc_html, "a+") { |fp| + doc = parse_xml(fp.read) + doc.search("div#documentation")[0].add_child(src.root) + fp.truncate 0 + fp.write doc.to_xhtml + } + end + end +end diff --git a/lib/wrongdoc/news_atom.rb b/lib/wrongdoc/news_atom.rb new file mode 100644 index 0000000..1e7a781 --- /dev/null +++ b/lib/wrongdoc/news_atom.rb @@ -0,0 +1,46 @@ +module Wrongdoc::NewsAtom + include Wrongdoc::History + include Wrongdoc::Readme + + # generates an Atom feed based on git tags in the document directory + def news_atom + project_name, short_desc, _ = x = readme_metadata + new_tags = tags[0,10] + atom_uri = @rdoc_uri.dup + atom_uri.path += "NEWS.atom.xml" + news_uri = @rdoc_uri.dup + news_uri.path += "NEWS.html" + doc = Nokogiri::XML::Builder.new { + feed :xmlns => "http://www.w3.org/2005/Atom" do + id! atom_uri.to_s + title "#{project_name} news" + subtitle short_desc + link! :rel => 'alternate', :type => 'text/html', :href => news_uri.to_s + updated new_tags.empty? ? '1970-01-01:00:00:00Z' : new_tags[0][:time] + new_tags.each do |tag| + entry { + title tag[:subject] + updated tag[:time] + published tag[:time] + author { + name tag[:tagger_name] + email tag[:tagger_email] + } + uri = tag_uri(tag[:tag]).to_s + link! :rel => "alternate", :type => "text/html", :href => uri + id! uri + message_only = tag[:body].split(/\n.+\(\d+\):\n {6}/s)[0].strip + content({:type =>:text}, message_only) + content(:type =>:xhtml) { pre tag[:body] } + } + end + end + } + fpath = "doc/NEWS.atom.xml" + File.open(fpath, "w") { |fp| fp.write doc.to_xml(:indent => 0) } + unless new_tags.empty? + time = new_tags[0][:ruby_time] + File.utime(time, time, fpath) + end + end +end diff --git a/lib/wrongdoc/news_rdoc.rb b/lib/wrongdoc/news_rdoc.rb new file mode 100644 index 0000000..ee21d6b --- /dev/null +++ b/lib/wrongdoc/news_rdoc.rb @@ -0,0 +1,30 @@ +# -*- encoding: utf-8 -*- +module Wrongdoc::NewsRdoc + include Wrongdoc::History + + def puts_tag(fp, tag) + time = tag[:time].tr!('T', ' ').gsub!(/:\d\dZ/, ' UTC') + fp.puts "=== #{tag[:subject]} / #{time}" + fp.puts "" + + body = tag[:body] + fp.puts tag[:body].gsub(/^/smu, " ").gsub(/[ \t]+$/smu, "") + fp.puts "" + end + + # generates a NEWS file in the top-level directory based on git tags + def news_rdoc + news = Tempfile.new('NEWS', '.') + tags.each { |tag| puts_tag(news, tag) } + File.open("LATEST", "wb") { |latest| + if tags.empty? + latest.puts "Currently unreleased" + news.puts "No news yet." + else + puts_tag(latest, tags[0]) + end + } + File.rename(news.path, 'NEWS') + news.close! + end +end diff --git a/lib/wrongdoc/parse_xml.rb b/lib/wrongdoc/parse_xml.rb new file mode 100644 index 0000000..6d7abe2 --- /dev/null +++ b/lib/wrongdoc/parse_xml.rb @@ -0,0 +1,11 @@ +module Wrongdoc::ParseXML + def parse_xml(str) + opts = { + :input_encoding => 'utf8', + :output_encoding => 'utf8', + :wrap => 0, + :tidy_mark => false, + } + Nokogiri::XML(TidyFFI::Tidy.new(str, opts).clean) + end +end diff --git a/lib/wrongdoc/prepare.rb b/lib/wrongdoc/prepare.rb new file mode 100644 index 0000000..85922ad --- /dev/null +++ b/lib/wrongdoc/prepare.rb @@ -0,0 +1,17 @@ +class Wrongdoc::Prepare + include Wrongdoc::NewsRdoc + include Wrongdoc::Changelog + include Wrongdoc::Readme + + def initialize(opts) + @rdoc_uri = URI.parse(opts[:rdoc_url]) + @cgit_uri = URI.parse(opts[:cgit_url]) + @changelog_start = opts[:changelog_start] + @name, @short_desc = readme_metadata + end + + def run + news_rdoc + changelog + end +end diff --git a/lib/wrongdoc/rdoc.rb b/lib/wrongdoc/rdoc.rb new file mode 100644 index 0000000..0b54aef --- /dev/null +++ b/lib/wrongdoc/rdoc.rb @@ -0,0 +1,68 @@ +# we won't deal with the non-Darkfish RDoc in older rubies, it has frames :< +if (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby") && \ + RUBY_VERSION.to_f <= 1.8 + require 'rubygems' + gem 'rdoc', '~> 3.0.1' +end +require 'rdoc/rdoc' + +class Wrongdoc::Rdoc + include Wrongdoc::RdocOptions + include Wrongdoc::ParseXML + + def initialize(opts) + @rdoc_uri = URI.parse(opts[:rdoc_url]) + @cgit_uri = URI.parse(opts[:cgit_url]) + end + + def run(argv = []) + rdoc(argv) + add_atom("doc/ChangeLog.html", cgit_atom_uri) + add_atom("doc/NEWS.html", news_atom_uri) + add_atom("doc/README.html", news_atom_uri) + + # the stock RDoc index page layout lacks a vertical sidebar full of links + rdoc_index = "doc/rdoc_index.html" + File.exist?(rdoc_index) and File.unlink(rdoc_index) + File.rename("doc/index.html", rdoc_index) + File.link("doc/README.html", "doc/index.html") + end + + def rdoc(argv) + r = RDoc::RDoc.new + r.document(rdoc_options.concat(argv)) + end + + def add_atom(path, atom_uri) + File.open(path, "a+") do |fp| + doc = parse_xml(fp.read) + doc.search("title").each { |t| + t.add_next_sibling(atom_node(doc, atom_uri)) + } + fp.truncate 0 + fp.write doc.to_xhtml + end + end + + def cgit_atom_uri + uri = @cgit_uri.dup + uri.path += "/atom/" + uri.query = "h=master" + uri + end + + def news_atom_uri + uri = @rdoc_uri.dup + uri.path += "NEWS.atom.xml" + uri + end + + def atom_node(doc, uri, title = 'Atom feed') + link = Nokogiri::XML::Node.new('link', doc) + link['rel'] = 'alternate' + link['title'] = title + link['href'] = uri.to_s + link['type'] = 'application/atom+xml' + link + end +end diff --git a/lib/wrongdoc/rdoc_options.rb b/lib/wrongdoc/rdoc_options.rb new file mode 100644 index 0000000..15474f4 --- /dev/null +++ b/lib/wrongdoc/rdoc_options.rb @@ -0,0 +1,11 @@ +module Wrongdoc::RdocOptions + include Wrongdoc::Readme + + def rdoc_options + webcvs = URI.parse(Wrongdoc.config[:cgit_url]) + webcvs.path += "/tree" + webcvs = "#{webcvs}/%s" + _, _, title = readme_metadata + [ '-t', title, '-W', webcvs ] + end +end diff --git a/lib/wrongdoc/readme.rb b/lib/wrongdoc/readme.rb new file mode 100644 index 0000000..8c5f5b6 --- /dev/null +++ b/lib/wrongdoc/readme.rb @@ -0,0 +1,20 @@ +# helpers for parsing the top-level README file (no suffix support :P) +module Wrongdoc::Readme + + # returns a one-paragraph summary from the README + def readme_description + File.read("README").split(/\n\n/)[1] + end + + # parses the README file in the top-level directory for project metadata + def readme_metadata + l = File.readlines("README")[0].strip! + l.gsub!(/^=\s+/, '') or abort "#{l.inspect} doesn't start with '='" + title = l.dup + if l.gsub!(/^(\w+\!)\s+/, '') # Rainbows! + return $1, l, title + else + return (l.split(/\s*[:-]\s*/, 2)).push(title) + end + end +end diff --git a/lib/wrongdoc/release.rb b/lib/wrongdoc/release.rb new file mode 100644 index 0000000..f6c72e5 --- /dev/null +++ b/lib/wrongdoc/release.rb @@ -0,0 +1,44 @@ +module Wrongdoc::Release + self.extend Wrongdoc::History + + def self.changes(io) + vtags = tags.map { |tag| tag[:tag] =~ /\Av/ and tag[:tag] }.sort + cmds = [] + if vtags.empty? + cmds << %w(git log) + else + version = vtags[0] + prev = vtags[vtags.index(version) - 1] + if prev + cmds << [ 'git', 'diff', '--stat', prev, version ] + cmds << [ 'git', 'log', "#{prev}..#{version}" ] + else + cmds << [ 'git', 'log', version ] + end + end + + io.sync = true + cmds.each_with_index do |cmd,i| + i > 0 and io.puts + pid, status = Process.waitpid2(fork do + $stdout.reopen(io) + io.close + exec *cmd + end) + status.success? or abort status.inspect + end + end + + def self.notes(io, opts) + spec = Gem::Specification.load(Dir['*.gemspec'][0]) + io.puts spec.description.strip + io.puts + io.puts "* #{spec.homepage}" + io.puts "* #{spec.email}" + io.puts "* #{opts[:git_url] || opts[:cgit_url]}" + + _, _, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3) + io.print "\nChanges:\n\n" + io.puts body + end +end -- cgit v1.2.3-24-ge0c7