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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 59b5fb6c0a410a172db5e2aeba391eac6653eaff
4
- data.tar.gz: 42154e62a00f5ec65919c512ad6398be9498fc64
3
+ metadata.gz: b122d954d5f9a9b2fa74b04626054c7d4bb3fdd4
4
+ data.tar.gz: 812679f9e80a65de77281ec0d5f53b95038022cd
5
5
  SHA512:
6
- metadata.gz: c25d9c37887e2d41a4a3a12796fcb19badf60a4615c0e437c3ad2f5d1d0db6710a55b94f524c2fe76494d8c062180290c752309a374f9135c7bf8749724b575b
7
- data.tar.gz: 97334f94d603ac07051958b58a074703a798485943f9a5b1dccc556517c02eb3301d3c7addf5405040e44c3b1be45353cd7b1af0dd4c36dd47fed65e03163a6b
6
+ metadata.gz: 6506836776c5df3516be8187fe47441a0f0170133596b498d72ccf8046f0106ee49d1722631a52c84ec0db6a7ac98b8b20b75b1d04116c0ce3ba00d292bf893f
7
+ data.tar.gz: 6977a66ef13badcee4bf28594346313c66b0d01c8f516daabed6b718d7a8382fcb587546d1366b6bf2fd3330f55bdedf1d22da0f73d1f3307dfbc01efb538d36
data/README.md CHANGED
@@ -1,6 +1,141 @@
1
1
  # Markdownplus
2
2
 
3
- Coming soon. This is a prerelease gem.
3
+ Markdownplus extends [Github Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/) by bringing programmatic features to the language portion of the fenced code blocks. A Markdownplus document can be transformed into a valid Markdown document, or taken all the way to html.
4
+
5
+ ## Usage
6
+
7
+ ### Syntax
8
+
9
+ Markdownplus files look like normal [Github Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/) files. However, the fenced code blocks have most functionality. You can still use
10
+
11
+ ```markdown
12
+ ```ruby
13
+ ```
14
+
15
+ or
16
+
17
+ ```markdown
18
+ ```json
19
+ ```
20
+
21
+ to add syntax hightlight, but you can also use a pipeline of functions to bring in other files, or perform more drastic formatting.
22
+
23
+ For instance, you could download a json file, format it, then highlight it as usual using:
24
+
25
+ ```
26
+ ```include('https://gist.githubusercontent.com/cpetersen/c6571117df132443ac81/raw/e5ac97e8e0665a0e4014ebc85ecef214763a7729/fake.json'),pretty_json()
27
+ ```
28
+
29
+ or, you could download a csv file and turn it into an html table:
30
+
31
+ ```
32
+ ```include('https://gist.githubusercontent.com/cpetersen/b5a473ddf0b796cd9502/raw/e140bdc32ff2f6a600e357c2575220c0312a88ee/fake.csv'),csv2html()
33
+ ```
34
+
35
+ ### Execution
36
+
37
+ Given a markdown plus file, you can get the resulting Markdown using the following:
38
+
39
+ ```ruby
40
+ require 'markdownplus'
41
+
42
+ parser = Markdownplus::Parser.parse(File.read("kitchensink.mdp")); nil
43
+ parser.execute; nil
44
+ puts parser.output_markdown; nil
45
+ ```
46
+
47
+ If the resulting HTML is what you're interested in, you can use ```html```:
48
+
49
+ ```ruby
50
+ require 'markdownplus'
51
+
52
+ parser = Markdownplus::Parser.parse(File.read("kitchensink.mdp")); nil
53
+ parser.execute; nil
54
+ puts parser.html; nil
55
+ ```
56
+
57
+ ## Function Pipeline
58
+
59
+ The function pipeline is the heart of Markdownplus.
60
+
61
+ ### Functions
62
+
63
+ A function looks like:
64
+
65
+ ```
66
+ function_name()
67
+ function_name(symbol_parameter)
68
+ function_name("string parameter", 'other string parameter')
69
+ function_name(mix, 'and', match, "parameters")
70
+ function_name(you, "may pass", nested_methods("also"))
71
+ ```
72
+
73
+ The first function in the pipeline gets the contents of the fenced code block as input. For instance:
74
+
75
+ ```
76
+ ```pretty_json()
77
+ {"a":1,"b":2,"c":3}
78
+ ```
79
+
80
+ would get `{"a":1,"b":2,"c":3}` as the input variable and produce the following:
81
+
82
+ ```json
83
+ {
84
+ "a": 1,
85
+ "b": 2,
86
+ "c": 3
87
+ }
88
+ ```
89
+
90
+ ### Pipeline
91
+
92
+ You create pipeline, you string multiple functions together with a comma:
93
+
94
+ ```
95
+ ```include("some url"), csv2html()
96
+ ```
97
+
98
+ In this case, the first function (include) gets the contents of the fenced code block as input. The second function (csv2html) gets the output of the first function as input.
99
+
100
+ The output of the last function in the pipeline is used as the content when generating the `output_markdown` and ultimately the resulting `html`
101
+
102
+ ## Built in functions:
103
+
104
+ ### include()
105
+
106
+ `include` takes a single parameter, a url. It downloads this url and outputs the result.
107
+
108
+ ### csv2html()
109
+
110
+ `csv2html` takes no parameters, but expects valid CSV as input. It creates an HTML table from the given CSV.
111
+
112
+ ### pretty_json()
113
+
114
+ `pretty_json` takes no parameters, but expects valid JSON as input. It formats the JSON nicely using Ruby's `JSON.pretty_generate` and outputs a fenced code block with the language specified as `json`.
115
+
116
+ ### set('variable_name')
117
+
118
+ Stores the input in a `variable_name` for use later in the page.
119
+
120
+ ### get('variable_name')
121
+
122
+ Outputs the content stored at a the `variable_name`.
123
+
124
+ ### empty()
125
+
126
+ Hides the output. Often used with `set()`
127
+
128
+ ### raw()
129
+
130
+ Output the content as raw html and skip any markdown formatting that may apply.
131
+
132
+ ### strip_whitespace()
133
+
134
+ Strips any leading or trailing whitespace from the `input` and outputs the result.
135
+
136
+ ## Extendable
137
+
138
+
4
139
 
