bunto 1.0.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 (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.markdown +59 -0
  4. data/bin/bunto +51 -0
  5. data/lib/bunto.rb +179 -0
  6. data/lib/bunto/cleaner.rb +105 -0
  7. data/lib/bunto/collection.rb +205 -0
  8. data/lib/bunto/command.rb +65 -0
  9. data/lib/bunto/commands/build.rb +77 -0
  10. data/lib/bunto/commands/clean.rb +42 -0
  11. data/lib/bunto/commands/doctor.rb +114 -0
  12. data/lib/bunto/commands/help.rb +31 -0
  13. data/lib/bunto/commands/new.rb +82 -0
  14. data/lib/bunto/commands/serve.rb +204 -0
  15. data/lib/bunto/commands/serve/servlet.rb +61 -0
  16. data/lib/bunto/configuration.rb +323 -0
  17. data/lib/bunto/converter.rb +48 -0
  18. data/lib/bunto/converters/identity.rb +21 -0
  19. data/lib/bunto/converters/markdown.rb +92 -0
  20. data/lib/bunto/converters/markdown/kramdown_parser.rb +117 -0
  21. data/lib/bunto/converters/markdown/rdiscount_parser.rb +33 -0
  22. data/lib/bunto/converters/markdown/redcarpet_parser.rb +102 -0
  23. data/lib/bunto/converters/smartypants.rb +34 -0
  24. data/lib/bunto/convertible.rb +297 -0
  25. data/lib/bunto/deprecator.rb +46 -0
  26. data/lib/bunto/document.rb +444 -0
  27. data/lib/bunto/drops/bunto_drop.rb +21 -0
  28. data/lib/bunto/drops/collection_drop.rb +22 -0
  29. data/lib/bunto/drops/document_drop.rb +27 -0
  30. data/lib/bunto/drops/drop.rb +176 -0
  31. data/lib/bunto/drops/site_drop.rb +38 -0
  32. data/lib/bunto/drops/unified_payload_drop.rb +25 -0
  33. data/lib/bunto/drops/url_drop.rb +83 -0
  34. data/lib/bunto/entry_filter.rb +72 -0
  35. data/lib/bunto/errors.rb +10 -0
  36. data/lib/bunto/excerpt.rb +127 -0
  37. data/lib/bunto/external.rb +59 -0
  38. data/lib/bunto/filters.rb +367 -0
  39. data/lib/bunto/frontmatter_defaults.rb +188 -0
  40. data/lib/bunto/generator.rb +3 -0
  41. data/lib/bunto/hooks.rb +101 -0
  42. data/lib/bunto/layout.rb +49 -0
  43. data/lib/bunto/liquid_extensions.rb +22 -0
  44. data/lib/bunto/liquid_renderer.rb +39 -0
  45. data/lib/bunto/liquid_renderer/file.rb +50 -0
  46. data/lib/bunto/liquid_renderer/table.rb +94 -0
  47. data/lib/bunto/log_adapter.rb +115 -0
  48. data/lib/bunto/mime.types +800 -0
  49. data/lib/bunto/page.rb +180 -0
  50. data/lib/bunto/plugin.rb +96 -0
  51. data/lib/bunto/plugin_manager.rb +95 -0
  52. data/lib/bunto/post.rb +329 -0
  53. data/lib/bunto/publisher.rb +21 -0
  54. data/lib/bunto/reader.rb +126 -0
  55. data/lib/bunto/readers/collection_reader.rb +20 -0
  56. data/lib/bunto/readers/data_reader.rb +69 -0
  57. data/lib/bunto/readers/layout_reader.rb +53 -0
  58. data/lib/bunto/readers/page_reader.rb +21 -0
  59. data/lib/bunto/readers/post_reader.rb +62 -0
  60. data/lib/bunto/readers/static_file_reader.rb +21 -0
  61. data/lib/bunto/regenerator.rb +175 -0
  62. data/lib/bunto/related_posts.rb +56 -0
  63. data/lib/bunto/renderer.rb +191 -0
  64. data/lib/bunto/site.rb +391 -0
  65. data/lib/bunto/static_file.rb +141 -0
  66. data/lib/bunto/stevenson.rb +58 -0
  67. data/lib/bunto/tags/highlight.rb +122 -0
  68. data/lib/bunto/tags/include.rb +190 -0
  69. data/lib/bunto/tags/post_url.rb +88 -0
  70. data/lib/bunto/url.rb +136 -0
  71. data/lib/bunto/utils.rb +287 -0
  72. data/lib/bunto/utils/ansi.rb +59 -0
  73. data/lib/bunto/utils/platforms.rb +30 -0
  74. data/lib/bunto/version.rb +3 -0
  75. data/lib/site_template/.gitignore +3 -0
  76. data/lib/site_template/_config.yml +21 -0
  77. data/lib/site_template/_includes/footer.html +38 -0
  78. data/lib/site_template/_includes/head.html +12 -0
  79. data/lib/site_template/_includes/header.html +27 -0
  80. data/lib/site_template/_includes/icon-github.html +1 -0
  81. data/lib/site_template/_includes/icon-github.svg +1 -0
  82. data/lib/site_template/_includes/icon-twitter.html +1 -0
  83. data/lib/site_template/_includes/icon-twitter.svg +1 -0
  84. data/lib/site_template/_layouts/default.html +20 -0
  85. data/lib/site_template/_layouts/page.html +14 -0
  86. data/lib/site_template/_layouts/post.html +15 -0
  87. data/lib/site_template/_posts/0000-00-00-welcome-to-bunto.markdown.erb +25 -0
  88. data/lib/site_template/_sass/_base.scss +206 -0
  89. data/lib/site_template/_sass/_layout.scss +242 -0
  90. data/lib/site_template/_sass/_syntax-highlighting.scss +71 -0
  91. data/lib/site_template/about.md +15 -0
  92. data/lib/site_template/css/main.scss +53 -0
  93. data/lib/site_template/feed.xml +30 -0
  94. data/lib/site_template/index.html +23 -0
  95. metadata +252 -0
@@ -0,0 +1,188 @@
1
+ module Bunto
2
+ # This class handles custom defaults for YAML frontmatter settings.
3
+ # These are set in _config.yml and apply both to internal use (e.g. layout)
4
+ # and the data available to liquid.
5
+ #
6
+ # It is exposed via the frontmatter_defaults method on the site class.
7
+ class FrontmatterDefaults
8
+ # Initializes a new instance.
9
+ def initialize(site)
10
+ @site = site
11
+ end
12
+
13
+ def update_deprecated_types(set)
14
+ return set unless set.key?('scope') && set['scope'].key?('type')
15
+
16
+ set['scope']['type'] =
17
+ case set['scope']['type']
18
+ when 'page'
19
+ Deprecator.defaults_deprecate_type('page', 'pages')
20
+ 'pages'
21
+ when 'post'
22
+ Deprecator.defaults_deprecate_type('post', 'posts')
23
+ 'posts'
24
+ when 'draft'
25
+ Deprecator.defaults_deprecate_type('draft', 'drafts')
26
+ 'drafts'
27
+ else
28
+ set['scope']['type']
29
+ end
30
+
31
+ set
32
+ end
33
+
34
+ def ensure_time!(set)
35
+ return set unless set.key?('values') && set['values'].key?('date')
36
+ return set if set['values']['date'].is_a?(Time)
37
+ set['values']['date'] = Utils.parse_date(set['values']['date'], "An invalid date format was found in a front-matter default set: #{set}")
38
+ set
39
+ end
40
+
41
+ # Finds a default value for a given setting, filtered by path and type
42
+ #
43
+ # path - the path (relative to the source) of the page, post or :draft the default is used in
44
+ # type - a symbol indicating whether a :page, a :post or a :draft calls this method
45
+ #
46
+ # Returns the default value or nil if none was found
47
+ def find(path, type, setting)
48
+ value = nil
49
+ old_scope = nil
50
+
51
+ matching_sets(path, type).each do |set|
52
+ if set['values'].key?(setting) && has_precedence?(old_scope, set['scope'])
53
+ value = set['values'][setting]
54
+ old_scope = set['scope']
55
+ end
56
+ end
57
+ value
58
+ end
59
+
60
+ # Collects a hash with all default values for a page or post
61
+ #
62
+ # path - the relative path of the page or post
63
+ # type - a symbol indicating the type (:post, :page or :draft)
64
+ #
65
+ # Returns a hash with all default values (an empty hash if there are none)
66
+ def all(path, type)
67
+ defaults = {}
68
+ old_scope = nil
69
+ matching_sets(path, type).each do |set|
70
+ if has_precedence?(old_scope, set['scope'])
71
+ defaults = Utils.deep_merge_hashes(defaults, set['values'])
72
+ old_scope = set['scope']
73
+ else
74
+ defaults = Utils.deep_merge_hashes(set['values'], defaults)
75
+ end
76
+ end
77
+ defaults
78
+ end
79
+
80
+ private
81
+
82
+ # Checks if a given default setting scope matches the given path and type
83
+ #
84
+ # scope - the hash indicating the scope, as defined in _config.yml
85
+ # path - the path to check for
86
+ # type - the type (:post, :page or :draft) to check for
87
+ #
88
+ # Returns true if the scope applies to the given path and type
89
+ def applies?(scope, path, type)
90
+ applies_path?(scope, path) && applies_type?(scope, type)
91
+ end
92
+
93
+ def applies_path?(scope, path)
94
+ return true if !scope.key?('path') || scope['path'].empty?
95
+
96
+ scope_path = Pathname.new(scope['path'])
97
+ Pathname.new(sanitize_path(path)).ascend do |path|
98
+ if path.to_s == scope_path.to_s
99
+ return true
100
+ end
101
+ end
102
+ end
103
+
104
+ # Determines whether the scope applies to type.
105
+ # The scope applies to the type if:
106
+ # 1. no 'type' is specified
107
+ # 2. the 'type' in the scope is the same as the type asked about
108
+ #
109
+ # scope - the Hash defaults set being asked about application
110
+ # type - the type of the document being processed / asked about
111
+ # its defaults.
112
+ #
113
+ # Returns true if either of the above conditions are satisfied,
114
+ # otherwise returns false
115
+ def applies_type?(scope, type)
116
+ !scope.key?('type') || scope['type'].eql?(type.to_s)
117
+ end
118
+
119
+ # Checks if a given set of default values is valid
120
+ #
121
+ # set - the default value hash, as defined in _config.yml
122
+ #
123
+ # Returns true if the set is valid and can be used in this class
124
+ def valid?(set)
125
+ set.is_a?(Hash) && set['values'].is_a?(Hash)
126
+ end
127
+
128
+ # Determines if a new scope has precedence over an old one
129
+ #
130
+ # old_scope - the old scope hash, or nil if there's none
131
+ # new_scope - the new scope hash
132
+ #
133
+ # Returns true if the new scope has precedence over the older
134
+ def has_precedence?(old_scope, new_scope)
135
+ return true if old_scope.nil?
136
+
137
+ new_path = sanitize_path(new_scope['path'])
138
+ old_path = sanitize_path(old_scope['path'])
139
+
140
+ if new_path.length != old_path.length
141
+ new_path.length >= old_path.length
142
+ elsif new_scope.key? 'type'
143
+ true
144
+ else
145
+ !old_scope.key? 'type'
146
+ end
147
+ end
148
+
149
+ # Collects a list of sets that match the given path and type
150
+ #
151
+ # Returns an array of hashes
152
+ def matching_sets(path, type)
153
+ valid_sets.select do |set|
154
+ !set.key?('scope') || applies?(set['scope'], path, type)
155
+ end
156
+ end
157
+
158
+ # Returns a list of valid sets
159
+ #
160
+ # This is not cached to allow plugins to modify the configuration
161
+ # and have their changes take effect
162
+ #
163
+ # Returns an array of hashes
164
+ def valid_sets
165
+ sets = @site.config['defaults']
166
+ return [] unless sets.is_a?(Array)
167
+
168
+ sets.map do |set|
169
+ if valid?(set)
170
+ ensure_time!(update_deprecated_types(set))
171
+ else
172
+ Bunto.logger.warn "Defaults:", "An invalid front-matter default set was found:"
173
+ Bunto.logger.warn "#{set}"
174
+ nil
175
+ end
176
+ end.compact
177
+ end
178
+
179
+ # Sanitizes the given path by removing a leading and adding a trailing slash
180
+ def sanitize_path(path)
181
+ if path.nil? || path.empty?
182
+ ""
183
+ else
184
+ path.gsub(/\A\//, '').gsub(/([^\/])\z/, '\1')
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,3 @@
1
+ module Bunto
2
+ Generator = Class.new(Plugin)
3
+ end
@@ -0,0 +1,101 @@
1
+ module Bunto
2
+ module Hooks
3
+ DEFAULT_PRIORITY = 20
4
+
5
+ # compatibility layer for octopress-hooks users
6
+ PRIORITY_MAP = {
7
+ :low => 10,
8
+ :normal => 20,
9
+ :high => 30
10
+ }.freeze
11
+
12
+ # initial empty hooks
13
+ @registry = {
14
+ :site => {
15
+ :after_reset => [],
16
+ :post_read => [],
17
+ :pre_render => [],
18
+ :post_render => [],
19
+ :post_write => []
20
+ },
21
+ :pages => {
22
+ :post_init => [],
23
+ :pre_render => [],
24
+ :post_render => [],
25
+ :post_write => []
26
+ },
27
+ :posts => {
28
+ :post_init => [],
29
+ :pre_render => [],
30
+ :post_render => [],
31
+ :post_write => []
32
+ },
33
+ :documents => {
34
+ :post_init => [],
35
+ :pre_render => [],
36
+ :post_render => [],
37
+ :post_write => []
38
+ }
39
+ }
40
+
41
+ # map of all hooks and their priorities
42
+ @hook_priority = {}
43
+
44
+ NotAvailable = Class.new(RuntimeError)
45
+ Uncallable = Class.new(RuntimeError)
46
+
47
+ # register hook(s) to be called later, public API
48
+ def self.register(owners, event, priority: DEFAULT_PRIORITY, &block)
49
+ Array(owners).each do |owner|
50
+ register_one(owner, event, priority_value(priority), &block)
51
+ end
52
+ end
53
+
54
+ # Ensure the priority is a Fixnum
55
+ def self.priority_value(priority)
56
+ return priority if priority.is_a?(Fixnum)
57
+ PRIORITY_MAP[priority] || DEFAULT_PRIORITY
58
+ end
59
+
60
+ # register a single hook to be called later, internal API
61
+ def self.register_one(owner, event, priority, &block)
62
+ @registry[owner] ||={
63
+ :post_init => [],
64
+ :pre_render => [],
65
+ :post_render => [],
66
+ :post_write => []
67
+ }
68
+
69
+ unless @registry[owner][event]
70
+ raise NotAvailable, "Invalid hook. #{owner} supports only the " \
71
+ "following hooks #{@registry[owner].keys.inspect}"
72
+ end
73
+
74
+ unless block.respond_to? :call
75
+ raise Uncallable, "Hooks must respond to :call"
76
+ end
77
+
78
+ insert_hook owner, event, priority, &block
79
+ end
80
+
81
+ def self.insert_hook(owner, event, priority, &block)
82
+ @hook_priority[block] = "#{priority}.#{@hook_priority.size}".to_f
83
+ @registry[owner][event] << block
84
+ end
85
+
86
+ # interface for Bunto core components to trigger hooks
87
+ def self.trigger(owner, event, *args)
88
+ # proceed only if there are hooks to call
89
+ return unless @registry[owner]
90
+ return unless @registry[owner][event]
91
+
92
+ # hooks to call for this owner and event
93
+ hooks = @registry[owner][event]
94
+
95
+ # sort and call hooks according to priority and load order
96
+ hooks.sort_by { |h| @hook_priority[h] }.each do |hook|
97
+ hook.call(*args)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,49 @@
1
+ module Bunto
2
+ class Layout
3
+ include Convertible
4
+
5
+ # Gets the Site object.
6
+ attr_reader :site
7
+
8
+ # Gets the name of this layout.
9
+ attr_reader :name
10
+
11
+ # Gets the path to this layout.
12
+ attr_reader :path
13
+
14
+ # Gets/Sets the extension of this layout.
15
+ attr_accessor :ext
16
+
17
+ # Gets/Sets the Hash that holds the metadata for this layout.
18
+ attr_accessor :data
19
+
20
+ # Gets/Sets the content of this layout.
21
+ attr_accessor :content
22
+
23
+ # Initialize a new Layout.
24
+ #
25
+ # site - The Site.
26
+ # base - The String path to the source.
27
+ # name - The String filename of the post file.
28
+ def initialize(site, base, name)
29
+ @site = site
30
+ @base = base
31
+ @name = name
32
+ @path = site.in_source_dir(base, name)
33
+
34
+ self.data = {}
35
+
36
+ process(name)
37
+ read_yaml(base, name)
38
+ end
39
+
40
+ # Extract information from the layout filename.
41
+ #
42
+ # name - The String filename of the layout file.
43
+ #
44
+ # Returns nothing.
45
+ def process(name)
46
+ self.ext = File.extname(name)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ module Bunto
2
+ module LiquidExtensions
3
+
4
+ # Lookup a Liquid variable in the given context.
5
+ #
6
+ # context - the Liquid context in question.
7
+ # variable - the variable name, as a string.
8
+ #
9
+ # Returns the value of the variable in the context
10
+ # or the variable name if not found.
11
+ def lookup_variable(context, variable)
12
+ lookup = context
13
+
14
+ variable.split(".").each do |value|
15
+ lookup = lookup[value]
16
+ end
17
+
18
+ lookup || variable
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ require 'bunto/liquid_renderer/file'
2
+ require 'bunto/liquid_renderer/table'
3
+
4
+ module Bunto
5
+ class LiquidRenderer
6
+ def initialize(site)
7
+ @site = site
8
+ reset
9
+ end
10
+
11
+ def reset
12
+ @stats = {}
13
+ end
14
+
15
+ def file(filename)
16
+ filename = @site.in_source_dir(filename).sub(/\A#{Regexp.escape(@site.source)}\//, '')
17
+
18
+ LiquidRenderer::File.new(self, filename).tap do
19
+ @stats[filename] ||= {}
20
+ @stats[filename][:count] ||= 0
21
+ @stats[filename][:count] += 1
22
+ end
23
+ end
24
+
25
+ def increment_bytes(filename, bytes)
26
+ @stats[filename][:bytes] ||= 0
27
+ @stats[filename][:bytes] += bytes
28
+ end
29
+
30
+ def increment_time(filename, time)
31
+ @stats[filename][:time] ||= 0.0
32
+ @stats[filename][:time] += time
33
+ end
34
+
35
+ def stats_table(n = 50)
36
+ LiquidRenderer::Table.new(@stats).to_s(n)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,50 @@
1
+ module Bunto
2
+ class LiquidRenderer
3
+ class File
4
+ def initialize(renderer, filename)
5
+ @renderer = renderer
6
+ @filename = filename
7
+ end
8
+
9
+ def parse(content)
10
+ measure_time do
11
+ @template = Liquid::Template.parse(content, line_numbers: true)
12
+ end
13
+
14
+ self
15
+ end
16
+
17
+ def render(*args)
18
+ measure_time do
19
+ measure_bytes do
20
+ @template.render(*args)
21
+ end
22
+ end
23
+ end
24
+
25
+ def render!(*args)
26
+ measure_time do
27
+ measure_bytes do
28
+ @template.render!(*args)
29
+ end
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def measure_bytes
36
+ yield.tap do |str|
37
+ @renderer.increment_bytes(@filename, str.bytesize)
38
+ end
39
+ end
40
+
41
+ def measure_time
42
+ before = Time.now
43
+ yield
44
+ ensure
45
+ after = Time.now
46
+ @renderer.increment_time(@filename, after - before)
47
+ end
48
+ end
49
+ end
50
+ end