markdown_prawn 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.pattern = './test/*.rb'
7
+ t.verbose = true
8
+ end
9
+ Rake::Task['test'].comment = "Test Markdown Prawn"
10
+
11
+ task :default => [:test]
data/bin/md2pdf ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Markdown to PDF by Ryan Stenhouse <ryan@ryanstenhouse.eu>
4
+ # November 1st 2010
5
+ #
6
+ # Takes input from standard in, expected to be a markdown document
7
+ # and renders a PDF to standard out.
8
+ #
9
+ root = File.expand_path(File.dirname(__FILE__)+ '/../')
10
+ load root + '/markdown_prawn.rb'
11
+ content = $stdin.read
12
+ puts MarkdownPrawn::StringParser.new(content).to_pdf.render
@@ -0,0 +1,20 @@
1
+ class HeadingFragment < MarkdownFragment
2
+ attr_accessor :level
3
+
4
+ def render_on(pdf_object, options = {})
5
+ arguments = _default_render_options.merge(options)
6
+ pdf_object.move_down(@level * 2)
7
+ pdf_object.text @content.join(' '), arguments
8
+ pdf_object.move_down(@level * 2)
9
+ end
10
+
11
+ private
12
+
13
+ def _default_render_options
14
+ options = { :size => (40 - (8*@level)), :align => :left, :leading => 2, :weight => :bold }
15
+ if Prawn::VERSION =~ /^0.1/ || Prawn::VERSION =~ /^1/
16
+ options.merge({:inline_format => true})
17
+ end
18
+ options
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ class HorizontalRuleFragment < MarkdownFragment
2
+
3
+ def render_on(pdf_object, options = {})
4
+ pdf_object.move_down(3)
5
+ old_width = pdf_object.line_width
6
+ pdf_object.line_width = 3
7
+ pdf_object.horizontal_rule
8
+ pdf_object.stroke
9
+ pdf_object.line_width = old_width
10
+ pdf_object.move_down(3)
11
+ end
12
+
13
+ private
14
+
15
+ def _default_render_options
16
+ options = { :size => 12, :align => :left, :leading => 2 }
17
+ if Prawn::VERSION =~ /^0.1/ || Prawn::VERSION =~ /^1/
18
+ options = options.merge({:inline_format => true})
19
+ else
20
+ options = options.merge({:inline_format => false})
21
+ end
22
+ options
23
+ end
24
+
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'net/http'
2
+ require 'tmpdir'
3
+
4
+ class ImageFragment < MarkdownFragment
5
+ def render_on(pdf_object)
6
+ if is_remote_uri?
7
+ filename = @content.first.split('/').last
8
+ file_path = "#{Dir.tmpdir}/#{filename}"
9
+ content = Net::HTTP.get(URI.parse(@content.first))
10
+ File.open(file_path, 'w') do |f|
11
+ f.puts content
12
+ end
13
+ else
14
+ file_path = File.expand_path(@content.first)
15
+ end
16
+ pdf_object.image file_path
17
+ end
18
+
19
+ private
20
+
21
+ def is_remote_uri?
22
+ !/^(http:|https:)/.match(@content.first).nil?
23
+ end
24
+
25
+ end
@@ -0,0 +1,17 @@
1
+ class LinksReferenceFragment < MarkdownFragment
2
+
3
+ def render_on(pdf_object)
4
+
5
+ pdf_object.move_down(10)
6
+ pdf_object.horizontal_rule
7
+ pdf_object.stroke
8
+ pdf_object.move_down(4)
9
+ pdf_object.text "Hyperlink References:"
10
+ pdf_object.move_down(4)
11
+ pdf_object.table @content do
12
+ cells.borders = []
13
+ end
14
+ pdf_object.move_down(4)
15
+ end
16
+
17
+ end
@@ -0,0 +1,40 @@
1
+ class ListFragment < MarkdownFragment
2
+ attr_accessor :ordered
3
+
4
+ def render_on(pdf_object, options = {})
5
+ bullet = '• '
6
+ arguments = _default_render_options.merge(options)
7
+ width = ((pdf_object.bounds.width / 100) * 90)
8
+ data = []
9
+
10
+ @content.each_with_index do |item, i|
11
+ # Strip any un-needed white space
12
+ #
13
+ item = item.gsub(/\s\s+/,' ')
14
+ if ordered?
15
+ bullet = "#{i+1}."
16
+ end
17
+ data << [bullet,item]
18
+ end
19
+
20
+ pdf_object.table data, arguments.merge({:width => width}) do
21
+ cells.borders = []
22
+ end
23
+ pdf_object.move_down(5)
24
+ end
25
+
26
+ def ordered?
27
+ @ordered == true
28
+ end
29
+
30
+ private
31
+
32
+ def _default_render_options
33
+ options = {}
34
+ if Prawn::VERSION =~ /^0.1/ || Prawn::VERSION =~ /^1/
35
+ options = options.merge({:cell_style => { :inline_format => true}})
36
+ end
37
+ options
38
+ end
39
+
40
+ end
@@ -0,0 +1,13 @@
1
+ class MarkdownFragment
2
+ attr_accessor :content
3
+
4
+ def initialize(content = [])
5
+ @content = content
6
+ end
7
+
8
+ # Renders the current fragment on the supplied prawn PDF Object. By Default,
9
+ # it will just join content and add it as text - not too useful.
10
+ def render_on(pdf_object)
11
+ pdf_object.text @content.join(' ')
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ class ParagraphFragment < MarkdownFragment
2
+
3
+ def render_on(pdf_object, options = {})
4
+ arguments = _default_render_options.merge(options)
5
+ pdf_object.move_down(3)
6
+ pdf_object.text @content.join(' '), arguments
7
+ end
8
+
9
+ private
10
+
11
+ def _default_render_options
12
+ options = { :size => 12, :align => :left, :leading => 2 }
13
+ if Prawn::VERSION =~ /^0.1/ || Prawn::VERSION =~ /^1/
14
+ options = options.merge({:inline_format => true})
15
+ else
16
+ options = options.merge({:inline_format => false})
17
+ end
18
+ options
19
+ end
20
+
21
+ end
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/markdown_fragments/markdown_fragment.rb'
2
+ require File.dirname(__FILE__) + '/markdown_fragments/paragraph_fragment.rb'
3
+ require File.dirname(__FILE__) + '/markdown_fragments/heading_fragment.rb'
4
+ require File.dirname(__FILE__) + '/markdown_fragments/list_fragment.rb'
5
+ require File.dirname(__FILE__) + '/markdown_fragments/image_fragment.rb'
6
+ require File.dirname(__FILE__) + '/markdown_fragments/horizontal_rule_fragment.rb'
7
+ require File.dirname(__FILE__) + '/markdown_fragments/links_reference_fragment.rb'
@@ -0,0 +1,18 @@
1
+ module MarkdownPrawn
2
+ class FileParser < Parser
3
+
4
+ # Give this the path to a file and (if it exists), it'll generate
5
+ # a PDF version of the markdown there.
6
+ #
7
+ def initialize(file_path)
8
+ file_path = File.expand_path(file_path)
9
+ if !File.exist?(file_path)
10
+ raise Errno::ENOENT.new("#{file_path} could not be found") and return
11
+ else
12
+ @content = detab(IO.read(file_path).gsub(/\r\n?/, "\n")).split("\n")
13
+ end
14
+ super
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,213 @@
1
+ require File.dirname(__FILE__) + '/../markdown_fragments.rb'
2
+
3
+ module MarkdownPrawn
4
+
5
+ # Horribly bodgy and wrong markdown parser which should just about do
6
+ # for a proof of concept. Some of the code comes from mislav's original
7
+ # BlueCloth since I cant find the source of a newer versoin.
8
+ #
9
+ class Parser
10
+ attr_accessor :links_list, :images_list, :document_structure
11
+
12
+ def initialize(document_structure = [])
13
+ @links_list = { :urls_seen => [], :object => LinksReferenceFragment.new }
14
+ @document_structure = []
15
+ @images_list = []
16
+ end
17
+
18
+ # Returns a Prawn::Document, accepts the same options as Prawn does when creating
19
+ # a new document, but defaults to creating pages in Portrait and at A4 size.
20
+ #
21
+ # Uses parse! rather than parse to ensure that the slate is always clean when
22
+ # generating the PDF.
23
+ #
24
+ def to_pdf(options = {})
25
+ parse!
26
+ options = options.merge({ :page_layout => :portrait, :page_size => 'A4' })
27
+ pdf = Prawn::Document.new(options)
28
+ @document_structure.each { |markdown_fragment| markdown_fragment.render_on(pdf) }
29
+ pdf
30
+ end
31
+
32
+ # Clears out the current sate of +@document_structure+ and then parses +@content+ content
33
+ #
34
+ def parse!
35
+ @document_structure = []
36
+ parse
37
+ end
38
+
39
+ def parse
40
+ paragraph = ParagraphFragment.new
41
+ list = ListFragment.new
42
+ in_list = false
43
+ @content.each_with_index do |line, index|
44
+ line = process_inline_formatting(line)
45
+
46
+ # Assume everything is part of a paragraph by default and
47
+ # add its content to the current in-scope paragraph object.
48
+ #
49
+ paragraph.content << line
50
+ if line == ""
51
+ unless paragraph.content.empty?
52
+ @document_structure << paragraph
53
+ paragraph = ParagraphFragment.new
54
+ end
55
+ end
56
+
57
+ # Deal with inline headings
58
+ #
59
+ unless /^(#+)(\s?)\S/.match(line).nil?
60
+ paragraph.content = paragraph.content.delete_if { |i| i == line }
61
+ hashes = $1.dup
62
+ heading = HeadingFragment.new([line.gsub(hashes,'')])
63
+ heading.level = hashes.length
64
+ @document_structure << heading
65
+ end
66
+
67
+ # Deal with Level 1 Headings
68
+ #
69
+ if !/^(=)+$/.match(line).nil?
70
+ paragraph.content = paragraph.content.delete_if do |item|
71
+ item == line || item == @content[index - 1]
72
+ end
73
+ heading = HeadingFragment.new([@content[index - 1]])
74
+ heading.level = 1
75
+ @document_structure << heading
76
+ end
77
+
78
+ # Deal with Level 2 Headings or horizontal rules.
79
+ #
80
+ if !/^(-)+$/.match(line).nil?
81
+ if @content[index - 1].strip == ''
82
+ # Assume it's a horizontal rule
83
+ #
84
+ paragraph.content = paragraph.content.delete_if { |i| i == line }
85
+ @document_structure << HorizontalRuleFragment.new
86
+ else
87
+ paragraph.content = paragraph.content.delete_if do |item|
88
+ item == line || item == @content[index - 1]
89
+ end
90
+ heading = HeadingFragment.new([@content[index - 1]])
91
+ heading.level = 2
92
+ @document_structure << heading
93
+ end
94
+ end
95
+
96
+ # Deal with all other kinds of horizontal rules
97
+ #
98
+ if !/^(\*+)(\s)?(\*+)(\s)?(\*+)/.match(line).nil? || !/^(-+)(\s)(-+)(\s)(-+)/.match(line).nil?
99
+ if @content[index - 1].strip == ''
100
+ paragraph.content = paragraph.content.delete_if { |i| i == line }
101
+ @document_structure << HorizontalRuleFragment.new
102
+ end
103
+ end
104
+
105
+ # Try to deal with lists.
106
+ #
107
+ if in_list
108
+ # We're in a list just now.
109
+ #
110
+
111
+ # Remove the content from the paragraph where it will have
112
+ # automatically been appended
113
+ #
114
+ paragraph.content = paragraph.content.delete_if { |i| i == line }
115
+
116
+ # Check to see if we've got a new list item.
117
+ #
118
+ if (!/^\s+\*\s/.match(line).nil? || !/^\s+\d+\.\s/.match(line).nil?)
119
+
120
+ # Find out if this new list item is for a different type of list
121
+ # and deal with that before adding the new list item.
122
+ #
123
+ if list.ordered? && !/^\s+\*\s/.match(line).nil?
124
+ @document_structure << list
125
+ list = ListFragment.new
126
+ elsif !list.ordered? && !/^\s+\d+\.\s/.match(line).nil?
127
+ @document_structure << list
128
+ list = ListFragment.new
129
+ list.ordered = true
130
+ end
131
+
132
+ # Remove the list style and add the new list item.
133
+ #
134
+ list.content << line.sub(/^\s+\*\s/,'').sub(/^\s+\d+\.\s/,'')
135
+
136
+ else
137
+ # If this line isn't a new list item, then it's a continuation for the current
138
+ # list item.
139
+ #
140
+ list.content[-1] += line
141
+ end
142
+
143
+ # If the current line is empty, then we're done with the list.
144
+ #
145
+ if line == ''
146
+ @document_structure << list
147
+ list = ListFragment.new
148
+ in_list = false
149
+ end
150
+ else
151
+ # Not currently in a list, but we've detected a list item
152
+ #
153
+ if (!/^\s+\*\s/.match(line).nil? || !/^\s+\d+\.\s/.match(line).nil?)
154
+ ordered = false
155
+ ordered = true if !/^\s+\d+\.\s/.match(line).nil?
156
+ list = ListFragment.new
157
+ list.ordered = ordered
158
+ list.content << line.sub(/^\s+\*\s/,'').sub(/^\s+\d+\.\s/,'')
159
+ paragraph.content = paragraph.content.delete_if { |i| i == line }
160
+ in_list = true
161
+ end
162
+ end
163
+
164
+
165
+ # Deal with a link reference by adding it ot the list of references
166
+ #
167
+ if !/^(\[\S+\]){1}:\s(\S+)\s?(.+)?/.match(line).nil?
168
+ reference, url, title = $1, $2, $3
169
+ paragraph.content = paragraph.content.delete_if { |i| i == line }
170
+ @links_list[:urls_seen] << url
171
+ @links_list[:object].content << [ reference, url, "#{title}" ]
172
+ end
173
+
174
+ # Deal with inline images
175
+ #
176
+ line.scan(/(?:^|\s)?(\!\[(?:.+?)\]\((.+?)\))/) do |val|
177
+ line.gsub(val[0],'')
178
+ @document_structure << ImageFragment.new([val[1]])
179
+ end
180
+ end
181
+ if !list.content.empty? && ! @document_structure.include?(list)
182
+ @document_structure << list
183
+ list = ListFragment.new
184
+ end
185
+ @document_structure << paragraph unless paragraph.content == ''
186
+ @document_structure << @links_list[:object] if !@links_list[:urls_seen].empty?
187
+ end
188
+
189
+ private
190
+
191
+ def detab(string, tabwidth = 2)
192
+ string.split("\n").collect { |line|
193
+ line.gsub(/(.*?)\t/) do
194
+ $1 + ' ' * (tabwidth - $1.length % tabwidth)
195
+ end
196
+ }.join("\n")
197
+ end
198
+
199
+ # Only do Inline formatting for versions of Prawn which support it.
200
+ #
201
+ def process_inline_formatting(str)
202
+ breg = [ %r{ \b(\_\_) (\S|\S.*?\S) \1\b }x, %r{ (\*\*) (\S|\S.*?\S) \1 }x ]
203
+ ireg = [ %r{ (\*) (\S|\S.*?\S) \1 }x, %r{ \b(_) (\S|\S.*?\S) \1\b }x ]
204
+ if Prawn::VERSION =~ /^0.1/ || Prawn::VERSION =~ /^1/
205
+ str.gsub(breg[0], %{<b>\\2</b>} ).gsub(breg[1], %{<b>\\2</b>} ).gsub(ireg[0], %{<i>\\2</i>} ).gsub(ireg[1], %{<i>\\2</i>} )
206
+ else
207
+ str.gsub(breg[0], %{\\2} ).gsub(breg[1], %{\\2} ).gsub(ireg[0], %{\\2} ).gsub(ireg[1], %{\\2} )
208
+ end
209
+ end
210
+
211
+ end
212
+
213
+ end
@@ -0,0 +1,11 @@
1
+ module MarkdownPrawn
2
+ class StringParser < Parser
3
+ # Take a string or an object which responds to to_s and attemps to
4
+ # parse markdown in it.
5
+ def initialize(string_to_convert)
6
+ string_to_convert = string_to_convert.to_s
7
+ @content = detab(string_to_convert.gsub(/\r\n?/, "\n")).split("\n")
8
+ super
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/markdown_fragments.rb'
2
+ require File.dirname(__FILE__) + '/markdown_parser/parser.rb'
3
+ require File.dirname(__FILE__) + '/markdown_parser/file_parser.rb'
4
+ require File.dirname(__FILE__) + '/markdown_parser/string_parser.rb'
5
+
@@ -0,0 +1,6 @@
1
+ module MarkdownPrawn
2
+ module Exceptions
3
+ class InvalidMarkdownFile < Exception; end
4
+ class InvalidDestinationPath < Exception; end
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ description = "Markdown Parawn is a library and an executable strip which allow you to generate a PDF from any valid Markdown."
2
+ Gem::Specification.new do |spec|
3
+ spec.name = "markdown_prawn"
4
+ spec.version = '0.0.1.pre'
5
+ spec.platform = Gem::Platform::RUBY
6
+ spec.files = Dir.glob("{bin,lib,test}/**/**/*") +
7
+ ["Rakefile", "markdown_prawn.gemspec"]
8
+ spec.require_path = "lib"
9
+ spec.required_ruby_version = '>= 1.8.7'
10
+ spec.required_rubygems_version = ">= 1.3.6"
11
+
12
+ spec.test_files = Dir[ "test/*_test.rb" ]
13
+ spec.has_rdoc = false
14
+ spec.author = "Ryan Stenhouse"
15
+ spec.email = "ryan@ryanstenhouse.eu"
16
+ spec.rubyforge_project = "markdown_prawn"
17
+ spec.add_dependency('prawn', '~>0.10')
18
+ spec.homepage = "http://ryanstenhouse.eu"
19
+ spec.summary = description
20
+ spec.description = description
21
+ end
@@ -0,0 +1,3 @@
1
+ 1. One Item
2
+ 2. Two Item
3
+ 3. Three Item
@@ -0,0 +1,8 @@
1
+ Paragaphs are a fundamental part of Markdown and
2
+ this makes sure that I can process them properly
3
+ even with
4
+ strange wrapping and
5
+ other
6
+ annoyances
7
+
8
+ This should be seen as the second paragraph.
@@ -0,0 +1,3 @@
1
+ * Bulleted
2
+ * List
3
+ * Support
data/test/helper.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'prawn'
3
+ require 'test/unit'
4
+ require File.dirname(__FILE__) + '/../markdown_prawn.rb'
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) + '/helper.rb'
2
+
3
+ class TestListsHandling < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @unordered = MarkdownPrawn::FileParser.new(File.expand_path(File.dirname(__FILE__) + '/fixtures/unordered_lists.mdown'))
7
+ @ordered = MarkdownPrawn::FileParser.new(File.expand_path(File.dirname(__FILE__) + '/fixtures/ordered_lists.mdown'))
8
+ end
9
+
10
+ def test_document_structure_is_correct_for_unordered_lists
11
+ @unordered.parse
12
+ assert !@unordered.document_structure.empty?
13
+ assert_equal ListFragment, @unordered.document_structure[0].class
14
+ assert_equal false, @unordered.document_structure[0].ordered?
15
+ end
16
+
17
+ def test_document_structure_is_correct_for_ordered_lists
18
+ @ordered.parse
19
+ assert !@ordered.document_structure.empty?
20
+ assert_equal ListFragment, @ordered.document_structure[0].class
21
+ assert_equal true, @ordered.document_structure[0].ordered?
22
+ end
23
+
24
+ end
@@ -0,0 +1,18 @@
1
+ require File.dirname(__FILE__) + '/helper.rb'
2
+
3
+ class TestParagraphHandling < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @paragraphs = MarkdownPrawn::FileParser.new(File.expand_path(File.dirname(__FILE__) + '/fixtures/paragraphs.mdown'))
7
+ end
8
+
9
+ def test_document_structure_is_correct_for_paragraphs
10
+ @paragraphs.parse
11
+ assert !@paragraphs.document_structure.empty?
12
+ assert_equal 2, @paragraphs.document_structure.nitems
13
+ @paragraphs.document_structure.each do |fragment|
14
+ assert_equal ParagraphFragment, fragment.class
15
+ end
16
+ end
17
+
18
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: markdown_prawn
3
+ version: !ruby/object:Gem::Version
4
+ hash: 961915968
5
+ prerelease: true
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ - pre
11
+ version: 0.0.1.pre
12
+ platform: ruby
13
+ authors:
14
+ - Ryan Stenhouse
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-11-01 00:00:00 +00:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: prawn
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ hash: 31
31
+ segments:
32
+ - 0
33
+ - 10
34
+ version: "0.10"
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: Markdown Parawn is a library and an executable strip which allow you to generate a PDF from any valid Markdown.
38
+ email: ryan@ryanstenhouse.eu
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - bin/md2pdf
47
+ - lib/markdown_fragments.rb
48
+ - lib/markdown_parser/parser.rb
49
+ - lib/markdown_parser/file_parser.rb
50
+ - lib/markdown_parser/string_parser.rb
51
+ - lib/markdown_parser.rb
52
+ - lib/markdown_fragments/paragraph_fragment.rb
53
+ - lib/markdown_fragments/markdown_fragment.rb
54
+ - lib/markdown_fragments/image_fragment.rb
55
+ - lib/markdown_fragments/heading_fragment.rb
56
+ - lib/markdown_fragments/links_reference_fragment.rb
57
+ - lib/markdown_fragments/list_fragment.rb
58
+ - lib/markdown_fragments/horizontal_rule_fragment.rb
59
+ - lib/markdown_prawn_exceptions.rb
60
+ - test/test_lists_handling.rb
61
+ - test/test_paragraph_handling.rb
62
+ - test/fixtures/ordered_lists.mdown
63
+ - test/fixtures/paragraphs.mdown
64
+ - test/fixtures/unordered_lists.mdown
65
+ - test/helper.rb
66
+ - Rakefile
67
+ - markdown_prawn.gemspec
68
+ has_rdoc: true
69
+ homepage: http://ryanstenhouse.eu
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options: []
74
+
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 57
83
+ segments:
84
+ - 1
85
+ - 8
86
+ - 7
87
+ version: 1.8.7
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 23
94
+ segments:
95
+ - 1
96
+ - 3
97
+ - 6
98
+ version: 1.3.6
99
+ requirements: []
100
+
101
+ rubyforge_project: markdown_prawn
102
+ rubygems_version: 1.3.7
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: Markdown Parawn is a library and an executable strip which allow you to generate a PDF from any valid Markdown.
106
+ test_files: []
107
+