asciidoctor_vaped 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +46 -0
  4. data/exe/asciidoctor_vaped +6 -0
  5. data/lib/asciidoctor_vaped/ast/document.rb +34 -0
  6. data/lib/asciidoctor_vaped/ast/element.rb +17 -0
  7. data/lib/asciidoctor_vaped/ast/node.rb +53 -0
  8. data/lib/asciidoctor_vaped/ast/text.rb +40 -0
  9. data/lib/asciidoctor_vaped/cli.rb +59 -0
  10. data/lib/asciidoctor_vaped/converter/base_converter.rb +68 -0
  11. data/lib/asciidoctor_vaped/converter/docbook.rb +116 -0
  12. data/lib/asciidoctor_vaped/converter/html.rb +119 -0
  13. data/lib/asciidoctor_vaped/converter/node.rb +92 -0
  14. data/lib/asciidoctor_vaped/converter.rb +6 -0
  15. data/lib/asciidoctor_vaped/error.rb +5 -0
  16. data/lib/asciidoctor_vaped/parser/blocks/admonition.rb +18 -0
  17. data/lib/asciidoctor_vaped/parser/blocks/common/base_node.rb +23 -0
  18. data/lib/asciidoctor_vaped/parser/blocks/common/blank_line.rb +19 -0
  19. data/lib/asciidoctor_vaped/parser/blocks/common/comment.rb +19 -0
  20. data/lib/asciidoctor_vaped/parser/blocks/delimited/delimited_node.rb +62 -0
  21. data/lib/asciidoctor_vaped/parser/blocks/delimited/example.rb +18 -0
  22. data/lib/asciidoctor_vaped/parser/blocks/delimited/listing.rb +14 -0
  23. data/lib/asciidoctor_vaped/parser/blocks/delimited/literal.rb +14 -0
  24. data/lib/asciidoctor_vaped/parser/blocks/delimited/open.rb +18 -0
  25. data/lib/asciidoctor_vaped/parser/blocks/delimited/passthrough.rb +14 -0
  26. data/lib/asciidoctor_vaped/parser/blocks/delimited/quote.rb +18 -0
  27. data/lib/asciidoctor_vaped/parser/blocks/delimited/sidebar.rb +18 -0
  28. data/lib/asciidoctor_vaped/parser/blocks/heading.rb +19 -0
  29. data/lib/asciidoctor_vaped/parser/blocks/lists/list_base.rb +29 -0
  30. data/lib/asciidoctor_vaped/parser/blocks/lists/list_item.rb +24 -0
  31. data/lib/asciidoctor_vaped/parser/blocks/lists/ordered_list.rb +16 -0
  32. data/lib/asciidoctor_vaped/parser/blocks/lists/unordered_list.rb +16 -0
  33. data/lib/asciidoctor_vaped/parser/blocks/metadata/caption.rb +17 -0
  34. data/lib/asciidoctor_vaped/parser/blocks/metadata/document_attribute.rb +18 -0
  35. data/lib/asciidoctor_vaped/parser/blocks/metadata/element_attributes.rb +86 -0
  36. data/lib/asciidoctor_vaped/parser/blocks/paragraph.rb +20 -0
  37. data/lib/asciidoctor_vaped/parser/blocks/tables/table.rb +58 -0
  38. data/lib/asciidoctor_vaped/parser/blocks/tables/table_cell.rb +13 -0
  39. data/lib/asciidoctor_vaped/parser/blocks/tables/table_row.rb +15 -0
  40. data/lib/asciidoctor_vaped/parser/blocks.rb +39 -0
  41. data/lib/asciidoctor_vaped/parser/context.rb +51 -0
  42. data/lib/asciidoctor_vaped/parser/inline.rb +60 -0
  43. data/lib/asciidoctor_vaped/parser/inlines/emphasis.rb +19 -0
  44. data/lib/asciidoctor_vaped/parser/inlines/link.rb +20 -0
  45. data/lib/asciidoctor_vaped/parser/inlines/monospace.rb +19 -0
  46. data/lib/asciidoctor_vaped/parser/inlines/strong.rb +19 -0
  47. data/lib/asciidoctor_vaped/parser/inlines/url.rb +19 -0
  48. data/lib/asciidoctor_vaped/parser.rb +72 -0
  49. data/lib/asciidoctor_vaped/reader.rb +47 -0
  50. data/lib/asciidoctor_vaped/version.rb +5 -0
  51. data/lib/asciidoctor_vaped.rb +52 -0
  52. data/sig/asciidoctor_vaped.rbs +53 -0
  53. metadata +96 -0
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "common/base_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Admonition < BaseNode
9
+ PATTERN = /\A(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\s+(.+)\z/
10
+
11
+ def parse(context)
12
+ name, text = context.reader.read.match(PATTERN).captures
13
+ context.append AST::Element.new(:admonition, attributes: { name: }, children: Inline.parse(text))
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciidoctorVaped
4
+ module Parser
5
+ module Blocks
6
+ class BaseNode
7
+ def initialize(successor = nil)
8
+ @successor = successor
9
+ end
10
+
11
+ def handle(context)
12
+ return parse(context) if match?(context)
13
+
14
+ @successor&.handle(context)
15
+ end
16
+
17
+ def match?(context)
18
+ context.reader.peek&.match?(self.class::PATTERN)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class BlankLine < BaseNode
9
+ def match?(context)
10
+ context.reader.peek&.strip == ""
11
+ end
12
+
13
+ def parse(context)
14
+ context.reader.skip_blank_lines
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Comment < BaseNode
9
+ def match?(context)
10
+ context.reader.peek&.start_with?("//")
11
+ end
12
+
13
+ def parse(context)
14
+ context.reader.peek == "////" ? context.reader.read_delimited("////") : context.reader.read
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/base_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class DelimitedNode < BaseNode
9
+ def match?(context)
10
+ delimiters.include?(context.reader.peek)
11
+ end
12
+
13
+ def parse(context)
14
+ lines = context.reader.read_delimited(context.reader.peek)
15
+ node = build_node(context, lines)
16
+ context.append node
17
+ end
18
+
19
+ def delimiters
20
+ [delimiter]
21
+ end
22
+
23
+ def attributes(context)
24
+ return {} unless admonition?(context)
25
+
26
+ { name: context.pending_attributes[:style].to_s.upcase }
27
+ end
28
+
29
+ def context_name(context)
30
+ admonition?(context) ? :admonition : default_context_name
31
+ end
32
+
33
+ private
34
+
35
+ def build_node(context, lines)
36
+ text = lines.join("\n")
37
+ return AST::Element.new(context_name(context), attributes: attributes(context), children: parse_children(context, lines)) if compound?(context)
38
+
39
+ AST::Element.new(context_name(context), attributes: attributes(context), children: [Inline.text_node(text)])
40
+ end
41
+
42
+ def parse_children(context, lines)
43
+ node = AST::Element.new(context_name(context), attributes: attributes(context))
44
+ Parser.parse_nodes context.nested(node, Reader.new(lines.join("\n")))
45
+ node.children
46
+ end
47
+
48
+ def compound?(context)
49
+ admonition?(context)
50
+ end
51
+
52
+ def admonition?(context)
53
+ context.pending_attributes[:style]&.to_s&.match?(/\A(?:NOTE|TIP|IMPORTANT|WARNING|CAUTION)\z/i)
54
+ end
55
+
56
+ def default_context_name
57
+ raise NotImplementedError, "#{self.class} must implement #default_context_name"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "delimited_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Example < DelimitedNode
9
+ def delimiter = "===="
10
+ def default_context_name = :example
11
+
12
+ private
13
+
14
+ def compound?(_context) = true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "delimited_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Listing < DelimitedNode
9
+ def delimiter = "----"
10
+ def default_context_name = :listing
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "delimited_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Literal < DelimitedNode
9
+ def delimiter = "...."
10
+ def default_context_name = :literal
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "delimited_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Open < DelimitedNode
9
+ def delimiter = "--"
10
+ def default_context_name = :open
11
+
12
+ private
13
+
14
+ def compound?(_context) = true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "delimited_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Passthrough < DelimitedNode
9
+ def delimiters = %w[+++ ++++]
10
+ def default_context_name = :pass
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "delimited_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Quote < DelimitedNode
9
+ def delimiter = "____"
10
+ def default_context_name = :quote
11
+
12
+ private
13
+
14
+ def compound?(_context) = true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "delimited_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Sidebar < DelimitedNode
9
+ def delimiter = "****"
10
+ def default_context_name = :sidebar
11
+
12
+ private
13
+
14
+ def compound?(_context) = true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "common/base_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Heading < BaseNode
9
+ PATTERN = /\A(=+)\s+(.+)\z/
10
+
11
+ def parse(context)
12
+ markers, title = context.reader.read.match(PATTERN).captures
13
+ level = [markers.length - 1, 1].max
14
+ context.open_section AST::Element.new(:section, attributes: { level: }, children: Inline.parse(title))
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/base_node"
4
+ require_relative "list_item"
5
+
6
+ module AsciidoctorVaped
7
+ module Parser
8
+ module Blocks
9
+ class ListBase < BaseNode
10
+ def match?(context)
11
+ context.reader.peek&.match?(pattern)
12
+ end
13
+
14
+ def parse(context)
15
+ list = AST::Element.new(list_context)
16
+ item_context = context.nested(list)
17
+ item_chain.handle(item_context) while match?(context)
18
+ context.append(list)
19
+ end
20
+
21
+ private
22
+
23
+ def item_chain
24
+ Blocks.chain([[ListItem, { pattern: }]])
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/base_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class ListItem < BaseNode
9
+ def initialize(successor = nil, pattern:)
10
+ super(successor)
11
+ @pattern = pattern
12
+ end
13
+
14
+ def match?(context)
15
+ context.reader.peek&.match?(@pattern)
16
+ end
17
+
18
+ def parse(context)
19
+ context.append AST::Element.new(:list_item, children: Inline.parse(context.reader.read.sub(@pattern, "")))
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "list_base"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class OrderedList < ListBase
9
+ private
10
+
11
+ def list_context = :olist
12
+ def pattern = /\A\.\s+/
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "list_base"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class UnorderedList < ListBase
9
+ private
10
+
11
+ def list_context = :ulist
12
+ def pattern = /\A[*-]\s+/
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/base_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Caption < BaseNode
9
+ PATTERN = /\A\.([^.\s].*)\z/
10
+
11
+ def parse(context)
12
+ context.pending_title = context.reader.read.match(PATTERN)[1]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/base_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class DocumentAttribute < BaseNode
9
+ PATTERN = /\A:([^:\s][^:]*):\s*(.*)\z/
10
+
11
+ def parse(context)
12
+ name, value = context.reader.read.match(PATTERN).captures
13
+ context.document.register_attribute(name, value)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../common/base_node"
4
+ require "strscan"
5
+
6
+ module AsciidoctorVaped
7
+ module Parser
8
+ module Blocks
9
+ class ElementAttributes < BaseNode
10
+ PATTERN = /\A\[(.+)\]\z/
11
+
12
+ def parse(context)
13
+ context.add_pending_attributes parse_attributes(context.reader.read.match(PATTERN)[1])
14
+ end
15
+
16
+ private
17
+
18
+ def parse_attributes(value)
19
+ split_attributes(value).each_with_object({ positional: [] }) do |part, attributes|
20
+ parse_attribute(part, attributes)
21
+ end.then { |attributes| normalize_attributes(attributes) }
22
+ end
23
+
24
+ def split_attributes(value)
25
+ value.scan(/(?:"[^"]*"|'[^']*'|[^,])+/).map(&:strip).reject(&:empty?)
26
+ end
27
+
28
+ def parse_attribute(part, attributes)
29
+ name, value = part.split("=", 2)
30
+ return attributes[:positional] << unquote(part) unless value
31
+
32
+ assign_named_attribute(attributes, name.strip, unquote(value.strip))
33
+ end
34
+
35
+ def assign_named_attribute(attributes, name, value)
36
+ key = normalize_name(name)
37
+ return parse_options(value, attributes) if key == :opts
38
+
39
+ attributes[key] = value
40
+ end
41
+
42
+ def normalize_attributes(attributes)
43
+ positional = attributes.delete(:positional)
44
+ parse_shorthand(positional.shift, attributes)
45
+ attributes[:language] = positional.shift if attributes[:style] == :source && positional.any?
46
+ positional.each { |part| attributes[normalize_name(part)] = true }
47
+ attributes
48
+ end
49
+
50
+ def parse_shorthand(value, attributes)
51
+ return unless value
52
+
53
+ scanner = StringScanner.new(value)
54
+ attributes[:style] = scanner.scan(/[^\s#.%]+/)&.to_sym
55
+ parse_shorthand_marks(scanner.rest, attributes)
56
+ end
57
+
58
+ def parse_shorthand_marks(value, attributes)
59
+ value.scan(/([#.%])([^#.%]+)/).each do |mark, name|
60
+ case mark
61
+ when "#" then attributes[:id] = name
62
+ when "." then append_role(attributes, name)
63
+ when "%" then attributes[normalize_name(name)] = true
64
+ end
65
+ end
66
+ end
67
+
68
+ def append_role(attributes, name)
69
+ attributes[:role] = [attributes[:role], name].compact.join(" ")
70
+ end
71
+
72
+ def parse_options(value, attributes)
73
+ value.split(",").map(&:strip).each { |option| attributes[normalize_name(option)] = true }
74
+ end
75
+
76
+ def normalize_name(value)
77
+ value.tr("-", "_").to_sym
78
+ end
79
+
80
+ def unquote(value)
81
+ value.sub(/\A(["'])(.*)\1\z/, "\\2")
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "common/base_node"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class Paragraph < BaseNode
9
+ def match?(_context)
10
+ true
11
+ end
12
+
13
+ def parse(context)
14
+ lines = context.reader.read_until_blank
15
+ context.append AST::Element.new(:paragraph, children: Inline.parse(lines.join("\n")))
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../delimited/delimited_node"
4
+ require_relative "table_row"
5
+
6
+ module AsciidoctorVaped
7
+ module Parser
8
+ module Blocks
9
+ class Table < DelimitedNode
10
+ def delimiter = "|==="
11
+
12
+ def parse(context)
13
+ table = AST::Element.new(:table)
14
+ rows(context.reader.read_delimited(delimiter)).each { |cells| table << TableRow.build(cells) }
15
+ context.append(table)
16
+ end
17
+
18
+ private
19
+
20
+ def rows(lines)
21
+ columns = nil
22
+ pending = []
23
+ rows = []
24
+
25
+ lines.each do |line|
26
+ cells = cells_for(line)
27
+ next if cells.empty?
28
+
29
+ if cells.length > 1
30
+ columns ||= cells.length
31
+ flush_pending(rows, pending, columns)
32
+ cells.each_slice(columns) { |row| rows << row }
33
+ elsif columns
34
+ pending.concat(cells)
35
+ flush_pending(rows, pending, columns)
36
+ else
37
+ rows << cells
38
+ end
39
+ end
40
+
41
+ rows << pending unless pending.empty?
42
+ rows
43
+ end
44
+
45
+ def flush_pending(rows, pending, columns)
46
+ rows << pending.shift(columns) while pending.length >= columns
47
+ end
48
+
49
+ def cells_for(line)
50
+ return [] if line.strip == ""
51
+
52
+ line.sub(/\A\|/, "").split(/\s+\|/).map(&:strip)
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciidoctorVaped
4
+ module Parser
5
+ module Blocks
6
+ class TableCell
7
+ def self.build(text)
8
+ AST::Element.new(:table_cell, children: Inline.parse(text))
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "table_cell"
4
+
5
+ module AsciidoctorVaped
6
+ module Parser
7
+ module Blocks
8
+ class TableRow
9
+ def self.build(cells)
10
+ AST::Element.new(:table_row, children: cells.map { |cell| TableCell.build(cell) })
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "blocks/common/base_node"
4
+ require_relative "blocks/common/blank_line"
5
+ require_relative "blocks/common/comment"
6
+ require_relative "blocks/metadata/document_attribute"
7
+ require_relative "blocks/metadata/caption"
8
+ require_relative "blocks/metadata/element_attributes"
9
+ require_relative "blocks/heading"
10
+ require_relative "blocks/delimited/delimited_node"
11
+ require_relative "blocks/delimited/listing"
12
+ require_relative "blocks/delimited/literal"
13
+ require_relative "blocks/delimited/example"
14
+ require_relative "blocks/delimited/quote"
15
+ require_relative "blocks/delimited/sidebar"
16
+ require_relative "blocks/delimited/open"
17
+ require_relative "blocks/delimited/passthrough"
18
+ require_relative "blocks/tables/table_cell"
19
+ require_relative "blocks/tables/table_row"
20
+ require_relative "blocks/tables/table"
21
+ require_relative "blocks/admonition"
22
+ require_relative "blocks/lists/list_item"
23
+ require_relative "blocks/lists/list_base"
24
+ require_relative "blocks/lists/unordered_list"
25
+ require_relative "blocks/lists/ordered_list"
26
+ require_relative "blocks/paragraph"
27
+
28
+ module AsciidoctorVaped
29
+ module Parser
30
+ module Blocks
31
+ def self.chain(handlers)
32
+ handlers.reverse.inject(nil) do |successor, handler|
33
+ handler, kwargs = handler
34
+ handler.new(successor, **(kwargs || {}))
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end