pasta 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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +57 -0
  6. data/Rakefile +9 -0
  7. data/bin/pasta +40 -0
  8. data/lib/pasta.rb +18 -0
  9. data/lib/pasta/cookbook.rb +13 -0
  10. data/lib/pasta/dish.rb +62 -0
  11. data/lib/pasta/error.rb +3 -0
  12. data/lib/pasta/ingredients.rb +49 -0
  13. data/lib/pasta/ingredients/anything.rb +12 -0
  14. data/lib/pasta/ingredients/atx_headings.rb +21 -0
  15. data/lib/pasta/ingredients/automatic_links.rb +14 -0
  16. data/lib/pasta/ingredients/blockquotes.rb +23 -0
  17. data/lib/pasta/ingredients/code.rb +15 -0
  18. data/lib/pasta/ingredients/codeblocks.rb +19 -0
  19. data/lib/pasta/ingredients/emphasis.rb +14 -0
  20. data/lib/pasta/ingredients/empty_lines.rb +13 -0
  21. data/lib/pasta/ingredients/html_entities.rb +17 -0
  22. data/lib/pasta/ingredients/links.rb +58 -0
  23. data/lib/pasta/ingredients/lists.rb +31 -0
  24. data/lib/pasta/ingredients/page_breaks.rb +14 -0
  25. data/lib/pasta/ingredients/paragraphs.rb +27 -0
  26. data/lib/pasta/ingredients/setext_headings.rb +20 -0
  27. data/lib/pasta/recipes/base.rb +40 -0
  28. data/lib/pasta/recipes/gruber.rb +27 -0
  29. data/lib/pasta/recipes/markdown.rb +11 -0
  30. data/lib/pasta/version.rb +3 -0
  31. data/pasta.gemspec +24 -0
  32. data/specs/base_spec.rb +8 -0
  33. data/specs/dish_spec.rb +30 -0
  34. data/specs/dsl_spec.rb +12 -0
  35. data/specs/recipes/gruber/automatic_links.yml +12 -0
  36. data/specs/recipes/gruber/blockquotes.yml +112 -0
  37. data/specs/recipes/gruber/code.yml +48 -0
  38. data/specs/recipes/gruber/codeblocks.yml +47 -0
  39. data/specs/recipes/gruber/emphasis.yml +57 -0
  40. data/specs/recipes/gruber/gruber_spec.rb +22 -0
  41. data/specs/recipes/gruber/headers.yml +88 -0
  42. data/specs/recipes/gruber/images.yml +49 -0
  43. data/specs/recipes/gruber/links.yml +184 -0
  44. data/specs/recipes/gruber/lists.yml +171 -0
  45. data/specs/recipes/gruber/page_breaks.yml +31 -0
  46. data/specs/recipes/gruber/paragraphs.yml +44 -0
  47. data/specs/spec_helper.rb +3 -0
  48. metadata +149 -0
