nanoc 4.0.0a1 → 4.0.0a2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +9 -4
  3. data/NEWS.md +13 -0
  4. data/lib/nanoc/base.rb +2 -0
  5. data/lib/nanoc/base/compilation/compiler.rb +0 -1
  6. data/lib/nanoc/base/compilation/compiler_dsl.rb +21 -7
  7. data/lib/nanoc/base/compilation/item_rep_proxy.rb +10 -1
  8. data/lib/nanoc/base/compilation/rule.rb +10 -12
  9. data/lib/nanoc/base/compilation/rules_collection.rb +2 -2
  10. data/lib/nanoc/base/pattern.rb +63 -0
  11. data/lib/nanoc/base/source_data/data_source.rb +33 -18
  12. data/lib/nanoc/base/source_data/identifier.rb +65 -3
  13. data/lib/nanoc/base/source_data/item.rb +1 -1
  14. data/lib/nanoc/base/source_data/item_array.rb +17 -2
  15. data/lib/nanoc/base/source_data/layout.rb +1 -1
  16. data/lib/nanoc/base/source_data/site.rb +4 -3
  17. data/lib/nanoc/base/views/config.rb +22 -0
  18. data/lib/nanoc/base/views/item.rb +76 -0
  19. data/lib/nanoc/base/views/item_collection.rb +46 -4
  20. data/lib/nanoc/base/views/item_rep.rb +23 -0
  21. data/lib/nanoc/base/views/layout.rb +4 -0
  22. data/lib/nanoc/base/views/layout_collection.rb +7 -1
  23. data/lib/nanoc/base/views/mutable_config.rb +5 -0
  24. data/lib/nanoc/base/views/mutable_item.rb +15 -0
  25. data/lib/nanoc/base/views/mutable_item_collection.rb +25 -0
  26. data/lib/nanoc/base/views/mutable_layout.rb +5 -0
  27. data/lib/nanoc/base/views/mutable_layout_collection.rb +20 -2
  28. data/lib/nanoc/cli/cleaning_stream.rb +15 -0
  29. data/lib/nanoc/cli/commands/create-site.rb +17 -35
  30. data/lib/nanoc/cli/commands/shell.rb +7 -4
  31. data/lib/nanoc/data_sources.rb +0 -1
  32. data/lib/nanoc/data_sources/filesystem.rb +75 -76
  33. data/lib/nanoc/data_sources/filesystem_unified.rb +4 -27
  34. data/lib/nanoc/data_sources/filesystem_verbose.rb +4 -21
  35. data/lib/nanoc/version.rb +1 -1
  36. data/nanoc.gemspec +1 -1
  37. data/test/base/test_compiler.rb +35 -15
  38. data/test/base/test_compiler_dsl.rb +32 -30
  39. data/test/base/test_data_source.rb +2 -2
  40. data/test/base/test_item_array.rb +10 -1
  41. data/test/base/test_rule.rb +2 -2
  42. data/test/base/test_site.rb +32 -0
  43. data/test/cli/commands/test_create_site.rb +7 -1
  44. data/test/cli/commands/test_prune.rb +2 -2
  45. data/test/data_sources/test_filesystem.rb +29 -9
  46. data/test/data_sources/test_filesystem_unified.rb +48 -68
  47. data/test/helper.rb +1 -0
  48. data/test/helpers/test_breadcrumbs.rb +4 -4
  49. data/test/test_gem.rb +0 -1
  50. metadata +4 -5
  51. data/lib/nanoc/data_sources/static.rb +0 -62
  52. data/test/data_sources/test_static.rb +0 -93
@@ -7,12 +7,37 @@ module Nanoc
7
7
  Nanoc::MutableItemView
8
8
  end
9
9
 
10
+ # Creates a new item and adds it to the site’s collection of items.
11
+ #
12
+ # @param [String] content The uncompiled item content (if it is a textual
13
+ # item) or the path to the filename containing the content (if it is a
14
+ # binary item).
15
+ #
16
+ # @param [Hash] attributes A hash containing this item's attributes.
17
+ #
18
+ # @param [Nanoc::Identifier, String] identifier This item's identifier.
19
+ #
20
+ # @param [Hash] params Extra parameters.
21
+ #
22
+ # @option params [Symbol, nil] :binary (true) Whether or not this item is
23
+ # binary
24
+ #
25
+ # @return [self]
10
26
  def create(content, attributes, identifier, params = {})
11
27
  @items << Nanoc::Int::Item.new(content, attributes, identifier, params)
28
+ self
12
29
  end
