nanoc 1.6.2 → 2.0

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