nanoc 1.6.2 → 2.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 (51) hide show
  1. data/ChangeLog +27 -0
  2. data/Rakefile +34 -27
  3. data/bin/nanoc +153 -49
  4. data/lib/nanoc.rb +15 -33
  5. data/lib/nanoc/base/auto_compiler.rb +124 -0
  6. data/lib/nanoc/base/compiler.rb +55 -0
  7. data/lib/nanoc/base/core_ext/hash.rb +34 -0
  8. data/lib/nanoc/base/data_source.rb +53 -0
  9. data/lib/nanoc/base/enhancements.rb +89 -0
  10. data/lib/nanoc/base/filter.rb +16 -0
  11. data/lib/nanoc/base/layout_processor.rb +33 -0
  12. data/lib/nanoc/base/page.rb +155 -0
  13. data/lib/nanoc/base/page_proxy.rb +31 -0
  14. data/lib/nanoc/base/plugin.rb +19 -0
  15. data/lib/nanoc/base/plugin_manager.rb +33 -0
  16. data/lib/nanoc/base/site.rb +143 -0
  17. data/lib/nanoc/data_sources/database.rb +259 -0
  18. data/lib/nanoc/data_sources/filesystem.rb +308 -0
  19. data/lib/nanoc/data_sources/trivial.rb +145 -0
  20. data/lib/nanoc/filters/erb.rb +34 -0
  21. data/lib/nanoc/filters/haml.rb +16 -0
  22. data/lib/nanoc/filters/markaby.rb +15 -0
  23. data/lib/nanoc/filters/markdown.rb +13 -0
  24. data/lib/nanoc/filters/rdoc.rb +14 -0
  25. data/lib/nanoc/filters/smartypants.rb +13 -0
  26. data/lib/nanoc/filters/textile.rb +13 -0
  27. data/lib/nanoc/layout_processors/erb.rb +35 -0
  28. data/lib/nanoc/layout_processors/haml.rb +18 -0
  29. data/lib/nanoc/layout_processors/markaby.rb +16 -0
  30. metadata +37 -30
  31. data/lib/nanoc/compiler.rb +0 -145
  32. data/lib/nanoc/core_ext.rb +0 -1
  33. data/lib/nanoc/core_ext/array.rb +0 -17
  34. data/lib/nanoc/core_ext/hash.rb +0 -43
  35. data/lib/nanoc/core_ext/string.rb +0 -13
  36. data/lib/nanoc/core_ext/yaml.rb +0 -10
  37. data/lib/nanoc/creator.rb +0 -180
  38. data/lib/nanoc/enhancements.rb +0 -101
  39. data/lib/nanoc/filters.rb +0 -7
  40. data/lib/nanoc/filters/eruby_filter.rb +0 -39
  41. data/lib/nanoc/filters/haml_filter.rb +0 -18
  42. data/lib/nanoc/filters/liquid_filter.rb +0 -47
  43. data/lib/nanoc/filters/markaby_filter.rb +0 -15
  44. data/lib/nanoc/filters/markdown_filter.rb +0 -13
  45. data/lib/nanoc/filters/rdoc_filter.rb +0 -15
  46. data/lib/nanoc/filters/sass_filter.rb +0 -13
  47. data/lib/nanoc/filters/smartypants_filter.rb +0 -13
  48. data/lib/nanoc/filters/textile_filter.rb +0 -13
  49. data/lib/nanoc/page.rb +0 -171
  50. data/lib/nanoc/page_drop.rb +0 -18
  51. data/lib/nanoc/page_proxy.rb +0 -30