13
30
 
31
+ # Deletes every item for which the block evaluates to true.
32
+ #
33
+ # @yieldparam [Nanoc::ItemView] item
34
+ #
35
+ # @yieldreturn [Boolean]
36
+ #
37
+ # @return [self]
14
38
  def delete_if(&block)
15
39
  @items.delete_if(&block)
40
+ self
16
41
  end
17
42
  end
18
43
  end
@@ -2,6 +2,11 @@
2
2
 
3
3
  module Nanoc
4
4
  class MutableLayoutView < Nanoc::LayoutView
5
+ # Sets the value for the given attribute.
6
+ #
7
+ # @param [Symbol] key
8
+ #
9
+ # @see Hash#[]=
5
10
  def []=(key, value)
6
11
  unwrap[key] = value
7
12
  end
@@ -7,12 +7,30 @@ module Nanoc
7
7
  Nanoc::MutableLayoutView
8
8
  end
9
9
 
10
- def create(content, attributes, identifier, params = {})
11
- @layouts << Nanoc::Int::Layout.new(content, attributes, identifier, params)
10
+ # Creates a new layout and adds it to the site’s collection of layouts.
11
+ #
12
+ # @param [String] content The layout content.
13
+ #
14
+ # @param [Hash] attributes A hash containing this layout's attributes.
15
+ #
16
+ # @param [Nanoc::Identifier, String] identifier This layout's identifier.
17
+ #
18
+ # @return [self]
19
+ def create(content, attributes, identifier)
20
+ @layouts << Nanoc::Int::Layout.new(content, attributes, identifier)
21
+ self
12
22
  end
13
23
 
24
+ # Deletes every layout for which the block evaluates to true.
25
+ #
26
+ # @yieldparam [Nanoc::LayoutView] layout
27
+ #
28
+ # @yieldreturn [Boolean]
29
+ #
30
+ # @return [self]
14
31
  def delete_if(&block)
15
32
  @layouts.delete_if(&block)
33
+ self
16
34
  end
17
35
  end
18
36
  end
@@ -119,6 +119,21 @@ module Nanoc::CLI
119
119
  @stream.winsize = (arg)
120
120
  end
121
121
 
122
+ # @see IO.sync
123
+ def sync
124
+ @stream.sync
125
+ end
126
+
127
+ # @see IO.sync=
128
+ def sync=(arg)
129
+ @stream.sync = arg
130
+ end
131
+
132
+ # @see IO.sync=
133
+ def external_encoding
134
+ @stream.external_encoding
135
+ end
136
+
122
137
  protected
123
138
 
124
139
  def _nanoc_clean(s)
@@ -19,6 +19,11 @@ module Nanoc::CLI::Commands
19
19
  end
20
20
 
21
21
  DEFAULT_CONFIG = <<EOS unless defined? DEFAULT_CONFIG
22
+ # The syntax to use for patterns in the Rules file. Can be either `"glob"`
23
+ # (default) or `null`. The former will enable glob patterns, which behave like
24
+ # Ruby’s File.fnmatch. The latter will enable nanoc 3.x-style patterns.
25
+ pattern_syntax: glob
26
+
22
27
  # A list of file extensions that nanoc will consider to be textual rather than
23
28
  # binary. If an item with an extension not in this list is found, the file
24
29
  # will be considered as binary.
@@ -83,6 +88,8 @@ data_sources:
83
88
  # UTF-8 (which they should be!), change this.
84
89
  encoding: utf-8
85
90
 
91
+ identifier_style: full
92
+
86
93
  # Configuration for the “check” command, which run unit tests on the site.
87
94
  checks:
88
95
  # Configuration for the “internal_links” checker, which checks whether all
@@ -98,44 +105,19 @@ EOS
98
105
  DEFAULT_RULES = <<EOS unless defined? DEFAULT_RULES
99
106
  #!/usr/bin/env ruby
100
107
 
101
- # A few helpful tips about the Rules file:
102
- #
103
- # * The string given to #compile and #route are matching patterns for
104
- # identifiers--not for paths. Therefore, you can’t match on extension.
105
- #
106
- # * The order of rules is important: for each item, only the first matching
107
- # rule is applied.
108
- #
109
- # * Item identifiers start and end with a slash (e.g. “/about/” for the file
110
- # “content/about.html”). To select all children, grandchildren, … of an
111
- # item, use the pattern “/about/*/”; “/about/*” will also select the parent,
112
- # because “*” matches zero or more characters.
113
-
114
- compile '*' do
115
- if item[:extension] == 'css'
116
- # don’t filter stylesheets
117
- elsif item.binary?
118
- # don’t filter binary items
119
- else
120
- filter :erb
121
- layout 'default'
122
- end
108
+ compile '/**/*.html' do
109
+ filter :erb
110
+ layout '/default.*'
123
111
  end
