nanoc 2.0.4 → 2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/ChangeLog +31 -1
  2. data/LICENSE +1 -1
  3. data/README +63 -3
  4. data/Rakefile +59 -12
  5. data/bin/nanoc +7 -199
  6. data/lib/nanoc.rb +83 -12
  7. data/lib/nanoc/base/asset.rb +113 -0
  8. data/lib/nanoc/base/asset_defaults.rb +21 -0
  9. data/lib/nanoc/base/asset_rep.rb +277 -0
  10. data/lib/nanoc/base/binary_filter.rb +44 -0
  11. data/lib/nanoc/base/code.rb +41 -0
  12. data/lib/nanoc/base/compiler.rb +46 -34
  13. data/lib/nanoc/base/core_ext/hash.rb +51 -7
  14. data/lib/nanoc/base/core_ext/string.rb +8 -0
  15. data/lib/nanoc/base/data_source.rb +253 -20
  16. data/lib/nanoc/base/defaults.rb +30 -0
  17. data/lib/nanoc/base/enhancements.rb +9 -84
  18. data/lib/nanoc/base/filter.rb +109 -6
  19. data/lib/nanoc/base/layout.rb +91 -0
  20. data/lib/nanoc/base/notification_center.rb +66 -0
  21. data/lib/nanoc/base/page.rb +94 -126
  22. data/lib/nanoc/base/page_defaults.rb +20 -0
  23. data/lib/nanoc/base/page_rep.rb +318 -0
  24. data/lib/nanoc/base/plugin.rb +57 -9
  25. data/lib/nanoc/base/proxies/asset_proxy.rb +29 -0
  26. data/lib/nanoc/base/proxies/asset_rep_proxy.rb +26 -0
  27. data/lib/nanoc/base/proxies/layout_proxy.rb +25 -0
  28. data/lib/nanoc/base/proxies/page_proxy.rb +35 -0
  29. data/lib/nanoc/base/proxies/page_rep_proxy.rb +28 -0
  30. data/lib/nanoc/base/proxy.rb +37 -0
  31. data/lib/nanoc/base/router.rb +72 -0
  32. data/lib/nanoc/base/site.rb +219 -88
  33. data/lib/nanoc/base/template.rb +64 -0
  34. data/lib/nanoc/binary_filters/image_science_thumbnail.rb +28 -0
  35. data/lib/nanoc/cli.rb +1 -0
  36. data/lib/nanoc/cli/base.rb +219 -0
  37. data/lib/nanoc/cli/cli.rb +16 -0
  38. data/lib/nanoc/cli/command.rb +105 -0
  39. data/lib/nanoc/cli/commands/autocompile.rb +80 -0
  40. data/lib/nanoc/cli/commands/compile.rb +273 -0
  41. data/lib/nanoc/cli/commands/create_layout.rb +85 -0
  42. data/lib/nanoc/cli/commands/create_page.rb +85 -0
  43. data/lib/nanoc/cli/commands/create_site.rb +327 -0
  44. data/lib/nanoc/cli/commands/create_template.rb +76 -0
  45. data/lib/nanoc/cli/commands/help.rb +69 -0
  46. data/lib/nanoc/cli/commands/info.rb +114 -0
  47. data/lib/nanoc/cli/commands/switch.rb +141 -0
  48. data/lib/nanoc/cli/commands/update.rb +91 -0
  49. data/lib/nanoc/cli/ext.rb +37 -0
  50. data/lib/nanoc/cli/logger.rb +66 -0
  51. data/lib/nanoc/cli/option_parser.rb +168 -0
  52. data/lib/nanoc/data_sources/filesystem.rb +645 -224
  53. data/lib/nanoc/data_sources/filesystem_combined.rb +495 -0
  54. data/lib/nanoc/extra/auto_compiler.rb +265 -0
  55. data/lib/nanoc/extra/context.rb +22 -0
  56. data/lib/nanoc/extra/core_ext/hash.rb +54 -0
  57. data/lib/nanoc/extra/core_ext/time.rb +13 -0
  58. data/lib/nanoc/extra/file_proxy.rb +29 -0
  59. data/lib/nanoc/extra/vcs.rb +48 -0
  60. data/lib/nanoc/extra/vcses/bazaar.rb +21 -0
  61. data/lib/nanoc/extra/vcses/dummy.rb +20 -0
  62. data/lib/nanoc/extra/vcses/git.rb +21 -0
  63. data/lib/nanoc/extra/vcses/mercurial.rb +21 -0
  64. data/lib/nanoc/extra/vcses/subversion.rb +21 -0
  65. data/lib/nanoc/filters/bluecloth.rb +13 -0
  66. data/lib/nanoc/filters/erb.rb +6 -22
  67. data/lib/nanoc/filters/erubis.rb +14 -0
  68. data/lib/nanoc/filters/haml.rb +7 -23
  69. data/lib/nanoc/filters/markaby.rb +5 -5
  70. data/lib/nanoc/filters/maruku.rb +14 -0
  71. data/lib/nanoc/filters/old.rb +19 -0
  72. data/lib/nanoc/filters/rdiscount.rb +13 -0
  73. data/lib/nanoc/filters/rdoc.rb +5 -4
  74. data/lib/nanoc/filters/redcloth.rb +14 -0
  75. data/lib/nanoc/filters/rubypants.rb +14 -0
  76. data/lib/nanoc/filters/sass.rb +13 -0
  77. data/lib/nanoc/helpers/blogging.rb +170 -0
  78. data/lib/nanoc/helpers/capturing.rb +59 -0
  79. data/lib/nanoc/helpers/html_escape.rb +23 -0
  80. data/lib/nanoc/helpers/link_to.rb +69 -0
  81. data/lib/nanoc/helpers/render.rb +47 -0
  82. data/lib/nanoc/helpers/tagging.rb +52 -0
  83. data/lib/nanoc/helpers/xml_sitemap.rb +58 -0
  84. data/lib/nanoc/routers/default.rb +54 -0
  85. data/lib/nanoc/routers/no_dirs.rb +66 -0
  86. data/lib/nanoc/routers/versioned.rb +79 -0
  87. metadata +112 -22
  88. data/lib/nanoc/base/auto_compiler.rb +0 -132
  89. data/lib/nanoc/base/layout_processor.rb +0 -33
  90. data/lib/nanoc/base/page_proxy.rb +0 -31
  91. data/lib/nanoc/base/plugin_manager.rb +0 -33
  92. data/lib/nanoc/data_sources/database.rb +0 -259
  93. data/lib/nanoc/data_sources/trivial.rb +0 -145
  94. data/lib/nanoc/filters/markdown.rb +0 -13
  95. data/lib/nanoc/filters/smartypants.rb +0 -13
  96. data/lib/nanoc/filters/textile.rb +0 -13
  97. data/lib/nanoc/layout_processors/erb.rb +0 -35
  98. data/lib/nanoc/layout_processors/haml.rb +0 -38
  99. data/lib/nanoc/layout_processors/markaby.rb +0 -16
