markdoc 1.0.1 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 903c73cf0ff9c9c6b3b9caaf41c0d9fd6ebda60624259acae36e09fa8fd4f81a
4
+ data.tar.gz: aad92c597c2acc1f40449d7e5f1feaa608140831bbd88d22640e77e23796e620
5
+ SHA512:
6
+ metadata.gz: fee4a4c2de9b2350c3df3991c6b6a3c11640fe7026f741e6eb62bb50475fc5adb6492004fb24e304231cbdfa1c9beba8de527b1d79a76e8e497e27bae0d66498
7
+ data.tar.gz: a2e44199fb3ed992890ce7a74d028b90e2d708c28eaa5cb9db34746784c11c4283d3134ecbdf72bf67c4077a7c2f3256ff5605f3a5bf5113d8b9db6a5cb15501
@@ -21,11 +21,7 @@ module Markdoc
21
21
  end
22
22
 
23
23
  rule sentence
24
- ( !keywords word )+ <SentenceLiteral>
25
- end
26
-
27
- rule word
28
- [a-zA-Z0-9 ]+
24
+ ( !keywords [^\n]+ )+ <SentenceLiteral>
29
25
  end
30
26
 
31
27
  rule space
@@ -36,4 +32,4 @@ module Markdoc
36
32
  space* [\n]+ space*
37
33
  end
38
34
  end
39
- end
35
+ end
@@ -1,211 +1,323 @@
1
1
  module Markdoc
2
2
  module Sequence
3
- def self.draw(code, format = :svg)
4
- parser = Parser.new(code)
3
+ DEFAULTS = {
4
+ diagram: {
5
+ offsetx: 10,
6
+ offsety: 10,
7
+ width: 900,
8
+ height: 600
9
+ },
10
+ role: {
11
+ font: "'Roboto Condensed', sans-serif",
12
+ border: '#3c4260',
13
+ fill: '#dcd7d7',
14
+ radius: 2,
15
+ spacing: 100,
16
+ width: 100,
17
+ height: 55,
18
+ line: 3,
19
+ },
20
+ message: {
21
+ color: '#3c4260',
22
+ font: "'Roboto Condensed', sans-serif",
23
+ size: 11,
24
+ spacing: 40,
25
+ offset: 100, # from top
26
+ line: 3,
27
+ dash: '4,2'
28
+ }
29
+ }
5
30
 
6
- digest = Digest::MD5.hexdigest code
31
+ def self.draw(code)
32
+ diagram = Diagram.new(code)
33
+ diagram.parse
34
+ diagram.print
35
+ end
36
+
37
+ class Role
38
+ include Comparable
39
+
40
+ attr_accessor :id, :label, :messages, :column,
41
+ :offsetx, :offsety, :border, :fill, :radius, :spacing, :width, :height, :line, :font,
42
+ :prev, :succ
7
43
 
8
- pic = nil
9
- Tempfile.open([digest, '.pic']) do |file|
10
- file.write parser.parse
11
- pic = file.path
44
+ def initialize(args)
45
+ self.messages = []
46
+ self.id = args[:id].strip
47
+ self.label = args[:label].strip
48
+
49
+ # ui settings
50
+ self.column = args[:column]
51
+ self.offsetx = args[:diagram][:offsetx]
52
+ self.offsety = args[:diagram][:offsety]
53
+ self.border = args[:ui][:border]
54
+ self.fill = args[:ui][:fill]
55
+ self.radius = args[:ui][:radius]
56
+ self.spacing = args[:ui][:spacing]
57
+ self.width = args[:ui][:width]
58
+ self.height = args[:ui][:height]
59
+ self.line = args[:ui][:line]
60
+ self.font = args[:ui][:font]
12
61
  end
13
62
 
14
- if format == :pic
15
- return IO.read(pic)
63
+ def <=> o
64
+ column <=> o.column
16
65
  end
17
66
 
18
- image = Tempfile.new([digest, ".#{format}"])
19
- image.close
67
+ def type
68
+ case label
69
+ when /actor/i
70
+ :actor
71
+ when /database/i
72
+ :database
73
+ when /site|web/i
74
+ :website
75
+ when /application|system/i
76
+ :system
77
+ else
78
+ :component
79
+ end
80
+ end
20
81
 
21
- if system("pic2plot -T#{format} #{pic} > #{image.path}")
22
- IO.read image
23
- else
24
- raise "Can't generate sequence diagram"
82
+ def center
83
+ x + width/2
25
84
  end