124
112
 
125
- route '*' do
126
- if item[:extension] == 'css'
127
- # Write item with identifier /foo/ to /foo.css
128
- item.identifier.chop + '.css'
129
- elsif item.binary?
130
- # Write item with identifier /foo/ to /foo.ext
131
- item.identifier.chop + (item[:extension] ? '.' + item[:extension] : '')
132
- else
133
- # Write item with identifier /foo/ to /foo/index.html
134
- item.identifier + 'index.html'
135
- end
113
+ compile '/**/*' do
114
+ end
115
+
116
+ route '/**/*' do
117
+ item.identifier.to_s
136
118
  end
137
119
 
138
- layout '*', :erb
120
+ layout '/**/*', :erb
139
121
  EOS
140
122
 
141
123
  DEFAULT_ITEM = <<EOS unless defined? DEFAULT_ITEM
@@ -261,7 +243,7 @@ EOS
261
243
  <head>
262
244
  <meta charset="utf-8">
263
245
  <title>A Brand New nanoc Site - <%= @item[:title] %></title>
264
- <link rel="stylesheet" href="<%= @items['/stylesheet/'].path %>">
246
+ <link rel="stylesheet" href="<%= @items['/stylesheet.*'].path %>">
265
247
 
266
248
  <!-- you don't need to keep this, but it's cool for stats! -->
267
249
  <meta name="generator" content="nanoc <%= Nanoc::VERSION %>">
@@ -20,11 +20,14 @@ module Nanoc::CLI::Commands
20
20
  protected
21
21
 
22
22
  def env
23
+ self.class.env_for_site(site)
24
+ end
25
+
26
+ def self.env_for(site)
23
27
  {
24
- site: site,
25
- items: site.items,
26
- layouts: site.layouts,
27
- config: site.config
28
+ items: Nanoc::ItemCollectionView.new(site.items),
29
+ layouts: Nanoc::LayoutCollectionView.new(site.layouts),
30
+ config: Nanoc::ConfigView.new(site.config),
28
31
  }
29
32
  end
30
33
  end
@@ -5,7 +5,6 @@ module Nanoc::DataSources
5
5
  autoload 'Filesystem', 'nanoc/data_sources/filesystem'
6
6
  autoload 'FilesystemUnified', 'nanoc/data_sources/filesystem_unified'
7
7
  autoload 'FilesystemVerbose', 'nanoc/data_sources/filesystem_verbose'
8
- autoload 'Static', 'nanoc/data_sources/static'
9
8
 
10
9
  Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemVerbose', :filesystem_verbose
11
10
  Nanoc::DataSource.register '::Nanoc::DataSources::FilesystemUnified', :filesystem_unified
@@ -33,15 +33,6 @@ module Nanoc::DataSources
33
33
 
34
34
  protected
35
35
 
36
- # Creates a new object (item or layout) on disk in dir_name according to
37
- # the given identifier. The file will have its attributes taken from the
38
- # attributes hash argument and its content from the content argument.
39
- def create_object(_dir_name, _content, _attributes, _identifier, _params = {})
40
- raise NotImplementedError.new(
41
- "#{self.class} does not implement ##{name}"
42
- )
43
- end
44
-
45
36
  # Creates instances of klass corresponding to the files in dir_name. The
46
37
  # kind attribute indicates the kind of object that is being loaded and is
47
38
  # used solely for debugging purposes.
@@ -54,96 +45,104 @@ module Nanoc::DataSources
54
45
  #
55
46
  # @see Nanoc::DataSources::Filesystem#load_objects
56
47
  def load_objects(dir_name, kind, klass)