@@ -0,0 +1,37 @@
1
+ class String
2
+
3
+ # Word-wraps and indents the string.
4
+ #
5
+ # +width+:: The maximal width of each line. This also includes indentation,
6
+ # i.e. the actual maximal width of the text is width-indentation.
7
+ #
8
+ # +indentation+:: The number of spaces to indent each wrapped line.
9
+ def wrap_and_indent(width, indentation)
10
+ # Split into paragraphs
11
+ paragraphs = self.split("\n").map { |p| p.strip }.reject { |p| p == '' }
12
+
13
+ # Wrap and indent each paragraph
14
+ paragraphs.map do |paragraph|
15
+ # Initialize
16
+ lines = []
17
+ line = ''
18
+
19
+ # Split into words
20
+ paragraph.split(/\s/).each do |word|
21
+ # Begin new line if it's too long
22
+ if (line + ' ' + word).length >= width
23
+ lines << line
24
+ line = ''
25
+ end
26
+
27
+ # Add word to line
28
+ line += (line == '' ? '' : ' ' ) + word
29
+ end
30
+ lines << line
31
+
32
+ # Join lines
33
+ lines.map { |l| ' '*indentation + l }.join("\n")
34
+ end.join("\n\n")
35
+ end
36
+
37
+ end
@@ -0,0 +1,66 @@
1
+ require 'singleton'
2
+
3
+ module Nanoc::CLI
4
+
5
+ # Nanoc::CLI::Logger is a singleton class responsible for generating
6
+ # feedback in the terminal.
7
+ class Logger
8
+
9
+ ACTION_COLORS = {
10
+ :create => "\e[1m" + "\e[32m", # bold + green
11
+ :update => "\e[1m" + "\e[33m", # bold + yellow
12
+ :identical => "\e[1m", # bold
13
+ :skip => "\e[1m" # bold
14
+ }
15
+
16
+ include Singleton
17
+
18
+ # The log leve, which can be :high, :low or :off (which will log all
19
+ # messages, only high-priority messages, or no messages at all,
20
+ # respectively).
21
+ attr_accessor :level
22
+
23
+ def initialize # :nodoc:
24
+ @level = :high
25
+ end
26
+
27
+ # Logs a file-related action.
28
+ #
29
+ # +level+:: The importance of this action. Can be :high or :low.
30
+ #
31
+ # +action+:: The kind of file action. Can be :create, :update or
32
+ # :identical.
33
+ #
34
+ # +path+:: The path to the file the action was performed on.
35
+ def file(level, action, path, duration=nil)
36
+ log(
37
+ level,
38
+ '%s%12s%s %s%s' % [
39
+ ACTION_COLORS[action.to_sym],
40
+ action,
41
+ "\e[0m",
42
+ duration.nil? ? '' : "[%2.2fs] " % [ duration ],
43
+ path
44
+ ]
45
+ )
46
+ end
47
+
48
+ # Logs a message.
49
+ #
50
+ # +level+:: The importance of this message. Can be :high or :low.
51
+ #
52
+ # +s+:: The message to be logged.
53
+ #
54
+ # +io+:: The IO instance to which the message will be written. Defaults to
55
+ # standard output.
56
+ def log(level, s, io=$stdout)
57
+ # Don't log when logging is disabled
58
+ return if @level == :off
59
+
60
+ # Log when level permits it
61
+ io.puts(s) if (@level == :low or @level == level)
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,168 @@
1
+ module Nanoc::CLI
2
+
3
+ # Nanoc::CLI::OptionParser is used for parsing commandline options.
4
+ class OptionParser
5
+
6
+ # Error that will be raised when an unknown option is encountered.
7
+ class IllegalOptionError < RuntimeError ; end
8
+
9
+ # Error that will be raised when an option without argument is
10
+ # encountered.
11
+ class OptionRequiresAnArgumentError < RuntimeError ; end
12
+
13
+ # Parses the commandline arguments in +arguments_and_options+, using the
14
+ # commandline option definitions in +definitions+.
15
+ #
16
+ # +arguments_and_options+ is an array of commandline arguments and
17
+ # options. This will usually be +ARGV+.
18
+ #
19
+ # +definitions+ contains a list of hashes defining which options are
20
+ # allowed and how they will be handled. Such a hash has three keys:
21
+ #
22
+ # :short:: The short name of the option, e.g. +a+. Do not include the '-'
23
+ # prefix.
24
+ #
25
+ # :long:: The long name of the option, e.g. +all+. Do not include the '--'
26
+ # prefix.
27
+ #
28
+ # :argument:: Whether this option's argument is required (:required) or
29
+ # forbidden (:forbidden).
30
+ #
31
+ # A sample array of definition hashes could look like this:
32
+ #
33
+ # [
34
+ # { :short => 'a', :long => 'all', :argument => :forbidden },
35
+ # { :short => 'p', :long => 'port', :argument => :required },
36
+ # ]
37
+ #
38
+ # During parsing, two errors can be raised:
39
+ #
40
+ # IllegalOptionError:: An unrecognised option was encountered, i.e. an
41
+ # option that is not present in the list of option
42
+ # definitions.
43
+ #
44
+ # OptionRequiresAnArgumentError:: An option was found that did not have a
45
+ # value, even though this value was
46
+ # required.
47
+ #
48
+ # What will be returned, is a hash with two keys, :arguments and :options.
49
+ # The :arguments value contains a list of arguments, and the :options
50
+ # value contains a hash with key-value pairs for each option. Options
51
+ # without values will have a +nil+ value instead.
52
+ #
53
+ # For example, the following commandline options (which should not be
54
+ # passed as a string, but as an array of strings):
55
+ #
56
+ # foo bar -xyz -a hiss --level 50 --father=ani -n luke squeak
57
+ #
58
+ # with the following option definitions:
59
+ #
60
+ # [
61
+ # { :short => 'x', :long => 'xxx', :argument => :forbidden },
62
+ # { :short => 'y', :long => 'yyy', :argument => :forbidden },
63
+ # { :short => 'z', :long => 'zzz', :argument => :forbidden },
64
+ # { :short => 'a', :long => 'all', :argument => :forbidden },
65
+ # { :short => 'l', :long => 'level', :argument => :required },
66
+ # { :short => 'f', :long => 'father', :argument => :required },
67
+ # { :short => 'n', :long => 'name', :argument => :required }
68
+ # ]
69
+ #
70
+ # will be translated into:
71
+ #
72
+ # {
73
+ # :arguments => [ 'foo', 'bar', 'hiss', 'squeak' ],
74
+ # :options => {
75
+ # :xxx => nil,
76
+ # :yyy => nil,
77
+ # :zzz => nil,
78
+ # :all => nil,
79
+ # :level => '50',
80
+ # :father => 'ani',
81
+ # :name => 'luke'
82
+ # }
83
+ # }
84
+ def self.parse(arguments_and_options, definitions)
85
+ # Don't touch original argument
86
+ unprocessed_arguments_and_options = arguments_and_options.dup
87
+
88
+ # Initialize
89
+ arguments = []
90
+ options = {}
91
+
92
+ # Determines whether we've passed the '--' marker or not
93
+ no_more_options = false
94
+
95
+ loop do
96
+ # Get next item
97
+ e = unprocessed_arguments_and_options.shift
98
+ break if e.nil?
99
+
100
+ # Handle end-of-options marker
101
+ if e == '--'
102
+ no_more_options = true
103
+ # Handle incomplete options
104
+ elsif e =~ /^--./ and !no_more_options
105
+ # Get option key, and option value if included
106
+ if e =~ /^--([^=]+)=(.+)$/
107
+ option_key = $1
108
+ option_value = $2
109
+ else
110
+ option_key = e[2..-1]
111
+ option_value = nil
112
+ end
113
+
114
+ # Find definition
115
+ definition = definitions.find { |d| d[:long] == option_key }
116
+ raise IllegalOptionError.new(option_key) if definition.nil?
117
+
118
+ if definition[:argument] == :required
119
+ # Get option value if necessary
120
+ if option_value.nil?
121
+ option_value = unprocessed_arguments_and_options.shift
122
+ raise OptionRequiresAnArgumentError.new(option_key) if option_value.nil?
123
+ end
124
+
125
+ # Store option
126
+ options[definition[:long].to_sym] = option_value
127
+ else
128
+ # Store option
129
+ options[definition[:long].to_sym] = nil
130
+ end
131
+ # Handle -xyz options
132
+ elsif e =~ /^-./ and !no_more_options
133
+ # Get option keys
134
+ option_keys = e[1..-1].scan(/./)
135
+
136
+ # For each key
137
+ option_keys.each do |option_key|
138
+ # Find definition
139
+ definition = definitions.find { |d| d[:short] == option_key }
140
+ raise IllegalOptionError.new(option_key) if definition.nil?
141
+
142
+ if option_keys.length > 1 and definition[:argument] == :required
143
+ # This is a combined option and it requires an argument, so complain
144
+ raise OptionRequiresAnArgumentError.new(option_key) if option_value.nil?
145
+ elsif definition[:argument] == :required
146
+ # Get option value
147
+ option_value = unprocessed_arguments_and_options.shift
148
+ raise OptionRequiresAnArgumentError.new(option_key) if option_value.nil?
149
+
150
+ # Store option
151
+ options[definition[:long].to_sym] = option_value
152
+ else
153
+ # Store option
154
+ options[definition[:long].to_sym] = nil
155
+ end
156
+ end
157
+ # Handle normal arguments
158
+ else
159
+ arguments << e
160
+ end
161
+ end
162
+
163
+ { :options => options, :arguments => arguments }
164
+ end
165
+
166
+ end
167
+
168
+ end
@@ -1,276 +1,586 @@
1
- module Nanoc::DataSource::Filesystem
1
+ module Nanoc::DataSources
2
+
3
+ # The filesystem data source is the default data source for a new nanoc
4
+ # site. It stores all data as files on the hard disk.
5
+ #
6
+ # None of the methods are documented in this file. See Nanoc::DataSource for
7
+ # documentation on the overridden methods instead.
8
+ #
9
+ # = Pages
10
+ #
11
+ # The filesystem data source stores its pages in nested directories. Each
12
+ # directory represents a single page. The root directory is the 'content'
13
+ # directory.
14
+ #
15
+ # Every directory has a content file and a meta file. The content file
16
+ # contains the actual page content, while the meta file contains the page's
17
+ # metadata, formatted as YAML.
18
+ #
19
+ # Both content files and meta files are named after its parent directory
20
+ # (i.e. page). For example, a page named 'foo' will have a directorynamed
21
+ # 'foo', with e.g. a 'foo.markdown' content file and a 'foo.yaml' meta file.
22
+ #
23
+ # Content file extensions are not used for determining the filter that
24
+ # should be run; the meta file defines the list of filters. The meta file
25
+ # extension must always be 'yaml', though.
26
+ #
27
+ # Content files can also have the 'index' basename. Similarly, meta files
28
+ # can have the 'meta' basename. For example, a parent directory named 'foo'
29
+ # can have an 'index.txt' content file and a 'meta.yaml' meta file. This is
30
+ # to preserve backward compatibility.
31
+ #
32
+ # = Page defaults
33
+ #
34
+ # The page defaults are loaded from a YAML-formatted file named
35
+ # 'page_defaults.yaml' at the top level of the nanoc site directory. For
36
+ # backward compatibility, the file can also be named 'meta.yaml'.
37
+ #
38
+ # = Assets
39
+ #
40
+ # Assets are stored in the 'assets' directory (surprise!). The structure is
41
+ # very similar to the structure of the 'content' directory, so see the Pages
42
+ # section for details on how this directory is structured.
43
+ #
44
+ # = Asset defaults
45
+ #
46
+ # The asset defaults are stored similar to the way page defaults are stored,
47
+ # except that the asset defaults file is named 'asset_defaults.yaml'
48
+ # instead.
49
+ #
50
+ # = Layouts
51
+ #
52
+ # Layouts are stored as directories in the 'layouts' directory. Each layout
53
+ # contains a content file and a meta file. The content file contain the
54
+ # actual layout, and the meta file describes how the page should be handled
55
+ # (contains the filter that should be used).
56
+ #
57
+ # For backward compatibility, a layout can also be a single file in the
58
+ # 'layouts' directory. Such a layout cannot have any metadata; the filter
59
+ # used for this layout is determined from the file extension.
60
+ #
61
+ # = Templates
62
+ #
63
+ # Templates are located in the 'templates' directroy. Every template is a
64
+ # directory consisting of a content file and a meta file, both named after
65
+ # the template. This is very similar to the way pages are stored, except
66
+ # that templates cannot be nested.
67
+ #
68
+ # = Code
69
+ #
70
+ # Code is stored in '.rb' files in the 'lib' directory. Code can reside in
71
+ # sub-directories.
72
+ class Filesystem < Nanoc::DataSource
73
+
74
+ PAGE_DEFAULTS_FILENAME = 'page_defaults.yaml'
75
+ PAGE_DEFAULTS_FILENAME_OLD = 'meta.yaml'
76
+ ASSET_DEFAULTS_FILENAME = 'asset_defaults.yaml'
2
77
 
