bookery 0.0.2 → 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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -8
  3. data/.travis.yml +5 -0
  4. data/{LICENSE.txt → LICENSE} +0 -0
  5. data/README.md +26 -11
  6. data/Rakefile +2 -1
  7. data/bookery.gemspec +18 -14
  8. data/docs/config.md +61 -0
  9. data/docs/contributing.md +47 -0
  10. data/docs/editing.md +82 -0
  11. data/docs/project.md +77 -0
  12. data/lib/bookery.rb +27 -4
  13. data/lib/bookery/book.rb +13 -15
  14. data/lib/bookery/cli.rb +29 -12
  15. data/lib/bookery/config.rb +42 -0
  16. data/lib/bookery/factories/chapter_factory.rb +35 -0
  17. data/lib/bookery/factories/publisher_factory.rb +13 -0
  18. data/lib/bookery/processors/include_processor.rb +17 -0
  19. data/lib/bookery/project.rb +75 -0
  20. data/lib/bookery/publishers/html_publisher.rb +68 -0
  21. data/lib/bookery/version.rb +1 -1
  22. data/{bin → libexec}/bookery +1 -4
  23. data/templates/basic/Gemfile.tt +2 -0
  24. data/templates/basic/assets/css/base.css +7 -0
  25. data/templates/basic/assets/css/layout.css +23 -0
  26. data/templates/basic/assets/css/modules/code-block.css +132 -0
  27. data/templates/{book/assets → basic/assets/includes}/.empty_directory +0 -0
  28. data/templates/basic/assets/style.css +3 -0
  29. data/templates/basic/assets/templates/template.html.erb +30 -0
  30. data/templates/basic/book/en/001-first-chapter/01-chapter-file.md +9 -0
  31. data/templates/basic/config.yml +14 -0
  32. data/test/bookery/book_test.rb +32 -9
  33. data/test/bookery/cli_test.rb +25 -14
  34. data/test/bookery/config_test.rb +26 -0
  35. data/test/bookery/factories/chapter_factory_test.rb +24 -0
  36. data/test/bookery/factories/publisher_factory_test.rb +16 -0
  37. data/test/bookery/processor/include_processor_test.rb +38 -0
  38. data/test/bookery/project_test.rb +37 -0
  39. data/test/bookery/publishers/html_publisher_test.rb +78 -0
  40. data/test/data/project/Gemfile.tt +2 -0
  41. data/{templates/book/book/.empty_directory → test/data/project/assets/images/cover.png} +0 -0
  42. data/test/data/project/assets/includes/ruby/chapter2.rb +3 -0
  43. data/test/data/project/assets/style.css +1 -0
  44. data/test/data/project/assets/templates/template.html.erb +15 -0
  45. data/test/data/project/book/en/001-first-chapter/01-chapter-file.md +9 -0
  46. data/test/data/project/book/en/002-second-chapter/01-chapter-file.md +7 -0
  47. data/test/data/project/book/en/002-second-chapter/02-second-chapter-file.md +1 -0
  48. data/{templates/book/book/en/.empty_directory → test/data/project/book/es/.git-keep} +0 -0
  49. data/test/data/project/config.yml +19 -0
  50. data/test/test_helper.rb +3 -46
  51. metadata +107 -79
  52. data/Gemfile.lock +0 -64
  53. data/Guardfile +0 -9
  54. data/lib/bookery/chapter.rb +0 -30
  55. data/templates/book/Gemfile +0 -2
  56. data/templates/book/README.md +0 -3
  57. data/templates/book/config.yml +0 -5
  58. data/templates/sample/Gemfile +0 -2
  59. data/templates/sample/README.md +0 -3
  60. data/templates/sample/book/en/01-introduction/01-intro-to-book.md +0 -0
  61. data/templates/sample/book/en/02-second-chapter/01-first-things-first.md +0 -1
  62. data/templates/sample/book/en/02-second-chapter/02-before_intro.md +0 -1
  63. data/templates/sample/config.yml +0 -5
  64. data/test/bookery/chapter_test.rb +0 -21
data/lib/bookery/book.rb CHANGED
@@ -1,23 +1,21 @@
1
1
  module Bookery
2
2
  class Book
3
- attr_reader :lang
3
+ attr_reader :title, :authors, :editors, :language, :template
4
+ attr_reader :chapters
4
5
 
