mkbok 0.0.1 → 0.0.2
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.
- data/bin/mkbok +268 -11
- data/lib/mkbok_version.rb +1 -1
- metadata +1 -1
data/bin/mkbok
CHANGED
@@ -1,17 +1,274 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
3
|
+
require 'optparse'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'erb'
|
6
|
+
require 'yaml'
|
4
7
|
|
5
|
-
|
6
|
-
$ gem install mkbok
|
7
|
-
$ mkbok create sample --title "learn mkbok"
|
8
|
-
$ mkbok build --local # needs pandoc,XeTex packages
|
8
|
+
include FileUtils
|
9
9
|
|
10
|
-
|
10
|
+
options = {}
|
11
|
+
CONFIG_FILE = File.join('.mkbok.yml')
|
12
|
+
if File.exists? CONFIG_FILE
|
13
|
+
config_options = YAML.load_file(CONFIG_FILE)
|
14
|
+
options.merge!(config_options)
|
15
|
+
end
|
16
|
+
|
17
|
+
option_parser = OptionParser.new do |opts|
|
18
|
+
executable_name = File.basename($PROGRAM_NAME)
|
19
|
+
opts.banner = "make ebooks from markdown plain text
|
20
|
+
Usage: #{executable_name} [options]
|
21
|
+
"
|
22
|
+
# Create a switch
|
23
|
+
opts.on("-b","--build FORMAT","build") do |format|
|
24
|
+
unless format=="pdf"
|
25
|
+
raise ArgumentError,"FORMAT must be in 'pdf' format"
|
26
|
+
end
|
27
|
+
options[:build] = format
|
28
|
+
end
|
29
|
+
# Create a flag
|
30
|
+
opts.on("-l","--lang LANG","language selection") do |lang|
|
31
|
+
unless lang=="zh" or lang=="en"
|
32
|
+
raise ArgumentError,"LANG must be in zh or en"
|
33
|
+
end
|
34
|
+
options[:lang] = lang
|
35
|
+
end
|
36
|
+
opts.on("-c","--config CONFIG","config file") do |config|
|
37
|
+
unless File.exists? config
|
38
|
+
raise ArgumentError,"config file \"#{config}\" doesn't exist"
|
39
|
+
end
|
40
|
+
options[:config] = config
|
41
|
+
end
|
42
|
+
opts.on("-t","--template template.tex","latex template file") do |template|
|
43
|
+
unless File.exists? template
|
44
|
+
raise ArgumentError,"template file \"#{template}\" doesn't exist"
|
45
|
+
end
|
46
|
+
options[:template] = template
|
47
|
+
end
|
48
|
+
opts.on("-n","--name book name","book name") do |name|
|
49
|
+
unless name =~ /^[a-zA-Z0-9]+$/
|
50
|
+
raise ArgumentError,"name should be [a-zA-Z0-9]"
|
51
|
+
end
|
52
|
+
options[:name] = name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
option_parser.parse!
|
57
|
+
puts options.inspect
|
58
|
+
|
59
|
+
$here = File.expand_path(File.dirname(__FILE__))
|
60
|
+
$root = File.join($here)
|
61
|
+
$outDir = File.join($root, 'pdf')
|
62
|
+
|
63
|
+
def figures(&block)
|
64
|
+
begin
|
65
|
+
Dir["#$root/figures/18333*.png"].each do |file|
|
66
|
+
cp(file, file.sub(/18333fig0(\d)0?(\d+)\-tn/, '\1.\2'))
|
67
|
+
end
|
68
|
+
block.call
|
69
|
+
ensure
|
70
|
+
Dir["#$root/figures/18333*.png"].each do |file|
|
71
|
+
rm(file.gsub(/18333fig0(\d)0?(\d+)\-tn/, '\1.\2'))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def command_exists?(command)
|
77
|
+
if File.executable?(command) then
|
78
|
+
return command
|
79
|
+
end
|
80
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).map do |path|
|
81
|
+
cmd = "#{path}/#{command}"
|
82
|
+
File.executable?(cmd) || File.executable?("#{cmd}.exe") || File.executable?("#{cmd}.cmd")
|
83
|
+
end.inject{|a, b| a || b}
|
84
|
+
end
|
85
|
+
|
86
|
+
def replace(string, &block)
|
87
|
+
string.instance_eval do
|
88
|
+
alias :s :gsub!
|
89
|
+
instance_eval(&block)
|
90
|
+
end
|
91
|
+
string
|
92
|
+
end
|
93
|
+
|
94
|
+
def verbatim_sanitize(string)
|
95
|
+
string.gsub('\\', '{\textbackslash}').
|
96
|
+
gsub('~', '{\textasciitilde}').
|
97
|
+
gsub(/([\$\#\_\^\%])/, '\\\\' + '\1{}')
|
98
|
+
end
|
99
|
+
|
100
|
+
def pre_pandoc(string, config)
|
101
|
+
replace(string) do
|
102
|
+
s /\#\#\#\#\# (.*?) \#\#\#\#\#/, 'PARASECTION: \1'
|
103
|
+
# Pandoc discards #### subsubsections #### - this hack recovers them
|
104
|
+
s /\#\#\#\# (.*?) \#\#\#\#/, 'SUBSUBSECTION: \1'
|
105
|
+
|
106
|
+
# Turns URLs into clickable links
|
107
|
+
s /\`(http:\/\/[A-Za-z0-9\/\%\&\=\-\_\\\.]+)\`/, '<\1>'
|
108
|
+
s /(\n\n)\t(http:\/\/[A-Za-z0-9\/\%\&\=\-\_\\\.]+)\n([^\t]|\t\n)/, '\1<\2>\1'
|
109
|
+
|
110
|
+
# Process figures
|
111
|
+
s /Insert\s18333fig\d+\.png\s*\n.*?\d{1,2}-\d{1,2}\. (.*)/, 'FIG: \1'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def post_pandoc(string, config, lang, chapter=true)
|
116
|
+
replace(string) do
|
117
|
+
space = /\s/
|
118
|
+
|
119
|
+
# Reformat for the book documentclass as opposed to article
|
120
|
+
s '\section', '\chap'
|
121
|
+
s '\sub', '\\'
|
122
|
+
s /SUBSUBSECTION: (.*)/, '\subsubsection{\1}'
|
123
|
+
s /PARASECTION: (.*)/, '\paragraph{\1}'
|
124
|
+
|
125
|
+
# Enable proper cross-reference
|
126
|
+
s /#{config['fig'].gsub(space, '\s')}\s*(\d+)\-\-(\d+)/, '\imgref{\1.\2}'
|
127
|
+
s /#{config['tab'].gsub(space, '\s')}\s*(\d+)\-\-(\d+)/, '\tabref{\1.\2}'
|
128
|
+
s /#{config['prechap'].gsub(space, '\s')}\s*(\d+)(\s*)#{config['postchap'].gsub(space, '\s')}/, '\chapref{\1}\2'
|
129
|
+
|
130
|
+
# Miscellaneous fixes
|
131
|
+
s /FIG: (.*)/, '\img{\1}'
|
132
|
+
s '\begin{enumerate}[1.]', '\begin{enumerate}'
|
133
|
+
s /(\w)--(\w)/, '\1-\2'
|
134
|
+
s /``(.*?)''/, "#{config['dql']}\\1#{config['dqr']}"
|
135
|
+
|
136
|
+
# Typeset the maths in the book with TeX
|
137
|
+
s '\verb!p = (n(n-1)/2) * (1/2^160))!', '$p = \frac{n(n-1)}{2} \times \frac{1}{2^{160}}$)'
|
138
|
+
s '2\^{}80', '$2^{80}$'
|
139
|
+
s /\sx\s10\\\^\{\}(\d+)/, '\e{\1}'
|
140
|
+
|
141
|
+
# Convert inline-verbatims into \texttt (which is able to wrap)
|
142
|
+
s /\\verb(\W)(.*?)\1/ do
|
143
|
+
"{\\texttt{#{verbatim_sanitize($2)}}}"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Ensure monospaced stuff is in a smaller font
|
147
|
+
s /(\\verb(\W).*?\2)/, '{\footnotesize\1}'
|
148
|
+
s /(\\begin\{verbatim\}.*?\\end\{verbatim\})/m, '{\footnotesize\1}'
|
149
|
+
|
150
|
+
# Shaded verbatim block
|
151
|
+
s /(\\begin\{verbatim\}.*?\\end\{verbatim\})/m, '\begin{shaded}\1\end{shaded}'
|
152
|
+
|
153
|
+
if lang=="zh"
|
154
|
+
# http://www.devdaily.com/blog/post/latex/control-line-spacing-in-itemize-enumerate-tags
|
155
|
+
# http://wiki.ctex.org/index.php/LaTeX/%E5%88%97%E8%A1%A8
|
156
|
+
# set the space of itemsize
|
157
|
+
s /(\\begin\{itemize\})/m,'\begin{itemize}\setlength{\itemsep}{1pt}\setlength{\parskip}{0pt}\setlength{\parsep}{0pt}'
|
158
|
+
s /(\\begin\{enumerate\})/m,'\begin{enumerate}\setlength{\itemsep}{1pt}\setlength{\parskip}{0pt}\setlength{\parsep}{0pt}'
|
159
|
+
# hardcode for itemize to use * instead of dot, which is missed in some chinese fonts
|
160
|
+
# and keep \item inside \enumerate env is not changed
|
161
|
+
# \item -> \item[*]
|
162
|
+
# solution is provided by Alexis, and it works under ruby 1.9+ only due to bug in 1.8.7
|
163
|
+
# http://stackoverflow.com/questions/9115018/regular-expression-using-ruby-string-gsub-method-to-replace-multi-matches
|
164
|
+
if RUBY_VERSION >= "1.9"
|
165
|
+
s /^\\item(?=((?!\\begin\{itemize\}).)*\\end\{itemize\})/m, '\\item[*]'
|
166
|
+
else
|
167
|
+
s /(^\\item)/m,'\item[*]'
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
if chapter != true
|
172
|
+
s /^\\chap\{(.*)\}/,'\chapter*{\1}'"\n"'\addcontentsline{toc}{chapter}{\1}'
|
173
|
+
s /^\\section\{(.*)\}/,'\section*{\1}'
|
174
|
+
s /^\\subsection\{(.*)\}/,'\subsection*{\1}'
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
#`
|
181
|
+
|
182
|
+
$config = YAML.load_file(options[:config])
|
183
|
+
template = ERB.new(File.read(options[:template]))
|
184
|
+
languages = [options[:lang]]
|
185
|
+
missing = ['pandoc', 'xelatex'].reject{|command| command_exists?(command)}
|
186
|
+
unless missing.empty?
|
187
|
+
puts "Missing dependencies: #{missing.join(', ')}."
|
188
|
+
puts "Install these and try again."
|
189
|
+
exit
|
190
|
+
end
|
191
|
+
|
192
|
+
figures do
|
193
|
+
languages.each do |lang|
|
194
|
+
config = $config['default'].merge($config[lang]) rescue $config['default']
|
195
|
+
|
196
|
+
puts "#{lang}:"
|
197
|
+
prefacemarkdown = Dir["#$root/#{lang}/0preface/*.markdown"].sort.map do |file|
|
198
|
+
File.read(file)
|
199
|
+
end.join("\n\n")
|
200
|
+
|
201
|
+
print "\tParsing preface markdown... "
|
202
|
+
preface = IO.popen('pandoc -p --no-wrap -f markdown -t latex', 'w+') do |pipe|
|
203
|
+
pipe.write(pre_pandoc(prefacemarkdown, config))
|
204
|
+
pipe.close_write
|
205
|
+
post_pandoc(pipe.read, config, lang, false)
|
206
|
+
end
|
207
|
+
|
208
|
+
chaptersmarkdown = Dir["#$root/#{lang}/*chapters/*.markdown"].sort.map do |file|
|
209
|
+
File.read(file)
|
210
|
+
end.join("\n\n")
|
211
|
+
puts "done"
|
212
|
+
|
213
|
+
print "\tParsing main chapters markdown... "
|
214
|
+
chapters = IO.popen('pandoc -p --no-wrap -f markdown -t latex', 'w+') do |pipe|
|
215
|
+
pipe.write(pre_pandoc(chaptersmarkdown, config))
|
216
|
+
pipe.close_write
|
217
|
+
post_pandoc(pipe.read, config, lang)
|
218
|
+
end
|
219
|
+
puts "done"
|
220
|
+
|
221
|
+
appendixmarkdown = Dir["#$root/#{lang}/*appendix/*.markdown"].sort.map do |file|
|
222
|
+
File.read(file)
|
223
|
+
end.join("\n\n")
|
224
|
+
|
225
|
+
print "\tParsing appendix markdown... "
|
226
|
+
appendix = IO.popen('pandoc -p --no-wrap -f markdown -t latex', 'w+') do |pipe|
|
227
|
+
pipe.write(pre_pandoc(appendixmarkdown, config))
|
228
|
+
pipe.close_write
|
229
|
+
post_pandoc(pipe.read, config, lang)
|
230
|
+
end
|
231
|
+
puts "done"
|
232
|
+
|
233
|
+
print "\tCreating main.tex for #{lang}... "
|
234
|
+
dir = "#$here/#{lang}"
|
235
|
+
mkdir_p(dir)
|
236
|
+
|
237
|
+
File.open("#{dir}/main.tex", 'w') do |file|
|
238
|
+
file.write(template.result(binding))
|
239
|
+
end
|
240
|
+
puts "done"
|
241
|
+
|
242
|
+
abort = false
|
243
|
+
|
244
|
+
puts "\tRunning XeTeX:"
|
245
|
+
cd($root)
|
246
|
+
3.times do |i|
|
247
|
+
print "\t\tPass #{i + 1}... "
|
248
|
+
IO.popen("xelatex -output-directory=\"#{dir}\" \"#{dir}/main.tex\" 2>&1",'r:UTF-16LE:UTF-8') do |pipe|
|
249
|
+
unless $DEBUG
|
250
|
+
if ~ /^!\s/
|
251
|
+
puts "failed with:\n\t\t\t#{$_.strip}"
|
252
|
+
puts "\tConsider running this again with --debug."
|
253
|
+
abort = true
|
254
|
+
end while pipe.gets and not abort
|
255
|
+
else
|
256
|
+
STDERR.print while pipe.gets rescue abort = true
|
257
|
+
end
|
258
|
+
end
|
259
|
+
break if abort
|
260
|
+
puts "done"
|
261
|
+
end
|
262
|
+
|
263
|
+
unless abort
|
264
|
+
print "\tMoving output to #{name}.#{lang}.pdf... "
|
265
|
+
mv("#{dir}/main.pdf", "#$root/#{name}.#{lang}.pdf")
|
266
|
+
puts "done"
|
267
|
+
else
|
268
|
+
print "\tConvert error, exit !\n"
|
269
|
+
exit 1
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
11
273
|
|
12
|
-
$ mkbok init # add needed configuration for your existing markdown files
|
13
|
-
$ mkbok build --web http://mkbok.com/diy # it will zip file and upload to web
|
14
|
-
$ mkbok build --web http://mkbok.com/ --source http://github.com/larrycai/mkbok # generate pdf from github
|
15
|
-
eof
|
16
274
|
|
17
|
-
puts "Please wait and check github.com/larrycai/mkbok"
|
data/lib/mkbok_version.rb
CHANGED