3
- class FileProxy
78
+ ########## Attributes ##########
79
+
80
+ identifier :filesystem
4
81
 
5
- instance_methods.each { |m| undef_method m unless m =~ /^__/ }
82
+ ########## VCSes ##########
6
83
 
7
- def initialize(path)
8
- @path = path
84
+ attr_accessor :vcs
85
+
86
+ def vcs
87
+ @vcs ||= Nanoc::Extra::VCSes::Dummy.new
9
88
  end
10
89
 
11
- def method_missing(sym, *args, &block)
12
- File.new(@path).__send__(sym, *args, &block)
90
+ ########## Preparation ##########
91
+
92
+ def up # :nodoc:
13
93
  end
14
94
 
15
- end
95
+ def down # :nodoc:
96
+ end
16
97
 
17
- class FilesystemDataSource < Nanoc::DataSource
98
+ def setup # :nodoc:
99
+ # Create directories
100
+ %w( assets content templates layouts lib ).each do |dir|
101
+ FileUtils.mkdir_p(dir)
102
+ vcs.add(dir)
103
+ end
104
+ end
18
105
 
19
- ########## Attributes ##########
106
+ def destroy # :nodoc:
107
+ # Remove files
108
+ vcs.remove(ASSET_DEFAULTS_FILENAME) if File.file?(ASSET_DEFAULTS_FILENAME)
109
+ vcs.remove(PAGE_DEFAULTS_FILENAME) if File.file?(PAGE_DEFAULTS_FILENAME)
110
+ vcs.remove(PAGE_DEFAULTS_FILENAME_OLD) if File.file?(PAGE_DEFAULTS_FILENAME_OLD)
111
+
112
+ # Remove directories
113
+ vcs.remove('assets')
114
+ vcs.remove('content')
115
+ vcs.remove('templates')
116
+ vcs.remove('layouts')
117
+ vcs.remove('lib')
118
+ end
20
119
 
