markdoc 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.
- checksums.yaml +7 -0
- data/bin/markdoc +7 -0
- data/bin/pseudo2svg +10 -0
- data/bin/sequence2svg +11 -0
- data/lib/markdoc.rb +13 -0
- data/lib/markdoc/pseudocode.rb +134 -0
- data/lib/markdoc/pseudocode.treetop +39 -0
- data/lib/markdoc/renderer.rb +47 -0
- data/lib/markdoc/sequence.rb +211 -0
- data/lib/markdoc/version.rb +3 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: deff189b4d519699fdc3084eb9b43d329cc65170
|
4
|
+
data.tar.gz: c33222481bf908352ae01a77c6f12d1133b5e1f6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 30ab7219434100465479985e1041b9e6eeb4db228540b5a033b8f638ed254d2967b745c6f43d73c3c4c7d01d26e7b7e4a8f55e61da9557af71129064d8c48f62
|
7
|
+
data.tar.gz: 81495e7b1a4cab6ace31bfb91d8cc60803c80f8777440c486360e5e26be979028aca4c24011562d89f132cf74f7f99ec1443e663d1c9a9dea7360c8038037b63
|
data/bin/markdoc
ADDED
data/bin/pseudo2svg
ADDED
data/bin/sequence2svg
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Sequence definition to image converter using sequence.pic macros.
|
4
|
+
# http://www.umlgraph.org/doc/seq-intro.html
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
# sequence2svg example.sequence > example.svg
|
8
|
+
|
9
|
+
require 'markdoc'
|
10
|
+
input = ARGV[0] ? IO.read(ARGV[0]) : ARGF.read
|
11
|
+
puts Markdoc::Sequence.draw(input)
|
data/lib/markdoc.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
require 'markdoc/pseudocode'
|
5
|
+
require 'markdoc/sequence'
|
6
|
+
require 'markdoc/renderer'
|
7
|
+
|
8
|
+
module Markdoc
|
9
|
+
def self.render(doc)
|
10
|
+
markdown = Redcarpet::Markdown.new(Renderer, fenced_code_blocks: true)
|
11
|
+
markdown.render(doc)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'polyglot'
|
2
|
+
require 'treetop'
|
3
|
+
|
4
|
+
module Markdoc
|
5
|
+
module Pseudocode
|
6
|
+
class Register
|
7
|
+
def self.id
|
8
|
+
@id ||= 0
|
9
|
+
@id += 1
|
10
|
+
"N#{@id}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ActionLiteral < Treetop::Runtime::SyntaxNode
|
15
|
+
def out(file)
|
16
|
+
file.write %Q(#{id} [shape=box label="#{label}"]\n)
|
17
|
+
end
|
18
|
+
def id
|
19
|
+
sentence.id
|
20
|
+
end
|
21
|
+
def label
|
22
|
+
sentence.value
|
23
|
+
end
|
24
|
+
def ends
|
25
|
+
[id]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class IfLiteral < Treetop::Runtime::SyntaxNode
|
30
|
+
def out(file)
|
31
|
+
file.write %Q(#{id} [shape=diamond label="#{cond.value}"]\n)
|
32
|
+
|
33
|
+
unless yes.nil?
|
34
|
+
yes.out(file)
|
35
|
+
file.write %Q( #{id} -> #{yes.id} [label="Yes"]\n)
|
36
|
+
end
|
37
|
+
unless no.nil?
|
38
|
+
no.out(file)
|
39
|
+
file.write %Q( #{id} -> #{no.id} [label="No"]\n)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def id
|
44
|
+
cond.id
|
45
|
+
end
|
46
|
+
def ends
|
47
|
+
ary = []
|
48
|
+
if yes.elements.empty?
|
49
|
+
ary << yes.id
|
50
|
+
else
|
51
|
+
ary << yes.elements.last.ends
|
52
|
+
end
|
53
|
+
if no.elements.empty?
|
54
|
+
ary << no.id
|
55
|
+
else
|
56
|
+
ary << no.elements.last.ends
|
57
|
+
end
|
58
|
+
ary.flatten
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class SentenceLiteral < Treetop::Runtime::SyntaxNode
|
63
|
+
def value
|
64
|
+
text_value.strip
|
65
|
+
end
|
66
|
+
def id
|
67
|
+
@id ||= Register.id
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class ExpressionLiteral < Treetop::Runtime::SyntaxNode
|
72
|
+
def out(file)
|
73
|
+
prev = nil
|
74
|
+
elements.each do |node|
|
75
|
+
next if node.nil?
|
76
|
+
node.out(file)
|
77
|
+
unless prev.nil?
|
78
|
+
prev.ends.each do |endid|
|
79
|
+
file.write %Q(#{endid} -> #{node.id}\n)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
prev = node
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def id
|
87
|
+
elements.first.id
|
88
|
+
end
|
89
|
+
|
90
|
+
def ends
|
91
|
+
ary = []
|
92
|
+
elements.each do |node|
|
93
|
+
if node.elements.empty?
|
94
|
+
ary << node.id
|
95
|
+
else
|
96
|
+
ary << node.ends
|
97
|
+
end
|
98
|
+
end
|
99
|
+
ary.flatten
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.draw(code)
|
104
|
+
parser = PseudocodeParser.new
|
105
|
+
tree = parser.parse(code)
|
106
|
+
|
107
|
+
if(tree.nil?)
|
108
|
+
puts parser.failure_reason
|
109
|
+
raise "Can't generate graphviz code"
|
110
|
+
else
|
111
|
+
digest = Digest::MD5.hexdigest code
|
112
|
+
|
113
|
+
graphviz = nil
|
114
|
+
Tempfile.open([digest, '.gv']) do |file|
|
115
|
+
file.write "digraph G {\n"
|
116
|
+
tree.out(file)
|
117
|
+
file.write "}\n"
|
118
|
+
graphviz = file.path
|
119
|
+
end
|
120
|
+
|
121
|
+
image = Tempfile.new([digest, '.svg'])
|
122
|
+
image.close
|
123
|
+
|
124
|
+
if system("dot -n -Tsvg -o#{image.path} #{graphviz}")
|
125
|
+
IO.read image
|
126
|
+
else
|
127
|
+
raise "Can't generate flowchart"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
Treetop.load File.join(File.dirname(__FILE__), 'pseudocode')
|
134
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Markdoc
|
2
|
+
grammar Pseudocode
|
3
|
+
rule expression
|
4
|
+
(if / action )+ <ExpressionLiteral>
|
5
|
+
end
|
6
|
+
|
7
|
+
rule if
|
8
|
+
'if' cond:sentence
|
9
|
+
newline yes:expression
|
10
|
+
'else' newline no:expression
|
11
|
+
'end' newline
|
12
|
+
<IfLiteral>
|
13
|
+
end
|
14
|
+
|
15
|
+
rule keywords
|
16
|
+
'if' / 'else' / 'end'
|
17
|
+
end
|
18
|
+
|
19
|
+
rule action
|
20
|
+
sentence newline <ActionLiteral>
|
21
|
+
end
|
22
|
+
|
23
|
+
rule sentence
|
24
|
+
( !keywords word )+ <SentenceLiteral>
|
25
|
+
end
|
26
|
+
|
27
|
+
rule word
|
28
|
+
[a-zA-Z0-9 ]+
|
29
|
+
end
|
30
|
+
|
31
|
+
rule space
|
32
|
+
[ ]
|
33
|
+
end
|
34
|
+
|
35
|
+
rule newline
|
36
|
+
space* [\n]+ space*
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'redcarpet'
|
2
|
+
require 'pygments'
|
3
|
+
|
4
|
+
module Markdoc
|
5
|
+
class Renderer < Redcarpet::Render::HTML
|
6
|
+
|
7
|
+
def block_code(code, language)
|
8
|
+
case language
|
9
|
+
when 'pseudo', 'pseudocode'
|
10
|
+
wrap_svg Pseudocode.draw(code)
|
11
|
+
when 'seq', 'sequence'
|
12
|
+
wrap_svg Sequence.draw(code)
|
13
|
+
else
|
14
|
+
Pygments.highlight(code, lexer: language)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# removes xml or doctype meta info
|
19
|
+
def wrap_svg(source)
|
20
|
+
stripped = source.
|
21
|
+
sub(/<\?xml[^>]+>/i, '').
|
22
|
+
sub(/<!DOCTYPE[^>]+>/im, '').
|
23
|
+
gsub(/<!\-\-[^>]+\-\->/, '')
|
24
|
+
|
25
|
+
%Q(<div class="svg-holder">\n#{stripped}\n</div>)
|
26
|
+
end
|
27
|
+
|
28
|
+
def doc_header
|
29
|
+
<<-END
|
30
|
+
<html>
|
31
|
+
<head>
|
32
|
+
<title>Doc</title>
|
33
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
34
|
+
<style>
|
35
|
+
#{IO.read File.expand_path('../../../css/style.css', __FILE__)}
|
36
|
+
#{IO.read File.expand_path('../../../css/pygments.css', __FILE__)}
|
37
|
+
</style>
|
38
|
+
</head>
|
39
|
+
<body>
|
40
|
+
END
|
41
|
+
end
|
42
|
+
|
43
|
+
def doc_footer
|
44
|
+
"</body>\n</html>"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
module Markdoc
|
2
|
+
module Sequence
|
3
|
+
def self.draw(code)
|
4
|
+
parser = Parser.new(code)
|
5
|
+
|
6
|
+
digest = Digest::MD5.hexdigest code
|
7
|
+
|
8
|
+
pic = nil
|
9
|
+
Tempfile.open([digest, '.pic']) do |file|
|
10
|
+
file.write parser.parse
|
11
|
+
pic = file.path
|
12
|
+
end
|
13
|
+
|
14
|
+
image = Tempfile.new([digest, '.svg'])
|
15
|
+
image.close
|
16
|
+
|
17
|
+
if system("pic2plot -Tsvg #{pic} > #{image.path}")
|
18
|
+
IO.read image
|
19
|
+
else
|
20
|
+
raise "Can't generate sequence diagram"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Role
|
25
|
+
attr_accessor :type, :id, :label, :active
|
26
|
+
def initialize(type, id, label)
|
27
|
+
self.type, self.id, self.label = type, id, label
|
28
|
+
self.active = false
|
29
|
+
end
|
30
|
+
def activate
|
31
|
+
return if active
|
32
|
+
self.active = true
|
33
|
+
"active(#{id});"
|
34
|
+
end
|
35
|
+
def deactivate
|
36
|
+
return unless active
|
37
|
+
self.active = false
|
38
|
+
"inactive(#{id});"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Message
|
43
|
+
attr_accessor :type, :source, :dest, :label, :comment
|
44
|
+
def initialize(type, source, dest, label, comment)
|
45
|
+
self.type, self.source, self.dest, self.label, self.comment = type, source, dest, label.strip, comment.strip
|
46
|
+
end
|
47
|
+
|
48
|
+
def roles
|
49
|
+
[source, dest]
|
50
|
+
end
|
51
|
+
|
52
|
+
def deliver
|
53
|
+
%Q[#{type}(#{source.id},#{dest.id}, "#{label}");]
|
54
|
+
end
|
55
|
+
|
56
|
+
def describe
|
57
|
+
return if comment.empty?
|
58
|
+
width = comment.length > 10 ? 1 : comment.length * 0.13
|
59
|
+
width = 0.5 if width < 0.5
|
60
|
+
%Q[comment(#{dest.id},C,up 0.2 right, wid #{'%.1f' % width} ht 0.3 "#{comment}");]
|
61
|
+
end
|
62
|
+
|
63
|
+
def height
|
64
|
+
source == dest ? 2 : 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Parser
|
69
|
+
attr_accessor :source, :variables, :roles, :messages, :output
|
70
|
+
|
71
|
+
def defaults
|
72
|
+
{
|
73
|
+
boxht: '0.5', # Object box height
|
74
|
+
boxwid: '1.3', # Object box width
|
75
|
+
awid: '0.1', # Active lifeline width
|
76
|
+
spacing: '0.25', # Spacing between messages
|
77
|
+
movewid: '0.75', # Spacing between objects
|
78
|
+
dashwid: '0.05', # Interval for dashed lines
|
79
|
+
maxpswid: '20', # Maximum width of picture
|
80
|
+
maxpsht: '20', # Maximum height of picture
|
81
|
+
underline: '0', # Underline the name of objects
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize(source, options = {})
|
86
|
+
self.source = source
|
87
|
+
self.output = []
|
88
|
+
self.roles = []
|
89
|
+
self.messages = []
|
90
|
+
self.variables = defaults.dup
|
91
|
+
options.each do |key, value|
|
92
|
+
variables[key] = value
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def find(id)
|
97
|
+
id.strip!
|
98
|
+
roles.detect{|role| role.id == id}
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse
|
102
|
+
source.split("\n").each do |line|
|
103
|
+
next if line.strip.empty?
|
104
|
+
# Student = Actor
|
105
|
+
# SD = SD Site
|
106
|
+
if match = line.match(/^([a-zA-Z0-9_ \t]+) *= *([a-zA-Z0-9_ \t]+)/)
|
107
|
+
if match[2] =~ /Actor/
|
108
|
+
roles << Role.new(:actor, match[1].strip, match[1].strip)
|
109
|
+
else
|
110
|
+
roles << Role.new(:object, match[1].strip, match[2].strip)
|
111
|
+
end
|
112
|
+
# Reg -> Rems : Register(K, F) # New API
|
113
|
+
elsif match = line.match(/^([a-zA-Z0-9_ \t]+) *([<\-~>]{2}) *([a-zA-Z0-9_ \t]+):([^#]+)#?(.*)/)
|
114
|
+
role1, role2, op = find(match[1]), find(match[3]), match[2]
|
115
|
+
|
116
|
+
if op.index('>')
|
117
|
+
source, dest = role1, role2
|
118
|
+
elsif op.index('<')
|
119
|
+
source, dest = role2, role1
|
120
|
+
else
|
121
|
+
raise "Message direction must be one of ->, ~>, <-, <~"
|
122
|
+
end
|
123
|
+
|
124
|
+
if op.index('-')
|
125
|
+
type = :message
|
126
|
+
elsif op.index('~')
|
127
|
+
type = :rmessage
|
128
|
+
else
|
129
|
+
raise "Message direction must be one of ->, ~>, <-, <~"
|
130
|
+
end
|
131
|
+
|
132
|
+
message = Message.new(type, source, dest, match[4], match[5])
|
133
|
+
# deactivate prev messages roles
|
134
|
+
if last = messages.last
|
135
|
+
(last.roles - message.roles).each do |role|
|
136
|
+
output << role.deactivate if role.active
|
137
|
+
end
|
138
|
+
end
|
139
|
+
# activate source before send message
|
140
|
+
output << source.activate unless source.active
|
141
|
+
output << message.deliver
|
142
|
+
output << message.describe
|
143
|
+
# activate dest
|
144
|
+
output << dest.activate unless dest.active
|
145
|
+
messages << message
|
146
|
+
# Reg : Save the form(F)
|
147
|
+
elsif match = line.match(/^([a-zA-Z0-9_ \t]+) *:([^#]+)#?(.*)/)
|
148
|
+
role = find(match[1])
|
149
|
+
message = Message.new(:message, role, role, match[2], match[3])
|
150
|
+
# deactivate prev messages roles
|
151
|
+
if last = messages.last
|
152
|
+
(last.roles - message.roles).each do |role|
|
153
|
+
output << role.deactivate if role.active
|
154
|
+
end
|
155
|
+
end
|
156
|
+
# activate role before send message
|
157
|
+
output << role.activate unless role.active
|
158
|
+
output << message.deliver
|
159
|
+
output << message.describe
|
160
|
+
messages << message
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
header
|
165
|
+
footer
|
166
|
+
|
167
|
+
output.compact.join("\n")
|
168
|
+
end
|
169
|
+
|
170
|
+
def macros
|
171
|
+
IO.read File.join(File.dirname(__FILE__), 'sequence.pic')
|
172
|
+
end
|
173
|
+
|
174
|
+
def header
|
175
|
+
headers = []
|
176
|
+
headers << '.PS'
|
177
|
+
headers << ''
|
178
|
+
headers << macros
|
179
|
+
headers << ''
|
180
|
+
headers << '# Variables'
|
181
|
+
# calculate height
|
182
|
+
|
183
|
+
variables[:maxpsht] = ((variables[:spacing].to_f *
|
184
|
+
messages.map(&:height).reduce(:+)) +
|
185
|
+
variables[:boxht].to_f).ceil
|
186
|
+
|
187
|
+
variables.each do |key, value|
|
188
|
+
headers << "#{key} = #{value};"
|
189
|
+
end
|
190
|
+
headers << '# Actors and Objects'
|
191
|
+
roles.each do |object|
|
192
|
+
headers << %Q[#{object.type}(#{object.id},"#{object.label}");]
|
193
|
+
end
|
194
|
+
headers << 'step();'
|
195
|
+
headers << ''
|
196
|
+
self.output = headers + output
|
197
|
+
end
|
198
|
+
|
199
|
+
def footer
|
200
|
+
output << ''
|
201
|
+
output << 'step();'
|
202
|
+
output << '# Complete the lifelines'
|
203
|
+
roles.each do |object|
|
204
|
+
output << "complete(#{object.id});"
|
205
|
+
end
|
206
|
+
output << ''
|
207
|
+
output << '.PE'
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: markdoc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lkhagva Ochirkhuyag
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: polyglot
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redcarpet
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: treetop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.6'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pygments.rb
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.6'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.6'
|
69
|
+
description: Markdown with support for pseudocode to flowchart and sequence diagram
|
70
|
+
generation
|
71
|
+
email: ochkoo@gmail.com
|
72
|
+
executables:
|
73
|
+
- markdoc
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- bin/markdoc
|
78
|
+
- bin/pseudo2svg
|
79
|
+
- bin/sequence2svg
|
80
|
+
- lib/markdoc.rb
|
81
|
+
- lib/markdoc/pseudocode.rb
|
82
|
+
- lib/markdoc/pseudocode.treetop
|
83
|
+
- lib/markdoc/renderer.rb
|
84
|
+
- lib/markdoc/sequence.rb
|
85
|
+
- lib/markdoc/version.rb
|
86
|
+
homepage: https://github.com/ochko/markdoc
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 1.9.2
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.4.5
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: Markdown to HTML converter with diagrams
|
110
|
+
test_files: []
|