Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Gihyo Digital Publishingの本のソースコードをシンタックスハイライトする
# coding: utf-8
gem 'epub-parser', '>= 0.2.4'
gem 'epub-maker', '0.0.3'
require 'English'
require 'epub/maker'
require 'rouge'
require 'rouge/lexers/docker'
ROUGE_THEME = 'github'
ROUGE_CSS_SCOPE = 'code'
CSS_PATH = 'syntax.css'
$formatter = Rouge::Formatters::HTML.new(wrap: false)
$default_lexers = {
'.rb' => :Ruby,
'Vagrantfile' => :Ruby,
'.yaml' => :YAML,
'.yml' => :YAML,
'.toml' => :TOML,
'.sh' => :Shell,
'.go' => :Go,
'.json' => :JSON,
'nginx.conf' => :Nginx,
'Dockerfile' => :Docker,
'.ini' => :INI
}
$lexers = {
'urn:uuid:bffeb831-ee98-40a9-9eae-b5693185ce74' => {
'リスト2 パッケージからインストールした場合の設定ファイル' => :TOML,
'リスト6 Private Variableの参照' => :YAML,
'リスト11 envの設定' => :YAML,
'リスト12 scriptの記述例' => :YAML,
'リスト13 notifyの設定例' => :YAML,
'リスト14 publishの設定例' => :YAML,
'リスト15 gitの設定例' => :YAML,
'リスト16 sshの設定例' => :YAML,
'リスト17 bashの設定例' => :YAML,
'supervisord.conf' => :INI
},
'urn:uuid:14eadcd9-214a-4f45-9623-d8ac54f22af7' => {
'Ruby' => :Ruby,
'C言語' => :C,
'Perl' => :Perl,
'Python' => :Python,
'ノンブロッキングI/Oはfdのフラグ' => :Ruby,
'システムコール' => :C,
'fdがオープンされたまま残る' => :Python,
'timegm関数と閏秒' => :C,
'Rubyにおける時刻の扱いの問題' => :PlainText,
}
}
def main(argv)
book_path = argv.shift
$book = parse_book(book_path)
add_css_files
highlight_contents
update_or_insert_modified_date Time.now
rescue => error
$stderr.puts error
$stderr.puts error.backtrace
$stderr.puts "Usage: ruby #{$PROGRAM_NAME} EPUB_PATH"
abort
end
def parse_book(path)
EPUB::Parser.parse(path)
end
def add_css_files
dummy_origin = Addressable::URI.parse('file:///')
rootfile_path = dummy_origin + $book.ocf.container.rootfile.full_path
theme = ROUGE_THEME
scope = ROUGE_CSS_SCOPE
if $book.unique_identifier.content == 'urn:uuid:479892c1-6cd6-409e-915e-647aca7e25a2'
scope = 'code[data-rouge-language="nginx"]'
end
style = Rouge::Theme.find(theme).new(scope: scope).render
$book.package.edit do |package|
package.manifest.make_item do |item|
item.href = Addressable::URI.parse("../#{CSS_PATH}") # IMPROVEMENT: Less need to call Addressable::URI.parse explicitly
# IMPROVEMENT: Want to call item.entry_name = $css_path
item.media_type = 'text/css'
item.id = CSS_PATH
item.content = style
item.save # IMPROVEMENT: Less need to call save explicitly
end
end
end
def highlight_contents
$book.resources.select(&:xhtml?).each do |xhtml|
xhtml.edit_with_nokogiri do |doc|
add_links_to_css_files(doc, xhtml)
markup(doc)
end
end
end
def add_links_to_css_files(doc, xhtml)
dummy_root_iri = Addressable::URI.parse('http://example.net/')
xhtml_entry_name = dummy_root_iri + xhtml.entry_name
head = (doc/'head').first
entry_name = dummy_root_iri + CSS_PATH
href = entry_name.route_from(xhtml_entry_name)
link = Nokogiri::XML::Node.new('link', doc)
link['href'] = href
link['type'] = 'text/css'
link['rel'] = 'stylesheet'
head << link
end
def markup(doc)
lexers = $default_lexers.merge($lexers[$book.unique_identifier.content] || {})
lexers_regexp = Regexp.new('(?<extension>' + lexers.keys.map {|k| k.gsub('.', '\.')}.join('|') + ')')
(doc/'code').each do |code|
list_name = ''
case $book.unique_identifier.content
when 'urn:uuid:bffeb831-ee98-40a9-9eae-b5693185ce74'
list_name = code.parent.previous_element.content if code.parent.previous_element
when 'urn:uuid:14eadcd9-214a-4f45-9623-d8ac54f22af7'
classes = code['class'] || ''
classes = classes.split(/\s+/)
next if classes.include? 'bw'
prev = code.parent.previous_element
while prev
if prev.name =~ /h\d/
list_name = prev.content
break
end
prev = prev.previous_element
end
if list_name == 'クローズして問題を解決する'
list_name = if code.content.start_with? 'from '
'Python'
elsif code.content.start_with? 'use '
'Perl'
end
end
unless list_name.match lexers_regexp
list_name = case list_name
when ''
'C言語'
else
'Ruby'
end
end
when 'urn:uuid:479892c1-6cd6-409e-915e-647aca7e25a2'
code_title_element = code.parent.previous_element
classes = []
if code_title_element && code_title_element['class']
classes = code_title_element['class'].split(/\s+/)
end
if classes.include? 'code_title'
list_name = code_title_element.content
end
if list_name.match 'nginxの公式リポジトリ情報(Debian GNU/Linux)'
list_name = ''
end
if list_name.match 'nginxの公式リポジトリ情報(Debian GNU/Linux)'
list_name = '.ini'
end
content = code.content
unless content.lines.first.chomp == '# pkg install nginx' or content[0] == '$'
list_name = 'nginx.conf'
end
end
match = list_name.match lexers_regexp
list_marker = '◆リスト'
if match and
($book.unique_identifier.content == 'urn:uuid:bffeb831-ee98-40a9-9eae-b5693185ce74' ? list_name.start_with?(list_marker) : true)
lexer_name = lexers[match[:extension]]
lexer = Rouge::Lexers.const_get(lexer_name).new
if $book.unique_identifier.content == 'urn:uuid:479892c1-6cd6-409e-915e-647aca7e25a2'
code['data-rouge-language'] = 'nginx'
end
else
lexer = Rouge::Lexer.guess(source: code.content)
end
code.inner_html = $formatter.format(lexer.lex(code.content))
end
end
def update_or_insert_modified_date(time)
$book.package.edit do |package|
modified = package.metadata.metas.find {|meta|
meta.property == 'dcterms:modified'
}
unless modified
modified = EPUB::Publication::Package::Metadata::Meta.new
modified.property = 'dcterms:modified'
end
modified.content = time.utc.iso8601
end
end
main(ARGV)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.