nanoc 1.1.3 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ == 1.2
2
+
3
+ * Sites now have an 'assets' directory, whose contents are copied to the 'output' directory when compiling
4
+ * Added support for non-eRuby layouts (Markaby, Haml, Liquid, ...)
5
+ * Added many more filters
6
+ * Improved error reporting
7
+ * Accessing page attributes using instance variables, and not through @page, is no longer possible
8
+ * Page attributes can now be accessed using dot notation, i.e. @page.title as well as @page[:title]
9
+
10
+ == 1.1.3
11
+
12
+ * Fixed bug which would cause pages without layouts to be outputed incorrectly
13
+
14
+ == 1.1.2
15
+
16
+ * Backup files (files ending with a “~”) are now ignored
17
+ * Fixed bug which would cause subpages not to be generated correctly
18
+
19
+ == 1.1
20
+
21
+ * Added support for nested layouts
22
+ * Added coloured logging
23
+ * @page now hold the page that is currently being processed
24
+ * Index files are now called “content” files and are now named after the directory they are in [Colin Barrett]
25
+ * It is now possible to access @page in the page’s content file
26
+
27
+ == 1.0.1
28
+
29
+ * Fixed a bug which would cause a “no such template” error to be displayed when the template existed but compiling it would raise an exception
30
+ * Fixed bug which would cause pages not to be sorted by order before compiling
31
+
32
+ == 1.0
33
+
34
+ * Initial release
data/Rakefile CHANGED
@@ -17,7 +17,8 @@ EMAIL = 'denis.defreyne@stoneship.org'
17
17
 
18
18
  #####
19
19
 
20
- CLEAN.include [ '*.gem', 'pkg', 'tmp', 'test/fixtures/*/output/*' ]
20
+ CLEAN.include [ 'tmp', 'test/fixtures/*/output/*', 'test/fixtures/*/tmp' ]
21
+ CLOBBER.include [ 'pkg' ]
21
22
 
22
23
  spec = Gem::Specification.new do |s|
23
24
  s.name = NAME
@@ -32,7 +33,7 @@ spec = Gem::Specification.new do |s|
32
33
  s.required_ruby_version = '>= 1.8.2'
33
34
 
34
35
  s.has_rdoc = false
35
- s.files = %w( README LICENSE Rakefile ) + Dir['{bin,lib}/**/*']
36
+ s.files = %w( README LICENSE ChangeLog Rakefile ) + Dir['{bin,lib}/**/*']
36
37
  s.executables = [ 'nanoc' ]
37
38
  s.require_path = 'lib'
38
39
  s.bindir = 'bin'
data/bin/nanoc CHANGED
@@ -1,29 +1,32 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Available options:
4
- #
5
- # -h, --help:
6
- # prints the help
7
- #
8
- # -t <template>, --template <template>
9
- # Uses the specified template when creating a page
10
- #
11
- # -v, --version:
12
- # prints the version information
3
+ require File.dirname(__FILE__) + '/../lib/nanoc.rb'
13
4
 
14
- require 'rubygems' rescue nil
15
5
  require 'getoptlong'
16
6
 
17
- require File.dirname(__FILE__) + '/../lib/nanoc.rb'
7
+ try_require 'rubygems'
18
8
 
19
9
  # Define some texts
20
10
  version_text = "nanoc #{Nanoc::VERSION} (c) 2007 Denis Defreyne."
11
+ usage_text = "Usage: nanoc [options] [command] [parameters]"
21
12
  help_text = <<EOT
22
- usage: nanoc [-hv]
13
+ Usage: nanoc [-chv]
23
14
  nanoc create_site <name>
24
15
  nanoc create_page <name> [-t template]
25
16
  nanoc create_template <name>
26
17
  nanoc compile
18
+
19
+ Options:
20
+ -h, --help Show this help message and quit.
21
+ -v, --version Show the nanoc version number and quit.
22
+ -t, --template Template that should be used when creating a page.
23
+ (can only be used with create_page)
24
+
25
+ Description:
26
+ The 'nanoc' command is used for creating nanoc-powered sites, as well as
27
+ managing them: creating pages and templates and compiling the site.
28
+
29
+ Read more about nanoc on the site: <http://stoneship.org/software/nanoc/>
27
30
  EOT
