Permalink
Browse files

added Graphviz visualization from DOT code in wiki pages

  • Loading branch information...
1 parent ebb58ae commit 94daf2104dfffacd32185fdf65e94def0200df31 @benalavi benalavi committed Aug 17, 2008
Showing with 184 additions and 61 deletions.
  1. +1 −0 .gitignore
  2. +7 −6 README
  3. +23 −0 app/models/dot_block.rb
  4. +29 −18 app/models/page.rb
  5. +21 −0 app/models/tableless.rb
  6. +103 −37 spec/models/page_spec.rb
View
@@ -4,3 +4,4 @@ tmp/*
config/database.yml
public/stylesheets/base.css
config/site.yml
+public/graphs/*
View
13 README
@@ -2,9 +2,10 @@
Quiki is a simple wiki aimed at developers.
-Saving a Page:
-1) Parse content
-2) Validate content
-3) Validate code chunks
-4) Render and save content
-5) Render and save code chunks
+External Dependencies:
+ Ultraviolet for code syntax highlighting
+ gem install ultraviolet
+ If it complains about needed Oniguruma:
+ http://snippets.aktagon.com/snippets/61-Installing-Ultraviolet-and-Oniguruma
+ Graphviz for diagram generation
+ http://graphviz.org/
View
@@ -0,0 +1,23 @@
+class DotBlock < Tableless
+ attr_accessible :code
+
+ GRAPH_PATH = File.join(RAILS_ROOT, 'public', 'graphs').freeze
+
+ column :code, :string
+
+ def render!
+ File.open(dot_file, 'w') { |file| file.puts code + "\n" }
+ `dot -Tpng -o#{graph_file} #{dot_file}`
+ File.unlink(dot_file)
+ graph_file.gsub(/^.*public\/(.*)$/, "\\1")
+ end
+
+ private
+ def dot_file
+ @dot_file ||= File.join(RAILS_ROOT, 'tmp', "#{Digest::MD5.hexdigest(Time.now.to_s)}.dot")
+ end
+
+ def graph_file
+ @graph_file ||= File.join(GRAPH_PATH, "#{Digest::MD5.hexdigest(Time.now.to_s)}.png")
+ end
+end
View
@@ -51,14 +51,26 @@ def parsers
@@parsers.collect{ |parser, klass| parser.to_s }.sort
end
+ # TODO: replace w/ permalink_fu, modified to support has_many for
+ # changeable permalinks that leave a trail
def path(title)
title.gsub(/#{PATH_REGEX.gsub(/\[/, '[^ ')}/, '').gsub(/\s/, '-')
end
-
- def parse_code_blocks(content)
- parts = content.scan /(.*?)\n(\-:.*?\n.*?\n\-:.*?)\n(.*)/m
- parts.empty? ? [content] : [parts[0][0], parts[0][1]] + parse_code_blocks(parts[0][2])
+
+ def parse_blocks(content, second_pass=false)
+ parts = parse_code_blocks(content) unless second_pass
+ parts = parse_dot_blocks(content) if second_pass || parts.empty?
+ parts.empty? ? [content] : (parse_blocks(parts[0][0], true) + [parts[0][1]] + parse_blocks(parts[0][2]))
end
+
+ private
+ def parse_code_blocks(content)
+ content.scan /(.*?)\n(\-:.*?\n.*?\n\-:.*?)\n(.*)/m
+ end
+
+ def parse_dot_blocks(content)
+ content.scan /(.*?)\n(::\n.*?\n::)\n(.*)/m
+ end
end
def validate
@@ -98,26 +110,24 @@ def parse
@body_parts = []
return nil if body.nil?
-
- # Page.parse_dot_blocks(body.dup.gsub(/\r\n/, "\n")).each do |part|
- # if part =~ /^::.*$/ # part is a DOT block
- # dot, check = part.scan(/^::\n(.*\n)::(.*?)$/m)[0]
- # if check
- # @body_parts << DotBlock.new(:dot => dot)
- # else
- # self.errors.add(:body, "has unclosed DOT block")
- # end
- # end
- # end
-
- Page.parse_code_blocks(body.dup.gsub(/\r\n/, "\n")).each do |part|
+
+ stripped = body.dup.gsub(/\r\n/, "\n")
+
+ Page.parse_blocks(body.dup.gsub(/\r\n/, "\n")).each do |part|
if part =~ /^\-:.*$/ # part is a code block
syntax, code, check = part.scan(/^\-:(.*?)\n(.*\n)\-:(.*?)$/m)[0]
if syntax == check
@body_parts << CodeBlock.new(:code => code, :language => syntax, :theme => CODE_THEME)
else
self.errors.add(:body, "has mismatched code highlighter block ('#{syntax}' and '#{check}')")
end
+ elsif part =~ /^::.*$/ # part is a DOT block
+ start, code, check = part.scan(/^::(.*?)\n(.*\n)::(.*?)$/m)[0]
+ if start == check
+ @body_parts << DotBlock.new(:code => code)
+ else
+ self.errors.add(:body, "has mismatched DOT code block")
+ end
else
@body_parts << part
end
@@ -128,14 +138,15 @@ def render
return self.rendered = '' if new_record? || body.nil?
self.rendered = []
-
@body_parts.each do |body_part|
if body_part.is_a?(CodeBlock)
body_part.version = next_version
self.code_blocks << body_part
# TODO: render this from the Haml partial code_blocks/stamp
self.rendered << "<p class=\"code_stamp\"><span class=\"syntax\">#{body_part.language.humanize}</span><a href=\"/#{path}/code/#{body_part.id}\">View Source</a></p>"
self.rendered << body_part.highlighted
+ elsif body_part.is_a?(DotBlock)
+ self.rendered << "<img src=\"#{body_part.render!}\" />"
else
self.rendered << Page.render(body_part, parser)
end
View
@@ -0,0 +1,21 @@
+class Tableless < ActiveRecord::Base
+ self.abstract_class = true
+
+ class << self
+ def columns()
+ @columns ||= [];
+ end
+
+ def column(name, sql_type = nil, default = nil, null = true)
+ columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
+ end
+ end
+
+ def create
+ save
+ end
+
+ def save(validate = true)
+ validate ? valid? : true
+ end
+end
View
@@ -28,31 +28,35 @@
_blah blah blah_
::
graph ER {
- node [shape=box]; course; institute; student;
- node [shape=ellipse]; {node [label="name"] name0; name1; name2;}
- code; grade; number;
- node [shape=diamond,style=filled,color=lightgrey]; "C-I"; "S-C"; "S-I";
-
- name0 -- course;
- code -- course;
- course -- "C-I" [label="n",len=1.00];
- "C-I" -- institute [label="1",len=1.00];
- institute -- name1;
- institute -- "S-I" [label="1",len=1.00];
- "S-I" -- student [label="n",len=1.00];
- student -- grade;
- student -- name2;
- student -- number;
- student -- "S-C" [label="m",len=1.00];
- "S-C" -- course [label="n",len=1.00];
-
- label = "\n\nEntity Relation Diagram\ndrawn by NEATO";
fontsize=20;
}
::
-blahdy blah blah
DOTBODY
+DOT_BODY_ALONE = <<-DOTBODYALONE
+*foo bar baz*
+::
+graph ER {
+ fontsize=20;
+}
+::
+_blah blah blah_
+DOTBODYALONE
+
+DOT_BEFORE_CODE = <<-DOTBEFORECODE
+*foo bar baz*
+::
+graph ER {
+ fontsize=20;
+}
+::
+*bing bang boom*
+-:javascript
+console.info('Hello World');
+-:javascript
+_blah blah blah_
+DOTBEFORECODE
describe Page do
describe "finding" do
@@ -275,38 +279,100 @@ def body
end
end
- describe "with multiple code blocks and text" do
+ # TODO: run the whole suite for BODY and DOT_BODY
+ [ 'BODY', 'DOT_BODY' ].each do |body|
+ describe "with multiple code blocks in #{body} and text" do
+ before :each do
+ DotBlock.stub!(:new).and_return(@dot_block = mock_model(DotBlock, :render! => 'foo.png'))
+ @page.body = body.constantize
+ end
+
+ it "should render the starting text" do
+ @page.save!
+ @page.rendered.should =~ /<em>foo bar baz<\/em>/
+ end
+
+ it "should highlight the ruby section" do
+ @page.save!
+ @page.rendered.should =~ /<pre class=.*?>.*10000000000000.*<\/pre>/m
+ end
+
+ it "should render the middle text" do
+ @page.save!
+ @page.rendered.should =~ /<em>bing bang boom<\/em>/
+ end
+
+ it "should highlight the javascript section" do
+ @page.save!
+ @page.rendered.should =~ /<pre class=.*?>.*console.*<\/pre>/m
+ end
+
+ it "should render the trailing text" do
+ @page.save!
+ @page.rendered.should =~ /<em>blah blah blah<\/em>/
+ end
+ end
+ end
+
+ describe "with DOT code block" do
before :each do
+ DotBlock.stub!(:new).and_return(@dot_block = mock_model(DotBlock, :render! => 'foo.png'))
+ @page.body = DOT_BODY
end
-
- def body
- BODY
+
+ it "should not show the DOT code in the output" do
+ @page.save!
+ @page.rendered.should_not =~ /::/
end
-
- it "should render the starting text" do
+
+ it "should create a new DOT block for DOT code" do
+ DotBlock.should_receive(:new).any_number_of_times.and_return(@dot_block)
@page.save!
- @page.rendered.should =~ /<em>foo bar baz<\/em>/
end
-
- it "should highlight the ruby section" do
+
+ it "should render! the DOT block" do
+ @dot_block.should_receive(:render!).and_return('foo.png')
@page.save!
- @page.rendered.should =~ /<pre class=.*?>.*10000000000000.*<\/pre>/m
end
-
- it "should render the middle text" do
+
+ it "should embed image" do
@page.save!
- @page.rendered.should =~ /<em>bing bang boom<\/em>/
+ @page.rendered.should =~ /<img src=\"foo.png\" \/>/
end
-
- it "should highlight the javascript section" do
+ end
+
+ describe "with only DOT code block" do
+ before :each do
+ DotBlock.stub!(:new).and_return(@dot_block = mock_model(DotBlock, :render! => 'foo.png'))
+ @page.body = body
@page.save!
- @page.rendered.should =~ /<pre class=.*?>.*console.*<\/pre>/m
+ end
+
+ def body
+ DOT_BODY_ALONE
+ end
+
+ it "should render the starting text" do
+ @page.rendered.should =~ /<em>foo bar baz<\/em>/
end
- it "should render the trailing text" do
- @page.save!
+ it "should render the DOT block" do
+ @page.rendered.should =~ /<img src.*?\/>/
+ end
+
+ it "should render the ending text" do
@page.rendered.should =~ /<em>blah blah blah<\/em>/
end
+
+ describe "with code after DOT block" do
+ def body
+ DOT_BEFORE_CODE
+ end
+
+ it "should render the code block" do
+ @page.rendered.should =~ /<pre class=.*?>.*console.*<\/pre>/m
+ end
+ end
end
end

0 comments on commit 94daf21

Please sign in to comment.