gimli 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.textile +60 -0
- data/bin/gimli +10 -0
- data/config/style.css +109 -0
- data/lib/gimli.rb +33 -0
- data/lib/gimli/albino.rb +7 -0
- data/lib/gimli/converter.rb +75 -0
- data/lib/gimli/file.rb +65 -0
- data/lib/gimli/markup.rb +268 -0
- data/lib/gimli/setup.rb +21 -0
- data/lib/gimli/version.rb +6 -0
- data/spec/fixtures/code_block.textile +8 -0
- data/spec/gimli/converter_spec.rb +71 -0
- data/spec/gimli/file_spec.rb +26 -0
- data/spec/gimli/markup_spec.rb +21 -0
- data/spec/spec_helper.rb +4 -0
- metadata +221 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Fredrik Wallgren
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
data/README.textile
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
h1. gimli - utility for converting markup to pdf
|
2
|
+
|
3
|
+
h2. Description
|
4
|
+
|
5
|
+
Gimli is a utility for converting markup to pdf files. Useful for reports and such things.
|
6
|
+
It's inspired by the markup convertion in "gollum":https://github.com/github/gollum
|
7
|
+
It works by converting the markup to html using "PDFKit":https://github.com/jdpace/PDFKit
|
8
|
+
The markup is converted to html using "github/markup":https://github.com/github/markup
|
9
|
+
|
10
|
+
h3. Markup
|
11
|
+
|
12
|
+
Markup files may be written in any format supported by GitHub-Markup (except roff).
|
13
|
+
|
14
|
+
h2. Installation
|
15
|
+
|
16
|
+
The best way to install Gimli is with RubyGems:
|
17
|
+
|
18
|
+
bc. $ [sudo] gem install gimli
|
19
|
+
|
20
|
+
You can install from source:
|
21
|
+
|
22
|
+
bc. $ cd gimli/
|
23
|
+
$ bundle
|
24
|
+
$ rake install
|
25
|
+
|
26
|
+
h2. Running
|
27
|
+
|
28
|
+
The standard way to run gimli is to go to a folder with markup files and running
|
29
|
+
|
30
|
+
bc. $ gimli
|
31
|
+
|
32
|
+
To apply some style to the pdf or override the standard style add a css file in the directory named @gimli.css@ or use the @-s@ flag to point out another css file.
|
33
|
+
|
34
|
+
Standard behavior is for gimli to output the files in the current directory. To override this use the @-o@ flag to point out another output directory. Gimli tries to create it if it doesn't exist.
|
35
|
+
|
36
|
+
Run @gimli -h@ for a full list of options available
|
37
|
+
|
38
|
+
h2. Syntax highlighting
|
39
|
+
|
40
|
+
In page files you can get automatic syntax highlighting for a wide range of languages by using the following syntax:
|
41
|
+
|
42
|
+
If you want to be able to use the syntax highlightning you have to install "pygments":http://pygments.org
|
43
|
+
|
44
|
+
Example installation on Ubuntu
|
45
|
+
|
46
|
+
bc. $ sudo aptitude install python-setuptools
|
47
|
+
$ sudo easy_install pygments
|
48
|
+
|
49
|
+
bc. ```ruby
|
50
|
+
def foo
|
51
|
+
puts 'bar'
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
The block must start with three backticks (as the first characters on the line). After that comes the name of the language that is contained by the block. The language must be one of the short name lexer strings supported by Pygments. See the list of lexers for valid options.
|
56
|
+
|
57
|
+
If the block contents are indented two spaces or one tab, then that whitespace will be ignored (this makes the blocks easier to read in plaintext).
|
58
|
+
|
59
|
+
The block must end with three backticks as the first characters on a line.
|
60
|
+
|
data/bin/gimli
ADDED
data/config/style.css
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
body
|
2
|
+
{
|
3
|
+
font-family: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Sans", Arial, sans-serif;
|
4
|
+
font-size: 12px;
|
5
|
+
}
|
6
|
+
|
7
|
+
/* Code */
|
8
|
+
code, tt
|
9
|
+
{
|
10
|
+
background-color: #f8f8f8;
|
11
|
+
border: 1px solid #dedede;
|
12
|
+
font-size: 13px;
|
13
|
+
padding: 1px 5px;
|
14
|
+
|
15
|
+
-moz-border-radius: 3px;
|
16
|
+
-webkit-border-radius: 3px;
|
17
|
+
border-radius: 3px;
|
18
|
+
}
|
19
|
+
|
20
|
+
.highlight pre, pre
|
21
|
+
{
|
22
|
+
background-color: #f8f8f8;
|
23
|
+
border: 1px solid #ccc;
|
24
|
+
font-size: 13px;
|
25
|
+
line-height: 19px;
|
26
|
+
overflow: auto;
|
27
|
+
padding: 6px;
|
28
|
+
|
29
|
+
-moz-border-radius: 3px;
|
30
|
+
-webkit-border-radius: 3px;
|
31
|
+
border-radius: 3px;
|
32
|
+
}
|
33
|
+
|
34
|
+
pre code, pre tt
|
35
|
+
{
|
36
|
+
background-color: transparent;
|
37
|
+
border: none;
|
38
|
+
}
|
39
|
+
|
40
|
+
.highlight .hll { background-color: #ffffcc }
|
41
|
+
.highlight .c { color: #8f5902; font-style: italic } /* Comment */
|
42
|
+
.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */
|
43
|
+
.highlight .g { color: #000000 } /* Generic */
|
44
|
+
.highlight .k { color: #204a87; font-weight: bold } /* Keyword */
|
45
|
+
.highlight .l { color: #000000 } /* Literal */
|
46
|
+
.highlight .n { color: #000000 } /* Name */
|
47
|
+
.highlight .o { color: #ce5c00; font-weight: bold } /* Operator */
|
48
|
+
.highlight .x { color: #000000 } /* Other */
|
49
|
+
.highlight .p { color: #000000; font-weight: bold } /* Punctuation */
|
50
|
+
.highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */
|
51
|
+
.highlight .cp { color: #8f5902; font-style: italic } /* Comment.Preproc */
|
52
|
+
.highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */
|
53
|
+
.highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */
|
54
|
+
.highlight .gd { color: #a40000 } /* Generic.Deleted */
|
55
|
+
.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */
|
56
|
+
.highlight .gr { color: #ef2929 } /* Generic.Error */
|
57
|
+
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
58
|
+
.highlight .gi { color: #00A000 } /* Generic.Inserted */
|
59
|
+
.highlight .go { color: #000000; font-style: italic } /* Generic.Output */
|
60
|
+
.highlight .gp { color: #8f5902 } /* Generic.Prompt */
|
61
|
+
.highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */
|
62
|
+
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
63
|
+
.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */
|
64
|
+
.highlight .kc { color: #204a87; font-weight: bold } /* Keyword.Constant */
|
65
|
+
.highlight .kd { color: #204a87; font-weight: bold } /* Keyword.Declaration */
|
66
|
+
.highlight .kn { color: #204a87; font-weight: bold } /* Keyword.Namespace */
|
67
|
+
.highlight .kp { color: #204a87; font-weight: bold } /* Keyword.Pseudo */
|
68
|
+
.highlight .kr { color: #204a87; font-weight: bold } /* Keyword.Reserved */
|
69
|
+
.highlight .kt { color: #204a87; font-weight: bold } /* Keyword.Type */
|
70
|
+
.highlight .ld { color: #000000 } /* Literal.Date */
|
71
|
+
.highlight .m { color: #0000cf; font-weight: bold } /* Literal.Number */
|
72
|
+
.highlight .s { color: #4e9a06 } /* Literal.String */
|
73
|
+
.highlight .na { color: #c4a000 } /* Name.Attribute */
|
74
|
+
.highlight .nb { color: #204a87 } /* Name.Builtin */
|
75
|
+
.highlight .nc { color: #000000 } /* Name.Class */
|
76
|
+
.highlight .no { color: #000000 } /* Name.Constant */
|
77
|
+
.highlight .nd { color: #5c35cc; font-weight: bold } /* Name.Decorator */
|
78
|
+
.highlight .ni { color: #ce5c00 } /* Name.Entity */
|
79
|
+
.highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */
|
80
|
+
.highlight .nf { color: #000000 } /* Name.Function */
|
81
|
+
.highlight .nl { color: #f57900 } /* Name.Label */
|
82
|
+
.highlight .nn { color: #000000 } /* Name.Namespace */
|
83
|
+
.highlight .nx { color: #000000 } /* Name.Other */
|
84
|
+
.highlight .py { color: #000000 } /* Name.Property */
|
85
|
+
.highlight .nt { color: #204a87; font-weight: bold } /* Name.Tag */
|
86
|
+
.highlight .nv { color: #000000 } /* Name.Variable */
|
87
|
+
.highlight .ow { color: #204a87; font-weight: bold } /* Operator.Word */
|
88
|
+
.highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */
|
89
|
+
.highlight .mf { color: #0000cf; font-weight: bold } /* Literal.Number.Float */
|
90
|
+
.highlight .mh { color: #0000cf; font-weight: bold } /* Literal.Number.Hex */
|
91
|
+
.highlight .mi { color: #0000cf; font-weight: bold } /* Literal.Number.Integer */
|
92
|
+
.highlight .mo { color: #0000cf; font-weight: bold } /* Literal.Number.Oct */
|
93
|
+
.highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */
|
94
|
+
.highlight .sc { color: #4e9a06 } /* Literal.String.Char */
|
95
|
+
.highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */
|
96
|
+
.highlight .s2 { color: #4e9a06 } /* Literal.String.Double */
|
97
|
+
.highlight .se { color: #4e9a06 } /* Literal.String.Escape */
|
98
|
+
.highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */
|
99
|
+
.highlight .si { color: #4e9a06 } /* Literal.String.Interpol */
|
100
|
+
.highlight .sx { color: #4e9a06 } /* Literal.String.Other */
|
101
|
+
.highlight .sr { color: #4e9a06 } /* Literal.String.Regex */
|
102
|
+
.highlight .s1 { color: #4e9a06 } /* Literal.String.Single */
|
103
|
+
.highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */
|
104
|
+
.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */
|
105
|
+
.highlight .vc { color: #000000 } /* Name.Variable.Class */
|
106
|
+
.highlight .vg { color: #000000 } /* Name.Variable.Global */
|
107
|
+
.highlight .vi { color: #000000 } /* Name.Variable.Instance */
|
108
|
+
.highlight .il { color: #0000cf; font-weight: bold } /* Literal.Number.Integer.Long */
|
109
|
+
|
data/lib/gimli.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'gimli/version'
|
2
|
+
require 'gimli/setup'
|
3
|
+
require 'gimli/file'
|
4
|
+
require 'gimli/converter'
|
5
|
+
require 'gimli/albino'
|
6
|
+
|
7
|
+
module Gimli
|
8
|
+
|
9
|
+
# Starts the processing of selected files
|
10
|
+
def self.process!
|
11
|
+
@files = []
|
12
|
+
if ARGV.flags.file?
|
13
|
+
Gimli.load_file(ARGV.flags.file)
|
14
|
+
else
|
15
|
+
Dir.glob("*").each do |file|
|
16
|
+
Gimli.load_file(file)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
@files.each do |file|
|
21
|
+
converter = Converter.new file
|
22
|
+
converter.convert!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Add file to the files to be converted if it's valid
|
27
|
+
# @param [String] file
|
28
|
+
def self.load_file(file)
|
29
|
+
file = File.new file
|
30
|
+
@files << file if file.valid?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
data/lib/gimli/albino.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
require 'pdfkit'
|
4
|
+
|
5
|
+
require 'gimli/markup'
|
6
|
+
|
7
|
+
module Gimli
|
8
|
+
|
9
|
+
# The class that communicates with PDFKit
|
10
|
+
class Converter
|
11
|
+
|
12
|
+
# Initialize the converter with a File
|
13
|
+
# @param [Gimli::File] file The file to convert
|
14
|
+
def initialize(file)
|
15
|
+
@file = file
|
16
|
+
end
|
17
|
+
|
18
|
+
# Convert the file and save it as a PDF file
|
19
|
+
def convert!
|
20
|
+
markup = Markup.new @file
|
21
|
+
html = markup.render
|
22
|
+
|
23
|
+
kit = pdf_kit(html)
|
24
|
+
|
25
|
+
kit.to_file(output_file)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Load the pdfkit with html
|
29
|
+
# @param [String] html
|
30
|
+
# @return [PDFKit]
|
31
|
+
def pdf_kit(html)
|
32
|
+
kit = PDFKit.new(html)
|
33
|
+
|
34
|
+
load_stylesheets kit
|
35
|
+
|
36
|
+
kit
|
37
|
+
end
|
38
|
+
|
39
|
+
# Load the stylesheets to pdfkit loads the default and the user selected if any
|
40
|
+
# @param [PDFKit] kit
|
41
|
+
def load_stylesheets(kit)
|
42
|
+
# Load standard stylesheet
|
43
|
+
style = ::File.expand_path("../../../config/style.css", __FILE__)
|
44
|
+
kit.stylesheets << style
|
45
|
+
|
46
|
+
stylesheet
|
47
|
+
|
48
|
+
kit.stylesheets << stylesheet if ::File.exists?(stylesheet)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the selected stylesheet. Defaults to ./gimli.css
|
52
|
+
# @return [String]
|
53
|
+
def stylesheet
|
54
|
+
stylesheet = 'gimli.css'
|
55
|
+
stylesheet = ARGV.flags.stylesheet if ARGV.flags.stylesheet?
|
56
|
+
stylesheet
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the directory where to save the output. Defaults to ./
|
60
|
+
# @return [String]
|
61
|
+
def output_dir
|
62
|
+
output_dir = Dir.getwd
|
63
|
+
output_dir = ARGV.flags.outputdir if ARGV.flags.outputdir?
|
64
|
+
FileUtils.mkdir_p(output_dir) unless ::File.directory?(output_dir)
|
65
|
+
output_dir
|
66
|
+
end
|
67
|
+
|
68
|
+
# Generate the name of the output file
|
69
|
+
# @return [String]
|
70
|
+
def output_file
|
71
|
+
::File.join(output_dir, "#{@file.name}.pdf")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
data/lib/gimli/file.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Gimli
|
2
|
+
|
3
|
+
# Class used to load files and determine if they are valid
|
4
|
+
class File
|
5
|
+
attr_reader :filename, :name, :data, :format
|
6
|
+
|
7
|
+
# Accepted formats
|
8
|
+
FORMATS = [:markdown, :textile, :rdoc, :org, :creole, :rest, :asciidoc, :pod, :roff, :mediawiki]
|
9
|
+
|
10
|
+
# Initializes the file object. Only reads contents if it's a valid file
|
11
|
+
def initialize(filename)
|
12
|
+
@filename = filename
|
13
|
+
extension = ::File.extname(@filename)
|
14
|
+
@format = load_format(extension)
|
15
|
+
@name = ::File.basename(@filename, extension)
|
16
|
+
@data = ::File.open(@filename, 'rb') { |f| f.read } if valid? && ::File.exists?(@filename)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Is the file valid
|
20
|
+
# @return [Boolean]
|
21
|
+
def valid?
|
22
|
+
valid_format? @format
|
23
|
+
end
|
24
|
+
|
25
|
+
# Converts the format to a symbol if it's a valid format nil otherwise
|
26
|
+
# @param [String] format
|
27
|
+
# @return [Symbol|nil]
|
28
|
+
def load_format(format)
|
29
|
+
case format
|
30
|
+
when /(md|mkdn?|mdown|markdown)$/i
|
31
|
+
:markdown
|
32
|
+
when /(textile)$/i
|
33
|
+
:textile
|
34
|
+
when /(rdoc)$/i
|
35
|
+
:rdoc
|
36
|
+
when /(org)$/i
|
37
|
+
:org
|
38
|
+
when /(creole)$/i
|
39
|
+
:creole
|
40
|
+
when /(re?st(\.txt)?)$/i
|
41
|
+
:rest
|
42
|
+
when /(asciidoc)$/i
|
43
|
+
:asciidoc
|
44
|
+
when /(pod)$/i
|
45
|
+
:pod
|
46
|
+
when /(\d)$/i
|
47
|
+
:roff
|
48
|
+
when /(media)?wiki$/i
|
49
|
+
:mediawiki
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checks if the format is a valid one
|
56
|
+
# @param [String] format
|
57
|
+
# @return [Boolean]
|
58
|
+
def valid_format?(format)
|
59
|
+
return false if format.nil?
|
60
|
+
|
61
|
+
FORMATS.include? format.to_sym
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/lib/gimli/markup.rb
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
require 'github/markup'
|
5
|
+
require 'nokogiri'
|
6
|
+
|
7
|
+
module Gimli
|
8
|
+
|
9
|
+
# Contains functionality to render html from a markup file
|
10
|
+
class Markup
|
11
|
+
# Initialize a new Markup object.
|
12
|
+
#
|
13
|
+
# @param [Gimli::File] file The Gimli::File to process
|
14
|
+
# @return [Gimli::Markup]
|
15
|
+
def initialize(file)
|
16
|
+
@filename = file.filename
|
17
|
+
@name = file.name
|
18
|
+
@data = file.data
|
19
|
+
@format = file.format
|
20
|
+
@tagmap = {}
|
21
|
+
@codemap = {}
|
22
|
+
@premap = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Render the content with Gollum wiki syntax on top of the file's own
|
26
|
+
# markup language.
|
27
|
+
#
|
28
|
+
# @return [String] The formatted data
|
29
|
+
def render
|
30
|
+
data = extract_code(@data.dup)
|
31
|
+
data = extract_tags(data)
|
32
|
+
begin
|
33
|
+
data = GitHub::Markup.render(@filename, data)
|
34
|
+
if data.nil?
|
35
|
+
raise "There was an error converting #{@name} to HTML."
|
36
|
+
end
|
37
|
+
rescue Object => e
|
38
|
+
data = %{<p class="gimli-error">#{e.message}</p>}
|
39
|
+
end
|
40
|
+
data = process_tags(data)
|
41
|
+
data = process_code(data)
|
42
|
+
|
43
|
+
doc = Nokogiri::HTML::DocumentFragment.parse(data)
|
44
|
+
yield doc if block_given?
|
45
|
+
data = doc_to_html(doc)
|
46
|
+
|
47
|
+
data.gsub!(/<p><\/p>/, '')
|
48
|
+
data
|
49
|
+
end
|
50
|
+
|
51
|
+
def doc_to_html(doc)
|
52
|
+
doc.to_xhtml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XHTML)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Extract all tags into the tagmap and replace with placeholders.
|
56
|
+
#
|
57
|
+
# @param [String] data - The raw string data.
|
58
|
+
# @return [String] Returns the placeholder's string data.
|
59
|
+
def extract_tags(data)
|
60
|
+
data.gsub!(/(.?)\[\[(.+?)\]\]([^\[]?)/m) do
|
61
|
+
if $1 == "'" && $3 != "'"
|
62
|
+
"[[#{$2}]]#{$3}"
|
63
|
+
elsif $2.include?('][')
|
64
|
+
if $2[0..4] == 'file:'
|
65
|
+
pre = $1
|
66
|
+
post = $3
|
67
|
+
parts = $2.split('][')
|
68
|
+
parts[0][0..4] = ""
|
69
|
+
link = "#{parts[1]}|#{parts[0].sub(/\.org/,'')}"
|
70
|
+
id = Digest::SHA1.hexdigest(link)
|
71
|
+
@tagmap[id] = link
|
72
|
+
"#{pre}#{id}#{post}"
|
73
|
+
else
|
74
|
+
$&
|
75
|
+
end
|
76
|
+
else
|
77
|
+
id = Digest::SHA1.hexdigest($2)
|
78
|
+
@tagmap[id] = $2
|
79
|
+
"#{$1}#{id}#{$3}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
data
|
83
|
+
end
|
84
|
+
|
85
|
+
# Process all tags from the tagmap and replace the placeholders with the
|
86
|
+
# final markup.
|
87
|
+
#
|
88
|
+
# @param [String] data - The data (with placeholders)
|
89
|
+
# @return [String] Returns the marked up String data.
|
90
|
+
def process_tags(data)
|
91
|
+
@tagmap.each do |id, tag|
|
92
|
+
data.gsub!(id, process_tag(tag))
|
93
|
+
end
|
94
|
+
data
|
95
|
+
end
|
96
|
+
|
97
|
+
# Process a single tag into its final HTML form.
|
98
|
+
#
|
99
|
+
# @param [String] tag The String tag contents (the stuff inside the double brackets).
|
100
|
+
# @return [String] Returns the String HTML version of the tag.
|
101
|
+
def process_tag(tag)
|
102
|
+
if html = process_image_tag(tag)
|
103
|
+
html
|
104
|
+
elsif html = process_file_link_tag(tag)
|
105
|
+
html
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Attempt to process the tag as an image tag.
|
110
|
+
#
|
111
|
+
# @param [String] tag The String tag contents (the stuff inside the double brackets).
|
112
|
+
# @return [String|nil] Returns the String HTML if the tag is a valid image tag or nil if it is not.
|
113
|
+
def process_image_tag(tag)
|
114
|
+
parts = tag.split('|')
|
115
|
+
return if parts.size.zero?
|
116
|
+
|
117
|
+
name = parts[0].strip
|
118
|
+
path = name
|
119
|
+
|
120
|
+
if path
|
121
|
+
opts = parse_image_tag_options(tag)
|
122
|
+
|
123
|
+
containered = false
|
124
|
+
|
125
|
+
classes = [] # applied to whatever the outermost container is
|
126
|
+
attrs = [] # applied to the image
|
127
|
+
|
128
|
+
align = opts['align']
|
129
|
+
if opts['float']
|
130
|
+
containered = true
|
131
|
+
align ||= 'left'
|
132
|
+
if %w{left right}.include?(align)
|
133
|
+
classes << "float-#{align}"
|
134
|
+
end
|
135
|
+
elsif %w{top texttop middle absmiddle bottom absbottom baseline}.include?(align)
|
136
|
+
attrs << %{align="#{align}"}
|
137
|
+
elsif align
|
138
|
+
if %w{left center right}.include?(align)
|
139
|
+
containered = true
|
140
|
+
classes << "align-#{align}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
if width = opts['width']
|
145
|
+
if width =~ /^\d+(\.\d+)?(em|px)$/
|
146
|
+
attrs << %{width="#{width}"}
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
if height = opts['height']
|
151
|
+
if height =~ /^\d+(\.\d+)?(em|px)$/
|
152
|
+
attrs << %{height="#{height}"}
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
if alt = opts['alt']
|
157
|
+
attrs << %{alt="#{alt}"}
|
158
|
+
end
|
159
|
+
|
160
|
+
attr_string = attrs.size > 0 ? attrs.join(' ') + ' ' : ''
|
161
|
+
|
162
|
+
if opts['frame'] || containered
|
163
|
+
classes << 'frame' if opts['frame']
|
164
|
+
%{<span class="#{classes.join(' ')}">} +
|
165
|
+
%{<span>} +
|
166
|
+
%{<img src="#{path}" #{attr_string}/>} +
|
167
|
+
(alt ? %{<span>#{alt}</span>} : '') +
|
168
|
+
%{</span>} +
|
169
|
+
%{</span>}
|
170
|
+
else
|
171
|
+
%{<img src="#{path}" #{attr_string}/>}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Parse any options present on the image tag and extract them into a
|
177
|
+
# Hash of option names and values.
|
178
|
+
#
|
179
|
+
# @param [String] tag The String tag contents (the stuff inside the double brackets).
|
180
|
+
# @return [Hash]
|
181
|
+
# Returns the options Hash:
|
182
|
+
# key - The String option name.
|
183
|
+
# val - The String option value or true if it is a binary option.
|
184
|
+
def parse_image_tag_options(tag)
|
185
|
+
tag.split('|')[1..-1].inject({}) do |memo, attr|
|
186
|
+
parts = attr.split('=').map { |x| x.strip }
|
187
|
+
memo[parts[0]] = (parts.size == 1 ? true : parts[1])
|
188
|
+
memo
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Extract all code blocks into the codemap and replace with placeholders.
|
193
|
+
#
|
194
|
+
# @param [String] data The raw String data.
|
195
|
+
# @return [String] Returns the placeholder'd String data.
|
196
|
+
def extract_code(data)
|
197
|
+
data.gsub!(/^``` ?([^\r\n]+)?\r?\n(.+?)\r?\n```\r?$/m) do
|
198
|
+
id = Digest::SHA1.hexdigest($2)
|
199
|
+
cached = check_cache(:code, id)
|
200
|
+
@codemap[id] = cached ?
|
201
|
+
{ :output => cached } :
|
202
|
+
{ :lang => $1, :code => $2 }
|
203
|
+
id
|
204
|
+
end
|
205
|
+
data
|
206
|
+
end
|
207
|
+
|
208
|
+
# Process all code from the codemap and replace the placeholders with the
|
209
|
+
# final HTML.
|
210
|
+
#
|
211
|
+
# @param [String] data The String data (with placeholders).
|
212
|
+
# @return [String] Returns the marked up String data.
|
213
|
+
def process_code(data)
|
214
|
+
return data if data.nil? || data.size.zero? || @codemap.size.zero?
|
215
|
+
|
216
|
+
blocks = []
|
217
|
+
@codemap.each do |id, spec|
|
218
|
+
next if spec[:output] # cached
|
219
|
+
|
220
|
+
code = spec[:code]
|
221
|
+
if code.lines.all? { |line| line =~ /\A\r?\n\Z/ || line =~ /^( |\t)/ }
|
222
|
+
code.gsub!(/^( |\t)/m, '')
|
223
|
+
end
|
224
|
+
|
225
|
+
blocks << [spec[:lang], code]
|
226
|
+
end
|
227
|
+
|
228
|
+
highlighted = begin
|
229
|
+
blocks.size.zero? ? [] : Gimli::Albino.colorize(blocks)
|
230
|
+
rescue ::Albino::ShellArgumentError, ::Albino::TimeoutExceeded,
|
231
|
+
::Albino::MaximumOutputExceeded
|
232
|
+
[]
|
233
|
+
end
|
234
|
+
|
235
|
+
@codemap.each do |id, spec|
|
236
|
+
body = spec[:output] || begin
|
237
|
+
if (body = highlighted.shift.to_s).size > 0
|
238
|
+
update_cache(:code, id, body)
|
239
|
+
body
|
240
|
+
else
|
241
|
+
"<pre><code>#{CGI.escapeHTML(spec[:code])}</code></pre>"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
data.gsub!(id, body)
|
245
|
+
end
|
246
|
+
|
247
|
+
data
|
248
|
+
end
|
249
|
+
|
250
|
+
# Hook for getting the formatted value of extracted tag data.
|
251
|
+
#
|
252
|
+
# @param [Symbol] type Symbol value identifying what type of data is being extracted.
|
253
|
+
# @param [String] id String SHA1 hash of original extracted tag data.
|
254
|
+
# @return [String] Returns the String cached formatted data, or nil.
|
255
|
+
def check_cache(type, id)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Hook for caching the formatted value of extracted tag data.
|
259
|
+
#
|
260
|
+
# @param [Symbol] type Symbol value identifying what type of data is being extracted.
|
261
|
+
# @param [String] id String SHA1 hash of original extracted tag data.
|
262
|
+
# @param [String] data The String formatted value to be cached.
|
263
|
+
# @return [nil]
|
264
|
+
def update_cache(type, id, data)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
data/lib/gimli/setup.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'optiflag'
|
2
|
+
|
3
|
+
# Set up the flags available
|
4
|
+
module Gimli extend OptiFlagSet
|
5
|
+
optional_flag 'file' do
|
6
|
+
description 'The file to convert if you do not want to convert all in the current folder'
|
7
|
+
alternate_forms 'f'
|
8
|
+
end
|
9
|
+
optional_flag 'outputdir' do
|
10
|
+
description 'The directory to save parsed files if you do not want to use the current folder. The directory is created if it does not exist'
|
11
|
+
alternate_forms 'o'
|
12
|
+
end
|
13
|
+
optional_flag 'stylesheet' do
|
14
|
+
description 'The stylesheet to use to override the standard'
|
15
|
+
alternate_forms 's'
|
16
|
+
end
|
17
|
+
usage_flag 'h', 'help', '?'
|
18
|
+
|
19
|
+
and_process!
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
require './lib/gimli'
|
4
|
+
|
5
|
+
describe Gimli::Converter do
|
6
|
+
it 'should give the correct output_file with none given' do
|
7
|
+
file = Gimli::File.new 'fake'
|
8
|
+
name = 'my_file'
|
9
|
+
mock(file).name { name }
|
10
|
+
|
11
|
+
converter = Gimli::Converter.new file
|
12
|
+
mock(converter).output_dir { Dir.getwd }
|
13
|
+
|
14
|
+
converter.output_file.should == File.join(Dir.getwd, "#{name}.pdf")
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should give the correct output_file with one given' do
|
18
|
+
file = Gimli::File.new 'fake'
|
19
|
+
name = 'my_file'
|
20
|
+
mock(file).name { name }
|
21
|
+
|
22
|
+
converter = Gimli::Converter.new file
|
23
|
+
mock(converter).output_dir { '/tmp/out' }
|
24
|
+
|
25
|
+
converter.output_file.should == "/tmp/out/#{name}.pdf"
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should give the correct output_dir when none given' do
|
29
|
+
dir = Dir.getwd
|
30
|
+
|
31
|
+
file = Gimli::File.new 'fake'
|
32
|
+
converter = Gimli::Converter.new file
|
33
|
+
|
34
|
+
converter.output_dir.should == dir
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should give the correct output_dir when given' do
|
38
|
+
dir = '/tmp/out'
|
39
|
+
|
40
|
+
file = Gimli::File.new 'fake'
|
41
|
+
converter = Gimli::Converter.new file
|
42
|
+
|
43
|
+
mock(ARGV).flags.mock!.outputdir? { true }
|
44
|
+
mock(ARGV).flags.mock!.outputdir { dir }
|
45
|
+
mock(File).directory?(dir) { true }
|
46
|
+
|
47
|
+
converter.output_dir.should == dir
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should use stylesheet if exists in folder' do
|
51
|
+
file = Gimli::File.new 'fake'
|
52
|
+
converter = Gimli::Converter.new file
|
53
|
+
|
54
|
+
mock(ARGV).flags.mock!.stylesheet? { false }
|
55
|
+
|
56
|
+
converter.stylesheet.should == 'gimli.css'
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should use stylesheet if given' do
|
60
|
+
file = Gimli::File.new 'fake'
|
61
|
+
converter = Gimli::Converter.new file
|
62
|
+
|
63
|
+
style = '/home/me/gimli/my-style.css'
|
64
|
+
|
65
|
+
mock(ARGV).flags.mock!.stylesheet? { true }
|
66
|
+
mock(ARGV).flags.mock!.stylesheet { style }
|
67
|
+
|
68
|
+
converter.stylesheet.should == style
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
require './lib/gimli'
|
4
|
+
|
5
|
+
describe Gimli::File do
|
6
|
+
it 'should recognize valid format' do
|
7
|
+
file = Gimli::File.new 'fake'
|
8
|
+
file.valid_format?('textile').should be_true
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should recognize invalid format' do
|
12
|
+
file = Gimli::File.new 'fake'
|
13
|
+
file.valid_format?('abc123').should be_false
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should recognize nil as invalid format' do
|
17
|
+
file = Gimli::File.new 'fake'
|
18
|
+
file.valid_format?(nil).should be_false
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should give the name as the filename without the extension' do
|
22
|
+
file = Gimli::File.new 'test.txt'
|
23
|
+
file.name.should == File.basename(file.filename, File.extname(file.filename))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
require './lib/gimli'
|
4
|
+
|
5
|
+
describe Gimli::Markup do
|
6
|
+
|
7
|
+
it 'should give correct code block' do
|
8
|
+
|
9
|
+
output = "<p>a</p>\n<div class=\"highlight\"><pre>" +
|
10
|
+
"<span class=\"n\">x</span> <span class=\"o\">=</span> " +
|
11
|
+
"<span class=\"mi\">1</span>\n</pre>\n</div>\n\n<p>b</p>"
|
12
|
+
|
13
|
+
file = Gimli::File.new File.expand_path('../../fixtures/code_block.textile', __FILE__)
|
14
|
+
markup = Gimli::Markup.new file
|
15
|
+
|
16
|
+
markup.render.should == output
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
end
|
21
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gimli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Fredrik Wallgren
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-04-29 00:00:00.000000000 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: github-markup
|
17
|
+
requirement: &16957440 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *16957440
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: redcarpet
|
28
|
+
requirement: &16957020 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *16957020
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: RedCloth
|
39
|
+
requirement: &16956600 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *16956600
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: org-ruby
|
50
|
+
requirement: &16956180 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *16956180
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: creole
|
61
|
+
requirement: &16955760 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
type: :runtime
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *16955760
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: wikicloth
|
72
|
+
requirement: &16955340 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: *16955340
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: albino
|
83
|
+
requirement: &16954920 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: *16954920
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: nokogiri
|
94
|
+
requirement: &16954500 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
type: :runtime
|
101
|
+
prerelease: false
|
102
|
+
version_requirements: *16954500
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: wkhtmltopdf-binary
|
105
|
+
requirement: &16985560 !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
type: :runtime
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: *16985560
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: pdfkit
|
116
|
+
requirement: &16985140 !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
type: :runtime
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: *16985140
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: optiflag
|
127
|
+
requirement: &16984720 !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
type: :runtime
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: *16984720
|
136
|
+
- !ruby/object:Gem::Dependency
|
137
|
+
name: rspec
|
138
|
+
requirement: &16984300 !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ! '>='
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
type: :development
|
145
|
+
prerelease: false
|
146
|
+
version_requirements: *16984300
|
147
|
+
- !ruby/object:Gem::Dependency
|
148
|
+
name: rr
|
149
|
+
requirement: &16983880 !ruby/object:Gem::Requirement
|
150
|
+
none: false
|
151
|
+
requirements:
|
152
|
+
- - ! '>='
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
type: :development
|
156
|
+
prerelease: false
|
157
|
+
version_requirements: *16983880
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: yard
|
160
|
+
requirement: &16983460 !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: *16983460
|
169
|
+
description: Utility for converting markup files to pdf files. Useful for reports
|
170
|
+
etc.
|
171
|
+
email: fredrik.wallgren@gmail.com
|
172
|
+
executables:
|
173
|
+
- gimli
|
174
|
+
extensions: []
|
175
|
+
extra_rdoc_files:
|
176
|
+
- README.textile
|
177
|
+
- LICENSE
|
178
|
+
files:
|
179
|
+
- bin/gimli
|
180
|
+
- lib/gimli/version.rb
|
181
|
+
- lib/gimli/albino.rb
|
182
|
+
- lib/gimli/file.rb
|
183
|
+
- lib/gimli/markup.rb
|
184
|
+
- lib/gimli/setup.rb
|
185
|
+
- lib/gimli/converter.rb
|
186
|
+
- lib/gimli.rb
|
187
|
+
- spec/spec_helper.rb
|
188
|
+
- spec/fixtures/code_block.textile
|
189
|
+
- spec/gimli/converter_spec.rb
|
190
|
+
- spec/gimli/markup_spec.rb
|
191
|
+
- spec/gimli/file_spec.rb
|
192
|
+
- config/style.css
|
193
|
+
- LICENSE
|
194
|
+
- README.textile
|
195
|
+
has_rdoc: true
|
196
|
+
homepage: https://github.com/walle/gimli
|
197
|
+
licenses: []
|
198
|
+
post_install_message:
|
199
|
+
rdoc_options:
|
200
|
+
- --charset=UTF-8
|
201
|
+
require_paths:
|
202
|
+
- - lib
|
203
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
204
|
+
none: false
|
205
|
+
requirements:
|
206
|
+
- - ! '>='
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
210
|
+
none: false
|
211
|
+
requirements:
|
212
|
+
- - ! '>='
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: '0'
|
215
|
+
requirements: []
|
216
|
+
rubyforge_project: gimli
|
217
|
+
rubygems_version: 1.6.2
|
218
|
+
signing_key:
|
219
|
+
specification_version: 3
|
220
|
+
summary: Utility for converting markup files to pdf files.
|
221
|
+
test_files: []
|