Last active
June 11, 2016 15:22
-
-
Save KitaitiMakoto/6927b14bce95a5f6cc654c00e41e3c16 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "English" | |
require "json" | |
require "epub/ocf/physical_container/zipruby" | |
require "epub/parser" | |
require "epub/parser/cfi" | |
require "epub/maker" | |
require "rouge" | |
require "rouge/lexers/fluentd" | |
def main(argv) | |
epub_file = argv.shift | |
patch_file = argv.shift | |
if patch_file.nil? || epub_file.nil? | |
abort "USAGE: ruby #{$PROGRAM_NAME} EPUB PATCH" | |
end | |
patch = JSON.parse(File.read(patch_file)) | |
epub = EPUB::Parser.parse(epub_file, container_adapter: :Zipruby) | |
# epub = EPUB::Parser.parse(epub_file) | |
unless patch["target"] == epub.release_identifier | |
abort "target release identifier doesn't match" | |
end | |
patch["patch"].each do |operation| | |
case operation["op"] | |
when "add" | |
cfi = EPUB::CFI(operation["cfi"]) | |
cfi.find_and_edit epub do |elem| | |
case elem | |
when EPUB::Publication::Package::Manifest::Item | |
value = Nokogiri::XML::DocumentFragment.parse(operation["value"]).first_element_child | |
# TODO: insert to proper position: just before elem | |
elem.manifest.make_item do |item| | |
["id", "href", "media-type", "properties", "media-overlay"].each do |attr| | |
item.__send__ "#{attr.gsub('-', '_')}=", value[attr] | |
end | |
theme = operation["rouge-theme"] | |
scope = operation["rouge-scope"] | |
theme_class = Rouge::Theme.find(theme) | |
if theme_class | |
item.content = theme_class.new(scope: scope).render | |
else | |
item.content = `pygmentize -f html -S #{theme} -a #{scope}` | |
end | |
item.save # FIXME: bothering | |
end | |
when Nokogiri::XML::Element | |
value = Nokogiri::XML::DocumentFragment.parse(operation["value"]).first_element_child | |
elem.add_previous_sibling value | |
else | |
raise NotImplementedError | |
end | |
end | |
when "replace" | |
if operation["lang"] | |
highlight epub, operation, patch["rouge-scope"] | |
else | |
value = Nokogiri::XML::DocumentFragment.parse(operation["value"]).first_element_child | |
# TODO: keep and consider order of metadata child elements | |
if value["property"] == "dcterms:modified" | |
epub.package.edit do |package| | |
package.metadata.modified.content = value.content | |
end | |
else | |
raise NotImplementedError | |
end | |
end | |
end | |
end | |
end | |
def highlight(epub, operation, default_scope) | |
formatter = Rouge::Formatters::HTML.new(wrap: false) | |
cfi = EPUB::CFI(operation["cfi"]) | |
cfi.find_and_edit epub do |elem| | |
lexer_name = operation["lang"] | |
case operation["lang"] | |
when "HiveQL", "PigLatin" | |
lexer_name = "SQL" | |
end | |
lexer = Rouge::Lexers.const_get(lexer_name).new | |
lexed = lexer.lex(elem.content) | |
begin | |
elem.inner_html = formatter.format(lexed) | |
scope = operation["rouge-scope"] || default_scope | |
additional_class = scope[1..-1] | |
class_value = elem["class"] ? | |
elem["class"] + " #{additional_class}" : | |
additional_class | |
elem["class"] = class_value | |
rescue NoMethodError => error | |
warn "Can't format #{operation['cfi']} as #{lexer_name}" | |
end | |
end | |
end | |
class EPUB::CFI::Location | |
def find_and_edit(epub) | |
path_in_package = paths.first.dup | |
first_step = path_in_package.steps.shift | |
modules = [:metadata, :manifest, :spine] | |
mod_name = modules[first_step.value / 2 - 1] | |
second_step = path_in_package.steps.shift | |
case mod_name | |
when :metadata | |
if second_step | |
raise "Can't resolve CFI" unless path_in_package.steps.empty? | |
index = second_step.value / 2 - 1 | |
# TODO: keep and consider order of metadata child elements | |
raise NotImplementedError | |
else | |
if paths.length == 1 | |
epub.package.edit do |package| | |
yield package.metadata | |
end | |
else | |
raise "Can't resolve indirect instruction" | |
end | |
end | |
when :manifest | |
if second_step | |
raise "Can't resolve CFI" unless path_in_package.steps.empty? | |
index = second_step.value / 2 - 1 | |
epub.package.edit do |package| | |
yield package.manifest.items[index] | |
end | |
else | |
if paths.length == 1 | |
epub.package.edit do |package| | |
yield package.manifest | |
end | |
else | |
raise "Can't find indirect instruction" | |
end | |
end | |
when :spine | |
unless second_step | |
if paths.length == 1 | |
# edit spine | |
else | |
raise "Can't resolve indirect instruction" | |
end | |
end | |
index = second_step.value / 2 - 1 | |
itemref = epub.package.spine.itemrefs[index] | |
raise "Unresolvable step in first path" unless path_in_package.steps.empty? | |
if paths.length == 1 | |
# edit itemref | |
else | |
itemref.item.edit_with_nokogiri do |doc| | |
elem = doc.root | |
paths[1].steps.each do |step| | |
if step.element? | |
index = step.value / 2 - 1 | |
elem = elem.element_children[index] | |
else | |
end | |
end | |
yield elem | |
end | |
end | |
end | |
end | |
end | |
main(ARGV) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"schema-version": "0.5.0", | |
"modified": "2016-06-03T00:00:00Z", | |
"target": "urn:uuid:5847863b-7653-4319-8c0b-ccee8a1dcac3@2014-08-08T09:00:00Z", | |
"rouge-scope": ".etupirika-monokai-sublime", | |
"patch": [ | |
{"op": "replace", "cfi": "/6/20!/4/2/42/2", "value": "", "lang": "Ruby"}, | |
{"op": "replace", "cfi": "/6/20!/4/2/80/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/20!/4/2/94/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/30!/4/2/70/2", "value": "", "lang": "JSON"}, | |
{"op": "replace", "cfi": "/6/30!/4/2/126/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/30!/4/2/236/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/30!/4/2/248/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/30!/4/2/252/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/30!/4/2/270/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/30!/4/2/284/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/30!/4/2/296/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/30!/4/2/302/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/30!/4/2/320/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/32!/4/2/92/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/32!/4/2/96/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/32!/4/2/122/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/32!/4/2/126/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/32!/4/2/130/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/46/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/50/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/54/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/136/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/140/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/166/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/178/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/188/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/224/2", "value": "", "lang": "PHP"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/228/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/280/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/284/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/310/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/314/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/326/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/378/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/396/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/404/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/34!/4/2/466/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/62/2", "value": "", "lang": "JSON"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/88/2", "value": "", "lang": "JSON"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/124/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/134/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/142/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/166/2", "value": "", "lang": "JSON"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/206/2", "value": "", "lang": "JSON"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/238/2", "value": "", "lang": "JSON"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/286/2", "value": "", "lang": "JSON"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/348/2", "value": "", "lang": "JSON"}, | |
{"op": "replace", "cfi": "/6/44!/4/2/354/2", "value": "", "lang": "JSON"}, | |
{"op": "replace", "cfi": "/6/46!/4/2/20/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/46!/4/2/40/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/48!/4/2/228/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/48!/4/2/236/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/50!/4/2/50/2", "value": "", "lang": "Java"}, | |
{"op": "replace", "cfi": "/6/50!/4/2/58/2", "value": "", "lang": "Java"}, | |
{"op": "replace", "cfi": "/6/50!/4/2/68/2", "value": "", "lang": "Java"}, | |
{"op": "replace", "cfi": "/6/50!/4/2/76/2", "value": "", "lang": "Java"}, | |
{"op": "replace", "cfi": "/6/50!/4/2/102/2", "value": "", "lang": "HiveQL", "rouge-scope": ".etupirika-colorful"}, | |
{"op": "replace", "cfi": "/6/50!/4/2/112/2", "value": "", "lang": "HiveQL", "rouge-scope": ".etupirika-colorful"}, | |
{"op": "replace", "cfi": "/6/50!/4/2/132/2", "value": "", "lang": "PigLatin"}, | |
{"op": "replace", "cfi": "/6/50!/4/2/140/2", "value": "", "lang": "PigLatin"}, | |
{"op": "replace", "cfi": "/6/50!/4/2/150/2", "value": "", "lang": "PigLatin"}, | |
{"op": "replace", "cfi": "/6/56!/4/2/48/2", "value": "", "lang": "Javascript", "rouge-scope": ".etupirika-emacs"}, | |
{"op": "replace", "cfi": "/6/56!/4/2/54/2", "value": "", "lang": "Javascript"}, | |
{"op": "replace", "cfi": "/6/66!/4/2/28/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/66!/4/2/44/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/66!/4/2/66/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/66!/4/2/76/2", "value": "", "lang": "Fluentd"}, | |
{"op": "replace", "cfi": "/6/66!/4/2/92/2", "value": "", "lang": "Nginx"}, | |
{"op": "replace", "cfi": "/6/66!/4/2/108/2", "value": "", "lang": "Nginx"}, | |
{"op": "replace", "cfi": "/6/66!/4/2/118/2", "value": "", "lang": "Nginx"}, | |
{"op": "add", "cfi": "/4/2", "value": "<item id=\"etupirika-monoki-sublime-css\" href=\"etupirika/monokai.sublime.css\" media-type=\"text/css\" />", "rouge-theme": "monokai.sublime", "rouge-scope": ".etupirika-monokai-sublime", "comment": "adding item means adding file together"}, | |
{"op": "add", "cfi": "/4/2", "value": "<item id=\"etupirika-colorful-css\" href=\"etupirika/colorful.css\" media-type=\"text/css\" />", "rouge-theme": "colorful", "rouge-theme": "colorful", "rouge-scope": ".etupirika-colorful"}, | |
{"op": "add", "cfi": "/4/2", "value": "<item id=\"etupirika-emacs-css\" href=\"etupirika/emacs.css\" media-type=\"text/css\" />", "rouge-theme": "emacs", "rouge-theme": "emacs", "rouge-scope": ".etupirika-emacs"}, | |
{"op": "add", "cfi": "/6/14!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/20!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/30!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/32!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/34!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/44!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/46!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/48!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/50!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/56!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/66!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/monokai.sublime.css\" />", "rouge-scope": ".etupirika-monokai-sublime", "rouge-theme": "monokai.sublime"}, | |
{"op": "add", "cfi": "/6/14!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/20!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/30!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/32!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/34!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/44!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/46!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/48!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/50!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/56!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/66!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/colorful.css\" />", "rouge-scope": ".etupirika-colorful", "rouge-theme": "colorful"}, | |
{"op": "add", "cfi": "/6/14!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "add", "cfi": "/6/20!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "add", "cfi": "/6/30!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "add", "cfi": "/6/32!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "add", "cfi": "/6/34!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "add", "cfi": "/6/44!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "add", "cfi": "/6/46!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "add", "cfi": "/6/48!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "add", "cfi": "/6/50!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "add", "cfi": "/6/56!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "add", "cfi": "/6/66!/2/2", "value": "<link rel=\"stylesheet\" href=\"../etupirika/emacs.css\" />", "rouge-scope": ".etupirika-emacs", "rouge-theme": "emacs"}, | |
{"op": "replace", "cfi": "/2/16", "value": "<meta property=\"dcterms:modified\">2016-06-03T00:00:00Z</meta>", "comment": "duplicated with top level modified; TODO: replace only content"} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment