ro 5.1.1 → 5.2.0
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.
- checksums.yaml +4 -4
- data/lib/ro/_lib.rb +1 -1
- data/lib/ro/collection.rb +47 -11
- data/lib/ro/migrator.rb +6 -4
- data/lib/ro/node.rb +15 -8
- data/ro.gemspec +78 -1
- data/test/integration/dual_structure_test.rb +232 -0
- data/test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/assets-only/assets/test.txt +1 -0
- data/test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760949758.backup.20251020084238/posts/assets-only/assets/test.txt +1 -0
- data/test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/assets-only/assets/test.txt +1 -0
- data/test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760949808.backup.20251020084328/posts/assets-only/assets/test.txt +1 -0
- data/test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/assets-only/assets/test.txt +1 -0
- data/test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760982056.backup.20251020174056/posts/assets-only/assets/test.txt +1 -0
- data/test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/new_structure_test_1760949758/mixed/test-json.json +5 -0
- data/test/tmp/new_structure_test_1760949758/mixed/test-yaml.yml +3 -0
- data/test/tmp/new_structure_test_1760949758/posts/metadata-only.yml +7 -0
- data/test/tmp/new_structure_test_1760949758/posts/nested-test/assets/subdirectory/image.png +2 -0
- data/test/tmp/new_structure_test_1760949758/posts/nested-test.yml +7 -0
- data/test/tmp/new_structure_test_1760949758/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/new_structure_test_1760949758/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/new_structure_test_1760949758/posts/sample-post.yml +7 -0
- data/test/unit/collection_test.rb +13 -12
- metadata +35 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a38ecc039006ed4a514cf2e7ef7704b571911ab495100939f858abab5018ede2
|
|
4
|
+
data.tar.gz: 0de7ade28c2f781b79cb7320ef4a28cba44c1d7c5551281ec7352c88efdc8885
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50b5cb23987b64672b73a5e9437dd922b901c0708639ea145f70a417888cbd98cdccbfb20329d74b012223709c93b901f238189f864ebe59f305669cdcdfa186
|
|
7
|
+
data.tar.gz: 528110f18aeb57caff429b4d27f8fe18cab2f8e6043bd25a9f9e28ff2cc300fba280a2c4e8c4ed4acbd68b9334e4ff0ec73dad7c30b675dfb149459b54c06c2c
|
data/lib/ro/_lib.rb
CHANGED
data/lib/ro/collection.rb
CHANGED
|
@@ -34,18 +34,39 @@ module Ro
|
|
|
34
34
|
Node.new(path)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
# T021: Scan for metadata files in new structure
|
|
37
|
+
# T021: Scan for metadata files in both new and old structure formats
|
|
38
|
+
# Returns array of hashes: [{id: 'node-id', path: Path, type: :new|:old}]
|
|
38
39
|
def metadata_files
|
|
39
40
|
extensions = %w[yml yaml json toml]
|
|
40
|
-
|
|
41
|
+
metadata_map = {}
|
|
41
42
|
|
|
43
|
+
# First, scan for new structure: collection-level metadata files (e.g., posts/ara.yml)
|
|
42
44
|
extensions.each do |ext|
|
|
43
45
|
@path.glob("*.#{ext}").each do |file|
|
|
44
|
-
|
|
46
|
+
next unless file.file?
|
|
47
|
+
node_id = file.basename.to_s.sub(/\.(yml|yaml|json|toml)$/, '')
|
|
48
|
+
metadata_map[node_id] ||= {id: node_id, path: file, type: :new}
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
|
|
48
|
-
files.
|
|
52
|
+
# Second, scan for old structure: subdirectory attributes files (e.g., posts/ara/attributes.yml)
|
|
53
|
+
subdirectories.each do |subdir|
|
|
54
|
+
node_id = subdir.basename.to_s
|
|
55
|
+
|
|
56
|
+
# Check if this subdirectory has an attributes file
|
|
57
|
+
extensions.each do |ext|
|
|
58
|
+
attributes_file = subdir.join("attributes.#{ext}")
|
|
59
|
+
if attributes_file.exist? && attributes_file.file?
|
|
60
|
+
# Only use old structure if new structure doesn't exist
|
|
61
|
+
# This allows gradual migration - new structure takes precedence
|
|
62
|
+
metadata_map[node_id] ||= {id: node_id, path: attributes_file, type: :old}
|
|
63
|
+
break
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Return sorted array of metadata info
|
|
69
|
+
metadata_map.values.sort_by { |m| m[:id] }
|
|
49
70
|
end
|
|
50
71
|
|
|
51
72
|
def subdirectories(...)
|
|
@@ -56,28 +77,43 @@ module Ro
|
|
|
56
77
|
@path.subdirectory_for(name)
|
|
57
78
|
end
|
|
58
79
|
|
|
59
|
-
# T020: Modified to discover nodes from metadata files (new structure)
|
|
80
|
+
# T020: Modified to discover nodes from metadata files (both new and old structure)
|
|
60
81
|
def each(offset:nil, limit:nil, &block)
|
|
61
82
|
# Return enumerator if no block given and no offset/limit
|
|
62
83
|
return to_enum(:each, offset: offset, limit: limit) unless block_given?
|
|
63
84
|
|
|
64
|
-
#
|
|
65
|
-
|
|
85
|
+
# Get metadata from both old and new structure
|
|
86
|
+
metadata_entries = metadata_files
|
|
66
87
|
|
|
67
88
|
if offset
|
|
68
89
|
i = -1
|
|
69
90
|
n = 0
|
|
70
|
-
|
|
91
|
+
metadata_entries.each do |entry|
|
|
71
92
|
i += 1
|
|
72
93
|
next if i < offset
|
|
73
|
-
|
|
94
|
+
|
|
95
|
+
# Create node based on structure type
|
|
96
|
+
node = if entry[:type] == :new
|
|
97
|
+
Node.new(self, entry[:path])
|
|
98
|
+
else
|
|
99
|
+
# Old structure: pass subdirectory path
|
|
100
|
+
Node.new(entry[:path].parent)
|
|
101
|
+
end
|
|
102
|
+
|
|
74
103
|
block.call(node)
|
|
75
104
|
n += 1
|
|
76
105
|
break if limit && n >= limit
|
|
77
106
|
end
|
|
78
107
|
else
|
|
79
|
-
|
|
80
|
-
node
|
|
108
|
+
metadata_entries.each do |entry|
|
|
109
|
+
# Create node based on structure type
|
|
110
|
+
node = if entry[:type] == :new
|
|
111
|
+
Node.new(self, entry[:path])
|
|
112
|
+
else
|
|
113
|
+
# Old structure: pass subdirectory path
|
|
114
|
+
Node.new(entry[:path].parent)
|
|
115
|
+
end
|
|
116
|
+
|
|
81
117
|
block.call(node)
|
|
82
118
|
end
|
|
83
119
|
end
|
data/lib/ro/migrator.rb
CHANGED
|
@@ -45,12 +45,14 @@ module Ro
|
|
|
45
45
|
result[:collections] << collection_name
|
|
46
46
|
|
|
47
47
|
# Check for new structure (metadata files at collection level)
|
|
48
|
-
collection.metadata_files.each do |
|
|
49
|
-
|
|
48
|
+
collection.metadata_files.each do |entry|
|
|
49
|
+
# Only count new-structure nodes for migration purposes
|
|
50
|
+
next unless entry[:type] == :new
|
|
51
|
+
|
|
50
52
|
result[:new_nodes] << {
|
|
51
53
|
collection: collection_name,
|
|
52
|
-
node_id:
|
|
53
|
-
metadata_file:
|
|
54
|
+
node_id: entry[:id],
|
|
55
|
+
metadata_file: entry[:path]
|
|
54
56
|
}
|
|
55
57
|
result[:has_new_structure] = true
|
|
56
58
|
end
|
data/lib/ro/node.rb
CHANGED
|
@@ -85,19 +85,26 @@ module Ro
|
|
|
85
85
|
|
|
86
86
|
# T026: Modified to load from external metadata_file (new structure)
|
|
87
87
|
def _load_base_attributes
|
|
88
|
+
# Start with collection-level metadata (new structure) if it exists
|
|
89
|
+
base_attrs = Map.new
|
|
90
|
+
|
|
88
91
|
if @metadata_file && @metadata_file.exist?
|
|
89
92
|
# New structure: load from explicit metadata file
|
|
90
93
|
attrs = _render(@metadata_file)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
# Old structure: search for attributes.yml in node directory
|
|
94
|
-
glob = "attributes.{yml,yaml,json}"
|
|
94
|
+
base_attrs = Map.for(attrs)
|
|
95
|
+
end
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
# Then merge in nested attributes.yml (old structure) if it exists
|
|
98
|
+
# "Deeper more specific wins" - nested attributes override collection-level
|
|
99
|
+
glob = "attributes.{yml,yaml,json}"
|
|
100
|
+
@path.glob(glob) do |file|
|
|
101
|
+
nested_attrs = _render(file)
|
|
102
|
+
# Use Map's smart merge: base.apply(override) means override wins
|
|
103
|
+
base_attrs = base_attrs.apply(Map.for(nested_attrs))
|
|
100
104
|
end
|
|
105
|
+
|
|
106
|
+
# Update with the merged result
|
|
107
|
+
update_attributes!(base_attrs.to_hash, file: @metadata_file || @path) if base_attrs.any?
|
|
101
108
|
end
|
|
102
109
|
|
|
103
110
|
def _load_asset_attributes
|
data/ro.gemspec
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
Gem::Specification::new do |spec|
|
|
5
5
|
spec.name = "ro"
|
|
6
|
-
spec.version = "5.
|
|
6
|
+
spec.version = "5.2.0"
|
|
7
7
|
spec.required_ruby_version = '>= 3.0'
|
|
8
8
|
spec.platform = Gem::Platform::RUBY
|
|
9
9
|
spec.summary = "all your content in github, as god intended"
|
|
@@ -269,6 +269,7 @@ Gem::Specification::new do |spec|
|
|
|
269
269
|
"test/fixtures/old_structure/posts/sample-post/assets/image.jpg",
|
|
270
270
|
"test/fixtures/old_structure/posts/sample-post/attributes.yml",
|
|
271
271
|
"test/integration",
|
|
272
|
+
"test/integration/dual_structure_test.rb",
|
|
272
273
|
"test/integration/ro_integration_test.rb",
|
|
273
274
|
"test/test_helper.rb",
|
|
274
275
|
"test/tmp/migration_test_1760746513.backup.20251018001513",
|
|
@@ -453,6 +454,66 @@ Gem::Specification::new do |spec|
|
|
|
453
454
|
"test/tmp/migration_test_1760944190.backup.20251020070950/posts/sample-post/assets/body.md",
|
|
454
455
|
"test/tmp/migration_test_1760944190.backup.20251020070950/posts/sample-post/assets/image.jpg",
|
|
455
456
|
"test/tmp/migration_test_1760944190.backup.20251020070950/posts/sample-post/attributes.yml",
|
|
457
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238",
|
|
458
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758",
|
|
459
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts",
|
|
460
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/assets-only",
|
|
461
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/assets-only/assets",
|
|
462
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/assets-only/assets/test.txt",
|
|
463
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post",
|
|
464
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post/assets",
|
|
465
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post/assets/body.md",
|
|
466
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post/assets/image.jpg",
|
|
467
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post/attributes.yml",
|
|
468
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/posts",
|
|
469
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/posts/assets-only",
|
|
470
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/posts/assets-only/assets",
|
|
471
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/posts/assets-only/assets/test.txt",
|
|
472
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post",
|
|
473
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post/assets",
|
|
474
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post/assets/body.md",
|
|
475
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post/assets/image.jpg",
|
|
476
|
+
"test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post/attributes.yml",
|
|
477
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328",
|
|
478
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808",
|
|
479
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts",
|
|
480
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/assets-only",
|
|
481
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/assets-only/assets",
|
|
482
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/assets-only/assets/test.txt",
|
|
483
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post",
|
|
484
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post/assets",
|
|
485
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post/assets/body.md",
|
|
486
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post/assets/image.jpg",
|
|
487
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post/attributes.yml",
|
|
488
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/posts",
|
|
489
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/posts/assets-only",
|
|
490
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/posts/assets-only/assets",
|
|
491
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/posts/assets-only/assets/test.txt",
|
|
492
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post",
|
|
493
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post/assets",
|
|
494
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post/assets/body.md",
|
|
495
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post/assets/image.jpg",
|
|
496
|
+
"test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post/attributes.yml",
|
|
497
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056",
|
|
498
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056",
|
|
499
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts",
|
|
500
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/assets-only",
|
|
501
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/assets-only/assets",
|
|
502
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/assets-only/assets/test.txt",
|
|
503
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post",
|
|
504
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post/assets",
|
|
505
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post/assets/body.md",
|
|
506
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post/assets/image.jpg",
|
|
507
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post/attributes.yml",
|
|
508
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/posts",
|
|
509
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/posts/assets-only",
|
|
510
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/posts/assets-only/assets",
|
|
511
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/posts/assets-only/assets/test.txt",
|
|
512
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post",
|
|
513
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post/assets",
|
|
514
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post/assets/body.md",
|
|
515
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post/assets/image.jpg",
|
|
516
|
+
"test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post/attributes.yml",
|
|
456
517
|
"test/tmp/new_structure_test_1760746452",
|
|
457
518
|
"test/tmp/new_structure_test_1760746452/mixed",
|
|
458
519
|
"test/tmp/new_structure_test_1760746452/mixed/test-json.json",
|
|
@@ -467,6 +528,22 @@ Gem::Specification::new do |spec|
|
|
|
467
528
|
"test/tmp/new_structure_test_1760746452/posts/sample-post.yml",
|
|
468
529
|
"test/tmp/new_structure_test_1760746452/posts/sample-post/body.md",
|
|
469
530
|
"test/tmp/new_structure_test_1760746452/posts/sample-post/image.jpg",
|
|
531
|
+
"test/tmp/new_structure_test_1760949758",
|
|
532
|
+
"test/tmp/new_structure_test_1760949758/mixed",
|
|
533
|
+
"test/tmp/new_structure_test_1760949758/mixed/test-json.json",
|
|
534
|
+
"test/tmp/new_structure_test_1760949758/mixed/test-yaml.yml",
|
|
535
|
+
"test/tmp/new_structure_test_1760949758/posts",
|
|
536
|
+
"test/tmp/new_structure_test_1760949758/posts/metadata-only.yml",
|
|
537
|
+
"test/tmp/new_structure_test_1760949758/posts/nested-test",
|
|
538
|
+
"test/tmp/new_structure_test_1760949758/posts/nested-test.yml",
|
|
539
|
+
"test/tmp/new_structure_test_1760949758/posts/nested-test/assets",
|
|
540
|
+
"test/tmp/new_structure_test_1760949758/posts/nested-test/assets/subdirectory",
|
|
541
|
+
"test/tmp/new_structure_test_1760949758/posts/nested-test/assets/subdirectory/image.png",
|
|
542
|
+
"test/tmp/new_structure_test_1760949758/posts/sample-post",
|
|
543
|
+
"test/tmp/new_structure_test_1760949758/posts/sample-post.yml",
|
|
544
|
+
"test/tmp/new_structure_test_1760949758/posts/sample-post/assets",
|
|
545
|
+
"test/tmp/new_structure_test_1760949758/posts/sample-post/assets/body.md",
|
|
546
|
+
"test/tmp/new_structure_test_1760949758/posts/sample-post/assets/image.jpg",
|
|
470
547
|
"test/unit",
|
|
471
548
|
"test/unit/asset_test.rb",
|
|
472
549
|
"test/unit/collection_test.rb",
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
require_relative '../test_helper'
|
|
2
|
+
|
|
3
|
+
class DualStructureTest < RoTestCase
|
|
4
|
+
def setup
|
|
5
|
+
@test_dir = Pathname.new(Dir.mktmpdir("dual_structure_test"))
|
|
6
|
+
@ro_dir = @test_dir.join('ro')
|
|
7
|
+
@posts_dir = @ro_dir.join('posts')
|
|
8
|
+
@posts_dir.mkpath
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def teardown
|
|
12
|
+
FileUtils.rm_rf(@test_dir) if @test_dir && @test_dir.exist?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_discovers_new_structure_nodes
|
|
16
|
+
# Create new structure: posts/foo.yml
|
|
17
|
+
File.write(@posts_dir.join('foo.yml'), {title: 'Foo New'}.to_yaml)
|
|
18
|
+
@posts_dir.join('foo').mkpath
|
|
19
|
+
|
|
20
|
+
root = Ro::Root.new(@ro_dir)
|
|
21
|
+
posts = root.posts
|
|
22
|
+
|
|
23
|
+
assert_equal 1, posts.to_a.size
|
|
24
|
+
assert_equal 'foo', posts.first.id
|
|
25
|
+
assert_equal 'Foo New', posts.first.attributes[:title]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_discovers_old_structure_nodes
|
|
29
|
+
# Create old structure: posts/bar/attributes.yml
|
|
30
|
+
bar_dir = @posts_dir.join('bar')
|
|
31
|
+
bar_dir.mkpath
|
|
32
|
+
File.write(bar_dir.join('attributes.yml'), {title: 'Bar Old'}.to_yaml)
|
|
33
|
+
|
|
34
|
+
root = Ro::Root.new(@ro_dir)
|
|
35
|
+
posts = root.posts
|
|
36
|
+
|
|
37
|
+
assert_equal 1, posts.to_a.size
|
|
38
|
+
assert_equal 'bar', posts.first.id
|
|
39
|
+
assert_equal 'Bar Old', posts.first.attributes[:title]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_discovers_both_structure_types_simultaneously
|
|
43
|
+
# Create new structure node
|
|
44
|
+
File.write(@posts_dir.join('new-node.yml'), {title: 'New Structure'}.to_yaml)
|
|
45
|
+
@posts_dir.join('new-node').mkpath
|
|
46
|
+
|
|
47
|
+
# Create old structure node
|
|
48
|
+
old_dir = @posts_dir.join('old-node')
|
|
49
|
+
old_dir.mkpath
|
|
50
|
+
File.write(old_dir.join('attributes.yml'), {title: 'Old Structure'}.to_yaml)
|
|
51
|
+
|
|
52
|
+
root = Ro::Root.new(@ro_dir)
|
|
53
|
+
posts = root.posts
|
|
54
|
+
nodes = posts.to_a
|
|
55
|
+
|
|
56
|
+
assert_equal 2, nodes.size
|
|
57
|
+
|
|
58
|
+
new_node = nodes.find { |n| n.id == 'new-node' }
|
|
59
|
+
old_node = nodes.find { |n| n.id == 'old-node' }
|
|
60
|
+
|
|
61
|
+
assert_equal 'New Structure', new_node.attributes[:title]
|
|
62
|
+
assert_equal 'Old Structure', old_node.attributes[:title]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_new_structure_takes_precedence_over_old
|
|
66
|
+
# Create both structures for same node - new should win
|
|
67
|
+
node_id = 'conflict'
|
|
68
|
+
|
|
69
|
+
# New structure
|
|
70
|
+
File.write(@posts_dir.join("#{node_id}.yml"), {title: 'New Wins'}.to_yaml)
|
|
71
|
+
|
|
72
|
+
# Old structure
|
|
73
|
+
node_dir = @posts_dir.join(node_id)
|
|
74
|
+
node_dir.mkpath
|
|
75
|
+
File.write(node_dir.join('attributes.yml'), {title: 'Old Loses'}.to_yaml)
|
|
76
|
+
|
|
77
|
+
root = Ro::Root.new(@ro_dir)
|
|
78
|
+
posts = root.posts
|
|
79
|
+
node = posts.to_a.first
|
|
80
|
+
|
|
81
|
+
assert_equal 'conflict', node.id
|
|
82
|
+
assert_equal 'New Wins', node.attributes[:title]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_mixed_structure_with_assets
|
|
86
|
+
# New structure with assets
|
|
87
|
+
File.write(@posts_dir.join('new-with-assets.yml'), {title: 'New'}.to_yaml)
|
|
88
|
+
new_assets_dir = @posts_dir.join('new-with-assets', 'assets')
|
|
89
|
+
new_assets_dir.mkpath
|
|
90
|
+
File.write(new_assets_dir.join('image.jpg'), 'fake jpg')
|
|
91
|
+
|
|
92
|
+
# Old structure with assets
|
|
93
|
+
old_dir = @posts_dir.join('old-with-assets')
|
|
94
|
+
old_dir.mkpath
|
|
95
|
+
File.write(old_dir.join('attributes.yml'), {title: 'Old'}.to_yaml)
|
|
96
|
+
old_assets_dir = old_dir.join('assets')
|
|
97
|
+
old_assets_dir.mkpath
|
|
98
|
+
File.write(old_assets_dir.join('photo.png'), 'fake png')
|
|
99
|
+
|
|
100
|
+
root = Ro::Root.new(@ro_dir)
|
|
101
|
+
posts = root.posts
|
|
102
|
+
nodes = posts.to_a
|
|
103
|
+
|
|
104
|
+
assert_equal 2, nodes.size
|
|
105
|
+
|
|
106
|
+
new_node = nodes.find { |n| n.id == 'new-with-assets' }
|
|
107
|
+
old_node = nodes.find { |n| n.id == 'old-with-assets' }
|
|
108
|
+
|
|
109
|
+
# Both should have assets
|
|
110
|
+
assert_equal 1, new_node.assets.size
|
|
111
|
+
assert new_node.assets.first.to_s.include?('image.jpg')
|
|
112
|
+
|
|
113
|
+
assert_equal 1, old_node.assets.size
|
|
114
|
+
assert old_node.assets.first.to_s.include?('photo.png')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_iteration_order_consistent
|
|
118
|
+
# Create nodes with both structures
|
|
119
|
+
File.write(@posts_dir.join('a-new.yml'), {}.to_yaml)
|
|
120
|
+
@posts_dir.join('a-new').mkpath
|
|
121
|
+
|
|
122
|
+
b_dir = @posts_dir.join('b-old')
|
|
123
|
+
b_dir.mkpath
|
|
124
|
+
File.write(b_dir.join('attributes.yml'), {}.to_yaml)
|
|
125
|
+
|
|
126
|
+
File.write(@posts_dir.join('c-new.yml'), {}.to_yaml)
|
|
127
|
+
@posts_dir.join('c-new').mkpath
|
|
128
|
+
|
|
129
|
+
root = Ro::Root.new(@ro_dir)
|
|
130
|
+
posts = root.posts
|
|
131
|
+
ids = posts.to_a.map(&:id)
|
|
132
|
+
|
|
133
|
+
# Should be sorted alphabetically regardless of structure type
|
|
134
|
+
assert_equal ['a-new', 'b-old', 'c-new'], ids
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def test_supports_multiple_metadata_formats
|
|
138
|
+
# New structure with different formats
|
|
139
|
+
File.write(@posts_dir.join('yaml-new.yml'), {format: 'yaml'}.to_yaml)
|
|
140
|
+
@posts_dir.join('yaml-new').mkpath
|
|
141
|
+
|
|
142
|
+
File.write(@posts_dir.join('json-new.json'), {format: 'json'}.to_json)
|
|
143
|
+
@posts_dir.join('json-new').mkpath
|
|
144
|
+
|
|
145
|
+
# Old structure with different formats
|
|
146
|
+
yaml_old_dir = @posts_dir.join('yaml-old')
|
|
147
|
+
yaml_old_dir.mkpath
|
|
148
|
+
File.write(yaml_old_dir.join('attributes.yaml'), {format: 'yaml_old'}.to_yaml)
|
|
149
|
+
|
|
150
|
+
json_old_dir = @posts_dir.join('json-old')
|
|
151
|
+
json_old_dir.mkpath
|
|
152
|
+
File.write(json_old_dir.join('attributes.json'), {format: 'json_old'}.to_json)
|
|
153
|
+
|
|
154
|
+
root = Ro::Root.new(@ro_dir)
|
|
155
|
+
posts = root.posts
|
|
156
|
+
nodes = posts.to_a
|
|
157
|
+
|
|
158
|
+
assert_equal 4, nodes.size
|
|
159
|
+
|
|
160
|
+
formats = nodes.map { |n| n.attributes[:format] }.sort
|
|
161
|
+
assert_equal ['json', 'json_old', 'yaml', 'yaml_old'], formats
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_merges_both_metadata_files_when_both_exist
|
|
165
|
+
# Create collection-level metadata
|
|
166
|
+
File.write(@posts_dir.join('merged.yml'), {
|
|
167
|
+
title: 'Collection Level',
|
|
168
|
+
author: 'Collection Author',
|
|
169
|
+
tags: ['collection']
|
|
170
|
+
}.to_yaml)
|
|
171
|
+
|
|
172
|
+
# Create nested attributes - should override collection level
|
|
173
|
+
merged_dir = @posts_dir.join('merged')
|
|
174
|
+
merged_dir.mkpath
|
|
175
|
+
File.write(merged_dir.join('attributes.yml'), {
|
|
176
|
+
title: 'Nested Wins', # Override
|
|
177
|
+
published: true, # New field
|
|
178
|
+
tags: ['nested', 'specific'] # Override
|
|
179
|
+
}.to_yaml)
|
|
180
|
+
|
|
181
|
+
root = Ro::Root.new(@ro_dir)
|
|
182
|
+
posts = root.posts
|
|
183
|
+
node = posts.to_a.first
|
|
184
|
+
|
|
185
|
+
# Nested (deeper, more specific) should win
|
|
186
|
+
assert_equal 'Nested Wins', node.attributes[:title]
|
|
187
|
+
assert_equal true, node.attributes[:published]
|
|
188
|
+
assert_equal ['nested', 'specific'], node.attributes[:tags]
|
|
189
|
+
|
|
190
|
+
# Collection-level field that wasn't overridden should still be there
|
|
191
|
+
assert_equal 'Collection Author', node.attributes[:author]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def test_deep_merge_with_nested_hashes
|
|
195
|
+
# Collection-level with nested structure
|
|
196
|
+
File.write(@posts_dir.join('deep.yml'), {
|
|
197
|
+
meta: {
|
|
198
|
+
created: '2024-01-01',
|
|
199
|
+
source: 'collection'
|
|
200
|
+
},
|
|
201
|
+
settings: {
|
|
202
|
+
public: true,
|
|
203
|
+
featured: false
|
|
204
|
+
}
|
|
205
|
+
}.to_yaml)
|
|
206
|
+
|
|
207
|
+
# Nested attributes with partial override
|
|
208
|
+
deep_dir = @posts_dir.join('deep')
|
|
209
|
+
deep_dir.mkpath
|
|
210
|
+
File.write(deep_dir.join('attributes.yml'), {
|
|
211
|
+
meta: {
|
|
212
|
+
updated: '2024-02-01',
|
|
213
|
+
source: 'nested' # Override
|
|
214
|
+
},
|
|
215
|
+
settings: {
|
|
216
|
+
featured: true # Override just this field
|
|
217
|
+
}
|
|
218
|
+
}.to_yaml)
|
|
219
|
+
|
|
220
|
+
root = Ro::Root.new(@ro_dir)
|
|
221
|
+
posts = root.posts
|
|
222
|
+
node = posts.to_a.first
|
|
223
|
+
|
|
224
|
+
# Deep merge should preserve non-conflicting values
|
|
225
|
+
assert_equal '2024-01-01', node.attributes[:meta][:created]
|
|
226
|
+
assert_equal '2024-02-01', node.attributes[:meta][:updated]
|
|
227
|
+
assert_equal 'nested', node.attributes[:meta][:source] # Nested wins
|
|
228
|
+
|
|
229
|
+
assert_equal true, node.attributes[:settings][:public]
|
|
230
|
+
assert_equal true, node.attributes[:settings][:featured] # Nested wins
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test asset
|
data/test/tmp/migration_test_1760949758.backup.20251020084238/posts/assets-only/assets/test.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test asset
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test asset
|
data/test/tmp/migration_test_1760949808.backup.20251020084328/posts/assets-only/assets/test.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test asset
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test asset
|
data/test/tmp/migration_test_1760982056.backup.20251020174056/posts/assets-only/assets/test.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test asset
|
|
@@ -11,31 +11,32 @@ class CollectionTest < RoTestCase
|
|
|
11
11
|
|
|
12
12
|
# T011: Test Collection#metadata_files
|
|
13
13
|
def test_metadata_files_returns_yml_and_json_files
|
|
14
|
-
|
|
14
|
+
entries = @collection.metadata_files
|
|
15
15
|
|
|
16
|
-
assert_not_nil
|
|
17
|
-
assert
|
|
16
|
+
assert_not_nil entries, "metadata_files should not be nil"
|
|
17
|
+
assert entries.is_a?(Array), "metadata_files should return an Array"
|
|
18
18
|
|
|
19
19
|
# Should find .yml and .json files
|
|
20
|
-
|
|
21
|
-
assert
|
|
20
|
+
yml_entries = entries.select { |e| e[:path].to_s.end_with?('.yml') }
|
|
21
|
+
assert yml_entries.any?, "Should find at least one .yml file"
|
|
22
22
|
|
|
23
|
-
#
|
|
24
|
-
assert
|
|
23
|
+
# Each entry should be a hash with :id, :path, :type
|
|
24
|
+
assert entries.all? { |e| e.is_a?(Hash) && e[:id] && e[:path] && e[:type] }, "All entries should be hashes with :id, :path, :type"
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def test_metadata_files_excludes_directories
|
|
28
|
-
|
|
28
|
+
entries = @collection.metadata_files
|
|
29
29
|
|
|
30
30
|
# Should only return files, not directories
|
|
31
|
-
assert
|
|
31
|
+
assert entries.all? { |e| e[:path].file? }, "metadata_files should only return file entries"
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def test_metadata_files_sorted
|
|
35
|
-
|
|
35
|
+
entries = @collection.metadata_files
|
|
36
36
|
|
|
37
|
-
# Should be sorted
|
|
38
|
-
|
|
37
|
+
# Should be sorted by id
|
|
38
|
+
ids = entries.map { |e| e[:id] }
|
|
39
|
+
assert_equal ids.sort, ids, "metadata_files should be sorted by id"
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
# T012: Test Collection#each with new structure
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ro
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ara T. Howard
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: map
|
|
@@ -329,6 +329,7 @@ files:
|
|
|
329
329
|
- test/fixtures/old_structure/posts/sample-post/assets/body.md
|
|
330
330
|
- test/fixtures/old_structure/posts/sample-post/assets/image.jpg
|
|
331
331
|
- test/fixtures/old_structure/posts/sample-post/attributes.yml
|
|
332
|
+
- test/integration/dual_structure_test.rb
|
|
332
333
|
- test/integration/ro_integration_test.rb
|
|
333
334
|
- test/test_helper.rb
|
|
334
335
|
- test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/assets/image.jpg
|
|
@@ -407,6 +408,30 @@ files:
|
|
|
407
408
|
- test/tmp/migration_test_1760944190.backup.20251020070950/posts/sample-post/assets/body.md
|
|
408
409
|
- test/tmp/migration_test_1760944190.backup.20251020070950/posts/sample-post/assets/image.jpg
|
|
409
410
|
- test/tmp/migration_test_1760944190.backup.20251020070950/posts/sample-post/attributes.yml
|
|
411
|
+
- test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/assets-only/assets/test.txt
|
|
412
|
+
- test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post/assets/body.md
|
|
413
|
+
- test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post/assets/image.jpg
|
|
414
|
+
- test/tmp/migration_test_1760949758.backup.20251020084238/migration_test_1760949758/posts/sample-post/attributes.yml
|
|
415
|
+
- test/tmp/migration_test_1760949758.backup.20251020084238/posts/assets-only/assets/test.txt
|
|
416
|
+
- test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post/assets/body.md
|
|
417
|
+
- test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post/assets/image.jpg
|
|
418
|
+
- test/tmp/migration_test_1760949758.backup.20251020084238/posts/sample-post/attributes.yml
|
|
419
|
+
- test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/assets-only/assets/test.txt
|
|
420
|
+
- test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post/assets/body.md
|
|
421
|
+
- test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post/assets/image.jpg
|
|
422
|
+
- test/tmp/migration_test_1760949808.backup.20251020084328/migration_test_1760949808/posts/sample-post/attributes.yml
|
|
423
|
+
- test/tmp/migration_test_1760949808.backup.20251020084328/posts/assets-only/assets/test.txt
|
|
424
|
+
- test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post/assets/body.md
|
|
425
|
+
- test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post/assets/image.jpg
|
|
426
|
+
- test/tmp/migration_test_1760949808.backup.20251020084328/posts/sample-post/attributes.yml
|
|
427
|
+
- test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/assets-only/assets/test.txt
|
|
428
|
+
- test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post/assets/body.md
|
|
429
|
+
- test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post/assets/image.jpg
|
|
430
|
+
- test/tmp/migration_test_1760982056.backup.20251020174056/migration_test_1760982056/posts/sample-post/attributes.yml
|
|
431
|
+
- test/tmp/migration_test_1760982056.backup.20251020174056/posts/assets-only/assets/test.txt
|
|
432
|
+
- test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post/assets/body.md
|
|
433
|
+
- test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post/assets/image.jpg
|
|
434
|
+
- test/tmp/migration_test_1760982056.backup.20251020174056/posts/sample-post/attributes.yml
|
|
410
435
|
- test/tmp/new_structure_test_1760746452/mixed/test-json.json
|
|
411
436
|
- test/tmp/new_structure_test_1760746452/mixed/test-yaml.yml
|
|
412
437
|
- test/tmp/new_structure_test_1760746452/posts/metadata-only.yml
|
|
@@ -415,6 +440,14 @@ files:
|
|
|
415
440
|
- test/tmp/new_structure_test_1760746452/posts/sample-post.yml
|
|
416
441
|
- test/tmp/new_structure_test_1760746452/posts/sample-post/body.md
|
|
417
442
|
- test/tmp/new_structure_test_1760746452/posts/sample-post/image.jpg
|
|
443
|
+
- test/tmp/new_structure_test_1760949758/mixed/test-json.json
|
|
444
|
+
- test/tmp/new_structure_test_1760949758/mixed/test-yaml.yml
|
|
445
|
+
- test/tmp/new_structure_test_1760949758/posts/metadata-only.yml
|
|
446
|
+
- test/tmp/new_structure_test_1760949758/posts/nested-test.yml
|
|
447
|
+
- test/tmp/new_structure_test_1760949758/posts/nested-test/assets/subdirectory/image.png
|
|
448
|
+
- test/tmp/new_structure_test_1760949758/posts/sample-post.yml
|
|
449
|
+
- test/tmp/new_structure_test_1760949758/posts/sample-post/assets/body.md
|
|
450
|
+
- test/tmp/new_structure_test_1760949758/posts/sample-post/assets/image.jpg
|
|
418
451
|
- test/unit/asset_test.rb
|
|
419
452
|
- test/unit/collection_test.rb
|
|
420
453
|
- test/unit/migrator_test.rb
|