21
- identifier :filesystem
120
+ def update # :nodoc:
121
+ update_page_defaults
122
+ update_pages
123
+ update_layouts
124
+ update_templates
125
+ end
22
126
 
23
- ########## Preparation ##########
127
+ ########## Pages ##########
128
+
129
+ def pages # :nodoc:
130
+ meta_filenames('content').map do |meta_filename|
131
+ # Read metadata
132
+ meta = YAML.load_file(meta_filename) || {}
133
+
134
+ if meta['is_draft']
135
+ # Skip drafts
136
+ nil
137
+ else
138
+ # Get content
139
+ content_filename = content_filename_for_dir(File.dirname(meta_filename))
140
+ content = File.read(content_filename)
141
+
142
+ # Get attributes
143
+ attributes = meta.merge(:file => Nanoc::Extra::FileProxy.new(content_filename))
144
+
145
+ # Get path
146
+ path = meta_filename.sub(/^content/, '').sub(/[^\/]+\.yaml$/, '')
147
+
148
+ # Get modification times
149
+ meta_mtime = File.stat(meta_filename).mtime
150
+ content_mtime = File.stat(content_filename).mtime
151
+ mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
152
+
153
+ # Create page object
154
+ Nanoc::Page.new(content, attributes, path, mtime)
155
+ end
156
+ end.compact
157
+ end
158
+
159
+ def save_page(page) # :nodoc:
160
+ # Determine possible meta file paths
161
+ last_component = page.path.split('/')[-1]
162
+ meta_filename_worst = 'content' + page.path + 'index.yaml'
163
+ meta_filename_best = 'content' + page.path + (last_component || 'content') + '.yaml'
164
+
165
+ # Get existing path
166
+ existing_path = nil
167
+ existing_path = meta_filename_best if File.file?(meta_filename_best)
168
+ existing_path = meta_filename_worst if File.file?(meta_filename_worst)
169
+
170
+ if existing_path.nil?
171
+ # Get filenames
172
+ dir_path = 'content' + page.path
173
+ meta_filename = meta_filename_best
174
+ content_filename = 'content' + page.path + (last_component || 'content') + '.html'
175
+
176
+ # Notify
177
+ Nanoc::NotificationCenter.post(:file_created, meta_filename)
178
+ Nanoc::NotificationCenter.post(:file_created, content_filename)
179
+
180
+ # Create directories if necessary
181
+ FileUtils.mkdir_p(dir_path)
182
+ else
183
+ # Get filenames
184
+ meta_filename = existing_path
185
+ content_filename = content_filename_for_dir(File.dirname(existing_path))
186
+
187
+ # Notify
188
+ Nanoc::NotificationCenter.post(:file_updated, meta_filename)
189
+ Nanoc::NotificationCenter.post(:file_updated, content_filename)
190
+ end
191
+
192
+ # Write files
193
+ File.open(meta_filename, 'w') { |io| io.write(page.attributes.to_split_yaml) }
194
+ File.open(content_filename, 'w') { |io| io.write(page.content) }
195
+
196
+ # Add to working copy if possible
197
+ if existing_path.nil?
198
+ vcs.add(meta_filename)
199
+ vcs.add(content_filename)
200
+ end
201
+ end
202
+
203
+ def move_page(page, new_path) # :nodoc:
204
+ # TODO implement
205
+ end
24
206
 
25
- def up
207
+ def delete_page(page) # :nodoc:
208
+ # TODO implement
26
209
  end
27
210
 
28
- def down
211
+ ########## Assets ##########
212
+
213
+ def assets # :nodoc:
214
+ meta_filenames('assets').map do |meta_filename|
215
+ # Read metadata
216
+ meta = YAML.load_file(meta_filename) || {}
217
+
218
+ # Get content file
219
+ content_filename = content_filename_for_dir(File.dirname(meta_filename))
220
+ content_file = File.new(content_filename)
221
+
222
+ # Get attributes
223
+ attributes = meta.merge(:extension => File.extname(content_filename)[1..-1])
224
+
225
+ # Get path
226
+ path = meta_filename.sub(/^assets/, '').sub(/[^\/]+\.yaml$/, '')
227
+
228
+ # Get modification times
229
+ meta_mtime = File.stat(meta_filename).mtime
230
+ content_mtime = File.stat(content_filename).mtime
231
+ mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
232
+
233
+ # Create asset object
234
+ Nanoc::Asset.new(content_file, attributes, path, mtime)
235
+ end
29
236
  end
30
237
 
31
- def setup
32
- # Create page
33
- FileManager.create_file 'content/content.txt' do
34
- "I'm a brand new root page. Please edit me!\n"
238
+ def save_asset(asset) # :nodoc:
239
+ # Determine meta file path
240
+ last_component = asset.path.split('/')[-1]
241
+ meta_filename = 'assets' + asset.path + last_component + '.yaml'
242
+
243
+ # Get existing path
244
+ existing_path = nil
245
+ existing_path = meta_filename_best if File.file?(meta_filename_best)
246
+ existing_path = meta_filename_worst if File.file?(meta_filename_worst)
247
+
248
+ if meta_filename.nil?
249
+ # Get filenames
250
+ dir_path = 'assets' + asset.path
251
+ content_filename = 'assets' + asset.path + last_component + '.dat'
252
+
253
+ # Notify
254
+ Nanoc::NotificationCenter.post(:file_created, meta_filename)
255
+ Nanoc::NotificationCenter.post(:file_created, content_filename)
256
+
257
+ # Create directories if necessary
258
+ FileUtils.mkdir_p(dir_path)
259
+ else
260
+ # Get filenames
261
+ content_filename = content_filename_for_dir(File.dirname(meta_filename))
262
+
263
+ # Notify
264
+ Nanoc::NotificationCenter.post(:file_updated, meta_filename)
265
+ Nanoc::NotificationCenter.post(:file_updated, content_filename)
35
266
  end