@@ -0,0 +1,55 @@
1
+ module Nanoc
2
+ class Compiler
3
+
4
+ attr_reader :stack
5
+
6
+ def initialize(site)
7
+ @site = site
8
+ end
9
+
10
+ def run(page=nil)
11
+ # Give feedback
12
+ log(:high, "Compiling #{page.nil? ? 'site' : 'page'}...")
13
+ time_before = Time.now
14
+
15
+ # Get the data we need
16
+ @site.load_data
17
+ eval(@site.code, $nanoc_binding)
18
+
19
+ # Create output directory if necessary
20
+ FileUtils.mkdir_p(@site.config[:output_dir])
21
+
22
+ # Compile
23
+ @stack = []
24
+ pages = (page.nil? ? @site.pages : [ page ])
25
+ pages.each do |current_page|
26
+ begin
27
+ current_page.compile
28
+ rescue => exception
29
+ handle_exception(exception, current_page, !page.nil?)
30
+ end
31
+ end
32
+
33
+ # Give feedback
34
+ log(:high, "No pages were modified.") unless pages.any? { |page| page.modified? }
35
+ log(:high, "#{page.nil? ? 'Site' : 'Pages'} compiled in #{format('%.2f', Time.now - time_before)}s.")
36
+ end
37
+
38
+ def handle_exception(exception, page, single_page)
39
+ raise exception if single_page
40
+
41
+ log(:high, "ERROR: An exception occured while compiling page #{page.path}.", $stderr)
42
+ log(:high, "", $stderr)
43
+ log(:high, "If you think this is a bug in nanoc, please do report it at", $stderr)
44
+ log(:high, "<http://nanoc.stoneship.org/trac/newticket> -- thanks!", $stderr)
45
+ log(:high, "", $stderr)
46
+ log(:high, 'Message:', $stderr)
47
+ log(:high, ' ' + exception.message, $stderr)
48
+ log(:high, 'Backtrace:', $stderr)
49
+ log(:high, exception.backtrace.map { |t| ' - ' + t }.join("\n"), $stderr)
50
+
51
+ exit(1)
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,34 @@
1
+ require 'time'
2
+
3
+ class Hash
4
+
5
+ # Cleans up the hash and returns the result. It performs the following
6
+ # operations:
7
+ #
8
+ # * Values with keys ending in _at and _on are converted into Times and
9
+ # Dates, respectively
10
+ # * All keys are converted to symbols
11
+ # * Value strings 'true', 'false', and 'none' are converted into
12
+ # true, false, and nil, respectively
13
+ def clean
14
+ inject({}) do |hash, (key, value)|
15
+ real_key = key.to_s
16
+ if real_key =~ /_on$/
17
+ hash.merge(key.to_sym => Date.parse(value))
18
+ elsif real_key =~ /_at$/
19
+ hash.merge(key.to_sym => Time.parse(value))
20
+ elsif value == 'true'
21
+ hash.merge(key.to_sym => true)
22
+ elsif value == 'false'
23
+ hash.merge(key.to_sym => false)
24
+ elsif value == 'none'
25
+ hash.merge(key.to_sym => nil)
26
+ elsif value.is_a?(Hash)
27
+ hash.merge(key.to_sym => value.clean)
28
+ else
29
+ hash.merge(key.to_sym => value)
30
+ end
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,53 @@
1
+ module Nanoc
2
+ class DataSource < Plugin
3
+
4
+ attr_reader :config
5
+
6
+ def initialize(site)
7
+ @site = site
8
+ @references = 0
9
+ end
10
+
11
+ # Preparation
12
+
13
+ def loading
14
+ # Load if necessary
15
+ up if @references == 0
16
+ @references += 1
17
+
18
+ yield
19
+ ensure
20
+ # Unload if necessary
21
+ @references -= 1
22
+ down if @references == 0
23
+ end
24
+
25
+ def up ; end
26
+ def down ; end
27
+
28
+ def setup ; end
29
+
30
+ # Loading data
31
+
32
+ def pages ; error 'DataSource#pages must be overridden' ; end
33
+ def page_defaults ; error 'DataSource#page_defaults must be overridden' ; end
34
+ def layouts ; error 'DataSource#layouts must be overridden' ; end
35
+ def templates ; error 'DataSource#templates must be overridden' ; end
36
+ def code ; error 'DataSource#code must be overridden' ; end
37
+
38
+ # Creating data
39
+
40
+ def create_page(name, template)
41
+ error 'DataSource#create_page must be overridden'
42
+ end
43
+
44
+ def create_layout(name)
45
+ error 'DataSource#create_layout must be overridden'
46
+ end
47
+
48
+ def create_template(name)
49
+ error 'DataSource#create_template must be overridden'
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,89 @@
1
+ # Get requirements
2
+ begin ; require 'rubygems' ; rescue LoadError ; end
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ # Logging (level can be :off, :high, :low)
7
+ $log_level = :high
8
+ def log(log_level, s, io=$stdout)
9
+ io.puts s if ($log_level == :low or $log_level == log_level) and $log_level != :off
10
+ end
11
+
12
+ # Convenience function for printing errors
13
+ def error(s, pre='ERROR')
14
+ log(:high, pre + ': ' + s, $stderr)
15
+ exit(1)
16
+ end
17
+
18
+ # Convenience function for requiring libraries
19
+ def nanoc_require(x, message="'#{x}' is required to compile this site.")
20
+ require x
21
+ rescue LoadError
22
+ error(message)
23
+ end
24
+
25
+ # Rendering sub-layouts
26
+ def render(name, other_assigns={})
27
+ layout = @site.layouts.find { |l| l[:name] == name }
28
+ layout_processor_class = Nanoc::PluginManager.layout_processor_for_extension(layout[:extension])
29
+ layout_processor = layout_processor_class.new(@page, @pages, @site.config, @site, other_assigns)
30
+ layout_processor.run(layout[:content])
31
+ end
32
+
33
+ # Convenience function for cd'ing in and out of a directory
34
+ def in_dir(path)
35
+ FileUtils.cd(File.join(path))
36
+ yield
37
+ ensure
38
+ FileUtils.cd(File.join(path.map { |n| '..' }))
39
+ end
40
+
41
+ class FileManager
42
+
43
+ ACTION_COLORS = {
44
+ :create => "\e[1m" + "\e[32m", # bold + green
45
+ :update => "\e[1m" + "\e[33m", # bold + yellow
46
+ :identical => "\e[1m" # bold
47
+ }
48
+
49
+ def self.file_log(log_level, action, path)
50
+ log(log_level, '%s%12s%s %s' % [ACTION_COLORS[action.to_sym], action, "\e[0m", path])
51
+ end
52
+
53
+ def self.create_dir(name)
54
+ # Check whether directory exists
55
+ return if File.exist?(name)
56
+
57
+ # Create dir
58
+ FileUtils.mkdir_p(name)
59
+ log(:create, name)
60
+ end
61
+
62
+ def self.create_file(path)
63
+ # Create parent directory if necessary
64
+ if path =~ /\//
65
+ parent_path = path.sub(/\/[^\/]+$/, '')
66
+ FileManager.create_dir(parent_path)
67
+ end
68
+
69
+ # Get contents
70
+ content_old = File.exist?(path) ? File.read(path) : nil
71
+ content_new = yield
72
+ content_new = content_new.force_encoding(content_old.encoding) if content_old and String.method_defined?(:force_encoding)
73
+ modified = (content_old != content_new)
74
+
75
+ # Log
76
+ if File.exist?(path)
77
+ file_log(*(modified ? [ :high, :update, path ] : [ :low, :identical, path ]))
78
+ else
79
+ file_log(:high, :create, path)
80
+ end
81
+
82
+ # Write
83
+ open(path, 'w') { |io| io.write(content_new) }
84
+
85
+ # Report back
86
+ modified
87
+ end
88
+
89
+ end
@@ -0,0 +1,16 @@
1
+ module Nanoc
2
+ class Filter < Plugin
3
+
4
+ def initialize(page, pages, config, site)
5
+ @page = page
6
+ @pages = pages
7
+ @config = config
8
+ @site = site
9
+ end
10
+
11
+ def run(content)
12
+ error 'Filter#run must be overridden'
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ module Nanoc
2
+ class LayoutProcessor < Plugin
3
+
4
+ def initialize(page, pages, config, site, other_assigns={})
5
+ @page = page
6
+ @pages = pages
7
+ @config = config
8
+ @site = site
9
+ @other_assigns = other_assigns
10
+ end
11
+
12
+ def run(layout)
13
+ error 'LayoutProcessor#run must be overridden'
14
+ end
15
+
16
+ # Extensions
17
+
18
+ class << self
19
+ attr_accessor :_extensions
20
+ end
21
+
22
+ def self.extensions(*extensions)
23
+ self._extensions = [] unless instance_variable_defined?(:@_extensions)
24
+ extensions.empty? ? self._extensions || [] : self._extensions = (self._extensions || []) + extensions
25
+ end
26
+
27
+ def self.extension(extension=nil)
28
+ self._extensions = [] unless instance_variable_defined?(:@_extensions)
29
+ extension.nil? ? self.extensions.first : self.extensions(extension)
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,155 @@
1
+ module Nanoc
2
+ class Page
3
+
4
+ PAGE_DEFAULTS = {
5
+ :custom_path => nil,
6
+ :extension => 'html',
7
+ :filename => 'index',
8
+ :filters_pre => [],
9
+ :filters_post => [],
10
+ :haml_options => {},
11
+ :is_draft => false,
12
+ :layout => 'default',
13
+ :path => nil,
14
+ :skip_output => false
15
+ }
16
+
17
+ attr_reader :attributes
18
+ attr_accessor :parent, :children
19
+
20
+ def initialize(hash, site)
21
+ @site = site
22
+ @compiler = site.compiler
23
+
24
+ @attributes = hash
25
+ @content = { :pre => attribute_named(:uncompiled_content), :post => nil }
26
+
27
+ @parent = nil
28
+ @children = []
29
+
30
+ @filtered_pre = false
31
+ @layouted = false
32
+ @filtered_post = false
33
+ @written = false
34
+ end
35
+
36
+ # Proxy support
37
+
38
+ def to_proxy
39
+ @proxy ||= PageProxy.new(self)
40
+ end
41
+
42
+ # Accessors, kind of
43
+
44
+ def modified? ; @modified ; end
45
+
46
+ def attribute_named(name)
47
+ return @attributes[name] if @attributes.has_key?(name)
48
+ return @site.page_defaults[name] if @site.page_defaults.has_key?(name)
49
+ return PAGE_DEFAULTS[name]
50
+ end
51
+
52
+ def content
53
+ compile(false) unless @filtered_pre
54
+ @content[:pre]
55
+ end
56
+
57
+ def layouted_content
58
+ compile(true)
59
+ @content[:post]
60
+ end
61
+
62
+ def skip_output? ; attribute_named(:skip_output) ; end
63
+ def path ; attribute_named(:path) ; end
64
+
65
+ def path_on_filesystem
66
+ if attribute_named(:custom_path).nil?
67
+ @site.config[:output_dir] + attribute_named(:path) +
68
+ attribute_named(:filename) + '.' + attribute_named(:extension)
69
+ else
70
+ @site.config[:output_dir] + attribute_named(:custom_path)
71
+ end
72
+ end
73
+
74
+ # Compiling
75
+
76
+ def compile(full=true)
77
+ @modified = false
78
+
79
+ # Check for recursive call
80
+ if @compiler.stack.include?(self)
81
+ log(:high, "\n" + 'ERROR: Recursive call to page content. Page filter stack:', $stderr)
82
+ log(:high, " - #{self.attribute_named(:path)}", $stderr)
83
+ @compiler.stack.each_with_index do |page, i|
84
+ log(:high, " - #{page.attribute_named(:path)}", $stderr)
85
+ end
86
+ exit(1)
87
+ end
88
+
89
+ @compiler.stack.push(self)
90
+
91
+ # Filter pre
92
+ unless @filtered_pre
93
+ filter(:pre)
94
+ @filtered_pre = true
95
+ end
96
+
97
+ # Layout
98
+ if !@layouted and full
99
+ layout
100
+ @layouted = true
101
+ end
102
+
103
+ # Filter post
104
+ if !@filtered_post and full
105
+ filter(:post)
106
+ @filtered_post = true
107
+ end
108
+
109
+ # Write
110
+ if !@written and full
111
+ @modified = FileManager.create_file(self.path_on_filesystem) { @content[:post] } unless skip_output?
112
+ @written = true
113
+ end
114
+
115
+ @compiler.stack.pop
116
+ end
117
+
118
+ def filter(stage)
119
+ # Get filters
120
+ error 'The `filters` property is no longer supported; please use `filters_pre` instead.' unless attribute_named(:filters).nil?
121
+ filters = attribute_named(stage == :pre ? :filters_pre : :filters_post)
122
+
123
+ filters.each do |filter_name|
124
+ # Create filter
125
+ filter_class = PluginManager.filter_named(filter_name)
126
+ error "Unknown filter: '#{filter_name}'" if filter_class.nil?
127
+ filter = filter_class.new(self.to_proxy, @site.pages.map { |p| p.to_proxy }, @site.config, @site)
128
+
129
+ # Run filter
130
+ @content[stage] = filter.run(@content[stage])
131
+ end
132
+ end
133
+
134
+ def layout
135
+ # Don't layout if not necessary
136
+ if attribute_named(:layout).nil?
137
+ @content[:post] = @content[:pre]
138
+ return
139
+ end
140
+
141
+ # Find layout
142
+ layout = @site.layouts.find { |l| l[:name] == attribute_named(:layout) }
143
+ error 'Unknown layout: ' + attribute_named(:layout) if layout.nil?
144
+
145
+ # Find layout processor
146
+ layout_processor_class = PluginManager.layout_processor_for_extension(layout[:extension])
147
+ error "Unknown layout processor: '#{layout[:extension]}'" if layout_processor_class.nil?
148
+ layout_processor = layout_processor_class.new(self.to_proxy, @site.pages.map { |p| p.to_proxy }, @site.config, @site)
149
+
150
+ # Layout
151
+ @content[:post] = layout_processor.run(layout[:content])
152
+ end
153
+
154
+ end
155
+ end
@@ -0,0 +1,31 @@
1
+ module Nanoc
2
+ class PageProxy
3
+
4
+ def initialize(page)
5
+ @page = page
6
+ end
7
+
8
+ def [](key)
9
+ real_key = key.to_s.sub(/\?$/, '').to_sym
10
+
11
+ if real_key == :content
12
+ @page.content
13
+ elsif real_key == :parent
14
+ @page.parent.nil? ? nil : @page.parent.to_proxy
15
+ elsif real_key == :children
16
+ @page.children.map { |page| page.to_proxy }
17
+ else
18
+ @page.attribute_named(real_key)
19
+ end
20
+ end
21
+
22
+ def []=(key, value)
23
+ @page.attributes[key.to_sym] = value
24
+ end
25
+
26
+ def method_missing(method, *args)
27
+ self[method]
28
+ end
29
+
30
+ end
31
+ end