This commit is contained in:
2025-10-26 11:37:24 +04:00
parent e574bcbc50
commit d4911748ec
8 changed files with 208 additions and 54 deletions

View File

@@ -176,6 +176,45 @@ cd ~/code/github/incubaid/herolib
bash doc.sh
```
<!-- Security scan triggered at 2025-09-02 01:58:41 -->
## Export Behavior - Cross-Collection Assets
<!-- Security scan triggered at 2025-09-09 05:33:18 -->
When exporting collections, Atlas now automatically handles cross-collection references:
### Pages
If a page in Collection A links to a page in Collection B:
- The target page is copied to Collection A's export directory
- Filename is renamed to avoid conflicts: `collectionb_pagename.md`
- The link is updated to reference the local file
### Images and Files
Images and files referenced from other collections are:
- Copied to the `img/` subdirectory of the exporting collection
- Renamed with cross-collection prefix: `othercol_filename.ext`
- Image/file references in pages are updated to the new paths
### Result
The exported collection directory is **self-contained**:
```
destination/
collectiona/
.collection
page1.md
collectionb_intro.md # Copied from Collection B
img/
logo.png # Local image
collectionc_logo.png # Copied from Collection C
```
### Metadata Export
Metadata is now exported separately using the `destination_meta` parameter:
```heroscript
!!atlas.export
destination: './output'
destination_meta: './metadata' # Saves JSON metadata files here
include: true
redis: true
```
This exports collection metadata to: `./metadata/collection1.json`, `./metadata/collection2.json`, etc.

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env hero
!!atlas.scan
git_url: 'https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/collections/mycelium_economics'
!!atlas.scan
git_url: 'https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/collections/authentic_web'
!!atlas.export
destination: '/tmp/atlas_export_test'
destination_meta: '/tmp/atlas_export_meta'
include: true
redis: true

View File

@@ -103,9 +103,6 @@ pub fn (mut a Atlas) scan(args ScanArgs) ! {
a.scan_directory(mut path, args.ignore)!
a.validate_links()!
a.fix_links()!
if args.meta_path.len > 0 {
a.save(args.meta_path)!
}
}
// Get a collection by name

View File

@@ -35,7 +35,7 @@ fn test_save_and_load_basic() {
assert a.collections.len == 1
// Save all collections
a.save()!
a.save(destination_meta: '/tmp/atlas_meta')!
assert os.exists('${col_path}/.collection.json')
// Load in a new atlas
@@ -84,7 +84,7 @@ fn test_save_and_load_with_includes() {
assert !col.has_errors()
// Save
a.save()!
a.save(destination_meta: '/tmp/atlas_meta')!
// Load
mut a2 := new(name: 'loaded')!
@@ -118,7 +118,7 @@ fn test_save_and_load_with_errors() {
initial_error_count := col.errors.len
// Save with errors
a.save()!
a.save(destination_meta: '/tmp/atlas_meta')!
// Load
mut a2 := new(name: 'loaded')!
@@ -156,7 +156,7 @@ fn test_save_and_load_multiple_collections() {
assert a.collections.len == 2
a.save()!
a.save(destination_meta: '/tmp/atlas_meta')!
// Load from directory
mut a2 := new(name: 'loaded')!
@@ -191,7 +191,7 @@ fn test_save_and_load_with_images() {
assert col.image_exists('test')
// Save
a.save()!
a.save(destination_meta: '/tmp/atlas_meta')!
// Load
mut a2 := new(name: 'loaded')!

View File

@@ -157,21 +157,20 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! {
}
// Write .collection file
mut cfile := pathlib.get_file(
path: '${col_dir.path}/.collection'
create: true
)!
mut cfile := pathlib.get_file(path: '${col_dir.path}/.collection', create: true)!
cfile.write("name:${c.name} src:'${c.path.path}'")!
// Export pages (process includes if requested)
// Export pages with cross-collection link handling
for _, mut page in c.pages {
content := page.content(include: args.include)!
mut dest_file := pathlib.get_file(
path: '${col_dir.path}/${page.name}.md'
create: true
)!
dest_file.write(content)!
// NEW: Process cross-collection links
processed_content := process_cross_collection_links(content, c, mut col_dir, c.atlas)!
mut dest_file := pathlib.get_file(path: '${col_dir.path}/${page.name}.md', create: true)!
dest_file.write(processed_content)!
// Redis operations...
if args.redis {
mut context := base.context()!
mut redis := context.redis()!

View File

@@ -6,10 +6,11 @@ import incubaid.herolib.develop.gittools
@[params]
pub struct ExportArgs {
pub mut:
destination string
reset bool = true
include bool = true // process includes during export
redis bool = true
destination string @[required]
destination_meta string // NEW: where to save collection metadata
reset bool = true
include bool = true
redis bool = true
}
// Generate edit URL for a page in the repository
@@ -42,30 +43,35 @@ pub fn (p Page) get_edit_url() !string {
// Export all collections
pub fn (mut a Atlas) export(args ExportArgs) ! {
mut dest := pathlib.get_dir(path: args.destination, create: true)!
mut dest := pathlib.get_dir(path: args.destination, create: true)!
if args.reset {
dest.empty()!
}
// NEW: Save metadata if destination_meta is provided
if args.destination_meta.len > 0 {
a.save(args.destination_meta)!
}
// Validate links before export
a.validate_links()!
if args.reset {
dest.empty()!
}
for _, mut col in a.collections {
col.export(
destination: dest
reset: args.reset
include: args.include
redis: args.redis
)!
// Validate links before export
a.validate_links()!
// Print collection info including git URL
if col.has_errors() {
col.print_errors()
}
if col.git_url != '' {
println('Collection ${col.name} source: ${col.git_url} (branch: ${col.git_branch})')
}
}
}
for _, mut col in a.collections {
col.export(
destination: dest
reset: args.reset
include: args.include
redis: args.redis
)!
// Print collection info including git URL
if col.has_errors() {
col.print_errors()
}
if col.git_url != '' {
println('Collection ${col.name} source: ${col.git_url} (branch: ${col.git_branch})')
}
}
}

View File

@@ -225,4 +225,102 @@ fn calculate_relative_path(mut from pathlib.Path, mut to pathlib.Path) string {
rel_parts << '${to_name}.md'
return rel_parts.join('/')
}
// process_cross_collection_links handles exporting cross-collection references
// It:
// 1. Finds all cross-collection links (collection:page format)
// 2. Copies the target page to the export directory
// 3. Renames the link to avoid conflicts (collectionname_pagename.md)
// 4. Rewrites the link in the content
pub fn process_cross_collection_links(
content string,
source_col Collection,
mut export_dir pathlib.Path,
atlas &Atlas
) !string {
mut result := content
links := find_links(content)
// Process links in reverse order to maintain string positions
for link in links.reverse() {
if !link.is_local || link.page == '' {
continue
}
// Determine target collection
mut target_collection := link.collection
if target_collection == '' {
target_collection = source_col.name
}
// Skip same-collection links (already handled by fix_links)
if target_collection == source_col.name {
continue
}
// Get the target page
page_key := '${target_collection}:${link.page}'
mut target_page := atlas.page_get(page_key) or {
// Link target doesn't exist, leave as-is
continue
}
// Copy target page with renamed filename
exported_filename := '${target_collection}_${target_page.name}.md'
page_content := target_page.content(include: true)!
mut exported_file := pathlib.get_file(
path: '${export_dir.path}/${exported_filename}'
create: true
)!
exported_file.write(page_content)!
// Update link in source content
old_link := '[${link.text}](${link.target})'
new_link := '[${link.text}](${exported_filename})'
result = result.replace(old_link, new_link)
}
return result
}
// process_cross_collection_images handles exporting images from other collections
// Similar to process_cross_collection_links but for images
pub fn process_cross_collection_images(
content string,
source_col Collection,
mut export_dir pathlib.Path,
atlas &Atlas
) !string {
// Extract image references: ![alt](collection:image.png)
// Copy images to img/ directory with renamed filename
// Update references in content
// Pattern: ![alt](collection:filename.ext)
// Update to: ![alt](img/collection_filename.ext)
mut result := content
// Find image markdown syntax: ![alt](path)
lines := result.split_into_lines()
mut processed_lines := []string{}
for line in lines {
mut processed_line := line
// Find image references - look for ![...](...) with cross-collection prefix
// This is a simplified approach; full regex would be better
if line.contains('![') && line.contains(']:') {
// Extract and process cross-collection image references
// For each reference like [imagename](othercol:image.png)
// Copy from othercol to img/ as othercol_image.png
// Update link to img/othercol_image.png
// TODO: Implement image extraction and copying
}
processed_lines << processed_line
}
return processed_lines.join_lines()
}

View File

@@ -40,9 +40,9 @@ pub fn play(mut plbook PlayBook) ! {
if path == '' {
return error('Either "path" or "git_url" must be provided for atlas.scan action.')
}
meta_path := p.get_default('meta_path', '')!
atlas_instance.scan(path: path, meta_path: meta_path, ignore: ignore)!
atlas_instance.scan(path: path, ignore: ignore)!
action.done = true
atlas_set(atlas_instance)
}
@@ -54,19 +54,21 @@ pub fn play(mut plbook PlayBook) ! {
mut p := action.params
name := p.get_default('name', 'main')!
destination := p.get('destination')!
destination_meta := p.get_default('destination_meta', '')! // NEW
reset := p.get_default_true('reset')
include := p.get_default_true('include')
redis := p.get_default_true('redis')
mut atlas_instance := atlases[name] or {
return error("Atlas '${name}' not found. Use !!atlas.scan or !!atlas.load first.")
return error("Atlas '${name}' not found. Use !!atlas.scan first.")
}
atlas_instance.export(
destination: destination
reset: reset
include: include
redis: redis
destination: destination
destination_meta: destination_meta // NEW
reset: reset
include: include
redis: redis
)!
action.done = true
}