26
- end
27
85
 
28
- class Role
29
- attr_accessor :type, :id, :label, :active
30
- def initialize(type, id, label)
31
- self.type, self.id, self.label = type, id, label
32
- self.active = false
86
+ def x
87
+ offsetx + column*(width + spacing)
33
88
  end
34
- def activate
35
- return if active
36
- self.active = true
37
- "active(#{id});"
89
+
90
+ def y
91
+ offsety
38
92
  end
39
- def deactivate
40
- return unless active
41
- self.active = false
42
- "inactive(#{id});"
93
+
94
+ def print
95
+ elements = []
96
+ case type
97
+ when :actor
98
+ elements << %Q[<g transform="translate(#{x+10},0)"><path d="M74,64 a30,30 0 0,0 -27,-27 a16,18 0 1,0 -16,0 a30,30 0, 0,0 -27,27 z" stroke-width="#{line}" fill="#{fill}" stroke="#{border}"/></g>]
99
+ elements << %Q[<text x="#{x+46-2*id.size}" y="#{y+height-5}" font-family="#{font}" font-size="12" fill="#{border}">#{id}</text>]
100
+ else
101
+ elements << %Q[<rect fill="#{fill}" stroke="#{border}" rx="#{radius}" ry="#{radius}" x="#{x}" y="#{y}" width="#{width}" height="#{height}" stroke-width="#{line}"/>]
102
+ elements << %Q[<text x="#{x+10}" y="#{y+20}" font-family="#{font}" font-size="12" fill="#{border}">#{label}</text>]
103
+ end
104
+
105
+ x1 = center
106
+ y1 = offsety + height
107
+ x2 = center
108
+ y2 = messages.last.y + 10
109
+ elements << %Q[<line x1="#{x1}" y1="#{y1}" x2="#{x2}" y2="#{y2}" stroke="#{border}" stroke-width="#{line}"/>]
110
+
111
+ elements.join("\n")
43
112
  end
44
113
  end
45
114
 
46
115
  class Message
47
- attr_accessor :type, :source, :dest, :label, :comment
48
- def initialize(type, source, dest, label, comment)
49
- self.type, self.source, self.dest, self.label, self.comment = type, source, dest, label.strip, comment.strip
50
- end
116
+ attr_accessor :source, :dest, :op, :label, :comment, :row,
117
+ :offset, :color, :font, :size, :spacing, :line, :dash,
118
+ :options
119
+
120
+ def initialize(args)
121
+ self.options = []
122
+ self.label = args[:label].strip
123
+ self.comment = args[:comment].strip
124
+
125
+ self.op = args[:op]
126
+ self.row = args[:row]
127
+ # ui
128
+ self.offset = args[:ui][:offset]
129
+ self.color = args[:ui][:color]
130
+ self.font = args[:ui][:font]
131
+ self.size = args[:ui][:size]
132
+ self.spacing = args[:ui][:spacing]
133
+ self.line = args[:ui][:line]
134
+ self.dash = args[:ui][:dash]
135
+
136
+ if op.index('~')
137
+ options << %Q[stroke-dasharray="#{dash}"]
138
+ elsif op.index('-').nil?
139
+ raise "Message direction must be one of ->, ~>, <-, <~"
140
+ end
141
+
142
+ if op.index('>')
143
+ self.source, self.dest = args[:role1], args[:role2]
144
+ elsif op.index('<')
145
+ self.source, self.dest = args[:role2], args[:role1]
146
+ else
147
+ raise "Message direction must be one of ->, ~>, <-, <~"
148
+ end
51
149
 
52
- def roles
53
- [source, dest]
150
+ source.messages << self
151
+ dest.messages << self unless source.eql?(dest)
54
152
  end
55
153
 
56
- def deliver
57
- %Q[#{type}(#{source.id},#{dest.id}, "#{label}");]
154
+ def print
155
+ role1, role2 = *(source < dest ? [source, dest] : [dest, source])
156
+ elements = []
157
+
158
+ if role1.eql?(role2)
159
+ x1 = role1.center
160
+ y1 = y
161
+ x2 = x1 + 50
162
+ y2 = y1 + spacing
163
+
164
+ elements << %Q(<polyline points="#{x1},#{y1} #{x2},#{y1} #{x2},#{y2} #{x1+5},#{y2}" fill="none" stroke-width="2" stroke-linejoin="round" stroke="#{color}" #{options.join ' '}/>)
165
+ elements << %Q(<polygon points="#{x1+10},#{y2-5} #{x1},#{y2} #{x1+10},#{y2+5}" fill="#{color}"/>)
166
+ elements << %Q(<text x="#{x1+10}" y="#{y1-5}" font-family="#{font}" font-size="#{size}" fill="#{color}">#{label}</text>)
167
+ else
168
+ x1 = role1.center
169
+ x2 = role2.center
170
+
171
+ if role1 == source
172
+ x2 -= 10
173
+ elements << %Q(<polygon points="#{x2},#{y-5} #{x2+10},#{y} #{x2},#{y+5}" fill="#{color}"/>)
174
+ else
175
+ x1 += 10
176
+ elements << %Q(<polygon points="#{x1},#{y-5} #{x1-10},#{y} #{x1},#{y+5}" fill="#{color}"/>)
177
+ end
178
+
179
+ elements << %Q(<line x1="#{x1}" y1="#{y}" x2="#{x2}" y2="#{y}" stroke="#{color}" stroke-width="2" #{options.join ' '}/>)
180
+ elements << %Q(<text x="#{x1+40}" y="#{y-5}" font-family="#{font}" font-size="#{size}" fill="#{color}">#{label}</text>)
181
+
182
+ if comment.size > 0
183
+ x = role2.prev.center + 15
184
+ elements << %Q(<path fill="#eeeeee" d="M#{x2-30},#{y+1} L#{x2-30},#{y+10} H#{x} V#{y+2*spacing - 25} H#{x2} V#{y+10} H#{x2-20} z" />)
185
+ elements << %Q(<text x="#{x+5}" y="#{y+23}" font-family="#{font}" font-size="#{size}" fill="#{color}">)
186
+ split(comment).each_with_index do |line, i|
187
+ elements << %Q(<tspan x="#{x+5}" y="#{y+23+13*i}">#{line}</tspan>)
188
+ end
189
+ elements << '</text>'
190
+ end
191
+ end
192
+
193
+ elements.join("\n")
58
194
  end
59
195
 
60
- def describe
61
- return if comment.empty?
62
- width = comment.length > 10 ? 1 : comment.length * 0.13
63
- width = 0.5 if width < 0.5
64
- %Q[comment(#{dest.id},C,up 0.2 right, wid #{'%.1f' % width} ht 0.3 "#{comment}");]
196
+ def y
197
+ offset + row*spacing
65
198
  end
66
199
 
67
- def height
68
- source == dest ? 2 : 1
200
+ private
201
+
202
+ def split(text, max = 35)
203
+ ary = []
204
+ line = ''
205
+ text.split.each do |word|
206
+ if (line + word).length < max
207
+ line << ' ' << word
208
+ else
209
+ ary << line
210
+ line = word
211
+ end
212
+ end
213
+ ary << line if line.length > 0
214
+ ary
69
215
  end
216
+
70
217
  end
71
218
 
72
- class Parser
73
- attr_accessor :source, :variables, :roles, :messages, :output
74
-
75
- def defaults
76
- {
77
- boxht: '0.5', # Object box height
78
- boxwid: '1.3', # Object box width
79
- awid: '0.1', # Active lifeline width
80
- spacing: '0.25', # Spacing between messages
81
- movewid: '0.75', # Spacing between objects
82
- dashwid: '0.05', # Interval for dashed lines
83
- maxpswid: '20', # Maximum width of picture
84
- maxpsht: '20', # Maximum height of picture
85
- underline: '0', # Underline the name of objects
86
- }
87
- end
219
+ class Diagram
220
+ attr_accessor :input, :output, :attributes, :roles, :messages, :rows
88
221
 
89
- def initialize(source, options = {})
90
- self.source = source
222
+ def initialize(input, options = {})
223
+ self.input = input
91
224
  self.output = []
92
225
  self.roles = []
93
226
  self.messages = []
94
- self.variables = defaults.dup
227
+ self.rows = 0
228
+ self.attributes = DEFAULTS.dup
229
+
95
230
  options.each do |key, value|
96
- variables[key] = value
231
+ attributes.merge!(key => value)
97
232
  end
98
233
  end
99
234
 
100
235
  def find(id)
101
- id.strip!
102
- roles.detect{|role| role.id == id}
236
+ roles.detect{|role| role.id == id.strip} or raise("Non-declared role: #{id}")
103
237
  end
104
238
 
105
239
  def parse
106
- source.split("\n").each do |line|
240
+ input.split("\n").each do |line|
107
241
  next if line.strip.empty?
108
- if match = line.match(/^([a-zA-Z0-9_ \t]+) *= *([a-zA-Z0-9_ \t]+)/)
109
- if match[2] =~ /Actor/
110
- roles << Role.new(:actor, match[1].strip, match[1].strip)
111
- else
112
- roles << Role.new(:object, match[1].strip, match[2].strip)
113
- end
114
- elsif match = line.match(/^([a-zA-Z0-9_ \t]+) *([<\-~>]{2}) *([a-zA-Z0-9_ \t]+):([^#]+)#?(.*)/)
115
- role1, role2, op = find(match[1]), find(match[3]), match[2]
116
-
117
- if op.index('>')
118
- source, dest = role1, role2
119
- elsif op.index('<')
120
- source, dest = role2, role1
121
- else
122
- raise "Message direction must be one of ->, ~>, <-, <~"
123
- end
124
242
 
125
- if op.index('-')
126
- type = :message
127
- elsif op.index('~')
128
- type = :rmessage
129
- else
130
- raise "Message direction must be one of ->, ~>, <-, <~"
131
- end
132
-
133
- message = Message.new(type, source, dest, match[4], match[5])
134
- # deactivate prev messages roles
135
- if last = messages.last
136
- (last.roles - message.roles).each do |role|
137
- output << role.deactivate if role.active
138
- end
139
- end
140
- # activate source before send message
141
- output << source.activate unless source.active
142
- output << message.deliver
143
- output << message.describe
144
- # activate dest
145
- output << dest.activate unless dest.active
146
- messages << message
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
243
+ if matches = line.match(/^(?<id>[a-zA-Z0-9_ \t]+) *= *(?<label>[a-zA-Z0-9_ \t]+)/)
244
+ # User = Actor
245
+ roles << Role.new(
246
+ id: matches[:id],
247
+ label: matches[:label],
248
+ column: roles.size,
249
+ diagram: attributes[:diagram],
250
+ ui: attributes[:role]
251
+ )
252
+ elsif matches = line.match(/^(?<role1>[a-zA-Z0-9_ \t]+) *(?<op>[<\-~>]{2}) *(?<role2>[a-zA-Z0-9_ \t]+):(?<label>[^#]+)#?(?<comment>.*)/)
253
+ # User -> Web : Login
254
+ messages << Message.new(
255
+ role1: find(matches[:role1]),
256
+ role2: find(matches[:role2]),
257
+ row: rows,
258
+ op: matches[:op],
259
+ label: matches[:label],
260
+ comment: matches[:comment],
261
+ diagram: attributes[:diagram],
262
+ ui: attributes[:message]
263
+ )
264
+ self.rows += 1
265
+ # comment takes 1 more row
266
+ self.rows += 1 if matches[:comment].length > 0
267
+ elsif matches = line.match(/^(?<role>[a-zA-Z0-9_ \t]+) *:(?<label>[^#]+)#?(?<comment>.*)/)
268
+ # Web : Save the form
269
+ messages << Message.new(
270
+ role1: find(matches[:role]),
271
+ role2: find(matches[:role]),
272
+ row: rows,
273
+ op: '->',
274
+ label: matches[:label],
275
+ comment: matches[:comment],
276
+ diagram: attributes[:diagram],
277
+ ui: attributes[:message]
278
+ )
279
+ # self message takes 2 rows
280
+ self.rows += 2
161
281
  end
162
282
  end
163
283
 
164
- header
165
- footer
284
+ prev = nil
285
+ roles.each do |succ|
286
+ succ.prev = prev
287
+ prev.succ = succ if prev
288
+ prev = succ
289
+ end
290
+ end
166
291
 
167
- output.compact.join("\n")
292
+ def print
293
+ template % {
294
+ width: width,
295
+ height: height,
296
+ content: (roles + messages).map(&:print).join("\n")
297
+ }
168
298
  end
169
299
 
170
- def macros
171
- IO.read File.join(File.dirname(__FILE__), 'sequence.pic')
300
+ def height
301
+ attributes[:message][:offset] +
302
+ attributes[:message][:spacing] * rows +
303
+ attributes[:diagram][:offsety] * 2
172
304
  end
173
305
 
174
- def header
175
- headers = []
176
- headers << '.PS'
177
- headers << ''
178
- headers << macros
179
- headers << ''
180
- headers << '# Variables'
181
- # calculate height
306
+ def width
307
+ (attributes[:role][:spacing] + attributes[:role][:width]) * roles.size +
308
+ attributes[:diagram][:offsetx]
309
+ end
182
310
 
183
- variables[:maxpsht] = ((variables[:spacing].to_f *
184
- messages.map(&:height).reduce(:+)) +
185
- variables[:boxht].to_f).ceil
311
+ private
186
312
 
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
313
+ def template
314
+ '<svg xmlns="http://www.w3.org/2000/svg" '+
315
+ 'xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" '+
316
+ 'width="%{width}" height="%{height}" '+
317
+ 'viewBox="0 0 %{width} %{height}">'+
318
+ '%{content}</svg>'
197
319
  end
198
320
 
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
321
  end
210
322
  end
211
323
  end
@@ -1,3 +1,3 @@
1
1
  module Markdoc
2
- VERSION = '1.0.1'
2
+ VERSION = '1.2.1'
3
3
  end
metadata CHANGED
@@ -1,122 +1,127 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markdoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
5
- prerelease:
4
+ version: 1.2.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Lkhagva Ochirkhuyag
9
- autorequire:
8
+ autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2015-04-10 00:00:00.000000000 Z
11
+ date: 2020-10-23 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: polyglot
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0.3'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
26
  version: '0.3'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: redcarpet
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ~>
31
+ - - "~>"
36
32
  - !ruby/object:Gem::Version
37
33
  version: '3.2'
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ~>
38
+ - - "~>"
44
39
  - !ruby/object:Gem::Version
45
40
  version: '3.2'
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: treetop
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ~>
45
+ - - "~>"
52
46
  - !ruby/object:Gem::Version
53
47
  version: '1.6'
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ~>
52
+ - - "~>"
60
53
  - !ruby/object:Gem::Version
61
54
  version: '1.6'
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: pygments.rb
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
- - - ~>
59
+ - - ">="
68
60
  - !ruby/object:Gem::Version
69
- version: '0.6'
61
+ version: '0'
70
62
  type: :runtime
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
- - - ~>
66
+ - - ">="
76
67
  - !ruby/object:Gem::Version
77
- version: '0.6'
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">"
74
+ - !ruby/object:Gem::Version
75
+ version: 10.0.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">"
81
+ - !ruby/object:Gem::Version
82
+ version: 10.0.0
78
83
  description: Markdown with support for pseudocode to flowchart and sequence diagram
79
84
  generation
80
85
  email: ochkoo@gmail.com
81
86
  executables:
82
87
  - markdoc
88
+ - pseudo2svg
89
+ - sequence2svg
83
90
  extensions: []
84
91
  extra_rdoc_files: []
85
92
  files:
93
+ - bin/markdoc
94
+ - bin/pseudo2svg
95
+ - bin/sequence2svg
96
+ - css/pygments.css
97
+ - css/style.css
86
98
  - lib/markdoc.rb
87
99
  - lib/markdoc/pseudocode.rb
88
100
  - lib/markdoc/pseudocode.treetop
89
101
  - lib/markdoc/renderer.rb
90
102
  - lib/markdoc/sequence.rb
91
103
  - lib/markdoc/version.rb
92
- - css/style.css
93
- - css/pygments.css
94
- - bin/markdoc
95
- - bin/pseudo2svg
96
- - bin/sequence2svg
97
104
  homepage: https://github.com/ochko/markdoc
98
105
  licenses:
99
106
  - MIT
100
- post_install_message:
107
+ metadata: {}
108
+ post_install_message:
101
109
  rdoc_options: []
102
110
  require_paths:
103
111
  - lib
104
112
  required_ruby_version: !ruby/object:Gem::Requirement
105
- none: false
106
113
  requirements:
107
- - - ! '>='
114
+ - - ">="
108
115
  - !ruby/object:Gem::Version
109
116
  version: 1.9.2
110
117
  required_rubygems_version: !ruby/object:Gem::Requirement
111
- none: false
112
118
  requirements:
113
- - - ! '>='
119
+ - - ">="
114
120
  - !ruby/object:Gem::Version
115
121
  version: '0'
116
122
  requirements: []
117
- rubyforge_project:
118
- rubygems_version: 1.8.23.2
119
- signing_key:
120
- specification_version: 3
123
+ rubygems_version: 3.0.3
124
+ signing_key:
125
+ specification_version: 4
121
126
  summary: Markdown to HTML converter with diagrams
122
127
  test_files: []