minimal_markdown 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f513dd2b70e6b64938c16b2c1ab15c2ba24e57dcdad5a8ac1ecacefc0cbdc8e
4
- data.tar.gz: a313ef705ff4a74d5cb516505d2c472e4149746cc4f9cb2c51864d75a39c5369
3
+ metadata.gz: c2c4b5e33d33ba7906773ba771a60ca8bdec59bbd7c70e782c1015eb415b3fc9
4
+ data.tar.gz: 3856b0171044a85155edd7cd1fe571257625a939475de43bb951710c48aa541e
5
5
  SHA512:
6
- metadata.gz: 8e15301d1ccf20fec7ff5efa8634e41b41280b0157ee1d7ee070a718c226c04c3f1a0aaf2999f1d94589aac75f55519c4d8e94dd7afcfa1e91c9b5e152207e23
7
- data.tar.gz: 6e849f0eb968d5fb994ec33d792554eb4d081ff7b74b97360449138cae18c99b001a537c152ec703c9cc67ce5b867531dc9df530433cc9396d4f908f1041cfa1
6
+ metadata.gz: '068c6461129d501ba17a00024bba4baff4f47047c993f8e400036f4b93cd377c3f75ed3d7178cc397a3d52f939f09d0e514ec9e0934ceff171dd90764911c303'
7
+ data.tar.gz: cbebb147ce28b85e1d414b8a242eed030dbe0e4c22c4aeb9d69bc58dd4f723ef7210ab38a3a514f19e4ed3d548f88ff74f4df1fdae27c8219ec2e3d6365b9ee0
@@ -0,0 +1 @@
1
+ *.gem
data/README.md CHANGED
@@ -4,7 +4,7 @@ A lightweight and minimal Ruby implementation of a Markdown renderer,
4
4
  designed with security in mind.
5
5
 
6
6
  Tiny codebase, no dependencies, and HTML is rendered from a tree so no
7
- possibility of user-supplied HTML sneaking into the output.
7
+ possibility of corrupted or user-supplied HTML sneaking into the output.
8
8
 
9
9
  ## Supported Markdown elements
10
10
 
@@ -25,5 +25,12 @@ Add to your app:
25
25
 
26
26
  ```
27
27
  MinimalMarkdown.render('Here is my *markdown*')
28
+ => "<div>Here is my <i>markdown</i></div>"
29
+ ```
30
+
31
+ Or use the Slack-style Markdown parser by passing an option:
32
+
33
+ ```
34
+ MinimalMarkdown.render('Here is my *markdown*', style: :slack)
28
35
  => "<div>Here is my <b>markdown</b></div>"
29
36
  ```
@@ -1,12 +1,19 @@
1
1
  require_relative 'minimal_markdown/version'
2
- require_relative 'minimal_markdown/nodes'
3
- require_relative 'minimal_markdown/implementors'
2
+ require_relative 'minimal_markdown/nodes/bold'
3
+ require_relative 'minimal_markdown/nodes/italic'
4
+ require_relative 'minimal_markdown/nodes/line'
5
+ require_relative 'minimal_markdown/nodes/text'
6
+ require_relative 'minimal_markdown/nodes/unordered_list'
7
+ require_relative 'minimal_markdown/parsers/base'
8
+ require_relative 'minimal_markdown/parsers/bold'
9
+ require_relative 'minimal_markdown/parsers/italic'
10
+ require_relative 'minimal_markdown/parsers/unordered_list'
4
11
  require_relative 'minimal_markdown/parser'
5
12
  require_relative 'minimal_markdown/html_renderer'
6
13
 
7
14
  module MinimalMarkdown
8
- def self.render(text)
9
- tree = MinimalMarkdown::Parser.new(text).parse
15
+ def self.render(text, style: :markdown)
16
+ tree = MinimalMarkdown::Parser.new(text, style: style).parse
10
17
  MinimalMarkdown::HtmlRenderer.new(tree).render
11
18
  end
12
19
  end
