Class: RailroadDiagrams::Document

Inherits:
Object
  • Object
show all
Defined in:
lib/railroad_diagrams/document.rb

Overview

Renders linked diagrams for named grammar rules in one HTML document.

Examples:

Document.new(title: 'Grammar').add_rule('entry', Terminal.new('a')).to_html

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(title: 'Grammar', theme: :auto, locale: :en) ⇒ Document

Returns a new instance of Document.

Raises:



12
13
14
15
16
17
18
19
20
21
# File 'lib/railroad_diagrams/document.rb', line 12

def initialize(title: 'Grammar', theme: :auto, locale: :en)
  raise InvalidArgument, 'title must be a String' unless title.is_a?(String)

  Theme[theme]
  I18n.t(:describe_truncated, locale: locale)
  @title = title
  @theme = theme
  @locale = locale
  @rules = []
end

Instance Attribute Details

#locale ⇒ Object (readonly)

Returns the value of attribute locale.



10
11
12
# File 'lib/railroad_diagrams/document.rb', line 10

def locale
  @locale
end

#rules ⇒ Object (readonly)

Returns the value of attribute rules.



10
11
12
# File 'lib/railroad_diagrams/document.rb', line 10

def rules
  @rules
end

#theme ⇒ Object (readonly)

Returns the value of attribute theme.



10
11
12
# File 'lib/railroad_diagrams/document.rb', line 10

def theme
  @theme
end

#title ⇒ Object (readonly)

Returns the value of attribute title.



10
11
12
# File 'lib/railroad_diagrams/document.rb', line 10

def title
  @title
end

Instance Method Details

#add_rule(name, node) ⇒ Object

Raises:



23
24
25
26
27
28
29
# File 'lib/railroad_diagrams/document.rb', line 23

def add_rule(name, node)
  raise InvalidArgument, 'rule name must be a nonempty String' unless name.is_a?(String) && !name.empty?
  raise InvalidArgument, 'rule body must be a DiagramItem' unless node.is_a?(DiagramItem)

  @rules << [name, node]
  self
end

#lint ⇒ Object

Returns warnings about references and redundant branches.

Examples:

Document.new.add_rule('entry', NonTerminal.new('missing')).lint


34
35
36
# File 'lib/railroad_diagrams/document.rb', line 34

def lint
  Transform::Lint.call(@rules)
end

#rule_sections(max_width: nil) ⇒ Object

Returns rendered rule sections with IDs, references, SVG, and text alternatives.

Examples:

Document.new.add_rule('entry', Terminal.new('a')).rule_sections.first[:id]


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

def rule_sections(max_width: nil)
  ids = rule_ids
  targets = @rules.each_with_index.with_object({}) { |((name, _), index), found| found[name] ||= ids[index] }
  references = @rules.map do |_, node|
    node.each_node.grep(NonTerminal).map { |item| item.instance_variable_get(:@text) }.uniq
  end

  @rules.each_with_index.map do |(name, node), index|
    diagram = Marshal.load(Marshal.dump(node))
    diagram.each_node do |item|
      next unless item.is_a?(NonTerminal) && item.instance_variable_get(:@href).nil?

      target = targets[item.instance_variable_get(:@text)]
      item.instance_variable_set(:@href, "##{target}") if target
    end
    diagram = Diagram.new(diagram) unless diagram.is_a?(Diagram)
    {
      name: name, id: ids[index],
      svg: diagram.to_svg(theme: @theme, locale: @locale, href_mode: :href, id_prefix: "r#{index}-", max_width: max_width),
      text: diagram.to_text(charset: :unicode),
      referenced_by: @rules.each_index.select { |source| references[source].include?(name) }.map { |source| @rules[source].first }.uniq
    }
  end
end

#to_html(interactive: true, index: :definition, max_width: nil, css: nil) ⇒ Object

Raises:



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/railroad_diagrams/document.rb', line 66

def to_html(interactive: true, index: :definition, max_width: nil, css: nil)
  raise InvalidArgument, 'index must be :definition or :alphabetical' unless %i[definition alphabetical].include?(index)
  raise InvalidArgument, 'CSS must be a String without </style>' if css && (!css.is_a?(String) || css.match?(%r{</style}i))

  sections = rule_sections(max_width: max_width)
  navigation = index == :alphabetical ? sections.sort_by { |section| section[:name].downcase } : sections
  warnings = lint
  stylesheet = [Theme[@theme].css(css_variables: false), css].compact.join("\n")
  labels = if @locale == :ja
             { rules: '規則', search: '規則を検索', text: 'テキスト図', referenced_by: '参照元', warnings: '文法の警告' }
           else
             { rules: 'Rules', search: 'Search rules', text: 'Text diagram', referenced_by: 'Referenced by', warnings: 'Lint warnings' }
           end
  ERB.new(File.read(File.expand_path('templates/document.erb', __dir__))).result(binding)
end