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
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class ItemRep
6
+ include Nanoc::Core::ContractsSupport
7
+
8
+ contract C::None => C::Bool
9
+ attr_accessor :compiled
10
+ alias compiled? compiled
11
+
12
+ contract C::None => C::HashOf[Symbol => C::IterOf[String]]
13
+ attr_reader :raw_paths
14
+
15
+ contract C::None => C::HashOf[Symbol => C::IterOf[String]]
16
+ attr_reader :paths
17
+
18
+ contract C::None => Nanoc::Core::Item
19
+ attr_reader :item
20
+
21
+ contract C::None => Symbol
22
+ attr_reader :name
23
+
24
+ contract C::None => C::IterOf[C::Named['Nanoc::Core::SnapshotDef']]
25
+ attr_accessor :snapshot_defs
26
+
27
+ contract C::None => C::Bool
28
+ attr_accessor :modified
29
+ alias modified? modified
30
+
31
+ contract Nanoc::Core::Item, Symbol => C::Any
32
+ def initialize(item, name)
33
+ # Set primary attributes
34
+ @item = item
35
+ @name = name
36
+
37
+ # Set default attributes
38
+ @raw_paths = {}
39
+ @paths = {}
40
+ @snapshot_defs = []
41
+
42
+ # Reset flags
43
+ @compiled = false
44
+ @modified = false
45
+ end
46
+
47
+ contract C::HashOf[Symbol => C::IterOf[C::AbsolutePathString]] => C::HashOf[Symbol => C::IterOf[C::AbsolutePathString]]
48
+ def raw_paths=(val)
49
+ @raw_paths = val
50
+ end
51
+
52
+ contract C::HashOf[Symbol => C::IterOf[String]] => C::HashOf[Symbol => C::IterOf[String]]
53
+ def paths=(val)
54
+ @paths = val
55
+ end
56
+
57
+ contract Symbol => C::Bool
58
+ def snapshot?(name)
59
+ snapshot_defs.any? { |sd| sd.name == name }
60
+ end
61
+
62
+ contract C::KeywordArgs[snapshot: C::Optional[Symbol]] => C::Maybe[String]
63
+ # Returns the item rep’s raw path. It includes the path to the output
64
+ # directory and the full filename.
65
+ def raw_path(snapshot: :last)
66
+ @raw_paths.fetch(snapshot, []).first
67
+ end
68
+
69
+ contract C::KeywordArgs[snapshot: C::Optional[Symbol]] => C::Maybe[String]
70
+ # Returns the item rep’s path, as used when being linked to. It starts
71
+ # with a slash and it is relative to the output directory. It does not
72
+ # include the path to the output directory. It will not include the
73
+ # filename if the filename is an index filename.
74
+ def path(snapshot: :last)
75
+ @paths.fetch(snapshot, []).first
76
+ end
77
+
78
+ # Returns an object that can be used for uniquely identifying objects.
79
+ def reference
80
+ "item_rep:#{item.identifier}:#{name}"
81
+ end
82
+
83
+ def to_s
84
+ "#{item.identifier} (rep name #{name.inspect})"
85
+ end
86
+
87
+ def inspect
88
+ "<#{self.class} name=\"#{name}\" raw_path=\"#{raw_path}\" item.identifier=\"#{item.identifier}\">"
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class Layout < ::Nanoc::Core::Document
6
+ def reference
7
+ "layout:#{identifier}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class LayoutCollection < IdentifiableCollection
6
+ def initialize(config, objects = [])
7
+ initialize_basic(config, objects, 'layouts')
8
+ end
9
+
10
+ def reference
11
+ 'layouts'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Holds a value that might be generated lazily.
6
+ class LazyValue
7
+ include Nanoc::Core::ContractsSupport
8
+
9
+ # Takes a value or a proc to generate the value
10
+ def initialize(value_or_proc)
11
+ @value = { raw: value_or_proc }
12
+ end
13
+
14
+ # Returns the value, generated when needed
15
+ def value
16
+ if @value.key?(:raw)
17
+ value = @value.delete(:raw)
18
+ @value[:final] = value.respond_to?(:call) ? value.call : value
19
+ @value.__nanoc_freeze_recursively if frozen?
20
+ end
21
+ @value[:final]
22
+ end
23
+
24
+ contract C::Func[C::Any => C::Any] => self
25
+ # Returns a new lazy value that will apply the given transformation when the value is requested.
26
+ def map
27
+ self.class.new(-> { yield(value) })
28
+ end
29
+
30
+ contract C::None => self
31
+ def freeze
32
+ super
33
+ @value.__nanoc_freeze_recursively unless @value[:raw]
34
+ self
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Provides a way to send notifications between objects. It allows blocks
6
+ # associated with a certain notification name to be registered; these blocks
7
+ # will be called when the notification with the given name is posted.
8
+ #
9
+ # It is a slightly different implementation of the Observer pattern; the
10
+ # table of subscribers is not stored in the observable object itself, but in
11
+ # the notification center.
12
+ class NotificationCenter
13
+ DONE = Object.new
14
+ SYNC = Object.new
15
+
16
+ def initialize
17
+ @thread = nil
18
+
19
+ # name => observers dictionary
20
+ @notifications = Hash.new { |hash, name| hash[name] = [] }
21
+
22
+ @queue = Queue.new
23
+
24
+ @sync_queue = Queue.new
25
+ on(SYNC, self) { @sync_queue << true }
26
+ end
27
+
28
+ def start
29
+ @thread ||= Thread.new do
30
+ Thread.current.abort_on_exception = true
31
+
32
+ loop do
33
+ elem = @queue.pop
34
+ break if DONE.equal?(elem)
35
+
36
+ name = elem[0]
37
+ args = elem[1]
38
+
39
+ @notifications[name].each do |observer|
40
+ observer[:block].call(*args)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def stop
47
+ @queue << DONE
48
+ @thread.join
49
+ end
50
+
51
+ def on(name, id = nil, &block)
52
+ @notifications[name] << { id: id, block: block }
53
+ end
54
+
55
+ def remove(name, id)
56
+ @notifications[name].reject! { |i| i[:id] == id }
57
+ end
58
+
59
+ def post(name, *args)
60
+ @queue << [name, args]
61
+ self
62
+ end
63
+
64
+ def sync
65
+ post(SYNC)
66
+ @sync_queue.pop
67
+ end
68
+
69
+ class << self
70
+ def instance
71
+ @_instance ||= new.tap(&:start)
72
+ end
73
+
74
+ def on(name, id = nil, &block)
75
+ instance.on(name, id, &block)
76
+ end
77
+
78
+ def post(name, *args)
79
+ instance.post(name, *args)
80
+ end
81
+
82
+ def remove(name, id)
83
+ instance.remove(name, id)
84
+ end
85
+
86
+ def reset
87
+ instance.stop
88
+ @_instance = nil
89
+ end
90
+
91
+ def sync
92
+ instance.sync
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class Pattern
6
+ include Nanoc::Core::ContractsSupport
7
+
8
+ contract C::Any => self
9
+ def self.from(obj)
10
+ case obj
11
+ when Nanoc::Core::StringPattern, Nanoc::Core::RegexpPattern
12
+ obj
13
+ when String
14
+ Nanoc::Core::StringPattern.new(obj)
15
+ when Regexp
16
+ Nanoc::Core::RegexpPattern.new(obj)
17
+ when Symbol
18
+ Nanoc::Core::StringPattern.new(obj.to_s)
19
+ else
20
+ raise ArgumentError, "Do not know how to convert `#{obj.inspect}` into a Nanoc::Pattern"
21
+ end
22
+ end
23
+
24
+ def initialize(_obj)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def match?(_identifier)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def captures(_identifier)
33
+ raise NotImplementedError
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class ProcessingAction
6
+ def serialize
7
+ raise NotImplementedError.new('Nanoc::ProcessingAction subclasses must implement #serialize and #to_s')
8
+ end
9
+
10
+ def to_s
11
+ raise NotImplementedError.new('Nanoc::ProcessingAction subclasses must implement #serialize and #to_s')
12
+ end
13
+
14
+ def inspect
15
+ format(
16
+ '<%s %s>',
17
+ self.class.to_s,
18
+ serialize[1..-1].map(&:inspect).join(', '),
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module ProcessingActions
6
+ class Filter < Nanoc::Core::ProcessingAction
7
+ # filter :foo
8
+ # filter :foo, params
9
+
10
+ attr_reader :filter_name
11
+ attr_reader :params
12
+
13
+ def initialize(filter_name, params)
14
+ @filter_name = filter_name
15
+ @params = params
16
+ end
17
+
18
+ def serialize
19
+ [:filter, @filter_name, Nanoc::Core::Checksummer.calc(@params)]
20
+ end
21
+
22
+ def to_s
23
+ "filter #{@filter_name.inspect}, #{@params.inspect}"
24
+ end
25
+
26
+ def hash
27
+ self.class.hash ^ filter_name.hash ^ params.hash
28
+ end
29
+
30
+ def ==(other)
31
+ self.class == other.class && filter_name == other.filter_name && params == other.params
32
+ end
33
+
34
+ def eql?(other)
35
+ self == other
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module ProcessingActions
6
+ class Layout < Nanoc::Core::ProcessingAction
7
+ # layout '/foo.erb'
8
+ # layout '/foo.erb', params
9
+
10
+ attr_reader :layout_identifier
11
+ attr_reader :params
12
+
13
+ def initialize(layout_identifier, params)
14
+ @layout_identifier = layout_identifier
15
+ @params = params
16
+ end
17
+
18
+ def serialize
19
+ [:layout, @layout_identifier, Nanoc::Core::Checksummer.calc(@params)]
20
+ end
21
+
22
+ def to_s
23
+ "layout #{@layout_identifier.inspect}, #{@params.inspect}"
24
+ end
25
+
26
+ def hash
27
+ self.class.hash ^ layout_identifier.hash ^ params.hash
28
+ end
29
+
30
+ def ==(other)
31
+ self.class == other.class && layout_identifier == other.layout_identifier && params == other.params
32
+ end
33
+
34
+ def eql?(other)
35
+ self == other
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module ProcessingActions
6
+ class Snapshot < Nanoc::Core::ProcessingAction
7
+ # snapshot :before_layout
8
+ # snapshot :before_layout, path: '/about.md'
9
+
10
+ include Nanoc::Core::ContractsSupport
11
+
12
+ attr_reader :snapshot_names
13
+ attr_reader :paths
14
+
15
+ contract C::IterOf[Symbol], C::IterOf[String] => C::Any
16
+ def initialize(snapshot_names, paths)
17
+ @snapshot_names = snapshot_names
18
+ @paths = paths
19
+ end
20
+
21
+ contract C::None => Array
22
+ def serialize
23
+ [:snapshot, @snapshot_names, true, @paths]
24
+ end
25
+
26
+ contract C::KeywordArgs[snapshot_names: C::Optional[C::IterOf[Symbol]], paths: C::Optional[C::IterOf[String]]] => self
27
+ def update(snapshot_names: [], paths: [])
28
+ self.class.new(@snapshot_names + snapshot_names.to_a, @paths + paths.to_a)
29
+ end
30
+
31
+ contract C::None => String
32
+ def to_s
33
+ "snapshot #{@snapshot_names.inspect}, paths: #{@paths.inspect}"
34
+ end
35
+
36
+ def hash
37
+ self.class.hash ^ snapshot_names.hash ^ paths.hash
38
+ end
39
+
40
+ def ==(other)
41
+ self.class == other.class && snapshot_names == other.snapshot_names && paths == other.paths
42
+ end
43
+
44
+ def eql?(other)
45
+ self == other
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module ProcessingActions
6
+ end
7
+ end
8
+ end
9
+
10
+ require 'nanoc/core/processing_actions/filter'
11
+ require 'nanoc/core/processing_actions/layout'
12
+ require 'nanoc/core/processing_actions/snapshot'
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class RegexpPattern < Pattern
6
+ contract Regexp => C::Any
7
+ def initialize(regexp)
8
+ @regexp = regexp
9
+ end
10
+
11
+ contract C::Or[Nanoc::Core::Identifier, String] => C::Bool
12
+ def match?(identifier)
13
+ (identifier.to_s =~ @regexp) != nil
14
+ end
15
+
16
+ contract C::Or[Nanoc::Core::Identifier, String] => C::Maybe[C::ArrayOf[String]]
17
+ def captures(identifier)
18
+ matches = @regexp.match(identifier.to_s)
19
+ matches&.captures
20
+ end
21
+
22
+ contract C::None => String
23
+ def to_s
24
+ @regexp.to_s
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class SnapshotDef
6
+ include Nanoc::Core::ContractsSupport
7
+
8
+ attr_reader :name
9
+ attr_reader :binary
10
+
11
+ contract Symbol, C::KeywordArgs[binary: C::Optional[C::Bool]] => C::Any
12
+ def initialize(name, binary:)
13
+ @name = name
14
+ @binary = binary
15
+ end
16
+
17
+ def binary?
18
+ @binary
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class StringPattern < Pattern
6
+ MATCH_OPTS = File::FNM_PATHNAME | File::FNM_EXTGLOB
7
+
8
+ contract String => C::Any
9
+ def initialize(string)
10
+ @string = string
11
+ end
12
+
13
+ contract C::Or[Nanoc::Core::Identifier, String] => C::Bool
14
+ def match?(identifier)
15
+ File.fnmatch(@string, identifier.to_s, MATCH_OPTS)
16
+ end
17
+
18
+ contract C::Or[Nanoc::Core::Identifier, String] => nil
19
+ def captures(_identifier)
20
+ nil
21
+ end
22
+
23
+ contract C::None => String
24
+ def to_s
25
+ @string
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class TempFilenameFactory
6
+ # @return [String] The root directory for all temporary filenames
7
+ attr_reader :root_dir
8
+
9
+ # @return [Nanoc::Core::TempFilenameFactory] A common instance
10
+ def self.instance
11
+ @instance ||= new
12
+ end
13
+
14
+ def initialize
15
+ @counts = {}
16
+ @root_dir = Dir.mktmpdir('nanoc')
17
+ end
18
+
19
+ # @param [String] prefix A string prefix to include in the temporary
20
+ # filename, often the type of filename being provided.
21
+ #
22
+ # @return [String] A new unused filename
23
+ def create(prefix)
24
+ count = @counts.fetch(prefix, 0)
25
+ @counts[prefix] = count + 1
26
+
27
+ dirname = File.join(@root_dir, prefix)
28
+ filename = File.join(@root_dir, prefix, count.to_s)
29
+
30
+ FileUtils.mkdir_p(dirname)
31
+
32
+ filename
33
+ end
34
+
35
+ # @param [String] prefix A string prefix that indicates which temporary
36
+ # filenames should be deleted.
37
+ #
38
+ # @return [void]
39
+ def cleanup(prefix)
40
+ path = File.join(@root_dir, prefix)
41
+ if File.exist?(path)
42
+ FileUtils.rm_rf(path)
43
+ end
44
+
45
+ @counts.delete(prefix)
46
+
47
+ if @counts.empty? && File.directory?(@root_dir)
48
+ FileUtils.rm_rf(@root_dir)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class TextualContent < Content
6
+ contract C::None => String
7
+ def string
8
+ @string.value
9
+ end
10
+
11
+ contract C::Or[String, Proc], C::KeywordArgs[filename: C::Optional[C::Maybe[String]]] => C::Any
12
+ def initialize(string, filename: nil)
13
+ super(filename)
14
+ @string = Nanoc::Core::LazyValue.new(string)
15
+ end
16
+
17
+ contract C::None => self
18
+ def freeze
19
+ super
20
+ @string.freeze
21
+ self
22
+ end
23
+
24
+ contract C::None => C::Bool
25
+ def binary?
26
+ false
27
+ end
28
+
29
+ contract C::None => Array
30
+ def marshal_dump
31
+ [filename, string]
32
+ end
33
+
34
+ contract Array => C::Any
35
+ def marshal_load(array)
36
+ @filename = array[0]
37
+ @string = Nanoc::Core::LazyValue.new(array[1])
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ VERSION = '4.11.1'
6
+ end
7
+ end