docdown 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,138 @@
1
+ module Docdown
2
+ class Parser
3
+ class ParseError < StandardError
4
+ def initialize(options = {})
5
+ keyword = options[:keyword]
6
+ command = options[:command]
7
+ line_number = options[:line_number]
8
+ block = options[:block].lines.map do |line|
9
+ if line == command
10
+ " > #{line}"
11
+ else
12
+ " #{line}"
13
+ end
14
+ end.join("")
15
+
16
+ msg = "Error parsing (line:#{line_number}):\n"
17
+ msg << "> '#{command.strip}'\n"
18
+ msg << "No such registered command: '#{keyword}'\n"
19
+ msg << "registered commands: #{Docdown.known_commands.inspect}\n\n"
20
+ msg << block
21
+ msg << "\n"
22
+ super msg
23
+ end
24
+
25
+ end
26
+
27
+ DEFAULT_KEYWORD = ":::"
28
+ INDENT_BLOCK = '(?<before_indent>(^\s*$\n|\A)(^(?:[ ]{4}|\t))(?<indent_contents>.*)(?<after_indent>[^\s].*$\n?(?:(?:^\s*$\n?)*^(?:[ ]{4}|\t).*[^\s].*$\n?)*))'
29
+ GITHUB_BLOCK = '^(?<fence>(?<fence_char>~|`){3,})\s*?(?<lang>\w+)?\s*?\n(?<contents>.*?)^\g<fence>\g<fence_char>*\s*?\n'
30
+ CODEBLOCK_REGEX = /(#{GITHUB_BLOCK})/m
31
+ COMMAND_REGEX = ->(keyword) {
32
+ /^#{keyword}(?<tag>(\s|=|-)?)\s*(?<command>(\S)+)\s+(?<statement>.*)$/
33
+ }
34
+
35
+ attr_reader :contents, :keyword, :stack
36
+
37
+ def initialize(contents, options = {})
38
+ @contents = contents
39
+ @original = contents.dup
40
+ @keyword = options[:keyword] || DEFAULT_KEYWORD
41
+ @stack = []
42
+ partition
43
+ end
44
+
45
+
46
+ def to_md
47
+ @stack.map do |s|
48
+ if s.respond_to?(:render)
49
+ s.render
50
+ else
51
+ s
52
+ end
53
+ end.join("")
54
+ end
55
+
56
+ # split into [before_code, code, after_code], process code, and re-run until tail is empty
57
+ def partition
58
+ until contents.empty?
59
+ head, code, tail = contents.partition(CODEBLOCK_REGEX)
60
+ @stack << head unless head.empty?
61
+ add_fenced_code(code) unless code.empty?
62
+ @contents = tail
63
+ end
64
+ end
65
+
66
+ def add_fenced_code(fenced_code_str)
67
+ fenced_code_str.match(CODEBLOCK_REGEX) do |m|
68
+ fence = m[:fence]
69
+ lang = m[:lang]
70
+ code = m[:contents]
71
+ @stack << "#{fence}#{lang}\n"
72
+ add_code_commands(code)
73
+ @stack << "#{fence}\n"
74
+ end
75
+ end
76
+
77
+ def add_match_to_code_commands(match, commands)
78
+ command = match[:command]
79
+ tag = match[:tag]
80
+ statement = match[:statement]
81
+
82
+ code_command = Docdown.code_command_from_keyword(command, statement)
83
+
84
+ case tag
85
+ when /\-/
86
+ code_command.hidden = true
87
+ when /\=/
88
+ code_command.render_result = true
89
+ when /\s/
90
+ # default do nothing
91
+ end
92
+
93
+ @stack << "\n" if commands.last.is_a?(Docdown::CodeCommand)
94
+ @stack << code_command
95
+ commands << code_command
96
+ code_command
97
+ end
98
+
99
+ def check_parse_error(command, code_block)
100
+ return unless code_command = @stack.last
101
+ return unless code_command.is_a?(Docdown::CodeCommands::NoSuchCommand)
102
+ @original.lines.each_with_index do |line, index|
103
+ next unless line == command
104
+ raise ParseError.new(keyword: code_command.keyword,
105
+ block: code_block,
106
+ command: command,
107
+ line_number: index.next)
108
+ end
109
+ end
110
+
111
+ def add_code_commands(code)
112
+ commands = []
113
+ code.lines.each do |line|
114
+ if match = line.match(command_regex)
115
+ add_match_to_code_commands(match, commands)
116
+ check_parse_error(line, code)
117
+ else
118
+ unless commands.empty?
119
+ commands.last << line
120
+ next
121
+ end
122
+ @stack << line
123
+ end
124
+ end
125
+ end
126
+
127
+ def contents_to_array
128
+ partition
129
+ end
130
+
131
+ def command_regex
132
+ COMMAND_REGEX.call(keyword)
133
+ end
134
+ end
135
+ end
136
+
137
+
138
+ # convert string of markdown to array of strings and code_commands
@@ -0,0 +1,3 @@
1
+ module Docdown
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,104 @@
1
+ require 'test_helper'
2
+
3
+ class ParserTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def test_parse_bash
9
+ contents = <<-RUBY
10
+ sup
11
+
12
+ ```
13
+ ::: $ mkdir foo
14
+ :::= $ ls
15
+ ```
16
+
17
+ yo
18
+ RUBY
19
+
20
+
21
+ Dir.mktmpdir do |dir|
22
+ Dir.chdir(dir) do
23
+ expected = "sup\n\n```\n$ mkdir foo \n$ ls \nfoo\n```\n\nyo\n"
24
+ parsed = Docdown::Parser.new(contents)
25
+ actual = parsed.to_md
26
+ assert_equal expected, actual
27
+
28
+ parsed = Docdown::Parser.new("\n```\n:::= $ ls\n```\n")
29
+ actual = parsed.to_md
30
+ expected = "\n```\n$ ls \nfoo\n```\n"
31
+ assert_equal expected, actual
32
+ end
33
+ end
34
+ end
35
+
36
+
37
+ def test_parse_write_commands
38
+ contents = <<-RUBY
39
+ sup
40
+
41
+ ```
42
+ ::: write foo/code.rb
43
+ a = 1 + 1
44
+ b = a * 2
45
+ ```
46
+ yo
47
+ RUBY
48
+
49
+ Dir.mktmpdir do |dir|
50
+ Dir.chdir(dir) do
51
+
52
+ expected = "sup\n\n```\nIn file `foo/code.rb` add:\na = 1 + 1\nb = a * 2\n```\nyo\n"
53
+ parsed = Docdown::Parser.new(contents)
54
+ actual = parsed.to_md
55
+ assert_equal expected, actual
56
+ end
57
+ end
58
+
59
+
60
+ contents = <<-RUBY
61
+
62
+ ```
63
+ ::: write foo/newb.rb
64
+ puts 'hello world'
65
+ :::= $ cat foo/newb.rb
66
+ ```
67
+ RUBY
68
+
69
+ Dir.mktmpdir do |dir|
70
+ Dir.chdir(dir) do
71
+
72
+ expected = "\n```\nIn file `foo/newb.rb` add:\nputs 'hello world'\n\n$ cat foo/newb.rb \nputs 'hello world'\n```\n"
73
+ parsed = Docdown::Parser.new(contents)
74
+ actual = parsed.to_md
75
+ assert_equal expected, actual
76
+ end
77
+ end
78
+ end
79
+
80
+ def test_irb
81
+
82
+ contents = <<-RUBY
83
+ ```
84
+ :::= irb --simple-prompt
85
+ a = 3
86
+ b = "foo" * a
87
+ puts b
88
+ ```
89
+ RUBY
90
+
91
+
92
+ Dir.mktmpdir do |dir|
93
+ Dir.chdir(dir) do
94
+
95
+ parsed = Docdown::Parser.new(contents)
96
+ actual = parsed.to_md
97
+ expected = "```\n$ irb --simple-prompt\na = 3\n=> 3\r\nb = \"foo\" * a\n=> \"foofoofoo\"\r\nputs b\nfoofoofoo\r\n=> nil\r```\n"
98
+ assert_equal expected, actual
99
+ end
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,200 @@
1
+ require 'test_helper'
2
+
3
+ class RegexTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def test_indent_regex
9
+
10
+ contents = <<-RUBY
11
+ foo
12
+
13
+ $ cd
14
+ yo
15
+ sup
16
+
17
+ bar
18
+ RUBY
19
+
20
+ regex = Docdown::Parser::INDENT_BLOCK
21
+ parsed = contents.match(/#{regex}/).to_s
22
+ assert_equal "\n $ cd\n yo\n sup\n", parsed
23
+ end
24
+
25
+ def test_github_regex
26
+
27
+ contents = <<-RUBY
28
+ foo
29
+
30
+ ```
31
+ $ cd
32
+ yo
33
+ sup
34
+ ```
35
+
36
+ bar
37
+ RUBY
38
+
39
+ regex = Docdown::Parser::GITHUB_BLOCK
40
+ parsed = contents.match(/#{regex}/m).to_s
41
+ assert_equal "```\n$ cd\nyo\nsup\n```\n", parsed
42
+ end
43
+
44
+ def test_github_tagged_regex
45
+ contents = <<-RUBY
46
+ foo
47
+
48
+ ```ruby
49
+ $ cd
50
+ yo
51
+ sup
52
+ ```
53
+
54
+ bar
55
+ RUBY
56
+
57
+ regex = Docdown::Parser::GITHUB_BLOCK
58
+ parsed = contents.match(/#{regex}/m).to_s
59
+ assert_equal "```ruby\n$ cd\nyo\nsup\n```\n", parsed
60
+ end
61
+
62
+ def test_command_regex
63
+ regex = Docdown::Parser::COMMAND_REGEX.call(":::")
64
+
65
+ contents = ":::$ mkdir schneems"
66
+ match = contents.match(regex)
67
+ assert_equal "", match[:tag]
68
+ assert_equal "$", match[:command]
69
+ assert_equal "mkdir schneems", match[:statement]
70
+
71
+ contents = ":::=$ mkdir schneems"
72
+ match = contents.match(regex)
73
+ assert_equal "=", match[:tag]
74
+ assert_equal "$", match[:command]
75
+ assert_equal "mkdir schneems", match[:statement]
76
+
77
+ contents = ":::= $ mkdir schneems"
78
+ match = contents.match(regex)
79
+ assert_equal "=", match[:tag]
80
+ assert_equal "$", match[:command]
81
+ assert_equal "mkdir schneems", match[:statement]
82
+
83
+ contents = ":::-$ mkdir schneems"
84
+ match = contents.match(regex)
85
+ assert_equal "-", match[:tag]
86
+ assert_equal "$", match[:command]
87
+ assert_equal "mkdir schneems", match[:statement]
88
+
89
+ contents = ":::- $ mkdir schneems"
90
+ match = contents.match(regex)
91
+ assert_equal "-", match[:tag]
92
+ assert_equal "$", match[:command]
93
+ assert_equal "mkdir schneems", match[:statement]
94
+ end
95
+
96
+
97
+ def test_codeblock_regex
98
+
99
+ contents = <<-RUBY
100
+ foo
101
+
102
+ ```
103
+ :::$ mkdir
104
+ ```
105
+
106
+ zoo
107
+
108
+ ```
109
+ :::$ cd ..
110
+ something
111
+ ```
112
+
113
+ bar
114
+ RUBY
115
+
116
+ regex = Docdown::Parser::CODEBLOCK_REGEX
117
+
118
+ actual = contents.partition(regex)
119
+ expected = ["foo\n\n",
120
+ "```\n:::$ mkdir\n```\n",
121
+ "\nzoo\n\n```\n:::$ cd ..\nsomething\n```\n\nbar\n"]
122
+
123
+ assert_equal expected, actual
124
+
125
+ str = "```\n:::$ mkdir\n```\n"
126
+ match = str.match(regex)
127
+ assert_equal ":::$ mkdir\n", match[:contents]
128
+
129
+ str = "\n\n```\n:::$ cd ..\nsomething\n```\n\nbar\n"
130
+ match = str.match(regex)
131
+ assert_equal ":::$ cd ..\nsomething\n", match[:contents]
132
+
133
+ # partition, shift, codebloc,
134
+ end
135
+
136
+ def test_complex_regex
137
+
138
+ contents = <<-RUBY
139
+ ```java
140
+ :::= write app/controllers/Application.java
141
+ package controllers;
142
+
143
+ import static java.util.concurrent.TimeUnit.SECONDS;
144
+ import models.Pinger;
145
+ import play.libs.Akka;
146
+ import play.libs.F.Callback0;
147
+ import play.mvc.Controller;
148
+ import play.mvc.Result;
149
+ import play.mvc.WebSocket;
150
+ import scala.concurrent.duration.Duration;
151
+ import views.html.index;
152
+ import akka.actor.ActorRef;
153
+ import akka.actor.Cancellable;
154
+ import akka.actor.Props;
155
+
156
+ public class Application extends Controller {
157
+ public static WebSocket<String> pingWs() {
158
+ return new WebSocket<String>() {
159
+ public void onReady(WebSocket.In<String> in, WebSocket.Out<String> out) {
160
+ final ActorRef pingActor = Akka.system().actorOf(Props.create(Pinger.class, in, out));
161
+ final Cancellable cancellable = Akka.system().scheduler().schedule(Duration.create(1, SECONDS),
162
+ Duration.create(1, SECONDS),
163
+ pingActor,
164
+ "Tick",
165
+ Akka.system().dispatcher(),
166
+ null
167
+ );
168
+
169
+ in.onClose(new Callback0() {
170
+ @Override
171
+ public void invoke() throws Throwable {
172
+ cancellable.cancel();
173
+ }
174
+ });
175
+ }
176
+
177
+ };
178
+ }
179
+
180
+ public static Result pingJs() {
181
+ return ok(views.js.ping.render());
182
+ }
183
+
184
+ public static Result index() {
185
+ return ok(index.render());
186
+ }
187
+ }
188
+ ```
189
+ RUBY
190
+
191
+ regex = Docdown::Parser::CODEBLOCK_REGEX
192
+ match = contents.match(regex)
193
+ assert_equal 'java', match[:lang]
194
+ assert_equal '```', match[:fence]
195
+ assert_equal '`', match[:fence_char]
196
+
197
+ assert_equal contents.strip, match.to_s.strip
198
+ end
199
+
200
+ end
@@ -0,0 +1,8 @@
1
+ require 'test_helper'
2
+
3
+ class ParseJavaTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+
7
+ end
8
+ end