markdoc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'markdoc'
4
+
5
+ input = ARGV[0] ? IO.read(ARGV[0]) : ARGF.read
6
+
7
+ puts Markdoc.render(input)
data/bin/pseudo2svg ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Pseudo code to Graphviz converter
4
+ #
5
+ # Usage:
6
+ # pseudo2svg example.pseudo > example.svg
7
+
8
+ require 'markdoc'
9
+ input = ARGV[0] ? IO.read(ARGV[0]) : ARGF.read
10
+ puts Markdoc::Pseudocode.draw(input)
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
@@ -0,0 +1,3 @@
1
+ module Markdoc
2
+ VERSION = '0.1.0'
3
+ 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: []