karmi-markout 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +23 -0
- data/VERSION.yml +4 -0
- data/bin/markout +56 -0
- data/lib/markout.rb +9 -0
- data/lib/markout/document.rb +20 -0
- data/lib/markout/formatter.rb +25 -0
- data/lib/markout/formatters/html/html.rb +59 -0
- data/lib/markout/formatters/html/templates/default/content.rhtml +27 -0
- data/lib/markout/formatters/html/templates/default/print.css +34 -0
- data/lib/markout/formatters/html/templates/default/screen.css +102 -0
- data/lib/markout/formatters/pdf/pdf.rb +27 -0
- data/lib/markout/output.rb +49 -0
- data/test/fixtures/markdown.html +467 -0
- data/test/fixtures/markdown.txt +235 -0
- data/test/fixtures/mt_textformat_menu.png +0 -0
- data/test/markout_document_test.rb +33 -0
- data/test/markout_formatter_test.rb +13 -0
- data/test/markout_html_test.rb +34 -0
- data/test/markout_output_test.rb +29 -0
- data/test/markout_test.rb +5 -0
- data/test/test_helper.rb +17 -0
- metadata +85 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Karel Minarik
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
= Markout
|
2
|
+
|
3
|
+
Markout makes it easy to export sexy formatted documents such as specifications from your plain text, Markdown-formatted files.
|
4
|
+
|
5
|
+
Designed for easy extending, theming and adding output formats.
|
6
|
+
|
7
|
+
More information soon.
|
8
|
+
|
9
|
+
|
10
|
+
== Usage
|
11
|
+
|
12
|
+
On the command line:
|
13
|
+
|
14
|
+
$ sudo gem install karmi-markout --source=http://gems.github.com
|
15
|
+
|
16
|
+
|
17
|
+
== Todo
|
18
|
+
|
19
|
+
[!] Visualize document history based on Git information
|
20
|
+
|
21
|
+
== Copyright
|
22
|
+
|
23
|
+
Copyright (c) 2009 Karel Minarik. Released under MIT license.
|
data/VERSION.yml
ADDED
data/bin/markout
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
require File.join( File.join(File.dirname(__FILE__), '..', 'lib', 'markout') )
|
7
|
+
|
8
|
+
opts = {}
|
9
|
+
|
10
|
+
def die(msg)
|
11
|
+
STDERR.puts("\033[41;1mError!\033[0m\n" + msg)
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
|
15
|
+
OptionParser.new do |o|
|
16
|
+
o.banner = "USAGE: #{$0} [options] [path/to/file]"
|
17
|
+
|
18
|
+
o.on("-f",
|
19
|
+
"--format [FORMAT]",
|
20
|
+
"Output format (html, pdf)") do |f|
|
21
|
+
opts[:format] = f
|
22
|
+
end
|
23
|
+
|
24
|
+
o.on("-t",
|
25
|
+
"--template [TEMPLATE]",
|
26
|
+
"Output template") do |t|
|
27
|
+
opts[:template] = t
|
28
|
+
end
|
29
|
+
|
30
|
+
o.on("-o",
|
31
|
+
"--output [path/to/directory]",
|
32
|
+
"Output directory (default: same as source file)") do |d|
|
33
|
+
die('Output directory does not exist!') unless File.exist?(d)
|
34
|
+
opts[:output] = d
|
35
|
+
end
|
36
|
+
|
37
|
+
o.on("-h", "--help", "Show help") do |h|
|
38
|
+
puts o
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
end.parse!
|
42
|
+
|
43
|
+
die("You need to provide path to a Markdown formatted text file!
|
44
|
+
USAGE: #{$0} [options] [path/to/file]
|
45
|
+
Type #{$0} -h for help") if ARGV.empty?
|
46
|
+
|
47
|
+
begin
|
48
|
+
time = Time.now
|
49
|
+
o = Markout::Output.new( ARGV.first, opts )
|
50
|
+
puts "\033[0mConverting \033[1m#{ARGV[0]}\033[30;0m to \033[1m#{o.output_path}\033[0m"
|
51
|
+
o.export(true)
|
52
|
+
puts "\033[42mMarkout finished\033[0m in #{sprintf('%.2f', Time.now-time)} seconds\n"
|
53
|
+
rescue Exception => e
|
54
|
+
puts "\033[41;1mMarkout Failed!\033[0m\n"
|
55
|
+
raise e
|
56
|
+
end
|
data/lib/markout.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'mime/types'
|
3
|
+
|
4
|
+
module Markout
|
5
|
+
|
6
|
+
class Document
|
7
|
+
|
8
|
+
attr_reader :path, :base_path, :content
|
9
|
+
|
10
|
+
def initialize(path)
|
11
|
+
@path = File.expand_path(path)
|
12
|
+
@base_path = Pathname.new( File.dirname(@path) )
|
13
|
+
raise FileNotFound, "File #{@path} not found" unless File.exist?(@path)
|
14
|
+
# TODO: Raise error for non-text file
|
15
|
+
@content = File.read(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Markout
|
4
|
+
|
5
|
+
class Formatter
|
6
|
+
|
7
|
+
def initialize(document, options={})
|
8
|
+
@document = document
|
9
|
+
@options = options
|
10
|
+
@format = self.class.to_s.gsub(/^.*::/, '').downcase
|
11
|
+
end
|
12
|
+
|
13
|
+
def export
|
14
|
+
raise NoMethodError, "Return String in `export()` method of your formatter (#{@format})"
|
15
|
+
end
|
16
|
+
|
17
|
+
def filename
|
18
|
+
basename = File.basename(@document.path).split('.')
|
19
|
+
ext = basename.pop
|
20
|
+
"#{basename.join('.')}.#{@format}"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rdiscount'
|
2
|
+
require 'erb'
|
3
|
+
require 'base64'
|
4
|
+
require 'mime/types'
|
5
|
+
|
6
|
+
module Markout
|
7
|
+
|
8
|
+
class Html < Formatter
|
9
|
+
|
10
|
+
def export
|
11
|
+
suck_in_images!
|
12
|
+
ERB.new( File.read(template_path.join('content.rhtml')) ).result(binding)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def template_name
|
18
|
+
@options[:template] || 'default'
|
19
|
+
end
|
20
|
+
|
21
|
+
def template_path
|
22
|
+
Pathname.new( File.join( File.dirname(__FILE__), 'templates', template_name ) )
|
23
|
+
end
|
24
|
+
|
25
|
+
def content
|
26
|
+
@content ||= RDiscount.new( @document.content ).to_html
|
27
|
+
end
|
28
|
+
|
29
|
+
def screen_style
|
30
|
+
File.read template_path.join('screen.css')
|
31
|
+
end
|
32
|
+
|
33
|
+
def print_style
|
34
|
+
File.read template_path.join('print.css')
|
35
|
+
end
|
36
|
+
|
37
|
+
def title
|
38
|
+
h1 = content.match(/<h1\s*.*>(.+)<\/h1>/)[1] rescue nil
|
39
|
+
h1 || ''
|
40
|
+
end
|
41
|
+
|
42
|
+
# TODO : Make 'alt' attribute optional
|
43
|
+
# TODO : Cleanup?
|
44
|
+
def suck_in_images!
|
45
|
+
content.to_s.gsub!(/<img src="([^"]+)".*alt="([^"]+)".*\s*\/?>/) do |match|
|
46
|
+
begin
|
47
|
+
file = File.read( @document.base_path.join($1) )
|
48
|
+
mime = MIME::Types.of(File.basename( @document.base_path.join($1) ))
|
49
|
+
"<img src=\"data:#{mime};base64,#{Base64.encode64(file)}\" alt=\"#{$2}\" />"
|
50
|
+
rescue
|
51
|
+
# TODO : Better error handling
|
52
|
+
puts "Error: Cannot load image #{$1}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
7
|
+
|
8
|
+
<title><%= title %></title>
|
9
|
+
|
10
|
+
<style type="text/css" media="screen">
|
11
|
+
<%= screen_style %>
|
12
|
+
</style>
|
13
|
+
|
14
|
+
<style type="text/css" media="print">
|
15
|
+
<%= print_style %>
|
16
|
+
</style>
|
17
|
+
|
18
|
+
</head>
|
19
|
+
|
20
|
+
<body>
|
21
|
+
|
22
|
+
<div id="content">
|
23
|
+
<%= content %>
|
24
|
+
</div>
|
25
|
+
|
26
|
+
</body>
|
27
|
+
</html>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
body
|
2
|
+
{ color: #000;
|
3
|
+
background: #fff;
|
4
|
+
font-size: 85%;
|
5
|
+
font-family: Helvetica, sans-serif;
|
6
|
+
margin: 0.5cm; }
|
7
|
+
|
8
|
+
a
|
9
|
+
{ color: #000;
|
10
|
+
text-decoration: underline; }
|
11
|
+
|
12
|
+
a img
|
13
|
+
{ border: none; }
|
14
|
+
|
15
|
+
h2 {
|
16
|
+
font-size: 1.5em;
|
17
|
+
letter-spacing: -0.05em;
|
18
|
+
padding: 1.15em 0 0 0;
|
19
|
+
margin: 1.25em 0 0.25em 0;
|
20
|
+
border-top: 1mm solid #999;
|
21
|
+
}
|
22
|
+
|
23
|
+
h3 {
|
24
|
+
font-size: 1.1em;
|
25
|
+
font-weight:bold;
|
26
|
+
margin: 1.8em 0 0.25em 0;
|
27
|
+
letter-spacing: -0.05em;
|
28
|
+
}
|
29
|
+
|
30
|
+
pre {
|
31
|
+
border: 1pt solid #ccc;
|
32
|
+
padding: 0.5em;
|
33
|
+
white-space: pre-wrap; white-space: -moz-pre-wrap !important;
|
34
|
+
}
|
@@ -0,0 +1,102 @@
|
|
1
|
+
/*
|
2
|
+
= COLORS =
|
3
|
+
|
4
|
+
Steel blue #d8dde3
|
5
|
+
Black blue #172533
|
6
|
+
Dark grey #525c66
|
7
|
+
Ash grey #dfe2e5
|
8
|
+
Live blue #1177DD
|
9
|
+
Pale green face #b9cbdd
|
10
|
+
|
11
|
+
*/
|
12
|
+
|
13
|
+
body {
|
14
|
+
color: #222;
|
15
|
+
background: #fafafa;
|
16
|
+
font-family: Helvetica, sans-serif;
|
17
|
+
font-size: 85%;
|
18
|
+
line-height: 155%;
|
19
|
+
margin: 0;
|
20
|
+
padding: 0 5em;
|
21
|
+
}
|
22
|
+
|
23
|
+
.cleaner
|
24
|
+
{ clear: both; height: 0; line-height: 0; width: 0; border: 0; font-size: 1px; }
|
25
|
+
|
26
|
+
#content {
|
27
|
+
color: #172533;
|
28
|
+
font-size: 1.1em;
|
29
|
+
max-width: 55em;
|
30
|
+
padding-bottom: 4em;
|
31
|
+
margin: 7px auto;
|
32
|
+
margin-bottom: 4em;
|
33
|
+
border-top: 4px solid #172533;
|
34
|
+
border-bottom: 4px solid #b9cbdd;
|
35
|
+
}
|
36
|
+
|
37
|
+
h1, h2, h3, h4, h5, h6 {
|
38
|
+
color: #222;
|
39
|
+
}
|
40
|
+
h1, h2 {
|
41
|
+
color: #172533;
|
42
|
+
font-weight: normal;
|
43
|
+
}
|
44
|
+
h1 a, h2 a { color: #172533; }
|
45
|
+
|
46
|
+
h1 {
|
47
|
+
font-size: 2.25em;
|
48
|
+
letter-spacing: -1px;
|
49
|
+
margin-bottom: 0.5em;
|
50
|
+
}
|
51
|
+
|
52
|
+
h2 {
|
53
|
+
font-size: 1.5em;
|
54
|
+
letter-spacing: -1px;
|
55
|
+
padding: 1.15em 0 0 0;
|
56
|
+
margin: 1.25em 0 0.25em 0;
|
57
|
+
border-top: 4px solid #d8dde3;
|
58
|
+
}
|
59
|
+
|
60
|
+
h3 {
|
61
|
+
color: #172533;
|
62
|
+
font-size: 1.1em;
|
63
|
+
font-weight:bold;
|
64
|
+
margin: 1.8em 0 0.25em 0;
|
65
|
+
letter-spacing:-1px;
|
66
|
+
}
|
67
|
+
|
68
|
+
h3 a {
|
69
|
+
text-decoration:underline;
|
70
|
+
}
|
71
|
+
|
72
|
+
p {
|
73
|
+
margin: 0 0 0.8em 0;
|
74
|
+
}
|
75
|
+
|
76
|
+
a {
|
77
|
+
color: #172533;
|
78
|
+
text-decoration:underline;
|
79
|
+
}
|
80
|
+
a:hover {
|
81
|
+
color: #1177DD;
|
82
|
+
text-decoration:underline;
|
83
|
+
}
|
84
|
+
a img {
|
85
|
+
border:none;
|
86
|
+
}
|
87
|
+
|
88
|
+
code, pre, textarea, tt {
|
89
|
+
font-family: "Monaco", "lucida console", "bitstream vera sans mono", monospace;
|
90
|
+
font-size: 100%;
|
91
|
+
}
|
92
|
+
pre {
|
93
|
+
color: #172533;
|
94
|
+
background: #f5f9fc;
|
95
|
+
font-size: 100%;
|
96
|
+
line-height: 155%;
|
97
|
+
border: 4px solid #dfe2e5;
|
98
|
+
padding: 0.5em;
|
99
|
+
white-space: pre-wrap; white-space: -moz-pre-wrap !important;
|
100
|
+
}
|
101
|
+
|
102
|
+
hr { display: none; }
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'html', 'html.rb')
|
3
|
+
|
4
|
+
# Depends on HTMLDOC utility (http://www.easysw.com/htmldoc/)
|
5
|
+
module Markout
|
6
|
+
|
7
|
+
class Pdf < Formatter
|
8
|
+
|
9
|
+
def export
|
10
|
+
`cat "#{tempfile.path}" | /opt/local/bin/htmldoc -t pdf \
|
11
|
+
--bodyfont "Helvetica" --headfootfont "Helvetica" \
|
12
|
+
--no-compression --color --embedfonts \
|
13
|
+
--header "" --footer .1. --links --no-title \
|
14
|
+
--toctitle "" --tocheader "..." --tocfooter "..." \
|
15
|
+
-`
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def tempfile
|
21
|
+
tempfile = Tempfile.new(File.basename(@document.path) + '.html')
|
22
|
+
tempfile << Markout::Html.new(@document).export
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Markout
|
2
|
+
|
3
|
+
class Output
|
4
|
+
|
5
|
+
attr_reader :document, :format, :formatter
|
6
|
+
|
7
|
+
def initialize(path, options = {})
|
8
|
+
@path = path
|
9
|
+
@document = Document.new(@path)
|
10
|
+
@options = options
|
11
|
+
@format = @options[:format] || 'html'
|
12
|
+
@formatter = pick_formatter.new( @document, :template => @options[:template] || 'default' )
|
13
|
+
@output = ''
|
14
|
+
end
|
15
|
+
|
16
|
+
def export(to_file=false)
|
17
|
+
@output = @formatter.export
|
18
|
+
write_file if to_file
|
19
|
+
return @output
|
20
|
+
end
|
21
|
+
|
22
|
+
def output_path
|
23
|
+
if @options[:output]
|
24
|
+
File.join( File.expand_path( @options[:output] ), @formatter.filename )
|
25
|
+
else
|
26
|
+
@document.base_path.join(@formatter.filename)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def write_file
|
33
|
+
File.open(output_path, 'w') { |f| f.write @output }
|
34
|
+
end
|
35
|
+
|
36
|
+
def pick_formatter
|
37
|
+
unless Markout::const_defined?(@format.to_s.capitalize)
|
38
|
+
begin
|
39
|
+
require File.join( File.dirname(__FILE__), 'formatters', @format, @format )
|
40
|
+
rescue Exception => e
|
41
|
+
raise LoadError, "Formatter '#{@format}' not found! (#{e.message})"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
Markout::const_get(@format.to_s.capitalize)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|