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,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