@@ -0,0 +1,9 @@
1
+ module MinimalMarkdown::Nodes
2
+ Bold = Struct.new(:child) do
3
+ def render(output)
4
+ output << '<b>'
5
+ child.render(output)
6
+ output << '</b>'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module MinimalMarkdown::Nodes
2
+ Italic = Struct.new(:child) do
3
+ def render(output)
4
+ output << '<i>'
5
+ child.render(output)
6
+ output << '</i>'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module MinimalMarkdown::Nodes
2
+ Line = Struct.new(:child, :spacing) do
3
+ def render(output)
4
+ if spacing < 2
5
+ output << '<div>'
6
+ child.render(output)
7
+ output << '</div>'
8
+ else
9
+ output << '<p>'
10
+ child.render(output)
11
+ output << '</p>'
12
+ if spacing > 2
13
+ output << '<br>' * (spacing - 2)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ require 'cgi'
2
+
3
+ module MinimalMarkdown::Nodes
4
+ Text = Struct.new(:text) do
5
+ def render(output)
6
+ if text.is_a?(Array)
7
+ text.each { |child| child.render(output) }
8
+ else
9
+ output << CGI.escapeHTML(text)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module MinimalMarkdown::Nodes
2
+ UnorderedList = Struct.new(:children) do
3
+ def render(output)
4
+ output << '<ul>'
5
+ children.each do |child|
6
+ output << '<li>'
7
+ child.render(output)
8
+ output << '</li>'
9
+ end
10
+ output << '</ul>'
11
+ end
12
+ end
13
+ end
@@ -1,35 +1,38 @@
1
- require 'cgi'
2
-
3
1
  module MinimalMarkdown
4
2
  class Parser
