feat: Support atlas_client module:

- Add client for atlas module
- Add unit tests to test the workflow
- Remove println statements from file_or_image_exists
- Remove println statements from link processing loop
This commit is contained in:
Mahmoud-Emad
2025-11-04 15:53:31 +02:00
parent ecfe77a2dc
commit 8bfb021939
11 changed files with 2010 additions and 3 deletions

View File

@@ -148,7 +148,7 @@ pub fn (c Collection) file_exists(name string) bool {
}
pub fn (c Collection) file_or_image_exists(name string) bool {
f := c.files[name] or { return false }
_ := c.files[name] or { return false }
return true
}

View File

@@ -118,7 +118,6 @@ fn (mut p Page) find_links(content string) ![]Link {
link.is_file_link = false
link.is_image_link = false
}
println(link)
links << link
pos = close_paren + 1
@@ -232,7 +231,6 @@ fn (mut p Page) process_links(mut export_dir pathlib.Path) !string {
// Process links in reverse order to maintain string positions
for mut link in links.reverse() {
println(link)
if link.status != .found {
continue
}

View File

@@ -0,0 +1,93 @@
# AtlasClient
A simple API for accessing document collections exported by the `atlas` module.
## What It Does
AtlasClient provides methods to:
- List collections, pages, files, and images
- Check if resources exist
- Get file paths and content
- Access metadata (links, errors)
- Copy images from pages
## Quick Start
```v
import incubaid.herolib.web.atlas_client
// Create client
mut client := atlas_client.new(export_dir: '/tmp/atlas_export')!
// List collections
collections := client.list_collections()!
// Get page content
content := client.get_page_content('my_collection', 'page_name')!
// Check for errors
if client.has_errors('my_collection')! {
errors := client.get_collection_errors('my_collection')!
}
```
## Export Structure
Atlas exports to this structure:
```txt
export_dir/
├── content/
│ └── collection_name/
│ ├── page.md
│ ├── image.png
│ └── file.pdf
└── meta/
└── collection_name.json
```
## Key Methods
**Collections:**
- `list_collections()` - List all collections
**Pages:**
- `list_pages(collection)` - List pages in collection
- `page_exists(collection, page)` - Check if page exists
- `get_page_content(collection, page)` - Get page markdown content
- `get_page_path(collection, page)` - Get page file path
**Files & Images:**
- `list_files(collection)` - List non-page, non-image files
- `list_images(collection)` - List image files
- `get_file_path(collection, file)` - Get file path
- `get_image_path(collection, image)` - Get image path
- `copy_images(collection, page, dest)` - Copy page images to dest/img/
**Metadata:**
- `get_collection_metadata(collection)` - Get full metadata
- `get_page_links(collection, page)` - Get links from page
- `get_collection_errors(collection)` - Get collection errors
- `has_errors(collection)` - Check if collection has errors
## Naming Convention
Names are normalized using `name_fix_no_underscore_no_ext()`:
- `My_Page-Name.md``mypagename`
- Removes: underscores, dashes, special chars, extensions
- Converts to lowercase
## Example
See `examples/data/atlas_client/basic_usage.vsh` for a complete working example.
## See Also
- `lib/data/atlas/` - Atlas module for exporting collections
- `lib/web/doctreeclient/` - Alternative client for doctree collections

View File

@@ -0,0 +1,422 @@
module atlas_client
import incubaid.herolib.core.pathlib
import incubaid.herolib.core.texttools
import os
import json
// List of recognized image file extensions
const image_extensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.bmp', '.tiff', '.ico']
// CollectionMetadata represents the metadata stored in meta/{collection}.json
pub struct CollectionMetadata {
pub mut:
name string
path string
pages map[string]PageMetadata
files map[string]FileMetadata
errors []ErrorMetadata
}
pub struct PageMetadata {
pub mut:
name string
path string
collection_name string
links []LinkMetadata
}
pub struct FileMetadata {
pub mut:
name string
path string
}
pub struct LinkMetadata {
pub mut:
src string
text string
target string
line int
target_collection_name string
target_item_name string
status string
is_file_link bool
is_image_link bool
}
pub struct ErrorMetadata {
pub mut:
category string
page_key string
message string
line int
}
// get_page_path returns the path for a page in a collection
// Pages are stored in {export_dir}/content/{collection}/{page}.md
pub fn (mut c AtlasClient) get_page_path(collection_name string, page_name string) !string {
// Apply name normalization (atlas uses name_fix_no_underscore_no_ext)
fixed_collection_name := texttools.name_fix_no_underscore_no_ext(collection_name)
fixed_page_name := texttools.name_fix_no_underscore_no_ext(page_name)
// Check if export directory exists
if !os.exists(c.export_dir) {
return c.error_export_dir_not_found(export_dir: c.export_dir)
}
// Construct the page path
page_path := os.join_path(c.export_dir, 'content', fixed_collection_name, '${fixed_page_name}.md')
// Check if the page file exists
if !os.exists(page_path) {
return c.error_page_not_found(
collection_name: collection_name
page_name: page_name
)
}
return page_path
}
// get_file_path returns the path for a file in a collection
// Files are stored in {export_dir}/content/{collection}/{filename}
pub fn (mut c AtlasClient) get_file_path(collection_name string, file_name string) !string {
// Apply name normalization
fixed_collection_name := texttools.name_fix_no_underscore_no_ext(collection_name)
// Files keep their original names with extensions
fixed_file_name := texttools.name_fix_keepext(file_name)
// Check if export directory exists
if !os.exists(c.export_dir) {
return c.error_export_dir_not_found(export_dir: c.export_dir)
}
// Construct the file path
file_path := os.join_path(c.export_dir, 'content', fixed_collection_name, fixed_file_name)
// Check if the file exists
if !os.exists(file_path) {
return c.error_file_not_found(
collection_name: collection_name
file_name: file_name
)
}
return file_path
}
// get_image_path returns the path for an image in a collection
// Images are stored in {export_dir}/content/{collection}/{imagename}
pub fn (mut c AtlasClient) get_image_path(collection_name string, image_name string) !string {
// Apply name normalization
fixed_collection_name := texttools.name_fix_no_underscore_no_ext(collection_name)
// Images keep their original names with extensions
fixed_image_name := texttools.name_fix_keepext(image_name)
// Check if export directory exists
if !os.exists(c.export_dir) {
return c.error_export_dir_not_found(export_dir: c.export_dir)
}
// Construct the image path
image_path := os.join_path(c.export_dir, 'content', fixed_collection_name, fixed_image_name)
// Check if the image exists
if !os.exists(image_path) {
return c.error_image_not_found(
collection_name: collection_name
image_name: image_name
)
}
return image_path
}
// page_exists checks if a page exists in a collection
pub fn (mut c AtlasClient) page_exists(collection_name string, page_name string) bool {
// Try to get the page path - if it succeeds, the page exists
_ := c.get_page_path(collection_name, page_name) or { return false }
return true
}
// file_exists checks if a file exists in a collection
pub fn (mut c AtlasClient) file_exists(collection_name string, file_name string) bool {
// Try to get the file path - if it succeeds, the file exists
_ := c.get_file_path(collection_name, file_name) or { return false }
return true
}
// image_exists checks if an image exists in a collection
pub fn (mut c AtlasClient) image_exists(collection_name string, image_name string) bool {
// Try to get the image path - if it succeeds, the image exists
_ := c.get_image_path(collection_name, image_name) or { return false }
return true
}
// get_page_content returns the content of a page in a collection
pub fn (mut c AtlasClient) get_page_content(collection_name string, page_name string) !string {
// Get the path for the page
page_path := c.get_page_path(collection_name, page_name)!
// Use pathlib to read the file content
mut path := pathlib.get_file(path: page_path)!
// Check if the file exists
if !path.exists() {
return c.error_page_file_not_exists(page_path: page_path)
}
// Read and return the file content
return path.read()!
}
// list_collections returns a list of all collection names
// Collections are directories in {export_dir}/content/
pub fn (mut c AtlasClient) list_collections() ![]string {
content_dir := os.join_path(c.export_dir, 'content')
// Check if content directory exists
if !os.exists(content_dir) {
return c.error_invalid_export_structure(content_dir: content_dir)
}
// Get all subdirectories in content/
mut collections := []string{}
entries := os.ls(content_dir)!
for entry in entries {
entry_path := os.join_path(content_dir, entry)
if os.is_dir(entry_path) {
collections << entry
}
}
return collections
}
// list_pages returns a list of all page names in a collection
// Uses metadata to get the authoritative list of pages that belong to this collection
pub fn (mut c AtlasClient) list_pages(collection_name string) ![]string {
// Get metadata which contains the authoritative list of pages
metadata := c.get_collection_metadata(collection_name)!
// Extract page names from metadata
mut page_names := []string{}
for page_name, _ in metadata.pages {
page_names << page_name
}
return page_names
}
// list_files returns a list of all file names in a collection (excluding pages and images)
pub fn (mut c AtlasClient) list_files(collection_name string) ![]string {
// Apply name normalization
fixed_collection_name := texttools.name_fix_no_underscore_no_ext(collection_name)
collection_dir := os.join_path(c.export_dir, 'content', fixed_collection_name)
// Check if collection directory exists
if !os.exists(collection_dir) {
return c.error_collection_not_found(collection_name: collection_name)
}
// Get all files that are not .md and not images
mut file_names := []string{}
entries := os.ls(collection_dir)!
for entry in entries {
entry_path := os.join_path(collection_dir, entry)
// Skip directories
if os.is_dir(entry_path) {
continue
}
// Skip .md files (pages)
if entry.ends_with('.md') {
continue
}
// Check if it's an image
mut is_image := false
for ext in image_extensions {
if entry.ends_with(ext) {
is_image = true
break
}
}
// Add to file_names if it's not an image
if !is_image {
file_names << entry
}
}
return file_names
}
// list_images returns a list of all image names in a collection
pub fn (mut c AtlasClient) list_images(collection_name string) ![]string {
// Apply name normalization
fixed_collection_name := texttools.name_fix_no_underscore_no_ext(collection_name)
collection_dir := os.join_path(c.export_dir, 'content', fixed_collection_name)
// Check if collection directory exists
if !os.exists(collection_dir) {
return c.error_collection_not_found(collection_name: collection_name)
}
// Get all image files
mut image_names := []string{}
entries := os.ls(collection_dir)!
for entry in entries {
entry_path := os.join_path(collection_dir, entry)
// Skip directories
if os.is_dir(entry_path) {
continue
}
// Check if it's an image
for ext in image_extensions {
if entry.ends_with(ext) {
image_names << entry
break
}
}
}
return image_names
}
// list_pages_map returns a map of collection names to a list of page names within that collection.
// The structure is map[collectionname][]pagename.
pub fn (mut c AtlasClient) list_pages_map() !map[string][]string {
mut result := map[string][]string{}
collections := c.list_collections()!
for col_name in collections {
mut page_names := c.list_pages(col_name)!
page_names.sort()
result[col_name] = page_names
}
return result
}
// list_markdown returns the collections and their pages in markdown format.
pub fn (mut c AtlasClient) list_markdown() !string {
mut markdown_output := ''
pages_map := c.list_pages_map()!
if pages_map.len == 0 {
return 'No collections or pages found in this atlas export.'
}
mut sorted_collections := pages_map.keys()
sorted_collections.sort()
for col_name in sorted_collections {
page_names := pages_map[col_name]
markdown_output += '## ${col_name}\n'
if page_names.len == 0 {
markdown_output += ' * No pages in this collection.\n'
} else {
for page_name in page_names {
markdown_output += ' * ${page_name}\n'
}
}
markdown_output += '\n' // Add a newline for spacing between collections
}
return markdown_output
}
// get_collection_metadata reads and parses the metadata JSON file for a collection
// Metadata is stored in {export_dir}/meta/{collection}.json
pub fn (mut c AtlasClient) get_collection_metadata(collection_name string) !CollectionMetadata {
// Apply name normalization
fixed_collection_name := texttools.name_fix_no_underscore_no_ext(collection_name)
meta_path := os.join_path(c.export_dir, 'meta', '${fixed_collection_name}.json')
// Check if metadata file exists
if !os.exists(meta_path) {
return c.error_collection_not_found_at(
collection_name: collection_name
path: meta_path
)
}
// Read and parse the JSON file
content := os.read_file(meta_path)!
metadata := json.decode(CollectionMetadata, content)!
return metadata
}
// get_page_links returns the links found in a page by reading the metadata
pub fn (mut c AtlasClient) get_page_links(collection_name string, page_name string) ![]LinkMetadata {
// Get collection metadata
metadata := c.get_collection_metadata(collection_name)!
// Apply name normalization to page name
fixed_page_name := texttools.name_fix_no_underscore_no_ext(page_name)
// Find the page in metadata
if fixed_page_name in metadata.pages {
return metadata.pages[fixed_page_name].links
}
return c.error_page_not_found_in_metadata(
collection_name: collection_name
page_name: page_name
)
}
// get_collection_errors returns the errors for a collection from metadata
pub fn (mut c AtlasClient) get_collection_errors(collection_name string) ![]ErrorMetadata {
metadata := c.get_collection_metadata(collection_name)!
return metadata.errors
}
// has_errors checks if a collection has any errors
pub fn (mut c AtlasClient) has_errors(collection_name string) bool {
errors := c.get_collection_errors(collection_name) or { return false }
return errors.len > 0
}
// copy_images copies all images linked in a page to a destination directory
// This is compatible with the doctreeclient API
pub fn (mut c AtlasClient) copy_images(collection_name string, page_name string, destination_path string) ! {
// Get page content
page_content := c.get_page_content(collection_name, page_name)!
// Extract image links from content
image_names := extract_image_links(page_content, true)!
// Ensure the destination directory exists
os.mkdir_all(destination_path)!
// Create an 'img' subdirectory within the destination
images_dest_path := os.join_path(destination_path, 'img')
os.mkdir_all(images_dest_path)!
// Copy each linked image
for image_name in image_names {
// Get the image path
image_path := c.get_image_path(collection_name, image_name) or {
// If an image is not found, return an error
return c.error_image_not_found_linked(
collection_name: collection_name
image_name: image_name
)
}
image_file_name := os.base(image_path)
dest_image_path := os.join_path(images_dest_path, image_file_name)
os.cp(image_path, dest_image_path)!
}
}

View File

@@ -0,0 +1,670 @@
module atlas_client
import os
import incubaid.herolib.core.texttools { name_fix_no_underscore_no_ext }
// Helper function to create a test export directory structure
fn setup_test_export() string {
test_dir := os.join_path(os.temp_dir(), 'atlas_client_test_${os.getpid()}')
// Clean up if exists
if os.exists(test_dir) {
os.rmdir_all(test_dir) or {}
}
// Create directory structure
os.mkdir_all(os.join_path(test_dir, 'content', 'testcollection')) or { panic(err) }
os.mkdir_all(os.join_path(test_dir, 'content', 'anothercollection')) or { panic(err) }
os.mkdir_all(os.join_path(test_dir, 'meta')) or { panic(err) }
// Create test pages
os.write_file(os.join_path(test_dir, 'content', 'testcollection', 'page1.md'), '# Page 1\n\nContent here.') or {
panic(err)
}
os.write_file(os.join_path(test_dir, 'content', 'testcollection', 'page2.md'), '# Page 2\n\n![logo](logo.png)') or {
panic(err)
}
os.write_file(os.join_path(test_dir, 'content', 'anothercollection', 'intro.md'),
'# Intro\n\nWelcome!') or { panic(err) }
// Create test images
os.write_file(os.join_path(test_dir, 'content', 'testcollection', 'logo.png'), 'fake png data') or {
panic(err)
}
os.write_file(os.join_path(test_dir, 'content', 'testcollection', 'banner.jpg'), 'fake jpg data') or {
panic(err)
}
// Create test files
os.write_file(os.join_path(test_dir, 'content', 'testcollection', 'data.csv'), 'col1,col2\nval1,val2') or {
panic(err)
}
// Create metadata files
metadata1 := '{
"name": "testcollection",
"path": "",
"pages": {
"page1": {
"name": "page1",
"path": "",
"collection_name": "testcollection",
"links": []
},
"page2": {
"name": "page2",
"path": "",
"collection_name": "testcollection",
"links": [
{
"src": "logo.png",
"text": "logo",
"target": "logo.png",
"line": 3,
"target_collection_name": "testcollection",
"target_item_name": "logo",
"status": "ok",
"is_file_link": false,
"is_image_link": true
}
]
}
},
"files": {},
"errors": []
}'
os.write_file(os.join_path(test_dir, 'meta', 'testcollection.json'), metadata1) or {
panic(err)
}
metadata2 := '{
"name": "anothercollection",
"path": "",
"pages": {
"intro": {
"name": "intro",
"path": "",
"collection_name": "anothercollection",
"links": []
}
},
"files": {},
"errors": [
{
"category": "test",
"page_key": "intro",
"message": "Test error",
"line": 10
}
]
}'
os.write_file(os.join_path(test_dir, 'meta', 'anothercollection.json'), metadata2) or {
panic(err)
}
return test_dir
}
// Helper function to cleanup test directory
fn cleanup_test_export(test_dir string) {
os.rmdir_all(test_dir) or {}
}
// Test creating a new client
fn test_new_client() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
assert client.export_dir == test_dir
}
// Test creating client with non-existent directory
fn test_new_client_nonexistent_dir() {
mut client := new(export_dir: '/nonexistent/path/to/export') or { panic(err) }
// Client creation should succeed, but operations will fail
assert client.export_dir == '/nonexistent/path/to/export'
}
// Test get_page_path - success
fn test_get_page_path_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
path := client.get_page_path('testcollection', 'page1') or { panic(err) }
assert path.contains('testcollection')
assert path.ends_with('page1.md')
assert os.exists(path)
}
// Test get_page_path - with naming normalization
fn test_get_page_path_normalization() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
// Create a page with normalized name
normalized_name := name_fix_no_underscore_no_ext('Test_Page-Name')
os.write_file(os.join_path(test_dir, 'content', 'testcollection', '${normalized_name}.md'),
'# Test') or { panic(err) }
mut client := new(export_dir: test_dir) or { panic(err) }
// Should find the page regardless of input format
path := client.get_page_path('testcollection', 'Test_Page-Name') or { panic(err) }
assert os.exists(path)
}
// Test get_page_path - page not found
fn test_get_page_path_not_found() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
client.get_page_path('testcollection', 'nonexistent') or {
assert err.msg().contains('page_not_found')
assert err.msg().contains('nonexistent')
return
}
assert false, 'Should have returned an error'
}
// Test get_page_path - export dir not found
fn test_get_page_path_no_export_dir() {
mut client := new(export_dir: '/nonexistent/path') or { panic(err) }
client.get_page_path('testcollection', 'page1') or {
assert err.msg().contains('export_dir_not_found')
return
}
assert false, 'Should have returned an error'
}
// Test get_file_path - success
fn test_get_file_path_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
path := client.get_file_path('testcollection', 'data.csv') or { panic(err) }
assert path.contains('testcollection')
assert path.ends_with('data.csv')
assert os.exists(path)
}
// Test get_file_path - file not found
fn test_get_file_path_not_found() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
client.get_file_path('testcollection', 'missing.pdf') or {
assert err.msg().contains('file_not_found')
assert err.msg().contains('missing.pdf')
return
}
assert false, 'Should have returned an error'
}
// Test get_image_path - success
fn test_get_image_path_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
path := client.get_image_path('testcollection', 'logo.png') or { panic(err) }
assert path.contains('testcollection')
assert path.ends_with('logo.png')
assert os.exists(path)
}
// Test get_image_path - image not found
fn test_get_image_path_not_found() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
client.get_image_path('testcollection', 'missing.jpg') or {
assert err.msg().contains('image_not_found')
assert err.msg().contains('missing.jpg')
return
}
assert false, 'Should have returned an error'
}
// Test page_exists - true
fn test_page_exists_true() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
exists := client.page_exists('testcollection', 'page1')
assert exists == true
}
// Test page_exists - false
fn test_page_exists_false() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
exists := client.page_exists('testcollection', 'nonexistent')
assert exists == false
}
// Test file_exists - true
fn test_file_exists_true() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
exists := client.file_exists('testcollection', 'data.csv')
assert exists == true
}
// Test file_exists - false
fn test_file_exists_false() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
exists := client.file_exists('testcollection', 'missing.pdf')
assert exists == false
}
// Test image_exists - true
fn test_image_exists_true() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
exists := client.image_exists('testcollection', 'logo.png')
assert exists == true
}
// Test image_exists - false
fn test_image_exists_false() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
exists := client.image_exists('testcollection', 'missing.svg')
assert exists == false
}
// Test get_page_content - success
fn test_get_page_content_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
content := client.get_page_content('testcollection', 'page1') or { panic(err) }
assert content.contains('# Page 1')
assert content.contains('Content here.')
}
// Test get_page_content - page not found
fn test_get_page_content_not_found() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
client.get_page_content('testcollection', 'nonexistent') or {
assert err.msg().contains('page_not_found')
return
}
assert false, 'Should have returned an error'
}
// Test list_collections
fn test_list_collections() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
collections := client.list_collections() or { panic(err) }
assert collections.len == 2
assert 'testcollection' in collections
assert 'anothercollection' in collections
}
// Test list_collections - no content dir
fn test_list_collections_no_content_dir() {
test_dir := os.join_path(os.temp_dir(), 'empty_export_${os.getpid()}')
os.mkdir_all(test_dir) or { panic(err) }
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
client.list_collections() or {
assert err.msg().contains('invalid_export_structure')
return
}
assert false, 'Should have returned an error'
}
// Test list_pages - success
fn test_list_pages_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
pages := client.list_pages('testcollection') or { panic(err) }
assert pages.len == 2
assert 'page1' in pages
assert 'page2' in pages
}
// Test list_pages - collection not found
fn test_list_pages_collection_not_found() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
client.list_pages('nonexistent') or {
assert err.msg().contains('collection_not_found')
return
}
assert false, 'Should have returned an error'
}
// Test list_files - success
fn test_list_files_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
files := client.list_files('testcollection') or { panic(err) }
assert files.len == 1
assert 'data.csv' in files
}
// Test list_files - no files
fn test_list_files_empty() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
files := client.list_files('anothercollection') or { panic(err) }
assert files.len == 0
}
// Test list_images - success
fn test_list_images_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
images := client.list_images('testcollection') or { panic(err) }
assert images.len == 2
assert 'logo.png' in images
assert 'banner.jpg' in images
}
// Test list_images - no images
fn test_list_images_empty() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
images := client.list_images('anothercollection') or { panic(err) }
assert images.len == 0
}
// Test list_pages_map
fn test_list_pages_map() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
pages_map := client.list_pages_map() or { panic(err) }
assert pages_map.len == 2
assert 'testcollection' in pages_map
assert 'anothercollection' in pages_map
assert pages_map['testcollection'].len == 2
assert pages_map['anothercollection'].len == 1
}
// Test list_markdown
fn test_list_markdown() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
markdown := client.list_markdown() or { panic(err) }
assert markdown.contains('testcollection')
assert markdown.contains('anothercollection')
assert markdown.contains('page1')
assert markdown.contains('page2')
assert markdown.contains('intro')
assert markdown.contains('##')
assert markdown.contains('*')
}
// Test get_collection_metadata - success
fn test_get_collection_metadata_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
metadata := client.get_collection_metadata('testcollection') or { panic(err) }
assert metadata.name == 'testcollection'
assert metadata.pages.len == 2
assert metadata.errors.len == 0
}
// Test get_collection_metadata - with errors
fn test_get_collection_metadata_with_errors() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
metadata := client.get_collection_metadata('anothercollection') or { panic(err) }
assert metadata.name == 'anothercollection'
assert metadata.pages.len == 1
assert metadata.errors.len == 1
assert metadata.errors[0].message == 'Test error'
assert metadata.errors[0].line == 10
}
// Test get_collection_metadata - not found
fn test_get_collection_metadata_not_found() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
client.get_collection_metadata('nonexistent') or {
assert err.msg().contains('collection_not_found')
return
}
assert false, 'Should have returned an error'
}
// Test get_page_links - success
fn test_get_page_links_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
links := client.get_page_links('testcollection', 'page2') or { panic(err) }
assert links.len == 1
assert links[0].target_item_name == 'logo'
assert links[0].target_collection_name == 'testcollection'
assert links[0].is_image_link == true
}
// Test get_page_links - no links
fn test_get_page_links_empty() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
links := client.get_page_links('testcollection', 'page1') or { panic(err) }
assert links.len == 0
}
// Test get_page_links - page not found
fn test_get_page_links_page_not_found() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
client.get_page_links('testcollection', 'nonexistent') or {
assert err.msg().contains('page_not_found')
return
}
assert false, 'Should have returned an error'
}
// Test get_collection_errors - success
fn test_get_collection_errors_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
errors := client.get_collection_errors('anothercollection') or { panic(err) }
assert errors.len == 1
assert errors[0].message == 'Test error'
}
// Test get_collection_errors - no errors
fn test_get_collection_errors_empty() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
errors := client.get_collection_errors('testcollection') or { panic(err) }
assert errors.len == 0
}
// Test has_errors - true
fn test_has_errors_true() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
has_errors := client.has_errors('anothercollection')
assert has_errors == true
}
// Test has_errors - false
fn test_has_errors_false() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
has_errors := client.has_errors('testcollection')
assert has_errors == false
}
// Test has_errors - collection not found
fn test_has_errors_collection_not_found() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
has_errors := client.has_errors('nonexistent')
assert has_errors == false
}
// Test copy_images - success
fn test_copy_images_success() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
dest_dir := os.join_path(os.temp_dir(), 'copy_dest_${os.getpid()}')
os.mkdir_all(dest_dir) or { panic(err) }
defer { cleanup_test_export(dest_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
client.copy_images('testcollection', 'page2', dest_dir) or { panic(err) }
// Check that logo.png was copied to img subdirectory
assert os.exists(os.join_path(dest_dir, 'img', 'logo.png'))
}
// Test copy_images - no images
fn test_copy_images_no_images() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
dest_dir := os.join_path(os.temp_dir(), 'copy_dest_empty_${os.getpid()}')
os.mkdir_all(dest_dir) or { panic(err) }
defer { cleanup_test_export(dest_dir) }
mut client := new(export_dir: test_dir) or { panic(err) }
client.copy_images('testcollection', 'page1', dest_dir) or { panic(err) }
// Should succeed even with no images
assert true
}
// Test naming normalization edge cases
fn test_naming_normalization_underscores() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
// Create page with underscores
normalized := name_fix_no_underscore_no_ext('test_page_name')
os.write_file(os.join_path(test_dir, 'content', 'testcollection', '${normalized}.md'),
'# Test') or { panic(err) }
mut client := new(export_dir: test_dir) or { panic(err) }
// Should find with underscores
exists := client.page_exists('testcollection', 'test_page_name')
assert exists == true
}
// Test naming normalization edge cases - dashes
fn test_naming_normalization_dashes() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
// Create page with dashes
normalized := name_fix_no_underscore_no_ext('test-page-name')
os.write_file(os.join_path(test_dir, 'content', 'testcollection', '${normalized}.md'),
'# Test') or { panic(err) }
mut client := new(export_dir: test_dir) or { panic(err) }
// Should find with dashes
exists := client.page_exists('testcollection', 'test-page-name')
assert exists == true
}
// Test naming normalization edge cases - mixed case
fn test_naming_normalization_case() {
test_dir := setup_test_export()
defer { cleanup_test_export(test_dir) }
// Create page with mixed case
normalized := name_fix_no_underscore_no_ext('TestPageName')
os.write_file(os.join_path(test_dir, 'content', 'testcollection', '${normalized}.md'),
'# Test') or { panic(err) }
mut client := new(export_dir: test_dir) or { panic(err) }
// Should find with mixed case
exists := client.page_exists('testcollection', 'TestPageName')
assert exists == true
}

