markdownplus 0.1.0 → 0.2.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.
@@ -1,12 +1,10 @@
1
1
  require 'csv'
2
2
  require 'json'
3
3
  require 'open-uri'
4
- require 'pygments'
5
- require 'redcarpet'
6
4
 
7
5
  module Markdownplus
8
6
  class Parser
9
- attr_reader :source
7
+ attr_reader :input
10
8
  attr_accessor :current_block
11
9
 
12
10
  def self.parse(value)
@@ -16,7 +14,7 @@ module Markdownplus
16
14
  end
17
15
 
18
16
  def initialize(value=nil)
19
- @source = value
17
+ @input = value
20
18
  end
21
19
 
22
20
  def blocks
@@ -30,38 +28,42 @@ module Markdownplus
30
28
  blocks.select { |b| b.class == CodeBlock }
31
29
  end
32
30
 
33
- def includable_blocks
34
- code_blocks.select(&:includable?)
35
- end
36
-
37
31
  def executable_blocks
38
32
  code_blocks.select(&:executable?)
39
33
  end
40
34
 
41
- def markdown
42
- blocks.collect { |b| b.markdown }.join
35
+ def input_markdown
36
+ blocks.collect { |b| b.input_markdown }.join
37
+ end
38
+
39
+ def output_markdown
40
+ blocks.collect { |b| b.output_markdown }.join
43
41
  end
44
42
 
45
43
  def html
46
- markdown_renderer.render(markdown)
44
+ markdown_renderer.render(output_markdown)
47
45
  end
48
46
 
49
47
  def markdown_renderer
50
- Redcarpet::Markdown.new(Bootstrap2Renderer, fenced_code_blocks: true)
48
+ Redcarpet::Markdown.new(GithubRenderer, fenced_code_blocks: true)
51
49
  end
52
50
 
53
51
  def lines
54
- @lines ||= source.split("\n")
52
+ @lines ||= input.split("\n")
55
53
  end
56
54
 
57
- def errors
58
- blocks.collect { |b| b.errors }.flatten
55
+ def variables
56
+ @variables ||= {}
59
57
  end
60
58
 
61
59
  def warnings
62
60
  blocks.collect { |b| b.warnings }.flatten
63
61
  end
64
62
 
63
+ def errors
64
+ blocks.collect { |b| b.errors }.flatten
65
+ end
66
+
65
67
  def each_line(&block)
66
68
  if block_given?
67
69
  lines.each do |line|
@@ -71,45 +73,52 @@ module Markdownplus
71
73
  end
72
74
 
73
75
  def parse
76
+ block_number = 0
74
77
  each_line do |line|
