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