nanoc 4.0.0a1 → 4.0.0a2

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