36
- FileManager.create_file 'content/content.yaml' do
37
- "# Built-in\n" +
38
- "\n" +
39
- "# Custom\n" +
40
- "title: \"A New Root Page\"\n"
267
+
268
+ # Write files
269
+ File.open(meta_filename, 'w') { |io| io.write(asset.attributes.to_split_yaml) }
270
+ File.open(content_filename, 'w') { }
271
+
272
+ # Add to working copy if possible
273
+ if meta_filename.nil?
274
+ vcs.add(meta_filename)
275
+ vcs.add(content_filename)
41
276
  end
277
+ end
278
+
279
+ def move_asset(asset, new_path) # :nodoc:
280
+ # TODO implement
281
+ end
282
+
283
+ def delete_asset(asset) # :nodoc:
284
+ # TODO implement
285
+ end
286
+
287
+ ########## Page Defaults ##########
288
+
289
+ def page_defaults # :nodoc:
290
+ # Get attributes
291
+ filename = File.file?(PAGE_DEFAULTS_FILENAME) ? PAGE_DEFAULTS_FILENAME : PAGE_DEFAULTS_FILENAME_OLD
292
+ attributes = YAML.load_file(filename) || {}
42
293
 
43
- # Create page defaults
44
- FileManager.create_file 'meta.yaml' do
45
- "# This file contains the default values for all metafiles.\n" +
46
- "# Other metafiles can override the contents of this one.\n" +
47
- "\n" +
48
- "# Built-in\n" +
49
- "custom_path: none\n" +
50
- "extension: \"html\"\n" +
51
- "filename: \"index\"\n" +
52
- "filters_post: []\n" +
53
- "filters_pre: []\n" +
54
- "is_draft: false\n" +
55
- "layout: \"default\"\n" +
56
- "skip_output: false\n" +
57
- "\n" +
58
- "# Custom\n"
294
+ # Get mtime
295
+ mtime = File.stat(filename).mtime
296
+
297
+ # Build page defaults
298
+ Nanoc::PageDefaults.new(attributes, mtime)
299
+ end
300
+
301
+ def save_page_defaults(page_defaults) # :nodoc:
302
+ # Notify
303
+ if File.file?(PAGE_DEFAULTS_FILENAME)
304
+ filename = PAGE_DEFAULTS_FILENAME
305
+ created = false
306
+ Nanoc::NotificationCenter.post(:file_updated, filename)
307
+ elsif File.file?(PAGE_DEFAULTS_FILENAME_OLD)
308
+ filename = PAGE_DEFAULTS_FILENAME_OLD
309
+ created = false
310
+ Nanoc::NotificationCenter.post(:file_updated, filename)
311
+ else
312
+ filename = PAGE_DEFAULTS_FILENAME
313
+ created = true
314
+ Nanoc::NotificationCenter.post(:file_created, filename)
59
315
  end
60
316
 
61
- # Create template
62
- FileManager.create_file 'templates/default/default.txt' do
63
- "Hi, I'm a new page!\n"
317
+ # Write
318
+ File.open(filename, 'w') do |io|
319
+ io.write(page_defaults.attributes.to_split_yaml)
64
320
  end
65
- FileManager.create_file 'templates/default/default.yaml' do
66
- "# Built-in\n" +
67
- "\n" +
68
- "# Custom\n" +
69
- "title: \"A New Page\"\n"
321
+
322
+ # Add to working copy if possible
323
+ vcs.add(filename) if created
324
+ end
325
+
326
+ ########## Asset Defaults ##########
327
+
328
+ def asset_defaults # :nodoc:
329
+ if File.file?(ASSET_DEFAULTS_FILENAME)
330
+ # Get attributes
331
+ attributes = YAML.load_file(ASSET_DEFAULTS_FILENAME) || {}
332
+
333
+ # Get mtime
334
+ mtime = File.stat(ASSET_DEFAULTS_FILENAME).mtime
335
+
336
+ # Build asset defaults
337
+ Nanoc::AssetDefaults.new(attributes, mtime)
338
+ else
339
+ Nanoc::AssetDefaults.new({})
70
340
  end
341
+ end
71
342
 
72
- # Create layout
73
- FileManager.create_file 'layouts/default.erb' do
74
- "<html>\n" +
75
- " <head>\n" +
76
- " <title><%= @page.title %></title>\n" +
77
- " </head>\n" +
78
- " <body>\n" +
79
- "<%= @page.content %>\n" +
80
- " </body>\n" +
81
- "</html>\n"
343
+ def save_asset_defaults(asset_defaults) # :nodoc:
344
+ # Notify
345
+ if File.file?(ASSET_DEFAULTS_FILENAME)
346
+ Nanoc::NotificationCenter.post(:file_updated, ASSET_DEFAULTS_FILENAME)
347
+ created = false
348
+ else
349
+ Nanoc::NotificationCenter.post(:file_created, ASSET_DEFAULTS_FILENAME)
350
+ created = true
82
351
  end
83
352
 
84
- # Create code
85
- FileManager.create_file 'lib/default.rb' do
86
- "\# All files in the 'lib' directory will be loaded\n" +
87
- "\# before nanoc starts compiling.\n" +
88
- "\n" +
89
- "def html_escape(str)\n" +
90
- " str.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;').gsub('\"', '&quot;')\n" +
91
- "end\n" +
92
- "alias h html_escape\n"
353
+ # Write
354
+ File.open(ASSET_DEFAULTS_FILENAME, 'w') do |io|
355
+ io.write(asset_defaults.attributes.to_split_yaml)
93
356
  end
94
357
 
358
+ # Add to working copy if possible
359
+ vcs.add(ASSET_DEFAULTS_FILENAME) if created
95
360
  end
96
361
 
97
- ########## Loading data ##########
98
-
99
- # The filesystem data source stores its pages in nested directories. Each
100
- # directory represents a single page. The root directory is the 'content'
101
- # directory.
102
- #
103
- # Every directory has a content file and a meta file. The content file
104
- # contains the actual page content, while the meta file contains the
105
- # page's metadata.
106
- #
107
- # Both content files and meta files are named after its parent directory
108
- # (i.e. page). For example, a page named 'foo' will have a directory named
109
- # 'foo', with e.g. a 'foo.markdown' content file and a 'foo.yaml' meta
110
- # file.
111
- #
112
- # Content file extensions are ignored by nanoc. The content file extension
113
- # does not determine the filters to run on it; the meta file defines the
114
- # list of filters. The meta file extension must always be 'yaml', though.
115
- #
116
- # Content files can also have the 'index' basename. Similarly, meta files
117
- # can have the 'meta' basename. For example, a parent directory named
118
- # 'foo' can have an 'index.txt' content file and a 'meta.yaml' meta file.
119
- # This is to preserve backward compatibility.
120
- def pages
121
- meta_filenames.inject([]) do |pages, filename|
122
- # Read metadata
123
- meta = (YAML.load_file(filename) || {}).clean
362
+ ########## Layouts ##########
124
363
 