57
- all_split_files_in(dir_name).map do |base_filename, (meta_ext, content_ext)|
58
- # Get filenames
59
- meta_filename = filename_for(base_filename, meta_ext)
60
- content_filename = filename_for(base_filename, content_ext)
61
-
62
- # Read content and metadata
63
- is_binary = content_filename && !@site.config[:text_extensions].include?(File.extname(content_filename)[1..-1])
64
- if is_binary && klass == Nanoc::Int::Item
65
- meta = (meta_filename && YAML.load_file(meta_filename)) || {}
66
- content_or_filename = content_filename
67
- elsif is_binary && klass == Nanoc::Int::Layout
68
- raise "The layout file '#{content_filename}' is a binary file, but layouts can only be textual"
69
- else
70
- meta, content_or_filename = parse(content_filename, meta_filename, kind)
71
- end
72
-
73
- # Get attributes
74
- attributes = {
75
- filename: content_filename,
76
- content_filename: content_filename,
77
- meta_filename: meta_filename,
78
- extension: content_filename ? ext_of(content_filename)[1..-1] : nil,
79
- }.merge(meta)
80
-
81
- # Get identifier
82
- if meta_filename
83
- identifier = identifier_for_filename(meta_filename[(dir_name.length + 1)..-1])
84
- elsif content_filename
85
- identifier = identifier_for_filename(content_filename[(dir_name.length + 1)..-1])
86
- else
87
- raise 'meta_filename and content_filename are both nil'
48
+ res = []
49
+
50
+ all_split_files_in(dir_name).each do |base_filename, (meta_ext, content_exts)|
51
+ content_exts.each do |content_ext|
52
+ # Get filenames
53
+ meta_filename = filename_for(base_filename, meta_ext)
54
+ content_filename = filename_for(base_filename, content_ext)
55
+
56
+ # Read content and metadata
57
+ is_binary = content_filename && !@site.config[:text_extensions].include?(File.extname(content_filename)[1..-1])
58
+ if is_binary && klass == Nanoc::Int::Item
59
+ meta = (meta_filename && YAML.load_file(meta_filename)) || {}
60
+ content_or_filename = content_filename
61
+ elsif is_binary && klass == Nanoc::Int::Layout
62
+ raise "The layout file '#{content_filename}' is a binary file, but layouts can only be textual"
63
+ else
64
+ meta, content_or_filename = parse(content_filename, meta_filename, kind)
65
+ end
66
+
67
+ # Get attributes
68
+ attributes = {
69
+ filename: content_filename,
70
+ content_filename: content_filename,
71
+ meta_filename: meta_filename,
72
+ extension: content_filename ? ext_of(content_filename)[1..-1] : nil,
73
+ }.merge(meta)
74
+
75
+ # Get identifier
76
+ if meta_filename
77
+ identifier = identifier_for_filename(meta_filename[dir_name.length..-1])
78
+ elsif content_filename
79
+ identifier = identifier_for_filename(content_filename[dir_name.length..-1])
80
+ else
81
+ raise 'meta_filename and content_filename are both nil'
82
+ end
83
+
84
+ # Get modification times
85
+ meta_mtime = meta_filename ? File.stat(meta_filename).mtime : nil
86
+ content_mtime = content_filename ? File.stat(content_filename).mtime : nil
87
+ if meta_mtime && content_mtime
88
+ mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
89
+ elsif meta_mtime
90
+ mtime = meta_mtime
91
+ elsif content_mtime
92
+ mtime = content_mtime
93
+ else
94
+ raise 'meta_mtime and content_mtime are both nil'
95
+ end
96
+
97
+ # Create layout object
98
+ res << klass.new(
99
+ content_or_filename, attributes, identifier,
100
+ binary: is_binary, mtime: mtime
101
+ )
88
102
  end
89
-
90
- # Get modification times
91
- meta_mtime = meta_filename ? File.stat(meta_filename).mtime : nil
92
- content_mtime = content_filename ? File.stat(content_filename).mtime : nil
93
- if meta_mtime && content_mtime
94
- mtime = meta_mtime > content_mtime ? meta_mtime : content_mtime
95
- elsif meta_mtime
96
- mtime = meta_mtime
97
- elsif content_mtime
98
- mtime = content_mtime
99
- else
100
- raise 'meta_mtime and content_mtime are both nil'
101
- end
102
-
103
- # Create layout object
104
- klass.new(
105
- content_or_filename, attributes, identifier,
106
- binary: is_binary, mtime: mtime
107
- )
108
103
  end
104
+
105
+ res
109
106
  end
110
107
 
111
- # Finds all items/layouts/... in the given base directory. Returns a hash
112
- # in which the keys are the file's dirname + basenames, and the values a
113
- # pair consisting of the metafile extension and the content file
114
- # extension. The meta file extension or the content file extension can be
115
- # nil, but not both. Backup files are ignored. For example:
108
+ # e.g.
116
109
  #
117
110
  # {
