439 lines
13 KiB
V
439 lines
13 KiB
V
module docusaurus
|
|
|
|
import incubaid.herolib.core.pathlib
|
|
import incubaid.herolib.data.atlas.client as atlas_client
|
|
import incubaid.herolib.web.site { Page, Section, Site }
|
|
import incubaid.herolib.data.markdown.tools as markdowntools
|
|
import incubaid.herolib.ui.console
|
|
|
|
struct SiteGenerator {
|
|
mut:
|
|
siteconfig_name string
|
|
path pathlib.Path
|
|
client IDocClient
|
|
flat bool // if flat then won't use sitenames as subdir's
|
|
site Site
|
|
errors []string // collect errors here
|
|
}
|
|
|
|
// Generate docs from site configuration
|
|
pub fn (mut docsite DocSite) generate_docs() ! {
|
|
c := config()!
|
|
|
|
// we generate the docs in the build path
|
|
docs_path := '${c.path_build.path}/docs'
|
|
|
|
// Create the appropriate client based on configuration
|
|
mut client_instance := atlas_client.new(export_dir: c.atlas_dir)!
|
|
mut client := IDocClient(client_instance)
|
|
|
|
mut gen := SiteGenerator{
|
|
path: pathlib.get_dir(path: docs_path, create: true)!
|
|
client: client
|
|
flat: true
|
|
site: docsite.website
|
|
}
|
|
|
|
for section in gen.site.sections {
|
|
gen.section_generate(section)!
|
|
}
|
|
|
|
for page in gen.site.pages {
|
|
gen.page_generate(page)!
|
|
}
|
|
|
|
if gen.errors.len > 0 {
|
|
println('Page List: is header collection and page name per collection.\nAvailable pages:\n${gen.client.list_markdown()!}')
|
|
return error('Errors occurred during site generation:\n${gen.errors.join('\n\n')}\n')
|
|
}
|
|
}
|
|
|
|
fn (mut generator SiteGenerator) error(msg string) ! {
|
|
console.print_stderr('Error: ${msg}')
|
|
generator.errors << msg
|
|
}
|
|
|
|
fn (mut generator SiteGenerator) page_generate(args_ Page) ! {
|
|
mut args := args_
|
|
|
|
mut content := ['---']
|
|
|
|
mut parts := args.src.split(':')
|
|
if parts.len != 2 {
|
|
generator.error("Invalid src format for page '${args.src}', expected format: collection:page_name, TODO: fix in ${args.path}, check the collection & page_name exists in the pagelist")!
|
|
return
|
|
}
|
|
collection_name := parts[0]
|
|
page_name := parts[1]
|
|
|
|
mut page_content := generator.client.get_page_content(collection_name, page_name) or {
|
|
generator.error("Couldn't find page '${collection_name}:${page_name}' is formatted as collectionname:pagename. TODO: fix in ${args.path}, check the collection & page_name exists in the pagelist. ")!
|
|
return
|
|
}
|
|
|
|
if args.description.len == 0 {
|
|
descnew := markdowntools.extract_title(page_content)
|
|
if descnew != '' {
|
|
args.description = descnew
|
|
} else {
|
|
args.description = page_name
|
|
}
|
|
}
|
|
|
|
if args.title.len == 0 {
|
|
descnew := markdowntools.extract_title(page_content)
|
|
if descnew != '' {
|
|
args.title = descnew
|
|
} else {
|
|
args.title = page_name
|
|
}
|
|
}
|
|
// Escape single quotes in YAML by doubling them
|
|
escaped_title := args.title.replace("'", "''")
|
|
content << "title: '${escaped_title}'"
|
|
|
|
if args.description.len > 0 {
|
|
escaped_description := args.description.replace("'", "''")
|
|
content << "description: '${escaped_description}'"
|
|
}
|
|
|
|
if args.slug.len > 0 {
|
|
escaped_slug := args.slug.replace("'", "''")
|
|
content << "slug: '${escaped_slug}'"
|
|
}
|
|
|
|
if args.hide_title {
|
|
content << 'hide_title: ${args.hide_title}'
|
|
}
|
|
|
|
if args.draft {
|
|
content << 'draft: ${args.draft}'
|
|
}
|
|
|
|
if args.position > 0 {
|
|
content << 'sidebar_position: ${args.position}'
|
|
}
|
|
|
|
content << '---'
|
|
|
|
mut c := content.join('\n')
|
|
|
|
if args.title_nr > 0 {
|
|
// Set the title number in the page content
|
|
page_content = markdowntools.set_titles(page_content, args.title_nr)
|
|
}
|
|
|
|
// Fix links to account for nested categories
|
|
page_content = generator.fix_links(page_content, args.path)
|
|
|
|
c += '\n${page_content}\n'
|
|
|
|
if args.path.ends_with('/') || args.path.trim_space() == '' {
|
|
// means is dir
|
|
args.path += page_name
|
|
}
|
|
|
|
if !args.path.ends_with('.md') {
|
|
args.path += '.md'
|
|
}
|
|
|
|
mut pagepath := '${generator.path.path}/${args.path}'
|
|
mut pagefile := pathlib.get_file(path: pagepath, create: true)!
|
|
|
|
pagefile.write(c)!
|
|
|
|
generator.client.copy_images(collection_name, page_name, pagefile.path_dir()) or {
|
|
generator.error("Couldn't copy images for page:'${page_name}' in collection:'${collection_name}'\nERROR:${err}")!
|
|
return
|
|
}
|
|
generator.client.copy_files(collection_name, page_name, pagefile.path_dir()) or {
|
|
generator.error("Couldn't copy files for page:'${page_name}' in collection:'${collection_name}'\nERROR:${err}")!
|
|
return
|
|
}
|
|
}
|
|
|
|
fn (mut generator SiteGenerator) section_generate(args_ Section) ! {
|
|
mut args := args_
|
|
|
|
mut c := ''
|
|
if args.description.len > 0 {
|
|
c = '{
|
|
"label": "${args.label}",
|
|
"position": ${args.position},
|
|
"link": {
|
|
"type": "generated-index",
|
|
"description": "${args.description}"
|
|
}
|
|
}'
|
|
} else {
|
|
c = '{
|
|
"label": "${args.label}",
|
|
"position": ${args.position},
|
|
"link": {
|
|
"type": "generated-index"
|
|
}
|
|
}'
|
|
}
|
|
|
|
mut category_path := '${generator.path.path}/${args.path}/_category_.json'
|
|
mut catfile := pathlib.get_file(path: category_path, create: true)!
|
|
|
|
catfile.write(c)!
|
|
}
|
|
|
|
// Strip numeric prefix from filename (e.g., "03_linux_installation" -> "linux_installation")
|
|
// Docusaurus automatically strips these prefixes from URLs
|
|
fn strip_numeric_prefix(name string) string {
|
|
// Match pattern: digits followed by underscore at the start
|
|
if name.len > 2 && name[0].is_digit() {
|
|
for i := 1; i < name.len; i++ {
|
|
if name[i] == `_` {
|
|
// Found the underscore, return everything after it
|
|
return name[i + 1..]
|
|
}
|
|
if !name[i].is_digit() {
|
|
// Not a numeric prefix pattern, return as-is
|
|
return name
|
|
}
|
|
}
|
|
}
|
|
return name
|
|
}
|
|
|
|
// Calculate relative path from current directory to target directory
|
|
// current_dir: directory of the current page (e.g., '' for root, 'tokens' for tokens/, 'farming/advanced' for nested)
|
|
// target_dir: directory of the target page
|
|
// page_name: name of the target page
|
|
// Returns: relative path (e.g., './page', '../dir/page', '../../page')
|
|
fn calculate_relative_path(current_dir string, target_dir string, page_name string) string {
|
|
// Both at root level
|
|
if current_dir == '' && target_dir == '' {
|
|
return './${page_name}'
|
|
}
|
|
|
|
// Current at root, target in subdirectory
|
|
if current_dir == '' && target_dir != '' {
|
|
return './${target_dir}/${page_name}'
|
|
}
|
|
|
|
// Current in subdirectory, target at root
|
|
if current_dir != '' && target_dir == '' {
|
|
// Count directory levels to go up
|
|
levels := current_dir.split('/').len
|
|
up := '../'.repeat(levels)
|
|
return '${up}${page_name}'
|
|
}
|
|
|
|
// Both in subdirectories
|
|
current_parts := current_dir.split('/')
|
|
target_parts := target_dir.split('/')
|
|
|
|
// Find common prefix
|
|
mut common_len := 0
|
|
for i := 0; i < current_parts.len && i < target_parts.len; i++ {
|
|
if current_parts[i] == target_parts[i] {
|
|
common_len++
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Calculate how many levels to go up
|
|
up_levels := current_parts.len - common_len
|
|
mut path_parts := []string{}
|
|
|
|
// Add ../ for each level up
|
|
for _ in 0 .. up_levels {
|
|
path_parts << '..'
|
|
}
|
|
|
|
// Add remaining target path parts
|
|
for i in common_len .. target_parts.len {
|
|
path_parts << target_parts[i]
|
|
}
|
|
|
|
// Add page name
|
|
path_parts << page_name
|
|
|
|
return path_parts.join('/')
|
|
}
|
|
|
|
// Fix links to account for nested categories and Docusaurus URL conventions
|
|
fn (generator SiteGenerator) fix_links(content string, current_page_path string) string {
|
|
mut result := content
|
|
|
|
// Extract current page's directory path
|
|
mut current_dir := current_page_path.trim('/')
|
|
if current_dir.contains('/') && !current_dir.ends_with('/') {
|
|
last_part := current_dir.all_after_last('/')
|
|
if last_part.contains('.') {
|
|
current_dir = current_dir.all_before_last('/')
|
|
}
|
|
}
|
|
// If path is just a filename or empty, current_dir should be empty (root level)
|
|
if !current_dir.contains('/') && current_dir.contains('.') {
|
|
current_dir = ''
|
|
}
|
|
|
|
// Build maps for link fixing
|
|
mut collection_paths := map[string]string{} // collection -> directory path (for nested collections)
|
|
mut page_to_path := map[string]string{} // page_name -> full directory path in Docusaurus
|
|
mut collection_page_map := map[string]string{} // "collection:page" -> directory path
|
|
|
|
for page in generator.site.pages {
|
|
parts := page.src.split(':')
|
|
if parts.len != 2 {
|
|
continue
|
|
}
|
|
collection := parts[0]
|
|
page_name := parts[1]
|
|
|
|
// Extract directory path from page.path
|
|
mut dir_path := page.path.trim('/')
|
|
if dir_path.contains('/') && !dir_path.ends_with('/') {
|
|
last_part := dir_path.all_after_last('/')
|
|
if last_part.contains('.') || last_part == page_name {
|
|
dir_path = dir_path.all_before_last('/')
|
|
}
|
|
}
|
|
|
|
// Store collection -> directory mapping for nested collections
|
|
if dir_path != collection && dir_path != '' {
|
|
collection_paths[collection] = dir_path
|
|
}
|
|
|
|
// Store page_name -> directory path for fixing same-collection links
|
|
// Strip numeric prefix from page_name for the map key
|
|
clean_page_name := strip_numeric_prefix(page_name)
|
|
page_to_path[clean_page_name] = dir_path
|
|
|
|
// Store collection:page -> directory path for fixing collection:page format links
|
|
collection_page_map['${collection}:${clean_page_name}'] = dir_path
|
|
}
|
|
|
|
// STEP 1: Strip numeric prefixes from all page references in links FIRST
|
|
mut lines := result.split('\n')
|
|
for i, line in lines {
|
|
if !line.contains('](') {
|
|
continue
|
|
}
|
|
|
|
mut new_line := line
|
|
parts := line.split('](')
|
|
if parts.len < 2 {
|
|
continue
|
|
}
|
|
|
|
for j := 1; j < parts.len; j++ {
|
|
close_idx := parts[j].index(')') or { continue }
|
|
link_url := parts[j][..close_idx]
|
|
|
|
mut new_url := link_url
|
|
if link_url.contains('/') {
|
|
path_part := link_url.all_before_last('/')
|
|
file_part := link_url.all_after_last('/')
|
|
new_file := strip_numeric_prefix(file_part)
|
|
if new_file != file_part {
|
|
new_url = '${path_part}/${new_file}'
|
|
}
|
|
} else {
|
|
new_url = strip_numeric_prefix(link_url)
|
|
}
|
|
|
|
if new_url != link_url {
|
|
new_line = new_line.replace('](${link_url})', '](${new_url})')
|
|
}
|
|
}
|
|
lines[i] = new_line
|
|
}
|
|
result = lines.join('\n')
|
|
|
|
// STEP 2: Replace ../collection/ with ../actual/nested/path/ for cross-collection links
|
|
for collection, actual_path in collection_paths {
|
|
result = result.replace('../${collection}/', '../${actual_path}/')
|
|
}
|
|
|
|
// STEP 3: Fix same-collection links: ./page -> correct path based on Docusaurus structure
|
|
for page_name, target_dir in page_to_path {
|
|
old_link := './${page_name}'
|
|
if result.contains(old_link) {
|
|
new_link := calculate_relative_path(current_dir, target_dir, page_name)
|
|
result = result.replace(old_link, new_link)
|
|
}
|
|
}
|
|
|
|
// STEP 4: Convert collection:page format to proper relative paths
|
|
// Calculate relative path from current page to target page
|
|
for collection_page, target_dir in collection_page_map {
|
|
old_pattern := collection_page
|
|
if result.contains(old_pattern) {
|
|
// Extract just the page name from "collection:page"
|
|
page_name := collection_page.all_after(':')
|
|
new_link := calculate_relative_path(current_dir, target_dir, page_name)
|
|
result = result.replace(old_pattern, new_link)
|
|
}
|
|
}
|
|
|
|
// STEP 5: Fix bare page references (from atlas self-contained exports)
|
|
// Atlas exports convert cross-collection links to simple relative links like "token_system2.md"
|
|
// We need to transform these to proper relative paths based on Docusaurus structure
|
|
for page_name, target_dir in page_to_path {
|
|
// Match links in the format ](page_name) or ](page_name.md)
|
|
old_link_with_md := '](${page_name}.md)'
|
|
old_link_without_md := '](${page_name})'
|
|
|
|
if result.contains(old_link_with_md) || result.contains(old_link_without_md) {
|
|
new_link := calculate_relative_path(current_dir, target_dir, page_name)
|
|
// Replace both .md and non-.md versions
|
|
result = result.replace(old_link_with_md, '](${new_link})')
|
|
result = result.replace(old_link_without_md, '](${new_link})')
|
|
}
|
|
}
|
|
|
|
// STEP 6: Remove .md extensions from all remaining links (Docusaurus doesn't use them in URLs)
|
|
result = result.replace('.md)', ')')
|
|
|
|
// STEP 7: Fix image links to point to img/ subdirectory
|
|
// Images are copied to img/ subdirectory by copy_images(), so we need to update the links
|
|
// Transform  to  for local images only
|
|
mut image_lines := result.split('\n')
|
|
for i, line in image_lines {
|
|
// Find image links:  but skip external URLs
|
|
if line.contains('![') {
|
|
mut pos := 0
|
|
for {
|
|
img_start := line.index_after('![', pos) or { break }
|
|
alt_end := line.index_after(']', img_start) or { break }
|
|
if alt_end + 1 >= line.len || line[alt_end + 1] != `(` {
|
|
pos = alt_end + 1
|
|
continue
|
|
}
|
|
url_start := alt_end + 2
|
|
url_end := line.index_after(')', url_start) or { break }
|
|
url := line[url_start..url_end]
|
|
|
|
// Skip external URLs and already-prefixed img/ paths
|
|
if url.starts_with('http://') || url.starts_with('https://')
|
|
|| url.starts_with('img/') || url.starts_with('./img/') {
|
|
pos = url_end + 1
|
|
continue
|
|
}
|
|
|
|
// Skip absolute paths and paths with ../
|
|
if url.starts_with('/') || url.starts_with('../') {
|
|
pos = url_end + 1
|
|
continue
|
|
}
|
|
|
|
// This is a local image reference - add img/ prefix
|
|
new_url := 'img/${url}'
|
|
image_lines[i] = line[0..url_start] + new_url + line[url_end..]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
result = image_lines.join('\n')
|
|
|
|
return result
|
|
}
|