@@ -0,0 +1,13 @@
1
+ module Pasta
2
+ module Ingredients
3
+
4
+ EMPTY_LINE = /\A\s*[\n\r]+/
5
+
6
+ def empty_lines(text, html)
7
+ text.sub!(EMPTY_LINE) do |match|
8
+ match = ''
9
+ end
10
+ [text, html]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module Pasta
2
+ module Ingredients
3
+
4
+ HTML_ENTITIES = {
5
+ '&' => '&'
6
+ }
7
+
8
+ def html_entities(text, html)
9
+ HTML_ENTITIES.each do |entity, transformation|
10
+ html = text.gsub(/[#{entity}]/) do |match|
11
+ match = transformation
12
+ end
13
+ end
14
+ [text, html]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,58 @@
1
+ module Pasta
2
+ module Ingredients
3
+
4
+ # Parses both links and images
5
+ # $1 => "!", $2 => link text, $4 => inline url, $5 => reference id
6
+ LINKS = /([!]*)\[(.+?)\](\((.+?)\)|\s?\[(.*?)\])/
7
+
8
+ LINK_DEFINITION = /^ {0,3}\[([\w\s]+?)\]: (.+?)(^\s*[\n\r]|\z)/m
9
+
10
+ def links(text, html)
11
+ find_link_definitions(text)
12
+
13
+ text.gsub!(LINKS) do |match|
14
+ link_text, target = $2, ''
15
+ if $4
16
+ # It's an inline link
17
+ target = $4
18
+ else
19
+ # It's a reference link
20
+ id = $5 == '' ? $2.downcase : $5.downcase
21
+ break unless @link_definitions && @link_definitions.key?(id)
22
+ target = @link_definitions[id][:target]
23
+ end
24
+ # format image or link
25
+ url, title = parse_link target
26
+ if $1 == '!'
27
+ match = "<img src=\"#{url}\" alt=\"#{link_text}\"#{title} />"
28
+ else
29
+ match = "<a href=\"#{url}\"#{title}>#{link_text}</a>"
30
+ end
31
+ end
32
+
33
+ text = clean_link_definitions(text)
34
+ [text, html]
35
+ end
36
+
37
+ def find_link_definitions(text)
38
+ @link_definitions = {}
39
+ text.gsub(LINK_DEFINITION) do |match|
40
+ @link_definitions[$1.downcase] = {target: $2, used: false}
41
+ end
42
+ end
43
+
44
+ def clean_link_definitions(text)
45
+ @link_definitions.each do |key,val|
46
+ text.gsub!(/^ {0,3}\[#{key}\]: (.+?)(^\s*[\n\r]|\z)/mi, '')
47
+ end
48
+ text
49
+ end
50
+
51
+ def parse_link(text)
52
+ target = text.split(' ')
53
+ url = target[0].gsub(/[<>]/, '')
54
+ title = " title=#{target[1..-1].join(' ').strip}" if target.length > 1
55
+ [url, title]
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,31 @@
1
+ module Pasta
2
+ module Ingredients
3
+
4
+ LIST = /\A\s{0,3}(([-*+]|([0-9]+)\.) .+?)((^\s*[\n\r])|\z)/m
5
+
6
+ def lists(text, html)
7
+ text.sub!(LIST) do |match|
8
+ inner_text, inner_html, list_type = $1, '', 'ul'
9
+
10
+ # determine the list_type
11
+ list_type = 'ol' unless %w{- * +}.include? $1.strip[0]
12
+
13
+ # extract list items
14
+ inner_html = list_items(inner_text)
15
+
16
+ html << "<#{list_type}>\n#{inner_html}</#{list_type}>\n\n"
17
+ match = ''
18
+ end
19
+ [text, html]
20
+ end
21
+
22
+ def list_items(text)
23
+ html = ''
24
+ text.gsub(/^([-*+]|\d+\.) (.+?)$(?=([\n\r]([*+-]|(\d+\.))|\z))/m) do |match|
25
+ html << "<li>#{$2.gsub(/\n\s+/, ' ').strip}</li>\n"
26
+ match = ''
27
+ end
28
+ html
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ module Pasta
2
+ module Ingredients
3
+
4
+ PAGE_BREAK = /\A([*-_]\s?){3,}$/
5
+
6
+ def page_breaks(text, html)
7
+ text.sub!(PAGE_BREAK) do |match|
8
+ html << "<hr />"
9
+ match = ''
10
+ end
11
+ [text, html]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ module Pasta
2
+ module Ingredients
3
+
4
+ PARAGRAPH = /\A(?![>])(.+?)((^\s*[\n\r])|\z)/m
5
+
6
+ def paragraphs(text, html)
7
+ text.sub!(PARAGRAPH) do |match|
8
+
9
+ inner_text, inner_html = $1, ''
10
+ inner_text.gsub!(/[ ]{2,}[\n\r]/, "<br />\n")
11
+ inner_text.strip!
12
+
13
+ if grammars.key? :paragraphs
14
+ grammars[:paragraphs].each do |rule|
15
+ inner_text, inner_html = send(rule, inner_text, '')
16
+ end
17
+ inner_text = inner_html unless inner_html == ''
18
+ end
19
+
20
+ html << "<p>#{inner_text}</p>\n\n" unless inner_text == ''
21
+ match = ''
22
+ end
23
+ [text, html]
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ module Pasta
2
+ module Ingredients
3
+
4
+ SETEXT_HEADING = /\A(.+?)[\n\r]([=|-]+)([\n\r]|\z)/
5
+
6
+ def setext_headings(text, html)
7
+ text.sub!(SETEXT_HEADING) do |match|
8
+ if grammars.key? :heading
9
+ grammars[:heading].each do |rule|
10
+ inner = send(rule, $1, '')
11
+ end
12
+ end
13
+ level = ($2[0] == '=') ? 1 : 2
14
+ html << "<h#{level}>#{$1}</h#{level}>\n" unless $1.nil?
15
+ match = ''
16
+ end
17
+ [text, html]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ require 'pasta/ingredients'
2
+
3
+ module Pasta
4
+ module Recipes
5
+ class Base
6
+
7
+ include Pasta::Ingredients
8
+
9
+ # Public. Generic to_x parser that converts a string to the specified output format.
10
+ # Uses a depth first strategy to parse a text according to incredient rules as defined in a grammar
11
+ # Raises a Pasta::Error if a grammar has not been defined
12
+ #
13
+ # text - a String to be parsed
14
+ #
15
+ # Example
16
+ # to_html('some yummy text')
17
+ # # => '<p>some yummy text</p>'
18
+ #
19
+ # Returns a String
20
+ def method_missing(method, *args, &block)
21
+ if method.to_s =~ /^to_(.+)$/
22
+ # ensure a grammar has been defined
23
+ raise Pasta::Error.new "A grammar for #{$1} has not been defined in recipe #{self.class.name}" if grammars($1.to_sym).nil?
24
+
25
+ # Depth first parsing - each ingredient should accept and return an input and output
26
+ input, output = args[0], ''
27
+ while input != "" do
28
+ grammars($1.to_sym).each do |rule|
29
+ input, output = send(rule, input, output)
30
+ end
31
+ end
32
+ output.strip # remove any trailing whitespace
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ module Pasta
2
+ module Recipes
3
+ class Gruber < Base
4
+
5
+ # Define the grammars
6
+ grammar(:html) {
7
+ [:automatic_links, :links, :empty_lines, :page_breaks, :codeblocks, :blockquotes, :lists, :atx_headings, :setext_headings, :paragraphs]
8
+ }
9
+ grammar(:blockquotes) {
10
+ [:empty_lines, :page_breaks, :codeblocks, :blockquotes, :lists, :atx_headings, :setext_headings, :paragraphs]
11
+ }
12
+ grammar(:lists) {
13
+ [:empty_lines, :codeblocks, :blockquotes, :lists, :atx_headings, :setext_headings, :paragraphs]
14
+ }
15
+ grammar(:atx_headings) {
16
+ [:emphasis]
17
+ }
18
+ grammar(:setext_headings) {
19
+ [:emphasis]
20
+ }
21
+ grammar(:paragraphs) {
22
+ [:code, :emphasis]
23
+ }
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ module Pasta
2
+ module Recipes
3
+ class Markdown < Gruber
4
+
5
+ # This is currently identical to the Gruber recipe.
6
+ # TODO: Extract the specs from http://www.w3.org/community/markdown/ and rewrite the grammar
7
+
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Pasta
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pasta/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pasta"
8
+ spec.version = Pasta::VERSION
9
+ spec.authors = ["Dave Kinkead"]
10
+ spec.email = ["dave@kinkead.com.au"]
11
+ spec.description = %q{À la carte markdown parsing}
12
+ spec.summary = %q{Pasta is an extensible markdown text parser with a delicious menu of options}
13
+ spec.homepage = "https://github.com/davekinkead"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|specs|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "markdown-testsuite"
24
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pasta::Recipes::Base do
4
+ let(:base) { Pasta::Recipes::Base.new }
5
+
6
+ specify { ->{ base.to_nothing }.must_raise Pasta::Error }
7
+ specify { ->{ base.to_html 'some yummy text' }.must_raise Pasta::Error }
8
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ describe Pasta::Dish do
4
+
5
+ specify { ->{ Pasta::Dish.new }.must_raise ArgumentError }
6
+
7
+ let(:spaghetti) { Pasta::Dish.new 'Long, thin, cylindrical strings of durum' }
8
+
9
+ specify { spaghetti.must_be_kind_of Pasta::Dish }
10
+ specify { spaghetti.must_respond_to :with }
11
+ specify { spaghetti.must_respond_to :make }
12
+
13
+ let(:fettuccine) { Pasta::Dish.new 'Flat, thick, strings of egg and durum'}
14
+
15
+ describe "#with" do
16
+ it "raises an error if a recipe can't be found" do
17
+ ->{ fettuccine.with(:unknown) }.must_raise Pasta::Error
18
+ end
19
+ end
20
+
21
+ describe "#make" do
22
+ it "raises an error if a recipe can't be found" do
23
+ ->{ fettuccine.make(:unknown) }.must_raise Pasta::Error
24
+ end
25
+
26
+ it "converts the sauce text into a string" do
27
+ fettuccine.make(:html).must_be_kind_of String
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ require "spec_helper"
2
+
3
+ describe Pasta do
4
+ specify { Pasta.must_respond_to :prepare }
5
+ specify { ->{ Pasta.prepare }.must_raise ArgumentError }
6
+ specify { Pasta.prepare('some yummy text').must_be_kind_of Pasta::Dish }
7
+ specify { Pasta.prepare('a handful of text').with(:gruber).make(:html).must_equal '<p>a handful of text</p>' }
8
+
9
+ it "uses :markdown as the default recipe" do
10
+ Pasta.prepare('thick, soft balls of flour and egg').make(:html).must_equal '<p>thick, soft balls of flour and egg</p>'
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ overview: |
2
+ Markdown supports a shortcut style for creating “automatic” links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link.
3
+
4
+ http://daringfireball.net/projects/markdown/syntax
5
+ tests:
6
+ - name: Automatic Links
7
+ desc: URL inside html tags are automatically parsed
8
+ text: |
9
+ <http://example.com/>
10
+ html: |
11
+ <p><a href="http://example.com/">http://example.com/</a></p>
12
+
@@ -0,0 +1,112 @@
1
+ overview: |
2
+ Markdown uses email-style > characters for blockquoting. If you’re familiar with quoting passages of text in an email message, then you know how to create a blockquote in Markdown. It looks best if you hard wrap the text and put a > before every line:
3
+
4
+ > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
5
+ > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
6
+ > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
7
+ >
8
+ > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
9
+ > id sem consectetuer libero luctus adipiscing.
10
+
11
+ Markdown allows you to be lazy and only put the > before the first line of a hard-wrapped paragraph:
12
+
13
+ > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
14
+ consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
15
+ Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
16
+
17
+ > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
18
+ id sem consectetuer libero luctus adipiscing.
19
+
20
+ Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by adding additional levels of >:
21
+
22
+ > This is the first level of quoting.
23
+ >
24
+ > > This is nested blockquote.
25
+ >
26
+ > Back to the first level.
27
+
28
+ Blockquotes can contain other Markdown elements, including headers, lists, and code blocks:
29
+
30
+ > ## This is a header.
31
+ >
32
+ > 1. This is the first list item.
33
+ > 2. This is the second list item.
34
+ >
35
+ > Here's some example code:
36
+ >
37
+ > return shell_exec("echo $input | $markdown_script");
38
+
39
+ Any decent text editor should make email-style quoting easy. For example, with BBEdit, you can make a selection and choose Increase Quote Level from the Text menu.
40
+
41
+ http://daringfireball.net/projects/markdown/syntax
42
+ tests:
43
+ - name: Single line block quote
44
+ desc: Quoting a single line of text
45
+ text: |
46
+ > This is a snazzy quote
47
+ html: |
48
+ <blockquote>
49
+ <p>This is a snazzy quote</p>
50
+ </blockquote>
51
+
52
+ - name: Double line block quote
53
+ desc: Quoting a two paragraphs of text
54
+ text: |
55
+ > This is a snazzy quote
56
+ >
57
+ > It's longer than you think!
58
+ html: |
59
+ <blockquote>
60
+ <p>This is a snazzy quote</p>
61
+
62
+ <p>It's longer than you think!</p>
63
+ </blockquote>
64
+
65
+ - name: Lazy double quotes
66
+ desc: Quoting two lines of text with only lead >
67
+ text: |
68
+ > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
69
+ consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
70
+ Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
71
+ html: |
72
+ <blockquote>
73
+ <p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
74
+ consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
75
+ Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
76
+ </blockquote>
77
+
78
+ - name: Multiple blockquotes
79
+ desc: Using multiple blockquotes in succession
80
+ text: |
81
+ > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
82
+ consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
83
+
84
+ > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
85
+ html: |
86
+ <blockquote>
87
+ <p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
88
+ consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.</p>
89
+ </blockquote>
90
+
91
+ <blockquote>
92
+ <p>Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
93
+ </blockquote>
94
+
95
+ - name: Nested blockquotes
96
+ desc: Blockquotes can contain blockquotes
97
+ text: |
98
+ > This is the first level of quoting.
99
+ >
100
+ > > This is nested blockquote.
101
+ >
102
+ > Back to the first level.
103
+ html: |
104
+ <blockquote>
105
+ <p>This is the first level of quoting.</p>
106
+
107
+ <blockquote>
108
+ <p>This is nested blockquote.</p>
109
+ </blockquote>
110
+
111
+ <p>Back to the first level.</p>
112
+ </blockquote>