...
This commit is contained in:
43
README.md
43
README.md
@@ -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.
|
||||
13
examples/data/atlas/heroscript_example_auth_web.hero
Executable file
13
examples/data/atlas/heroscript_example_auth_web.hero
Executable 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
|
||||
@@ -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
|
||||
|
||||
@@ -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')!
|
||||
|
||||
@@ -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()!
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
for _, mut col in a.collections {
|
||||
col.export(
|
||||
destination: dest
|
||||
reset: args.reset
|
||||
include: args.include
|
||||
redis: args.redis
|
||||
)!
|
||||
|
||||
if col.git_url != '' {
|
||||
println('Collection ${col.name} source: ${col.git_url} (branch: ${col.git_branch})')
|
||||
}
|
||||
}
|
||||
// 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})')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,3 +226,101 @@ fn calculate_relative_path(mut from pathlib.Path, mut to pathlib.Path) string {
|
||||
|
||||
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: 
|
||||
// Copy images to img/ directory with renamed filename
|
||||
// Update references in content
|
||||
|
||||
// Pattern: 
|
||||
// Update to: 
|
||||
|
||||
mut result := content
|
||||
|
||||
// Find image markdown syntax: 
|
||||
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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user