nanoc 2.0.4 → 2.1
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.
- data/ChangeLog +31 -1
- data/LICENSE +1 -1
- data/README +63 -3
- data/Rakefile +59 -12
- data/bin/nanoc +7 -199
- data/lib/nanoc.rb +83 -12
- data/lib/nanoc/base/asset.rb +113 -0
- data/lib/nanoc/base/asset_defaults.rb +21 -0
- data/lib/nanoc/base/asset_rep.rb +277 -0
- data/lib/nanoc/base/binary_filter.rb +44 -0
- data/lib/nanoc/base/code.rb +41 -0
- data/lib/nanoc/base/compiler.rb +46 -34
- data/lib/nanoc/base/core_ext/hash.rb +51 -7
- data/lib/nanoc/base/core_ext/string.rb +8 -0
- data/lib/nanoc/base/data_source.rb +253 -20
- data/lib/nanoc/base/defaults.rb +30 -0
- data/lib/nanoc/base/enhancements.rb +9 -84
- data/lib/nanoc/base/filter.rb +109 -6
- data/lib/nanoc/base/layout.rb +91 -0
- data/lib/nanoc/base/notification_center.rb +66 -0
- data/lib/nanoc/base/page.rb +94 -126
- data/lib/nanoc/base/page_defaults.rb +20 -0
- data/lib/nanoc/base/page_rep.rb +318 -0
- data/lib/nanoc/base/plugin.rb +57 -9
- data/lib/nanoc/base/proxies/asset_proxy.rb +29 -0
- data/lib/nanoc/base/proxies/asset_rep_proxy.rb +26 -0
- data/lib/nanoc/base/proxies/layout_proxy.rb +25 -0
- data/lib/nanoc/base/proxies/page_proxy.rb +35 -0
- data/lib/nanoc/base/proxies/page_rep_proxy.rb +28 -0
- data/lib/nanoc/base/proxy.rb +37 -0
- data/lib/nanoc/base/router.rb +72 -0
- data/lib/nanoc/base/site.rb +219 -88
- data/lib/nanoc/base/template.rb +64 -0
- data/lib/nanoc/binary_filters/image_science_thumbnail.rb +28 -0
- data/lib/nanoc/cli.rb +1 -0
- data/lib/nanoc/cli/base.rb +219 -0
- data/lib/nanoc/cli/cli.rb +16 -0
- data/lib/nanoc/cli/command.rb +105 -0
- data/lib/nanoc/cli/commands/autocompile.rb +80 -0
- data/lib/nanoc/cli/commands/compile.rb +273 -0
- data/lib/nanoc/cli/commands/create_layout.rb +85 -0
- data/lib/nanoc/cli/commands/create_page.rb +85 -0
- data/lib/nanoc/cli/commands/create_site.rb +327 -0
- data/lib/nanoc/cli/commands/create_template.rb +76 -0
- data/lib/nanoc/cli/commands/help.rb +69 -0
- data/lib/nanoc/cli/commands/info.rb +114 -0
- data/lib/nanoc/cli/commands/switch.rb +141 -0
- data/lib/nanoc/cli/commands/update.rb +91 -0
- data/lib/nanoc/cli/ext.rb +37 -0
- data/lib/nanoc/cli/logger.rb +66 -0
- data/lib/nanoc/cli/option_parser.rb +168 -0
- data/lib/nanoc/data_sources/filesystem.rb +645 -224
- data/lib/nanoc/data_sources/filesystem_combined.rb +495 -0
- data/lib/nanoc/extra/auto_compiler.rb +265 -0
- data/lib/nanoc/extra/context.rb +22 -0
- data/lib/nanoc/extra/core_ext/hash.rb +54 -0
- data/lib/nanoc/extra/core_ext/time.rb +13 -0
- data/lib/nanoc/extra/file_proxy.rb +29 -0
- data/lib/nanoc/extra/vcs.rb +48 -0
- data/lib/nanoc/extra/vcses/bazaar.rb +21 -0
- data/lib/nanoc/extra/vcses/dummy.rb +20 -0
- data/lib/nanoc/extra/vcses/git.rb +21 -0
- data/lib/nanoc/extra/vcses/mercurial.rb +21 -0
- data/lib/nanoc/extra/vcses/subversion.rb +21 -0
- data/lib/nanoc/filters/bluecloth.rb +13 -0
- data/lib/nanoc/filters/erb.rb +6 -22
- data/lib/nanoc/filters/erubis.rb +14 -0
- data/lib/nanoc/filters/haml.rb +7 -23
- data/lib/nanoc/filters/markaby.rb +5 -5
- data/lib/nanoc/filters/maruku.rb +14 -0
- data/lib/nanoc/filters/old.rb +19 -0
- data/lib/nanoc/filters/rdiscount.rb +13 -0
- data/lib/nanoc/filters/rdoc.rb +5 -4
- data/lib/nanoc/filters/redcloth.rb +14 -0
- data/lib/nanoc/filters/rubypants.rb +14 -0
- data/lib/nanoc/filters/sass.rb +13 -0
- data/lib/nanoc/helpers/blogging.rb +170 -0
- data/lib/nanoc/helpers/capturing.rb +59 -0
- data/lib/nanoc/helpers/html_escape.rb +23 -0
- data/lib/nanoc/helpers/link_to.rb +69 -0
- data/lib/nanoc/helpers/render.rb +47 -0
- data/lib/nanoc/helpers/tagging.rb +52 -0
- data/lib/nanoc/helpers/xml_sitemap.rb +58 -0
- data/lib/nanoc/routers/default.rb +54 -0
- data/lib/nanoc/routers/no_dirs.rb +66 -0
- data/lib/nanoc/routers/versioned.rb +79 -0
- metadata +112 -22
- data/lib/nanoc/base/auto_compiler.rb +0 -132
- data/lib/nanoc/base/layout_processor.rb +0 -33
- data/lib/nanoc/base/page_proxy.rb +0 -31
- data/lib/nanoc/base/plugin_manager.rb +0 -33
- data/lib/nanoc/data_sources/database.rb +0 -259
- data/lib/nanoc/data_sources/trivial.rb +0 -145
- data/lib/nanoc/filters/markdown.rb +0 -13
- data/lib/nanoc/filters/smartypants.rb +0 -13
- data/lib/nanoc/filters/textile.rb +0 -13
- data/lib/nanoc/layout_processors/erb.rb +0 -35
- data/lib/nanoc/layout_processors/haml.rb +0 -38
- 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::
|
|
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
|
-
|
|
78
|
+
########## Attributes ##########
|
|
79
|
+
|
|
80
|
+
identifier :filesystem
|
|
4
81
|
|
|
5
|
-
|
|
82
|
+
########## VCSes ##########
|
|
6
83
|
|
|
7
|
-
|
|
8
|
-
|
|
84
|
+
attr_accessor :vcs
|
|
85
|
+
|
|
86
|
+
def vcs
|
|
87
|
+
@vcs ||= Nanoc::Extra::VCSes::Dummy.new
|
|
9
88
|
end
|
|
10
89
|
|
|
11
|
-
|
|
12
|
-
|
|
90
|
+
########## Preparation ##########
|
|
91
|
+
|
|
92
|
+
def up # :nodoc:
|
|
13
93
|
end
|
|
14
94
|
|
|
15
|
-
|
|
95
|
+
def down # :nodoc:
|
|
96
|
+
end
|
|
16
97
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
+
def update # :nodoc:
|
|
121
|
+
update_page_defaults
|
|
122
|
+
update_pages
|
|
123
|
+
update_layouts
|
|
124
|
+
update_templates
|
|
125
|
+
end
|
|
22
126
|
|
|
23
|
-
##########
|
|
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
|
|
207
|
+
def delete_page(page) # :nodoc:
|
|
208
|
+
# TODO implement
|
|
26
209
|
end
|
|
27
210
|
|
|
28
|
-
|
|
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
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
317
|
+
# Write
|
|
318
|
+
File.open(filename, 'w') do |io|
|
|
319
|
+
io.write(page_defaults.attributes.to_split_yaml)
|
|
64
320
|
end
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
"\# before nanoc starts compiling.\n" +
|
|
88
|
-
"\n" +
|
|
89
|
-
"def html_escape(str)\n" +
|
|
90
|
-
" str.gsub('&', '&').gsub('<', '<').gsub('>', '>').gsub('\"', '"')\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
|
-
##########
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
#
|
|
139
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
(YAML.load_file('meta.yaml') || {}).clean
|
|
461
|
+
def delete_layout(layout) # :nodoc:
|
|
462
|
+
# TODO implement
|
|
147
463
|
end
|
|
148
464
|
|
|
149
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
-
##########
|
|
535
|
+
########## Code ##########
|
|
207
536
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
#
|
|
261
|
-
|
|
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
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
273
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
299
|
-
|
|
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
|
-
|
|
627
|
+
raise RuntimeError.new(
|
|
628
|
+
"Expected 1 content file in #{dir} but found #{filenames.size}"
|
|
629
|
+
)
|
|
314
630
|
end
|
|
315
631
|
|
|
316
|
-
#
|
|
317
|
-
|
|
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
|