5
- def initialize(lang, options = {})
6
- @lang = lang
7
- @cwd = options[:cwd] || ''
6
+ def initialize(chapters, config)
7
+ @title = config[:title]
8
+ @authors = config[:authors]
9
+ @editors = config[:editors]
10
+ @language = config[:language]
11
+ @template = config[:template]
12
+ @chapters = chapters
8
13
  end
9
14
 
10
- def chapters
11
- @chapters ||= Dir.entries(book_dir).collect do |entry|
12
- Chapter.new(entry) if entry.size > 2
13
- end.compact
15
+ def content
16
+ chapters.inject('') do |content, chapter|
17
+ content << chapter.content
18
+ end
14
19
  end
15
-
16
- private
17
-
18
- def book_dir
19
- File.join(@cwd, 'book', @lang)
20
- end
21
-
22
20
  end
23
21
  end
data/lib/bookery/cli.rb CHANGED
@@ -1,21 +1,38 @@
1
- require 'thor'
2
- require 'pathname'
3
-
4
- bin_file = Pathname.new(__FILE__).realpath
5
-
6
1
  module Bookery
7
2
  class CLI < Thor
8
3
  include Thor::Actions
9
4
 
10
- source_paths << File.expand_path(
11
- '../../../templates',
12
- Pathname.new(__FILE__).realpath
13
- )
5
+ source_root(File.expand_path(
6
+ '../../../templates',
7
+ Pathname.new(__FILE__).realpath
8
+ ))
14
9
 
15
- desc 'new PATH', 'creates a new book in PATH'
16
- def new(path, template_name = 'book')
17
- directory(template_name, path)
10
+ map 'v' => :version
11
+ map 'p' => :publish
12
+
13
+ desc 'version', 'prints the version of bookery being used'
14
+ def version
15
+ puts Bookery::VERSION
18
16
  end
19
17
 
18
+ desc 'new <path>', 'creates a new book at the specified path'
19
+ method_option :template, banner: '<template>'
20
+ def new(path)
21
+ template = 'basic'
22
+ template = options[:template] if options[:template]
23
+ directory(template, path)
24
+ end
25
+
26
+ desc 'publish', 'publishes your book'
27
+ method_option :project_dir, banner: '<project_dir>'
28
+ def publish
29
+ project_dir = Dir.pwd
30
+ project_dir = options[:project_dir] if options[:project_dir]
31
+ config = YAML.load_file(File.join(project_dir, 'config.yml'))
32
+
33
+ project = Project.new(project_dir, config.symbolize_keys)
34
+ publishers = Factories::PublisherFactory.create_publishers(project.generator_config)
35
+ project.publish(publishers)
36
+ end
20
37
  end
21
38
  end
