review-retrovert 0.3.3 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/retrovert.yml +39 -0
- data/.gitignore +2 -2
- data/.ruby-version +1 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +67 -1
- data/README.md +6 -3
- data/lib/review/retrovert/converter.rb +231 -6
- data/lib/review/retrovert/version.rb +1 -1
- data/review-retrovert.gemspec +1 -1
- data/testdata/mybook/.gitignore +7 -0
- data/testdata/mybook/README.md +43 -0
- data/testdata/mybook/Rakefile +16 -0
- data/testdata/mybook/catalog.yml +33 -0
- data/testdata/mybook/config-starter.yml +139 -0
- data/testdata/mybook/config.yml +375 -0
- data/testdata/mybook/contents/00-preface.re +83 -0
- data/testdata/mybook/contents/01-install.re +272 -0
- data/testdata/mybook/contents/02-tutorial.re +1008 -0
- data/testdata/mybook/contents/03-syntax.re +2613 -0
- data/testdata/mybook/contents/04-customize.re +728 -0
- data/testdata/mybook/contents/05-faq.re +328 -0
- data/testdata/mybook/contents/06-bestpractice.re +971 -0
- data/testdata/mybook/contents/91-compare.re +18 -0
- data/testdata/mybook/contents/92-filelist.re +119 -0
- data/testdata/mybook/contents/93-background.re +267 -0
- data/testdata/mybook/contents/99-postface.re +38 -0
- data/testdata/mybook/css/normalize.css +349 -0
- data/testdata/mybook/css/webstyle.css +514 -0
- data/testdata/mybook/images/03-syntax/favicon-16x16.png +0 -0
- data/testdata/mybook/images/03-syntax/figure_heretop.png +0 -0
- data/testdata/mybook/images/03-syntax/tw-icon1.jpg +0 -0
- data/testdata/mybook/images/03-syntax/tw-icon2.jpg +0 -0
- data/testdata/mybook/images/03-syntax/tw-icon3.jpg +0 -0
- data/testdata/mybook/images/03-syntax/tw-icon4.jpg +0 -0
- data/testdata/mybook/images/04-customize/caption_pagebreak.png +0 -0
- data/testdata/mybook/images/04-customize/chaptitlepage_sample.png +0 -0
- data/testdata/mybook/images/05-faq/codeblock_rpadding1.png +0 -0
- data/testdata/mybook/images/05-faq/codeblock_rpadding2.png +0 -0
- data/testdata/mybook/images/06-bestpractice/figure_heretop.png +0 -0
- data/testdata/mybook/images/06-bestpractice/font_beramono.png +0 -0
- data/testdata/mybook/images/06-bestpractice/heading_design1.png +0 -0
- data/testdata/mybook/images/06-bestpractice/heading_design2.png +0 -0
- data/testdata/mybook/images/06-bestpractice/margin_book.png +0 -0
- data/testdata/mybook/images/06-bestpractice/multiline-title.png +0 -0
- data/testdata/mybook/images/06-bestpractice/preface_numbered.png +0 -0
- data/testdata/mybook/images/06-bestpractice/program_border.png +0 -0
- data/testdata/mybook/images/06-bestpractice/sechead_design_4.png +0 -0
- data/testdata/mybook/images/06-bestpractice/titlepage-samples.png +0 -0
- data/testdata/mybook/images/93-background/bug913.png +0 -0
- data/testdata/mybook/images/93-background/slide2.png +0 -0
- data/testdata/mybook/images/cover_a5.pdf +0 -0
- data/testdata/mybook/images/cover_b5.pdf +0 -0
- data/testdata/mybook/images/tw-icon.jpg +0 -0
- data/testdata/mybook/layouts/layout.epub.erb +29 -0
- data/testdata/mybook/layouts/layout.html5.erb +106 -0
- data/testdata/mybook/layouts/layout.tex.erb +546 -0
- data/testdata/mybook/lib/hooks/beforetexcompile.rb +55 -0
- data/testdata/mybook/lib/ruby/review-builder.rb +503 -0
- data/testdata/mybook/lib/ruby/review-cli.rb +58 -0
- data/testdata/mybook/lib/ruby/review-compiler.rb +523 -0
- data/testdata/mybook/lib/ruby/review-epubmaker.rb +606 -0
- data/testdata/mybook/lib/ruby/review-htmlbuilder.rb +661 -0
- data/testdata/mybook/lib/ruby/review-latexbuilder.rb +782 -0
- data/testdata/mybook/lib/ruby/review-maker.rb +235 -0
- data/testdata/mybook/lib/ruby/review-monkeypatch.rb +91 -0
- data/testdata/mybook/lib/ruby/review-pdfmaker.rb +468 -0
- data/testdata/mybook/lib/ruby/review-textbuilder.rb +36 -0
- data/testdata/mybook/lib/ruby/review-tocparser.rb +285 -0
- data/testdata/mybook/lib/ruby/review-webmaker.rb +433 -0
- data/testdata/mybook/lib/tasks/mytasks.rake +31 -0
- data/testdata/mybook/lib/tasks/review.rake +142 -0
- data/testdata/mybook/lib/tasks/review.rake.orig +72 -0
- data/testdata/mybook/lib/tasks/starter.rake +326 -0
- data/testdata/mybook/locale.yml +6 -0
- data/testdata/mybook/review-ext.rb +206 -0
- data/testdata/mybook/sty/jumoline.sty +310 -0
- data/testdata/mybook/sty/mycolophon.sty +81 -0
- data/testdata/mybook/sty/mystyle.sty +8 -0
- data/testdata/mybook/sty/mytextsize.sty +61 -0
- data/testdata/mybook/sty/mytitlepage.sty +103 -0
- data/testdata/mybook/sty/reviewmacro.sty +60 -0
- data/testdata/mybook/sty/starter-codeblock.sty +332 -0
- data/testdata/mybook/sty/starter-color.sty +79 -0
- data/testdata/mybook/sty/starter-font.sty +112 -0
- data/testdata/mybook/sty/starter-heading.sty +514 -0
- data/testdata/mybook/sty/starter-note.sty +127 -0
- data/testdata/mybook/sty/starter-section.sty +262 -0
- data/testdata/mybook/sty/starter-toc.sty +72 -0
- data/testdata/mybook/sty/starter.sty +554 -0
- data/testdata/mybook/style.css +597 -0
- metadata +100 -3
@@ -0,0 +1,235 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
##
|
4
|
+
## ReVIEW::PDFMaker から他のMakerでも使える機能を分離する
|
5
|
+
##
|
6
|
+
|
7
|
+
require 'pathname'
|
8
|
+
|
9
|
+
require 'review/logger'
|
10
|
+
require 'review/version'
|
11
|
+
require 'review/configure'
|
12
|
+
require 'review/yamlloader'
|
13
|
+
require 'review/i18n'
|
14
|
+
require 'review/book'
|
15
|
+
require 'review/template'
|
16
|
+
|
17
|
+
require_relative './review-cli'
|
18
|
+
|
19
|
+
|
20
|
+
module ReVIEW
|
21
|
+
|
22
|
+
|
23
|
+
class Maker
|
24
|
+
|
25
|
+
attr_accessor :config, :basedir
|
26
|
+
attr_reader :maker_name
|
27
|
+
|
28
|
+
def initialize(config_filename, optional_values={})
|
29
|
+
@basedir = File.dirname(config_filename)
|
30
|
+
@basehookdir = File.absolute_path(@basedir)
|
31
|
+
@logger = ReVIEW.logger
|
32
|
+
@maker_name = self.class.name.split('::')[-1].downcase()
|
33
|
+
@config = load_config(config_filename, optional_values)
|
34
|
+
@config.maker = @maker_name
|
35
|
+
@config.check_version(ReVIEW::VERSION) # may raise ReVIEW::ConfigError
|
36
|
+
#
|
37
|
+
yamlfile = File.join(@basedir, 'config-starter.yml')
|
38
|
+
yamldata = File.open(yamlfile) {|f| YAML.safe_load(f) }
|
39
|
+
@starter_config = yamldata['starter']
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.execute(*args)
|
43
|
+
script_name = self.const_get(:SCRIPT_NAME)
|
44
|
+
cli = CLI.new(script_name)
|
45
|
+
begin
|
46
|
+
cmdopts, config_filename = cli.parse_opts(args)
|
47
|
+
rescue OptionParser::InvalidOption => ex
|
48
|
+
$stderr.puts "Error: #{ex.message}"
|
49
|
+
$stderr.puts cli.help_message()
|
50
|
+
return 1
|
51
|
+
end
|
52
|
+
if cmdopts['help']
|
53
|
+
puts cli.help_message()
|
54
|
+
return 0
|
55
|
+
end
|
56
|
+
#
|
57
|
+
maker = nil
|
58
|
+
begin
|
59
|
+
maker = self.new(config_filename, cmdopts)
|
60
|
+
maker.generate()
|
61
|
+
rescue ApplicationError, ReVIEW::ConfigError => ex
|
62
|
+
raise if maker && maker.config['debug']
|
63
|
+
error(ex.message)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate()
|
68
|
+
raise NotImplementedError.new("#{self.class.name}#generate(): not implemented yet.")
|
69
|
+
end
|
70
|
+
|
71
|
+
def debug?
|
72
|
+
return @config['debug']
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def load_config(config_filename, additionals={})
|
78
|
+
begin
|
79
|
+
config_data = ReVIEW::YAMLLoader.new.load_file(config_filename)
|
80
|
+
rescue => ex
|
81
|
+
error "yaml error #{ex.message}"
|
82
|
+
end
|
83
|
+
#
|
84
|
+
config = ReVIEW::Configure.values
|
85
|
+
config.deep_merge!(config_data)
|
86
|
+
# YAML configs will be overridden by command line options.
|
87
|
+
config.deep_merge!(additionals)
|
88
|
+
I18n.setup(config['language'])
|
89
|
+
#
|
90
|
+
if ENV['STARTER_CHAPTER'].present?
|
91
|
+
modify_config(config)
|
92
|
+
end
|
93
|
+
#
|
94
|
+
return config
|
95
|
+
end
|
96
|
+
|
97
|
+
def modify_config(config)
|
98
|
+
config.deep_merge!({
|
99
|
+
'toc' => false,
|
100
|
+
'cover' => false,
|
101
|
+
'coverimage' => nil,
|
102
|
+
'titlepage' => false,
|
103
|
+
'titlefile' => nil,
|
104
|
+
'colophon' => nil,
|
105
|
+
'pdfmaker' => {
|
106
|
+
'colophon' => nil,
|
107
|
+
'titlepage' => nil,
|
108
|
+
'coverimage' => nil,
|
109
|
+
},
|
110
|
+
})
|
111
|
+
end
|
112
|
+
|
113
|
+
def load_book()
|
114
|
+
book = ReVIEW::Book.load(@basedir) # also loads 'review-ext.rb'
|
115
|
+
book.config = @config
|
116
|
+
return book
|
117
|
+
end
|
118
|
+
|
119
|
+
def system_or_raise(*args)
|
120
|
+
Kernel.system(*args) or raise("failed to run command: #{args.join(' ')}")
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.error(msg)
|
124
|
+
$stderr.puts "ERROR: #{File.basename($PROGRAM_NAME, '.*')}: #{msg}"
|
125
|
+
exit 1
|
126
|
+
end
|
127
|
+
|
128
|
+
def error(msg)
|
129
|
+
@logger.error "#{File.basename($PROGRAM_NAME, '.*')}: #{msg}"
|
130
|
+
exit 1
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.warn(msg)
|
134
|
+
$stderr.puts "Waring: #{File.basename($PROGRAM_NAME, '.*')}: #{msg}"
|
135
|
+
end
|
136
|
+
|
137
|
+
def warn(msg)
|
138
|
+
@logger.warn "#{File.basename($PROGRAM_NAME, '.*')}: #{msg}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def empty_dir(path)
|
142
|
+
if File.directory?(path)
|
143
|
+
Pathname.new(path).children.each {|x| x.rmtree() }
|
144
|
+
end
|
145
|
+
path
|
146
|
+
end
|
147
|
+
|
148
|
+
def run_cmd(cmd)
|
149
|
+
puts ""
|
150
|
+
puts "[#{@maker_name}]$ #{cmd}"
|
151
|
+
return Kernel.system(cmd)
|
152
|
+
end
|
153
|
+
|
154
|
+
def run_cmd!(cmd)
|
155
|
+
run_cmd(cmd) or raise("failed to run command: #{cmd}")
|
156
|
+
end
|
157
|
+
|
158
|
+
def call_hook(hookname)
|
159
|
+
d = @config[@maker_name]
|
160
|
+
return unless d.is_a?(Hash)
|
161
|
+
return unless d[hookname]
|
162
|
+
if ENV['REVIEW_SAFE_MODE'].to_i & 1 > 0
|
163
|
+
warn 'hook configuration is prohibited in safe mode. ignored.'
|
164
|
+
return
|
165
|
+
end
|
166
|
+
## hookname が文字列の配列なら、それらを全部実行する
|
167
|
+
basehookdir = @basehookdir
|
168
|
+
[d[hookname]].flatten.each do |hook|
|
169
|
+
script = File.absolute_path(hook, basehookdir)
|
170
|
+
## 拡張子が .rb なら、rubyコマンドで実行する(ファイルに実行属性がなくてもよい)
|
171
|
+
if script.end_with?('.rb')
|
172
|
+
ruby = ruby_fullpath()
|
173
|
+
ruby = "ruby" unless File.exist?(ruby)
|
174
|
+
run_cmd!("#{ruby} #{script} #{Dir.pwd} #{basehookdir}")
|
175
|
+
else
|
176
|
+
run_cmd!("#{script} #{Dir.pwd} #{basehookdir}")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def ruby_fullpath
|
182
|
+
c = RbConfig::CONFIG
|
183
|
+
return File.join(c['bindir'], c['ruby_install_name']) + c['EXEEXT'].to_s
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
class BaseRenderer
|
190
|
+
include ERB::Util
|
191
|
+
|
192
|
+
def initialize(config, book, basedir, starter_config)
|
193
|
+
@config = config
|
194
|
+
@book = book
|
195
|
+
@basedir = basedir
|
196
|
+
@starter_config = starter_config
|
197
|
+
end
|
198
|
+
|
199
|
+
def render(context={})
|
200
|
+
context.each {|k, v| instance_variable_set("@#{k}", v) }
|
201
|
+
tmpl_filename ||= layout_template_name()
|
202
|
+
tmpl_filepath = find_layout_template(tmpl_filename)
|
203
|
+
return render_template(tmpl_filepath)
|
204
|
+
end
|
205
|
+
|
206
|
+
def generate_file(filepath, context={})
|
207
|
+
content = render(context)
|
208
|
+
File.open(filepath, 'wb') {|f| f.write(content) }
|
209
|
+
return content
|
210
|
+
end
|
211
|
+
|
212
|
+
protected
|
213
|
+
|
214
|
+
def layout_template_name()
|
215
|
+
raise NotImplementedError.new("#{self.class.name}#template_name(): not implemented yet.")
|
216
|
+
end
|
217
|
+
|
218
|
+
def find_layout_template(filename)
|
219
|
+
filepath = File.join(@basedir, 'layouts', File.basename(filename))
|
220
|
+
return filepath if File.exist?(filepath)
|
221
|
+
return File.expand_path(filename, ReVIEW::Template::TEMPLATE_DIR)
|
222
|
+
end
|
223
|
+
|
224
|
+
def render_template(filepath)
|
225
|
+
return ReVIEW::Template.load(filepath, '-').result(binding())
|
226
|
+
end
|
227
|
+
|
228
|
+
def i18n(*args)
|
229
|
+
ReVIEW::I18n.t(*args)
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
##
|
4
|
+
## change ReVIEW source code
|
5
|
+
##
|
6
|
+
|
7
|
+
require_relative './review-compiler' ## Compilerクラスを修正
|
8
|
+
require_relative './review-builder' ## Builderクラスを修正
|
9
|
+
require_relative './review-latexbuilder' ## LaTeX用の機能を修正
|
10
|
+
require_relative './review-htmlbuilder' ## HTML用の機能を修正
|
11
|
+
require_relative './review-textbuilder' ## テキスト用の機能を修正
|
12
|
+
#require_relative './review-pdfmaker' ## PDF用の機能を修正 (for Rake task)
|
13
|
+
require_relative './review-webmaker' ## Webページ用の機能を修正
|
14
|
+
require_relative './review-tocparser' ## Web用目次作成機能を修正
|
15
|
+
|
16
|
+
|
17
|
+
module ReVIEW
|
18
|
+
|
19
|
+
|
20
|
+
Book::Base.class_eval do
|
21
|
+
|
22
|
+
## 環境変数 $STARTER_CHAPTER で指定された章だけをコンパイルするよう改造
|
23
|
+
def each_input_file()
|
24
|
+
env_starter = ENV['STARTER_CHAPTER']
|
25
|
+
env_starter = nil unless env_starter.present?
|
26
|
+
found = false
|
27
|
+
self.parts.each do |part|
|
28
|
+
yield part, nil if part.name.present? && !env_starter
|
29
|
+
part.chapters.each do |chap|
|
30
|
+
next if env_starter && env_starter != chap.name
|
31
|
+
found = true
|
32
|
+
yield nil, chap
|
33
|
+
end
|
34
|
+
end
|
35
|
+
if env_starter && !found
|
36
|
+
raise "ERROR: chapter file '#{env_starter}.re' not found. ($STARTER_CHAPTER='#{env_starter}')"
|
37
|
+
end
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
class PDFMaker
|
45
|
+
|
46
|
+
### original: 2.4, 2.5
|
47
|
+
#def call_hook(hookname)
|
48
|
+
# return if !@config['pdfmaker'].is_a?(Hash) || @config['pdfmaker'][hookname].nil?
|
49
|
+
# hook = File.absolute_path(@config['pdfmaker'][hookname], @basehookdir)
|
50
|
+
# if ENV['REVIEW_SAFE_MODE'].to_i & 1 > 0
|
51
|
+
# warn 'hook configuration is prohibited in safe mode. ignored.'
|
52
|
+
# else
|
53
|
+
# system_or_raise("#{hook} #{Dir.pwd} #{@basehookdir}")
|
54
|
+
# end
|
55
|
+
#end
|
56
|
+
### /original
|
57
|
+
|
58
|
+
def call_hook(hookname) # review-pdfmaker がエラーにならないために
|
59
|
+
d = @config['pdfmaker']
|
60
|
+
return unless d.is_a?(Hash)
|
61
|
+
return unless d[hookname]
|
62
|
+
if ENV['REVIEW_SAFE_MODE'].to_i & 1 > 0
|
63
|
+
warn 'hook configuration is prohibited in safe mode. ignored.'
|
64
|
+
return
|
65
|
+
end
|
66
|
+
## hookname が文字列の配列なら、それらを全部実行する
|
67
|
+
basehookdir = @basehookdir
|
68
|
+
[d[hookname]].flatten.each do |hook|
|
69
|
+
script = File.absolute_path(hook, basehookdir)
|
70
|
+
## 拡張子が .rb なら、rubyコマンドで実行する(ファイルに実行属性がなくてもよい)
|
71
|
+
if script.end_with?('.rb')
|
72
|
+
ruby = ruby_fullpath()
|
73
|
+
ruby = "ruby" unless File.exist?(ruby)
|
74
|
+
system_or_raise(ruby, script, Dir.pwd, basehookdir)
|
75
|
+
else
|
76
|
+
system_or_raise(script, Dir.pwd, basehookdir)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
def ruby_fullpath
|
83
|
+
require 'rbconfig'
|
84
|
+
c = RbConfig::CONFIG
|
85
|
+
return File.join(c['bindir'], c['ruby_install_name']) + c['EXEEXT'].to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
end unless defined?(Maker)
|
89
|
+
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,468 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
##
|
4
|
+
## ReVIEW::PDFMakerのソースコードがいろいろひどいので、書き直す。
|
5
|
+
## 具体的には、もとのコードではPDFMakerクラスの責務が大きすぎるので、分割する。
|
6
|
+
## ・CLIクラス … コマンドラインオプションの解析
|
7
|
+
## ・Makerクラス … PDFに限定されない機能(設定ファイルの読み込み等)
|
8
|
+
## ・PDFMakerクラス … 原稿ファイルを読み込んでPDFファイルを生成する機能
|
9
|
+
## ・LATEXRendererクラス … layout.tex.erbのレンダリング
|
10
|
+
##
|
11
|
+
|
12
|
+
require 'date'
|
13
|
+
require 'pathname'
|
14
|
+
require 'digest/md5'
|
15
|
+
require 'rbconfig'
|
16
|
+
require 'review/pdfmaker'
|
17
|
+
|
18
|
+
require_relative './review-cli'
|
19
|
+
require_relative './review-maker'
|
20
|
+
|
21
|
+
|
22
|
+
module ReVIEW
|
23
|
+
|
24
|
+
|
25
|
+
remove_const :PDFMaker if defined?(PDFMaker)
|
26
|
+
|
27
|
+
|
28
|
+
class PDFMaker < Maker
|
29
|
+
|
30
|
+
SCRIPT_NAME = "review-pdfmaker"
|
31
|
+
|
32
|
+
def generate()
|
33
|
+
remove_old_file()
|
34
|
+
@build_dir = make_build_dir()
|
35
|
+
begin
|
36
|
+
book = load_book() # also loads 'review-ext.rb'
|
37
|
+
#
|
38
|
+
converter = ReVIEW::Converter.new(book, new_builder())
|
39
|
+
errors = create_input_files(converter, book)
|
40
|
+
if errors && !errors.empty?
|
41
|
+
FileUtils.rm_f pdf_filepath() ###
|
42
|
+
handle_compile_errors(errors)
|
43
|
+
end
|
44
|
+
#
|
45
|
+
prepare_files()
|
46
|
+
tmp_file = build_pdf(book)
|
47
|
+
copy_outfile(tmp_file, pdf_filepath())
|
48
|
+
ensure
|
49
|
+
remove_entry_secure(@build_dir) unless @config['debug']
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def new_builder()
|
56
|
+
return ReVIEW::LATEXBuilder.new
|
57
|
+
end
|
58
|
+
|
59
|
+
def pdf_filepath
|
60
|
+
return File.join(@basedir, "#{@config['bookname']}.pdf")
|
61
|
+
end
|
62
|
+
|
63
|
+
def remove_old_file
|
64
|
+
#FileUtils.rm_f(pdf_filepath()) # don't delete pdf file
|
65
|
+
end
|
66
|
+
|
67
|
+
def make_build_dir()
|
68
|
+
dir = "#{@config['bookname']}-pdf"
|
69
|
+
if !File.directory?(dir)
|
70
|
+
Dir.mkdir(dir)
|
71
|
+
else
|
72
|
+
## 目次と相互参照に関するファイルだけを残し、あとは削除
|
73
|
+
Pathname.new(dir).children.each do |x|
|
74
|
+
next if x.basename.to_s =~ /\.(aux|toc|out|mtc\d*|maf)\z/
|
75
|
+
x.rmtree()
|
76
|
+
end
|
77
|
+
end
|
78
|
+
return dir
|
79
|
+
end
|
80
|
+
|
81
|
+
def create_input_files(converter, book)
|
82
|
+
errors = []
|
83
|
+
book.each_input_file do |part, chap|
|
84
|
+
if part
|
85
|
+
err = output_chaps(converter, part.name) if part.file?
|
86
|
+
elsif chap
|
87
|
+
filename = File.basename(chap.path, '.*')
|
88
|
+
err = output_chaps(converter, filename)
|
89
|
+
end
|
90
|
+
errors << err if err
|
91
|
+
end
|
92
|
+
return errors
|
93
|
+
end
|
94
|
+
|
95
|
+
def output_chaps(converter, filename)
|
96
|
+
contdir = @config['contentdir']
|
97
|
+
infile = "#{filename}.re"
|
98
|
+
infile = File.join(contdir, infile) if contdir.present?
|
99
|
+
outfile = "#{filename}.tex"
|
100
|
+
$stderr.puts "compiling #{outfile}"
|
101
|
+
begin
|
102
|
+
converter.convert(infile, File.join(@build_dir, outfile))
|
103
|
+
nil
|
104
|
+
## 文法エラーだけキャッチし、それ以外のエラーはキャッチしないよう変更
|
105
|
+
## (LATEXBuilderで起こったエラーのスタックトレースを表示する)
|
106
|
+
rescue ApplicationError => ex
|
107
|
+
warn "compile error in #{outfile} (#{ex.class})"
|
108
|
+
warn ex.message
|
109
|
+
ex.message
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def handle_compile_errors(errors)
|
114
|
+
return if errors.nil? || errors.empty?
|
115
|
+
if @config['ignore-errors']
|
116
|
+
$stderr.puts 'compile error, but try to generate PDF file'
|
117
|
+
else
|
118
|
+
error 'compile error, No PDF file output.'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def build_pdf(book)
|
123
|
+
base = "book"
|
124
|
+
#
|
125
|
+
texfile = File.join(@build_dir, "#{base}.tex")
|
126
|
+
new_renderer(book).generate_file(texfile)
|
127
|
+
#
|
128
|
+
Dir.chdir(@build_dir) do
|
129
|
+
if ENV['REVIEW_SAFE_MODE'].to_i & 4 > 0
|
130
|
+
warn 'command configuration is prohibited in safe mode. ignored.'
|
131
|
+
config = ReVIEW::Configure
|
132
|
+
else
|
133
|
+
config = @config
|
134
|
+
end
|
135
|
+
compile_latex_files(base, config)
|
136
|
+
end
|
137
|
+
#
|
138
|
+
return File.join(@build_dir, "#{base}.pdf")
|
139
|
+
end
|
140
|
+
|
141
|
+
def absolute_path(filename)
|
142
|
+
return nil unless filename.present?
|
143
|
+
filepath = File.absolute_path(filename, @basedir)
|
144
|
+
return File.exist?(fielpath) ? filepath : nil
|
145
|
+
end
|
146
|
+
|
147
|
+
def compile_latex_files(base, config)
|
148
|
+
latex_cmd = config['texcommand']
|
149
|
+
latex_opt = config['texoptions']
|
150
|
+
dvipdf_cmd = config['dvicommand']
|
151
|
+
dvipdf_opt = config['dvioptions']
|
152
|
+
mkidx_p = config['pdfmaker']['makeindex']
|
153
|
+
mkidx_cmd = config['pdfmaker']['makeindex_command']
|
154
|
+
mkidx_opt = config['pdfmaker']['makeindex_options']
|
155
|
+
mkidx_sty = absolute_path(config['pdfmaker']['makeindex_sty'])
|
156
|
+
mkidx_dic = absolute_path(config['pdfmaker']['makeindex_dic'])
|
157
|
+
#
|
158
|
+
latex = "#{latex_cmd} #{latex_opt}"
|
159
|
+
dvipdf = "#{dvipdf_cmd} #{dvipdf_opt}"
|
160
|
+
mkidx = "#{mkidx_cmd} #{mkidx_opt}"
|
161
|
+
mkidx << " -s #{mkidx_sty}" if mkidx_sty
|
162
|
+
mkidx << " -d #{mkidx_dic}" if mkidx_dic
|
163
|
+
#
|
164
|
+
call_hook('hook_beforetexcompile')
|
165
|
+
changed = run_latex!(latex, "#{base}.tex")
|
166
|
+
changed = run_latex!(latex, "#{base}.tex") if changed
|
167
|
+
changed = run_latex!(latex, "#{base}.tex") if changed
|
168
|
+
if mkidx_p # 索引を作る場合
|
169
|
+
call_hook('hook_beforemakeindex')
|
170
|
+
run_cmd!("#{mkidx} #{base}") if File.exist?("#{base}.idx")
|
171
|
+
call_hook('hook_aftermakeindex')
|
172
|
+
run_latex!(latex, "#{base}.tex")
|
173
|
+
end
|
174
|
+
call_hook('hook_aftertexcompile')
|
175
|
+
return unless File.exist?("#{base}.dvi")
|
176
|
+
run_cmd!("#{dvipdf} #{base}.dvi")
|
177
|
+
call_hook('hook_afterdvipdf')
|
178
|
+
end
|
179
|
+
|
180
|
+
## コンパイルメッセージを減らすために、uplatexコマンドをバッチモードで起動する。
|
181
|
+
## エラーがあったら、バッチモードにせずに再コンパイルしてエラーメッセージを出す。
|
182
|
+
def run_latex!(latex, file)
|
183
|
+
## *.auxファイルと*.tocファイルのハッシュ値を計算する。
|
184
|
+
## (「//list[?]」が生成するラベルはランダムなのでそれを削除してハッシュ値を計算する。)
|
185
|
+
delete_rexp = /^\\newlabel\{_\d+\}/
|
186
|
+
auxfile = file.sub(/\.tex\z/, '.aux')
|
187
|
+
tocfile = file.sub(/\.tex\z/, '.toc')
|
188
|
+
auxhash_old = _filehash(auxfile, delete_rexp)
|
189
|
+
tochash_old = _filehash(tocfile)
|
190
|
+
## invoke latex command with batchmode option in order to suppress
|
191
|
+
## compilation message (in other words, to be quiet mode).
|
192
|
+
ok = run_cmd("#{latex} -interaction=batchmode #{file}")
|
193
|
+
if ! ok
|
194
|
+
## latex command with batchmode option doesn't show any error,
|
195
|
+
## therefore we must invoke latex command again without batchmode option
|
196
|
+
## in order to show error.
|
197
|
+
$stderr.puts "*"
|
198
|
+
$stderr.puts "* latex command failed; retry without batchmode option to show error."
|
199
|
+
$stderr.puts "*"
|
200
|
+
run_cmd!("#{latex} #{file}")
|
201
|
+
end
|
202
|
+
## *.tocファイルのハッシュ値を計算し、コンパイル前と変わっていたらtrueを返す。
|
203
|
+
## (つまりもう一度コンパイルが必要ならtrueを返す。)
|
204
|
+
auxhash_new = _filehash(auxfile, delete_rexp)
|
205
|
+
tochash_new = _filehash(tocfile)
|
206
|
+
return auxhash_old.nil? || tochash_old.nil? \
|
207
|
+
|| auxhash_old != auxhash_new || tochash_old != tochash_new
|
208
|
+
## LaTeXのログファイルに「ラベルが変更された」と出ていたらtrueを返す。
|
209
|
+
## (つまりもう一度コンパイルが必要ならtrueを返す。)
|
210
|
+
## → この方法は、ページ番号が変わったことは検出できても、
|
211
|
+
## 章や節のタイトルが変わったことを検出できない。
|
212
|
+
#logfile = file.sub(/\.tex\z/, '.log')
|
213
|
+
#begin
|
214
|
+
# lines = File.open(logfile, 'r') {|f|
|
215
|
+
# f.grep(/^LaTeX Warning: Label\(s\) may have changed./)
|
216
|
+
# }
|
217
|
+
# return !lines.empty?
|
218
|
+
#rescue IOError
|
219
|
+
# return true
|
220
|
+
#end
|
221
|
+
end
|
222
|
+
|
223
|
+
def _filehash(filepath, delete_rexp=nil)
|
224
|
+
return nil unless File.exist?(filepath)
|
225
|
+
binary = File.open(filepath, 'rb') {|f| f.read() }
|
226
|
+
binary = binary.gsub(delete_rexp, '') if delete_rexp
|
227
|
+
return Digest::MD5.hexdigest(binary)
|
228
|
+
end
|
229
|
+
private :_filehash
|
230
|
+
|
231
|
+
## 開発用。LaTeXコンパイル回数を環境変数で指定する。
|
232
|
+
if ENV['STARTER_COMPILETIMES']
|
233
|
+
alias __run_latex! run_latex!
|
234
|
+
def run_latex!(latex, file)
|
235
|
+
n = ENV['STARTER_COMPILETIMES']
|
236
|
+
@_done ||= {}
|
237
|
+
k = "#{latex} #{file}"
|
238
|
+
return if (@_done[k] ||= 0) >= n.to_i
|
239
|
+
@_done[k] += 1
|
240
|
+
__run_latex!(latex, file)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def prepare_files()
|
245
|
+
## copy image files
|
246
|
+
copy_images(@config['imagedir'], File.join(@build_dir, @config['imagedir']))
|
247
|
+
## copy style files
|
248
|
+
stydir = File.join(Dir.pwd, 'sty')
|
249
|
+
copy_sty(stydir, @build_dir)
|
250
|
+
copy_sty(stydir, @build_dir, 'fd')
|
251
|
+
copy_sty(stydir, @build_dir, 'cls')
|
252
|
+
copy_sty(Dir.pwd, @build_dir, 'tex')
|
253
|
+
end
|
254
|
+
|
255
|
+
def copy_outfile(tmp_pdffile, out_pdffile)
|
256
|
+
if File.exist?(tmp_pdffile)
|
257
|
+
FileUtils.cp(tmp_pdffile, out_pdffile)
|
258
|
+
elsif File.exist?(out_pdffile)
|
259
|
+
FileUtils.rm(out_pdffile)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def copy_images(from, to)
|
264
|
+
return unless File.exist?(from)
|
265
|
+
FileUtils.mkdir_p(to)
|
266
|
+
ReVIEW::MakerHelper.copy_images_to_dir(from, to)
|
267
|
+
## extractbb コマンドは、最近のLaTeX処理系では必要ないだけでなく、
|
268
|
+
## XeTeX において図がずれる原因になるので、使わない。
|
269
|
+
#|Dir.chdir(to) do
|
270
|
+
#| images = Dir.glob('**/*.{jpg,jpeg,png,pdf,ai,eps,tif,tiff}')
|
271
|
+
#| images = images.find_all {|f| File.file?(f) }
|
272
|
+
#| break if images.empty?
|
273
|
+
#| d = @config[@maker_name]
|
274
|
+
#| if d['bbox']
|
275
|
+
#| system('extractbb', '-B', d['bbox'], *images)
|
276
|
+
#| system_or_raise('ebb', '-B', d['bbox'], *images) unless system('extractbb', '-B', d['bbox'], '-m', *images)
|
277
|
+
#| else
|
278
|
+
#| system('extractbb', *images)
|
279
|
+
#| system_or_raise('ebb', *images) unless system('extractbb', '-m', *images)
|
280
|
+
#| end
|
281
|
+
#|end
|
282
|
+
end
|
283
|
+
|
284
|
+
def copy_sty(dirname, copybase, extname = 'sty')
|
285
|
+
unless File.directory?(dirname)
|
286
|
+
warn "No such directory - #{dirname}"
|
287
|
+
return
|
288
|
+
end
|
289
|
+
Dir.open(dirname) do |dir|
|
290
|
+
dir.each do |fname|
|
291
|
+
if File.extname(fname).downcase == '.' + extname
|
292
|
+
FileUtils.mkdir_p(copybase)
|
293
|
+
FileUtils.cp File.join(dirname, fname), copybase
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def new_renderer(book)
|
300
|
+
return LATEXRenderer.new(@config, book, @basedir, @starter_config)
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
## layout.tex.erb をレンダリングする
|
305
|
+
class LATEXRenderer < BaseRenderer
|
306
|
+
include ReVIEW::LaTeXUtils
|
307
|
+
|
308
|
+
def layout_template_name
|
309
|
+
return './latex/layout.tex.erb'
|
310
|
+
end
|
311
|
+
|
312
|
+
def initialize(*args)
|
313
|
+
super
|
314
|
+
setup()
|
315
|
+
end
|
316
|
+
|
317
|
+
def setup()
|
318
|
+
@input_files = make_input_files(@book)
|
319
|
+
#
|
320
|
+
dclass = @config['texdocumentclass'] || []
|
321
|
+
@documentclass = dclass[0] || 'jsbook'
|
322
|
+
@documentclassoption = dclass[1] || 'uplatex,oneside'
|
323
|
+
|
324
|
+
@okuduke = make_colophon()
|
325
|
+
@authors = make_authors()
|
326
|
+
|
327
|
+
@custom_titlepage = make_custom_page(@config['cover']) || make_custom_page(@config['coverfile'])
|
328
|
+
@custom_originaltitlepage = make_custom_page(@config['originaltitlefile'])
|
329
|
+
@custom_creditpage = make_custom_page(@config['creditfile'])
|
330
|
+
|
331
|
+
@custom_profilepage = make_custom_page(@config['profile'])
|
332
|
+
@custom_advfilepage = make_custom_page(@config['advfile'])
|
333
|
+
@custom_colophonpage = make_custom_page(@config['colophon']) if @config['colophon'] && @config['colophon'].is_a?(String)
|
334
|
+
@custom_backcoverpage = make_custom_page(@config['backcover'])
|
335
|
+
|
336
|
+
if @config['pubhistory']
|
337
|
+
warn 'pubhistory is oboleted. use history.'
|
338
|
+
else
|
339
|
+
@config['pubhistory'] = make_history_list.join("\n")
|
340
|
+
end
|
341
|
+
|
342
|
+
@coverimageoption =
|
343
|
+
if @documentclass == 'ubook' || @documentclass == 'utbook'
|
344
|
+
'width=\\textheight,height=\\textwidth,keepaspectratio,angle=90'
|
345
|
+
else
|
346
|
+
'width=\\textwidth,height=\\textheight,keepaspectratio'
|
347
|
+
end
|
348
|
+
|
349
|
+
part_tuple = I18n.get('part' ).split(/\%[A-Za-z]{1,3}/, 2)
|
350
|
+
chapter_tuple = I18n.get('chapter' ).split(/\%[A-Za-z]{1,3}/, 2)
|
351
|
+
appendix_tuple = I18n.get('appendix').split(/\%[A-Za-z]{1,3}/, 2)
|
352
|
+
@locale_latex = {
|
353
|
+
'prepartname' => part_tuple[0],
|
354
|
+
'postpartname' => part_tuple[1],
|
355
|
+
'prechaptername' => chapter_tuple[0],
|
356
|
+
'postchaptername' => chapter_tuple[1],
|
357
|
+
'preappendixname' => appendix_tuple[0],
|
358
|
+
'postappendixname' => appendix_tuple[1],
|
359
|
+
}
|
360
|
+
|
361
|
+
@texcompiler = File.basename(@config['texcommand'], '.*')
|
362
|
+
#
|
363
|
+
return self
|
364
|
+
end
|
365
|
+
|
366
|
+
private
|
367
|
+
|
368
|
+
def chapter_key(chap)
|
369
|
+
return 'PREDEF' if chap.on_predef?
|
370
|
+
return 'CHAPS' if chap.on_chaps?
|
371
|
+
return 'APPENDIX' if chap.on_appendix?
|
372
|
+
return 'POSTDEF' if chap.on_postdef?
|
373
|
+
return nil
|
374
|
+
end
|
375
|
+
|
376
|
+
def make_input_files(book)
|
377
|
+
input_files = {'PREDEF'=>"", 'CHAPS'=>"", 'APPENDIX'=>"", 'POSTDEF'=>""}
|
378
|
+
book.each_input_file do |part, chap|
|
379
|
+
if part
|
380
|
+
key = 'CHAPS'
|
381
|
+
latex_code = (part.file? ? "\\input{#{part.name}.tex}\n"
|
382
|
+
: "\\part{#{part.name}}\n")
|
383
|
+
elsif chap
|
384
|
+
key = chapter_key(chap) # 'PREDEF', 'CHAPS', 'APPENDEX', or 'POSTDEF'
|
385
|
+
latex_code = "\\input{#{File.basename(chap.path, '.*')}.tex}\n"
|
386
|
+
end
|
387
|
+
input_files[key] << latex_code if key
|
388
|
+
end
|
389
|
+
return input_files
|
390
|
+
end
|
391
|
+
|
392
|
+
def make_custom_page(file)
|
393
|
+
file_sty = file.to_s.sub(/\.[^.]+\Z/, '.tex')
|
394
|
+
return File.read(file_sty) if File.exist?(file_sty)
|
395
|
+
nil
|
396
|
+
end
|
397
|
+
|
398
|
+
def join_with_separator(value, sep)
|
399
|
+
return [value].flatten.join(sep)
|
400
|
+
end
|
401
|
+
|
402
|
+
def make_colophon_role(role)
|
403
|
+
return "" unless @config[role].present?
|
404
|
+
initialize_metachars(@config['texcommand'])
|
405
|
+
names = @config.names_of(role)
|
406
|
+
sep = i18n('names_splitter')
|
407
|
+
str = [names].flatten.join(sep)
|
408
|
+
#return "#{i18n(role)} & #{escape_latex(str)} \\\\\n"
|
409
|
+
return "\\startercolophonrow{#{i18n(role)}}{#{escape_latex(str)}}\n"
|
410
|
+
end
|
411
|
+
|
412
|
+
def make_colophon()
|
413
|
+
return @config['colophon_order'].map {|role| make_colophon_role(role) }.join
|
414
|
+
end
|
415
|
+
|
416
|
+
def make_authors
|
417
|
+
sep = i18n('names_splitter')
|
418
|
+
pr = proc do |key, labelkey, linebreak|
|
419
|
+
@config[key].present? ? \
|
420
|
+
linebreak + i18n(labelkey, names_of(key, sep)) : ""
|
421
|
+
end
|
422
|
+
authors = ''
|
423
|
+
authors << pr.call('aut', 'author_with_label', '')
|
424
|
+
authors << pr.call('csl', 'supervisor_with_label', " \\\\\n")
|
425
|
+
authors << pr.call('trl', 'translator_with_label', " \\\\\n")
|
426
|
+
return authors
|
427
|
+
end
|
428
|
+
|
429
|
+
def names_of(key, sep)
|
430
|
+
return @config.names_of(key).map {|s| escape_latex(s) }.join(sep)
|
431
|
+
end
|
432
|
+
|
433
|
+
def make_history_list
|
434
|
+
buf = []
|
435
|
+
if @config['history']
|
436
|
+
@config['history'].each_with_index do |items, edit|
|
437
|
+
items.each_with_index do |item, rev|
|
438
|
+
editstr = edit == 0 ? i18n('first_edition') : i18n('nth_edition', (edit + 1).to_s)
|
439
|
+
revstr = i18n('nth_impression', (rev + 1).to_s)
|
440
|
+
if item =~ /\A\d+\-\d+\-\d+\Z/
|
441
|
+
buf << i18n('published_by1', [date_to_s(item), editstr + revstr])
|
442
|
+
elsif item =~ /\A(\d+\-\d+\-\d+)[\s ](.+)/
|
443
|
+
# custom date with string
|
444
|
+
item.match(/\A(\d+\-\d+\-\d+)[\s ](.+)/) { |m| buf << i18n('published_by3', [date_to_s(m[1]), m[2]]) }
|
445
|
+
else
|
446
|
+
# free format
|
447
|
+
buf << item
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
elsif @config['date']
|
452
|
+
buf << i18n('published_by2', date_to_s(@config['date']))
|
453
|
+
end
|
454
|
+
buf
|
455
|
+
end
|
456
|
+
|
457
|
+
def date_to_s(date)
|
458
|
+
d = Date.parse(date)
|
459
|
+
d.strftime(i18n('date_format'))
|
460
|
+
end
|
461
|
+
|
462
|
+
end
|
463
|
+
|
464
|
+
|
465
|
+
end
|
466
|
+
|
467
|
+
|
468
|
+
end
|