5
- DEFAULT_IMPLEMENTORS = [
6
- UnorderedListImplementor,
7
- BoldImplementor,
8
- ItalicImplementor
3
+ DEFAULT_PARSERS = [
4
+ Parsers::UnorderedList,
5
+ Parsers::Bold,
6
+ Parsers::Italic,
9
7
  ]
10
8
 
11
- attr_reader :text, :implementors
9
+ STYLES = [:markdown, :slack]
10
+
11
+ attr_reader :text, :parsers
12
+
13
+ def initialize(text, style: :markdown, parsers: DEFAULT_PARSERS)
14
+ raise ArgumentError, 'invalid style' unless STYLES.include?(style)
12
15
 
13
- def initialize(text, implementors: DEFAULT_IMPLEMENTORS)
14
16
  @text = text
15
- @implementors = implementors
17
+ @style = style
18
+ @parsers = parsers
16
19
  end
17
20
 
18
21
  def parse
19
22
  prepared_text = text.strip.gsub("\r", "")
20
23
 
21
- preline_implementors, postline_implementors = implementors.map(&:new).partition(&:multiline?)
24
+ preline_parsers, postline_parsers = parsers.map { |parser| parser.new(@style) }.partition(&:multiline?)
22
25
 
23
- preline_tree = run_implementors(preline_implementors, [prepared_text])
26
+ preline_tree = run_parsers(preline_parsers, [prepared_text])
24
27
  postline_tree = convert_to_lines(preline_tree)
25
- run_implementors(postline_implementors, postline_tree)
28
+ run_parsers(postline_parsers, postline_tree)
26
29
  end
27
30
 
28
31
  private
29
32
 
30
- def run_implementors(implementors, input_tree)
31
- implementors.inject(input_tree) do |tree, implementor|
32
- implementor.update_tree(tree)
33
+ def run_parsers(parsers, input_tree)
34
+ parsers.inject(input_tree) do |tree, parser|
35
+ parser.update_tree(tree)
33
36
  end
34
37
  end
35
38
 
@@ -48,7 +51,7 @@ module MinimalMarkdown
48
51
  if line.empty?
49
52
  []
50
53
  else
51
- Line.new(Text.new(line), spaces&.count("\n") || 0)
54
+ Nodes::Line.new(Nodes::Text.new(line), spaces&.count("\n") || 0)
52
55
  end
53
56
  end
54
57
  end
@@ -0,0 +1,38 @@
1
+ module MinimalMarkdown::Parsers
2
+ class Base
3
+ def multiline?
4
+ false
5
+ end
6
+
7
+ def update_tree(tree)
8
+ tree.each { |node| update_node(node) }
9
+ end
10
+
11
+ private
12
+
13
+ def update_node(node)
14
+ if node.is_a?(::MinimalMarkdown::Nodes::Text)
15
+ if node.text.is_a?(Array)
16
+ update_tree(node.text)
17
+ else
18
+ out = []
19
+ input = node.text
20
+ while result = input.match(@regex)
21
+ out << ::MinimalMarkdown::Nodes::Text.new(result.pre_match + result[1]) unless result.pre_match.empty?
22
+ out << @node_class.new(::MinimalMarkdown::Nodes::Text.new(result[3]))
23
+ input = result[4] + result.post_match
24
+ end
25
+
26
+ if !out.empty?
27
+ out << ::MinimalMarkdown::Nodes::Text.new(input) unless input.empty?
28
+ node.text = out
29
+ end
30
+ end
31
+ elsif node.respond_to?(:children)
32
+ update_tree(node.children)
33
+ else
34
+ update_node(node.child)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ module MinimalMarkdown::Parsers
2
+ class Bold < Base
3
+ def initialize(style)
4
+ @regex = case style
5
+ when :markdown then /(^|\W)(\*\*|__)(\S|\S.*?\S)\2(\W|$)/
6
+ when :slack then /(^|\W)(\*)(\S[^*]*|[^*]*\S)\*(\W|$)/
7
+ end
8
+
9
+ @node_class = ::MinimalMarkdown::Nodes::Bold
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module MinimalMarkdown::Parsers
2
+ class Italic < Base
3
+ def initialize(style)
4
+ @regex = case style
5
+ when :markdown then /(^|\W)([_*])(?!\2)(\S|\S.*?(?!\2)\S)\2(\W|$)/
6
+ when :slack then /(^|\W)(_)(\S[^_]*|[^_]*\S)_(\W|$)/
7
+ end
8
+
9
+ @node_class = ::MinimalMarkdown::Nodes::Italic
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,40 @@
1
+ module MinimalMarkdown::Parsers
2
+ class UnorderedList
3
+ def initialize(style)
4
+ end
5
+
6
+ def multiline?
7
+ true
8
+ end
9
+
10
+ def update_tree(tree)
11
+ tree.flat_map do |node|
12
+ if node.is_a?(String)
13
+ update_text(node)
14
+ else
15
+ node
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def update_text(input)
23
+ out = []
24
+
25
+ while result = input.match(/(?:^[ ]*[*+-][ ]+[^\n]+(?:\n|$))+/m)
26
+ out << result.pre_match unless result.pre_match.empty?
27
+
28
+ children = result[0].split("\n").map do |line|
29
+ ::MinimalMarkdown::Nodes::Text.new(line.sub(/^\s*[*+-]\s+/, ''))
30
+ end
31
+ out << ::MinimalMarkdown::Nodes::UnorderedList.new(children)
32
+
33
+ input = result.post_match
34
+ end
35
+
36
+ out << input unless input.empty?
37
+ out
38
+ end
39
+ end
40
+ end
@@ -1,3 +1,3 @@
1
1
  module MinimalMarkdown
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minimal_markdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roger Nesbitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-08 00:00:00.000000000 Z
11
+ date: 2019-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -31,15 +31,23 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - ".gitignore"
34
35
  - Gemfile
35
36
  - Gemfile.lock
36
37
  - LICENSE
37
38
  - README.md
38
39
  - lib/minimal_markdown.rb
39
40
  - lib/minimal_markdown/html_renderer.rb
40
- - lib/minimal_markdown/implementors.rb
41
- - lib/minimal_markdown/nodes.rb
41
+ - lib/minimal_markdown/nodes/bold.rb
42
+ - lib/minimal_markdown/nodes/italic.rb
43
+ - lib/minimal_markdown/nodes/line.rb
44
+ - lib/minimal_markdown/nodes/text.rb
45
+ - lib/minimal_markdown/nodes/unordered_list.rb
42
46
  - lib/minimal_markdown/parser.rb
47
+ - lib/minimal_markdown/parsers/base.rb
48
+ - lib/minimal_markdown/parsers/bold.rb
49
+ - lib/minimal_markdown/parsers/italic.rb
50
+ - lib/minimal_markdown/parsers/unordered_list.rb
43
51
  - lib/minimal_markdown/version.rb
44
52
  - minimal_markdown.gemspec
45
53
  homepage: https://github.com/mogest/minimal_markdown
@@ -1,88 +0,0 @@
1
- module MinimalMarkdown
2
- class ImplementorBase
3
- def multiline?
4
- false
5
- end
6
-
7
- def update_tree(tree)
8
- tree.each { |node| update_node(node) }
9
- end
10
-
11
- private
12
-
13
- def update_node(node)
14
- if node.is_a?(Text)
15
- if node.text.is_a?(Array)
16
- update_tree(node.text)
17
- else
18
- out = []
19
- input = node.text
20
- while result = input.match(@regex)
21
- out << Text.new(result.pre_match + result[1]) unless result.pre_match.empty?
22
- out << @node_class.new(Text.new(result[2]))
23
- input = result[3] + result.post_match
24
- end
25
-
26
- if !out.empty?
27
- out << Text.new(input) unless input.empty?
28
- node.text = out
29
- end
30
- end
31
- elsif node.respond_to?(:children)
32
- update_tree(node.children)
33
- else
34
- update_node(node.child)
35
- end
36
- end
37
- end
38
-
39
- class BoldImplementor < ImplementorBase
40
- def initialize
41
- @regex = /(^|\W)\*(\S[^*]*|[^*]*\S)\*(\W|$)/
42
- @node_class = Bold
43
- end
44
- end
45
-
46
- class ItalicImplementor < ImplementorBase
47
- def initialize
48
- @regex = /(^|\W)_(\S[^_]*|[^_]*\S)_(\W|$)/
49
- @node_class = Italic
50
- end
51
- end
52
-
53
- class UnorderedListImplementor
54
- def multiline?
55
- true
56
- end
57
-
58
- def update_tree(tree)
59
- tree.flat_map do |node|
60
- if node.is_a?(String)
61
- update_text(node)
62
- else
63
- node
64
- end
65
- end
66
- end
67
-
68
- private
69
-
70
- def update_text(input)
71
- out = []
72
-
73
- while result = input.match(/(?:^[ ]*\*[ ]+[^\n]+(?:\n|$))+/m)
74
- out << result.pre_match unless result.pre_match.empty?
75
-
76
- children = result[0].split("\n").map do |line|
77
- Text.new(line.gsub(/^\s*\*\s+/, ''))
78
- end
79
- out << UnorderedList.new(children)
80
-
81
- input = result.post_match
82
- end
83
-
84
- out << input unless input.empty?
85
- out
86
- end
87
- end
88
- end
@@ -1,58 +0,0 @@
1
- require 'cgi'
2
-
3
- module MinimalMarkdown
4
- UnorderedList = Struct.new(:children) do
5
- def render(output)
6
- output << '<ul>'
7
- children.each do |child|
8
- output << '<li>'
9
- child.render(output)
10
- output << '</li>'
11
- end
12
- output << '</ul>'
13
- end
14
- end
15
-
16
- Line = Struct.new(:child, :spacing) do
17
- def render(output)
18
- if spacing < 2
19
- output << '<div>'
20
- child.render(output)
21
- output << '</div>'
22
- else
23
- output << '<p>'
24
- child.render(output)
25
- output << '</p>'
26
- if spacing > 2
27
- output << '<br>' * (spacing - 2)
28
- end
29
- end
30
- end
31
- end
32
-
33
- Text = Struct.new(:text) do
34
- def render(output)
35
- if text.is_a?(Array)
36
- text.each { |child| child.render(output) }
37
- else
38
- output << CGI.escapeHTML(text)
39
- end
40
- end
41
- end
42
-
43
- Bold = Struct.new(:child) do
44
- def render(output)
45
- output << '<b>'
46
- child.render(output)
47
- output << '</b>'
48
- end
49
- end
50
-
51
- Italic = Struct.new(:child) do
52
- def render(output)
53
- output << '<i>'
54
- child.render(output)
55
- output << '</i>'
56
- end
57
- end
58
- end