5
140
  ## Installation
6
141
 
@@ -23,7 +158,6 @@ Or install it yourself as:
23
158
  Use a fenced code block:
24
159
 
25
160
  ```markdown
26
- ```include|csv
27
- https://gist.githubusercontent.com/cpetersen/b5a473ddf0b796cd9502/raw/e140bdc32ff2f6a600e357c2575220c0312a88ee/fake.csv
161
+ ```include('https://gist.githubusercontent.com/cpetersen/b5a473ddf0b796cd9502/raw/e140bdc32ff2f6a600e357c2575220c0312a88ee/fake.csv'),csv()
28
162
  ```
29
163
  ```
data/lib/markdownplus.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  require "markdownplus/version"
2
+ require "markdownplus/github_renderer"
2
3
  require "markdownplus/parser"
4
+ require "markdownplus/literals"
5
+ require "markdownplus/directive_parser"
6
+ require "markdownplus/handler_registry"
7
+ require "markdownplus/handler"
3
8
 
4
9
  module Markdownplus
5
10
  # Your code goes here...
@@ -0,0 +1,17 @@
1
+ require 'treetop'
2
+
3
+ module Markdownplus
4
+ class DirectiveParser
5
+ def self.parse(data)
6
+ Treetop.load(File.expand_path("../directives", __FILE__))
7
+ @@parser ||= TransformationParser.new
8
+ tree = @@parser.parse(data)
9
+ # If the AST is nil then there was an error during parsing
10
+ # we need to report a simple error message to help the user
11
+ raise "Parse error at offset: #{@@parser.index}" if(tree.nil?)
12
+
13
+ return tree
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,33 @@
1
+ grammar Transformation
2
+ rule transformations
3
+ ( function / "" ) (',' function)* <Markdownplus::Literals::TransformationLiteral>
4
+ end
5
+
6
+ rule expression
7
+ ( function / single_quote_string / double_quote_string / symbol / "" ) (',' expression)* <Markdownplus::Literals::ExpressionLiteral>
8
+ end
9
+
10
+ rule function
11
+ symbol parameters <Markdownplus::Literals::FunctionLiteral>
12
+ end
13
+
14
+ rule parameters
15
+ "(" expression ")" <Markdownplus::Literals::ParensLiteral>
16
+ end
17
+
18
+ rule single_quote_string
19
+ space? "'" [a-zA-Z0-9\-_@ \t:\/\.]+ "'" space? <Markdownplus::Literals::StringLiteral>
20
+ end
21
+
22
+ rule double_quote_string
23
+ space? '"' [a-zA-Z0-9\-_@ \t:\/\.]+ '"' space? <Markdownplus::Literals::StringLiteral>
24
+ end
25
+
26
+ rule symbol
27
+ space? [a-zA-Z0-9\-_]+ space? <Markdownplus::Literals::SymbolLiteral>
28
+ end
29
+
30
+ rule space
31
+ [\s]+
32
+ end
33
+ end
@@ -0,0 +1,19 @@
1
+ require 'pygments'
2
+ require 'redcarpet'
3
+
4
+ module Markdownplus
5
+ class GithubRenderer < Redcarpet::Render::HTML
6
+ # alias_method :existing_block_code, :block_code
7
+ def block_code(code, language)
8
+ if language == "raw"
9
+ code
10
+ else
11
+ begin
12
+ Pygments.highlight(code, lexer: language)
13
+ rescue
14
+ "<pre><code>#{code}</code></pre>"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,120 @@
1
+ module Markdownplus
2
+ class Handler
3
+ def execute(input, parameters)
4
+ end
5
+ end
6
+
7
+ class IncludeHandler < Handler
8
+ def execute(input, parameters, variables, warnings, errors)
9
+ output = nil
10
+ warnings << "Include handler ignores input" if(input!=nil && !input.strip.empty?)
11
+ if parameters==nil
12
+ errors << "No url given"
13
+ elsif parameters.count == 0
14
+ errors << "No url given"
15
+ else
16
+ begin
17
+ output = IncludeHandler.cached(parameters.first.to_s)
18
+ rescue => e
19
+ errors << "Error opening [#{parameters.first}] [#{e.message}]"
20
+ end
21
+ end
22
+ output
23
+ end
24
+
25
+ @@cache = {}
26
+ def self.cached(url)
27
+ return @@cache[url] if @@cache[url]
28
+ @@cache[url] = open(url).read
29
+ @@cache[url]
30
+ end
31
+ end
32
+ HandlerRegistry.register("include", IncludeHandler)
33
+
34
+ class Csv2HtmlHandler < Handler
35
+ def execute(input, parameters, variables, warnings, errors)
36
+ output = "<table class='table table-striped'>"
37
+ row_num = 0
38
+ CSV.parse(input) do |row|
39
+ if row_num == 0
40
+ output += "<thead><tr>#{row.collect { |c| "<th>#{c}</th>"}.join}</tr></thead>\n<tbody>\n"
41
+ else
42
+ output += "<tr>#{row.collect { |c| "<td>#{c}</td>"}.join}</tr>\n"
43
+ end
44
+ row_num += 1
45
+ end
46
+ output += "</tbody></table>"
47
+ output
48
+ end
49
+ end
50
+ HandlerRegistry.register("csv2html", Csv2HtmlHandler)
51
+
52
+ class PrettyJsonHandler < Handler
53
+ def execute(input, parameters, variables, warnings, errors)
54
+ begin
55
+ obj = JSON.parse(input)
56
+ output = JSON.pretty_generate(obj)
57
+ rescue => e
58
+ output = input
59
+ errors << "Invalid json"
60
+ end
61
+ "```json\n#{output}\n```"
62
+ end
63
+ end
64
+ HandlerRegistry.register("pretty_json", PrettyJsonHandler)
65
+
66
+ class StripWhitespaceHandler < Handler
67
+ def execute(input, parameters, variables, warnings, errors)
68
+ input.gsub(/\s*\n\s*/,"\n")
69
+ end
70
+ end
71
+ HandlerRegistry.register("strip_whitespace", StripWhitespaceHandler)
72
+
73
+ class RawHandler < Handler
74
+ def execute(input, parameters, variables, warnings, errors)
75
+ "```raw\n#{input}\n```"
76
+ end
77
+ end
78
+ HandlerRegistry.register("raw", RawHandler)
79
+
80
+ class EmptyHandler < Handler
81
+ def execute(input, parameters, variables, warnings, errors)
82
+ ""
83
+ end
84
+ end
85
+ HandlerRegistry.register("empty", EmptyHandler)
86
+
87
+ ### START VARIABLES ###
88
+ class SetHandler < Handler
89
+ def execute(input, parameters, variables, warnings, errors)
90
+ if parameters==nil
91
+ errors << "No variable name given"
92
+ elsif parameters.count == 0
93
+ errors << "No variable name given"
94
+ else
95
+ warnings << "More than one variable name given [#{parameters.inspect}]" if parameters.count > 1
96
+ variables[parameters.first.to_s] = input
97
+ end
98
+ input
99
+ end
100
+ end
101
+
102
+ class GetHandler < Handler
103
+ def execute(input, parameters, variables, warnings, errors)
104
+ output = input
105
+ if parameters==nil
106
+ errors << "No variable name given"
107
+ elsif parameters.count == 0
108
+ errors << "No variable name given"
109
+ else
110
+ warnings << "More than one variable name given [#{parameters.inspect}]" if parameters.count > 1
111
+ output = variables[parameters.first.to_s]
112
+ end
113
+ output
114
+ end
115
+ end
116
+
117
+ HandlerRegistry.register("set", SetHandler)
118
+ HandlerRegistry.register("get", GetHandler)
119
+ #### END VARIABLES ####
120
+ end
@@ -0,0 +1,17 @@
1
+ module Markdownplus
2
+ class HandlerRegistry
3
+ @@registry = {}
4
+
5
+ def self.handler_instance(name)
6
+ handler_class(name).new if handler_class(name)
7
+ end
8
+
9
+ def self.handler_class(name)
10
+ @@registry[name]
11
+ end
12
+
13
+ def self.register(name, handler)
14
+ @@registry[name] = handler
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,101 @@
1
+ require 'treetop'
2
+
3
+ module Markdownplus
4
+ module Literals
5
+ class ExpressionLiteral < Treetop::Runtime::SyntaxNode
6
+ def functions
7
+ _functions(self.elements).flatten.compact
8
+ end
9
+
10
+ def _functions(elements)
11
+ return unless elements
12
+ results = elements.select { |e| e.class==Markdownplus::Literals::FunctionLiteral }
13
+ elements.each do |element|
14
+ if [Treetop::Runtime::SyntaxNode, Markdownplus::Literals::ExpressionLiteral, Markdownplus::Literals::TransformationLiteral].include?(element.class)
15
+ results << _functions(element.elements)
16
+ end
17
+ end
18
+ results
19
+ end
20
+ def symbols
21
+ self.elements.select { |e| e.class==SymbolLiteral }
22
+ end
23
+ def parens
24
+ self.elements.select { |e| e.class==ParensLiteral }
25
+ end
26
+ end
27
+
28
+ class TransformationLiteral < ExpressionLiteral
29
+ #Specific subclass, the root should only match this
30
+ end
31
+
32
+ class FunctionLiteral < ExpressionLiteral
33
+ def function_name
34
+ self.symbols[0].text_value.strip
35
+ end
36
+
37
+ def function_parameters
38
+ self.parens.first.function_parameters
39
+ end
40
+
41
+ def function_parameter_values(input, variables, warnings, errors)
42
+ self.parens.first.function_parameters.collect { |fp| fp.value(input, variables, warnings, errors) }
43
+ end
44
+
45
+ def execute(input, variables, warnings, errors)
46
+ handler = HandlerRegistry.handler_instance(self.function_name)
47
+ if handler
48
+ output = handler.execute(input, self.function_parameter_values(nil, variables, warnings, errors), variables, warnings, errors)
49
+ else
50
+ errors << "No handler defined for [#{self.function_name}]"
51
+ end
52
+ output
53
+ end
54
+
55
+ def value(input, variables, warnings, errors)
56
+ execute(input, variables, warnings, errors)
57
+ end
58
+
59
+ end
60
+
61
+ class ParensLiteral < ExpressionLiteral
62
+ def function_parameters
63
+ self.find_parameters(self.elements)
64
+ end
65
+
66
+ def find_parameters(elements, params=[])
67
+ return params unless elements
68
+ elements.each do |element|
69
+ if [StringLiteral, SymbolLiteral, FunctionLiteral].include?(element.class)
70
+ params << element
71
+ else
72
+ find_parameters(element.elements, params)
73
+ end
74
+ end
75
+ return params
76
+ end
77
+ end
78
+
79
+ class StringLiteral < ExpressionLiteral
80
+ def to_s
81
+ v = self.text_value.strip
82
+ v[1..v.length-2]
83
+ end
84
+
85
+ def value(input, variables, warnings, errors)
86
+ to_s
87
+ end
88
+ end
89
+
90
+ class SymbolLiteral < ExpressionLiteral
91
+ def to_s
92
+ self.text_value.strip
93
+ end
94
+
95
+ def value(input, variables, warnings, errors)
96
+ to_s
97
+ end
98
+ end
99
+ end
100
+ end
101
+