28
31
 
29
32
  # Parse options
@@ -47,13 +50,13 @@ begin
47
50
  end
48
51
  end
49
52
  rescue GetoptLong::InvalidOption
50
- $stderr.puts help_text
53
+ $stderr.puts usage_text
51
54
  exit
52
55
  end
53
56
 
54
57
  # Make sure we have at least one argument
55
58
  if ARGV.size == 0
56
- $stderr.puts help_text
59
+ $stderr.puts usage_text
57
60
  exit
58
61
  end
59
62
 
@@ -2,51 +2,157 @@ module Nanoc
2
2
  class Compiler
3
3
 
4
4
  DEFAULT_CONFIG = { :output_dir => 'output' }
5
- DEFAULT_PAGE = { :filters => [], :extension => 'html', :order => 0, :layout => "default" }
5
+
6
+ FILE_TYPES = {
7
+ '.erb' => :eruby,
8
+ '.rhtml' => :eruby,
9
+ '.haml' => :haml,
10
+ '.mab' => :markaby,
11
+ '.liquid' => :liquid
12
+ }
13
+
14
+ PAGE_DEFAULTS = {
15
+ :custom_path => nil,
16
+ :extension => 'html',
17
+ :filters => [],
18
+ :is_draft => false,
19
+ :layout => 'default',
20
+ :order => 0
21
+ }
6
22
 
7
23
  def initialize
8
24
  Nanoc.ensure_in_site
9
25
 
10
26
  @config = DEFAULT_CONFIG.merge(YAML.load_file_and_clean('config.yaml'))
11
- @global_page = DEFAULT_PAGE.merge(YAML.load_file_and_clean('meta.yaml'))
27
+ @global_page = PAGE_DEFAULTS.merge(YAML.load_file_and_clean('meta.yaml'))
12
28
  end
13
29
 
14
30
  def run
31
+ # Require all Ruby source files in lib/
15
32
  Dir['lib/*.rb'].each { |f| require f }
16
33
 
34
+ # Copy assets to output directory
35
+ copy_assets
36
+
37
+ # Compile all pages
17
38
  pages = compile_pages(uncompiled_pages)
39
+
40
+ # Put pages in their layouts
18
41
  pages.each do |page|
19
- content = (page[:layout].nil? ? "<%= @page[:content] %>" : File.read("layouts/#{page[:layout]}.erb")).eruby(page.merge({ :page => page, :pages => pages })) # fallback for nanoc 1.0
42
+ # Prepare layout content
43
+ content = nil
44
+ context = { :page => page.merge(:_content_filename => nil), :pages => pages }
45
+ layout = layout_for_page(page)
46
+ begin
47
+ case layout[:type]
48
+ when :eruby
49
+ content = layout[:content].eruby(context)
50
+ when :haml
51
+ content = layout[:content].haml(context)
52
+ when :markaby
53
+ content = layout[:content].markaby(context)
54
+ when :liquid
55
+ content = layout[:content].liquid(context)
56
+ end
57
+ rescue => exception
58
+ $stderr.puts "Exception occured while layouting page" +
59
+ "'#{page[:_content_filename]}' in layout '#{page[:layout]}':" unless $quiet
60
+ $stderr.puts exception unless $quiet
61
+ $stderr.puts exception.backtrace.join("\n") unless $quiet
62
+ exit
63
+ end
64
+
65
+ # Write page with layout
20
66
  FileManager.create_file(path_for_page(page)) { content }
21
67
  end
22
68
  end
23
69
 
24
70
  private
25
71
 
72
+ # Copies the contents of the assets directory into the output directory
73
+ def copy_assets
74
+ Dir['assets/*'].each { |f| FileUtils.remove_entry_secure(f.sub('assets/', 'output/'), true) }
75
+ FileUtils.cp_r(Dir['assets/*'], 'output') if File.directory?('assets') and !Dir['assets/*'].empty?
76
+ end
77
+
78
+ # Returns a list of uncompiled pages
26
79
  def uncompiled_pages
