pasta 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +57 -0
- data/Rakefile +9 -0
- data/bin/pasta +40 -0
- data/lib/pasta.rb +18 -0
- data/lib/pasta/cookbook.rb +13 -0
- data/lib/pasta/dish.rb +62 -0
- data/lib/pasta/error.rb +3 -0
- data/lib/pasta/ingredients.rb +49 -0
- data/lib/pasta/ingredients/anything.rb +12 -0
- data/lib/pasta/ingredients/atx_headings.rb +21 -0
- data/lib/pasta/ingredients/automatic_links.rb +14 -0
- data/lib/pasta/ingredients/blockquotes.rb +23 -0
- data/lib/pasta/ingredients/code.rb +15 -0
- data/lib/pasta/ingredients/codeblocks.rb +19 -0
- data/lib/pasta/ingredients/emphasis.rb +14 -0
- data/lib/pasta/ingredients/empty_lines.rb +13 -0
- data/lib/pasta/ingredients/html_entities.rb +17 -0
- data/lib/pasta/ingredients/links.rb +58 -0
- data/lib/pasta/ingredients/lists.rb +31 -0
- data/lib/pasta/ingredients/page_breaks.rb +14 -0
- data/lib/pasta/ingredients/paragraphs.rb +27 -0
- data/lib/pasta/ingredients/setext_headings.rb +20 -0
- data/lib/pasta/recipes/base.rb +40 -0
- data/lib/pasta/recipes/gruber.rb +27 -0
- data/lib/pasta/recipes/markdown.rb +11 -0
- data/lib/pasta/version.rb +3 -0
- data/pasta.gemspec +24 -0
- data/specs/base_spec.rb +8 -0
- data/specs/dish_spec.rb +30 -0
- data/specs/dsl_spec.rb +12 -0
- data/specs/recipes/gruber/automatic_links.yml +12 -0
- data/specs/recipes/gruber/blockquotes.yml +112 -0
- data/specs/recipes/gruber/code.yml +48 -0
- data/specs/recipes/gruber/codeblocks.yml +47 -0
- data/specs/recipes/gruber/emphasis.yml +57 -0
- data/specs/recipes/gruber/gruber_spec.rb +22 -0
- data/specs/recipes/gruber/headers.yml +88 -0
- data/specs/recipes/gruber/images.yml +49 -0
- data/specs/recipes/gruber/links.yml +184 -0
- data/specs/recipes/gruber/lists.yml +171 -0
- data/specs/recipes/gruber/page_breaks.yml +31 -0
- data/specs/recipes/gruber/paragraphs.yml +44 -0
- data/specs/spec_helper.rb +3 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2c8fdd32cce14e905c1580e6af1c06da7d2f59a0
|
4
|
+
data.tar.gz: b005f8e3e02b5bd0535e6280fb8394341d5df30f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b8ba1e99b022691f6552459d2a4031384077b831834de4dd19dbbfc940aa9978ab8beef93be68ba5f94479d843e0ba9df78a1f67837224f69f400ea7ed19343a
|
7
|
+
data.tar.gz: fad438c988da4bb8c0987e9276b68a6c0e89748dcef143662e456cb8fd8802d35faed0bc35097426cde031e49ad18150d327c44b3d0bd81464ad8939907fe9dd
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Dave Kinkead
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Pasta
|
2
|
+
|
3
|
+
### À la carte markdown parsing.
|
4
|
+
|
5
|
+
There's no such thing as markdown, only markdowns. Multi-Markdown, Github Flavoured, Pandoc, or Stack Overflow style...each implementation makes subtle changes to Gruber's original in order to better address the needs of their users.
|
6
|
+
|
7
|
+
It might be nice to have one true standard but that just ain't gonna happen. And it shouldn't. One of the great strengths of writing in plain text is that it can easily be adapted to specific needs through different flavours or dialects.
|
8
|
+
|
9
|
+
Pasta allows you to choose the flavour of markdown that best meets your needs, or to whip a new one up as needed.
|
10
|
+
|
11
|
+
## Ingredients
|
12
|
+
|
13
|
+
- 1 pasta gem
|
14
|
+
- A handful of text
|
15
|
+
- A sprig of file references
|
16
|
+
|
17
|
+
## Directions
|
18
|
+
|
19
|
+
Ensure your pasta gem is ready to go with
|
20
|
+
|
21
|
+
$ gem install pasta
|
22
|
+
|
23
|
+
Then use Pasta as a tasty addition to your ruby code:
|
24
|
+
|
25
|
+
Pasta.prepare('a handful of text').with(:markdown).make(:html)
|
26
|
+
|
27
|
+
Alternatively, you can use Pasta as a binary:
|
28
|
+
|
29
|
+
$ pasta a-tasty-file.txt --recipe gruber
|
30
|
+
|
31
|
+
## Recipes
|
32
|
+
|
33
|
+
Choose a recipe from the menu or make your own - extensibility is Pasta's _raison d'être_.
|
34
|
+
|
35
|
+
- **Gruber** The original but rarely the best.
|
36
|
+
- **Markdown** The default according to the http://www.w3.org/community/markdown/ spec
|
37
|
+
|
38
|
+
## Works in Progress
|
39
|
+
|
40
|
+
- **Github** Github flavoured markdown.
|
41
|
+
- **Latin** Markdown for scholars.
|
42
|
+
- **Reveal** Generate slide decks from markdown.
|
43
|
+
|
44
|
+
## Rolling your own
|
45
|
+
|
46
|
+
Creating your own recipe for a markdown flavour is simple. First up, write a test suite for your recipe and add it to the `specs` directory. Use the yaml format like the others.
|
47
|
+
|
48
|
+
Next, extend `Pasta::Recipe::Base` and use the DSL to declare your recipe's `grammar`. For example:
|
49
|
+
|
50
|
+
grammar(:html) { [:headings, :paragraphs] }
|
51
|
+
grammar(:pdf) { [:meta, :headings, :paragraphs] }
|
52
|
+
|
53
|
+
Each grammar rule creates a `to_x` method and invokes the code in `Pasta::Ingredients` of the same name. You can either create your own ingredients or use existing ones. Extensibility is the name of the game here.
|
54
|
+
|
55
|
+
Grammars are order sensitive and use a depth-first strategy allowing you to nest rules.
|
56
|
+
|
57
|
+
Finally, add your recipe to the `Pasta::Menu` so in order to create a delicious new `Pasta::Dish`.
|
data/Rakefile
ADDED
data/bin/pasta
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'pasta'
|
5
|
+
|
6
|
+
options = {recipe: :gruber, output: :html}
|
7
|
+
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
opts.banner = "Usage: pasta [sauce] [options]"
|
10
|
+
opts.summary_indent = ' '*4
|
11
|
+
|
12
|
+
opts.separator ""
|
13
|
+
opts.separator "The Pasta Menu:"
|
14
|
+
opts.separator ""
|
15
|
+
|
16
|
+
opts.on("-h", "--help", "Show the menu") do
|
17
|
+
p opts.summarize('', 5, 72)
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("-v", "--version", "Show the version") do
|
22
|
+
p Pasta::VERSION
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("-r", "--recipe [RECIPE]", String, "Specify a recipe") do |recipe|
|
27
|
+
options[:recipe] = recipe.to_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
end.parse!
|
31
|
+
|
32
|
+
begin
|
33
|
+
while ARGV.length > 1
|
34
|
+
ARGV.pop
|
35
|
+
end
|
36
|
+
p Pasta.prepare(ARGF.read).with(options[:recipe]).make(:html)
|
37
|
+
rescue Pasta::Error => e
|
38
|
+
$stderr.puts "Error: #{e.message}"
|
39
|
+
exit(1)
|
40
|
+
end
|
data/lib/pasta.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'pasta/dish'
|
2
|
+
|
3
|
+
module Pasta
|
4
|
+
|
5
|
+
# Public: Instantiates a new Pasta::Dish object with text to be parsed.
|
6
|
+
#
|
7
|
+
# sauce - the text to be parsed
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# Pasta.prepare 'some yummy text'
|
12
|
+
# # => #<Pasta::Dish:0x007ff232948938>
|
13
|
+
#
|
14
|
+
# Returns a Pasta::Dish
|
15
|
+
def self.prepare(sauce)
|
16
|
+
Pasta::Dish.new sauce
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'pasta/version'
|
2
|
+
require 'pasta/error'
|
3
|
+
|
4
|
+
module Pasta
|
5
|
+
module Recipes
|
6
|
+
|
7
|
+
autoload :Base, 'pasta/recipes/base' # The granddaddy of them all. All recipes should extend Base
|
8
|
+
autoload :Gruber, 'pasta/recipes/gruber' # The original but rarely the best
|
9
|
+
autoload :Markdown, 'pasta/recipes/markdown' # Markdown as per the http://www.w3.org/community/markdown/ spec
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
data/lib/pasta/dish.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'pasta/cookbook'
|
2
|
+
|
3
|
+
module Pasta
|
4
|
+
class Dish
|
5
|
+
|
6
|
+
# Public: The source document that will be the subject of parsing.
|
7
|
+
attr_reader :sauce
|
8
|
+
|
9
|
+
|
10
|
+
# Public: Creates a new Pasta::Dish.
|
11
|
+
#
|
12
|
+
# sauce - The String document to be parsed. Required
|
13
|
+
#
|
14
|
+
# Example
|
15
|
+
# Pasta::Dish.new 'Some yummy text to parse'
|
16
|
+
#
|
17
|
+
# Returns a new Pasta::Dish.
|
18
|
+
def initialize(sauce)
|
19
|
+
@sauce = sauce
|
20
|
+
@recipe = Pasta::Recipes::Markdown.new
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# Public: Looks up a recipe from the menu
|
25
|
+
#
|
26
|
+
# recipe - A Symbol specifying the recipe to use when parsing and outputing
|
27
|
+
#
|
28
|
+
# Example
|
29
|
+
# with(:markdown)
|
30
|
+
#
|
31
|
+
# Returns Self
|
32
|
+
def with(recipe)
|
33
|
+
@receipe = check_cookbook_for recipe
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Public. Converts the sauce to an output according to a recipe. Raises a Pasta::Error is the recipe
|
39
|
+
# doesn't have a to_x method
|
40
|
+
#
|
41
|
+
# Example
|
42
|
+
# make(:html)
|
43
|
+
# # => '<p>....</p>'
|
44
|
+
#
|
45
|
+
# Returns a String of the converted root
|
46
|
+
def make(output)
|
47
|
+
@recipe.send "to_#{output}", @sauce
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
|
54
|
+
# Private. Checks the cookbook for a recipe, raising an error if not found.
|
55
|
+
#
|
56
|
+
# Returns the @recipe
|
57
|
+
def check_cookbook_for recipe
|
58
|
+
raise Pasta::Error.new "Recipe '#{recipe}' wasn't found in the cookbook" unless Recipes.const_defined? "#{recipe.to_s.capitalize}"
|
59
|
+
Recipes.const_get("#{recipe.to_s.capitalize}").new
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/pasta/error.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Pasta
|
2
|
+
module Ingredients
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
# Public. Returns the grammars of a recipe
|
9
|
+
#
|
10
|
+
# name - the name of a grammar rule as a Symbol
|
11
|
+
#
|
12
|
+
# Example
|
13
|
+
# grammars(:html)
|
14
|
+
# # => <Proc:>
|
15
|
+
#
|
16
|
+
# Returns a Proc containing grammar rules
|
17
|
+
def grammars(name=nil)
|
18
|
+
return name.nil? ? self.class.grammars : self.class.grammars[name]
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
@@grammars = {}
|
23
|
+
|
24
|
+
def grammars
|
25
|
+
@@grammars
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public. The DSL for declaring grammar rules in a recipe.
|
29
|
+
#
|
30
|
+
# Recipes contain grammars that are defined in ordered arrays. Grammars are recursively matched in a top down
|
31
|
+
# fashion where the array elements match ingredients. Grammar matching is done on a first match basis so that
|
32
|
+
# the last rule can be a catch-all one.
|
33
|
+
#
|
34
|
+
# name - a Symbol naming the grammar
|
35
|
+
# rules - a block defining the grammar
|
36
|
+
#
|
37
|
+
# Example
|
38
|
+
# grammar(:html) { [:blockquotes, :paragraphs] }
|
39
|
+
# grammar(:blockquote) { [:blockquote, :paragraphs] }
|
40
|
+
# grammar(:paragraph) { [:links, :emphasis, :bold, :text] }
|
41
|
+
#
|
42
|
+
# Returns nothing
|
43
|
+
def grammar(name, &rules)
|
44
|
+
rules.call.each { |rule| require "pasta/ingredients/#{rule.to_s}" }
|
45
|
+
@@grammars[name] = rules.call
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Pasta
|
2
|
+
module Ingredients
|
3
|
+
|
4
|
+
ATX_HEADING = /\A(\#{1,6})(.+?)\#*($|\z)/
|
5
|
+
|
6
|
+
def atx_headings(text, html)
|
7
|
+
text.sub!(ATX_HEADING) do |match|
|
8
|
+
inner = $2
|
9
|
+
if grammars.key? :heading
|
10
|
+
grammars[:heading].each do |rule|
|
11
|
+
inner, text = send(rule, $2, '')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
html << "<h#{$1.length}>#{inner.strip}</h#{$1.length}>\n" unless $2.nil?
|
15
|
+
match = ''
|
16
|
+
end
|
17
|
+
[text, html]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Pasta
|
2
|
+
module Ingredients
|
3
|
+
|
4
|
+
AUTOMATIC_LINKS = /(?<!\]: )<(.+?):\/\/(.+?)>/
|
5
|
+
|
6
|
+
def automatic_links(text, html)
|
7
|
+
text.gsub!(AUTOMATIC_LINKS) do |match|
|
8
|
+
url = "#{$1}://#{$2}"
|
9
|
+
match = "<a href=\"#{url}\">#{url}</a>"
|
10
|
+
end
|
11
|
+
[text, html]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Pasta
|
2
|
+
module Ingredients
|
3
|
+
|
4
|
+
BLOCKQUOTE = /\A {0,3}(>.+?)(^\s*[\n\r]|\z)/m
|
5
|
+
|
6
|
+
def blockquotes(text, html)
|
7
|
+
text.sub!(BLOCKQUOTE) do |match|
|
8
|
+
inner_text, inner_html = $1, ''
|
9
|
+
inner_text.gsub!(/^>/, '')
|
10
|
+
while inner_text != '' do
|
11
|
+
if grammars.key? :blockquotes
|
12
|
+
grammars[:blockquotes].each do |rule|
|
13
|
+
inner_text, inner_html = send(rule, inner_text, inner_html)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
html << "<blockquote>\n#{inner_html.strip}\n</blockquote>\n\n"
|
18
|
+
match = ''
|
19
|
+
end
|
20
|
+
[text, html]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Pasta
|
2
|
+
module Ingredients
|
3
|
+
|
4
|
+
CODEBLOCK = /\A( {4,}|\t)(.+?)(^\s{0,3}[\n\r]|\z)/m
|
5
|
+
|
6
|
+
def codeblocks(text, html)
|
7
|
+
text.sub!(CODEBLOCK) do |match|
|
8
|
+
require 'cgi'
|
9
|
+
# clean the leading whitespace from the block and escape html
|
10
|
+
new_text = $2
|
11
|
+
new_text.gsub!(/^#{$1}/, '')
|
12
|
+
new_text = CGI::escapeHTML new_text
|
13
|
+
html << "<pre><code>#{new_text}</code></pre>\n\n"
|
14
|
+
match = ''
|
15
|
+
end
|
16
|
+
[text, html]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Pasta
|
2
|
+
module Ingredients
|
3
|
+
|
4
|
+
EMPHASIS = /(?!\\)([*_]{1,2})(?!\s)(.+?)(?!\s)(?!\\)\1/
|
5
|
+
|
6
|
+
def emphasis(text, html)
|
7
|
+
html = text.gsub(EMPHASIS) do |match|
|
8
|
+
tag = $1.length == 1 ? 'em' : 'strong'
|
9
|
+
match = "<#{tag}>#{$2}</#{tag}>"
|
10
|
+
end
|
11
|
+
[text, html]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|