markdoc 1.0.1 → 1.2.1

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.
@@ -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: []