27
- Dir['content/**/meta.yaml'].collect do |filename|
80
+ # Read all meta files
81
+ pages = Dir['content/**/meta.yaml'].collect do |filename|
82
+ # Read the meta file
28
83
  page = @global_page.merge(YAML.load_file_and_clean(filename))
84
+
85
+ # Fix the path
29
86
  page[:path] = filename.sub(/^content/, '').sub('meta.yaml', '')
30
87
 
88
+ # Get the content filename
31
89
  content_filenames = Dir[filename.sub('meta.yaml', File.basename(File.dirname(filename)) + '.*')].reject { |f| f =~ /~$/ }
32
90
  content_filenames += Dir["#{File.dirname(filename)}/index.*"] # fallback for nanoc 1.0
33
91
  content_filenames.ensure_single('content files', File.dirname(filename))
34
92
  page[:_content_filename] = content_filenames[0]
35
93
 
36
94
  page
37
- end.compact.reject { |page| page[:is_draft] }.sort do |x,y|
38
- x[:order].to_i == y[:order].to_i ? x[:path] <=> y[:path] : x[:order].to_i <=> y[:order].to_i
95
+ end
96
+
97
+ # Ignore drafts
98
+ pages.reject! { |page| page[:is_draft] }
99
+
100
+ # Sort pages by order and by path
101
+ pages.sort! do |x,y|
102
+ if x[:order].to_i == y[:order].to_i
103
+ x[:path] <=> y[:path]
104
+ else
105
+ x[:order].to_i <=> y[:order].to_i
106
+ end
107
+ end
108
+
109
+ pages
110
+ end
111
+
112
+ # Returns the layout for the given page
113
+ def layout_for_page(a_page)
114
+ if a_page[:layout].nil?
115
+ { :type => :eruby, :content => "<%= @page[:content] %>" }
116
+ else
117
+ filenames = Dir["layouts/#{a_page[:layout]}.*"]
118
+ filenames.ensure_single('layout files', a_page[:layout])
119
+ filename = filenames[0]
120
+
121
+ { :type => FILE_TYPES[File.extname(filename)], :content => File.read(filename) }
39
122
  end
40
123
  end
41
124
 
125
+ # Returns the path for the given page
42
126
  def path_for_page(a_page)
43
- @config[:output_dir] + ( a_page[:custom_path].nil? ? a_page[:path] + 'index.' + a_page[:extension] : a_page[:custom_path] )
127
+ if a_page[:custom_path].nil?
128
+ @config[:output_dir] + a_page[:path] + 'index.' + a_page[:extension]
129
+ else
130
+ @config[:output_dir] + a_page[:custom_path]
131
+ end
44
132
  end
45
133
 
134
+ # Compiles the given pages and returns the compiled pages
46
135
  def compile_pages(a_pages)
47
136
  a_pages.inject([]) do |pages, page|
48
- content = File.read(page[:_content_filename]).filter(page[:filters], :eruby_context => { :page => page, :pages => pages })
49
- pages + [ page.merge( { :content => content, :_content_filename => nil }) ]
137
+ # Read page
138
+ content = File.read(page[:_content_filename])
139
+
140
+ # Filter page
141
+ begin
142
+ content = content.filter(page[:filters], :assigns => { :page => Page.new(page), :pages => pages })
143
+ rescue Exception => exception
144
+ $stderr.puts "Exception occured while compiling page" +
145
+ "'#{page[:_content_filename]}':" unless $quiet
146
+ $stderr.puts exception unless $quiet
147
+ $stderr.puts exception.backtrace.join("\n") unless $quiet
148
+ exit
149
+ end
150
+
151
+ # Create compiled page
152
+ compiled_page = page.merge( :content => content )
153
+
154
+ # Remember page
155
+ pages + [ Page.new(compiled_page) ]
50
156
  end
51
157
  end
52
158
 
@@ -4,6 +4,7 @@ module Nanoc
4
4
 
5
5
  def self.create_site(a_sitename)
6
6
  FileManager.create_dir a_sitename do
7
+ FileManager.create_dir 'assets'
7
8
  FileManager.create_dir 'output'
