erb_latex 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/Guardfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +119 -0
- data/Rakefile +28 -0
- data/erb_latex.gemspec +27 -0
- data/lib/erb_latex.rb +17 -0
- data/lib/erb_latex/context.rb +61 -0
- data/lib/erb_latex/errors.rb +17 -0
- data/lib/erb_latex/guard.rb +58 -0
- data/lib/erb_latex/guard_runner.rb +83 -0
- data/lib/erb_latex/stringio.rb +34 -0
- data/lib/erb_latex/template.rb +150 -0
- data/lib/erb_latex/version.rb +5 -0
- data/test/erb_latex_test.rb +45 -0
- data/test/fixtures/body.tex.erb +1 -0
- data/test/fixtures/layout.tex.erb +4 -0
- data/test/fixtures/multi_page.tex.erb +16 -0
- data/test/fixtures/partial.tex.erb +1 -0
- data/test/fixtures/valid.tex.erb +34 -0
- data/test/fixtures/with_error.tex.erb +1 -0
- data/test/fixtures/with_partial.tex.erb +4 -0
- data/test/helper.rb +32 -0
- data/yard_ext/templates/default/method_details/html/github_link.erb +1 -0
- data/yard_ext/templates/default/method_details/setup.rb +3 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 85a04610d1b87c2b98b9b9c12236031c2a205018
|
4
|
+
data.tar.gz: f6f80ea5b96e39d6f8d1cc0abd0da6fe1c700410
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b325e8f90702b6a105623667c00d707f9f905512c3687e637c28fd4d4074ba8691781b0d068e2405576b35d647b1dbae6920e6d17e6d993f40284c28f6485c7d
|
7
|
+
data.tar.gz: 873c768effa755f69585530350414b1529741ea6232a052c5269c08ee4cd045c360be2f5df3d4d2a1a8f5504e650f3272f00aac320deae88f1d131f61adebb7a
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
notification :growl
|
2
|
+
|
3
|
+
guard :minitest, :all_on_start => true do
|
4
|
+
watch(%r{^test/test_helper\.rb}) { 'test' }
|
5
|
+
|
6
|
+
watch(%r{^test/.+_test\.rb})
|
7
|
+
watch(%r{^test/fixtures/(.+)s\.tex}) { |m| "test/erb_latex_test.rb" }
|
8
|
+
|
9
|
+
watch(%r{^lib/erb_latex/(.+)\.rb}) { |m| "test/erb_latex_test.rb" }
|
10
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Nathan Stitt
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# ERB LaTeX
|
2
|
+
|
3
|
+
Applies ERB template processing to a LaTeX file and compiles it to a PDF.
|
4
|
+
|
5
|
+
Supports layouts, partials, and string escaping.
|
6
|
+
|
7
|
+
Also supplies a Guard task to watch for modifications and auto-building files.
|
8
|
+
|
9
|
+
## Use Case
|
10
|
+
|
11
|
+
[Argosity](http://argosity.com/) uses this for several different projects
|
12
|
+
|
13
|
+
* [Nathan](http://nathan.stitt.org/) originally used a bare-bones of this to generate his resume.
|
14
|
+
* [Stockor](http://stockor.org/), our open-source ERP platform. It's used for building all the form that output as PDF.
|
15
|
+
* Generating proposals for client projects.
|
16
|
+
|
17
|
+
## Api Docs
|
18
|
+
|
19
|
+
Are hosted at [nathan.stitt.org/code/erb-latex/](http://nathan.stitt.org/code/erb-latex/)
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
gem 'erb_latex'
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
$ gem install erb_latex
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
Given a LaTeX layout template: ``layout.tex``
|
38
|
+
|
39
|
+
```latex
|
40
|
+
\documentclass[12pt]{article}
|
41
|
+
\usepackage{background}
|
42
|
+
\usepackage{lettrine}
|
43
|
+
\makeatletter
|
44
|
+
\AddEverypageHook{
|
45
|
+
\SetBgContents{
|
46
|
+
\includegraphics[width=\paperwidth]{argosity-background-wave.png}
|
47
|
+
}
|
48
|
+
\SetBgPosition{current page.south}
|
49
|
+
\SetBgAnchor{above}
|
50
|
+
\SetBgAngle{0}
|
51
|
+
\SetBgScale{1}
|
52
|
+
\SetBgVshift{-2mm}
|
53
|
+
\SetBgOpacity{0.3}
|
54
|
+
\bg@material}
|
55
|
+
\makeatother
|
56
|
+
\begin{document}
|
57
|
+
<%= yield %>
|
58
|
+
\end{document}
|
59
|
+
```
|
60
|
+
|
61
|
+
and a simple LaTeX body: ``body.tex``
|
62
|
+
|
63
|
+
```latex
|
64
|
+
\lettrine[lines=2]{T}{hank} you for your your consideration.
|
65
|
+
Please let us know if we can help you any further at all and don't forget to fill out the speakers notes.
|
66
|
+
|
67
|
+
<%=q message %>
|
68
|
+
|
69
|
+
Thank you,
|
70
|
+
<%=q author %>
|
71
|
+
```
|
72
|
+
The following will convert it to a pdf
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
require "erb_latex"
|
76
|
+
|
77
|
+
tmpl = ErbLatex::Template.new( 'body.tex', {
|
78
|
+
:layout=>'layout.tex',
|
79
|
+
:data=>{
|
80
|
+
:author=>"Nathan",
|
81
|
+
:messge = "Please call our department at 555-5555"
|
82
|
+
}
|
83
|
+
})
|
84
|
+
tmpl.to_file('thank-you.pdf')
|
85
|
+
```
|
86
|
+
## Guard plugin
|
87
|
+
|
88
|
+
ERB LaTeX also includes a plugin for [Guard](https://github.com/guard/guard) to automatically
|
89
|
+
generate a PDF from a LaTeX file whenever the LaTeX file is modified.
|
90
|
+
|
91
|
+
This is useful for shortening the build/review/modify cycle when developing a complex layout.
|
92
|
+
|
93
|
+
A Guardfile such as this would compile all *.tex files in a directory
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
require 'erb_latex/guard'
|
97
|
+
|
98
|
+
guard :erb_latex do
|
99
|
+
watch(%r{.tex$})
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
While the one below would compile the hypothetical body.tex file above
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
require 'erb_latex/guard'
|
107
|
+
|
108
|
+
guard :erb_latex, :layout=>'proposal.tex', :data=>{:author=>'Nathan Stitt', :message=>'Buy low, Sell High!'} do
|
109
|
+
watch 'body.tex'
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
## Contributing
|
114
|
+
|
115
|
+
1. Fork it ( http://github.com/nathanstitt/erb_latex/fork )
|
116
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
117
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
118
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
119
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require "bundler/gem_tasks"
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'yard'
|
7
|
+
require 'guard'
|
8
|
+
|
9
|
+
Rake::TestTask.new do |t|
|
10
|
+
t.libs << 'test'
|
11
|
+
t.pattern = "test/*_test.rb"
|
12
|
+
end
|
13
|
+
|
14
|
+
YARD::Rake::YardocTask.new do |t|
|
15
|
+
t.files = ['lib/**/*.rb']
|
16
|
+
t.options = [
|
17
|
+
"--title=ERB LaTeX",
|
18
|
+
"--markup=markdown",
|
19
|
+
"--template-path=yard_ext/templates",
|
20
|
+
"--readme=README.md"
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Deploy docs to web server"
|
25
|
+
task :docs do
|
26
|
+
Rake::Task["yard"].invoke
|
27
|
+
system( "rsync", "doc/", "-avz", "--delete", "nathan.stitt.org:~/docs/erb-latex")
|
28
|
+
end
|
data/erb_latex.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'erb_latex/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "erb_latex"
|
8
|
+
spec.version = ErbLatex::VERSION
|
9
|
+
spec.authors = ["Nathan Stitt"]
|
10
|
+
spec.email = ["nathan@stitt.org"]
|
11
|
+
spec.summary = %q{Applies ERB template processing to a Latex file and compiles it to a PDF}
|
12
|
+
spec.description = %q{Applies ERB template processing to a Latex file and compiles it to a PDF. Supports layouts, partials, and string escaping. Also supplies a Guard task to watch for modifications and auto-building files.}
|
13
|
+
spec.homepage = "http://nathan.stitt.org/code/erb-latex/"
|
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|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake", "~> 10"
|
23
|
+
spec.add_development_dependency "growl", "~> 1.0" # sorry Linux. Anyone have suggestions on how to do this multi-platform?
|
24
|
+
spec.add_development_dependency "yard", "~> 0.8"
|
25
|
+
spec.add_development_dependency "guard", "~> 2.0"
|
26
|
+
spec.add_development_dependency "guard-minitest", "~> 2.2"
|
27
|
+
end
|
data/lib/erb_latex.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "erb_latex/version"
|
2
|
+
require "erb_latex/errors"
|
3
|
+
require "erb_latex/context"
|
4
|
+
require "erb_latex/stringio"
|
5
|
+
require "erb_latex/template"
|
6
|
+
module ErbLatex
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
def self.xelatex_binary
|
11
|
+
defined?(@@xelatex_binary) ? @@xelatex_binary : "xelatex"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.xelatex_binary=(bin)
|
15
|
+
@@xelatex_binary = bin
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module ErbLatex
|
2
|
+
|
3
|
+
# Is the execution context for ERB evaluation
|
4
|
+
#
|
5
|
+
class Context
|
6
|
+
|
7
|
+
# List of characters that need to be escaped
|
8
|
+
# and their escaped values
|
9
|
+
ESCAPE = {
|
10
|
+
"#"=>"\\#",
|
11
|
+
"$"=>"\\$",
|
12
|
+
"%"=>"\\%",
|
13
|
+
"&"=>"\\&",
|
14
|
+
"~"=>"\\~{}",
|
15
|
+
"_"=>"\\_",
|
16
|
+
"^"=>"\\^{}",
|
17
|
+
"\\"=>"\\textbackslash{}",
|
18
|
+
"{"=>"\\{",
|
19
|
+
"}"=>"\\}"
|
20
|
+
}
|
21
|
+
|
22
|
+
# create new Context
|
23
|
+
# @param directory [String] directory to use as a base for finding partials
|
24
|
+
# @param data [Hash]
|
25
|
+
def initialize( directory, data )
|
26
|
+
@directory = directory
|
27
|
+
@data = data
|
28
|
+
data.each{ |k,v| instance_variable_set( '@'+k.to_s, v ) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# include another latex file into the current template
|
32
|
+
def partial( template, data={} )
|
33
|
+
view_file = @directory.join( template )
|
34
|
+
if view_file.exist?
|
35
|
+
context = Context.new( @directory, data )
|
36
|
+
ERB.new( view_file.read, 0, '-' ).result( context.getBinding )
|
37
|
+
else
|
38
|
+
"missing partial: #{template}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# convert newline characters into latex '\\newline'
|
43
|
+
def break_lines( txt )
|
44
|
+
q(txt.to_s).gsub("\n",'\\newline ')
|
45
|
+
end
|
46
|
+
|
47
|
+
# return a reference to the instance's scope or 'binding'
|
48
|
+
def getBinding
|
49
|
+
return binding()
|
50
|
+
end
|
51
|
+
|
52
|
+
# escape using latex escaping rules
|
53
|
+
# @param text string to escape
|
54
|
+
# @return [String] text after {ESCAPE} characters are replaced
|
55
|
+
def q(text)
|
56
|
+
text.to_s.gsub( /([\^\%~\\\\#\$%&_\{\}])/ ) { |s| ESCAPE[s] }
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ErbLatex
|
2
|
+
|
3
|
+
# Encapsolates an error that occurs while processing a latex template
|
4
|
+
class LatexError < RuntimeError
|
5
|
+
|
6
|
+
# @attribute [r] log
|
7
|
+
# @return the log from the xelatex run
|
8
|
+
attr_reader :log
|
9
|
+
|
10
|
+
def initialize( msg, log )
|
11
|
+
super(msg)
|
12
|
+
@log = log
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'guard'
|
2
|
+
require 'guard/plugin'
|
3
|
+
require 'erb_latex'
|
4
|
+
require 'erb_latex/guard_runner'
|
5
|
+
|
6
|
+
module Guard
|
7
|
+
|
8
|
+
|
9
|
+
# The ErbLatex guard that gets notifications about the following
|
10
|
+
# Guard events: `start`, `run_all` and `run_on_modifications`.
|
11
|
+
class ErbLatex < Plugin
|
12
|
+
|
13
|
+
DEFAULT_OPTIONS = {
|
14
|
+
:layout => false,
|
15
|
+
:data => {},
|
16
|
+
:all_on_start => true,
|
17
|
+
:hide_success => false
|
18
|
+
}
|
19
|
+
|
20
|
+
# Initialize Guard::ErbLates
|
21
|
+
#
|
22
|
+
# @param [Hash] options the options for the Guard
|
23
|
+
# @option options [String] :layout, the layout to apply to latex files
|
24
|
+
# @option options [String] :data, for use during the ERB processing
|
25
|
+
# @option options [Boolean] :all_on_start process all latex files on start
|
26
|
+
# @option options [Boolean] :hide_success hide success message notification, unless the previous run failed
|
27
|
+
def initialize( options = {} )
|
28
|
+
super( DEFAULT_OPTIONS.merge(options) )
|
29
|
+
end
|
30
|
+
|
31
|
+
# Gets called once when Guard starts.
|
32
|
+
#
|
33
|
+
# @raise [:task_has_failed] when stop has failed
|
34
|
+
#
|
35
|
+
def start
|
36
|
+
run_all if options[:all_on_start]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Gets called when all files should be regenerated.
|
40
|
+
#
|
41
|
+
# @raise [:task_has_failed] when stop has failed
|
42
|
+
#
|
43
|
+
def run_all
|
44
|
+
run_on_modifications( Watcher.match_files(self, Dir.glob('**{,/*/**}/*.{tex,erb.tex,tex.erb}')) )
|
45
|
+
end
|
46
|
+
|
47
|
+
# Gets called when watched paths and files have changes.
|
48
|
+
#
|
49
|
+
# @param [Array<String>] paths the changed paths and files
|
50
|
+
# @raise [:task_has_failed] when stop has failed
|
51
|
+
#
|
52
|
+
def run_on_modifications(paths)
|
53
|
+
throw :task_has_failed unless ::ErbLatex::GuardRunner.run( paths, watchers, options )
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'erb_latex'
|
2
|
+
|
3
|
+
module ErbLatex
|
4
|
+
module GuardRunner
|
5
|
+
class << self
|
6
|
+
|
7
|
+
attr_accessor :last_run_failed
|
8
|
+
|
9
|
+
# The ErbLatex runner handles the ErbLatex compilation,
|
10
|
+
# creates the output pdf file, writes the result
|
11
|
+
# to the console and triggers optional system notifications.
|
12
|
+
#
|
13
|
+
# @param [Array<String>] files the spec files or directories
|
14
|
+
# @param [Array<Guard::Watcher>] watchers the Guard watchers in the block
|
15
|
+
# @param [Hash] options the options for the execution
|
16
|
+
# @option options [String] :layout, the layout to apply to latex files
|
17
|
+
# @option options [String] :data, for use during the ERB processing
|
18
|
+
# @return [Array<Array<String>, Boolean>] the result for the compilation run
|
19
|
+
#
|
20
|
+
def run(files, watchers, options = { })
|
21
|
+
::Guard::UI.info('Compiling ' + files.join(', '), :reset=>true )
|
22
|
+
changed_files, errors = compile_files(files, watchers, options)
|
23
|
+
notify_result(changed_files, errors, options)
|
24
|
+
return errors.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Compiles all ErbLatex files and writes the PDF files.
|
31
|
+
#
|
32
|
+
# @param [Array<String>] files the files to compile
|
33
|
+
# @param [Array<Guard::Watcher>] watchers the Guard watchers in the block
|
34
|
+
# @param [Hash] options the options for the execution
|
35
|
+
# @return [Array<Array<String>, Array<String>] the result for the compilation run
|
36
|
+
#
|
37
|
+
def compile_files(files, watchers, options)
|
38
|
+
errors = []
|
39
|
+
changed_files = []
|
40
|
+
|
41
|
+
files.each do |file|
|
42
|
+
begin
|
43
|
+
pdf = ErbLatex::Template.new( file, options ).to_file
|
44
|
+
changed_files << pdf
|
45
|
+
rescue LatexError => e
|
46
|
+
error_message = file + ': ' + e.message.to_s
|
47
|
+
errors << error_message
|
48
|
+
::Guard::UI.error(color(error_message, ';31'), options)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
[changed_files.flatten.compact, errors]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Writes console and system notifications about the result of the compilation.
|
55
|
+
#
|
56
|
+
# @param [Array<String>] changed_files the changed JavaScript files
|
57
|
+
# @param [Array<String>] errors the error messages
|
58
|
+
# @param [Hash] options the options for the execution
|
59
|
+
# @option options [Boolean] :hide_success hide success message notification
|
60
|
+
# @option options [Boolean] :noop do not generate an output file
|
61
|
+
#
|
62
|
+
def notify_result(changed_files, errors, options = { })
|
63
|
+
if !errors.empty?
|
64
|
+
self.last_run_failed = true
|
65
|
+
::Guard::Notifier.notify(errors.join("\n"), :title => 'ErbLatex results', :image => :failed, :priority => 2)
|
66
|
+
elsif !options[:hide_success] || last_run_failed
|
67
|
+
self.last_run_failed = false
|
68
|
+
message = "Successfully #{ options[:noop] ? 'verified' : 'generated' } #{ changed_files.join(', ') }"
|
69
|
+
::Guard::UI.info(color( "#{Time.now.strftime('%r')} #{message}", ';32'), options)
|
70
|
+
::Guard::Notifier.notify(message, :title => 'ErbLatex results')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Print a info message to the console.
|
75
|
+
# @param [String] text the text to colorize
|
76
|
+
# @param [String] color_code the color code
|
77
|
+
def color(text, color_code)
|
78
|
+
::Guard::UI.send(:color_enabled?) ? "\e[0#{ color_code }m#{ text }\e[0m" : text
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ErbLatex
|
2
|
+
|
3
|
+
# An ErbLatex::StringIO extends Ruby's StringIO
|
4
|
+
# with a path and file name
|
5
|
+
# This allows it to be used in situations where a
|
6
|
+
# File is expected
|
7
|
+
class StringIO < ::StringIO
|
8
|
+
|
9
|
+
# @attribute [rw] filepath
|
10
|
+
# @return [String] complete *virtual* path for the data
|
11
|
+
attr_accessor :filepath
|
12
|
+
|
13
|
+
def initialize( name='' )
|
14
|
+
@filepath = name
|
15
|
+
super('')
|
16
|
+
self.set_encoding( Encoding.find("UTF-8") )
|
17
|
+
end
|
18
|
+
|
19
|
+
# define original_filename for clients that
|
20
|
+
# depend on it, i.e. carrierwave
|
21
|
+
def original_filename
|
22
|
+
File.basename(filepath)
|
23
|
+
end
|
24
|
+
|
25
|
+
# reset filepath
|
26
|
+
# @param name [String] new value for filepath
|
27
|
+
# @return self
|
28
|
+
def rename( name )
|
29
|
+
@filepath = name
|
30
|
+
self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require "tmpdir"
|
2
|
+
require "open3"
|
3
|
+
require "erb"
|
4
|
+
require 'pathname'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module ErbLatex
|
8
|
+
|
9
|
+
# A template is an latex file that contains embedded ERB code
|
10
|
+
# It can optionally have a layout be rendered to either a file
|
11
|
+
# or StringIO instance
|
12
|
+
# @example for a hypothetical Rails controller
|
13
|
+
# tmpl = ErbLatex::Template.new( 'article.tex',
|
14
|
+
# :layout => 'layout.tex'
|
15
|
+
# :data => { :sentence=>"hello, this is doge" }
|
16
|
+
# )
|
17
|
+
# render :pdf => tmpl.to_stringio.read
|
18
|
+
#
|
19
|
+
class Template
|
20
|
+
|
21
|
+
# @attribute [r] log
|
22
|
+
# @return [String] the log from the last xelatex run
|
23
|
+
# @attribute [r] pass_count
|
24
|
+
# @return [Fixnum] how many passes it took to compile the tex into a PDF
|
25
|
+
attr_reader :log, :pass_count
|
26
|
+
# @attribute [rw] layout
|
27
|
+
# @return [String] path to a file to use for layout
|
28
|
+
attr_accessor :layout
|
29
|
+
|
30
|
+
# create a new Template
|
31
|
+
# @param view_file [String] path to the latex template
|
32
|
+
# @option options [String] :layout path to a latex template that calls yield
|
33
|
+
# @option options [Hash] :data an instance variable will be created in the view for each key/value pair
|
34
|
+
def initialize( view_file, options={} )
|
35
|
+
@data = options[:data] || {}
|
36
|
+
@layout = options[:layout]
|
37
|
+
@view = Pathname.new( view_file )
|
38
|
+
@log = ''
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sets the data to be used for the template
|
42
|
+
# @param hash [Hash] data to set for the template
|
43
|
+
# An instance variable will be created for the template
|
44
|
+
# for each key in the hash with it's value set accordingly
|
45
|
+
def data=( hash )
|
46
|
+
@data = hash
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [String] the suggested filename for this template.
|
50
|
+
# It removes the extension from the name and replaces it with '.pdf'
|
51
|
+
def suggested_filename
|
52
|
+
@view.basename.to_s.gsub(/\..*$/, '.pdf')
|
53
|
+
end
|
54
|
+
|
55
|
+
# Save the PDF to the file
|
56
|
+
# @param file [String,IO] if file is a String, the PDF is moved to the path indicated (most efficient).
|
57
|
+
# Otherwise, file is considered an instance of IO, and write is called on it with the PDF contents
|
58
|
+
# @return [String,IO] the file
|
59
|
+
def to_file( file = suggested_filename )
|
60
|
+
execute do | contents |
|
61
|
+
if file.is_a?(String)
|
62
|
+
FileUtils.mv contents, file
|
63
|
+
else
|
64
|
+
file.write contents.read
|
65
|
+
pdf.rewind
|
66
|
+
end
|
67
|
+
end
|
68
|
+
file
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [StringIO] containing the the PDF
|
72
|
+
def to_stringio
|
73
|
+
to_file( ::ErbLatex::StringIO.new(suggested_filename) ).rewind
|
74
|
+
end
|
75
|
+
|
76
|
+
# Compile the Latex template into a PDF file
|
77
|
+
# @yield [Pathname] complete path to the PDF file
|
78
|
+
# @raise [LatexError] if the xelatex process does not complete successfully
|
79
|
+
def execute
|
80
|
+
latex = compile_latex
|
81
|
+
Dir.mktmpdir do | dir |
|
82
|
+
@pass_count = 0
|
83
|
+
@log = ''
|
84
|
+
success = false
|
85
|
+
while log_suggests_rerunning? && @pass_count < 5
|
86
|
+
@pass_count += 1
|
87
|
+
success = execute_xelatex(latex,dir)
|
88
|
+
end
|
89
|
+
pdf_file = Pathname.new(dir).join( "output.pdf" )
|
90
|
+
if success && pdf_file.exist?
|
91
|
+
yield pdf_file
|
92
|
+
else
|
93
|
+
errors = @log.scan(/\*\!\s(.*?)\n\s*\n/m).map{|e| e.first.gsub(/\n/,'') }.join("; ")
|
94
|
+
raise LatexError.new( errors.empty? ? "xelatex compile error" : errors, @log )
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [Pathname] layout file
|
100
|
+
def layout_file
|
101
|
+
Pathname.new( layout )
|
102
|
+
end
|
103
|
+
|
104
|
+
# Runs the ERB pre-process on the latex file
|
105
|
+
# @return [String] latex with ERB substitutions performed
|
106
|
+
# @raise [LatexError] if the xelatex process does not complete successfully
|
107
|
+
def compile_latex
|
108
|
+
begin
|
109
|
+
context = ErbLatex::Context.new( @view.dirname, @data )
|
110
|
+
content = ERB.new( @view.read, 0, '-' ).result( context.getBinding )
|
111
|
+
if layout
|
112
|
+
ERB.new( layout_file.read, nil, '-' ).result( context.getBinding{
|
113
|
+
content
|
114
|
+
})
|
115
|
+
else
|
116
|
+
content
|
117
|
+
end
|
118
|
+
rescue RuntimeError,LocalJumpError=>e
|
119
|
+
raise LatexError.new( "ERB compile raised #{e.class} on #{@view}", e.backtrace )
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# @return [Boolean] True if the log is empty(not ran yet), or contains the string "Rerun"
|
126
|
+
def log_suggests_rerunning?
|
127
|
+
@log.empty? || !! ( @log =~ /Rerun/ )
|
128
|
+
end
|
129
|
+
|
130
|
+
# Execute xelatex on the file.
|
131
|
+
# @param latex [String] contents of the template after running ERB on it
|
132
|
+
# @param dir [String] path to the temporary working directory
|
133
|
+
def execute_xelatex( latex, dir )
|
134
|
+
success = false
|
135
|
+
@log = ''
|
136
|
+
Open3.popen2e( ErbLatex.xelatex_binary,
|
137
|
+
"--no-shell-escape", "-shell-restricted",
|
138
|
+
"-jobname=output", "-output-directory=#{dir}",
|
139
|
+
) do |stdin, output, wait_thr|
|
140
|
+
stdin.write latex
|
141
|
+
stdin.close
|
142
|
+
@log = output.read.strip
|
143
|
+
success = ( 0 == wait_thr.value )
|
144
|
+
end
|
145
|
+
success
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
class ErbLatexTest < MiniTest::Test
|
5
|
+
|
6
|
+
|
7
|
+
def test_document
|
8
|
+
tmpl = ErbLatex::Template.new( document(:valid) )
|
9
|
+
tmpl.to_file tmp_output_file
|
10
|
+
assert_match "This is a very simple file", text_output
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_errors
|
14
|
+
tmpl = ErbLatex::Template.new( document(:with_error) )
|
15
|
+
assert_raises(ErbLatex::LatexError) do
|
16
|
+
tmpl.to_stringio
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_layout
|
21
|
+
tmpl = ErbLatex::Template.new( document(:body),
|
22
|
+
:layout => document(:layout),
|
23
|
+
:data => { :sentence=>"hello, this is doge" }
|
24
|
+
)
|
25
|
+
tmpl.to_file tmp_output_file
|
26
|
+
assert_match "hello, this is doge", text_output
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_multiple_runs
|
30
|
+
tmpl = ErbLatex::Template.new( document(:multi_page) )
|
31
|
+
tmpl.to_file tmp_output_file
|
32
|
+
assert_equal 2, tmpl.pass_count
|
33
|
+
text = text_output
|
34
|
+
assert_match "Page 1 of 3", text
|
35
|
+
assert_match "Page 2 of 3", text
|
36
|
+
assert_match "Page 3 of 3", text
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_partials
|
40
|
+
tmpl = ErbLatex::Template.new( document(:with_partial) )
|
41
|
+
tmpl.to_file tmp_output_file
|
42
|
+
assert_match "a test ’ of a partial", text_output
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= @sentence %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
\documentclass[oneside,12pt]{memoir}
|
2
|
+
\usepackage{xltxtra}
|
3
|
+
\usepackage{lastpage}
|
4
|
+
\usepackage[T1]{fontenc}
|
5
|
+
\makepagestyle{plain}
|
6
|
+
\makeoddfoot{plain}{}{}{Page \thepage\ of \pageref{LastPage}}
|
7
|
+
\makeevenfoot{plain}{}{}{Page \thepage\ of \pageref{LastPage}}
|
8
|
+
\begin{document}
|
9
|
+
\pagestyle{plain}
|
10
|
+
Page 1
|
11
|
+
\newpage
|
12
|
+
Page 2
|
13
|
+
\newpage
|
14
|
+
Page 3
|
15
|
+
\newpage
|
16
|
+
\end{document}
|
@@ -0,0 +1 @@
|
|
1
|
+
<%=q @sentence %>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
\documentclass[12pt]{article}
|
2
|
+
\begin{document}
|
3
|
+
|
4
|
+
This is a very simple file, though it does include some mathematical
|
5
|
+
symbols, $\beta, x$ and $y$, and some equations,
|
6
|
+
\begin{equation}
|
7
|
+
\frac{1}{2} + \frac{1}{5} = \frac{7}{10}.
|
8
|
+
\end{equation}
|
9
|
+
|
10
|
+
The equations are automatically numbered:
|
11
|
+
\begin{equation}
|
12
|
+
1 + 1 = 2 \Rightarrow E = m c^2.
|
13
|
+
\end{equation}
|
14
|
+
|
15
|
+
\LaTeX\ can handle complicated mathematical expressions.
|
16
|
+
\begin{equation}
|
17
|
+
\int_0^\infty \cos (k t) e^{-s t} d t = \frac{s}{s^2 + k^2}
|
18
|
+
\end{equation}
|
19
|
+
if $s > 0$.
|
20
|
+
\begin{equation}
|
21
|
+
e^z = \sum_{n=0}^\infty \frac{ z^n}{n!} .
|
22
|
+
\end{equation}
|
23
|
+
|
24
|
+
Leave a blank line in your file when you want a new paragraph.
|
25
|
+
\LaTeX\ will automatically
|
26
|
+
arrange your text
|
27
|
+
into tidy lines of
|
28
|
+
even length, even
|
29
|
+
if the
|
30
|
+
original text in the .tex file is a mess.
|
31
|
+
|
32
|
+
\sffamily\textregistered\textcopyright <%= @person %>
|
33
|
+
|
34
|
+
\end{document}
|
@@ -0,0 +1 @@
|
|
1
|
+
This document is in no way a valid LaTex Document
|
data/test/helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
|
11
|
+
#require 'test/unit'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'erb_latex'
|
16
|
+
|
17
|
+
class MiniTest::Test
|
18
|
+
|
19
|
+
def document( name )
|
20
|
+
File.expand_path(File.join(File.dirname(__FILE__), "fixtures/#{name}.tex.erb"))
|
21
|
+
end
|
22
|
+
|
23
|
+
def tmp_output_file
|
24
|
+
File.expand_path( File.join( File.dirname(__FILE__), "tmp/output.pdf") )
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def text_output
|
29
|
+
`pdftotext #{tmp_output_file} -`
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
[<a href="https://github.com/nathanstitt/code/erb_latex/tree/master/<%= object.file %><% if object.line %>#L<%= object.line %><% end %>">github</a>]
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: erb_latex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Stitt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: growl
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.8'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.2'
|
97
|
+
description: Applies ERB template processing to a Latex file and compiles it to a
|
98
|
+
PDF. Supports layouts, partials, and string escaping. Also supplies a Guard task
|
99
|
+
to watch for modifications and auto-building files.
|
100
|
+
email:
|
101
|
+
- nathan@stitt.org
|
102
|
+
executables: []
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".gitignore"
|
107
|
+
- Gemfile
|
108
|
+
- Guardfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- erb_latex.gemspec
|
113
|
+
- lib/erb_latex.rb
|
114
|
+
- lib/erb_latex/context.rb
|
115
|
+
- lib/erb_latex/errors.rb
|
116
|
+
- lib/erb_latex/guard.rb
|
117
|
+
- lib/erb_latex/guard_runner.rb
|
118
|
+
- lib/erb_latex/stringio.rb
|
119
|
+
- lib/erb_latex/template.rb
|
120
|
+
- lib/erb_latex/version.rb
|
121
|
+
- test/erb_latex_test.rb
|
122
|
+
- test/fixtures/body.tex.erb
|
123
|
+
- test/fixtures/layout.tex.erb
|
124
|
+
- test/fixtures/multi_page.tex.erb
|
125
|
+
- test/fixtures/partial.tex.erb
|
126
|
+
- test/fixtures/valid.tex.erb
|
127
|
+
- test/fixtures/with_error.tex.erb
|
128
|
+
- test/fixtures/with_partial.tex.erb
|
129
|
+
- test/helper.rb
|
130
|
+
- yard_ext/templates/default/method_details/html/github_link.erb
|
131
|
+
- yard_ext/templates/default/method_details/setup.rb
|
132
|
+
homepage: http://nathan.stitt.org/code/erb-latex/
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
metadata: {}
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 2.2.1
|
153
|
+
signing_key:
|
154
|
+
specification_version: 4
|
155
|
+
summary: Applies ERB template processing to a Latex file and compiles it to a PDF
|
156
|
+
test_files:
|
157
|
+
- test/erb_latex_test.rb
|
158
|
+
- test/fixtures/body.tex.erb
|
159
|
+
- test/fixtures/layout.tex.erb
|
160
|
+
- test/fixtures/multi_page.tex.erb
|
161
|
+
- test/fixtures/partial.tex.erb
|
162
|
+
- test/fixtures/valid.tex.erb
|
163
|
+
- test/fixtures/with_error.tex.erb
|
164
|
+
- test/fixtures/with_partial.tex.erb
|
165
|
+
- test/helper.rb
|
166
|
+
has_rdoc:
|