bookmaker 0.1.0
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/Readme.md +0 -0
- data/bin/bookmaker +5 -0
- data/bookmaker.gemspec +37 -0
- data/features/basic.feature +8 -0
- data/features/export.feature +8 -0
- data/features/support/setup.rb +1 -0
- data/lib/bookmaker.rb +54 -0
- data/lib/bookmaker/adapters/markdown.rb +35 -0
- data/lib/bookmaker/cli.rb +74 -0
- data/lib/bookmaker/dependency.rb +19 -0
- data/lib/bookmaker/exporter.rb +68 -0
- data/lib/bookmaker/extensions/string.rb +11 -0
- data/lib/bookmaker/generator.rb +40 -0
- data/lib/bookmaker/parser.rb +81 -0
- data/lib/bookmaker/parser/epub.rb +137 -0
- data/lib/bookmaker/parser/html.rb +199 -0
- data/lib/bookmaker/parser/mobi.rb +14 -0
- data/lib/bookmaker/parser/pdf.rb +40 -0
- data/lib/bookmaker/stats.rb +45 -0
- data/lib/bookmaker/stream.rb +27 -0
- data/lib/bookmaker/toc.rb +6 -0
- data/lib/bookmaker/toc/epub.rb +41 -0
- data/lib/bookmaker/toc/html.rb +78 -0
- data/lib/bookmaker/version.rb +3 -0
- data/templates/config.erb +51 -0
- data/templates/epub.css +500 -0
- data/templates/epub.erb +15 -0
- data/templates/html.erb +42 -0
- data/templates/latex.erb +201 -0
- data/templates/sample.md +24 -0
- data/templates/syntax.css +58 -0
- data/templates/user.css +1 -0
- metadata +172 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/Readme.md
ADDED
File without changes
|
data/bin/bookmaker
ADDED
data/bookmaker.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "bookmaker/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "bookmaker"
|
7
|
+
s.version = Bookmaker::VERSION
|
8
|
+
s.authors = ["Merovex"]
|
9
|
+
s.email = ["dausha@gmail.com"]
|
10
|
+
s.homepage = "http://dausha.net"
|
11
|
+
s.summary = %q{ODO: Write a gem summary}
|
12
|
+
s.description = %q{ODO: Write a gem description}
|
13
|
+
|
14
|
+
s.rubyforge_project = "bookmaker"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_dependency "activesupport"
|
23
|
+
s.add_development_dependency "aruba"
|
24
|
+
s.add_development_dependency "cucumber"
|
25
|
+
s.add_dependency "eeepub"
|
26
|
+
s.add_dependency "kramdown"
|
27
|
+
s.add_dependency "thor"
|
28
|
+
# s.add_dependency "erb"
|
29
|
+
# s.add_dependency "logger"
|
30
|
+
s.add_dependency "nokogiri"
|
31
|
+
s.add_dependency "notifier"
|
32
|
+
# s.add_dependency "open3"
|
33
|
+
# s.add_dependency "optparse"
|
34
|
+
# s.add_dependency "ostruct"
|
35
|
+
# s.add_dependency "tempfile"
|
36
|
+
# s.add_dependency "pathname"
|
37
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'aruba/cucumber'
|
data/lib/bookmaker.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# require "bookmaker/version"
|
2
|
+
require "active_support/all"
|
3
|
+
require "awesome_print"
|
4
|
+
require "eeepub"
|
5
|
+
require "erb"
|
6
|
+
require "logger"
|
7
|
+
require "nokogiri"
|
8
|
+
require "notifier"
|
9
|
+
require "open3"
|
10
|
+
require "optparse"
|
11
|
+
require "ostruct"
|
12
|
+
require "tempfile"
|
13
|
+
require "pathname"
|
14
|
+
require "thor"
|
15
|
+
require "thor/group"
|
16
|
+
require "yaml"
|
17
|
+
require "cgi"
|
18
|
+
|
19
|
+
module Bookmaker
|
20
|
+
|
21
|
+
require "bookmaker/extensions/string"
|
22
|
+
ROOT = Pathname.new(File.dirname(__FILE__) + "/..")
|
23
|
+
|
24
|
+
autoload :Cli, "bookmaker/cli"
|
25
|
+
autoload :Dependency, "bookmaker/dependency"
|
26
|
+
autoload :Exporter, "bookmaker/exporter"
|
27
|
+
autoload :Generator, "bookmaker/generator"
|
28
|
+
autoload :Markdown, "bookmaker/adapters/markdown"
|
29
|
+
autoload :Parser, "bookmaker/parser"
|
30
|
+
autoload :Stats, "bookmaker/stats"
|
31
|
+
autoload :Stream, "bookmaker/stream"
|
32
|
+
autoload :TOC, "bookmaker/toc"
|
33
|
+
autoload :Version, "bookmaker/version"
|
34
|
+
|
35
|
+
Encoding.default_internal = "utf-8"
|
36
|
+
Encoding.default_external = "utf-8"
|
37
|
+
|
38
|
+
def self.config(root_dir = nil)
|
39
|
+
root_dir ||= Pathname.new(Dir.pwd)
|
40
|
+
path = root_dir.join("_bookmaker.yml")
|
41
|
+
|
42
|
+
raise "Invalid Bookmaker directory; couldn't found #{path} file." unless File.file?(path)
|
43
|
+
content = File.read(path)
|
44
|
+
erb = ERB.new(content).result
|
45
|
+
|
46
|
+
YAML.load(erb)#.with_indifferent_access
|
47
|
+
end
|
48
|
+
def self.logger
|
49
|
+
@logger ||= Logger.new(File.open("/tmp/bookmaker.log", "a"))
|
50
|
+
end
|
51
|
+
def self.hi
|
52
|
+
puts "hi"
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Kitabu
|
2
|
+
class Markdown
|
3
|
+
# Supported Markdown libraries
|
4
|
+
#
|
5
|
+
MARKDOWN_LIBRARIES = %w[Kramdown]
|
6
|
+
|
7
|
+
# Retrieve preferred Markdown processor.
|
8
|
+
# You'll need one of the following libraries:
|
9
|
+
#
|
10
|
+
# # RDiscount: https://rubygems.org/gems/rdiscount
|
11
|
+
# # Maruku: https://rubygems.org/gems/maruku
|
12
|
+
# # PEGMarkdown: https://rubygems.org/gems/rpeg-markdown
|
13
|
+
# # BlueCloth: https://rubygems.org/gems/bluecloth
|
14
|
+
# # Redcarpet: https://rubygems.org/gems/redcarpet
|
15
|
+
# # Kramdown: http://kramdown.rubyforge.org/
|
16
|
+
#
|
17
|
+
# Note: RDiscount will always be installed as Kitabu's dependency but only used when no
|
18
|
+
# alternative library is available.
|
19
|
+
#
|
20
|
+
def self.engine
|
21
|
+
@engine ||= Object.const_get(MARKDOWN_LIBRARIES.find {|lib| Object.const_defined?(lib)})
|
22
|
+
end
|
23
|
+
|
24
|
+
# Convert Markdown to HTML.
|
25
|
+
def self.to_html(content)
|
26
|
+
case engine.name
|
27
|
+
when "Redcarpet"
|
28
|
+
render = Redcarpet::Render::HTML.new(:hard_wrap => true, :xhtml => true)
|
29
|
+
Redcarpet::Markdown.new(render).render(content)
|
30
|
+
else
|
31
|
+
engine.new(content).to_html
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'thor'
|
3
|
+
module Bookmaker
|
4
|
+
class Cli < Thor
|
5
|
+
FORMATS = %w[pdf draft proof html epub mobi txt]
|
6
|
+
check_unknown_options!
|
7
|
+
|
8
|
+
def self.exit_on_failure?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
def initialize(args = [], options = {}, config = {})
|
12
|
+
if config[:current_task].name == "new" && args.empty?
|
13
|
+
raise Error, "The e-Book path is required. For details run: bookmaker help new"
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "create", "Start new work"
|
19
|
+
def create(path)
|
20
|
+
puts "A Million Monkeys Writing Your Masterpiece."
|
21
|
+
generator = Generator.new
|
22
|
+
generator.destination_root = path.squish.gsub(' ','-')
|
23
|
+
generator.invoke_all
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "export [OPTIONS]", "Export e-book"
|
27
|
+
method_option :only, :type => :string, :desc => "Can be one of: #{FORMATS.join(", ")}"
|
28
|
+
method_option :open, :type => :boolean, :desc => "Automatically open PDF (Preview.app for Mac OS X and xdg-open for Linux)"
|
29
|
+
def export
|
30
|
+
inside_ebook!
|
31
|
+
if options[:only] && !FORMATS.include?(options[:only])
|
32
|
+
raise Error, "The --only option need to be one of: #{FORMATS.join(", ")}"
|
33
|
+
end
|
34
|
+
Bookmaker::Exporter.run(root_dir, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "version", "Prints the Bookmaker's version information"
|
38
|
+
map %w(-v --version) => :version
|
39
|
+
def version
|
40
|
+
say "Bookmaker version #{Bookmaker::VERSION}"
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "stats", "Display some stats about your e-book"
|
44
|
+
def stats
|
45
|
+
inside_ebook!
|
46
|
+
stats = Bookmaker::Stats.new(root_dir)
|
47
|
+
|
48
|
+
say [
|
49
|
+
"Chapters: #{stats.chapters}",
|
50
|
+
"Words: #{stats.words}",
|
51
|
+
# "Images: #{stats.images}",
|
52
|
+
# "Links: #{stats.links}",
|
53
|
+
# "Footnotes: #{stats.footnotes}",
|
54
|
+
# "Code blocks: #{stats.code_blocks}"
|
55
|
+
].join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def config
|
60
|
+
YAML.load_file(config_path).with_indifferent_access
|
61
|
+
end
|
62
|
+
def config_path
|
63
|
+
root_dir.join("_bookmaker.yml")
|
64
|
+
end
|
65
|
+
def root_dir
|
66
|
+
@root ||= Pathname.new(Dir.pwd)
|
67
|
+
end
|
68
|
+
def inside_ebook!
|
69
|
+
unless File.exist?(config_path)
|
70
|
+
raise Error, "You have to run this command from inside an e-book directory."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Bookmaker
|
2
|
+
class Dependency
|
3
|
+
def self.kindlegen?
|
4
|
+
@kindlegen ||= `which kindlegen` && $?.success?
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.xelatex?
|
8
|
+
@xelatex ||= `which xelatex` && $?.success?
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.html2text?
|
12
|
+
@html2text ||= `which html2text` && $?.success?
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.pygments_rb?
|
16
|
+
@pygments_rb ||= defined?(Pygments)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Bookmaker
|
2
|
+
class Exporter
|
3
|
+
def self.run(root_dir, options)
|
4
|
+
exporter = new(root_dir, options)
|
5
|
+
exporter.export!
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_accessor :root_dir
|
9
|
+
attr_accessor :options
|
10
|
+
|
11
|
+
def initialize(root_dir, options)
|
12
|
+
@root_dir = root_dir
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def ui
|
17
|
+
@ui ||= Thor::Base.shell.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def export!
|
21
|
+
helper = root_dir.join("config/helper.rb")
|
22
|
+
load(helper) if helper.exist?
|
23
|
+
|
24
|
+
export_pdf = [nil, "pdf"].include?(options[:only])
|
25
|
+
export_html = [nil, "html", "mobi", "epub"].include?(options[:only])
|
26
|
+
export_epub = [nil, "mobi", "epub"].include?(options[:only])
|
27
|
+
export_mobi = [nil, "mobi"].include?(options[:only])
|
28
|
+
export_txt = [nil, "txt"].include?(options[:only])
|
29
|
+
|
30
|
+
exported = []
|
31
|
+
exported << Parser::PDF.parse(root_dir) if export_pdf && Dependency.xelatex?# && Dependency.prince?
|
32
|
+
exported << Parser::HTML.parse(root_dir) if export_html
|
33
|
+
exported << Parser::Epub.parse(root_dir) if export_epub
|
34
|
+
exported << Parser::Mobi.parse(root_dir) if export_mobi && Dependency.kindlegen?
|
35
|
+
# exported << Parser::Txt.parse(root_dir) if export_txt && Dependency.html2text?
|
36
|
+
|
37
|
+
if exported.all?
|
38
|
+
color = :green
|
39
|
+
message = options[:auto] ? "exported!" : "** e-book has been exported"
|
40
|
+
|
41
|
+
if options[:open] && export_pdf
|
42
|
+
filepath = root_dir.join("output/#{File.basename(root_dir)}.pdf")
|
43
|
+
|
44
|
+
if RUBY_PLATFORM =~ /darwin/
|
45
|
+
IO.popen("open -a Preview.app '#{filepath}'").close
|
46
|
+
elsif RUBY_PLATFORM =~ /linux/
|
47
|
+
Process.detach(Process.spawn("xdg-open '#{filepath}'", :out => "/dev/null"))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Notifier.notify(
|
52
|
+
:image => Bookmaker::ROOT.join("templates/ebook.png"),
|
53
|
+
:title => "Bookmaker",
|
54
|
+
:message => "Your \"#{config[:title]}\" e-book has been exported!"
|
55
|
+
)
|
56
|
+
else
|
57
|
+
color = :red
|
58
|
+
message = options[:auto] ? "could not be exported!" : "** e-book couldn't be exported"
|
59
|
+
end
|
60
|
+
|
61
|
+
ui.say message, color
|
62
|
+
end
|
63
|
+
|
64
|
+
def config
|
65
|
+
Bookmaker.config(root_dir)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class String
|
2
|
+
def to_permalink
|
3
|
+
str = ActiveSupport::Multibyte::Chars.new(self.dup)
|
4
|
+
str = str.normalize(:kd).gsub(/[^\x00-\x7F]/,'').to_s
|
5
|
+
str.gsub!(/[^-\w\d]+/xim, "-")
|
6
|
+
str.gsub!(/-+/xm, "-")
|
7
|
+
str.gsub!(/^-?(.*?)-?$/, '\1')
|
8
|
+
str.downcase!
|
9
|
+
str
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Bookmaker
|
2
|
+
class Generator < Thor::Group
|
3
|
+
include Thor::Actions
|
4
|
+
def self.source_root
|
5
|
+
File.dirname(__FILE__) + "/../../templates"
|
6
|
+
end
|
7
|
+
def build_config_file
|
8
|
+
@title = File.basename(destination_root).gsub('-', ' ')
|
9
|
+
@name = full_name
|
10
|
+
@uid = Digest::MD5.hexdigest("#{Time.now}--#{rand}")
|
11
|
+
@year = Date.today.year
|
12
|
+
template "config.erb", "_bookmaker .yml"
|
13
|
+
end
|
14
|
+
def copy_templates
|
15
|
+
copy_file "latex.erb", "templates/pdf/layout.erb"
|
16
|
+
|
17
|
+
copy_file "html.erb", "templates/html/layout.erb"
|
18
|
+
copy_file "user.css", "templates/html/user.css"
|
19
|
+
copy_file "syntax.css", "templates/html/syntax.css"
|
20
|
+
|
21
|
+
copy_file "epub.erb", "templates/epub/page.erb"
|
22
|
+
copy_file "epub.css", "templates/epub/user.css"
|
23
|
+
end
|
24
|
+
def copy_sample_text
|
25
|
+
copy_file "sample.md" , "text/01-Welcome.md"
|
26
|
+
end
|
27
|
+
def create_directories
|
28
|
+
empty_directory "output"
|
29
|
+
empty_directory "images"
|
30
|
+
end
|
31
|
+
private
|
32
|
+
# Retrieve user's name using finger.
|
33
|
+
# Defaults to <tt>John Doe</tt>.
|
34
|
+
#
|
35
|
+
def full_name
|
36
|
+
name = `finger $USER 2> /dev/null | grep Login | colrm 1 46`.chomp
|
37
|
+
name.empty? ? "John Doe" : name.squish
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Bookmaker
|
4
|
+
module Parser
|
5
|
+
autoload :HTML , "bookmaker/parser/html"
|
6
|
+
autoload :PDF , "bookmaker/parser/pdf"
|
7
|
+
autoload :Epub , "bookmaker/parser/epub"
|
8
|
+
autoload :Mobi , "bookmaker/parser/mobi"
|
9
|
+
# autoload :Txt , "bookmaker/parser/txt"
|
10
|
+
|
11
|
+
class Base
|
12
|
+
# The e-book directory.
|
13
|
+
#
|
14
|
+
attr_accessor :root_dir
|
15
|
+
|
16
|
+
# Where the text files are stored.
|
17
|
+
#
|
18
|
+
attr_accessor :source
|
19
|
+
|
20
|
+
def self.parse(root_dir)
|
21
|
+
new(root_dir).parse
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(root_dir)
|
25
|
+
@root_dir = Pathname.new(root_dir)
|
26
|
+
@source = root_dir.join("text")
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return directory's basename.
|
30
|
+
#
|
31
|
+
def name
|
32
|
+
File.basename(root_dir)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return the configuration file.
|
36
|
+
#
|
37
|
+
def config
|
38
|
+
Bookmaker.config(root_dir)
|
39
|
+
end
|
40
|
+
def entries
|
41
|
+
return @entries unless @entries.nil?
|
42
|
+
files = Dir["text/**/*.md"]
|
43
|
+
@entries = {}
|
44
|
+
files.each do |f|
|
45
|
+
k = File.dirname(f)
|
46
|
+
k.gsub!('text/','')
|
47
|
+
@entries[k] = [] if @entries[k].nil?
|
48
|
+
@entries[k] << f
|
49
|
+
end
|
50
|
+
return @entries
|
51
|
+
end
|
52
|
+
def render_template(file, locals = {})
|
53
|
+
ERB.new(File.read(file)).result OpenStruct.new(locals).instance_eval{ binding }
|
54
|
+
end
|
55
|
+
def read_content(file)
|
56
|
+
content = File.read(file)
|
57
|
+
begin
|
58
|
+
if content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
59
|
+
content = "\n#{$'}\n"
|
60
|
+
data = YAML.load($1)
|
61
|
+
end
|
62
|
+
return [content, data]
|
63
|
+
rescue => e
|
64
|
+
puts "Error reading file #{File.join(base, name)}: #{e.message}"
|
65
|
+
rescue SyntaxError => e
|
66
|
+
puts "YAML Exception reading #{File.join(base, name)}: #{e.message}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
def spawn_command(cmd)
|
70
|
+
begin
|
71
|
+
stdout_and_stderr, status = Open3.capture2e(*cmd)
|
72
|
+
rescue Errno::ENOENT => e
|
73
|
+
puts e.message
|
74
|
+
else
|
75
|
+
puts stdout_and_stderr unless status.success?
|
76
|
+
status.success?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|