8
9
 
9
10
  FileManager.create_file 'config.yaml' do
@@ -1,18 +1,24 @@
1
1
  def try_require(s) ; begin ; require s ; rescue LoadError ; end ; end
2
2
 
3
3
  try_require 'rubygems'
4
- try_require 'bluecloth'
5
- try_require 'rubypants'
6
4
 
7
5
  require 'erubis'
8
6
  require 'fileutils'
9
7
  require 'yaml'
10
8
 
9
+ try_require 'bluecloth'
10
+ try_require 'rubypants'
11
+ try_require 'markaby'
12
+ try_require 'liquid'
13
+ try_require 'haml'
14
+ try_require 'rdoc/markup/simple_markup'
15
+ try_require 'rdoc/markup/simple_markup/to_html'
16
+
11
17
  class Array
12
18
  # Ensures that the array contains only one element
13
19
  def ensure_single(a_noun, a_context)
14
20
  if self.size != 1
15
- $stderr.puts "ERROR: expected 1 #{a_noun}, found #{self.size} (#{a_context})" unless $quiet == true
21
+ $stderr.puts "ERROR: expected 1 #{a_noun}, found #{self.size} (#{a_context})" unless $quiet
16
22
  exit
17
23
  end
18
24
  end
@@ -46,6 +52,13 @@ class Hash
46
52
  hash
47
53
  end
48
54
  end
55
+
56
+ def stringify_keys
57
+ inject({}) do |hash, (key, value)|
58
+ hash[key.to_s] = value
59
+ hash
60
+ end
61
+ end
49
62
  end
50
63
 
51
64
  class String
@@ -54,47 +67,134 @@ class String
54
67
  def filter(a_filters, a_params={})
55
68
  a_filters.inject(self) do |result, filter|
56
69
  case filter
70
+ when 'eruby'
71
+ result.replace(result.eruby(a_params[:assigns]))
72
+ when 'haml'
73
+ result.replace(result.haml(a_params[:assigns]))
74
+ when 'liquid'
75
+ result.replace(result.liquid(a_params[:assigns]))
76
+ when 'markaby'
77
+ result.replace(result.markaby(a_params[:assigns]))
57
78
  when 'markdown'
58
79
  result.replace(result.markdown)
80
+ when 'rdoc'
81
+ result.replace(result.rdoc)
59
82
  when 'smartypants'
60
83
  result.replace(result.smartypants)
61
- when 'eruby'
62
- result.replace(result.eruby(a_params[:eruby_context]))
63
84
  end
64
85
  end
65
86
  end
66
87
 
67
- # Converts the string to HTML using Markdown.
88
+ # Converts the string using eRuby.
89
+ def eruby(a_assigns={})
90
+ Erubis::Eruby.new(self).evaluate(a_assigns)
91
+ end
92
+
93
+ # Converts the string using Haml
94
+ def haml(a_assigns={})
95
+ Haml::Engine.new(self, :locals => a_assigns).to_html
96
+ rescue NameError
97
+ $stderr.puts 'ERROR: String#haml failed (Haml not installed?)' unless $quiet
98
+ exit
99
+ end
100
+
101
+ # Converts the string using Liquid
102
+ def liquid(a_assigns={})
103
+ Liquid::Template.parse(self).render(a_assigns.stringify_keys)
104
+ rescue NameError
105
+ $stderr.puts 'ERROR: String#liquid failed (Liquid not installed?)' unless $quiet
106
+ exit
107
+ end
108
+
109
+ # Converts the string using Markaby
110
+ # TODO perhaps add support for helpers
111
+ def markaby(a_assigns={})
112
+ Markaby::Builder.new(a_assigns).instance_eval(self).to_s
113
+ rescue NameError
114
+ $stderr.puts 'ERROR: String#markaby failed (Markaby not installed?)' unless $quiet
115
+ exit
116
+ end
117
+
118
+ # Converts the string to HTML using BlueCloth/Markdown.
68
119
  def markdown
69
- BlueCloth::new(self).to_html
120
+ BlueCloth.new(self).to_html
70
121
  rescue NameError
