Class: Asciidoctor::Ldl::Renderer

Inherits:
Object
  • Object
show all
Defined in:
lib/asciidoctor/ldl/renderer.rb

Overview

Renders LDL source into an image file by shelling out to the bundled Node helper. Handles output-directory resolution and content-hash caching so an unchanged diagram is only rendered once.

Kept intentionally free of Asciidoctor-specific types so it is trivial to unit test in isolation.

Defined Under Namespace

Classes: Error

Constant Summary collapse

SCRIPT =
File.expand_path('js/ldl_render.mjs', __dir__)
FORMATS =
%w[svg png].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Renderer

opts is a plain Hash with symbol keys:

:format, :scale, :theme, :show_ids, :show_labels,
:node, :package_dir, :out_dir, :cache


25
26
27
28
29
30
31
32
33
34
35
# File 'lib/asciidoctor/ldl/renderer.rb', line 25

def initialize(opts = {})
  @format      = normalize_format(opts[:format])
  @scale       = normalize_scale(opts[:scale])
  @theme       = (opts[:theme] || 'light').to_s.downcase
  @show_ids    = truthy(opts[:show_ids], false)
  @show_labels = truthy(opts[:show_labels], true)
  @node        = opts[:node] || 'node'
  @package_dir = opts[:package_dir]
  @out_dir     = opts[:out_dir] || Dir.pwd
  @cache       = opts.key?(:cache) ? opts[:cache] : true
end

Instance Attribute Details

#formatObject (readonly)

Returns the value of attribute format.



37
38
39
# File 'lib/asciidoctor/ldl/renderer.rb', line 37

def format
  @format
end

#out_dirObject (readonly)

Returns the value of attribute out_dir.



37
38
39
# File 'lib/asciidoctor/ldl/renderer.rb', line 37

def out_dir
  @out_dir
end

#scaleObject (readonly)

Returns the value of attribute scale.



37
38
39
# File 'lib/asciidoctor/ldl/renderer.rb', line 37

def scale
  @scale
end

#themeObject (readonly)

Returns the value of attribute theme.



37
38
39
# File 'lib/asciidoctor/ldl/renderer.rb', line 37

def theme
  @theme
end

Instance Method Details

#command_for(out_path) ⇒ Object

Assemble the node command line (exposed for testing).



77
78
79
80
81
82
83
84
85
86
87
# File 'lib/asciidoctor/ldl/renderer.rb', line 77

def command_for(out_path)
  cmd = [@node, SCRIPT,
         '--format', @format,
         '--scale', sprintf('%g', @scale),
         '--theme', @theme,
         '--out', out_path]
  cmd << (@show_ids ? '--show-ids' : '--no-show-ids')
  cmd << (@show_labels ? '--show-labels' : '--no-show-labels')
  cmd.push('--package-dir', @package_dir) if @package_dir
  cmd
end

#render(source, basename = nil) ⇒ Object

Render source and return the basename of the generated file (which lives in out_dir). Pass basename (without extension) to get a stable, human-chosen file name instead of a content hash. Raises Renderer::Error on failure.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/asciidoctor/ldl/renderer.rb', line 43

def render(source, basename = nil)
  FileUtils.mkdir_p(@out_dir)
  filename = target_filename(source, basename)
  path = File.join(@out_dir, filename)
  key = digest(source)

  return filename if @cache && cache_valid?(path, key)

  cmd = command_for(path)
  stdout, stderr, status = Open3.capture3(*cmd, stdin_data: source, binmode: true)

  unless status.success?
    detail = stderr.to_s.strip
    detail = stdout.to_s.strip if detail.empty?
    raise Error, "LDL rendering failed (#{@format}): #{detail}"
  end
  unless File.file?(path)
    raise Error, "LDL renderer produced no output at #{path}: #{stderr.to_s.strip}"
  end
  write_cache_key(path, key) if @cache
  filename
end

#target_filename(source, basename = nil) ⇒ Object

The file name for source under the current options. Uses basename verbatim when given, otherwise a content-addressed name.



68
69
70
71
72
73
74
# File 'lib/asciidoctor/ldl/renderer.rb', line 68

def target_filename(source, basename = nil)
  if basename && !basename.to_s.strip.empty?
    "#{sanitize_basename(basename)}.#{@format}"
  else
    "ldl-#{digest(source)}.#{@format}"
  end
end