jekyll-relationships 0.1.0.alpha

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/lib/jekyll-relationships/configuration/debug_setting.rb +117 -0
  3. data/lib/jekyll-relationships/configuration/defaults.rb +54 -0
  4. data/lib/jekyll-relationships/configuration/frontmatter.rb +102 -0
  5. data/lib/jekyll-relationships/configuration/hash_utilities.rb +55 -0
  6. data/lib/jekyll-relationships/configuration/multiple_settings.rb +101 -0
  7. data/lib/jekyll-relationships/configuration/parser.rb +551 -0
  8. data/lib/jekyll-relationships/configuration/prune_rule_settings.rb +101 -0
  9. data/lib/jekyll-relationships/configuration/prune_settings.rb +82 -0
  10. data/lib/jekyll-relationships/configuration/tree_frontmatter.rb +244 -0
  11. data/lib/jekyll-relationships/configuration/tree_settings.rb +74 -0
  12. data/lib/jekyll-relationships/configuration.rb +250 -0
  13. data/lib/jekyll-relationships/debug_logger.rb +104 -0
  14. data/lib/jekyll-relationships/definitions/configured_relationship.rb +61 -0
  15. data/lib/jekyll-relationships/definitions/normal_relationship.rb +51 -0
  16. data/lib/jekyll-relationships/definitions/prune_rule.rb +67 -0
  17. data/lib/jekyll-relationships/definitions/tree_relationship.rb +48 -0
  18. data/lib/jekyll-relationships/documents/registry.rb +136 -0
  19. data/lib/jekyll-relationships/engine/raw_path_state.rb +217 -0
  20. data/lib/jekyll-relationships/engine/relationship_state.rb +251 -0
  21. data/lib/jekyll-relationships/engine/session.rb +187 -0
  22. data/lib/jekyll-relationships/engine/write_back.rb +154 -0
  23. data/lib/jekyll-relationships/engine.rb +228 -0
  24. data/lib/jekyll-relationships/errors.rb +30 -0
  25. data/lib/jekyll-relationships/generators/relationships.rb +27 -0
  26. data/lib/jekyll-relationships/pruning/normal_graph.rb +119 -0
  27. data/lib/jekyll-relationships/pruning/rule_pruner.rb +67 -0
  28. data/lib/jekyll-relationships/pruning/tree_phase.rb +351 -0
  29. data/lib/jekyll-relationships/pruning/tree_provenance.rb +32 -0
  30. data/lib/jekyll-relationships/references/accumulator.rb +185 -0
  31. data/lib/jekyll-relationships/references/template.rb +222 -0
  32. data/lib/jekyll-relationships/resolvers/base.rb +207 -0
  33. data/lib/jekyll-relationships/run_logger.rb +101 -0
  34. data/lib/jekyll-relationships/support/data_path.rb +194 -0
  35. data/lib/jekyll-relationships/support/frontmatter_path.rb +217 -0
  36. data/lib/jekyll-relationships/support/hash_deep_merge.rb +63 -0
  37. data/lib/jekyll-relationships/support/placeholders.rb +21 -0
  38. data/lib/jekyll-relationships/support/string_array.rb +157 -0
  39. data/lib/jekyll-relationships/trees/edge_builder.rb +196 -0
  40. data/lib/jekyll-relationships/trees/graph.rb +454 -0
  41. data/lib/jekyll-relationships/version.rb +11 -0
  42. data/lib/jekyll-relationships.rb +34 -0
  43. data/readme.md +509 -0
  44. metadata +189 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9656850968f173e997fe9e9a0b5500f12682c94d613f84444827bc5e52aa955a