View File

@@ -0,0 +1,169 @@
module atlas_client
// AtlasErrors represents different types of errors that can occur in AtlasClient
pub enum AtlasErrors {
collection_not_found
page_not_found
file_not_found
image_not_found
export_dir_not_found
invalid_export_structure
}
// AtlasError represents an error with a message and a reason
struct AtlasError {
pub mut:
message string // The error message
reason AtlasErrors // The reason for the error
}
@[params]
struct ErrorArgs {
pub mut:
message string @[required] // The error message
reason AtlasErrors @[required] // The error reason
}
// new_error creates a new AtlasError
pub fn new_error(args ErrorArgs) AtlasError {
return AtlasError{
message: args.message
reason: args.reason
}
}
// throw_error throws an error with a message and a reason
fn (err AtlasError) throw_error(args ErrorArgs) IError {
return error('${args.reason}: ${args.message}')
}
// Error helper methods following the same pattern
@[params]
struct CollectionNotFoundArgs {
pub mut:
collection_name string @[required] // The collection name
}
// error_collection_not_found returns an error for when a collection is not found
pub fn (err AtlasError) error_collection_not_found(args CollectionNotFoundArgs) IError {
return err.throw_error(
message: 'Collection "${args.collection_name}" not found'
reason: .collection_not_found
)
}
@[params]
struct CollectionNotFoundAtArgs {
pub mut:
collection_name string @[required] // The collection name
path string @[required] // The path where metadata was expected
}
// error_collection_not_found_at returns an error for when a collection metadata file is not found
pub fn (err AtlasError) error_collection_not_found_at(args CollectionNotFoundAtArgs) IError {
return err.throw_error(
message: 'Metadata file for collection "${args.collection_name}" not found at "${args.path}"'
reason: .collection_not_found
)
}
@[params]
struct PageNotFoundArgs {
pub mut:
collection_name string @[required] // The collection name
page_name string @[required] // The page name
}
// error_page_not_found returns an error for when a page is not found in a collection
pub fn (err AtlasError) error_page_not_found(args PageNotFoundArgs) IError {
return err.throw_error(
message: 'Page "${args.page_name}" not found in collection "${args.collection_name}"'
reason: .page_not_found
)
}
// error_page_not_found_in_metadata returns an error for when a page is not found in collection metadata
pub fn (err AtlasError) error_page_not_found_in_metadata(args PageNotFoundArgs) IError {
return err.throw_error(
message: 'Page "${args.page_name}" not found in collection metadata'
reason: .page_not_found
)
}
@[params]
struct PageFileNotExistsArgs {
pub mut:
page_path string @[required] // The page file path
}
// error_page_file_not_exists returns an error for when a page file doesn't exist on disk
pub fn (err AtlasError) error_page_file_not_exists(args PageFileNotExistsArgs) IError {
return err.throw_error(
message: 'Page file "${args.page_path}" does not exist on disk'
reason: .page_not_found
)
}
@[params]
struct FileNotFoundArgs {
pub mut:
collection_name string @[required] // The collection name
file_name string @[required] // The file name
}
// error_file_not_found returns an error for when a file is not found in a collection
pub fn (err AtlasError) error_file_not_found(args FileNotFoundArgs) IError {
return err.throw_error(
message: 'File "${args.file_name}" not found in collection "${args.collection_name}"'
reason: .file_not_found
)
}
@[params]
struct ImageNotFoundArgs {
pub mut:
collection_name string @[required] // The collection name
image_name string @[required] // The image name
}
// error_image_not_found returns an error for when an image is not found in a collection
pub fn (err AtlasError) error_image_not_found(args ImageNotFoundArgs) IError {
return err.throw_error(
message: 'Image "${args.image_name}" not found in collection "${args.collection_name}"'
reason: .image_not_found
)
}
// error_image_not_found_linked returns an error for when a linked image is not found
pub fn (err AtlasError) error_image_not_found_linked(args ImageNotFoundArgs) IError {
return error('Error: Linked image "${args.image_name}" not found in collection "${args.collection_name}".')
}
@[params]
struct ExportDirNotFoundArgs {
pub mut:
export_dir string @[required] // The export directory path
}
// error_export_dir_not_found returns an error for when the export directory doesn't exist
pub fn (err AtlasError) error_export_dir_not_found(args ExportDirNotFoundArgs) IError {
return err.throw_error(
message: 'Export directory "${args.export_dir}" not found'
reason: .export_dir_not_found
)
}
@[params]
struct InvalidExportStructureArgs {
pub mut:
content_dir string @[required] // The content directory path
}
// error_invalid_export_structure returns an error for when the export directory structure is invalid
pub fn (err AtlasError) error_invalid_export_structure(args InvalidExportStructureArgs) IError {
return err.throw_error(
message: 'Content directory not found at "${args.content_dir}"'
reason: .invalid_export_structure
)
}

