markdoc 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|