review-retrovert 0.2.2 → 0.9.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/retrovert.yml +39 -0
  3. data/.gitignore +2 -2
  4. data/.ruby-version +1 -0
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +68 -2
  7. data/README.md +6 -3
  8. data/lib/review/retrovert/cli.rb +2 -0
  9. data/lib/review/retrovert/converter.rb +255 -16
  10. data/lib/review/retrovert/version.rb +1 -1
  11. data/review-retrovert.gemspec +1 -1
  12. data/testdata/mybook/.gitignore +7 -0
  13. data/testdata/mybook/README.md +43 -0
  14. data/testdata/mybook/Rakefile +16 -0
  15. data/testdata/mybook/catalog.yml +33 -0
  16. data/testdata/mybook/config-starter.yml +139 -0
  17. data/testdata/mybook/config.yml +375 -0
  18. data/testdata/mybook/contents/00-preface.re +83 -0
  19. data/testdata/mybook/contents/01-install.re +272 -0
  20. data/testdata/mybook/contents/02-tutorial.re +1008 -0
  21. data/testdata/mybook/contents/03-syntax.re +2613 -0
  22. data/testdata/mybook/contents/04-customize.re +728 -0
  23. data/testdata/mybook/contents/05-faq.re +328 -0
  24. data/testdata/mybook/contents/06-bestpractice.re +971 -0
  25. data/testdata/mybook/contents/91-compare.re +18 -0
  26. data/testdata/mybook/contents/92-filelist.re +119 -0
  27. data/testdata/mybook/contents/93-background.re +267 -0
  28. data/testdata/mybook/contents/99-postface.re +38 -0
  29. data/testdata/mybook/css/normalize.css +349 -0
  30. data/testdata/mybook/css/webstyle.css +514 -0
  31. data/testdata/mybook/images/03-syntax/favicon-16x16.png +0 -0
  32. data/testdata/mybook/images/03-syntax/figure_heretop.png +0 -0
  33. data/testdata/mybook/images/03-syntax/tw-icon1.jpg +0 -0
  34. data/testdata/mybook/images/03-syntax/tw-icon2.jpg +0 -0
  35. data/testdata/mybook/images/03-syntax/tw-icon3.jpg +0 -0
  36. data/testdata/mybook/images/03-syntax/tw-icon4.jpg +0 -0
  37. data/testdata/mybook/images/04-customize/caption_pagebreak.png +0 -0
  38. data/testdata/mybook/images/04-customize/chaptitlepage_sample.png +0 -0
  39. data/testdata/mybook/images/05-faq/codeblock_rpadding1.png +0 -0
  40. data/testdata/mybook/images/05-faq/codeblock_rpadding2.png +0 -0
  41. data/testdata/mybook/images/06-bestpractice/figure_heretop.png +0 -0
  42. data/testdata/mybook/images/06-bestpractice/font_beramono.png +0 -0
  43. data/testdata/mybook/images/06-bestpractice/heading_design1.png +0 -0
  44. data/testdata/mybook/images/06-bestpractice/heading_design2.png +0 -0
  45. data/testdata/mybook/images/06-bestpractice/margin_book.png +0 -0
  46. data/testdata/mybook/images/06-bestpractice/multiline-title.png +0 -0
  47. data/testdata/mybook/images/06-bestpractice/preface_numbered.png +0 -0
  48. data/testdata/mybook/images/06-bestpractice/program_border.png +0 -0
  49. data/testdata/mybook/images/06-bestpractice/sechead_design_4.png +0 -0
  50. data/testdata/mybook/images/06-bestpractice/titlepage-samples.png +0 -0
  51. data/testdata/mybook/images/93-background/bug913.png +0 -0
  52. data/testdata/mybook/images/93-background/slide2.png +0 -0
  53. data/testdata/mybook/images/cover_a5.pdf +0 -0
  54. data/testdata/mybook/images/cover_b5.pdf +0 -0
  55. data/testdata/mybook/images/tw-icon.jpg +0 -0
  56. data/testdata/mybook/layouts/layout.epub.erb +29 -0
  57. data/testdata/mybook/layouts/layout.html5.erb +106 -0
  58. data/testdata/mybook/layouts/layout.tex.erb +546 -0
  59. data/testdata/mybook/lib/hooks/beforetexcompile.rb +55 -0
  60. data/testdata/mybook/lib/ruby/review-builder.rb +503 -0
  61. data/testdata/mybook/lib/ruby/review-cli.rb +58 -0
  62. data/testdata/mybook/lib/ruby/review-compiler.rb +523 -0
  63. data/testdata/mybook/lib/ruby/review-epubmaker.rb +606 -0
  64. data/testdata/mybook/lib/ruby/review-htmlbuilder.rb +661 -0
  65. data/testdata/mybook/lib/ruby/review-latexbuilder.rb +782 -0
  66. data/testdata/mybook/lib/ruby/review-maker.rb +235 -0
  67. data/testdata/mybook/lib/ruby/review-monkeypatch.rb +91 -0
  68. data/testdata/mybook/lib/ruby/review-pdfmaker.rb +468 -0
  69. data/testdata/mybook/lib/ruby/review-textbuilder.rb +36 -0
  70. data/testdata/mybook/lib/ruby/review-tocparser.rb +285 -0
  71. data/testdata/mybook/lib/ruby/review-webmaker.rb +433 -0
  72. data/testdata/mybook/lib/tasks/mytasks.rake +31 -0
  73. data/testdata/mybook/lib/tasks/review.rake +142 -0
  74. data/testdata/mybook/lib/tasks/review.rake.orig +72 -0
  75. data/testdata/mybook/lib/tasks/starter.rake +326 -0
  76. data/testdata/mybook/locale.yml +6 -0
  77. data/testdata/mybook/review-ext.rb +206 -0
  78. data/testdata/mybook/sty/jumoline.sty +310 -0
  79. data/testdata/mybook/sty/mycolophon.sty +81 -0
  80. data/testdata/mybook/sty/mystyle.sty +8 -0
  81. data/testdata/mybook/sty/mytextsize.sty +61 -0
  82. data/testdata/mybook/sty/mytitlepage.sty +103 -0
  83. data/testdata/mybook/sty/reviewmacro.sty +60 -0
  84. data/testdata/mybook/sty/starter-codeblock.sty +332 -0
  85. data/testdata/mybook/sty/starter-color.sty +79 -0
  86. data/testdata/mybook/sty/starter-font.sty +112 -0
  87. data/testdata/mybook/sty/starter-heading.sty +514 -0
  88. data/testdata/mybook/sty/starter-note.sty +127 -0
  89. data/testdata/mybook/sty/starter-section.sty +262 -0
  90. data/testdata/mybook/sty/starter-toc.sty +72 -0
  91. data/testdata/mybook/sty/starter.sty +554 -0
  92. data/testdata/mybook/style.css +597 -0
  93. 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