Class: Docdown::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/docdown/parser.rb

Defined Under Namespace

Classes: ParseError

Constant Summary collapse

DEFAULT_KEYWORD =
":::"
INDENT_BLOCK =
'(?<before_indent>(^\s*$\n|\A)(^(?:[ ]{4}|\t))(?<indent_contents>.*)(?<after_indent>[^\s].*$\n?(?:(?:^\s*$\n?)*^(?:[ ]{4}|\t).*[^\s].*$\n?)*))'
GITHUB_BLOCK =
'^(?<fence>(?<fence_char>~|`){3,})\s*?(?<lang>\w+)?\s*?\n(?<contents>.*?)^\g<fence>\g<fence_char>*\s*?\n'
CODEBLOCK_REGEX =
/(#{GITHUB_BLOCK})/m
COMMAND_REGEX =
->(keyword) {
 /^#{keyword}(?<tag>(\s|=|-)?)\s*(?<command>(\S)+)\s+(?<statement>.*)$/
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(contents, options = {}) ⇒ Parser

Returns a new instance of Parser.



37
38
39
40
41
42
43
# File 'lib/docdown/parser.rb', line 37

def initialize(contents, options = {})
  @contents = contents
  @original = contents.dup
  @keyword  = options[:keyword] || DEFAULT_KEYWORD
  @stack    = []
  partition
end

Instance Attribute Details

#contentsObject (readonly)

Returns the value of attribute contents.



35
36
37
# File 'lib/docdown/parser.rb', line 35

def contents
  @contents
end

#keywordObject (readonly)

Returns the value of attribute keyword.



35
36
37
# File 'lib/docdown/parser.rb', line 35

def keyword
  @keyword
end

#stackObject (readonly)

Returns the value of attribute stack.



35
36
37
# File 'lib/docdown/parser.rb', line 35

def stack
  @stack
end

Instance Method Details

#add_code_commands(code) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/docdown/parser.rb', line 111

def add_code_commands(code)
  commands = []
  code.lines.each do |line|
    if match = line.match(command_regex)
      add_match_to_code_commands(match, commands)
      check_parse_error(line, code)
    else
      unless commands.empty?
        commands.last << line
        next
      end
      @stack << line
    end
  end
end

#add_fenced_code(fenced_code_str) ⇒ Object



66
67
68
69
70
71
72
73
74
75
# File 'lib/docdown/parser.rb', line 66

def add_fenced_code(fenced_code_str)
  fenced_code_str.match(CODEBLOCK_REGEX) do |m|
    fence = m[:fence]
    lang  = m[:lang]
    code  = m[:contents]
    @stack << "#{fence}#{lang}\n"
    add_code_commands(code)
    @stack << "#{fence}\n"
  end
end

#add_match_to_code_commands(match, commands) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/docdown/parser.rb', line 77

def add_match_to_code_commands(match, commands)
  command      = match[:command]
  tag          = match[:tag]
  statement    = match[:statement]

  code_command = Docdown.code_command_from_keyword(command, statement)

  case tag
  when /\-/
    code_command.hidden        = true
  when /\=/
    code_command.render_result = true
  when /\s/
    # default do nothing
  end

  @stack   << "\n" if commands.last.is_a?(Docdown::CodeCommand)
  @stack   << code_command
  commands << code_command
  code_command
end

#check_parse_error(command, code_block) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
# File 'lib/docdown/parser.rb', line 99

def check_parse_error(command, code_block)
  return unless code_command = @stack.last
  return unless code_command.is_a?(Docdown::CodeCommands::NoSuchCommand)
  @original.lines.each_with_index do |line, index|
    next unless line == command
    raise ParseError.new(keyword:     code_command.keyword,
                         block:       code_block,
                         command:     command,
                         line_number: index.next)
  end
end

#command_regexObject



131
132
133
# File 'lib/docdown/parser.rb', line 131

def command_regex
  COMMAND_REGEX.call(keyword)
end

#contents_to_arrayObject



127
128
129
# File 'lib/docdown/parser.rb', line 127

def contents_to_array
  partition
end

#partitionObject

split into [before_code, code, after_code], process code, and re-run until tail is empty



57
58
59
60
61
62
63
64
# File 'lib/docdown/parser.rb', line 57

def partition
  until contents.empty?
    head, code, tail = contents.partition(CODEBLOCK_REGEX)
    @stack << head        unless head.empty?
    add_fenced_code(code) unless code.empty?
    @contents = tail
  end
end

#to_mdObject



46
47
48
49
50
51
52
53
54
# File 'lib/docdown/parser.rb', line 46

def to_md
  @stack.map do |s|
    if s.respond_to?(:render)
      s.render
    else
      s
    end
  end.join("")
end