bookery 0.0.2 → 0.1.0

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