Skip to content

Instantly share code, notes, and snippets.

@KitaitiMakoto
Last active June 11, 2016 15:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KitaitiMakoto/6927b14bce95a5f6cc654c00e41e3c16 to your computer and use it in GitHub Desktop.
Save KitaitiMakoto/6927b14bce95a5f6cc654c00e41e3c16 to your computer and use it in GitHub Desktop.
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)
{
"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