@@ -0,0 +1,42 @@
1
+ module Bookery
2
+ class Config
3
+ META_METHODS = [:title, :authors, :editors, :template]
4
+
5
+ attr_reader :language
6
+
7
+ def initialize(language, config)
8
+ @language = language
9
+ @config = config.symbolize_keys
10
+ end
11
+
12
+ def [](key)
13
+ public_send(key)
14
+ end
15
+
16
+ def method_missing(method, *args)
17
+ if META_METHODS.include?(method)
18
+ if value = lang_specific_value(method)
19
+ value
20
+ else
21
+ config[method]
22
+ end
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def config
31
+ @config
32
+ end
33
+
34
+ def lang_specific_value(key)
35
+ if config[language.to_sym] and config[language.to_sym][key]
36
+ config[language.to_sym][key]
37
+ else
38
+ nil
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,35 @@
1
+ module Bookery
2
+ module Factories
3
+ module ChapterFactory
4
+ extend self
5
+
6
+ def create_chapters(book_dir)
7
+ chapters = []
8
+
9
+ Dir.entries(book_dir).sort.each do |path|
10
+ next if path =~ /\.\.?\z/
11
+ chapters << Chapter.new(File.join(book_dir, path))
12
+ end
13
+
14
+ chapters
15
+ end
16
+
17
+ private
18
+
19
+ class Chapter
20
+ attr_reader :name, :content
21
+
22
+ def initialize(chapter_dir)
23
+ @name = File.basename(chapter_dir)
24
+ @content = combine_chapter_files(chapter_dir)
25
+ end
26
+
27
+ def combine_chapter_files(dir)
28
+ Dir.glob(File.join(dir, '*.md')).sort.inject('') do |content, file|
29
+ content << File.read(file) << "\n"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ module Bookery
2
+ module Factories
3
+ module PublisherFactory
4
+ extend self
5
+
6
+ def create_publishers(config)
7
+ [ Publishers::HTMLPublisher.new(config) ]
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ require_relative '../publishers/html_publisher'
@@ -0,0 +1,17 @@
1
+ module Bookery
2
+ module Processors
3
+ class IncludeProcessor
4
+ attr_reader :current_dir
5
+
6
+ def initialize(cwd)
7
+ @current_dir = cwd
8
+ end
9
+
10
+ def process(markdown)
11
+ markdown.gsub(/\b*includes::(.*)\b*/) do |match|
12
+ File.read(File.join(current_dir, $1))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,75 @@
1
+ module Bookery
2
+ class Project
3
+ attr_reader :dir, :config
4
+
5
+ DEFAULTS = {
6
+ book_dir: 'book',
7
+ assets_dir: 'assets',
8
+ config_file: 'config.yml'
9
+ }
10
+
11
+ def initialize(project_dir, config = {})
12
+ @dir = project_dir
13
+ @config = DEFAULTS.merge(config)
14
+ end
15
+
16
+ def book_config
17
+ @book_config ||= Proc.new do
18
+ conf = config[:book]
19
+ conf[:template] = File.join(dir, conf[:template])
20
+ conf
21
+ end.call
22
+ end
23
+
24
+ def generator_config
25
+ conf = config[:generator]
26
+ conf[:project_dir] = dir
27
+ conf
28
+ end
29
+
30
+ def books
31
+ @books ||= load_books
32
+ end
33
+
34
+ def assets
35
+ Dir.glob(File.join(dir, config[:assets_dir], '**/*')).reject do |file|
36
+ File.directory?(file) or file =~ /\/(includes|templates)\//
37
+ end.sort
38
+ end
39
+
40
+ def publish(publishers)
41
+ books.each do |book|
42
+ prepare_output_dir(book)
43
+ publishers.each { |publisher| publisher.publish(book) }
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def prepare_output_dir(book)
50
+ output_dir = File.join(dir, 'output', book.language)
51
+ ::FileUtils.rm_rf(output_dir)
52
+ ::FileUtils.mkdir_p(output_dir)
53
+
54
+ assets.each do |asset|
55
+ dest = File.dirname(File.join(output_dir, asset.sub("#{dir}/", '')))
56
+ ::FileUtils.mkdir_p(dest)
57
+ ::FileUtils.cp(asset, dest)
58
+ end
59
+ end
60
+
61
+ def load_books
62
+ path = File.join(dir, config[:book_dir])
63
+ Dir.entries(path).sort.collect do |language|
64
+ make_book(path, language) unless language =~ /\.\.?\z/
65
+ end.compact
66
+ end
67
+
68
+ def make_book(path, language)
69
+ Book.new(
70
+ Factories::ChapterFactory.create_chapters(File.join(path, language)),
71
+ Config.new(language, book_config)
72
+ )
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,68 @@
1
+ module Bookery
2
+ module Publishers
3
+ class HTMLPublisher
4
+ DEFAULTS = {
5
+ input: 'GFM',
6
+ enable_coderay: true,
7
+ coderay_css: 'class',
8
+ coderay_line_numbers: 'inline'
9
+ }
10
+
11
+ attr_reader :options, :processors
12
+
13
+ def initialize(options = {})
14
+ @options = DEFAULTS.merge(options)
15
+
16
+ includes_path = File.join(options[:project_dir], 'assets', 'includes')
17
+ @processors = [
18
+ Processors::IncludeProcessor.new(includes_path)
19
+ ]
20
+ end
21
+
22
+ def publish(book)
23
+ markdown = process_content(book.content)
24
+
25
+ pre_publish(book)
26
+ output = Kramdown::Document.new(markdown, options).to_html
27
+
28
+ output_dir = File.join(options[:project_dir], 'output', book.language)
29
+ ::FileUtils.mkdir_p(output_dir)
30
+ File.write(File.join(output_dir, 'index.html'), output)
31
+
32
+ output
33
+ end
34
+
35
+ def pre_publish(book)
36
+ erb = ERB.new(File.read(book.template))
37
+ bound_object = TemplateObject.new(book)
38
+ options[:template] = "string://#{erb.result(bound_object.get_binding)}"
39
+ end
40
+
41
+ private
42
+
43
+ def process_content(content)
44
+ result = content.dup
45
+ processors.each do |processor|
46
+ result = processor.process(result)
47
+ end
48
+
49
+ result
50
+ end
51
+
52
+ class TemplateObject
53
+ attr_reader :title, :authors, :editors, :language, :body
54
+ def initialize(book)
55
+ @title = book.title
56
+ @authors = book.authors.join(', ')
57
+ @editors = book.editors.join(', ')
58
+ @language = book.language
59
+ @body = '<%= @body %>'
60
+ end
61
+
62
+ def get_binding
63
+ binding
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,3 +1,3 @@
1
1
  module Bookery
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,9 +1,6 @@
1
1
  #! /usr/bin/env ruby
