This commit is contained in:
2025-10-26 22:24:18 +04:00
parent 9d1c347da7
commit 46ce903d4d
8 changed files with 126 additions and 57 deletions

View File

@@ -1,9 +1,12 @@
#!/usr/bin/env hero #!/usr/bin/env hero
!!atlas.scan !!atlas.scan
git_url: 'https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/collections/mycelium_economics' git_url: 'https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/collections/mycelium_economics'
!!atlas.scan !!atlas.scan
git_url: 'https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/collections/authentic_web' git_url: 'https://git.ourworld.tf/tfgrid/docs_tfgrid4/src/branch/main/collections/authentic_web'
// !!atlas.scan
// git_url: 'https://git.ourworld.tf/geomind/docs_geomind/src/branch/main/collections/usecases'
!!atlas.export destination: '/tmp/atlas_export' !!atlas.export destination: '/tmp/atlas_export'

View File

@@ -38,7 +38,7 @@ fn (mut self Atlas) add_collection(mut path pathlib.Path) !Collection {
error_cache: map[string]bool{} error_cache: map[string]bool{}
} }
c.init()! c.init_pre()!
self.collections[name] = &c self.collections[name] = &c
@@ -56,16 +56,9 @@ pub fn (a Atlas) get_collection(name string) !&Collection {
} }
// Validate all links in all collections // Validate all links in all collections
pub fn (mut a Atlas) validate_links() ! { pub fn (mut a Atlas) init_post() ! {
for _, mut col in a.collections { for _, mut col in a.collections {
col.validate_links()! col.init_post()!
}
}
// Fix all links in all collections
pub fn (mut a Atlas) fix_links() ! {
for _, mut col in a.collections {
col.fix_links()!
} }
} }
@@ -97,12 +90,6 @@ pub fn (a Atlas) groups_get(session Session) []&Group {
return matching return matching
} }
pub fn (mut a Atlas) validate() ! {
a.validate_links()!
a.fix_links()!
}
//////////////////SCAN //////////////////SCAN
// Scan a path for collections // Scan a path for collections

View File

