...
This commit is contained in:
@@ -5,53 +5,14 @@ import incubaid.herolib.core.texttools
|
||||
import incubaid.herolib.ui.console
|
||||
import os
|
||||
import json
|
||||
import incubaid.herolib.core.redisclient
|
||||
|
||||
// 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 {
|
||||
// AtlasClient provides access to Atlas-exported documentation collections
|
||||
// It reads from both the exported directory structure and Redis metadata
|
||||
pub struct AtlasClient {
|
||||
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
|
||||
redis &redisclient.Redis
|
||||
export_dir string // Path to the atlas export directory (contains content/ and meta/)
|
||||
}
|
||||
|
||||
// get_page_path returns the path for a page in a collection
|
||||
@@ -325,7 +286,7 @@ pub fn (mut c AtlasClient) copy_images(collection_name string, page_name string,
|
||||
|
||||
// Copy only image links
|
||||
for link in links {
|
||||
if !link.is_image_link {
|
||||
if link.file_type != .image {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -349,7 +310,7 @@ pub fn (mut c AtlasClient) copy_files(collection_name string, page_name string,
|
||||
|
||||
// Copy only file links (non-image files)
|
||||
for link in links {
|
||||
if !link.is_file_link {
|
||||
if link.file_type != .file {
|
||||
continue
|
||||
}
|
||||
println(link)
|
||||
|
||||
@@ -63,8 +63,7 @@ fn setup_test_export() string {
|
||||
"target_collection_name": "testcollection",
|
||||
"target_item_name": "logo.png",
|
||||
"status": "ok",
|
||||
"is_file_link": false,
|
||||
"is_image_link": true
|
||||
"file_type": "image"
|
||||
},
|
||||
{
|
||||
"src": "data.csv",
|
||||
@@ -74,8 +73,7 @@ fn setup_test_export() string {
|
||||
"target_collection_name": "testcollection",
|
||||
"target_item_name": "data.csv",
|
||||
"status": "ok",
|
||||
"is_file_link": true,
|
||||
"is_image_link": false
|
||||
"file_type": "file"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,11 +1,57 @@
|
||||
module 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 {
|
||||
|
||||
// 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:
|
||||
redis &redisclient.Redis
|
||||
export_dir string // Path to the atlas export directory (contains content/ and meta/)
|
||||
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 // name with extension
|
||||
path string // path in the collection
|
||||
}
|
||||
|
||||
pub struct LinkMetadata {
|
||||
pub mut:
|
||||
src string
|
||||
text string
|
||||
target string
|
||||
line int
|
||||
target_collection_name string
|
||||
target_item_name string
|
||||
status string
|
||||
file_type LinkFileType
|
||||
}
|
||||
|
||||
pub enum LinkFileType {
|
||||
page // Default: link to another page
|
||||
file // Link to a non-image file
|
||||
image // Link to an image file
|
||||
}
|
||||
|
||||
pub struct ErrorMetadata {
|
||||
pub mut:
|
||||
category string
|
||||
page_key string
|
||||
message string
|
||||
line int
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module atlas
|
||||
|
||||
import incubaid.herolib.core.pathlib
|
||||
// import incubaid.herolib.core.texttools
|
||||
import incubaid.herolib.core.texttools
|
||||
import incubaid.herolib.develop.gittools
|
||||
import incubaid.herolib.data.paramsparser { Params }
|
||||
import incubaid.herolib.ui.console
|
||||
@@ -70,8 +70,7 @@ fn (mut c Collection) add_page(mut path pathlib.Path) ! {
|
||||
|
||||
// Add an image to the collection
|
||||
fn (mut c Collection) add_file(mut p pathlib.Path) ! {
|
||||
// Use name without extension for the key and name field
|
||||
name := p.name_fix_no_ext()
|
||||
name := p.name_fix_keepext() // keep extension
|
||||
if name in c.files {
|
||||
return error('File ${name} already exists in collection ${c.name}')
|
||||
}
|
||||
@@ -96,7 +95,8 @@ fn (mut c Collection) add_file(mut p pathlib.Path) ! {
|
||||
}
|
||||
|
||||
// Get a page by name
|
||||
pub fn (c Collection) page_get(name string) !&Page {
|
||||
pub fn (c Collection) page_get(name_ string) !&Page {
|
||||
name := texttools.name_fix_no_ext(name_)
|
||||
return c.pages[name] or { return PageNotFound{
|
||||
collection: c.name
|
||||
page: name
|
||||
@@ -104,7 +104,8 @@ pub fn (c Collection) page_get(name string) !&Page {
|
||||
}
|
||||
|
||||
// Get an image by name
|
||||
pub fn (c Collection) image_get(name string) !&File {
|
||||
pub fn (c Collection) image_get(name_ string) !&File {
|
||||
name := texttools.name_fix(name_)
|
||||
mut img := c.files[name] or { return FileNotFound{
|
||||
collection: c.name
|
||||
file: name
|
||||
@@ -116,7 +117,8 @@ pub fn (c Collection) image_get(name string) !&File {
|
||||
}
|
||||
|
||||
// Get a file by name
|
||||
pub fn (c Collection) file_get(name string) !&File {
|
||||
pub fn (c Collection) file_get(name_ string) !&File {
|
||||
name := texttools.name_fix(name_)
|
||||
mut f := c.files[name] or { return FileNotFound{
|
||||
collection: c.name
|
||||
file: name
|
||||
@@ -127,7 +129,8 @@ pub fn (c Collection) file_get(name string) !&File {
|
||||
return f
|
||||
}
|
||||
|
||||
pub fn (c Collection) file_or_image_get(name string) !&File {
|
||||
pub fn (c Collection) file_or_image_get(name_ string) !&File {
|
||||
name := texttools.name_fix(name_)
|
||||
mut f := c.files[name] or { return FileNotFound{
|
||||
collection: c.name
|
||||
file: name
|
||||
@@ -136,23 +139,27 @@ pub fn (c Collection) file_or_image_get(name string) !&File {
|
||||
}
|
||||
|
||||
// Check if page exists
|
||||
pub fn (c Collection) page_exists(name string) bool {
|
||||
pub fn (c Collection) page_exists(name_ string) !bool {
|
||||
name := texttools.name_fix_no_ext(name_)
|
||||
return name in c.pages
|
||||
}
|
||||
|
||||
// Check if image exists
|
||||
pub fn (c Collection) image_exists(name string) bool {
|
||||
pub fn (c Collection) image_exists(name_ string) !bool {
|
||||
name := texttools.name_fix(name_)
|
||||
f := c.files[name] or { return false }
|
||||
return f.ftype == .image
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
pub fn (c Collection) file_exists(name string) bool {
|
||||
pub fn (c Collection) file_exists(name_ string) !bool {
|
||||
name := texttools.name_fix(name_)
|
||||
f := c.files[name] or { return false }
|
||||
return f.ftype == .file
|
||||
}
|
||||
|
||||
pub fn (c Collection) file_or_image_exists(name string) bool {
|
||||
pub fn (c Collection) file_or_image_exists(name_ string) !bool {
|
||||
name := texttools.name_fix(name_)
|
||||
_ := c.files[name] or { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! {
|
||||
|
||||
// Collect cross-collection page references
|
||||
is_local := link.target_collection_name == c.name
|
||||
if !link.is_file_link && !is_local {
|
||||
if link.file_type == .page && !is_local {
|
||||
mut target_page := link.target_page() or { continue }
|
||||
// Use page name as key to avoid duplicates
|
||||
if target_page.name !in cross_collection_pages {
|
||||
@@ -100,7 +100,7 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! {
|
||||
}
|
||||
|
||||
// Collect cross-collection file/image references
|
||||
if link.is_file_link && !is_local {
|
||||
if (link.file_type == .file || link.file_type == .image) && !is_local {
|
||||
mut target_file := link.target_file() or { continue }
|
||||
// Use file name as key to avoid duplicates
|
||||
file_key := target_file.file_name()
|
||||
|
||||
@@ -4,7 +4,7 @@ module atlas
|
||||
pub fn (a Atlas) page_get(key string) !&Page {
|
||||
parts := key.split(':')
|
||||
if parts.len != 2 {
|
||||
return error('Invalid page key format. Use "collection:page"')
|
||||
return error('Invalid page key format. Use "collection:page" in page_get')
|
||||
}
|
||||
|
||||
col := a.get_collection(parts[0])!
|
||||
@@ -15,7 +15,7 @@ pub fn (a Atlas) page_get(key string) !&Page {
|
||||
pub fn (a Atlas) image_get(key string) !&File {
|
||||
parts := key.split(':')
|
||||
if parts.len != 2 {
|
||||
return error('Invalid image key format. Use "collection:image"')
|
||||
return error('Invalid image key format. Use "collection:image" in image_get')
|
||||
}
|
||||
|
||||
col := a.get_collection(parts[0])!
|
||||
@@ -26,7 +26,7 @@ pub fn (a Atlas) image_get(key string) !&File {
|
||||
pub fn (a Atlas) file_get(key string) !&File {
|
||||
parts := key.split(':')
|
||||
if parts.len != 2 {
|
||||
return error('Invalid file key format. Use "collection:file"')
|
||||
return error('Invalid file key format. Use "collection:file" in file_get')
|
||||
}
|
||||
|
||||
col := a.get_collection(parts[0])!
|
||||
@@ -44,10 +44,10 @@ pub fn (a Atlas) file_or_image_get(key string) !&File {
|
||||
}
|
||||
|
||||
// Check if page exists
|
||||
pub fn (a Atlas) page_exists(key string) bool {
|
||||
pub fn (a Atlas) page_exists(key string) !bool {
|
||||
parts := key.split(':')
|
||||
if parts.len != 2 {
|
||||
return false
|
||||
return error("Invalid file key format. Use 'collection:file' in page_exists")
|
||||
}
|
||||
|
||||
col := a.get_collection(parts[0]) or { return false }
|
||||
@@ -55,10 +55,10 @@ pub fn (a Atlas) page_exists(key string) bool {
|
||||
}
|
||||
|
||||
// Check if image exists
|
||||
pub fn (a Atlas) image_exists(key string) bool {
|
||||
pub fn (a Atlas) image_exists(key string) !bool {
|
||||
parts := key.split(':')
|
||||
if parts.len != 2 {
|
||||
return false
|
||||
return error("Invalid file key format. Use 'collection:file' in image_exists")
|
||||
}
|
||||
|
||||
col := a.get_collection(parts[0]) or { return false }
|
||||
@@ -66,26 +66,25 @@ pub fn (a Atlas) image_exists(key string) bool {
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
pub fn (a Atlas) file_exists(key string) bool {
|
||||
pub fn (a Atlas) file_exists(key string) !bool {
|
||||
parts := key.split(':')
|
||||
if parts.len != 2 {
|
||||
return false
|
||||
return error("Invalid file key format. Use 'collection:file' in file_exists")
|
||||
}
|
||||
|
||||
col := a.get_collection(parts[0]) or { return false }
|
||||
return col.file_exists(parts[1])
|
||||
}
|
||||
|
||||
pub fn (a Atlas) file_or_image_exists(key string) bool {
|
||||
pub fn (a Atlas) file_or_image_exists(key string) !bool {
|
||||
parts := key.split(':')
|
||||
if parts.len != 2 {
|
||||
return false
|
||||
return error("Invalid file key format. Use 'collection:file' in file_or_image_exists")
|
||||
}
|
||||
col := a.get_collection(parts[0]) or { return false }
|
||||
return col.file_or_image_exists(parts[1])
|
||||
}
|
||||
|
||||
|
||||
// List all pages in Atlas
|
||||
pub fn (a Atlas) list_pages() map[string][]string {
|
||||
mut result := map[string][]string{}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
module atlas
|
||||
|
||||
import incubaid.herolib.core.texttools
|
||||
import incubaid.herolib.ui.console
|
||||
|
||||
pub enum LinkFileType {
|
||||
page // Default: link to another page
|
||||
file // Link to a non-image file
|
||||
image // Link to an image file
|
||||
}
|
||||
|
||||
// Link represents a markdown link found in content
|
||||
pub struct Link {
|
||||
@@ -13,8 +20,7 @@ pub mut:
|
||||
target_collection_name string
|
||||
target_item_name string
|
||||
status LinkStatus
|
||||
is_file_link bool // is the link pointing to a file
|
||||
is_image_link bool // is the link pointing to an image
|
||||
file_type LinkFileType // Type of the link target: file, image, or page (default)
|
||||
page &Page @[skip; str: skip] // Reference to page where this link is found
|
||||
}
|
||||
|
||||
@@ -85,37 +91,41 @@ fn (mut p Page) find_links(content string) ![]Link {
|
||||
text := line[open_bracket + 1..close_bracket]
|
||||
target := line[open_paren + 1..close_paren]
|
||||
|
||||
// Determine link type
|
||||
// File links have extensions (but not .md), image links start with ![
|
||||
is_file_link := target.contains('.') && !target.trim_space().to_lower().ends_with('.md')
|
||||
is_image_link := image_open != -1 && !is_file_link
|
||||
// Determine link type based on content
|
||||
mut detected_file_type := LinkFileType.page
|
||||
|
||||
// Check if it's an image link (starts with !)
|
||||
if image_open != -1 {
|
||||
detected_file_type = .image
|
||||
} else if target.contains('.') && !target.trim_space().to_lower().ends_with('.md') {
|
||||
// File link: has extension but not .md
|
||||
detected_file_type = .file
|
||||
}
|
||||
|
||||
// console.print_debug('Found link: text="${text}", target="${target}", type=${detected_file_type}')
|
||||
|
||||
// Store position - use image_open if it's an image, otherwise open_bracket
|
||||
link_start_pos := if is_image_link { image_open } else { open_bracket }
|
||||
link_start_pos := if detected_file_type == .image { image_open } else { open_bracket }
|
||||
|
||||
// For image links, src should include the ! prefix
|
||||
link_src := if is_image_link {
|
||||
link_src := if detected_file_type == .image {
|
||||
line[image_open..close_paren + 1]
|
||||
} else {
|
||||
line[open_bracket..close_paren + 1]
|
||||
}
|
||||
|
||||
mut link := Link{
|
||||
src: link_src
|
||||
text: text
|
||||
target: target.trim_space()
|
||||
line: line_idx + 1
|
||||
pos: link_start_pos
|
||||
is_file_link: is_file_link
|
||||
is_image_link: is_image_link
|
||||
page: &p
|
||||
src: link_src
|
||||
text: text
|
||||
target: target.trim_space()
|
||||
line: line_idx + 1
|
||||
pos: link_start_pos
|
||||
file_type: detected_file_type
|
||||
page: &p
|
||||
}
|
||||
|
||||
p.parse_link_target(mut link)
|
||||
if link.status == .external {
|
||||
link.is_file_link = false
|
||||
link.is_image_link = false
|
||||
}
|
||||
p.parse_link_target(mut link)!
|
||||
// No need to set file_type to false for external links, as it's already .page by default
|
||||
links << link
|
||||
|
||||
pos = close_paren + 1
|
||||
@@ -125,7 +135,7 @@ fn (mut p Page) find_links(content string) ![]Link {
|
||||
}
|
||||
|
||||
// Parse link target to extract collection and page
|
||||
fn (mut p Page) parse_link_target(mut link Link) {
|
||||
fn (mut p Page) parse_link_target(mut link Link) ! {
|
||||
mut target := link.target.to_lower().trim_space()
|
||||
|
||||
// Check for external links (http, https, mailto, ftp)
|
||||
@@ -155,7 +165,7 @@ fn (mut p Page) parse_link_target(mut link Link) {
|
||||
if parts.len >= 2 {
|
||||
link.target_collection_name = texttools.name_fix(parts[0])
|
||||
// For file links, use name without extension; for page links, normalize normally
|
||||
if link.is_file_link {
|
||||
if link.file_type == .file {
|
||||
link.target_item_name = texttools.name_fix_no_ext(parts[1])
|
||||
} else {
|
||||
link.target_item_name = normalize_page_name(parts[1])
|
||||
@@ -163,7 +173,7 @@ fn (mut p Page) parse_link_target(mut link Link) {
|
||||
}
|
||||
} else {
|
||||
// For file links, use name without extension; for page links, normalize normally
|
||||
if link.is_file_link {
|
||||
if link.file_type == .file {
|
||||
link.target_item_name = texttools.name_fix_no_ext(target).trim_space()
|
||||
} else {
|
||||
link.target_item_name = normalize_page_name(target).trim_space()
|
||||
@@ -171,19 +181,23 @@ fn (mut p Page) parse_link_target(mut link Link) {
|
||||
link.target_collection_name = p.collection.name
|
||||
}
|
||||
|
||||
// console.print_debug('Parsed link target: collection="${link.target_collection_name}", item="${link.target_item_name}", type=${link.file_type}')
|
||||
|
||||
// Validate link target exists
|
||||
mut target_exists := false
|
||||
mut error_category := CollectionErrorCategory.invalid_page_reference
|
||||
mut error_prefix := 'Broken link'
|
||||
|
||||
if link.is_file_link {
|
||||
target_exists = p.collection.atlas.file_or_image_exists(link.key())
|
||||
if link.file_type == .file || link.file_type == .image {
|
||||
target_exists = p.collection.atlas.file_or_image_exists(link.key())!
|
||||
error_category = .invalid_file_reference
|
||||
error_prefix = 'Broken file link'
|
||||
error_prefix = if link.file_type == .file { 'Broken file link' } else { 'Broken image link' }
|
||||
} else {
|
||||
target_exists = p.collection.atlas.page_exists(link.key())
|
||||
target_exists = p.collection.atlas.page_exists(link.key())!
|
||||
}
|
||||
|
||||
// console.print_debug('Link target exists: ${target_exists} for key=${link.key()}')
|
||||
|
||||
if target_exists {
|
||||
link.status = .found
|
||||
} else {
|
||||
@@ -239,7 +253,7 @@ fn (mut p Page) content_with_fixed_links(args FixLinksArgs) !string {
|
||||
|
||||
// Build the complete link markdown
|
||||
// For image links, link.src already includes the !, so we build the same format
|
||||
prefix := if link.is_image_link { '!' } else { '' }
|
||||
prefix := if link.file_type == .image { '!' } else { '' }
|
||||
new_link_md := '${prefix}[${link.text}](${new_link})'
|
||||
|
||||
// Replace in content
|
||||
@@ -251,13 +265,19 @@ fn (mut p Page) content_with_fixed_links(args FixLinksArgs) !string {
|
||||
|
||||
// export_link_path calculates path for export (self-contained: all references are local)
|
||||
fn (mut p Page) export_link_path(mut link Link) !string {
|
||||
if link.is_file_link {
|
||||
mut tf := link.target_file()!
|
||||
mut subdir := if tf.is_image() { 'img' } else { 'files' }
|
||||
return '${subdir}/${tf.file_name()}'
|
||||
} else {
|
||||
mut tp := link.target_page()!
|
||||
return '${tp.name}.md'
|
||||
match link.file_type {
|
||||
.image {
|
||||
mut tf := link.target_file()!
|
||||
return 'img/${tf.file_name()}'
|
||||
}
|
||||
.file {
|
||||
mut tf := link.target_file()!
|
||||
return 'files/${tf.file_name()}'
|
||||
}
|
||||
.page {
|
||||
mut tp := link.target_page()!
|
||||
return '${tp.name}.md'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,12 +285,15 @@ fn (mut p Page) export_link_path(mut link Link) !string {
|
||||
fn (mut p Page) filesystem_link_path(mut link Link) !string {
|
||||
source_path := p.path()!
|
||||
|
||||
mut target_path := if link.is_file_link {
|
||||
mut tf := link.target_file()!
|
||||
tf.path()!
|
||||
} else {
|
||||
mut tp := link.target_page()!
|
||||
tp.path()!
|
||||
mut target_path := match link.file_type {
|
||||
.image, .file {
|
||||
mut tf := link.target_file()!
|
||||
tf.path()!
|
||||
}
|
||||
.page {
|
||||
mut tp := link.target_page()!
|
||||
tp.path()!
|
||||
}
|
||||
}
|
||||
|
||||
return target_path.path_relative(source_path.path)!
|
||||
|
||||
Reference in New Issue
Block a user