nanoc-core 4.11.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/NEWS.md +3 -0
  3. data/README.md +3 -0
  4. data/lib/nanoc/core/binary_content.rb +12 -0
  5. data/lib/nanoc/core/checksummer.rb +287 -0
  6. data/lib/nanoc/core/code_snippet.rb +57 -0
  7. data/lib/nanoc/core/configuration-schema.json +122 -0
  8. data/lib/nanoc/core/configuration.rb +206 -0
  9. data/lib/nanoc/core/content.rb +46 -0
  10. data/lib/nanoc/core/context.rb +70 -0
  11. data/lib/nanoc/core/contracts_support.rb +131 -0
  12. data/lib/nanoc/core/core_ext/array.rb +54 -0
  13. data/lib/nanoc/core/core_ext/hash.rb +58 -0
  14. data/lib/nanoc/core/core_ext/string.rb +20 -0
  15. data/lib/nanoc/core/data_source.rb +170 -0
  16. data/lib/nanoc/core/directed_graph.rb +195 -0
  17. data/lib/nanoc/core/document.rb +124 -0
  18. data/lib/nanoc/core/error.rb +9 -0
  19. data/lib/nanoc/core/identifiable_collection.rb +142 -0
  20. data/lib/nanoc/core/identifier.rb +218 -0
  21. data/lib/nanoc/core/item.rb +11 -0
  22. data/lib/nanoc/core/item_collection.rb +15 -0
  23. data/lib/nanoc/core/item_rep.rb +92 -0
  24. data/lib/nanoc/core/layout.rb +11 -0
  25. data/lib/nanoc/core/layout_collection.rb +15 -0
  26. data/lib/nanoc/core/lazy_value.rb +38 -0
  27. data/lib/nanoc/core/notification_center.rb +97 -0
  28. data/lib/nanoc/core/pattern.rb +37 -0
  29. data/lib/nanoc/core/processing_action.rb +23 -0
  30. data/lib/nanoc/core/processing_actions/filter.rb +40 -0
  31. data/lib/nanoc/core/processing_actions/layout.rb +40 -0
  32. data/lib/nanoc/core/processing_actions/snapshot.rb +50 -0
  33. data/lib/nanoc/core/processing_actions.rb +12 -0
  34. data/lib/nanoc/core/regexp_pattern.rb +28 -0
  35. data/lib/nanoc/core/snapshot_def.rb +22 -0
  36. data/lib/nanoc/core/string_pattern.rb +29 -0
  37. data/lib/nanoc/core/temp_filename_factory.rb +53 -0
  38. data/lib/nanoc/core/textual_content.rb +41 -0
  39. data/lib/nanoc/core/version.rb +7 -0
  40. data/lib/nanoc/core.rb +60 -0
  41. data/lib/nanoc-core.rb +3 -0
  42. metadata +152 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6b14c2953e85fe025ebc7b886e49e7c5479f9a2a5e4ae490b427760ed916646b