71
- $stderr.puts 'ERROR: String#markdown failed: BlueCloth not installed' unless $quiet == true
122
+ $stderr.puts 'ERROR: String#markdown failed: BlueCloth not installed' unless $quiet
72
123
  exit
73
124
  end
74
125
 
75
- # Styles the string as HTML by converting quotes, dashes, ... using RubyPants
126
+ # Converts the string using RDoc
127
+ def rdoc
128
+ SM::SimpleMarkup.new.convert(self, SM::ToHtml.new)
129
+ end
130
+
131
+ # Converts the string using RedCloth/Textile
132
+ def textile
133
+ RedCloth.new(self).to_html
134
+ rescue NameError
135
+ $stderr.puts 'ERROR: String#textile failed (RedCloth not installed?)' unless $quiet
136
+ exit
137
+ end
138
+
139
+ # Converts the string using RubyPants/SmartyPants
76
140
  def smartypants
77
- RubyPants::new(self).to_html
141
+ RubyPants.new(self).to_html
78
142
  rescue NameError
79
- $stderr.puts 'ERROR: String#smartypants failed: RubyPants not installed' unless $quiet == true
143
+ $stderr.puts 'ERROR: String#smartypants failed (RubyPants not installed?)' unless $quiet
80
144
  exit
81
145
  end
146
+ end
147
+
148
+ class FileLogger
149
+ COLORS = {
150
+ :reset => "\e[0m",
151
+
152
+ :bold => "\e[1m",
153
+
154
+ :black => "\e[30m",
155
+ :red => "\e[31m",
156
+ :green => "\e[32m",
157
+ :yellow => "\e[33m",
158
+ :blue => "\e[34m",
159
+ :magenta => "\e[35m",
160
+ :cyan => "\e[36m",
161
+ :white => "\e[37m"
162
+ }
163
+
164
+ ACTION_COLORS = {
165
+ :create => COLORS[:bold] + COLORS[:green],
166
+ :update => COLORS[:bold] + COLORS[:yellow],
167
+ :move => COLORS[:bold] + COLORS[:blue],
168
+ :identical => COLORS[:bold]
169
+ }
170
+
171
+ attr_reader :out
172
+
173
+ def initialize(a_out = $stdout)
174
+ @out = a_out
175
+ end
82
176
 
83
- # Converts the string using eRuby.
84
- def eruby(a_context={})
85
- Erubis::Eruby.new(self).evaluate(a_context)
177
+ def log(a_action, a_path)
178
+ @out.puts('%s%12s%s %s' % [ACTION_COLORS[a_action.to_sym], a_action, COLORS[:reset], a_path]) unless $quiet
179
+ end
180
+
181
+ private
182
+
183
+ def method_missing(a_method, *a_args)
184
+ log(a_method.to_s, a_args.first)
86
185
  end
87
186
  end
88
187
 
89
188
  class FileManager
90
189
  @@stack = []
190
+ @@logger = FileLogger.new
91
191
 
92
192
  def self.create_dir(a_name)
93
193
  @@stack.push(a_name)
94
194
  path = File.join(@@stack)
95
195
  unless File.directory?(path)
96
196
  FileUtils.mkdir_p(path)
97
- log('create', path)
197
+ @@logger.create(path)
98
198
  end
99
199
  yield if block_given?
100
200
  @@stack.pop
@@ -104,35 +204,19 @@ class FileManager
104
204
  path = File.join(@@stack + [ a_name ])
105
205
  FileManager.create_dir(path.sub(/\/[^\/]+$/, '')) if @@stack.empty?
106
206
  content = block_given? ? yield : nil
107
- File.exist?(path) ? ( block_given? and File.read(path) == content ? log('identical', path) : log('update', path) ) : log('create', path)
207
+ if File.exist?(path)
208
+ if block_given? and File.read(path) == content
209
+ @@logger.identical(path)
210
+ else
211
+ @@logger.update(path)
212
+ end
213
+ else
214
+ @@logger.create(path)
215
+ end
108
216
  open(path, 'w') { |io| io.write(content) unless content.nil? }
109
217
  end
110
218
  end
111
219
 