125
- if meta[:is_draft]
126
- # Skip drafts
127
- pages
128
- else
129
- # Get extra info
130
- path = filename.sub(/^content/, '').sub(/[^\/]+\.yaml$/, '')
131
- file = content_file_for_dir(File.dirname(filename))
132
- extras = {
133
- :path => path,
134
- :file => FileProxy.new(file.path),
135
- :uncompiled_content => file.read
136
- }
137
-
138
- # Add to list of pages
139
- pages + [ meta.merge(extras) ]
364
+ def layouts # :nodoc:
365
+ # Determine what layout directory structure is being used
366
+ dir_count = Dir[File.join('layouts', '*')].select { |f| File.directory?(f) }.size
367
+ is_old_school = (dir_count == 0)
368
+
369
+ if is_old_school
370
+ # Warn about deprecation
371
+ warn(
372
+ 'nanoc 2.1 changes the way layouts are stored. Future versions will not support these outdated sites. To update your site, issue \'nanoc update\'.',
373
+ 'DEPRECATION WARNING'
374
+ )
375
+
376
+ Dir[File.join('layouts', '*')].reject { |f| f =~ /~$/ }.map do |filename|
377
+ # Get content
378
+ content = File.read(filename)
379
+
380
+ # Get attributes
381
+ attributes = { :extension => File.extname(filename)}
382
+
383
+ # Get path
384
+ path = File.basename(filename, attributes[:extension])
385
+
386
+ # Get modification time
387
+ mtime = File.stat(filename).mtime
388
+
389
+ # Create layout object
390
+ Nanoc::Layout.new(content, attributes, path, mtime)
140
391
  end
