bookmaker 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|