118
- # 'content/foo' => [ 'yaml', 'html' ],
119
- # 'content/bar' => [ 'yaml', nil ],
120
- # 'content/qux' => [ nil, 'html' ]
111
+ # 'content/foo' => [ 'yaml', ['html', 'md'] ],
112
+ # 'content/bar' => [ 'yaml', [nil] ],
113
+ # 'content/qux' => [ nil, ['html'] ]
121
114
  # }
122
115
  def all_split_files_in(dir_name)
123
- grouped_filenames =
116
+ by_basename =
124
117
  all_files_in(dir_name)
125
118
  .reject { |fn| fn =~ /(~|\.orig|\.rej|\.bak)$/ }
126
119
  .group_by { |fn| basename_of(fn) }
127
120
 
128
- grouped_filenames.each_pair do |key, filenames|
121
+ all = {}
122
+
123
+ by_basename.each_pair do |basename, filenames|
129
124
  # Divide
130
125
  meta_filenames = filenames.select { |fn| ext_of(fn) == '.yaml' }
131
126
  content_filenames = filenames.select { |fn| ext_of(fn) != '.yaml' }
132
127
 
133
128
  # Check number of files per type
134
129
  unless [0, 1].include?(meta_filenames.size)
135
- raise "Found #{meta_filenames.size} meta files for #{key}; expected 0 or 1"
130
+ raise "Found #{meta_filenames.size} meta files for #{basename}; expected 0 or 1"
136
131
  end
137
- unless [0, 1].include?(content_filenames.size)
138
- raise "Found #{content_filenames.size} content files for #{key}; expected 0 or 1"
132
+ unless config[:identifier_style] == 'full'
133
+ unless [0, 1].include?(content_filenames.size)
134
+ raise "Found #{content_filenames.size} content files for #{basename}; expected 0 or 1"
135
+ end
139
136
  end
140
137
 
141
- # Reorder elements and convert to extnames
142
- filenames[0] = meta_filenames[0] ? 'yaml' : nil
143
- filenames[1] = content_filenames[0] ? ext_of(content_filenames[0])[1..-1] || '' : nil
138
+ all[basename] = []
139
+ all[basename][0] =
140
+ meta_filenames[0] ? 'yaml' : nil
141
+ all[basename][1] =
142
+ content_filenames.any? ? content_filenames.map { |fn| ext_of(fn)[1..-1] || '' } : [nil]
144
143
  end
145
144
 
146
- grouped_filenames
145
+ all
147
146
  end
148
147
 
149
148
  # Returns all files in the given directory and directories below it.
@@ -73,33 +73,6 @@ module Nanoc::DataSources
73
73
 
74
74
  private
75
75
 
76
- # See {Nanoc::DataSources::Filesystem#create_object}.
77
- def create_object(dir_name, content, attributes, identifier, params = {})
78
- # Check for periods
79
- if (@config.nil? || !@config[:allow_periods_in_identifiers]) && identifier.include?('.')
80
- raise "Attempted to create an object in #{dir_name} with identifier #{identifier} containing a period, but allow_periods_in_identifiers is not enabled in the site configuration. (Enabling allow_periods_in_identifiers may cause the site to break, though.)"
81
- end
82
-
83
- # Determine path
84
- ext = params[:extension] || '.html'
85
- path = dir_name + (identifier == '/' ? '/index.html' : identifier[0..-2] + ext)
86
- parent_path = File.dirname(path)
87
-
88
- # Notify
89
- Nanoc::Int::NotificationCenter.post(:file_created, path)
90
-
91
- # Write item
92
- FileUtils.mkdir_p(parent_path)
93
- File.open(path, 'w') do |io|
94
- meta = attributes.__nanoc_stringify_keys_recursively
95
- unless meta == {}
96
- io.write(YAML.dump(meta).strip + "\n")
97
- io.write("---\n")
98
- end
99
- io.write(content)
100
- end
101
- end
102
-
103
76
  # See {Nanoc::DataSources::Filesystem#filename_for}.
104
77
  def filename_for(base_filename, ext)
105
78
  if ext.nil?
@@ -114,6 +87,10 @@ module Nanoc::DataSources
114
87
  # Returns the identifier derived from the given filename, first stripping
115
88
  # the given directory name off the filename.
116
89
  def identifier_for_filename(filename)
90
+ if config[:identifier_style] == 'full'
91
+ return Nanoc::Identifier.new(filename, style: :full)
92
+ end
93
+
117
94
  if filename =~ /(^|\/)index(\.[^\/]+)?$/
118
95
  regex = @config && @config[:allow_periods_in_identifiers] ? /\/?(index)?(\.[^\/\.]+)?$/ : /\/?index(\.[^\/]+)?$/
119
96
  else