4
+ data.tar.gz: cdc17c42152a6ef9474e1406d1f2078884b298833213707b54cd2825785fe448
5
+ SHA512:
6
+ metadata.gz: 0b039e4fdf615feb7cacd556600fa3cf967290335d2034cbe9046b59613ea0e8b736078aa83ca2fc2c921869ee50cf4e62f5fa823341674612ae72801df53dbc
7
+ data.tar.gz: e1b1374ba0c0bd461de1ccb8ec658dbe65f49c9b0eae2a48efe138757cea4f0f66c0a3b79e0243b9cbc6d6ad312b6a589dbbd3f620bea0948da37b79b82d156d
data/NEWS.md ADDED
@@ -0,0 +1,3 @@
1
+ # nanoc-core news
2
+
3
+ See the release notes for Nanoc for details.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # nanoc-core
2
+
3
+ This repository contains the core of Nanoc. It is designed to have no dependencies (neither gems nor external applications), and be operating system-agnostic.
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class BinaryContent < Content
6
+ contract C::None => C::Bool
7
+ def binary?
8
+ true
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,287 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Creates checksums for given objects.
6
+ #
7
+ # A checksum is a string, such as “mL+TaqNsEeiPkWloPgCtAofT1yg=”, that is used
8
+ # to determine whether a piece of data has changed.
9
+ class Checksummer
10
+ class VerboseDigest
11
+ def initialize
12
+ @str = +''
13
+ end
14
+
15
+ def update(str)
16
+ @str << str
17
+ end
18
+
19
+ def to_s
20
+ @str
21
+ end
22
+ end
23
+
24
+ class CompactDigest
25
+ def initialize
26
+ @digest = Digest::SHA1.new
27
+ end
28
+
29
+ def update(str)
30
+ @digest.update(str)
31
+ end
32
+
33
+ def to_s
34
+ @digest.base64digest
35
+ end
36
+ end
37
+
38
+ class << self
39
+ # @param obj The object to create a checksum for
40
+ #
41
+ # @return [String] The digest
42
+ def calc(obj, digest_class = CompactDigest)
43
+ digest = digest_class.new
44
+ update(obj, digest)
45
+ digest.to_s
46
+ end
47
+
48
+ def calc_for_content_of(obj)
49
+ obj.content_checksum_data || obj.checksum_data || Nanoc::Core::Checksummer.calc(obj.content)
50
+ end
51
+
52
+ def calc_for_each_attribute_of(obj, digest_class = CompactDigest)
53
+ obj.attributes.each_with_object({}) do |(key, value), memo|
54
+ memo[key] = Nanoc::Core::Checksummer.calc(value, digest_class)
55
+ end
56
+ end
57
+
58
+ def define_behavior(klass, behavior)
59
+ behaviors[klass] = behavior
60
+ end
61
+
62
+ private
63
+
64
+ def update(obj, digest, visited = Hamster::Set.new)
65
+ digest.update(obj.class.to_s)
66
+
67
+ if visited.include?(obj)
68
+ digest.update('<recur>')
69
+ else
70
+ digest.update('<')
71
+ behavior_for(obj).update(obj, digest) { |o| update(o, digest, visited.add(obj)) }
72
+ digest.update('>')
73
+ end
74
+ end
75
+
76
+ def behaviors
77
+ return @behaviors if @behaviors
78
+
79
+ @behaviors = {}
80
+
81
+ # NOTE: Other behaviors are registered elsewhere
82
+ # (search for `define_behavior`).
83
+
84
+ define_behavior(Array, ArrayUpdateBehavior)
85
+ define_behavior(FalseClass, NoUpdateBehavior)
86
+ define_behavior(Hash, HashUpdateBehavior)
87
+ define_behavior(NilClass, NoUpdateBehavior)
88
+ define_behavior(Numeric, RawUpdateBehavior)
89
+ define_behavior(Pathname, PathnameUpdateBehavior)
90
+ define_behavior(String, RawUpdateBehavior)
91
+ define_behavior(Symbol, RawUpdateBehavior)
92
+ define_behavior(Time, ToIToSUpdateBehavior)
93
+ define_behavior(TrueClass, NoUpdateBehavior)
94
+
95
+ define_behavior(Nanoc::Core::BinaryContent, BinaryContentUpdateBehavior)
96
+ define_behavior(Nanoc::Core::Configuration, HashUpdateBehavior)
97
+ define_behavior(Nanoc::Core::Context, ContextUpdateBehavior)
98
+ define_behavior(Nanoc::Core::IdentifiableCollection, ArrayUpdateBehavior)
99
+ define_behavior(Nanoc::Core::Identifier, ToSUpdateBehavior)
100
+ define_behavior(Nanoc::Core::Item, DocumentUpdateBehavior)
101
+ define_behavior(Nanoc::Core::ItemRep, ItemRepUpdateBehavior)
102
+ define_behavior(Nanoc::Core::Layout, DocumentUpdateBehavior)
103
+ define_behavior(Nanoc::Core::TextualContent, StringUpdateBehavior)
104
+
105
+ @behaviors
106
+ end
107
+
108
+ def behavior_for_class(klass)
109
+ behaviors.fetch(klass) do
110
+ if Object.equal?(klass.superclass)
111
+ RescueUpdateBehavior
112
+ else
113
+ behavior_for_class(klass.superclass)
114
+ end
115
+ end
116
+ end
117
+
118
+ def behavior_for(obj)
119
+ behavior_for_class(obj.class)
120
+ end
121
+ end
122
+
123
+ class UpdateBehavior
124
+ def self.update(_obj, _digest)
125
+ raise NotImpementedError
126
+ end
127
+ end
128
+
129
+ class RuleContextUpdateBehavior < UpdateBehavior
130
+ def self.update(obj, digest)
131
+ digest.update('item=')
132
+ yield(obj.item)
133
+ digest.update(',rep=')
134
+ yield(obj.rep)
135
+ digest.update(',items=')
136
+ yield(obj.items)
137
+ digest.update(',layouts=')
138
+ yield(obj.layouts)
139
+ digest.update(',config=')
140
+ yield(obj.config)
141
+ end
142
+ end
143
+
144
+ class ContextUpdateBehavior < UpdateBehavior
145
+ def self.update(obj, digest)
146
+ obj.instance_variables.each do |var|
147
+ digest.update(var.to_s)
148
+ digest.update('=')
149
+ yield(obj.instance_variable_get(var))
150
+ digest.update(',')
151
+ end
152
+ end
153
+ end
154
+
155
+ class RawUpdateBehavior < UpdateBehavior
156
+ def self.update(obj, digest)
157
+ digest.update(obj.to_s)
158
+ end
159
+ end
160
+
161
+ class ToSUpdateBehavior < UpdateBehavior
162
+ def self.update(obj, _digest)
163
+ yield(obj.to_s)
164
+ end
165
+ end
166
+
167
+ class ToIToSUpdateBehavior < UpdateBehavior
168
+ def self.update(obj, digest)
169
+ digest.update(obj.to_i.to_s)
170
+ end
171
+ end
172
+
173
+ class StringUpdateBehavior < UpdateBehavior
174
+ def self.update(obj, _digest)
175
+ yield(obj.string)
176
+ end
177
+ end
178
+
179
+ class DataUpdateBehavior < UpdateBehavior
180
+ def self.update(obj, _digest)
181
+ yield(obj.data)
182
+ end
183
+ end
184
+
185
+ class NoUpdateBehavior < UpdateBehavior
186
+ def self.update(_obj, _digest); end
187
+ end
188
+
189
+ class UnwrapUpdateBehavior < UpdateBehavior
190
+ def self.update(obj, _digest)
191
+ yield(obj._unwrap)
192
+ end
193
+ end
194
+
195
+ class ArrayUpdateBehavior < UpdateBehavior
196
+ def self.update(obj, digest)
197
+ obj.each do |el|
198
+ yield(el)
199
+ digest.update(',')
200
+ end
201
+ end
202
+ end
203
+
204
+ class HashUpdateBehavior < UpdateBehavior
205
+ def self.update(obj, digest)
206
+ obj.each do |key, value|
207
+ yield(key)
208
+ digest.update('=')
209
+ yield(value)
210
+ digest.update(',')
211
+ end
212
+ end
213
+ end
214
+
215
+ class DocumentUpdateBehavior < UpdateBehavior
216
+ def self.update(obj, digest)
217
+ if obj.checksum_data
218
+ digest.update('checksum_data=' + obj.checksum_data)
219
+ else
220
+ if obj.content_checksum_data
221
+ digest.update('content_checksum_data=' + obj.content_checksum_data)
222
+ else
223
+ digest.update('content=')
224
+ yield(obj.content)
225
+ end
226
+
227
+ if obj.attributes_checksum_data
228
+ digest.update(',attributes_checksum_data=' + obj.attributes_checksum_data)
229
+ else
230
+ digest.update(',attributes=')
231
+ yield(obj.attributes)
232
+ end
233
+
234
+ digest.update(',identifier=')
235
+ yield(obj.identifier)
236
+ end
237
+ end
238
+ end
239
+
240
+ class ItemRepUpdateBehavior < UpdateBehavior
241
+ def self.update(obj, digest)
242
+ digest.update('item=')
243
+ yield(obj.item)
244
+ digest.update(',name=')
245
+ yield(obj.name)
246
+ end
247
+ end
248
+
249
+ class PathnameUpdateBehavior < UpdateBehavior
250
+ def self.update(obj, digest)
251
+ filename = obj.to_s
252
+ if File.exist?(filename)
253
+ stat = File.stat(filename)
254
+ digest.update(stat.size.to_s + '-' + stat.mtime.to_i.to_s)
255
+ else
256
+ digest.update('???')
257
+ end
258
+ end
259
+ end
260
+
261
+ class BinaryContentUpdateBehavior < UpdateBehavior
262
+ def self.update(obj, _digest)
263
+ yield(Pathname.new(obj.filename))
264
+ end
265
+ end
266
+
267
+ class RescueUpdateBehavior < UpdateBehavior
268
+ def self.update(obj, digest)
269
+ if obj.class.to_s == 'Sass::Importers::Filesystem'
270
+ digest.update('root=')
271
+ digest.update(obj.root)
272
+ return
273
+ end
274
+
275
+ data =
276
+ begin
277
+ Marshal.dump(obj)
278
+ rescue
279
+ obj.inspect
280
+ end
281
+
282
+ digest.update(data)
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Nanoc::Core::CodeSnippet represent a piece of custom code of a Nanoc site.
6
+ #
7
+ # @api private
8
+ class CodeSnippet
9
+ include Nanoc::Core::ContractsSupport
10
+
11
+ # A string containing the actual code in this code snippet.
12
+ #
13
+ # @return [String]
14
+ attr_reader :data
15
+
16
+ # The filename corresponding to this code snippet.
17
+ #
18
+ # @return [String]
19
+ attr_reader :filename
20
+
21
+ contract String, String => C::Any
22
+ # Creates a new code snippet.
23
+ #
24
+ # @param [String] data The raw source code which will be executed before
25
+ # compilation
26
+ #
27
+ # @param [String] filename The filename corresponding to this code snippet
28
+ def initialize(data, filename)
29
+ @data = data
30
+ @filename = filename
31
+ end
32
+
33
+ contract C::None => nil
34
+ # Loads the code by executing it.
35
+ #
36
+ # @return [void]
37
+ def load
38
+ # rubocop:disable Security/Eval
39
+ eval('def self.use_helper(mod); Nanoc::Core::Context.instance_eval { include mod }; end', TOPLEVEL_BINDING)
40
+ eval(@data, TOPLEVEL_BINDING, @filename)
41
+ # rubocop:enable Security/Eval
42
+ nil
43
+ end
44
+
45
+ # Returns an object that can be used for uniquely identifying objects.
46
+ #
47
+ # @return [Object] An unique reference to this object
48
+ def reference
49
+ "code_snippet:#{filename}"
50
+ end
51
+
52
+ def inspect
53
+ "<#{self.class} filename=\"#{filename}\">"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,122 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "Nanoc configuration schema",
4
+ "type": "object",
5
+ "properties": {
6
+ "text_extensions": {
7
+ "type": "array",
8
+ "items": {
9
+ "type": "string"
10
+ }
11
+ },
12
+ "output_dir": {
13
+ "type": "string"
14
+ },
15
+ "index_filenames": {
16
+ "type": "array",
17
+ "items": {
18
+ "type": "string"
19
+ }
20
+ },
21
+ "enable_output_diff": {
22
+ "type": "boolean"
23
+ },
24
+ "prune": {
25
+ "type": "object",
26
+ "additionalProperties": false,
27
+ "properties": {
28
+ "auto_prune": {
29
+ "type": "boolean"
30
+ },
31
+ "exclude": {
32
+ "type": "array",
33
+ "items": {
34
+ "type": "string"
35
+ }
36
+ }
37
+ }
38
+ },
39
+ "commands_dirs": {
40
+ "type": "array",
41
+ "items": {
42
+ "type": "string"
43
+ }
44
+ },
45
+ "lib_dirs": {
46
+ "type": "array",
47
+ "items": {
48
+ "type": "string"
49
+ }
50
+ },
51
+ "data_sources": {
52
+ "type": "array",
53
+ "items": {
54
+ "type": "object",
55
+ "properties": {
56
+ "type": {
57
+ "type": "string"
58
+ },
59
+ "items_root": {
60
+ "anyOf": [
61
+ { "type": "string" },
62
+ { "type": "null" }
63
+ ]
64
+ },
65
+ "layouts_root": {
66
+ "anyOf": [
67
+ { "type": "string" },
68
+ { "type": "null" }
69
+ ]
70
+ }
71
+ }
72
+ }
73
+ },
74
+ "string_pattern_type": {
75
+ "type": "string",
76
+ "enum": ["glob", "legacy"]
77
+ },
78
+ "checks": {
79
+ "type": "object",
80
+ "properties": {
81
+ "internal_links": {
82
+ "type": "object",
83
+ "additionalProperties": false,
84
+ "properties": {
85
+ "exclude": {
86
+ "type": "array",
87
+ "items": {
88
+ "type": "string"
89
+ }
90
+ }
91
+ }
92
+ },
93
+ "external_links": {
94
+ "type": "object",
95
+ "additionalProperties": false,
96
+ "properties": {
97
+ "exclude": {
98
+ "type": "array",
99
+ "items": {
100
+ "type": "string"
101
+ }
102
+ },
103
+ "exclude_files": {
104
+ "type": "array",
105
+ "items": {
106
+ "type": "string"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ },
113
+ "environments": {
114
+ "type": "object",
115
+ "patternProperties": {
116
+ "^.*$": {
117
+ "type": "object"
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Represents the site configuration.
6
+ class Configuration
7
+ include Nanoc::Core::ContractsSupport
8
+
9
+ NONE = Object.new.freeze
10
+
11
+ # The default configuration for a data source. A data source's
12
+ # configuration overrides these options.
13
+ DEFAULT_DATA_SOURCE_CONFIG = {
14
+ type: 'filesystem',
15
+ items_root: '/',
16
+ layouts_root: '/',
17
+ config: {},
18
+ identifier_type: 'full',
19
+ }.freeze
20
+
21
+ # The default configuration for a site. A site's configuration overrides
22
+ # these options: when a {Nanoc::Int::Site} is created with a configuration
23
+ # that lacks some options, the default value will be taken from
24
+ # `DEFAULT_CONFIG`.
25
+ DEFAULT_CONFIG = {
26
+ text_extensions: %w[adoc asciidoc atom css erb haml htm html js less markdown md php rb sass scss tex txt xhtml xml coffee hb handlebars mustache ms slim rdoc].sort,
27
+ lib_dirs: %w[lib],
28
+ commands_dirs: %w[commands],
29
+ output_dir: 'output',
30
+ data_sources: [{}],
31
+ index_filenames: ['index.html'],
32
+ enable_output_diff: false,
33
+ prune: { auto_prune: false, exclude: ['.git', '.hg', '.svn', 'CVS'] },
34
+ string_pattern_type: 'glob',
35
+ action_provider: 'rule_dsl',
36
+ }.freeze
37
+
38
+ # @return [String, nil] The active environment for the configuration
39
+ attr_reader :env_name
40
+
41
+ contract C::None => C::AbsolutePathString
42
+ attr_reader :dir
43
+
44
+ # Configuration environments property key
45
+ ENVIRONMENTS_CONFIG_KEY = :environments
46
+ NANOC_ENV = 'NANOC_ENV'
47
+ NANOC_ENV_DEFAULT = 'default'
48
+
49
+ contract C::KeywordArgs[hash: C::Optional[Hash], env_name: C::Maybe[String], dir: C::AbsolutePathString] => C::Any
50
+ def initialize(hash: {}, dir:, env_name: nil)
51
+ @env_name = env_name
52
+ @wrapped = hash.__nanoc_symbolize_keys_recursively
53
+ @dir = dir
54
+
55
+ validate
56
+ end
57
+
58
+ contract C::None => self
59
+ def with_defaults
60
+ new_wrapped = DEFAULT_CONFIG.merge(@wrapped)
61
+ new_wrapped[:data_sources] = new_wrapped[:data_sources].map do |ds|
62
+ DEFAULT_DATA_SOURCE_CONFIG.merge(ds)
63
+ end
64
+
65
+ self.class.new(hash: new_wrapped, dir: @dir, env_name: @env_name)
66
+ end
67
+
68
+ def with_environment
69
+ return self unless @wrapped.key?(ENVIRONMENTS_CONFIG_KEY)
70
+
71
+ # Set active environment
72
+ env_name = @env_name || ENV.fetch(NANOC_ENV, NANOC_ENV_DEFAULT)
73
+
74
+ # Load given environment configuration
75
+ env_config = @wrapped[ENVIRONMENTS_CONFIG_KEY].fetch(env_name.to_sym, {})
76
+
77
+ self.class.new(hash: @wrapped, dir: @dir, env_name: env_name).merge(env_config)
78
+ end
79
+
80
+ contract C::None => Hash
81
+ def to_h
82
+ @wrapped
83
+ end
84
+
85
+ # For compat
86
+ contract C::None => Hash
87
+ def attributes
88
+ to_h
89
+ end
90
+
91
+ contract C::Any => C::Bool
92
+ def key?(key)
93
+ @wrapped.key?(key)
94
+ end
95
+
96
+ contract C::Any => C::Any
97
+ def [](key)
98
+ @wrapped[key]
99
+ end
100
+
101
+ contract C::Args[C::Any] => C::Any
102
+ def dig(*keys)
103
+ @wrapped.dig(*keys)
104
+ end
105
+
106
+ contract C::Any, C::Maybe[C::Any], C::Maybe[C::Func[C::None => C::Any]] => C::Any
107
+ def fetch(key, fallback = NONE, &_block)
108
+ @wrapped.fetch(key) do
109
+ if !fallback.equal?(NONE)
110
+ fallback
111
+ elsif block_given?
112
+ yield(key)
113
+ else
114
+ raise KeyError, "key not found: #{key.inspect}"
115
+ end
116
+ end
117
+ end
118
+
119
+ contract C::Any, C::Any => C::Any
120
+ def []=(key, value)
121
+ @wrapped[key] = value
122
+ end
123
+
124
+ contract C::Or[Hash, self] => self
125
+ def merge(hash)
126
+ self.class.new(hash: merge_recursively(@wrapped, hash.to_h), dir: @dir, env_name: @env_name)
127
+ end
128
+
129
+ contract C::Any => self
130
+ def without(key)
131
+ self.class.new(hash: @wrapped.reject { |k, _v| k == key }, dir: @dir, env_name: @env_name)
132
+ end
133
+
134
+ contract C::Any => self
135
+ def update(hash)
136
+ @wrapped.update(hash)
137
+ self
138
+ end
139
+
140
+ contract C::Func[C::Any, C::Any => C::Any] => self
141
+ def each
142
+ @wrapped.each { |k, v| yield(k, v) }
143
+ self
144
+ end
145
+
146
+ contract C::None => self
147
+ def freeze
148
+ super
149
+ @wrapped.__nanoc_freeze_recursively
150
+ self
151
+ end
152
+
153
+ contract C::None => C::AbsolutePathString
154
+ def output_dir
155
+ make_absolute(self[:output_dir]).freeze
156
+ end
157
+
158
+ contract C::None => Symbol
159
+ def action_provider
160
+ self[:action_provider].to_sym
161
+ end
162
+
163
+ contract C::None => C::IterOf[C::AbsolutePathString]
164
+ def output_dirs
165
+ envs = @wrapped.fetch(ENVIRONMENTS_CONFIG_KEY, {})
166
+ res = [output_dir] + envs.values.map { |v| make_absolute(v[:output_dir]) }
167
+ res.uniq.compact
168
+ end
169
+
170
+ # Returns an object that can be used for uniquely identifying objects.
171
+ #
172
+ # @return [Object] An unique reference to this object
173
+ def reference
174
+ 'configuration'
175
+ end
176
+
177
+ def inspect
178
+ "<#{self.class}>"
179
+ end
180
+
181
+ private
182
+
183
+ def make_absolute(path)
184
+ path && @dir && File.absolute_path(path, @dir).encode('UTF-8')
185
+ end
186
+
187
+ def merge_recursively(config1, config2)
188
+ config1.merge(config2) do |_, value1, value2|
189
+ if value1.is_a?(Hash) && value2.is_a?(Hash)
190
+ merge_recursively(value1, value2)
191
+ else
192
+ value2
193
+ end
194
+ end
195
+ end
196
+
197
+ def validate
198
+ dir = File.dirname(__FILE__)
199
+ schema_data = JSON.parse(File.read(dir + '/configuration-schema.json'))
200
+ schema = JsonSchema.parse!(schema_data)
201
+ schema.expand_references!
202
+ schema.validate!(@wrapped.__nanoc_stringify_keys_recursively)
203
+ end
204
+ end
205
+ end
206
+ end