View File

@@ -0,0 +1,268 @@
module atlas_client
// Test error_collection_not_found
fn test_error_collection_not_found() {
err_handler := AtlasError{}
result := err_handler.error_collection_not_found(collection_name: 'test_collection')
assert result.msg().contains('collection_not_found')
assert result.msg().contains('test_collection')
assert result.msg().contains('Collection')
assert result.msg().contains('not found')
}
// Test error_collection_not_found with special characters
fn test_error_collection_not_found_special_chars() {
err_handler := AtlasError{}
result := err_handler.error_collection_not_found(collection_name: 'test-collection_123')
assert result.msg().contains('test-collection_123')
}
// Test error_collection_not_found with empty string
fn test_error_collection_not_found_empty() {
err_handler := AtlasError{}
result := err_handler.error_collection_not_found(collection_name: '')
assert result.msg().contains('collection_not_found')
}
// Test error_collection_not_found_at
fn test_error_collection_not_found_at() {
err_handler := AtlasError{}
result := err_handler.error_collection_not_found_at(
collection_name: 'my_collection'
path: '/tmp/meta/my_collection.json'
)
assert result.msg().contains('collection_not_found')
assert result.msg().contains('my_collection')
assert result.msg().contains('/tmp/meta/my_collection.json')
assert result.msg().contains('Metadata file')
}
// Test error_page_not_found
fn test_error_page_not_found() {
err_handler := AtlasError{}
result := err_handler.error_page_not_found(
collection_name: 'docs'
page_name: 'intro'
)
assert result.msg().contains('page_not_found')
assert result.msg().contains('docs')
assert result.msg().contains('intro')
assert result.msg().contains('Page')
}
// Test error_page_not_found with underscores and dashes
fn test_error_page_not_found_naming() {
err_handler := AtlasError{}
result := err_handler.error_page_not_found(
collection_name: 'my-docs_v2'
page_name: 'getting_started'
)
assert result.msg().contains('my-docs_v2')
assert result.msg().contains('getting_started')
}
// Test error_page_not_found_in_metadata
fn test_error_page_not_found_in_metadata() {
err_handler := AtlasError{}
result := err_handler.error_page_not_found_in_metadata(
collection_name: 'api'
page_name: 'endpoints'
)
assert result.msg().contains('page_not_found')
assert result.msg().contains('endpoints')
assert result.msg().contains('metadata')
}
// Test error_page_file_not_exists
fn test_error_page_file_not_exists() {
err_handler := AtlasError{}
result := err_handler.error_page_file_not_exists(
page_path: '/tmp/content/docs/page.md'
)
assert result.msg().contains('page_not_found')
assert result.msg().contains('/tmp/content/docs/page.md')
assert result.msg().contains('does not exist')
}
// Test error_file_not_found
fn test_error_file_not_found() {
err_handler := AtlasError{}
result := err_handler.error_file_not_found(
collection_name: 'resources'
file_name: 'data.csv'
)
assert result.msg().contains('file_not_found')
assert result.msg().contains('resources')
assert result.msg().contains('data.csv')
assert result.msg().contains('File')
}
// Test error_file_not_found with various extensions
fn test_error_file_not_found_extensions() {
err_handler := AtlasError{}
// Test PDF
result1 := err_handler.error_file_not_found(
collection_name: 'docs'
file_name: 'manual.pdf'
)
assert result1.msg().contains('manual.pdf')
// Test JSON
result2 := err_handler.error_file_not_found(
collection_name: 'config'
file_name: 'settings.json'
)
assert result2.msg().contains('settings.json')
}
// Test error_image_not_found
fn test_error_image_not_found() {
err_handler := AtlasError{}
result := err_handler.error_image_not_found(
collection_name: 'gallery'
image_name: 'logo.png'
)
assert result.msg().contains('image_not_found')
assert result.msg().contains('gallery')
assert result.msg().contains('logo.png')
assert result.msg().contains('Image')
}
// Test error_image_not_found with various image formats
fn test_error_image_not_found_formats() {
err_handler := AtlasError{}
formats := ['logo.png', 'banner.jpg', 'icon.svg', 'photo.webp', 'diagram.gif']
for format in formats {
result := err_handler.error_image_not_found(
collection_name: 'images'
image_name: format
)
assert result.msg().contains(format)
}
}
// Test error_image_not_found_linked
fn test_error_image_not_found_linked() {
err_handler := AtlasError{}
result := err_handler.error_image_not_found_linked(
collection_name: 'blog'
image_name: 'header.jpg'
)
assert result.msg().contains('Linked image')
assert result.msg().contains('blog')
assert result.msg().contains('header.jpg')
}
// Test error_export_dir_not_found
fn test_error_export_dir_not_found() {
err_handler := AtlasError{}
result := err_handler.error_export_dir_not_found(
export_dir: '/nonexistent/path'
)
assert result.msg().contains('export_dir_not_found')
assert result.msg().contains('/nonexistent/path')
assert result.msg().contains('Export directory')
}
// Test error_invalid_export_structure
fn test_error_invalid_export_structure() {
err_handler := AtlasError{}
result := err_handler.error_invalid_export_structure(
content_dir: '/tmp/export/content'
)
assert result.msg().contains('invalid_export_structure')
assert result.msg().contains('/tmp/export/content')
assert result.msg().contains('Content directory')
}
// Test new_error function
fn test_new_error() {
err := new_error(
message: 'Test error message'
reason: .page_not_found
)
assert err.message == 'Test error message'
assert err.reason == .page_not_found
}
// Test new_error with all error types
fn test_new_error_all_types() {
error_types := [
AtlasErrors.collection_not_found,
AtlasErrors.page_not_found,
AtlasErrors.file_not_found,
AtlasErrors.image_not_found,
AtlasErrors.export_dir_not_found,
AtlasErrors.invalid_export_structure,
]
for error_type in error_types {
err := new_error(
message: 'Test message'
reason: error_type
)
assert err.reason == error_type
}
}
// Test throw_error internal method
fn test_throw_error() {
err_handler := AtlasError{}
result := err_handler.throw_error(
message: 'Custom error message'
reason: .file_not_found
)
assert result.msg().contains('file_not_found')
assert result.msg().contains('Custom error message')
}
// Test error messages are properly formatted
fn test_error_message_format() {
err_handler := AtlasError{}
// Test that error messages follow the pattern: "reason: message"
result := err_handler.error_page_not_found(
collection_name: 'test'
page_name: 'page'
)
msg := result.msg()
assert msg.contains(':')
// Split by colon and verify format
parts := msg.split(':')
assert parts.len >= 2
}
// Test error consistency across similar methods
fn test_error_consistency() {
err_handler := AtlasError{}
// All "not found" errors should contain "not found" in message
err1 := err_handler.error_collection_not_found(collection_name: 'test')
err2 := err_handler.error_page_not_found(collection_name: 'test', page_name: 'page')
err3 := err_handler.error_file_not_found(collection_name: 'test', file_name: 'file')
err4 := err_handler.error_image_not_found(collection_name: 'test', image_name: 'img')
assert err1.msg().contains('not found')
assert err2.msg().contains('not found')
assert err3.msg().contains('not found')
assert err4.msg().contains('not found')
}

View File

@@ -0,0 +1,55 @@
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

@@ -0,0 +1,298 @@
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

@@ -0,0 +1,22 @@
module atlas_client
import incubaid.herolib.core.base
@[params]
pub struct AtlasClientArgs {
pub:
export_dir string @[required] // Path to atlas export directory
}
// Create a new AtlasClient instance
// The export_dir should point to the directory containing content/ and meta/ subdirectories
pub fn new(args AtlasClientArgs) !&AtlasClient {
mut context := base.context()!
mut redis := context.redis()!
return &AtlasClient{
AtlasError: AtlasError{}
redis: redis
export_dir: args.export_dir
}
}

View File

@@ -0,0 +1,12 @@
module atlas_client
import incubaid.herolib.core.redisclient
// AtlasClient provides access to Atlas-exported documentation collections
// It reads from both the exported directory structure and Redis metadata
pub struct AtlasClient {
AtlasError // Embedded error handler for generating standardized errors
pub mut:
redis &redisclient.Redis
export_dir string // Path to the atlas export directory (contains content/ and meta/)
}