docdown 0.0.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,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