Render plantuml text files into binary images

61 views
Skip to first unread message

yaloki

unread,
Sep 8, 2015, 8:23:09 AM9/8/15
to nanoc
I need to implement a Filter that takes plain text PlantUML and renders into a PNG.

Simple naive implementation (1) (can't be bothered with popen3 magic at this time):

class Plantuml < Nanoc::Filter
  identifier
:plantuml
  type
:binary
 
def run(content, params={})
    system
("java -Xmx128m -jar lib/plantuml.jar -pipe < #{filename} > #{output_filename}")
 
end
end

Problem is... the input is text and the output is binary and Nanoc assumes that the output "type" is the same as the input "type".

The options I see:
  • use a binary filter as above: problem is, I'd also like to use ERB filtering on the source file before compiling it into a PNG
  • mark PlantUML source files as text instead of binary: that would make the Filter more annoying to implement as the output (PNG binary data) needs to be buffered into memory by Nanoc instead of being written to a temporary file as with binary filters (e.g. code above (1))
  • create Nanoc Item objects programatically at the preprocess stage (2) and route the original PlantUML source text files into oblivion (3)

Anything I'm missing here ? Does someone have a better idea ?


(2) Create items programatically (untested):


preprocess do
 
@items.select{|item| item[:extension] == 'pu'}.each do |pu|
    file
= ... # similar to above + create a temp file myself
    png
= Nanoc::Item.new(file, {}, "/plantuml#{item.identifier}", { :mtime => item.mtime, :binary => true })
   
@items << png
 
end
end


(3) Route original PlantUML sources into void:

route '*' do
 
if item[:extension] != 'pu'
   
# ...
 
else
   
# do nothing, don't output anywhere, might have to return nil here, idk.
 
end    
end



yaloki

unread,
Sep 8, 2015, 9:42:39 AM9/8/15
to nanoc
And another option which is a bit annoying as it duplicates code from Nanoc::Filters::ERB:

class ERB_binary < Nanoc::Filter
  requires 'erb'
  identifier :erbbinary
  type :binary

  def run(f, params={})
    assigns.merge!(params[:locals] || {})
    context = ::Nanoc::Context.new(assigns)
    proc = assigns[:content] ? -> { assigns[:content] } : nil
    assigns_binding = context.get_binding(&proc)

    content = IO.read(f)

    # Get result
    safe_level = params[:safe_level]
    trim_mode = params[:trim_mode]
    erb = ::ERB.new(content, safe_level, trim_mode)
    erb.filename = filename
    result = erb.result(assigns_binding)

    File.open(output_filename, "w") {|o| o.write(result)}
  end
end

Eric Sunshine

unread,
Sep 8, 2015, 10:48:11 AM9/8/15
to na...@googlegroups.com
On Tue, Sep 8, 2015 at 8:23 AM, yaloki <pascal...@gmail.com> wrote:
> I need to implement a Filter that takes plain text PlantUML and renders into
> a PNG.
>
> Simple naive implementation (1) (can't be bothered with popen3 magic at this
> time):
>
> class Plantuml < Nanoc::Filter
> identifier :plantuml
> type :binary
> def run(content, params={})
> system("java -Xmx128m -jar lib/plantuml.jar -pipe < #{filename} >
> #{output_filename}")
> end
> end
>
> Problem is... the input is text and the output is binary and Nanoc assumes
> that the output "type" is the same as the input "type".

Declare the filter as text-to-binary:

type :text => :binary

yaloki

unread,
Sep 9, 2015, 7:34:24 AM9/9/15
to nanoc
> Problem is... the input is text and the output is binary and Nanoc assumes
> that the output "type" is the same as the input "type".

Declare the filter as text-to-binary:

   type :text => :binary

Perfect, that does the trick :)

For reference (would require a nicer version with popen3):
 
class Plantuml < Nanoc::Filter
  identifier :plantuml
  type :text => :binary

yaloki

unread,
Sep 10, 2015, 4:22:25 AM9/10/15
to nanoc
And an even better version below. It works great, except that Nanoc doesn't recompile when the PlantUML source file is modified.

Here is the filter:
class Plantuml < Nanoc::Filter
  identifier :plantuml
  type :text => :binary
  requires 'open3'

  def run(content, params={})
    content = "@startuml\n" + content unless content =~ %r{^\s*@startuml\b}x
    content += "\n@enduml" unless content =~ %r{\b@enduml$}x
    o, e, s = Open3.capture3(
      'java', '-Djava.awt.headless=true', '-Xmx128m', '-jar', 'lib/plantuml.jar', '-pipe',
      :stdin_data => content,
      :binmode => true
    )
    unless s.success?
      fail "error running plantuml: #{e}\n#{o}"
    end
    File.open(output_filename, "w") do |out|
      out.write(o)
    end
  end
end

And here the Rules:
compile '*' do
  unless item.binary?
    filter :erb
    case item[:extension]
    when 'markdown'
      filter :kramdown
      layout 'default'
      filter :relativize_paths, :type => :html
    when 'html'
      layout 'default'
      filter :relativize_paths, :type => :html
    when 'pu'
      filter :plantuml
    end
  end
end

route '*' do
  if item.binary?
    item.identifier.chop + (item[:extension] ? '.' + item[:extension] : '')
  elsif item.identifier == '/'
    '/index.html'
  else
    case item[:extension]
    when 'markdown', 'html'
      item.identifier.chop + '.html'
    when 'pu'
      item.identifier.chop + '.png'
    else
      item.identifier.chop + '.' + item[:extension]
    end
  end
end

layout '*', :erb

An idea what might be causing this (modifications to .pu files don't cause nanoc to run the filter on them) ?

Reply all
Reply to author
Forward
0 new messages