nanoc-core 4.11.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class Content
6
+ include Nanoc::Core::ContractsSupport
7
+
8
+ contract C::None => C::Maybe[String]
9
+ attr_reader :filename
10
+
11
+ contract C::Maybe[String] => C::Any
12
+ def initialize(filename)
13
+ if filename && Pathname.new(filename).relative?
14
+ raise ArgumentError, 'Content filename is not absolute'
15
+ end
16
+
17
+ @filename = filename
18
+ end
19
+
20
+ contract C::None => self
21
+ def freeze
22
+ super
23
+ @filename.freeze
24
+ self
25
+ end
26
+
27
+ contract C::Or[self, String, Proc], C::KeywordArgs[binary: C::Optional[C::Bool], filename: C::Optional[C::Maybe[String]]] => self
28
+ def self.create(content, binary: false, filename: nil)
29
+ if content.nil?
30
+ raise ArgumentError, 'Cannot create nil content'
31
+ elsif content.is_a?(Nanoc::Core::Content)
32
+ content
33
+ elsif binary
34
+ Nanoc::Core::BinaryContent.new(content)
35
+ else
36
+ Nanoc::Core::TextualContent.new(content, filename: filename)
37
+ end
38
+ end
39
+
40
+ contract C::None => C::Bool
41
+ def binary?
42
+ raise NotImplementedError
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Provides a context and a binding for use in filters such as the ERB and
6
+ # Haml ones.
7
+ class Context
8
+ # Creates a new context based off the contents of the hash.
9
+ #
10
+ # Each pair in the hash will be converted to an instance variable and an
11
+ # instance method. For example, passing the hash `{ :foo => 'bar' }` will
12
+ # cause `@foo` to have the value `"bar"`, and the instance method `#foo`
13
+ # to return the same value `"bar"`.
14
+ #
15
+ # @param [Hash] hash A list of key-value pairs to make available
16
+ #
17
+ # @example Defining a context and accessing values
18
+ #
19
+ # context = Nanoc::Core::Context.new(
20
+ # :name => 'Max Payne',
21
+ # :location => 'in a cheap motel'
22
+ # )
23
+ # context.instance_eval do
24
+ # "I am #{name} and I am hiding #{@location}."
25
+ # end
26
+ # # => "I am Max Payne and I am hiding in a cheap motel."
27
+ def initialize(hash)
28
+ hash.each_pair do |key, value|
29
+ instance_variable_set('@' + key.to_s, value)
30
+ end
31
+ end
32
+
33
+ # Returns a binding for this instance.
34
+ #
35
+ # @return [Binding] A binding for this instance
36
+ # rubocop:disable Naming/AccessorMethodName
37
+ def get_binding
38
+ binding
39
+ end
40
+ # rubocop:enable Naming/AccessorMethodName
41
+
42
+ def method_missing(method, *args, &blk)
43
+ ivar_name = '@' + method.to_s
44
+ if instance_variable_defined?(ivar_name)
45
+ instance_variable_get(ivar_name)
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ def respond_to_missing?(method, include_all)
52
+ ivar_name = '@' + method.to_s
53
+
54
+ valid_ivar_name =
55
+ if defined?(Contracts)
56
+ ivar_name =~ /\A@[A-Za-z_]+\z/
57
+ else
58
+ true # probably good enough
59
+ end
60
+
61
+ (valid_ivar_name && instance_variable_defined?(ivar_name)) || super
62
+ end
63
+
64
+ def include(mod)
65
+ metaclass = class << self; self; end
66
+ metaclass.instance_eval { include(mod) }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module ContractsSupport
6
+ class Ignorer
7
+ include Singleton
8
+
9
+ def method_missing(*_args) # rubocop:disable Style/MethodMissingSuper
10
+ self
11
+ end
12
+
13
+ def respond_to_missing?(*_args)
14
+ true
15
+ end
16
+ end
17
+
18
+ module DisabledContracts
19
+ Any = Ignorer.instance
20
+ Bool = Ignorer.instance
21
+ Num = Ignorer.instance
22
+ KeywordArgs = Ignorer.instance
23
+ Args = Ignorer.instance
24
+ Optional = Ignorer.instance
25
+ Maybe = Ignorer.instance
26
+ None = Ignorer.instance
27
+ ArrayOf = Ignorer.instance
28
+ Or = Ignorer.instance
29
+ Func = Ignorer.instance
30
+ RespondTo = Ignorer.instance
31
+ Named = Ignorer.instance
32
+ IterOf = Ignorer.instance
33
+ HashOf = Ignorer.instance
34
+ AbsolutePathString = Ignorer.instance
35
+
36
+ def contract(*args); end
37
+ end
38
+
39
+ module EnabledContracts
40
+ class AbstractContract
41
+ def self.[](*vals)
42
+ new(*vals)
43
+ end
44
+ end
45
+
46
+ class Named < AbstractContract
47
+ def initialize(name)
48
+ @name = name
49
+ end
50
+
51
+ def valid?(val)
52
+ val.is_a?(Kernel.const_get(@name))
53
+ end
54
+
55
+ def inspect
56
+ "#{self.class}(#{@name})"
57
+ end
58
+ end
59
+
60
+ class IterOf < AbstractContract
61
+ def initialize(contract)
62
+ @contract = contract
63
+ end
64
+
65
+ def valid?(val)
66
+ val.respond_to?(:each) && val.all? { |v| Contract.valid?(v, @contract) }
67
+ end
68
+
69
+ def inspect
70
+ "#{self.class}(#{@contract})"
71
+ end
72
+ end
73
+
74
+ class AbsolutePathString < AbstractContract
75
+ def self.valid?(val)
76
+ val.is_a?(String) && Pathname.new(val).absolute?
77
+ end
78
+ end
79
+
80
+ def contract(*args)
81
+ Contract(*args)
82
+ end
83
+ end
84
+
85
+ def self.setup_once
86
+ @_contracts_support__setup ||= false
87
+ return @_contracts_support__should_enable if @_contracts_support__setup
88
+
89
+ @_contracts_support__setup = true
90
+
91
+ contracts_loadable =
92
+ begin
93
+ require 'contracts'
94
+ true
95
+ rescue LoadError
96
+ false
97
+ end
98
+
99
+ @_contracts_support__should_enable = contracts_loadable && !ENV.key?('DISABLE_CONTRACTS')
100
+
101
+ if @_contracts_support__should_enable
102
+ # FIXME: ugly
103
+ ::Contracts.const_set('Named', EnabledContracts::Named)
104
+ ::Contracts.const_set('IterOf', EnabledContracts::IterOf)
105
+ ::Contracts.const_set('AbsolutePathString', EnabledContracts::AbsolutePathString)
106
+ end
107
+
108
+ @_contracts_support__should_enable
109
+ end
110
+
111
+ def self.enabled?
112
+ setup_once
113
+ end
114
+
115
+ def self.included(base)
116
+ should_enable = setup_once
117
+
118
+ if should_enable
119
+ unless base.include?(::Contracts::Core)
120
+ base.include(::Contracts::Core)
121
+ base.extend(EnabledContracts)
122
+ base.const_set('C', ::Contracts)
123
+ end
124
+ else
125
+ base.extend(DisabledContracts)
126
+ base.const_set('C', DisabledContracts)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module CoreExt
6
+ module ArrayExtensions
7
+ # Returns a new array where all items' keys are recursively converted to
8
+ # symbols by calling {Nanoc::ArrayExtensions#__nanoc_symbolize_keys_recursively} or
9
+ # {Nanoc::HashExtensions#__nanoc_symbolize_keys_recursively}.
10
+ #
11
+ # @return [Array] The converted array
12
+ def __nanoc_symbolize_keys_recursively
13
+ array = []
14
+ each do |element|
15
+ array << (element.respond_to?(:__nanoc_symbolize_keys_recursively) ? element.__nanoc_symbolize_keys_recursively : element)
16
+ end
17
+ array
18
+ end
19
+
20
+ def __nanoc_stringify_keys_recursively
21
+ array = []
22
+ each do |element|
23
+ array << (element.respond_to?(:__nanoc_stringify_keys_recursively) ? element.__nanoc_stringify_keys_recursively : element)
24
+ end
25
+ array
26
+ end
27
+
28
+ # Freezes the contents of the array, as well as all array elements. The
29
+ # array elements will be frozen using {#__nanoc_freeze_recursively} if they respond
30
+ # to that message, or #freeze if they do not.
31
+ #
32
+ # @see Hash#__nanoc_freeze_recursively
33
+ #
34
+ # @return [void]
35
+ def __nanoc_freeze_recursively
36
+ return if frozen?
37
+
38
+ freeze
39
+ each do |value|
40
+ if value.respond_to?(:__nanoc_freeze_recursively)
41
+ value.__nanoc_freeze_recursively
42
+ else
43
+ value.freeze
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ class Array
53
+ include Nanoc::Core::CoreExt::ArrayExtensions
54
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module CoreExt
6
+ module HashExtensions
7
+ # Returns a new hash where all keys are recursively converted to symbols by
8
+ # calling {Nanoc::ArrayExtensions#__nanoc_symbolize_keys_recursively} or
9
+ # {Nanoc::HashExtensions#__nanoc_symbolize_keys_recursively}.
10
+ #
11
+ # @return [Hash] The converted hash
12
+ def __nanoc_symbolize_keys_recursively
13
+ hash = {}
14
+ each_pair do |key, value|
15
+ new_key = key.respond_to?(:to_sym) ? key.to_sym : key
16
+ new_value = value.respond_to?(:__nanoc_symbolize_keys_recursively) ? value.__nanoc_symbolize_keys_recursively : value
17
+ hash[new_key] = new_value
18
+ end
19
+ hash
20
+ end
21
+
22
+ def __nanoc_stringify_keys_recursively
23
+ hash = {}
24
+ each_pair do |key, value|
25
+ new_key = key.is_a?(Symbol) ? key.to_s : key
26
+ new_value = value.respond_to?(:__nanoc_stringify_keys_recursively) ? value.__nanoc_stringify_keys_recursively : value
27
+ hash[new_key] = new_value
28
+ end
29
+ hash
30
+ end
31
+
32
+ # Freezes the contents of the hash, as well as all hash values. The hash
33
+ # values will be frozen using {#__nanoc_freeze_recursively} if they respond to
34
+ # that message, or #freeze if they do not.
35
+ #
36
+ # @see Array#__nanoc_freeze_recursively
37
+ #
38
+ # @return [void]
39
+ def __nanoc_freeze_recursively
40
+ return if frozen?
41
+
42
+ freeze
43
+ each_pair do |_key, value|
44
+ if value.respond_to?(:__nanoc_freeze_recursively)
45
+ value.__nanoc_freeze_recursively
46
+ else
47
+ value.freeze
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ class Hash
57
+ include Nanoc::Core::CoreExt::HashExtensions
58
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module CoreExt
6
+ module StringExtensions
7
+ # Transforms string into an actual identifier
8
+ #
9
+ # @return [String] The identifier generated from the receiver
10
+ def __nanoc_cleaned_identifier
11
+ "/#{self}/".gsub(/^\/+|\/+$/, '/')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ class String
19
+ include Nanoc::Core::CoreExt::StringExtensions
20
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Responsible for loading site data. It is the (abstract) superclass for all
6
+ # data sources. Subclasses must at least implement the data reading methods
7
+ # ({#items} and {#layouts}).
8
+ #
9
+ # Apart from the methods for loading and storing data, there are the {#up}
10
+ # and {#down} methods for bringing up and tearing down the connection to the
11
+ # data source. These should be overridden in subclasses. The {#loading}
12
+ # method wraps {#up} and {#down}. {#loading} is a convenience method for the
13
+ # more low-level methods {#use} and {#unuse}, which respectively increment
14
+ # and decrement the reference count; when the reference count goes from 0 to
15
+ # 1, the data source will be loaded ({#up} will be called) and when the
16
+ # reference count goes from 1 to 0, the data source will be unloaded
17
+ # ({#down} will be called).
18
+ #
19
+ # @abstract Subclasses should at least implement {#items} and {#layouts}.
20
+ class DataSource
21
+ # @return [String] The root path where items returned by this data source
22
+ # should be mounted.
23
+ attr_reader :items_root
24
+
25
+ # @return [String] The root path where layouts returned by this data
26
+ # source should be mounted.
27
+ attr_reader :layouts_root
28
+
29
+ # @return [Hash] The configuration for this data source. For example,
30
+ # online data sources could contain authentication details.
31
+ attr_reader :config
32
+
33
+ extend DDPlugin::Plugin
34
+
35
+ def initialize(site_config, items_root, layouts_root, config)
36
+ @site_config = site_config
37
+ @items_root = items_root
38
+ @layouts_root = layouts_root
39
+ @config = config || {}
40
+
41
+ @references = 0
42
+ end
43
+
44
+ # Marks the data source as used by the caller.
45
+ #
46
+ # Calling this method increases the internal reference count. When the
47
+ # data source is used for the first time (first {#use} call), the data
48
+ # source will be loaded ({#up} will be called).
49
+ #
50
+ # @return [void]
51
+ def use
52
+ up if @references.zero?
53
+ @references += 1
54
+ end
55
+
56
+ # Marks the data source as unused by the caller.
57
+ #
58
+ # Calling this method decreases the internal reference count. When the
59
+ # reference count reaches zero, i.e. when the data source is not used any
60
+ # more, the data source will be unloaded ({#down} will be called).
61
+ #
62
+ # @return [void]
63
+ def unuse
64
+ @references -= 1
65
+ down if @references.zero?
66
+ end
67
+
68
+ # Brings up the connection to the data. Depending on the way data is
69
+ # stored, this may not be necessary. This is the ideal place to connect to
70
+ # the database, for example.
71
+ #
72
+ # Subclasses may override this method, but are not required to do so; the
73
+ # default implementation simply does nothing.
74
+ #
75
+ # @return [void]
76
+ def up; end
77
+
78
+ # Brings down the connection to the data. This method should undo the
79
+ # effects of the {#up} method. For example, a database connection
80
+ # established in {#up} should be closed in this method.
81
+ #
82
+ # Subclasses may override this method, but are not required to do so; the
83
+ # default implementation simply does nothing.
84
+ #
85
+ # @return [void]
86
+ def down; end
87
+
88
+ # Returns the collection of items (represented by {Nanoc::Core::Item}) in
89
+ # this site. The default implementation simply returns an empty array.
90
+ #
91
+ # Subclasses should not prepend `items_root` to the item's identifiers, as
92
+ # this will be done automatically.
93
+ #
94
+ # Subclasses may override this method, but are not required to do so; the
95
+ # default implementation simply does nothing.
96
+ #
97
+ # @return [Enumerable] The collection of items
98
+ def items
99
+ []
100
+ end
101
+
102
+ # @api private
103
+ def item_changes
104
+ warn "Caution: Data source #{self.class.identifier.inspect} does not implement #item_changes; live compilation will not pick up changes in this data source."
105
+ Enumerator.new { |_y| sleep }
106
+ end
107
+
108
+ # @api private
109
+ def layout_changes
110
+ warn "Caution: Data source #{self.class.identifier.inspect} does not implement #layout_changes; live compilation will not pick up changes in this data source."
111
+ Enumerator.new { |_y| sleep }
112
+ end
113
+
114
+ # Returns the collection of layouts (represented by {Nanoc::Core::Layout}) in
115
+ # this site. The default implementation simply returns an empty array.
116
+ #
117
+ # Subclasses should prepend `layout_root` to the layout's identifiers,
118
+ # since this is not done automatically.
119
+ #
120
+ # Subclasses may override this method, but are not required to do so; the
121
+ # default implementation simply does nothing.
122
+ #
123
+ # @return [Enumerable] The collection of layouts
124
+ def layouts
125
+ []
126
+ end
127
+
128
+ # Creates a new in-memory item instance. This is intended for use within
129
+ # the {#items} method.
130
+ #
131
+ # @param [String, Proc] content The uncompiled item content
132
+ # (if it is a textual item) or the path to the filename containing the
133
+ # content (if it is a binary item).
134
+ #
135
+ # @param [Hash, Proc] attributes A hash containing this item's attributes.
136
+ #
137
+ # @param [String] identifier This item's identifier.
138
+ #
139
+ # @param [Boolean] binary Whether or not this item is binary
140
+ #
141
+ # @param [String, nil] checksum_data
142
+ #
143
+ # @param [String, nil] content_checksum_data
144
+ #
145
+ # @param [String, nil] attributes_checksum_data
146
+ def new_item(content, attributes, identifier, binary: false, checksum_data: nil, content_checksum_data: nil, attributes_checksum_data: nil)
147
+ content = Nanoc::Core::Content.create(content, binary: binary)
148
+ Nanoc::Core::Item.new(content, attributes, identifier, checksum_data: checksum_data, content_checksum_data: content_checksum_data, attributes_checksum_data: attributes_checksum_data)
149
+ end
150
+
151
+ # Creates a new in-memory layout instance. This is intended for use within
152
+ # the {#layouts} method.
153
+ #
154
+ # @param [String] raw_content The raw content of this layout.
155
+ #
156
+ # @param [Hash] attributes A hash containing this layout's attributes.
157
+ #
158
+ # @param [String] identifier This layout's identifier.
159
+ #
160
+ # @param [String, nil] checksum_data
161
+ #
162
+ # @param [String, nil] content_checksum_data
163
+ #
164
+ # @param [String, nil] attributes_checksum_data
165
+ def new_layout(raw_content, attributes, identifier, checksum_data: nil, content_checksum_data: nil, attributes_checksum_data: nil)
166
+ Nanoc::Core::Layout.new(raw_content, attributes, identifier, checksum_data: checksum_data, content_checksum_data: content_checksum_data, attributes_checksum_data: attributes_checksum_data)
167
+ end
168
+ end
169
+ end
170
+ end