2
-
3
- require "pathname"
2
+ require 'pathname'
4
3
  bin_file = Pathname.new(__FILE__).realpath
5
-
6
- # add self to libpath
7
4
  $:.unshift File.expand_path("../../lib", bin_file)
8
5
 
9
6
  require 'bookery'
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gem 'bookery'
@@ -0,0 +1,7 @@
1
+ /*! normalize.css v2.1.3 | MIT License | git.io/normalize */
2
+ article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a{background:transparent}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}
3
+
4
+ body {
5
+ background-color: #f9f9f9;
6
+ color: #505050;
7
+ }
@@ -0,0 +1,23 @@
1
+ body > header, body > footer {
2
+ background: #000;
3
+ color: #fff;
4
+ }
5
+
6
+ body > header {
7
+ padding: 2px 0;
8
+ }
9
+
10
+ body > footer {
11
+ padding: 6px 0;
12
+ text-align: center;
13
+ }
14
+
15
+ .container {
16
+ width: 960px;
17
+ margin: 0 auto;
18
+ }
19
+
20
+ section.container {
21
+ background: #fff;
22
+ padding: 20px;
23
+ }
@@ -0,0 +1,132 @@
1
+ .CodeRay {
2
+ background-color: #FFF;
3
+ border: 1px solid #CCC;
4
+ font-family: Monaco, "Courier New", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", monospace;
5
+ color: #000;
6
+ }
7
+
8
+ .CodeRay pre {
9
+ margin: 0px;
10
+ }
11
+
12
+ div.CodeRay { }
13
+ span.CodeRay { white-space: pre; border: 0px; padding: 2px }
14
+
15
+ table.CodeRay { border-collapse: collapse; width: 100%; padding: 2px }
16
+ table.CodeRay td {
17
+ padding: 1em 0.5em;
18
+ vertical-align: top;
19
+ }
20
+
21
+ .CodeRay .line-numbers, .CodeRay .no {
22
+ background-color: #ECECEC;
23
+ color: #AAA;
24
+ text-align: right;
25
+ margin-right: 10px;
26
+ }
27
+
28
+ .CodeRay .line-numbers a {
29
+ color: #AAA;
30
+ }
31
+
32
+ .CodeRay .line-numbers tt { font-weight: bold }
33
+ .CodeRay .line-numbers .highlighted { color: red }
34
+ .CodeRay .line { display: block; float: left; width: 100%; }
35
+ .CodeRay span.line-numbers { padding: 0px 8px; }
36
+ .CodeRay .code { width: 100% }
37
+
38
+ ol.CodeRay { font-size: 10pt }
39
+ ol.CodeRay li { white-space: pre }
40
+
41
+ .CodeRay .code pre { overflow: auto }
42
+ .CodeRay .debug { color:white ! important; background:blue ! important; }
43
+
44
+ .CodeRay .annotation { color:#007 }
45
+ .CodeRay .attribute-name { color:#f08 }
46
+ .CodeRay .attribute-value { color:#700 }
47
+ .CodeRay .binary { color:#509; font-weight:bold }
48
+ .CodeRay .comment { color:#998; font-style: italic;}
49
+ .CodeRay .char { color:#04D }
50
+ .CodeRay .char .content { color:#04D }
51
+ .CodeRay .char .delimiter { color:#039 }
52
+ .CodeRay .class { color:#458; font-weight:bold }
53
+ .CodeRay .complex { color:#A08; font-weight:bold }
54
+ .CodeRay .constant { color:teal; }
55
+ .CodeRay .color { color:#0A0 }
56
+ .CodeRay .class-variable { color:#369 }
57
+ .CodeRay .decorator { color:#B0B; }
58
+ .CodeRay .definition { color:#099; font-weight:bold }
59
+ .CodeRay .directive { color:#088; font-weight:bold }
60
+ .CodeRay .delimiter { color:black }
61
+ .CodeRay .doc { color:#970 }
62
+ .CodeRay .doctype { color:#34b }
63
+ .CodeRay .doc-string { color:#D42; font-weight:bold }
64
+ .CodeRay .escape { color:#666; font-weight:bold }
65
+ .CodeRay .entity { color:#800; font-weight:bold }
66
+ .CodeRay .error { color:#F00; background-color:#FAA }
67
+ .CodeRay .exception { color:#C00; font-weight:bold }
68
+ .CodeRay .filename { color:#099; }
69
+ .CodeRay .function { color:#900; font-weight:bold }
70
+ .CodeRay .global-variable { color:teal; font-weight:bold }
71
+ .CodeRay .hex { color:#058; font-weight:bold }
72
+ .CodeRay .integer { color:#099; }
73
+ .CodeRay .include { color:#B44; font-weight:bold }
74
+ .CodeRay .inline { color: black }
75
+ .CodeRay .inline .inline { background: #ccc }
76
+ .CodeRay .inline .inline .inline { background: #bbb }
77
+ .CodeRay .inline .inline-delimiter { color: #D14; }
78
+ .CodeRay .inline-delimiter { color: #D14; }
79
+ .CodeRay .important { color:#f00; }
80
+ .CodeRay .interpreted { color:#B2B; font-weight:bold }
81
+ .CodeRay .instance-variable { color:teal }
82
+ .CodeRay .label { color:#970; font-weight:bold }
83
+ .CodeRay .local-variable { color:#963 }
84
+ .CodeRay .octal { color:#40E; font-weight:bold }
85
+ .CodeRay .operator { }
86
+ .CodeRay .predefined-constant { font-weight:bold }
87
+ .CodeRay .predefined { color:#369; font-weight:bold }
88
+ .CodeRay .preprocessor { color:#579; }
89
+ .CodeRay .pseudo-class { color:#00C; font-weight:bold }
90
+ .CodeRay .predefined-type { color:#074; font-weight:bold }
91
+ .CodeRay .reserved, .keyword { color:#000; font-weight:bold }
92
+
93
+ .CodeRay .key { color: #808; }
94
+ .CodeRay .key .delimiter { color: #606; }
95
+ .CodeRay .key .char { color: #80f; }
96
+ .CodeRay .value { color: #088; }
97
+
98
+ .CodeRay .regexp { background-color:#fff0ff }
99
+ .CodeRay .regexp .content { color:#808 }
100
+ .CodeRay .regexp .delimiter { color:#404 }
101
+ .CodeRay .regexp .modifier { color:#C2C }
102
+ .CodeRay .regexp .function { color:#404; font-weight: bold }
103
+
104
+ .CodeRay .string { color: #D20; }
105
+ .CodeRay .string .string { }
106
+ .CodeRay .string .string .string { background-color:#ffd0d0 }
107
+ .CodeRay .string .content { color: #D14; }
108
+ .CodeRay .string .char { color: #D14; }
109
+ .CodeRay .string .delimiter { color: #D14; }
110
+
111
+ .CodeRay .shell { color:#D14 }
112
+ .CodeRay .shell .content { }
113
+ .CodeRay .shell .delimiter { color:#D14 }
114
+
115
+ .CodeRay .symbol { color:#990073 }
116
+ .CodeRay .symbol .content { color:#A60 }
117
+ .CodeRay .symbol .delimiter { color:#630 }
118
+
119
+ .CodeRay .tag { color:#070 }
120
+ .CodeRay .tag-special { color:#D70; font-weight:bold }
121
+ .CodeRay .type { color:#339; font-weight:bold }
122
+ .CodeRay .variable { color:#036 }
123
+
124
+ .CodeRay .insert { background: #afa; }
125
+ .CodeRay .delete { background: #faa; }
126
+ .CodeRay .change { color: #aaf; background: #007; }
127
+ .CodeRay .head { color: #f8f; background: #505 }
128
+
129
+ .CodeRay .insert .insert { color: #080; font-weight:bold }
130
+ .CodeRay .delete .delete { color: #800; font-weight:bold }
131
+ .CodeRay .change .change { color: #66f; }
132
+ .CodeRay .head .head { color: #f4f; }