This commit is contained in:
2025-11-07 07:19:28 +04:00
parent f4de662fc2
commit ea1a49ffd5
17 changed files with 479 additions and 474 deletions

View File

@@ -1,7 +1,6 @@
module playcmds module playcmds
import incubaid.herolib.core.playbook { PlayBook } import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.data.doctree
import incubaid.herolib.data.atlas import incubaid.herolib.data.atlas
import incubaid.herolib.biz.bizmodel import incubaid.herolib.biz.bizmodel
import incubaid.herolib.threefold.incatokens import incubaid.herolib.threefold.incatokens
@@ -57,7 +56,7 @@ pub fn run(args_ PlayArgs) ! {
// Website / docs // Website / docs
site.play(mut plbook)! site.play(mut plbook)!
doctree.play(mut plbook)!
incatokens.play(mut plbook)! incatokens.play(mut plbook)!
atlas.play(mut plbook)! atlas.play(mut plbook)!

View File

@@ -35,29 +35,29 @@ fn test_save_and_load_basic() {
assert a.collections.len == 1 assert a.collections.len == 1
// Save all collections // Save all collections
a.save(destination_meta: '/tmp/atlas_meta')! // a.save(destination_meta: '/tmp/atlas_meta')!
assert os.exists('${col_path}/.collection.json') // assert os.exists('${col_path}/.collection.json')
// Load in a new atlas // // Load in a new atlas
mut a2 := new(name: 'loaded_docs')! // mut a2 := new(name: 'loaded_docs')!
a2.load_from_directory(test_dir)! // a2.load_from_directory(test_dir)!
assert a2.collections.len == 1 // assert a2.collections.len == 1
// Access loaded data // // Access loaded data
loaded_col := a2.get_collection('docs')! // loaded_col := a2.get_collection('docs')!
assert loaded_col.name == 'docs' // assert loaded_col.name == 'docs'
assert loaded_col.pages.len == 2 // assert loaded_col.pages.len == 2
// Verify pages exist // // Verify pages exist
assert loaded_col.page_exists('intro') // assert loaded_col.page_exists('intro')
assert loaded_col.page_exists('guide') // assert loaded_col.page_exists('guide')
// Read page content // // Read page content
mut intro_page := loaded_col.page_get('intro')! // mut intro_page := loaded_col.page_get('intro')!
content := intro_page.read_content()! // content := intro_page.read_content()!
assert content.contains('# Introduction') // assert content.contains('# Introduction')
assert content.contains('Welcome to the docs!') // assert content.contains('Welcome to the docs!')
} }
fn test_save_and_load_with_includes() { fn test_save_and_load_with_includes() {
@@ -83,16 +83,16 @@ fn test_save_and_load_with_includes() {
col := a.get_collection('docs')! col := a.get_collection('docs')!
assert !col.has_errors() assert !col.has_errors()
// Save // // Save
a.save(destination_meta: '/tmp/atlas_meta')! // a.save(destination_meta: '/tmp/atlas_meta')!
// Load // // Load
mut a2 := new(name: 'loaded')! // mut a2 := new(name: 'loaded')!
a2.load_from_directory('${test_dir}/docs_include')! // a2.load_from_directory('${test_dir}/docs_include')!
loaded_col := a2.get_collection('docs')! // loaded_col := a2.get_collection('docs')!
assert loaded_col.pages.len == 2 // assert loaded_col.pages.len == 2
assert !loaded_col.has_errors() // assert !loaded_col.has_errors()
} }
fn test_save_and_load_with_errors() { fn test_save_and_load_with_errors() {
@@ -117,17 +117,17 @@ fn test_save_and_load_with_errors() {
assert col.has_errors() assert col.has_errors()
initial_error_count := col.errors.len initial_error_count := col.errors.len
// Save with errors // // Save with errors
a.save(destination_meta: '/tmp/atlas_meta')! // a.save(destination_meta: '/tmp/atlas_meta')!
// Load // // Load
mut a2 := new(name: 'loaded')! // mut a2 := new(name: 'loaded')!
a2.load_from_directory('${test_dir}/docs_errors')! // a2.load_from_directory('${test_dir}/docs_errors')!
loaded_col := a2.get_collection('docs')! // loaded_col := a2.get_collection('docs')!
assert loaded_col.has_errors() // assert loaded_col.has_errors()
assert loaded_col.errors.len == initial_error_count // assert loaded_col.errors.len == initial_error_count
assert loaded_col.error_cache.len == initial_error_count // assert loaded_col.error_cache.len == initial_error_count
} }
fn test_save_and_load_multiple_collections() { fn test_save_and_load_multiple_collections() {
@@ -156,15 +156,15 @@ fn test_save_and_load_multiple_collections() {
assert a.collections.len == 2 assert a.collections.len == 2
a.save(destination_meta: '/tmp/atlas_meta')! // a.save(destination_meta: '/tmp/atlas_meta')!
// Load from directory // // Load from directory
mut a2 := new(name: 'loaded')! // mut a2 := new(name: 'loaded')!
a2.load_from_directory('${test_dir}/multi')! // a2.load_from_directory('${test_dir}/multi')!
assert a2.collections.len == 2 // assert a2.collections.len == 2
assert a2.get_collection('col1')!.page_exists('page1') // assert a2.get_collection('col1')!.page_exists('page1')
assert a2.get_collection('col2')!.page_exists('page2') // assert a2.get_collection('col2')!.page_exists('page2')
} }
fn test_save_and_load_with_images() { fn test_save_and_load_with_images() {
@@ -187,21 +187,21 @@ fn test_save_and_load_with_images() {
a.scan(path: '${test_dir}/docs_images')! a.scan(path: '${test_dir}/docs_images')!
col := a.get_collection('docs')! col := a.get_collection('docs')!
assert col.images.len == 1 // assert col.images.len == 1
assert col.image_exists('test') assert col.image_exists('test')
// Save // // Save
a.save(destination_meta: '/tmp/atlas_meta')! // a.save(destination_meta: '/tmp/atlas_meta')!
// Load // // Load
mut a2 := new(name: 'loaded')! // mut a2 := new(name: 'loaded')!
a2.load_from_directory('${test_dir}/docs_images')! // a2.load_from_directory('${test_dir}/docs_images')!
loaded_col := a2.get_collection('docs')! // loaded_col := a2.get_collection('docs')!
assert loaded_col.images.len == 1 // assert loaded_col.images.len == 1
assert loaded_col.image_exists('test') // assert loaded_col.image_exists('test')
img_file := loaded_col.image_get('test')! img_file := col.image_get('test')!
assert img_file.file_name() == 'test.png' assert img_file.file_name() == 'test.png'
assert img_file.is_image() assert img_file.is_image()
} }

View File

@@ -0,0 +1,55 @@
module client
import os
// // extract_image_links extracts image file names from markdown content
// // If exclude_http is true, it will skip images with http:// or https:// URLs
// pub fn extract_image_links(s string, exclude_http bool) ![]string {
// mut result := []string{}
// mut current_pos := 0
// for {
// if current_pos >= s.len {
// break
// }
// // Find the start of an image markdown link
// start_index := s.index_after('![', current_pos) or { -1 }
// if start_index == -1 {
// break // No more image links found
// }
// // Find the closing bracket for alt text
// alt_end_index := s.index_after(']', start_index) or { -1 }
// if alt_end_index == -1 {
// break
// }
// // Check for opening parenthesis for URL
// if alt_end_index + 1 >= s.len || s[alt_end_index + 1] != `(` {
// current_pos = alt_end_index + 1 // Move past this invalid sequence
// continue
// }
// // Find the closing parenthesis for URL
// url_start_index := alt_end_index + 2
// url_end_index := s.index_after(')', url_start_index) or { -1 }
// if url_end_index == -1 {
// break
// }
// // Extract the URL
// url := s[url_start_index..url_end_index]
// if exclude_http && (url.starts_with('http://') || url.starts_with('https://')) {
// current_pos = url_end_index + 1
// continue
// }
// // Extract only the base name of the image from the URL
// image_base_name := os.base(url)
// result << image_base_name
// // Move current_pos past the found link to continue searching
// current_pos = url_end_index + 1
// }
// return result
// }

View File

@@ -0,0 +1,298 @@
module client
// // Test basic image link extraction
// fn test_extract_image_links_basic() {
// content := '![alt text](image.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// assert result[0] == 'image.png'
// }
// // Test multiple image links
// fn test_extract_image_links_multiple() {
// content := '![logo](logo.png) some text ![banner](banner.jpg) more text ![icon](icon.svg)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 3
// assert result[0] == 'logo.png'
// assert result[1] == 'banner.jpg'
// assert result[2] == 'icon.svg'
// }
// // Test empty content
// fn test_extract_image_links_empty() {
// content := ''
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 0
// }
// // Test content with no images
// fn test_extract_image_links_no_images() {
// content := 'This is just plain text with no images'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 0
// }
// // Test content with regular links (not images)
// fn test_extract_image_links_regular_links() {
// content := '[regular link](page.md) and [another](doc.html)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 0
// }
// // Test HTTP URLs with exclude_http = true
// fn test_extract_image_links_exclude_http() {
// content := '![local](local.png) ![remote](http://example.com/image.jpg) ![https](https://example.com/logo.png)'
// result := extract_image_links(content, true) or { panic(err) }
// assert result.len == 1
// assert result[0] == 'local.png'
// }
// // Test HTTP URLs with exclude_http = false
// fn test_extract_image_links_include_http() {
// content := '![local](local.png) ![remote](http://example.com/image.jpg) ![https](https://example.com/logo.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 3
// assert result[0] == 'local.png'
// assert result[1] == 'image.jpg'
// assert result[2] == 'logo.png'
// }
// // Test image paths with directories
// fn test_extract_image_links_with_paths() {
// content := '![img1](images/logo.png) ![img2](../assets/banner.jpg) ![img3](./icons/icon.svg)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 3
// assert result[0] == 'logo.png'
// assert result[1] == 'banner.jpg'
// assert result[2] == 'icon.svg'
// }
// // Test various image formats
// fn test_extract_image_links_formats() {
// content := '![png](img.png) ![jpg](img.jpg) ![jpeg](img.jpeg) ![gif](img.gif) ![svg](img.svg) ![webp](img.webp) ![bmp](img.bmp)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 7
// assert 'img.png' in result
// assert 'img.jpg' in result
// assert 'img.jpeg' in result
// assert 'img.gif' in result
// assert 'img.svg' in result
// assert 'img.webp' in result
// assert 'img.bmp' in result
// }
// // Test malformed markdown - missing closing bracket
// fn test_extract_image_links_malformed_no_closing_bracket() {
// content := '![alt text(image.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 0
// }
// // Test malformed markdown - missing opening parenthesis
// fn test_extract_image_links_malformed_no_paren() {
// content := '![alt text]image.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 0
// }
// // Test malformed markdown - missing closing parenthesis
// fn test_extract_image_links_malformed_no_closing_paren() {
// content := '![alt text](image.png'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 0
// }
// // Test empty alt text
// fn test_extract_image_links_empty_alt() {
// content := '![](image.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// assert result[0] == 'image.png'
// }
// // Test alt text with special characters
// fn test_extract_image_links_special_alt() {
// content := '![Logo & Banner - 2024!](logo.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// assert result[0] == 'logo.png'
// }
// // Test image names with special characters
// fn test_extract_image_links_special_names() {
// content := '![img1](logo-2024.png) ![img2](banner_v2.jpg) ![img3](icon.final.svg)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 3
// assert result[0] == 'logo-2024.png'
// assert result[1] == 'banner_v2.jpg'
// assert result[2] == 'icon.final.svg'
// }
// // Test mixed content with text, links, and images
// fn test_extract_image_links_mixed_content() {
// content := '
// # Header
// Some text with [a link](page.md) and an image ![logo](logo.png).
// ## Section
// More text and ![banner](images/banner.jpg) another image.
// [Another link](doc.html)
// ![icon](icon.svg)
// '
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 3
// assert result[0] == 'logo.png'
// assert result[1] == 'banner.jpg'
// assert result[2] == 'icon.svg'
// }
// // Test consecutive images
// fn test_extract_image_links_consecutive() {
// content := '![img1](a.png)![img2](b.jpg)![img3](c.svg)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 3
// assert result[0] == 'a.png'
// assert result[1] == 'b.jpg'
// assert result[2] == 'c.svg'
// }
// // Test images with query parameters
// fn test_extract_image_links_query_params() {
// content := '![img](image.png?size=large&format=webp)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// // Should extract the full filename including query params
// assert result[0].contains('image.png')
// }
// // Test images with anchors
// fn test_extract_image_links_anchors() {
// content := '![img](image.png#section)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// assert result[0].contains('image.png')
// }
// // Test duplicate images
// fn test_extract_image_links_duplicates() {
// content := '![img1](logo.png) some text ![img2](logo.png) more text ![img3](logo.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 3
// assert result[0] == 'logo.png'
// assert result[1] == 'logo.png'
// assert result[2] == 'logo.png'
// }
// // Test very long content
// fn test_extract_image_links_long_content() {
// mut content := ''
// for i in 0 .. 100 {
// content += 'Some text here. '
// if i % 10 == 0 {
// content += '![img${i}](image${i}.png) '
// }
// }
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 10
// }
// // Test image with absolute path
// fn test_extract_image_links_absolute_path() {
// content := '![img](/absolute/path/to/image.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// assert result[0] == 'image.png'
// }
// // Test image with Windows-style path
// fn test_extract_image_links_windows_path() {
// content := '![img](C:\\Users\\images\\logo.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// assert result[0] == 'logo.png'
// }
// // Test nested brackets in alt text
// fn test_extract_image_links_nested_brackets() {
// content := '![alt [with] brackets](image.png)'
// result := extract_image_links(content, false) or { panic(err) }
// // This might not work correctly due to nested brackets
// // The function should handle it gracefully
// assert result.len >= 0
// }
// // Test image link at start of string
// fn test_extract_image_links_at_start() {
// content := '![logo](logo.png) followed by text'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// assert result[0] == 'logo.png'
// }
// // Test image link at end of string
// fn test_extract_image_links_at_end() {
// content := 'text followed by ![logo](logo.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// assert result[0] == 'logo.png'
// }
// // Test only image link
// fn test_extract_image_links_only() {
// content := '![logo](logo.png)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// assert result[0] == 'logo.png'
// }
// // Test whitespace in URL
// fn test_extract_image_links_whitespace() {
// content := '![img]( image.png )'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 1
// // Should preserve whitespace as-is
// assert result[0].contains('image.png')
// }
// // Test case sensitivity
// fn test_extract_image_links_case_sensitivity() {
// content := '![img1](Image.PNG) ![img2](LOGO.jpg) ![img3](banner.SVG)'
// result := extract_image_links(content, false) or { panic(err) }
// assert result.len == 3
// assert result[0] == 'Image.PNG'
// assert result[1] == 'LOGO.jpg'
// assert result[2] == 'banner.SVG'
// }

View File

@@ -1,4 +1,4 @@
module atlas_client module client
import incubaid.herolib.core.pathlib import incubaid.herolib.core.pathlib
import incubaid.herolib.core.texttools import incubaid.herolib.core.texttools
@@ -93,7 +93,8 @@ pub fn (mut c AtlasClient) get_file_path(collection_name string, file_name strin
} }
// Construct the file path // Construct the file path
file_path := os.join_path(c.export_dir, 'content', fixed_collection_name, fixed_file_name) file_path := os.join_path(c.export_dir, 'content', 'files', fixed_collection_name,
fixed_file_name)
// Check if the file exists // Check if the file exists
if !os.exists(file_path) { if !os.exists(file_path) {
@@ -120,7 +121,8 @@ pub fn (mut c AtlasClient) get_image_path(collection_name string, image_name str
} }
// Construct the image path // Construct the image path
image_path := os.join_path(c.export_dir, 'content', fixed_collection_name, fixed_image_name) image_path := os.join_path(c.export_dir, 'content', 'img', fixed_collection_name,
fixed_image_name)
// Check if the image exists // Check if the image exists
if !os.exists(image_path) { if !os.exists(image_path) {
@@ -391,44 +393,44 @@ pub fn (mut c AtlasClient) has_errors(collection_name string) bool {
// get_page_paths returns the path of a page and the paths of its linked images. // get_page_paths returns the path of a page and the paths of its linked images.
// Returns (page_path, image_paths) // Returns (page_path, image_paths)
// This is compatible with the doctreeclient API // This is compatible with the doctreeclient API
pub fn (mut c AtlasClient) get_page_paths(collection_name string, page_name string) !(string, []string) { // pub fn (mut c AtlasClient) get_page_paths(collection_name string, page_name string) !(string, []string) {
// Get the page path // // Get the page path
page_path := c.get_page_path(collection_name, page_name)! // page_path := c.get_page_path(collection_name, page_name)!
page_content := c.get_page_content(collection_name, page_name)! // page_content := c.get_page_content(collection_name, page_name)!
// Extract image names from the page content // // Extract image names from the page content
image_names := extract_image_links(page_content, true)! // image_names := extract_image_links(page_content, true)!
mut image_paths := []string{} // mut image_paths := []string{}
for image_name in image_names { // for image_name in image_names {
// Get the path for each image // // Get the path for each image
image_path := c.get_image_path(collection_name, image_name) or { // image_path := c.get_image_path(collection_name, image_name) or {
// If an image is not found, log a warning and continue, don't fail the whole operation // // If an image is not found, log a warning and continue, don't fail the whole operation
return error('Error: Linked image "${image_name}" not found in collection "${collection_name}". Skipping.') // return error('Error: Linked image "${image_name}" not found in collection "${collection_name}". Skipping.')
} // }
image_paths << image_path // image_paths << image_path
} // }
return page_path, image_paths // return page_path, image_paths
} // }
// copy_images copies all images linked in a page to a destination directory // copy_images copies all images linked in a page to a destination directory
// This is compatible with the doctreeclient API // This is compatible with the doctreeclient API
pub fn (mut c AtlasClient) copy_images(collection_name string, page_name string, destination_path string) ! { // pub fn (mut c AtlasClient) copy_images(collection_name string, page_name string, destination_path string) ! {
// Get the page path and linked image paths // // Get the page path and linked image paths
_, image_paths := c.get_page_paths(collection_name, page_name)! // _, image_paths := c.get_page_paths(collection_name, page_name)!
// Ensure the destination directory exists // // Ensure the destination directory exists
os.mkdir_all(destination_path)! // os.mkdir_all(destination_path)!
// Create an 'img' subdirectory within the destination // // Create an 'img' subdirectory within the destination
images_dest_path := os.join_path(destination_path, 'img') // images_dest_path := os.join_path(destination_path, 'img')
os.mkdir_all(images_dest_path)! // os.mkdir_all(images_dest_path)!
// Copy each linked image // // Copy each linked image
for image_path in image_paths { // for image_path in image_paths {
image_file_name := os.base(image_path) // image_file_name := os.base(image_path)
dest_image_path := os.join_path(images_dest_path, image_file_name) // dest_image_path := os.join_path(images_dest_path, image_file_name)
os.cp(image_path, dest_image_path)! // os.cp(image_path, dest_image_path)!
} // }
} // }

View File

@@ -1,4 +1,4 @@
module atlas_client module client
import os import os
import incubaid.herolib.core.texttools { name_fix_no_underscore_no_ext } import incubaid.herolib.core.texttools { name_fix_no_underscore_no_ext }

View File

@@ -1,4 +1,4 @@
module atlas_client module client
// AtlasErrors represents different types of errors that can occur in AtlasClient // AtlasErrors represents different types of errors that can occur in AtlasClient
pub enum AtlasErrors { pub enum AtlasErrors {

View File

@@ -1,4 +1,4 @@
module atlas_client module client
// Test error_collection_not_found // Test error_collection_not_found
fn test_error_collection_not_found() { fn test_error_collection_not_found() {

View File

@@ -1,4 +1,4 @@
module atlas_client module client
import incubaid.herolib.core.base import incubaid.herolib.core.base

View File

@@ -1,4 +1,4 @@
module atlas_client module client
import incubaid.herolib.core.redisclient import incubaid.herolib.core.redisclient

View File

@@ -122,7 +122,17 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! {
// Copy all files/images from this collection to the export directory // Copy all files/images from this collection to the export directory
for _, mut file in c.files { for _, mut file in c.files {
mut src_file := file.path()! mut src_file := file.path()!
mut dest_path := '${col_dir.path}/${file.file_name()}'
// Determine subdirectory based on file type
mut subdir := if file.is_image() { 'img' } else { 'files' }
// Ensure subdirectory exists
mut subdir_path := pathlib.get_dir(
path: '${col_dir.path}/${subdir}'
create: true
)!
mut dest_path := '${subdir_path.path}/${file.file_name()}'
mut dest_file := pathlib.get_file(path: dest_path, create: true)! mut dest_file := pathlib.get_file(path: dest_path, create: true)!
src_file.copy(dest: dest_file.path)! src_file.copy(dest: dest_file.path)!
} }
@@ -144,7 +154,17 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! {
// Third pass: copy cross-collection referenced files/images to make collection self-contained // Third pass: copy cross-collection referenced files/images to make collection self-contained
for _, mut ref_file in cross_collection_files { for _, mut ref_file in cross_collection_files {
mut src_file := ref_file.path()! mut src_file := ref_file.path()!
mut dest_path := '${col_dir.path}/${ref_file.file_name()}'
// Determine subdirectory based on file type
mut subdir := if ref_file.is_image() { 'img' } else { 'files' }
// Ensure subdirectory exists
mut subdir_path := pathlib.get_dir(
path: '${col_dir.path}/${subdir}'
create: true
)!
mut dest_path := '${subdir_path.path}/${ref_file.file_name()}'
mut dest_file := pathlib.get_file(path: dest_path, create: true)! mut dest_file := pathlib.get_file(path: dest_path, create: true)!
src_file.copy(dest: dest_file.path)! src_file.copy(dest: dest_file.path)!
} }

View File

@@ -251,19 +251,14 @@ fn (mut p Page) content_with_fixed_links(args FixLinksArgs) !string {
// export_link_path calculates path for export (self-contained: all references are local) // export_link_path calculates path for export (self-contained: all references are local)
fn (mut p Page) export_link_path(mut link Link) !string { fn (mut p Page) export_link_path(mut link Link) !string {
mut target_filename := ''
if link.is_file_link { if link.is_file_link {
mut tf := link.target_file()! mut tf := link.target_file()!
// Use file_name() to include the extension mut subdir := if tf.is_image() { 'img' } else { 'files' }
target_filename = tf.file_name() return '${subdir}/${tf.file_name()}'
} else { } else {
mut tp := link.target_page()! mut tp := link.target_page()!
target_filename = '${tp.name}.md' return '${tp.name}.md'
} }
// For self-contained exports, all links are local (cross-collection pages are copied)
return target_filename
} }
// filesystem_link_path calculates path using actual filesystem paths // filesystem_link_path calculates path using actual filesystem paths

View File

@@ -1,55 +0,0 @@
module atlas_client
import os
// extract_image_links extracts image file names from markdown content
// If exclude_http is true, it will skip images with http:// or https:// URLs
pub fn extract_image_links(s string, exclude_http bool) ![]string {
mut result := []string{}
mut current_pos := 0
for {
if current_pos >= s.len {
break
}
// Find the start of an image markdown link
start_index := s.index_after('![', current_pos) or { -1 }
if start_index == -1 {
break // No more image links found
}
// Find the closing bracket for alt text
alt_end_index := s.index_after(']', start_index) or { -1 }
if alt_end_index == -1 {
break
}
// Check for opening parenthesis for URL
if alt_end_index + 1 >= s.len || s[alt_end_index + 1] != `(` {
current_pos = alt_end_index + 1 // Move past this invalid sequence
continue
}
// Find the closing parenthesis for URL
url_start_index := alt_end_index + 2
url_end_index := s.index_after(')', url_start_index) or { -1 }
if url_end_index == -1 {
break
}
// Extract the URL
url := s[url_start_index..url_end_index]
if exclude_http && (url.starts_with('http://') || url.starts_with('https://')) {
current_pos = url_end_index + 1
continue
}
// Extract only the base name of the image from the URL
image_base_name := os.base(url)
result << image_base_name
// Move current_pos past the found link to continue searching
current_pos = url_end_index + 1
}
return result
}

View File

@@ -1,298 +0,0 @@
module atlas_client
// Test basic image link extraction
fn test_extract_image_links_basic() {
content := '![alt text](image.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
assert result[0] == 'image.png'
}
// Test multiple image links
fn test_extract_image_links_multiple() {
content := '![logo](logo.png) some text ![banner](banner.jpg) more text ![icon](icon.svg)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 3
assert result[0] == 'logo.png'
assert result[1] == 'banner.jpg'
assert result[2] == 'icon.svg'
}
// Test empty content
fn test_extract_image_links_empty() {
content := ''
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 0
}
// Test content with no images
fn test_extract_image_links_no_images() {
content := 'This is just plain text with no images'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 0
}
// Test content with regular links (not images)
fn test_extract_image_links_regular_links() {
content := '[regular link](page.md) and [another](doc.html)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 0
}
// Test HTTP URLs with exclude_http = true
fn test_extract_image_links_exclude_http() {
content := '![local](local.png) ![remote](http://example.com/image.jpg) ![https](https://example.com/logo.png)'
result := extract_image_links(content, true) or { panic(err) }
assert result.len == 1
assert result[0] == 'local.png'
}
// Test HTTP URLs with exclude_http = false
fn test_extract_image_links_include_http() {
content := '![local](local.png) ![remote](http://example.com/image.jpg) ![https](https://example.com/logo.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 3
assert result[0] == 'local.png'
assert result[1] == 'image.jpg'
assert result[2] == 'logo.png'
}
// Test image paths with directories
fn test_extract_image_links_with_paths() {
content := '![img1](images/logo.png) ![img2](../assets/banner.jpg) ![img3](./icons/icon.svg)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 3
assert result[0] == 'logo.png'
assert result[1] == 'banner.jpg'
assert result[2] == 'icon.svg'
}
// Test various image formats
fn test_extract_image_links_formats() {
content := '![png](img.png) ![jpg](img.jpg) ![jpeg](img.jpeg) ![gif](img.gif) ![svg](img.svg) ![webp](img.webp) ![bmp](img.bmp)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 7
assert 'img.png' in result
assert 'img.jpg' in result
assert 'img.jpeg' in result
assert 'img.gif' in result
assert 'img.svg' in result
assert 'img.webp' in result
assert 'img.bmp' in result
}
// Test malformed markdown - missing closing bracket
fn test_extract_image_links_malformed_no_closing_bracket() {
content := '![alt text(image.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 0
}
// Test malformed markdown - missing opening parenthesis
fn test_extract_image_links_malformed_no_paren() {
content := '![alt text]image.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 0
}
// Test malformed markdown - missing closing parenthesis
fn test_extract_image_links_malformed_no_closing_paren() {
content := '![alt text](image.png'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 0
}
// Test empty alt text
fn test_extract_image_links_empty_alt() {
content := '![](image.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
assert result[0] == 'image.png'
}
// Test alt text with special characters
fn test_extract_image_links_special_alt() {
content := '![Logo & Banner - 2024!](logo.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
assert result[0] == 'logo.png'
}
// Test image names with special characters
fn test_extract_image_links_special_names() {
content := '![img1](logo-2024.png) ![img2](banner_v2.jpg) ![img3](icon.final.svg)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 3
assert result[0] == 'logo-2024.png'
assert result[1] == 'banner_v2.jpg'
assert result[2] == 'icon.final.svg'
}
// Test mixed content with text, links, and images
fn test_extract_image_links_mixed_content() {
content := '
# Header
Some text with [a link](page.md) and an image ![logo](logo.png).
## Section
More text and ![banner](images/banner.jpg) another image.
[Another link](doc.html)
![icon](icon.svg)
'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 3
assert result[0] == 'logo.png'
assert result[1] == 'banner.jpg'
assert result[2] == 'icon.svg'
}
// Test consecutive images
fn test_extract_image_links_consecutive() {
content := '![img1](a.png)![img2](b.jpg)![img3](c.svg)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 3
assert result[0] == 'a.png'
assert result[1] == 'b.jpg'
assert result[2] == 'c.svg'
}
// Test images with query parameters
fn test_extract_image_links_query_params() {
content := '![img](image.png?size=large&format=webp)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
// Should extract the full filename including query params
assert result[0].contains('image.png')
}
// Test images with anchors
fn test_extract_image_links_anchors() {
content := '![img](image.png#section)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
assert result[0].contains('image.png')
}
// Test duplicate images
fn test_extract_image_links_duplicates() {
content := '![img1](logo.png) some text ![img2](logo.png) more text ![img3](logo.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 3
assert result[0] == 'logo.png'
assert result[1] == 'logo.png'
assert result[2] == 'logo.png'
}
// Test very long content
fn test_extract_image_links_long_content() {
mut content := ''
for i in 0 .. 100 {
content += 'Some text here. '
if i % 10 == 0 {
content += '![img${i}](image${i}.png) '
}
}
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 10
}
// Test image with absolute path
fn test_extract_image_links_absolute_path() {
content := '![img](/absolute/path/to/image.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
assert result[0] == 'image.png'
}
// Test image with Windows-style path
fn test_extract_image_links_windows_path() {
content := '![img](C:\\Users\\images\\logo.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
assert result[0] == 'logo.png'
}
// Test nested brackets in alt text
fn test_extract_image_links_nested_brackets() {
content := '![alt [with] brackets](image.png)'
result := extract_image_links(content, false) or { panic(err) }
// This might not work correctly due to nested brackets
// The function should handle it gracefully
assert result.len >= 0
}
// Test image link at start of string
fn test_extract_image_links_at_start() {
content := '![logo](logo.png) followed by text'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
assert result[0] == 'logo.png'
}
// Test image link at end of string
fn test_extract_image_links_at_end() {
content := 'text followed by ![logo](logo.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
assert result[0] == 'logo.png'
}
// Test only image link
fn test_extract_image_links_only() {
content := '![logo](logo.png)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
assert result[0] == 'logo.png'
}
// Test whitespace in URL
fn test_extract_image_links_whitespace() {
content := '![img]( image.png )'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 1
// Should preserve whitespace as-is
assert result[0].contains('image.png')
}
// Test case sensitivity
fn test_extract_image_links_case_sensitivity() {
content := '![img1](Image.PNG) ![img2](LOGO.jpg) ![img3](banner.SVG)'
result := extract_image_links(content, false) or { panic(err) }
assert result.len == 3
assert result[0] == 'Image.PNG'
assert result[1] == 'LOGO.jpg'
assert result[2] == 'banner.SVG'
}

View File

@@ -1,20 +1,19 @@
module docusaurus module docusaurus
import incubaid.herolib.core.pathlib import incubaid.herolib.core.pathlib
import incubaid.herolib.web.atlas_client import incubaid.herolib.data.atlas.client
import incubaid.herolib.web.doctreeclient
import incubaid.herolib.web.site { Page, Section, Site } import incubaid.herolib.web.site { Page, Section, Site }
import incubaid.herolib.data.markdown.tools as markdowntools import incubaid.herolib.data.markdown.tools as markdowntools
import incubaid.herolib.ui.console import incubaid.herolib.ui.console
// THIS CODE GENERATES A DOCUSAURUS SITE FROM A DOCUMENT CLIENT AND SITE DEFINITION // THIS CODE GENERATES A DOCUSAURUS SITE FROM A DOCUMENT CLIENT AND SITE DEFINITION
// Supports both atlas_client and doctreeclient through the unified IDocClient interface // Supports both atlas.client and doctreeclient through the unified IDocClient interface
// IDocClient defines the common interface that both atlas_client and doctreeclient implement // IDocClient defines the common interface that both atlas.client and doctreeclient implement
// This allows the Docusaurus module to work with either client transparently // This allows the Docusaurus module to work with either client transparently
// //
// Note: V interfaces require exact signature matching, so all methods use `mut` receivers // Note: V interfaces require exact signature matching, so all methods use `mut` receivers
// to match the implementation in both atlas_client and doctreeclient // to match the implementation in both atlas.client and doctreeclient
pub interface IDocClient { pub interface IDocClient {
mut: mut:
// Path methods - get absolute paths to resources // Path methods - get absolute paths to resources
@@ -61,16 +60,7 @@ pub fn (mut docsite DocSite) generate_docs() ! {
docs_path := '${c.path_build.path}/docs' docs_path := '${c.path_build.path}/docs'
// Create the appropriate client based on configuration // Create the appropriate client based on configuration
mut client := if c.use_atlas { mut client := IDocClient(atlas.client.new(export_dir: c.atlas_export_dir)!)
// Use atlas_client for filesystem-based access
if c.atlas_export_dir == '' {
return error('atlas_export_dir is required when use_atlas is true')
}
IDocClient(atlas_client.new(export_dir: c.atlas_export_dir)!)
} else {
// Use doctreeclient for Redis-based access
IDocClient(doctreeclient.new()!)
}
mut gen := SiteGenerator{ mut gen := SiteGenerator{
path: pathlib.get_dir(path: docs_path, create: true)! path: pathlib.get_dir(path: docs_path, create: true)!

View File

@@ -19,8 +19,7 @@ pub fn play(mut plbook PlayBook) ! {
reset: param_define.get_default_false('reset') reset: param_define.get_default_false('reset')
template_update: param_define.get_default_false('template_update') template_update: param_define.get_default_false('template_update')
install: param_define.get_default_false('install') install: param_define.get_default_false('install')
use_atlas: param_define.get_default_true('use_atlas') atlas_dir: param_define.get_default('atlas_dir', '')!
atlas_export_dir: param_define.get_default('atlas_export_dir', '')!
)! )!
site_name := param_define.get('name') or { site_name := param_define.get('name') or {