@@ -1,7 +1,7 @@
module atlas module atlas
import incubaid.herolib.core.pathlib import incubaid.herolib.core.pathlib
import incubaid.herolib.core.texttools // import incubaid.herolib.core.texttools
import incubaid.herolib.develop.gittools import incubaid.herolib.develop.gittools
import incubaid.herolib.data.paramsparser { Params } import incubaid.herolib.data.paramsparser { Params }
import incubaid.herolib.ui.console import incubaid.herolib.ui.console
@@ -34,12 +34,18 @@ pub fn (mut c Collection) path() !pathlib.Path {
return pathlib.get_dir(path: c.path, create: false)! return pathlib.get_dir(path: c.path, create: false)!
} }
fn (mut c Collection) init() ! { fn (mut c Collection) init_pre() ! {
mut p := mut c.path()! mut p := mut c.path()!
c.scan(mut p)! c.scan(mut p)!
c.scan_acl()! c.scan_acl()!
} }
fn (mut c Collection) init_post() ! {
c.validate_links()!
c.init_git_info()!
}
//////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////
// Add a page to the collection // Add a page to the collection
@@ -62,7 +68,7 @@ fn (mut c Collection) add_page(mut path pathlib.Path) ! {
// Add an image to the collection // Add an image to the collection
fn (mut c Collection) add_file(mut p pathlib.Path) ! { fn (mut c Collection) add_file(mut p pathlib.Path) ! {
name := p.name_fix_no_ext() name := p.name_fix_keepext()
if name in c.files { if name in c.files {
return error('Page ${name} already exists in collection ${c.name}') return error('Page ${name} already exists in collection ${c.name}')
} }
@@ -115,6 +121,15 @@ pub fn (c Collection) file_get(name string) !&File {
return f return f
} }
pub fn (c Collection) file_or_image_get(name string) !&File {
mut f := c.files[name] or { return FileNotFound{
collection: c.name
file: name
} }
return f
}
// Check if page exists // Check if page exists
pub fn (c Collection) page_exists(name string) bool { pub fn (c Collection) page_exists(name string) bool {
return name in c.pages return name in c.pages
@@ -132,6 +147,14 @@ pub fn (c Collection) file_exists(name string) bool {
return f.ftype == .file return f.ftype == .file
} }
pub fn (c Collection) file_or_image_exists(name string) bool {
f := c.files[name] or { return false }
return true
}
@[params] @[params]
pub struct CollectionErrorArgs { pub struct CollectionErrorArgs {
pub mut: pub mut:
@@ -220,7 +243,7 @@ pub fn (c Collection) print_errors() {
pub fn (mut c Collection) validate_links() ! { pub fn (mut c Collection) validate_links() ! {
for _, mut page in c.pages { for _, mut page in c.pages {
content := page.content(include: true)! content := page.content(include: true)!
page.find_links(content)! // will walk over links see if errors and add errors page.links=page.find_links(content)! // will walk over links see if errors and add errors
} }
} }

View File

@@ -8,6 +8,7 @@ pub enum CollectionErrorCategory {
missing_include missing_include
include_syntax_error include_syntax_error
invalid_page_reference invalid_page_reference
invalid_file_reference
file_not_found file_not_found
invalid_collection invalid_collection
general_error general_error
@@ -26,13 +27,13 @@ pub mut:
// Hash is based on category + page_key (or file if page_key is empty) // Hash is based on category + page_key (or file if page_key is empty)
pub fn (e CollectionError) hash() string { pub fn (e CollectionError) hash() string {
mut hash_input := '${e.category}' mut hash_input := '${e.category}'
if e.page_key != '' { if e.page_key != '' {
hash_input += ':${e.page_key}' hash_input += ':${e.page_key}'
} else if e.file != '' { } else if e.file != '' {
hash_input += ':${e.file}' hash_input += ':${e.file}'
} }
return md5.hexhash(hash_input) return md5.hexhash(hash_input)
} }
@@ -44,7 +45,7 @@ pub fn (e CollectionError) str() string {
} else if e.file != '' { } else if e.file != '' {
location = ' [${e.file}]' location = ' [${e.file}]'
} }
return '[${e.category}]${location}: ${e.message}' return '[${e.category}]${location}: ${e.message}'
} }
@@ -55,9 +56,10 @@ pub fn (e CollectionError) category_str() string {
.missing_include { 'Missing Include' } .missing_include { 'Missing Include' }
.include_syntax_error { 'Include Syntax Error' } .include_syntax_error { 'Include Syntax Error' }
.invalid_page_reference { 'Invalid Page Reference' } .invalid_page_reference { 'Invalid Page Reference' }
.invalid_file_reference { 'Invalid File Reference' }
.file_not_found { 'File Not Found' } .file_not_found { 'File Not Found' }
.invalid_collection { 'Invalid Collection' } .invalid_collection { 'Invalid Collection' }
.general_error { 'General Error' } .general_error { 'General Error' }
.acl_denied { 'ACL Access Denied' } .acl_denied { 'ACL Access Denied' }
} }
} }

View File

@@ -23,7 +23,7 @@ pub fn (mut a Atlas) export(args ExportArgs) ! {
} }
// Validate links before export // Validate links before export
a.validate_links()! // a.validate_links()!
for _, mut col in a.collections { for _, mut col in a.collections {
col.export( col.export(
@@ -61,7 +61,7 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! {
col_dir_meta.empty()! col_dir_meta.empty()!
} }
c.init_git_info()!
if c.has_errors() { if c.has_errors() {
c.print_errors() c.print_errors()

View File

@@ -33,6 +33,16 @@ pub fn (a Atlas) file_get(key string) !&File {
return col.file_get(parts[1])! return col.file_get(parts[1])!
} }
// Get a file (can be image) from any collection using format "collection:file"
pub fn (a Atlas) file_or_image_get(key string) !&File {
parts := key.split(':')
if parts.len != 2 {
return error('Invalid file key format. Use "collection:file"')
}
col := a.get_collection(parts[0])!
return col.file_or_image_get(parts[1])!
}
// Check if page exists // 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(':') parts := key.split(':')
@@ -66,6 +76,16 @@ pub fn (a Atlas) file_exists(key string) bool {
return col.file_exists(parts[1]) return col.file_exists(parts[1])
} }
pub fn (a Atlas) file_or_image_exists(key string) bool {
parts := key.split(':')
if parts.len != 2 {
return false
}
col := a.get_collection(parts[0]) or { return false }
return col.file_or_image_exists(parts[1])
}
// List all pages in Atlas // List all pages in Atlas
pub fn (a Atlas) list_pages() map[string][]string { pub fn (a Atlas) list_pages() map[string][]string {
mut result := map[string][]string{} mut result := map[string][]string{}

View File

@@ -11,24 +11,23 @@ pub mut:
target string // Original link target (the source text) target string // Original link target (the source text)
line int // Line number where link was found line int // Line number where link was found
target_collection_name string target_collection_name string
target_page_name string target_item_name string
status LinkStatus status LinkStatus
is_file_link bool // is the link pointing to a file
page &Page @[skip; str: skip] // Reference to page where this link is found page &Page @[skip; str: skip] // Reference to page where this link is found
} }
pub enum LinkStatus { pub enum LinkStatus {
init init
external external
page_found found
page_not_found not_found
file_found
file_not_found
anchor anchor
error error
} }
fn (mut self Link) key() string { fn (mut self Link) key() string {
return '${self.target_collection_name}:${self.target_page_name}' return '${self.target_collection_name}:${self.target_item_name}'
} }
// is the link in the same collection as the page containing the link // is the link in the same collection as the page containing the link
@@ -57,6 +56,8 @@ fn (mut p Page) find_links(content string) ![]Link {
for line_idx, line in lines { for line_idx, line in lines {
mut pos := 0 mut pos := 0
for { for {
mut image_open := line.index_after('!', pos) or { break }
// Find next [ // Find next [
open_bracket := line.index_after('[', pos) or { break } open_bracket := line.index_after('[', pos) or { break }
@@ -69,6 +70,10 @@ fn (mut p Page) find_links(content string) ![]Link {
continue continue
} }
if image_open + 1 != open_bracket {
image_open = -1
}
// Find matching ) // Find matching )
open_paren := close_bracket + 1 open_paren := close_bracket + 1
close_paren := line.index_after(')', open_paren) or { break } close_paren := line.index_after(')', open_paren) or { break }
@@ -77,12 +82,15 @@ fn (mut p Page) find_links(content string) ![]Link {
text := line[open_bracket + 1..close_bracket] text := line[open_bracket + 1..close_bracket]
target := line[open_paren + 1..close_paren] target := line[open_paren + 1..close_paren]
islink_file_link := (image_open != -1)
mut link := Link{ mut link := Link{
src: line[open_bracket..close_paren + 1] src: line[open_bracket..close_paren + 1]
text: text text: text
target: target.trim_space() target: target.trim_space()
line: line_idx + 1 line: line_idx + 1
page: &p is_file_link: islink_file_link
page: &p
} }
p.parse_link_target(mut link) p.parse_link_target(mut link)
@@ -96,7 +104,7 @@ fn (mut p Page) find_links(content string) ![]Link {
// Parse link target to extract collection and page // 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) {
target := link.target mut target := link.target
// Skip external links // Skip external links
if target.starts_with('http://') || target.starts_with('https://') if target.starts_with('http://') || target.starts_with('https://')
@@ -111,28 +119,43 @@ fn (mut p Page) parse_link_target(mut link Link) {
return return
} }
if target.contains('/') {
parts9 := target.split('/')
if parts9.len >= 1 {
target = parts9[1]
}
}
// Format: $collection:$pagename or $collection:$pagename.md // Format: $collection:$pagename or $collection:$pagename.md
if target.contains(':') { if target.contains(':') {
parts := target.split(':') parts := target.split(':')
if parts.len >= 2 { if parts.len >= 2 {
link.target_collection_name = texttools.name_fix(parts[0]) link.target_collection_name = texttools.name_fix(parts[0])
link.target_page_name = normalize_page_name(parts[1]) link.target_item_name = normalize_page_name(parts[1])
} }
} else { } else {
link.target_page_name = normalize_page_name(target).trim_space() link.target_item_name = normalize_page_name(target).trim_space()
link.target_collection_name = p.collection.name link.target_collection_name = p.collection.name
} }
if !p.collection.atlas.page_exists(link.key()) { if link.is_file_link == false && !p.collection.atlas.page_exists(link.key()) {
p.collection.error( p.collection.error(
category: .invalid_page_reference category: .invalid_page_reference
page_key: p.key() page_key: p.key()
message: 'Broken link to `${link.key()}` at line ${link.line}: `${link.src}`' message: 'Broken link to `${link.key()}` at line ${link.line}: `${link.src}`'
show_console: false show_console: true
) )
link.status = .page_not_found link.status = .not_found
} else if link.is_file_link && !p.collection.atlas.file_or_image_exists(link.key()) {
p.collection.error(
category: .invalid_file_reference
page_key: p.key()
message: 'Broken file link to `${link.key()}` at line ${link.line}: `${link.src}`'
show_console: true
)
link.status = .not_found
} else { } else {
link.status = .page_found link.status = .found
} }
} }
@@ -148,7 +171,7 @@ fn (mut p Page) content_with_fixed_links() !string {
// Process links in reverse order to maintain positions // Process links in reverse order to maintain positions
for mut link in p.links.reverse() { for mut link in p.links.reverse() {
// if page not existing no point in fixing // if page not existing no point in fixing
if link.status != .page_found { if link.status != .found {
continue continue
} }
// if not local then no point in fixing // if not local then no point in fixing
@@ -183,7 +206,7 @@ fn (mut p Page) process_cross_collection_links(mut export_dir pathlib.Path) !str
// Process links in reverse order to maintain string positions // Process links in reverse order to maintain string positions
for mut link in links.reverse() { for mut link in links.reverse() {
if link.status != .page_found { if link.status != .found {
continue continue
} }
mut target_page := link.target_page()! mut target_page := link.target_page()!
@@ -206,11 +229,11 @@ fn (mut p Page) process_cross_collection_links(mut export_dir pathlib.Path) !str
panic('need to do for files too') panic('need to do for files too')
} }
for mut link in links.reverse() { // for mut link in links.reverse() {
if link.status != . { // if link.status != . {
continue // continue
} // }
} // }
return c return c
} }

View File

@@ -12,11 +12,13 @@ pub fn play(mut plbook PlayBook) ! {
mut atlases := map[string]&Atlas{} mut atlases := map[string]&Atlas{}
mut name := ""
// Process scan actions - scan directories for collections // Process scan actions - scan directories for collections
mut scan_actions := plbook.find(filter: 'atlas.scan')! mut scan_actions := plbook.find(filter: 'atlas.scan')!
for mut action in scan_actions { for mut action in scan_actions {
mut p := action.params mut p := action.params
name := p.get_default('name', 'main')! name = p.get_default('name', 'main')!
ignore := p.get_list_default('ignore', [])! ignore := p.get_list_default('ignore', [])!
console.print_item("Scanning Atlas '${name}' with ignore patterns: ${ignore}\n${p}") console.print_item("Scanning Atlas '${name}' with ignore patterns: ${ignore}\n${p}")
// Get or create atlas // Get or create atlas
@@ -48,13 +50,22 @@ pub fn play(mut plbook PlayBook) ! {
set(atlas_instance) set(atlas_instance)
} }
mut atlas_instance_post := atlases[name] or {
return error("Atlas '${name}' not found. Use !!atlas.scan first.")
}
atlas_instance_post.init_post()!
println(atlas_instance_post)
// Process export actions - export collections to destination // Process export actions - export collections to destination
mut export_actions := plbook.find(filter: 'atlas.export')! mut export_actions := plbook.find(filter: 'atlas.export')!
// Process explicit export actions // Process explicit export actions
for mut action in export_actions { for mut action in export_actions {
mut p := action.params mut p := action.params
name := p.get_default('name', 'main')! name = p.get_default('name', 'main')!
destination := p.get('destination')! destination := p.get('destination')!
reset := p.get_default_true('reset') reset := p.get_default_true('reset')
include := p.get_default_true('include') include := p.get_default_true('include')