75
78
  matcher = line.match(/\s*`{3,}\s*(\S*)\s*/)
76
79
  if matcher
77
- if self.current_block && self.current_block.class == CodeBlock
80
+ if self.current_block && self.current_block.is_a?(CodeBlock)
78
81
  self.blocks << self.current_block
79
82
  self.current_block = nil
83
+ block_number += 1
80
84
  else
81
85
  self.blocks << self.current_block if self.current_block
82
- self.current_block = CodeBlock.new(matcher[1])
86
+ self.current_block = CodeBlock.new(block_number, matcher[1])
83
87
  end
84
88
  else
85
- self.current_block ||= TextBlock.new
89
+ self.current_block ||= TextBlock.new(block_number)
86
90
  self.current_block.append line
87
91
  end
88
92
  end
89
93
  self.blocks << self.current_block if self.current_block
90
94
  end
91
95
 
92
- def include
93
- includable_blocks.each do |block|
94
- block.include
96
+ def execute
97
+ self.executable_blocks.each do |block|
98
+ block.execute(self.variables)
95
99
  end
96
100
  end
97
101
  end
98
102
 
99
103
  class Block
100
- def source
101
- @source ||= ""
104
+ attr_reader :block_number
105
+ def initialize(block_number)
106
+ @block_number = block_number
102
107
  end
103
- def source=(value)
104
- @source = value
108
+
109
+ def input
110
+ @input ||= ""
111
+ end
112
+ def input=(value)
113
+ @input = value
105
114
  end
106
115
 
107
116
  def lines
108
- self.source.split("\n")
117
+ self.input.split("\n")
109
118
  end
110
119
 
111
120
  def append(line)
112
- self.source += "#{line}\n"
121
+ self.input += "#{line}\n"
113
122
  end
114
123
 
115
124
  def errors
@@ -122,125 +131,66 @@ module Markdownplus
122
131
  end
123
132
 
124
133
  class TextBlock < Block
125
- def markdown
126
- self.source
134
+
135
+ def input_markdown
136
+ self.input
137
+ end
138
+
139
+ def output_markdown
140
+ self.input
127
141
  end
128
142
  end
129
143
 
130
144
  class CodeBlock < Block
131
- attr_reader :directives
145
+ attr_reader :directive, :program
146
+ attr_accessor :output
132
147
 
133
- def initialize(value=nil)
134
- @directives = value.split("|").collect{|v| v.strip} if value
135
- end
148
+ def initialize(block_number, value=nil)
149
+ @block_number = block_number
150
+ @directive = value
136
151
 
137
- def markdown
138
- s = source
139
- if s.end_with?("\n")
140
- result = "```#{directives.join("|")}\n#{source}```\n"
141
- else
142
- result = "```#{directives.join("|")}\n#{source}\n```\n"
152
+ if @directive.match(/\(/)
153
+ begin
154
+ @program ||= Markdownplus::DirectiveParser.parse(@directive)
155
+ rescue => e
156
+ errors << e.message
157
+ end
143
158
  end
144
159
  end
145
160
 
146
- def includable?
147
- first_directive == "include"
161
+ def functions
162
+ program.functions if program!=nil
148
163
  end
149
164
 
150
165
  def executable?
151
- first_directive == "execute"
166
+ (functions!=nil && functions.size>0)
152
167
  end
153
168
 
154
- def include
155
- if includable?
156
- if lines.count == 0
157
- self.warnings << "No url given"
158
- else
159
- self.warnings << "More than one line given" if lines.count > 1
160
- begin
161
- self.source = open(lines.first).read
162
- rescue => e
163
- self.errors << "Error opening [#{lines.first}] [#{e.message}]"
164
- end
169
+ def execute(variables)
170
+ self.output = self.input
171
+ if functions
172
+ self.functions.each do |function|
173
+ self.output = function.execute(self.output, variables, self.warnings, self.errors)
165
174
  end
166
- directives.shift
167
175
  end
176
+ self.output
168
177
  end
169
178
 
170
- def first_directive
171
- directives.first if directives
179
+ def input_markdown
180
+ s = input
181
+ if s.end_with?("\n")
182
+ result = "```#{directive}\n#{input}```\n"
183
+ else
184
+ result = "```#{directive}\n#{input}\n```\n"
185
+ end
172
186
  end
173
187
 
174
- # def method
175
- # matcher = directive.match(/(.*?)\((.*)\)/)
176
- # if matcher
177
- # method = matcher[1].strip
178
- # params = matcher[2].split(",").collect { |p| p.strip }
179
- # end
180
- # end
181
- end
188
+ def output_markdown
189
+ self.output
190
+ end
182
191
 
183
- # class Value
184
- # def self.parse(string)
185
- # items = string.split(",").collect do |item|
186
- # matcher = item.match(/(.*?)\((.*)\)/)
187
- # if matcher
188
- # name = matcher[1].strip
189
- # params = parse(matcher[2])
190
- # Method.new(name: name, params: params)
191
- # else
192
- # Param.new(item.strip)
193
- # end
194
- # end
195
- # end
196
- # end
197
-
198
- # class Param < Value
199
- # def initialize(v)
200
- # @value = value
201
- # end
202
-
203
- # def value
204
- # @value
205
- # end
206
- # end
207
-
208
- # class Method < Value
209
- # attr_reader :name
210
- # attr_reader :params
211
-
212
- # def initialize(opts={})
213
- # @name = opts[:name]
214
- # @params = opts[:params]
215
- # end
216
- # end
217
-
218
- class Bootstrap2Renderer < Redcarpet::Render::HTML
219
- # alias_method :existing_block_code, :block_code
220
- def block_code(code, language)
221
- if language == "csv"
222
- result = "<table class='table table-striped'>"
223
- row_num = 0
224
- CSV.parse(code) do |row|
225
- if row_num == 0
226
- result += "<thead><tr>#{row.collect { |c| "<th>#{c}</th>"}.join}</tr></thead>\n<tbody>\n"
227
- else
228
- result += "<tr>#{row.collect { |c| "<td>#{c}</td>"}.join}</tr>\n"
229
- end
230
- row_num += 1
231
- end
232
- result += "</tbody></table>"
233
- elsif language == "formatted_json"
234
- begin
235
- obj = JSON.parse(code)
236
- result = Pygments.highlight(JSON.pretty_generate(obj), lexer: "json")
237
- rescue => e
238
- result = Pygments.highlight(code, lexer: "json")
239
- end
240
- else
241
- result = Pygments.highlight(code, lexer: language)
242
- end
243
- result
192
+ def output_lines
193
+ self.output.split("\n") if self.output
244
194
  end
245
195
  end
246
196
  end
@@ -1,3 +1,3 @@
1
1
  module Markdownplus
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/markdownplus.gemspec CHANGED
@@ -24,4 +24,5 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_dependency "redcarpet"
26
26
  spec.add_dependency "pygments.rb"
27
+ spec.add_dependency "treetop"
27
28
  end
@@ -0,0 +1,275 @@
1
+ require 'markdownplus'
2
+ require 'spec_helper'
3
+
4
+ describe Markdownplus::DirectiveParser do
5
+ context "a simple function with no params" do
6
+ let(:value) { Markdownplus::DirectiveParser.parse("include()") }
7
+
8
+ it "should return a TransformationLiteral" do
9
+ expect(value.class).to eq(Markdownplus::Literals::TransformationLiteral)
10
+ end
11
+
12
+ it "should have a single function" do
13
+ expect(value.functions.count).to eq(1)
14
+ end
15
+
16
+ it "should have a function named 'include'" do
17
+ expect(value.functions.first.function_name).to eq("include")
18
+ end
19
+
20
+ it "should have no parameters" do
21
+ expect(value.functions.first.function_parameters.count).to eq(0)
22
+ end
23
+ end
24
+
25
+ context "a simple function with a single param" do
26
+ let(:value) { Markdownplus::DirectiveParser.parse("include(test)") }
27
+
28
+ it "should return a TransformationLiteral" do
29
+ expect(value.class).to eq(Markdownplus::Literals::TransformationLiteral)
30
+ end
31
+
32
+ it "should have a single function" do
33
+ expect(value.functions.count).to eq(1)
34
+ end
35
+
36
+ it "should have a function named 'include'" do
37
+ expect(value.functions.first.function_name).to eq("include")
38
+ end
39
+
40
+ it "should have a single parameter" do
41
+ expect(value.functions.first.function_parameters.count).to eq(1)
42
+ end
43
+
44
+ it "should have the parameter 'test'" do
45
+ expect(value.functions.first.function_parameters.first.to_s).to eq("test")
46
+ end
47
+ end
48
+ context "a simple function with a 3 params" do
49
+ let(:value) { Markdownplus::DirectiveParser.parse("include(test, 'this is a test', nil)") }
50
+
51
+ it "should return a TransformationLiteral" do
52
+ expect(value.class).to eq(Markdownplus::Literals::TransformationLiteral)
53
+ end
54
+
55
+ it "should have a single function" do
56
+ expect(value.functions.count).to eq(1)
57
+ end
58
+
59
+ it "should have a function named 'include'" do
60
+ expect(value.functions.first.function_name).to eq("include")
61
+ end
62
+
63
+ it "should have 3 parameters" do
64
+ expect(value.functions.first.function_parameters.count).to eq(3)
65
+ end
66
+
67
+ it "the first parameter should be correct" do
68
+ expect(value.functions.first.function_parameters[0].class).to eq(Markdownplus::Literals::SymbolLiteral)
69
+ expect(value.functions.first.function_parameters[0].to_s).to eq("test")
70
+ end
71
+
72
+ it "the second parameter should be correct" do
73
+ expect(value.functions.first.function_parameters[1].class).to eq(Markdownplus::Literals::StringLiteral)
74
+ expect(value.functions.first.function_parameters[1].to_s).to eq("this is a test")
75
+ end
76
+
77
+ it "the third parameter should be correct" do
78
+ expect(value.functions.first.function_parameters[2].class).to eq(Markdownplus::Literals::SymbolLiteral)
79
+ expect(value.functions.first.function_parameters[2].to_s).to eq("nil")
80
+ end
81
+ end
82
+
83
+ context "a series of two functions" do
84
+ let(:value) { Markdownplus::DirectiveParser.parse("include(), execute()") }
85
+
86
+ it "should return a TransformationLiteral" do
87
+ expect(value.class).to eq(Markdownplus::Literals::TransformationLiteral)
88
+ end
89
+
90
+ it "should have 2 functions" do
91
+ expect(value.functions.count).to eq(2)
92
+ end
93
+
94
+ context "the first function" do
95
+ let(:function) { value.functions[0] }
96
+
97
+ it "should be 'include'" do
98
+ expect(function.function_name).to eq("include")
99
+ end
100
+
101
+ it "should have no parameters" do
102
+ expect(function.function_parameters.count).to eq(0)
103
+ end
104
+ end
105
+
106
+ context "the second function" do
107
+ let(:function) { value.functions[1] }
108
+
109
+ it "should be 'include'" do
110
+ expect(function.function_name).to eq("execute")
111
+ end
112
+
113
+ it "should have no parameters" do
114
+ expect(function.function_parameters.count).to eq(0)
115
+ end
116
+ end
117
+ end
118
+
119
+ context "a function with a nested function" do
120
+ let(:value) { Markdownplus::DirectiveParser.parse("include(test, execute('nested test'))") }
121
+
122
+ it "should return a TransformationLiteral" do
123
+ expect(value.class).to eq(Markdownplus::Literals::TransformationLiteral)
124
+ end
125
+
126
+ it "should have a single function" do
127
+ expect(value.functions.count).to eq(1)
128
+ end
129
+
130
+ it "should have a function named 'include'" do
131
+ expect(value.functions.first.function_name).to eq("include")
132
+ end
133
+
134
+ it "should have 2 parameters" do
135
+ expect(value.functions.first.function_parameters.count).to eq(2)
136
+ end
137
+
138
+ context "the second function parameter" do
139
+ let(:param) { value.functions.first.function_parameters[1] }
140
+
141
+ it "should be a functionLiteral" do
142
+ expect(param.class).to eq(Markdownplus::Literals::FunctionLiteral)
143
+ end
144
+
145
+ it "should be a function named 'execute'" do
146
+ expect(param.function_name).to eq("execute")
147
+ end
148
+ end
149
+ end
150
+
151
+ context "a function with a url parameter" do
152
+ let(:value) { Markdownplus::DirectiveParser.parse("include('http://www.google.com')") }
153
+
154
+ it "should have a single function" do
155
+ expect(value.functions.count).to eq(1)
156
+ end
157
+
158
+ it "should have a function named 'include'" do
159
+ expect(value.functions.first.function_name).to eq("include")
160
+ end
161
+
162
+ it "should have 1 parameter" do
163
+ expect(value.functions.first.function_parameters.count).to eq(1)
164
+ end
165
+
166
+ context "the first parameter" do
167
+ let(:param) { value.functions.first.function_parameters.first }
168
+
169
+ it "should be google.com" do
170
+ expect(param.to_s).to eq("http://www.google.com")
171
+ end
172
+ end
173
+ end
174
+
175
+ context "a function with parameters that contain strange characters" do
176
+ let(:value) { Markdownplus::DirectiveParser.parse("include('this-is_a @test', so_is-this)") }
177
+
178
+ it "should have a single function" do
179
+ expect(value.functions.count).to eq(1)
180
+ end
181
+
182
+ it "should have a function named 'include'" do
183
+ expect(value.functions.first.function_name).to eq("include")
184
+ end
185
+
186
+ it "should have 2 parameters" do
187
+ expect(value.functions.first.function_parameters.count).to eq(2)
188
+ end
189
+
190
+ context "the first parameter" do
191
+ let(:param) { value.functions.first.function_parameters[0] }
192
+
193
+ it "should be correct" do
194
+ expect(param.to_s).to eq("this-is_a @test")
195
+ end
196
+ end
197
+
198
+ context "the second parameter" do
199
+ let(:param) { value.functions.first.function_parameters[1] }
200
+
201
+ it "should be correct" do
202
+ expect(param.to_s).to eq("so_is-this")
203
+ end
204
+ end
205
+ end
206
+
207
+ context "some specific cases" do
208
+ context "value 1" do
209
+ let(:value) { Markdownplus::DirectiveParser.parse("julia(dataFrame('my_variable', get('my_variable'))),strip_whitespace(),raw()") }
210
+
211
+ it "should parse" do
212
+ expect(value.class).to eq(Markdownplus::Literals::TransformationLiteral)
213
+ end
214
+
215
+ it "should have a three functions" do
216
+ expect(value.functions.count).to eq(3)
217
+ end
218
+
219
+ it "should have a function named 'julia'" do
220
+ expect(value.functions.first.function_name).to eq("julia")
221
+ end
222
+
223
+ it "should have 1 parameter" do
224
+ expect(value.functions.first.function_parameters.count).to eq(1)
225
+ end
226
+
227
+ context "the function parameter" do
228
+ let(:param) { value.functions.first.function_parameters.first }
229
+
230
+ it "should be a functionLiteral" do
231
+ expect(param.class).to eq(Markdownplus::Literals::FunctionLiteral)
232
+ end
233
+
234
+ it "should be a function named 'dataFrame'" do
235
+ expect(param.function_name).to eq("dataFrame")
236
+ end
237
+
238
+ it "should have 2 parameters" do
239
+ expect(param.function_parameters.count).to eq(2)
240
+ end
241
+
242
+ context "the first parameter" do
243
+ let(:param1) { param.function_parameters.first }
244
+ it "should be a String" do
245
+ expect(param1.class).to eq(Markdownplus::Literals::StringLiteral)
246
+ end
247
+
248
+ it "should equal 'my_variable'" do
249
+ expect(param1.to_s).to eq("my_variable")
250
+ end
251
+ end
252
+
253
+ context "the second parameter" do
254
+ let(:param2) { param.function_parameters[1] }
255
+ it "should be a functionLiteral" do
256
+ expect(param2.class).to eq(Markdownplus::Literals::FunctionLiteral)
257
+ end
258
+ it "should be a function named 'get'" do
259
+ expect(param2.function_name).to eq("get")
260
+ end
261
+ it "should have a parameter 'my_variable'" do
262
+ expect(param2.function_parameters.first.to_s).to eq("my_variable")
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ context "value 2" do
269
+ let(:value) { Markdownplus::DirectiveParser.parse("sql('postgres://username1:password2@ec2-12-345-678-9.compute-1.amazonaws.com:1234/database'),set('my_variable'),empty()") }
270
+ it "should parse" do
271
+ expect(value.class).to eq(Markdownplus::Literals::TransformationLiteral)
272
+ end
273
+ end
274
+ end
275
+ end