112
- COLORS = {
113
- :reset => "\e[0m",
114
-
115
- :bold => "\e[1m",
116
-
117
- :black => "\e[30m",
118
- :red => "\e[31m",
119
- :green => "\e[32m",
120
- :yellow => "\e[33m",
121
- :blue => "\e[34m",
122
- :magenta => "\e[35m",
123
- :cyan => "\e[36m",
124
- :white => "\e[37m"
125
- }
126
- ACTION_COLORS = {
127
- :create => COLORS[:bold] + COLORS[:green],
128
- :update => COLORS[:bold] + COLORS[:yellow],
129
- :identical => COLORS[:bold]
130
- }
131
-
132
- def log(a_action, a_path)
133
- puts format('%s%12s%s %s', ACTION_COLORS[a_action.to_sym], a_action, COLORS[:reset], a_path) unless $quiet == true
134
- end
135
-
136
220
  def render(a_name, a_context={})
137
- File.read('layouts/' + a_name + '.erb').eruby(a_context.merge({ :page => @page, :pages => @pages }))
221
+ File.read('layouts/' + a_name.to_s + '.erb').eruby(a_context.merge({ :page => @page, :pages => @pages }))
138
222
  end
@@ -1,9 +1,10 @@
1
1
  module Nanoc
2
- VERSION = '1.1.3'
2
+ VERSION = '1.2'
3
3
 
4
4
  def self.ensure_in_site
5
5
  unless in_site?
6
- $stderr.puts 'ERROR: The current working directory does not seem to be a valid/complete nanoc site directory; aborting.' unless $quiet
6
+ $stderr.puts 'ERROR: The current working directory does not seem to ' +
7
+ 'be a valid/complete nanoc site directory; aborting.' unless $quiet
7
8
  exit
8
9
  end
9
10
  end
@@ -17,7 +18,6 @@ module Nanoc
17
18
  return false unless File.directory?('output')
18
19
  return false unless File.directory?('tasks')
19
20
  return false unless File.directory?('templates')
20
-
21
21
  return false unless File.exist?('config.yaml')
22
22
  return false unless File.exist?('meta.yaml')
23
23
  return false unless File.exist?('Rakefile')
@@ -26,6 +26,7 @@ module Nanoc
26
26
  end
27
27
  end
28
28
 
29
+ require File.dirname(__FILE__) + '/page.rb'
29
30
  require File.dirname(__FILE__) + '/creator.rb'
30
31
  require File.dirname(__FILE__) + '/compiler.rb'
31
32
  require File.dirname(__FILE__) + '/enhancements.rb'
@@ -0,0 +1,30 @@
1
+ module Nanoc
2
+ class Page
3
+
4
+ def initialize(a_hash={})
5
+ @attributes = a_hash
6
+ end
7
+
8
+ def [](a_key)
9
+ @attributes[a_key]
10
+ end
11
+
12
+ def []=(a_key, a_value)
13
+ @attributes[a_key] = a_value
14
+ end
15
+
16
+ def merge(a_other)
17
+ @attributes.merge(a_other.to_hash)
18
+ self
19
+ end
20
+
21
+ def to_hash
22
+ @attributes
23
+ end
24
+
25
+ def method_missing(a_method, *a_args)
26
+ @attributes[a_method.to_sym]
27
+ end
28
+
29
+ end
30
+ end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: nanoc
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.1.3
7
- date: 2007-05-18 00:00:00 +02:00
6
+ version: "1.2"
7
+ date: 2007-06-05 00:00:00 +02:00
8
8
  summary: a CMS that doesn't run on your server
9
9
  require_paths:
10
10
  - lib
@@ -31,12 +31,14 @@ authors: []
31
31
  files:
32
32
  - README
33
33
  - LICENSE
34
+ - ChangeLog
34
35
  - Rakefile
35
36
  - bin/nanoc
36
37
  - lib/compiler.rb
37
38
  - lib/creator.rb
38
39
  - lib/enhancements.rb
39
40
  - lib/nanoc.rb
41
+ - lib/page.rb
40
42
  test_files: []
41
43
 
42
44
  rdoc_options: []