erb_latex 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|