392
+ else
393
+ meta_filenames('layouts').map do |meta_filename|
394
+ # Get content
395
+ content_filename = content_filename_for_dir(File.dirname(meta_filename))
396
+ content = File.read(content_filename)
397
+
398
+ # Get attributes
399
+ attributes = YAML.load_file(meta_filename) || {}
400
+
401
+ # Get path
402
+ path = meta_filename.sub(/^layouts\//, '').sub(/\/[^\/]+\.yaml$/, '')
403
+
404
+ # Get modification times
405
+ meta_mtime = File.stat(meta_filename).mtime
406
+ content_mtime = File.stat(content_filename).mtime
407
+ mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
408
+
409
+ # Create layout object
410
+ Nanoc::Layout.new(content, attributes, path, mtime)
411
+ end
412
+ end
413
+ end
414
+
415
+ def save_layout(layout) # :nodoc:
416
+ # Determine what layout directory structure is being used
417
+ layout_file_count = Dir[File.join('layouts', '*')].select { |f| File.file?(f) }.size
418
+ error_outdated if layout_file_count > 0
419
+
420
+ # Get paths
421
+ last_component = layout.path.split('/')[-1]
422
+ dir_path = 'layouts' + layout.path
423
+ meta_filename = dir_path + last_component + '.yaml'
424
+ content_filename = Dir[dir_path + last_component + '.*'][0]
425
+
426
+ if File.file?(meta_filename)
427
+ created = false
428
+
429
+ # Notify
430
+ Nanoc::NotificationCenter.post(:file_updated, meta_filename)
431
+ Nanoc::NotificationCenter.post(:file_updated, content_filename)
432
+ else
433
+ created = true
434
+
435
+ # Create dir
436
+ FileUtils.mkdir_p(dir_path)
437
+
438
+ # Get content filename
439
+ content_filename = dir_path + last_component + '.html'
440
+
441
+ # Notify
442
+ Nanoc::NotificationCenter.post(:file_created, meta_filename)
443
+ Nanoc::NotificationCenter.post(:file_created, content_filename)
141
444
  end
445
+
446
+ # Write files
447
+ File.open(meta_filename, 'w') { |io| io.write(layout.attributes.to_split_yaml) }
448
+ File.open(content_filename, 'w') { |io| io.write(layout.content) }
449
+
450
+ # Add to working copy if possible
451
+ if created
452
+ vcs.add(meta_filename)
453
+ vcs.add(content_filename)
454
+ end
455
+ end
456
+
457
+ def move_layout(layout, new_path) # :nodoc:
458
+ # TODO implement
142
459
  end
143
460
 
144
- # The page defaults are loaded from a 'meta.yaml' file
145
- def page_defaults
146
- (YAML.load_file('meta.yaml') || {}).clean
461
+ def delete_layout(layout) # :nodoc:
462
+ # TODO implement
147
463
  end
148
464
 
149
- # Layouts are stored as files in the 'layouts' directory. Each layout has
150
- # a basename (the part before the extension) and an extension. Unlike page
151
- # content files, the extension _is_ used for determining the layout
152
- # processor; which extension maps to which layout processor is defined in
153
- # the layout processors.
154
- def layouts
155
- Dir["layouts/*"].reject { |f| f =~ /~$/ }.map do |filename|
156
- # Get layout details
157
- extension = File.extname(filename)
158
- name = File.basename(filename, extension)
159
- content = File.read(filename)
465
+ ########## Templates ##########
160
466
 
161
- # Build hash for layout
162
- { :name => name, :content => content, :extension => extension }
467
+ def templates # :nodoc:
468
+ meta_filenames('templates').map do |meta_filename|
469
+ # Get name
470
+ name = meta_filename.sub(/^templates\/(.*)\/[^\/]+\.yaml$/, '\1')
471
+
472
+ # Get content
473
+ content_filename = content_filename_for_dir(File.dirname(meta_filename))
474
+ content = File.read(content_filename)
475
+
476
+ # Get attributes
477
+ attributes = YAML.load_file(meta_filename) || {}
478
+
479
+ # Build template
480
+ Nanoc::Template.new(content, attributes, name)
163
481
  end
164
482
  end
165
483
 
166
- # Templates are located in the 'templates' directroy. Every template is a
167
- # directory consisting of a content file and a meta file, both named after
168
- # the template. This is very similar to the way pages are stored, except
169
- # that templates cannot be nested.
170
- def templates
171
- meta_filenames('templates').inject([]) do |templates, filename|
172
- # Get template name
173
- name = filename.sub(/^templates\/(.*)\/[^\/]+\.yaml$/, '\1')
174
-
175
- # Get file names
176
- meta_filename = filename
177
- content_filenames = Dir['templates/' + name + '/' + name + '.*'] +
178
- Dir['templates/' + name + '/index.*'] -
179
- Dir['templates/' + name + '/*.yaml' ]
180
-
181
- # Read files
182
- extension = nil
183
- content = nil
184
- content_filenames.each do |content_filename|
185
- content = File.read(content_filename)
186
- extension = File.extname(content_filename)
187
- end
188
- meta = File.read(meta_filename)
189
-
190
- # Add it to the list of templates
191
- templates + [{
192
- :name => name,
193
- :extension => extension,
194
- :content => content,
195
- :meta => meta
196
- }]
484
+ def save_template(template) # :nodoc:
485
+ # Determine possible meta file paths
486
+ meta_filename_worst = 'templates/' + template.name + '/index.yaml'
487
+ meta_filename_best = 'templates/' + template.name + '/' + template.name + '.yaml'
488
+
489
+ # Get existing path
490
+ existing_path = nil
491
+ existing_path = meta_filename_best if File.file?(meta_filename_best)
492
+ existing_path = meta_filename_worst if File.file?(meta_filename_worst)
493
+
494
+ if existing_path.nil?
495
+ # Get filenames
496
+ dir_path = 'templates/' + template.name
497
+ meta_filename = meta_filename_best
498
+ content_filename = 'templates/' + template.name + '/' + template.name + '.html'
499
+
500
+ # Notify
501
+ Nanoc::NotificationCenter.post(:file_created, meta_filename)
502
+ Nanoc::NotificationCenter.post(:file_created, content_filename)
503
+
504
+ # Create directories if necessary
505
+ FileUtils.mkdir_p(dir_path)
506
+ else
507
+ # Get filenames
508
+ meta_filename = existing_path
509
+ content_filename = content_filename_for_dir(File.dirname(existing_path))
510
+
511
+ # Notify
512
+ Nanoc::NotificationCenter.post(:file_updated, meta_filename)
513
+ Nanoc::NotificationCenter.post(:file_updated, content_filename)
197
514
  end
515
+
516
+ # Write files
517
+ File.open(meta_filename, 'w') { |io| io.write(template.page_attributes.to_split_yaml) }
518
+ File.open(content_filename, 'w') { |io| io.write(template.page_content) }
519
+
520
+ # Add to working copy if possible
521
+ if existing_path.nil?
522
+ vcs.add(meta_filename)
523
+ vcs.add(content_filename)
524
+ end
525
+ end
526
+
527
+ def move_template(template, new_name) # :nodoc:
528
+ # TODO implement
198
529
  end
199
530
 
200
- # Code is stored in '.rb' files in the 'lib' directory. Code can reside
201
- # in sub-directories.
202
- def code
203
- Dir['lib/**/*.rb'].sort.inject('') { |m, f| m + File.read(f) + "\n" }
531
+ def delete_template(template) # :nodoc:
532
+ # TODO implement
204
533
  end
205
534
 
206
- ########## Creating data ##########
535
+ ########## Code ##########
207
536
 
208
- # Creating a page creates a page directory with the name of the page in
209
- # the 'content' directory, as well as a content file named xxx.txt and a
210
- # meta file named xxx.yaml (with xxx being the name of the page).
211
- def create_page(path, template)
212
- # Make sure path does not start or end with a slash
213
- sanitized_path = path.gsub(/^\/+|\/+$/, '')
537
+ def code # :nodoc:
538
+ # Get files
539
+ filenames = Dir['lib/**/*.rb'].sort
214
540
 
215
- # Get paths
216
- dir_path = 'content/' + sanitized_path
217
- name = sanitized_path.sub(/.*\/([^\/]+)$/, '\1')
218
- meta_path = dir_path + '/' + name + '.yaml'
219
- content_path = dir_path + '/' + name + template[:extension]
220
-
221
- # Make sure the page doesn't exist yet
222
- error "A page named '#{path}' already exists." if File.exist?(meta_path)
223
-
224
- # Create index and meta file
225
- FileManager.create_file(meta_path) { template[:meta] }
226
- FileManager.create_file(content_path) { template[:content] }
227
- end
228
-
229
- # Creating a layout creates a single file in the 'layouts' directory,
230
- # named xxx.erb (with xxx being the name of the layout).
231
- def create_layout(name)
232
- # Get details
233
- path = 'layouts/' + name + '.erb'
234
-
235
- # Make sure the layout doesn't exist yet
236
- error "A layout named '#{name}' already exists." if File.exist?(path)
237
-
238
- # Create layout file
239
- FileManager.create_file(path) do
240
- "<html>\n" +
241
- " <head>\n" +
242
- " <title><%= @page.title %></title>\n" +
243
- " </head>\n" +
244
- " <body>\n" +
245
- "<%= @page.content %>\n" +
246
- " </body>\n" +
247
- "</html>\n"
248
- end
541
+ # Get data
542
+ data = filenames.map { |filename| File.read(filename) + "\n" }.join('')
543
+
544
+ # Get modification time
545
+ mtimes = filenames.map { |filename| File.stat(filename).mtime }
546
+ mtime = mtimes.inject { |memo, mtime| memo > mtime ? mtime : memo }
547
+
548
+ # Build code
549
+ Nanoc::Code.new(data, mtime)
249
550
  end
250
551
 
251
- # Creating a template creates a template directory with the name of the
252
- # template in the 'templates' directory, as well as a content file named
253
- # xxx.txt and a meta file named xxx.yaml (with xxx being the name of the
254
- # template).
255
- def create_template(name)
256
- # Get paths
257
- meta_path = 'templates/' + name + '/' + name + '.yaml'
258
- content_path = 'templates/' + name + '/' + name + '.txt'
552
+ def save_code(code) # :nodoc:
553
+ # Check whether code existed
554
+ existed = File.file?('lib/default.rb')
259
555
 
260
- # Make sure the template doesn't exist yet
261
- error "A template named '#{name}' already exists." if File.exist?(meta_path)
556
+ # Remove all existing code files
557
+ Dir['lib/**/*.rb'].each do |file|
558
+ vcs.remove(file) unless file == 'lib/default.rb'
559
+ end
560
+
561
+ # Notify
562
+ if existed
563
+ Nanoc::NotificationCenter.post(:file_updated, 'lib/default.rb')
564
+ else
565
+ Nanoc::NotificationCenter.post(:file_created, 'lib/default.rb')
566
+ end
262
567
 
263
- # Create index and meta file
264
- FileManager.create_file(meta_path) { "# Built-in\n\n# Custom\ntitle: A New Page\n" }
265
- FileManager.create_file(content_path) { "Hi, I'm new here!\n" }
568
+ # Write new code
569
+ File.open('lib/default.rb', 'w') do |io|
570
+ io.write(code.data)
571
+ end
572
+
573
+ # Add to working copy if possible
574
+ vcs.add('lib/default.rb') unless existed
266
575
  end
267
576
 
268
577
  private
269
578
 
270
579
  ########## Custom functions ##########
271
580
 
272
- # Returns the list of meta files in the given (optional) base directory.
273
- def meta_filenames(base='content')
581
+ # Returns the list of all meta files in the given base directory as well
582
+ # as its subdirectories.
583
+ def meta_filenames(base)
274
584
  # Find all possible meta file names
275
585
  filenames = Dir[base + '/**/*.yaml']
276
586
 
@@ -287,16 +597,20 @@ module Nanoc::DataSource::Filesystem
287
597
 
288
598
  # Warn about bad filenames
289
599
  unless bad_filenames.empty?
290
- error "The following files appear to be meta files, " +
291
- "but have an invalid name:\n - " +
292
- bad_filenames.join("\n - ")
600
+ raise RuntimeError.new(
601
+ "The following files appear to be meta files, " +
602
+ "but have an invalid name:\n - " +
603
+ bad_filenames.join("\n - ")
604
+ )
293
605
  end
294
606
 
295
607
  good_filenames
296
608
  end
297
609
 
298
- # Returns a File object for the content file in the given directory
299
- def content_file_for_dir(dir)
610
+ # Returns the filename of the content file in the given directory,
611
+ # ignoring any unwanted files (files that end with '~', '.orig', '.rej' or
612
+ # '.bak')
613
+ def content_filename_for_dir(dir)
300
614
  # Find all files
301
615
  filename_glob_1 = dir.sub(/([^\/]+)$/, '\1/\1.*')
302
616
  filename_glob_2 = dir.sub(/([^\/]+)$/, '\1/index.*')
@@ -306,15 +620,122 @@ module Nanoc::DataSource::Filesystem
306
620
  filenames.reject! { |f| f =~ /\.yaml$/ }
307
621
 
308
622
  # Reject backups
309
- filenames.reject! { |f| f =~ /~$/ }
623
+ filenames.reject! { |f| f =~ /(~|\.orig|\.rej|\.bak)$/ }
310
624
 
311
625
  # Make sure there is only one content file
312
626
  if filenames.size != 1
313
- error "Expected 1 content file in #{dir} but found #{filenames.size}"
627
+ raise RuntimeError.new(
628
+ "Expected 1 content file in #{dir} but found #{filenames.size}"
629
+ )
314
630
  end
315
631
 
316
- # Read content file
317
- File.new(filenames.first)
632
+ # Return content filename
633
+ filenames.first
634
+ end
635
+
636
+ # Raises an "outdated data format" error.
637
+ def error_outdated
638
+ raise RuntimeError.new(
639
+ 'This site\'s data is stored in an old format and must be updated. ' +
640
+ 'To do so, issue the \'nanoc update\' command. For help on ' +
641
+ 'updating a site\'s data, issue \'nanoc help update\'.'
642
+ )
643
+ end
644
+
645
+ # Updated outdated page defaults (renames page defaults file)
646
+ def update_page_defaults
647
+ return unless File.file?(PAGE_DEFAULTS_FILENAME_OLD)
648
+
649
+ vcs.move(PAGE_DEFAULTS_FILENAME_OLD, PAGE_DEFAULTS_FILENAME)
650
+ end
651
+
652
+ # Updates outdated pages (both content and meta file names).
653
+ def update_pages
654
+ # Update content files
655
+ # content/foo/bar/baz/index.ext -> content/foo/bar/baz/baz.ext
656
+ Dir['content/**/index.*'].select { |f| File.file?(f) }.each do |old_filename|
657
+ # Determine new name
658
+ if old_filename =~ /^content\/index\./
659
+ new_filename = old_filename.sub(/^content\/index\./, 'content/content.')
660
+ else
661
+ new_filename = old_filename.sub(/([^\/]+)\/index\.([^\/]+)$/, '\1/\1.\2')
662
+ end
663
+
664
+ # Move
665
+ vcs.move(old_filename, new_filename)
666
+ end
667
+
668
+ # Update meta files
669
+ # content/foo/bar/baz/meta.yaml -> content/foo/bar/baz/baz.yaml
670
+ Dir['content/**/meta.yaml'].select { |f| File.file?(f) }.each do |old_filename|
671
+ # Determine new name
672
+ if old_filename == 'content/meta.yaml'
673
+ new_filename = 'content/content.yaml'
674
+ else
675
+ new_filename = old_filename.sub(/([^\/]+)\/meta.yaml$/, '\1/\1.yaml')
676
+ end
677
+
678
+ # Move
679
+ vcs.move(old_filename, new_filename)
680
+ end
681
+ end
682
+
683
+ # Updates outdated layouts.
684
+ def update_layouts
685
+ # layouts/abc.ext -> layouts/abc/abc.{html,yaml}
686
+ Dir[File.join('layouts', '*')].select { |f| File.file?(f) }.each do |filename|
687
+ # Get filter class
688
+ filter_class = Nanoc::Filter.with_extension(File.extname(filename))
689
+
690
+ # Get data
691
+ content = File.read(filename)
692
+ attributes = { :filter => filter_class.identifier.to_s }
693
+ path = File.basename(filename, File.extname(filename))
694
+
695
+ # Get layout
696
+ tmp_layout = Nanoc::Layout.new(content, attributes, path)
697
+
698
+ # Get filenames
699
+ last_component = tmp_layout.path.split('/')[-1]
700
+ dir_path = 'layouts' + tmp_layout.path
701
+ meta_filename = dir_path + last_component + '.yaml'
702
+ content_filename = dir_path + last_component + File.extname(filename)
703
+
704
+ # Create new files
705
+ FileUtils.mkdir_p(dir_path)
706
+ File.open(meta_filename, 'w') { |io| io.write(tmp_layout.attributes.to_split_yaml) }
707
+ File.open(content_filename, 'w') { |io| io.write(tmp_layout.content) }
708
+
709
+ # Add
710
+ vcs.add(meta_filename)
711
+ vcs.add(content_filename)
712
+
713
+ # Delete old files
714
+ vcs.remove(filename)
715
+ end
716
+ end
717
+
718
+ # Updates outdated templates (both content and meta file names).
719
+ def update_templates
720
+ # Update content files
721
+ # templates/foo/index.ext -> templates/foo/foo.ext
722
+ Dir['templates/**/index.*'].select { |f| File.file?(f) }.each do |old_filename|
723
+ # Determine new name
724
+ new_filename = old_filename.sub(/([^\/]+)\/index\.([^\/]+)$/, '\1/\1.\2')
725
+
726
+ # Move
727
+ vcs.move(old_filename, new_filename)
728
+ end
729
+
730
+ # Update meta files
731
+ # templates/foo/meta.yaml -> templates/foo/foo.yaml
732
+ Dir['templates/**/meta.yaml'].select { |f| File.file?(f) }.each do |old_filename|
733
+ # Determine new name
734
+ new_filename = old_filename.sub(/([^\/]+)\/meta.yaml$/, '\1/\1.yaml')
735
+
736
+ # Move
737
+ vcs.move(old_filename, new_filename)
738
+ end
318
739
  end
319
740
 
320
741
  end