conversio 0.1.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.
data/HISTORY.md ADDED
@@ -0,0 +1,8 @@
1
+ ## 0.1.1
2
+
3
+ * finishing Kramdown support
4
+ * adding a couple of templates shipped with the gem
5
+
6
+ ## 0.1.0
7
+
8
+ * First release
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ Conversio by Jörg Behrendt and Victor Penso
2
+
3
+ # Description
4
+
5
+ Renders plain text files with [Markdown][1] syntax to XHTML pages.
6
+ User can define their own Ruby ERB templates to customize the
7
+ XHTML page generation. Also the creation of a table of content
8
+ using the HTML header elements (like `<h1>`) and the syntax
9
+ high-lighting of code snippets is supported.
10
+
11
+ ## Installation
12
+
13
+ Conversio RubyGem:
14
+
15
+ gem install conversio
16
+
17
+ Syntax high-lighting is done with Pyhton [Pygments][2]:
18
+
19
+ easy_install pygments
20
+
21
+ ## Usage Examples
22
+
23
+ Take a look to the help text:
24
+
25
+ conversio -h
26
+
27
+ Convert all files called `*.markdown` inside a defined directory
28
+ and all sub-directories into HTML and store the in the destination
29
+ directory.
30
+
31
+ conversio ~/docs/path/to/files ~/public/path
32
+
33
+ Create a single `readme.html` file including a table of content by
34
+ using the 'dark' template:
35
+
36
+ conversio -t -p dark readme.markdown
37
+
38
+ ## License
39
+
40
+ GPLv3 - see the COPYING file.
41
+
42
+ [1]: http://daringfireball.net/projects/markdown/
43
+ [2]: http://pygments.org/
data/bin/conversio ADDED
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $DEBUG = false
4
+
5
+ require 'rubygems'
6
+ require 'erb'
7
+ require 'yaml'
8
+ require 'fileutils'
9
+ require 'pathname'
10
+ require 'getoptlong'
11
+ require 'ostruct'
12
+ require 'conversio'
13
+
14
+ include Conversio
15
+
16
+ help = <<EOF
17
+ Synopsis
18
+ --------
19
+
20
+ #{File.split(__FILE__)[-1]}: Renders Markdown plain text files to HTML
21
+
22
+ Purpose
23
+ -------
24
+
25
+ Uses Ruby ERB Templates to generate XHTML documents rendered from Markdown
26
+ plain text files.
27
+
28
+ Usage
29
+ -----
30
+
31
+ #{File.split(__FILE__)[-1]} [OPTIONS] SRC [DST]
32
+
33
+ SRC: File or directory containing the Markdown formated plain text
34
+ DST: Target directory for the XHTML output.
35
+
36
+ Options
37
+ -------
38
+
39
+ --config:
40
+
41
+ Creates a personal configuration file in ~/.conversiorc
42
+
43
+ -c, --colorize:
44
+
45
+ Enable syntax high-lighting for marked code blocks.
46
+
47
+ -e, --engine:
48
+
49
+ Select the Markdown parser to be used:
50
+ * 'bluecloth' (default)
51
+ * 'kramdown'
52
+
53
+ -f, --template-file FILE:
54
+
55
+ FILE containing an ERB template with:
56
+ * '<%= content %>' to mark the postion inside the body tag
57
+ to place the passed in content.
58
+ * '<%= style %>' to mark the position for placing CSS.
59
+
60
+ -h, --help:
61
+
62
+ Show this help documentation.
63
+
64
+ -i, --ignore-config:
65
+
66
+ Don't read the configuration from ~/.conversiorc
67
+
68
+ -l, --list-templates:
69
+
70
+ Show a list of all available templates.
71
+
72
+ -t, --table-of-content:
73
+
74
+ Enables the creation of a table of content.
75
+
76
+ -p, --template NAME:
77
+
78
+ Select a specific template to be used.
79
+
80
+ -v, --verbose:
81
+
82
+ Print more verbose output.
83
+ EOF
84
+
85
+ config = <<EOF
86
+ colorize: true
87
+ table_of_content: true
88
+ template_file:
89
+ engine:
90
+ EOF
91
+
92
+ # -------------------------------------------------------------
93
+ # helper functions
94
+ # -------------------------------------------------------------
95
+
96
+ def ask?(*question)
97
+ print question.join(' '), '? (y/n) > '
98
+ return true if STDIN.gets.chomp.downcase.eql?('y')
99
+ return false
100
+ end
101
+
102
+ def overwrite?(*str)
103
+ return ask?('Overwrite',str)
104
+ end
105
+
106
+
107
+ # -------------------------------------------------------------
108
+ # class extensions
109
+ # -------------------------------------------------------------
110
+
111
+ class Array
112
+ def resolv_path
113
+ Hash[ *self.collect { |e| [e,e.gsub(/.markdown/,'.html') ] }.flatten ]
114
+ end
115
+ end
116
+
117
+ # -------------------------------------------------------------
118
+ # main program
119
+ # -------------------------------------------------------------
120
+ begin
121
+
122
+ user_config = "#{ENV['HOME']}/.conversiorc"
123
+
124
+
125
+ # defaults for program arguments
126
+ options = OpenStruct.new
127
+ options.ignore_config = false
128
+ options.colorize = false
129
+ options.engine = nil
130
+ options.template_file = nil
131
+ options.table_of_content = false
132
+ options.verbose = false
133
+ options.template = 'default'
134
+
135
+ # list of user options
136
+ opts = GetoptLong.new(
137
+ [ '--colorize', '-c', GetoptLong::NO_ARGUMENT],
138
+ [ '--config', GetoptLong::NO_ARGUMENT],
139
+ [ '--engine', '-e', GetoptLong::OPTIONAL_ARGUMENT],
140
+ [ '--ignore-config', '-i', GetoptLong::NO_ARGUMENT],
141
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
142
+ [ '--list-templates', '-l', GetoptLong::NO_ARGUMENT ],
143
+ [ '--table-of-content', '-t', GetoptLong::NO_ARGUMENT],
144
+ [ '--template-file', '-f', GetoptLong::OPTIONAL_ARGUMENT ],
145
+ [ '--template', '-p', GetoptLong::OPTIONAL_ARGUMENT ],
146
+ [ '--verbose', '-v', GetoptLong::NO_ARGUMENT]
147
+ )
148
+
149
+ # parse the options from the command line
150
+ opts.each do |opt, arg|
151
+ case opt
152
+ when '--colorize'
153
+ options.colorize = true
154
+ when '--config'
155
+ open(user_config,'w') { |f| f.write config } if overwrite? user_config
156
+ exit 0
157
+ when '--engine'
158
+ options.engine = arg
159
+ when '--ignore-config'
160
+ options.ignore_config = true
161
+ when '--help'
162
+ puts help
163
+ exit 0
164
+ when '--list-templates'
165
+ Template.constants.each { |tpl| puts tpl.downcase }
166
+ exit 0
167
+ when '--table-of-content'
168
+ options.table_of_content = true
169
+ when '--template-file'
170
+ options.template_file = arg
171
+ when '--template'
172
+ options.template = arg
173
+ when '--verbose'
174
+ options.verbose = true
175
+ end
176
+ end
177
+
178
+
179
+ # get the input source
180
+ src = ARGV[0] || raise("no input defined")
181
+ dst = ARGV[1] # optional parameter!
182
+
183
+ # read the default configuration of the user
184
+ if not options.ignore_config and File.exists? user_config then
185
+ STDERR.puts "Reading configuration file: #{user_config}" if options.verbose
186
+ defaults = YAML.load_file user_config
187
+ # command-line arguments have precedents before the suer configuration
188
+ options.colorize = defaults['colorize']
189
+ options.engine = defaults['engine'] if options.engine.nil?
190
+ options.table_of_content = defaults['table_of_content']
191
+ options.template_file = defaults['template_file'] if options.template_file.nil?
192
+ end
193
+
194
+ STDERR.print 'Configuration: ', options, "\n" if options.verbose
195
+
196
+ template = String.new
197
+ # user the default ERB template if the user hasn't defined its own
198
+ if options.template_file.nil? then
199
+ STDERR.print 'Using ERB template: ', options.template, "\n" if options.verbose
200
+ template = Template.const_get(options.template.upcase)
201
+ else
202
+ STDERR.print 'Using ERB template: ', options.template_file, "\n" if options.verbose
203
+ options.template_file = File.expand_path(options.template_file)
204
+ if File.exists? options.template_file
205
+ template = File.read options.template_file
206
+ else
207
+ raise("The specified ERB templates is not existing!")
208
+ end
209
+ end
210
+
211
+ # setup the converter object
212
+ converter = Converter.new(template)
213
+ converter.load_parser(options.engine) unless options.engine.nil?
214
+ converter.color = true if options.colorize
215
+ converter.table_of_content = true if options.table_of_content
216
+
217
+ # get all the input files
218
+ input_files = Array.new
219
+ if File.directory?(src) then
220
+ input_files = Dir["#{src}/**/*.markdown"]
221
+ else
222
+ file = File.expand_path(src)
223
+ input_files << file
224
+ src = File.dirname(file)
225
+ end
226
+ src_dst_pairs = input_files.resolv_path
227
+ # fix the destination path if needed
228
+ unless dst.nil? then
229
+ src_dst_pairs.each_pair do |src_path,dst_path|
230
+ src_dst_pairs[src_path] = dst_path.gsub(/#{src}/,dst)
231
+ end
232
+ end
233
+ # render the XHTML docs
234
+ STDERR.puts 'Created files:' if options.verbose
235
+ src_dst_pairs.each_pair do |s,d|
236
+ converter.markdown_to_xhtml(s,d)
237
+ STDERR.print ' ', d, "\n" if options.verbose
238
+ end
239
+
240
+ exit 0
241
+
242
+ rescue => exc
243
+ STDERR.puts "ERROR: #{exc.message}"
244
+ STDERR.puts " use -h for detailed instructions"
245
+ exit 1
246
+ end
247
+
data/conversio.gemspec ADDED
@@ -0,0 +1,56 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+
5
+ s.name = %q{conversio}
6
+ s.version = "0.1.1"
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
9
+ s.authors = ["Jörg Behrendt","Victor Penso"]
10
+ s.date = %q{2010-11-19}
11
+ s.default_executable = %q{conversio}
12
+ s.homepage = 'https://github.com/vpenso/conversio'
13
+
14
+ s.description = <<-EOF
15
+ Renders plain text files with Markdown syntax to XHTML pages.
16
+ User can define their own Ruby ERB templates to customize the
17
+ XHTML page generation. Also the creation of a table of content
18
+ using the HTML header elements (like `<h1>`) and the syntax
19
+ high-lighting of code snippets is supported.
20
+ EOF
21
+
22
+ s.email = %q{v.penso@gsi.de}
23
+ s.executables = ["conversio"]
24
+ s.extra_rdoc_files = [
25
+ "bin/conversio",
26
+ "lib/conversio.rb",
27
+ "lib/conversio/converter.rb",
28
+ "lib/conversio/htmltoc.rb",
29
+ "lib/conversio/pygmentizer.rb"
30
+ ]
31
+ s.files = [
32
+ "README.md",
33
+ "HISTORY.md",
34
+ "bin/conversio",
35
+ "conversio.gemspec",
36
+ "lib/conversio.rb",
37
+ "lib/conversio/converter.rb",
38
+ "lib/conversio/htmltoc.rb",
39
+ "lib/conversio/pygmentizer.rb",
40
+ "templates/default.erb",
41
+ "templates/no_css.erb",
42
+ "templates/dark.erb",
43
+ "templates/light.erb"
44
+ ]
45
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Conversio"]
46
+ s.require_paths = ["lib"]
47
+ s.rubyforge_project = %q{conversio}
48
+ s.rubygems_version = %q{1.3.6}
49
+ s.summary = %q{Renders Markdown plain text files to HTML}
50
+
51
+ s.add_dependency('bluecloth', '>= 2.0.9')
52
+ s.add_dependency('kramdown')
53
+ s.requirements << 'Pygments (http://pygments.org/)'
54
+ s.licenses = 'GPLv3'
55
+
56
+ end
@@ -0,0 +1,141 @@
1
+ require 'fileutils'
2
+
3
+ module Conversio
4
+
5
+ class Hash
6
+
7
+ def deep_merge(hash)
8
+ target = dup
9
+
10
+ hash.keys.each do |key|
11
+ if hash[key].is_a? Hash and self[key].is_a? Hash
12
+ target[key] = target[key].deep_merge(hash[key])
13
+ next
14
+ end
15
+
16
+ target[key] = hash[key]
17
+ end
18
+
19
+ target
20
+ end
21
+
22
+
23
+ def deep_merge!(second)
24
+ second.each_pair do |k,v|
25
+ if self[k].is_a?(Hash) and second[k].is_a?(Hash)
26
+ self[k].deep_merge!(second[k])
27
+ else
28
+ self[k] = second[k]
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+
35
+ class Converter
36
+
37
+ attr_accessor :table_of_content, :color
38
+
39
+ def initialize(template)
40
+ @template = template
41
+ @table_of_content = false
42
+ @color = false
43
+ #user_config = "#{ENV['HOME']}/.conversiorc"
44
+ #if File.exists?(user_config)
45
+ # overwrite defaults
46
+ #@meta_data = @meta_data.deep_merge(YAML.load_file(user_config))
47
+ #end
48
+ # Holds the input Markdown plain text
49
+ @source = nil
50
+ # Hold Markdown rendered to HTML
51
+ @content = nil
52
+ # Hold the finished XHTML document
53
+ @result = nil
54
+ # load the default parser
55
+ @parser = 'bluecloth'
56
+ load_parser(@parser)
57
+ end
58
+
59
+ def markdown_to_xhtml(src,dst)
60
+ @source = open(src).readlines.join
61
+ colorize() if @color
62
+ parse()
63
+ generate_table_of_content() if @table_of_content
64
+ render()
65
+ # write the HTML file
66
+ FileUtils::mkdir_p(File.dirname(dst)) unless File.exists?(File.dirname(dst))
67
+ open(dst,'w') { |f| f.write @result }
68
+ end
69
+
70
+ def load_parser(parser)
71
+ begin
72
+ case parser
73
+ when 'bluecloth'
74
+ require 'bluecloth'
75
+ when 'kramdown'
76
+ require 'kramdown'
77
+ else
78
+ raise "Parser '#{parser}' is not a known Markdown library"
79
+ end
80
+ rescue LoadError
81
+ raise "Couldn't load #{parser}."
82
+ end
83
+ @parser = parser
84
+ end
85
+
86
+ private
87
+
88
+ def configure()
89
+ config = nil
90
+ # read the header of the source file
91
+ start = @source.index("|--")
92
+ ende = @source.index("--|")
93
+ #if start != nil and ende != nil then
94
+ if false
95
+ STDERR.puts 'Meta data found in file!' if $DEBUG
96
+ yamlheader = @source[start+3,ende-start-3]
97
+ # overwrite defaults
98
+ config = @meta_data.deep_merge(YAML.load(yamlheader))
99
+ splitted = @source.split('--|',2)
100
+ @source = splitted[1]
101
+ else
102
+ config = @meta_data
103
+ end
104
+ return config
105
+ end
106
+
107
+ def load_template(tpl)
108
+ puts "Loading template : "+tpl.to_s
109
+ raise "Couldn't open ERB template: #{tpl}" unless File.exists?(File.expand_path(tpl))
110
+ return open(File.expand_path(tpl)).readlines.join
111
+ end
112
+
113
+ def parse
114
+ raise "Define source before rendering!" if @source == nil
115
+ case @parser
116
+ when 'bluecloth'
117
+ @content = BlueCloth::new(@source).to_html
118
+ when 'kramdown'
119
+ @content = Kramdown::Document.new(@source).to_html
120
+ else
121
+ puts "Markdown parser #{@parser} not supported yet"
122
+ end
123
+ end
124
+
125
+ def colorize
126
+ @source = Pygmentizer.new.transform_code_blocks(@source)
127
+ end
128
+
129
+ def generate_table_of_content #füge zum geparsten Text ein Inhaltsverzeichnis hinzu -> davor parsen
130
+ raise "No content to generate table of content - Run the parser first!" if @content == nil
131
+ @content = HTMLTableOfContent.new(@content).get_html()
132
+ end
133
+
134
+ def render(values = {})
135
+ values.store(:content, @content)
136
+ @result = ERB.new(@template).result(binding)
137
+ end
138
+
139
+ end
140
+
141
+ end
@@ -0,0 +1,115 @@
1
+ module Conversio
2
+
3
+ class HTMLTableOfContent
4
+
5
+ attr_accessor :numbering, :table_of_content_div_class
6
+
7
+ def initialize(html_input)
8
+ @numbering = true
9
+ @table_of_content_div_class = 'toc'
10
+ # Variables
11
+ @html_input = Array.new
12
+ @heading_elements = Array.new
13
+ html_input.split("\n").each { |l| @html_input << l }
14
+ scan_for_heading_elements()
15
+ numbers()
16
+ end
17
+
18
+ def get_html_with_anchors()
19
+ inject_anchors()
20
+ return @html_input.join("\n")
21
+ end
22
+
23
+ def get_html_table_of_content()
24
+ output = String.new
25
+ @heading_elements.each do |heading|
26
+ index, level, content, anchor, number = heading
27
+ level = level.to_i
28
+ next if level > 3 # only h1,h2, and h3 tags are used
29
+ space = '&nbsp;&nbsp;'
30
+ case level
31
+ when 2 then output << space
32
+ when 3 then output << space << space
33
+ end
34
+ content = "#{number}&nbsp;#{content}" if numbering?
35
+ output << %{<a href="##{anchor}">#{content}</a><br/>\n}
36
+ end
37
+ return %{<div class="#{@table_of_content_div_class}">\n#{output}</div>\n}
38
+ end
39
+
40
+ def get_html()
41
+ return get_html_table_of_content() << "\n" << get_html_with_anchors
42
+ end
43
+
44
+ protected
45
+
46
+ def numbering?
47
+ return @numbering
48
+ end
49
+
50
+ def scan_for_heading_elements()
51
+ @html_input.each_index do |index|
52
+ if @html_input[index] =~ %r{<h(\d)(.*?)>(.*?)</h\1>$}m then
53
+ # Pattern match values:
54
+ # $1 -- header tag level, e.g. <h2>...</h2> will be 2
55
+ # $3 -- content between the tags
56
+ @heading_elements << [index, $1, $3, anchor($3)]
57
+ end
58
+ end
59
+ end
60
+
61
+ # Transforms the input string into a valid XHTML anchor (ID attribute).
62
+ #
63
+ # anchor("Text with spaces") # textwithspaces
64
+ # anchor("step 1 step 2 step: 3") # step1step2step3
65
+ def anchor(string)
66
+ alnum = String.new
67
+ string.gsub(/[[:alnum:]]/) { |c| alnum << c }
68
+ return alnum.downcase
69
+ end
70
+
71
+ def numbers()
72
+ chapters = 0
73
+ sections = 0
74
+ subsections = 0
75
+ @heading_elements.each_index do |index|
76
+ level = @heading_elements[index][1].to_i
77
+ case level
78
+ when 1
79
+ chapters = chapters.next
80
+ @heading_elements[index] << "#{chapters}"
81
+ sections = 0
82
+ subsections = 0
83
+ when 2
84
+ sections = sections.next
85
+ @heading_elements[index] << "#{chapters}.#{sections}"
86
+ subsections = 0
87
+ when 3
88
+ subsections = subsections.next
89
+ @heading_elements[index] << "#{chapters}.#{sections}.#{subsections}"
90
+ end
91
+ end
92
+ end
93
+
94
+ def inject_anchors()
95
+ @heading_elements.each do |heading|
96
+ line = String.new
97
+ index = heading[0]
98
+ level = heading[1].to_i
99
+ content = heading[2]
100
+ anchor = heading[3]
101
+ next if level > 3 # only h1,h2, and h3 tags are used
102
+ if numbering? then
103
+ number = heading[4]
104
+ line = %{<h#{level}><a name="#{anchor}"></a>#{number}&nbsp;#{content}</h#{level}>}
105
+ else
106
+ line = %{<h#{level}><a name="#{anchor}"></a>#{content}</h#{level}>}
107
+ end
108
+ @html_input[index] = line
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+
@@ -0,0 +1,82 @@
1
+ module Conversio
2
+
3
+
4
+ class Pygmentizer
5
+
6
+ def self.respond_to?( command )
7
+ return true if `which #{command}`.empty?
8
+ return false
9
+ end
10
+
11
+ def self.run(command, input='')
12
+ puts command if $DEBUG
13
+ IO.popen(command, 'r+') do |io|
14
+ io.puts input
15
+ io.close_write
16
+ return io.read
17
+ end
18
+ end
19
+
20
+
21
+
22
+ def output(string)
23
+ @output << "#{string}\n"
24
+ end
25
+
26
+ def transform_code_blocks(text)
27
+ raise RuntimeError, "pygmentize not in path" if
28
+ Pygmentizer::respond_to?("pygmentize")
29
+ @input_by_line = Array.new
30
+ text.each_line { |line| @input_by_line << line.chop }
31
+ @output = String.new
32
+ buffer = Array.new
33
+ rec = false
34
+ @input_by_line.each do |line|
35
+ # true if a Markdown code block is found
36
+ rec = true if !rec and line =~ /^ /
37
+ # store the code block into buffer
38
+ if rec and line =~ /^ / then
39
+ # remove the leading 4 spaces
40
+ line = line.gsub(/^ /,'')
41
+ buffer << line
42
+ # End of code block
43
+ elsif rec
44
+ block_to_html(buffer)
45
+ # Wipe the buffer
46
+ buffer.clear
47
+ rec = false
48
+ # Anyting beside code blocks will be output
49
+ else
50
+ output(line)
51
+ end
52
+ end
53
+ return @output
54
+ end
55
+
56
+
57
+ def block_to_html(block)
58
+ style = get_style(block[0])
59
+ unless style.empty? then
60
+ # remove the style information from the code block
61
+ block.slice!(0)
62
+ output(highlight(block.join("\n"),style))
63
+ else
64
+ # Code blocks without style information will be
65
+ # put to output includeing the 4 leading spaces
66
+ block.each {|line| output(" #{line}") }
67
+ output("")
68
+ end
69
+ end
70
+
71
+
72
+ def get_style(string)
73
+ return string.gsub(/--/,'').strip if string =~ /^--[a-z]* */
74
+ return ""
75
+ end
76
+
77
+ def highlight(string, style)
78
+ return Pygmentizer::run("pygmentize -f html -l #{style}", string) << "\n"
79
+ end
80
+ end
81
+
82
+ end
data/lib/conversio.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'conversio/converter'
2
+ require 'conversio/pygmentizer'
3
+ require 'conversio/htmltoc'
4
+
5
+ module Conversio
6
+ class Template
7
+ end
8
+ end
9
+
10
+ # find the template directory
11
+ templates = File.join(File.dirname(File.expand_path(__FILE__)),'..','templates')
12
+ # find the ERB templates
13
+ Dir.glob("#{templates}/*.erb") do |template|
14
+ # add the template as constant to the class Templates
15
+ Conversio::Template.const_set(
16
+ File.basename(template,".erb").upcase,
17
+ File.read(template))
18
+ end
19
+