4
+ data.tar.gz: e56a5ec1a9a44b58d2aa03e2066e99f7a4b39df1335c449e5325f6a5599b6871
5
+ SHA512:
6
+ metadata.gz: 010f090f1083c9f8ede2f8a99bd23e05a68b474f6c90b1519a4002c2446bf97eee6256b8cb7858cbd9a7e9aa9def901885fed07290e8a83c995656f71e47afd4
7
+ data.tar.gz: 1f0c0c85af8fd938f7d6d66a15e4dfb2941615057656867702a01b146e10426901969a2498e10d9fe08427d2226fe9d0f3968e50e262560223fff439ebfcda0f
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Plugins
5
+
6
+ module Relationships
7
+
8
+ class Configuration
9
+
10
+ # Normalises the `debug` config into one explicit set of enabled log areas.
11
+ #
12
+ # Supported areas:
13
+ # * `resolution`: relationship-resolution start and finish
14
+ # * `upgrading`: raw reference reading, resolution, and write-back
15
+ # * `mutations`: link and unlink operations
16
+ # * `resolvers`: resolver execution and helper calls
17
+ # * `trees`: tree discovery, edge handling, and tree write-back
18
+ class DebugSetting
19
+ AREAS = {
20
+ 'resolution' => 'relationship-resolution lifecycle',
21
+ 'upgrading' => 'reference upgrading and write-back',
22
+ 'mutations' => 'link and unlink operations',
23
+ 'resolvers' => 'resolver execution and helper calls',
24
+ 'trees' => 'tree discovery, edge handling, and tree write-back'
25
+ }.freeze
26
+
27
+ ALL_AREAS = AREAS.keys.freeze
28
+
29
+ class << self
30
+ # Builds one explicit debug setting from a loose config value.
31
+ def build(value:, string_array:, context:)
32
+ return disabled if value.nil? || false_value?(value)
33
+ return all if true_value?(value)
34
+
35
+ raw_areas = string_array.interpret(value, split: true, flatten: true)
36
+ raise_invalid_value!(context: context) if raw_areas.empty?
37
+
38
+ areas = raw_areas.each_with_object([]) do |raw_area, interpreted_areas|
39
+ normalised_area = normalise_area(raw_area, context: context)
40
+ return all if normalised_area == 'all'
41
+
42
+ interpreted_areas << normalised_area
43
+ end
44
+
45
+ new(areas.uniq)
46
+ end
47
+
48
+ # Returns one disabled debug setting.
49
+ def disabled
50
+ new([])
51
+ end
52
+
53
+ # Returns one debug setting that enables every area.
54
+ def all
55
+ new(ALL_AREAS)
56
+ end
57
+
58
+ private
59
+
60
+ # Returns true when a loose value means "debug everything".
61
+ def true_value?(value)
62
+ value == true || value.to_s.strip.casecmp('true').zero?
63
+ end
64
+
65
+ # Returns true when a loose value means "debug nothing".
66
+ def false_value?(value)
67
+ value == false || value.to_s.strip.casecmp('false').zero?
68
+ end
69
+
70
+ # Normalises one configured area name and validates it.
71
+ def normalise_area(raw_area, context:)
72
+ area = raw_area.to_s.strip.downcase
73
+ raise_invalid_value!(context: context) if area.empty?
74
+ return 'all' if area == 'all'
75
+ return area if AREAS.key?(area)
76
+
77
+ raise ConfigurationError,
78
+ "`#{context}` contains unknown debug area `#{raw_area}`. Supported areas: #{ALL_AREAS.join(', ')}."
79
+ end
80
+
81
+ # Raises one shared error for invalid debug values.
82
+ def raise_invalid_value!(context:)
83
+ raise ConfigurationError,
84
+ "`#{context}` must be true, false, `all`, or a comma-delimited string/array of debug areas: #{ALL_AREAS.join(', ')}."
85
+ end
86
+ end
87
+
88
+ attr_reader :areas
89
+
90
+ # Captures one immutable set of enabled debug areas.
91
+ def initialize(areas)
92
+ @areas = areas.freeze
93
+ end
94
+
95
+ # Returns true when any debug area is enabled, or when one named area is enabled.
96
+ def enabled?(area = nil)
97
+ return !@areas.empty? if area.nil?
98
+
99
+ @areas.include?(normalise_runtime_area(area))
100
+ end
101
+
102
+ private
103
+
104
+ # Normalises one runtime area name and raises on internal typos.
105
+ def normalise_runtime_area(area)
106
+ string_area = area.to_s.strip.downcase
107
+ return string_area if self.class::ALL_AREAS.include?(string_area)
108
+
109
+ raise ArgumentError, "Unknown runtime debug area `#{area}`."
110
+ end
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Plugins
5
+
6
+ module Relationships
7
+
8
+ class Configuration
9
+
10
+ # Holds the built-in configuration defaults used when the site omits them.
11
+ module Defaults
12
+ ENABLED = true
13
+ DEBUG = false
14
+
15
+ FRONTMATTER = {
16
+ 'base' => 'relationships',
17
+ 'primary' => nil,
18
+ 'foreign' => '<collection>',
19
+ 'output' => nil
20
+ }.freeze
21
+
22
+ TREE = {
23
+ 'frontmatter' => {
24
+ 'parent' => 'parent',
25
+ 'child' => 'child',
26
+ 'parents' => 'parents',
27
+ 'children' => 'children',
28
+ 'ancestors' => 'ancestors',
29
+ 'descendants' => 'descendants'
30
+ }.freeze,
31
+ 'max' => {
32
+ 'parents' => -1,
33
+ 'children' => -1
34
+ }.freeze,
35
+ 'url' => false
36
+ }.freeze
37
+
38
+ MULTIPLE = {
39
+ 'mode' => 'drop',
40
+ 'sort' => nil
41
+ }.freeze
42
+
43
+ KEYWORDS = {
44
+ 'self' => 'self',
45
+ 'others' => 'others',
46
+ 'all' => 'all'
47
+ }.freeze
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Plugins
5
+
6
+ module Relationships
7
+
8
+ class Configuration
9
+
10
+ # Encapsulates one merged frontmatter configuration block.
11
+ #
12
+ # Instances resolve `base`, `primary`, `foreign`, and `output` paths so the
13
+ # rest of the engine can work with explicit values only.
14
+ class Frontmatter
15
+ # Builds one frontmatter configuration helper.
16
+ def initialize(raw_config:, string_array:)
17
+ @raw_config = raw_config.is_a?(Hash) ? raw_config : {}
18
+ @string_array = string_array
19
+ end
20
+
21
+ # Returns the configured base path.
22
+ def base
23
+ raw_base = fetch_value('base')
24
+ return '' if raw_base.nil?
25
+
26
+ raw_base.to_s
27
+ end
28
+
29
+ # Returns the resolved primary-key path, or nil for the default key rule.
30
+ def primary_path
31
+ raw_primary = fetch_value('primary')
32
+ return nil if raw_primary.nil?
33
+
34
+ apply_base(raw_primary.to_s)
35
+ end
36
+
37
+ # Returns the resolved foreign input paths for one target collection.
38
+ def foreign_paths_for(to_collection:)
39
+ raw_foreign = fetch_value('foreign')
40
+ paths = @string_array.interpret(raw_foreign, split: true, flatten: true)
41
+ raise ConfigurationError, 'Each normal relationship must define at least one foreign path.' if paths.empty?
42
+
43
+ paths.map do |path|
44
+ string_path = path.to_s.strip
45
+ raise ConfigurationError, 'Foreign paths cannot be blank.' if string_path.empty?
46
+
47
+ resolve_collection_path(path: string_path, to_collection: to_collection)
48
+ end.uniq
49
+ end
50
+
51
+ # Returns the resolved output path, if one is configured.
52
+ def output_path_for(to_collection:)
53
+ raw_output = fetch_value('output')
54
+ return nil if raw_output.nil? || raw_output.to_s.strip.empty?
55
+
56
+ resolve_collection_path(path: raw_output.to_s.strip, to_collection: to_collection)
57
+ end
58
+
59
+ # Returns one arbitrary path with the active base applied.
60
+ def path_with_base(path)
61
+ apply_base(path.to_s)
62
+ end
63
+
64
+ # Returns a cloned frontmatter configuration with one override merged in.
65
+ def merge(raw_override)
66
+ self.class.new(
67
+ raw_config: Configuration::HashUtilities.merge_hash(@raw_config, raw_override),
68
+ string_array: @string_array
69
+ )
70
+ end
71
+
72
+ private
73
+
74
+ # Reads one configured property from the raw hash.
75
+ def fetch_value(key)
76
+ Configuration::HashUtilities.fetch_hash_value(@raw_config, key)
77
+ end
78
+
79
+ # Applies the active base path to one relative path.
80
+ def apply_base(path)
81
+ return path if base.empty?
82
+ return base if path.to_s.empty?
83
+
84
+ "#{base}.#{path}"
85
+ end
86
+
87
+ # Resolves one path that may include the active collection placeholder.
88
+ def resolve_collection_path(path:, to_collection:)
89
+ apply_base(path.gsub(collection_placeholder, to_collection))
90
+ end
91
+
92
+ # Returns the active `<collection>` placeholder token.
93
+ def collection_placeholder
94
+ Jekyll::Plugins::Relationships::Support::Placeholders::COLLECTION
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Plugins
5
+
6
+ module Relationships
7
+
8
+ class Configuration
9
+
10
+ # Provides shared hash-reading helpers for configuration components.
11
+ module HashUtilities
12
+ module_function
13
+
14
+ # Returns true when a string- or symbol-keyed hash includes one key.
15
+ def hash_key?(hash, key)
16
+ return false unless hash.is_a?(Hash)
17
+
18
+ hash.key?(key) || hash.key?(key.to_s) || hash.key?(key.to_sym)
19
+ end
20
+
21
+ # Reads one string- or symbol-keyed hash value.
22
+ def fetch_hash_value(hash, key)
23
+ return nil unless hash.is_a?(Hash)
24
+
25
+ Jekyll::Plugins::Relationships::Support::FrontmatterPath.read_hash(hash, key)
26
+ end
27
+
28
+ # Deep-merges two hashes without mutating either.
29
+ def merge_hash(base_hash, override_hash)
30
+ Jekyll::Plugins::Relationships::Support.hash_deep_merge(base_hash, override_hash)
31
+ end
32
+
33
+ # Converts one config value to an integer with a helpful error.
34
+ def integer_value(value, context)
35
+ return value if value.is_a?(Integer)
36
+ return value.to_i if value.is_a?(String) && value.strip.match?(/\A-?\d+\z/)
37
+
38
+ raise ConfigurationError, "`#{context}` must be an integer."
39
+ end
40
+
41
+ # Converts one config value to a boolean with a helpful error.
42
+ def boolean_value(value, context)
43
+ return value if value == true || value == false
44
+ return true if value.is_a?(String) && value.strip.casecmp('true').zero?
45
+ return false if value.is_a?(String) && value.strip.casecmp('false').zero?
46
+
47
+ raise ConfigurationError, "`#{context}` must be true or false."
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Plugins
5
+
6
+ module Relationships
7
+
8
+ class Configuration
9
+
10
+ # Encapsulates the configured handling for duplicate normal relationships.
11
+ #
12
+ # The selected mode determines whether duplicates are counted, dropped, or
13
+ # preserved, while the optional sort controls the final output order in count
14
+ # mode only.
15
+ class MultipleSettings
16
+ attr_reader :mode, :sort
17
+
18
+ # Builds one multiple-settings helper from raw configuration.
19
+ def initialize(raw_config:)
20
+ config = normalise_config(raw_config)
21
+ @mode = normalise_mode(config['mode'])
22
+ @sort = normalise_sort(config['sort'])
23
+ validate!
24
+ end
25
+
26
+ # Returns true when duplicate links should aggregate into counts.
27
+ def count?
28
+ @mode == 'count'
29
+ end
30
+
31
+ # Returns true when duplicate links should be dropped.
32
+ def drop?
33
+ @mode == 'drop'
34
+ end
35
+
36
+ # Returns true when duplicate links should be preserved individually.
37
+ def keep?
38
+ @mode == 'keep'
39
+ end
40
+
41
+ # Returns true when counted output should be sorted in ascending order.
42
+ def sort_ascending?
43
+ @sort == 'asc'
44
+ end
45
+
46
+ # Returns true when counted output should be sorted in descending order.
47
+ def sort_descending?
48
+ @sort == 'desc'
49
+ end
50
+
51
+ private
52
+
53
+ # Converts the loose raw value into a normalised hash.
54
+ def normalise_config(raw_config)
55
+ case raw_config
56
+ when nil
57
+ Defaults::MULTIPLE
58
+ when String, Symbol
59
+ { 'mode' => raw_config.to_s }
60
+ when Hash
61
+ Configuration::HashUtilities.merge_hash(Defaults::MULTIPLE, raw_config)
62
+ else
63
+ raise ConfigurationError, '`relationships.multiple` must be a string or hash.'
64
+ end
65
+ end
66
+
67
+ # Resolves one configured duplicate-handling mode.
68
+ def normalise_mode(raw_mode)
69
+ string_mode = raw_mode.to_s.strip.downcase
70
+ string_mode = Defaults::MULTIPLE.fetch('mode') if string_mode.empty?
71
+
72
+ return string_mode if %w[count drop keep].include?(string_mode)
73
+
74
+ raise ConfigurationError, "Unsupported relationships.multiple mode `#{raw_mode}`."
75
+ end
76
+
77
+ # Resolves one configured count-sort direction.
78
+ def normalise_sort(raw_sort)
79
+ return nil if raw_sort.nil?
80
+
81
+ string_sort = raw_sort.to_s.strip.downcase
82
+ return nil if string_sort.empty?
83
+ return 'asc' if %w[asc ascending].include?(string_sort)
84
+ return 'desc' if %w[desc descending].include?(string_sort)
85
+
86
+ raise ConfigurationError, "Unsupported relationships.multiple.sort value `#{raw_sort}`."
87
+ end
88
+
89
+ # Raises for mode combinations that are not meaningful.
90
+ def validate!
91
+ return if @sort.nil? || count?
92
+
93
+ raise ConfigurationError, '`relationships.multiple.sort` is only supported when `relationships.multiple` is `count`.'
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+ end