feat: Refactor docusaurus playbook and sidebar JSON serialization
- Extract playbook action processing into separate functions - Add auto-export for Atlas collections - Simplify sidebar JSON serialization - Update sidebar navigation item structure
This commit is contained in:
@@ -1,26 +1,139 @@
|
|||||||
module docusaurus
|
module docusaurus
|
||||||
|
|
||||||
import incubaid.herolib.core.pathlib
|
import incubaid.herolib.core.pathlib
|
||||||
// import incubaid.herolib.data.atlas.client as atlas_client
|
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.data.markdown.tools as markdowntools
|
||||||
import incubaid.herolib.ui.console
|
import incubaid.herolib.ui.console
|
||||||
|
import incubaid.herolib.web.site
|
||||||
import os
|
import os
|
||||||
|
|
||||||
// Generate docs from site configuration
|
// ============================================================================
|
||||||
|
// Doc Linking - Generate Docusaurus docs from Atlas collections
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// link_docs generates markdown files from site page definitions.
|
||||||
|
// Pages are fetched from Atlas collections and written with frontmatter.
|
||||||
pub fn (mut docsite DocSite) link_docs() ! {
|
pub fn (mut docsite DocSite) link_docs() ! {
|
||||||
c := config()!
|
c := config()!
|
||||||
|
|
||||||
// we generate the docs in the build path
|
|
||||||
docs_path := '${c.path_build.path}/docs'
|
docs_path := '${c.path_build.path}/docs'
|
||||||
|
|
||||||
//reset it
|
reset_docs_dir(docs_path)!
|
||||||
os.rmdir_all(docs_path)!
|
console.print_header('Linking docs to ${docs_path}')
|
||||||
os.mkdir(docs_path)!
|
|
||||||
|
|
||||||
//TODO: now link all the collections to the docs folder
|
mut client := atlas_client.new(export_dir: c.atlas_dir)!
|
||||||
|
mut errors := []string{}
|
||||||
|
|
||||||
println(c)
|
for _, page in docsite.website.pages {
|
||||||
|
process_page(mut client, docs_path, page, mut errors)
|
||||||
|
}
|
||||||
|
|
||||||
$dbg;
|
if errors.len > 0 {
|
||||||
|
report_errors(mut client, errors)!
|
||||||
|
}
|
||||||
|
|
||||||
|
console.print_green('Successfully linked ${docsite.website.pages.len} pages to docs folder')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_docs_dir(docs_path string) ! {
|
||||||
|
if os.exists(docs_path) {
|
||||||
|
os.rmdir_all(docs_path) or {}
|
||||||
|
}
|
||||||
|
os.mkdir_all(docs_path)!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn report_errors(mut client atlas_client.AtlasClient, errors []string) ! {
|
||||||
|
available := client.list_markdown() or { 'Could not list available pages' }
|
||||||
|
console.print_stderr('Available pages:\n${available}')
|
||||||
|
return error('Errors during doc generation:\n${errors.join('\n\n')}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Page Processing
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn process_page(mut client atlas_client.AtlasClient, docs_path string, page site.Page, mut errors []string) {
|
||||||
|
collection, page_name := parse_page_src(page.src) or {
|
||||||
|
errors << err.msg()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content := client.get_page_content(collection, page_name) or {
|
||||||
|
errors << "Page not found: '${collection}:${page_name}'"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
write_page(docs_path, page_name, page, content) or {
|
||||||
|
errors << "Failed to write page '${page_name}': ${err.msg()}"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_page_assets(mut client, docs_path, collection, page_name)
|
||||||
|
console.print_item('Generated: ${page_name}.md')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_page_src(src string) !(string, string) {
|
||||||
|
parts := src.split(':')
|
||||||
|
if parts.len != 2 {
|
||||||
|
return error("Invalid src format '${src}' - expected 'collection:page_name'")
|
||||||
|
}
|
||||||
|
return parts[0], parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_page(docs_path string, page_name string, page site.Page, content string) ! {
|
||||||
|
frontmatter := build_frontmatter(page, content)
|
||||||
|
final_content := frontmatter + '\n\n' + content
|
||||||
|
|
||||||
|
output_path := '${docs_path}/${page_name}.md'
|
||||||
|
mut file := pathlib.get_file(path: output_path, create: true)!
|
||||||
|
file.write(final_content)!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_page_assets(mut client atlas_client.AtlasClient, docs_path string, collection string, page_name string) {
|
||||||
|
client.copy_images(collection, page_name, docs_path) or {}
|
||||||
|
client.copy_files(collection, page_name, docs_path) or {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Frontmatter Generation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn build_frontmatter(page site.Page, content string) string {
|
||||||
|
title := get_title(page, content)
|
||||||
|
description := get_description(page, title)
|
||||||
|
|
||||||
|
mut lines := ['---']
|
||||||
|
lines << "title: '${escape_yaml(title)}'"
|
||||||
|
lines << "description: '${escape_yaml(description)}'"
|
||||||
|
|
||||||
|
if page.draft {
|
||||||
|
lines << 'draft: true'
|
||||||
|
}
|
||||||
|
if page.hide_title {
|
||||||
|
lines << 'hide_title: true'
|
||||||
|
}
|
||||||
|
|
||||||
|
lines << '---'
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_title(page site.Page, content string) string {
|
||||||
|
if page.title.len > 0 {
|
||||||
|
return page.title
|
||||||
|
}
|
||||||
|
extracted := markdowntools.extract_title(content)
|
||||||
|
if extracted.len > 0 {
|
||||||
|
return extracted
|
||||||
|
}
|
||||||
|
return page.src.split(':').last()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_description(page site.Page, title string) string {
|
||||||
|
if page.description.len > 0 {
|
||||||
|
return page.description
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
|
fn escape_yaml(s string) string {
|
||||||
|
return s.replace("'", "''")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
module docusaurus
|
module docusaurus
|
||||||
|
|
||||||
import incubaid.herolib.core.playbook { PlayBook }
|
import incubaid.herolib.core.playbook { PlayBook }
|
||||||
|
import incubaid.herolib.data.atlas
|
||||||
|
import incubaid.herolib.ui.console
|
||||||
import os
|
import os
|
||||||
|
|
||||||
pub fn play(mut plbook PlayBook) ! {
|
pub fn play(mut plbook PlayBook) ! {
|
||||||
@@ -8,61 +10,78 @@ pub fn play(mut plbook PlayBook) ! {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// there should be 1 define section
|
mut dsite := process_define(mut plbook)!
|
||||||
mut action_define := plbook.ensure_once(filter: 'docusaurus.define')!
|
|
||||||
mut param_define := action_define.params
|
|
||||||
|
|
||||||
config_set(
|
|
||||||
path_build: param_define.get_default('path_build', '')!
|
|
||||||
path_publish: param_define.get_default('path_publish', '')!
|
|
||||||
reset: param_define.get_default_false('reset')
|
|
||||||
template_update: param_define.get_default_false('template_update')
|
|
||||||
install: param_define.get_default_false('install')
|
|
||||||
atlas_dir: param_define.get_default('atlas_dir', '${os.home_dir()}/hero/var/atlas_export')!
|
|
||||||
)!
|
|
||||||
|
|
||||||
site_name := param_define.get('name') or {
|
|
||||||
return error('In docusaurus.define, param "name" is required.')
|
|
||||||
}
|
|
||||||
|
|
||||||
dsite_define(site_name)!
|
|
||||||
|
|
||||||
action_define.done = true
|
|
||||||
mut dsite := dsite_get(site_name)!
|
|
||||||
|
|
||||||
dsite.generate()!
|
dsite.generate()!
|
||||||
|
|
||||||
mut actions_build := plbook.find(filter: 'docusaurus.build')!
|
process_build(mut plbook, mut dsite)!
|
||||||
if actions_build.len > 1 {
|
process_publish(mut plbook, mut dsite)!
|
||||||
return error('Multiple "docusaurus.build" actions found. Only one is allowed.')
|
process_dev(mut plbook, mut dsite)!
|
||||||
|
|
||||||
|
plbook.ensure_processed(filter: 'docusaurus.')!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_define(mut plbook PlayBook) !&DocSite {
|
||||||
|
mut action := plbook.ensure_once(filter: 'docusaurus.define')!
|
||||||
|
p := action.params
|
||||||
|
|
||||||
|
atlas_dir := p.get_default('atlas_dir', '${os.home_dir()}/hero/var/atlas_export')!
|
||||||
|
|
||||||
|
config_set(
|
||||||
|
path_build: p.get_default('path_build', '')!
|
||||||
|
path_publish: p.get_default('path_publish', '')!
|
||||||
|
reset: p.get_default_false('reset')
|
||||||
|
template_update: p.get_default_false('template_update')
|
||||||
|
install: p.get_default_false('install')
|
||||||
|
atlas_dir: atlas_dir
|
||||||
|
)!
|
||||||
|
|
||||||
|
site_name := p.get('name') or { return error('docusaurus.define: "name" is required') }
|
||||||
|
atlas_name := p.get_default('atlas', 'main')!
|
||||||
|
|
||||||
|
export_atlas(atlas_name, atlas_dir)!
|
||||||
|
dsite_define(site_name)!
|
||||||
|
action.done = true
|
||||||
|
|
||||||
|
return dsite_get(site_name)!
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_build(mut plbook PlayBook, mut dsite DocSite) ! {
|
||||||
|
if !plbook.max_once(filter: 'docusaurus.build')! {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
for mut action in actions_build {
|
mut action := plbook.get(filter: 'docusaurus.build')!
|
||||||
dsite.build()!
|
dsite.build()!
|
||||||
action.done = true
|
action.done = true
|
||||||
}
|
}
|
||||||
|
|
||||||
mut actions_export := plbook.find(filter: 'docusaurus.publish')!
|
fn process_publish(mut plbook PlayBook, mut dsite DocSite) ! {
|
||||||
if actions_export.len > 1 {
|
if !plbook.max_once(filter: 'docusaurus.publish')! {
|
||||||
return error('Multiple "docusaurus.publish" actions found. Only one is allowed.')
|
return
|
||||||
}
|
}
|
||||||
for mut action in actions_export {
|
mut action := plbook.get(filter: 'docusaurus.publish')!
|
||||||
dsite.build_publish()!
|
dsite.build_publish()!
|
||||||
action.done = true
|
action.done = true
|
||||||
}
|
}
|
||||||
|
|
||||||
mut actions_dev := plbook.find(filter: 'docusaurus.dev')!
|
fn process_dev(mut plbook PlayBook, mut dsite DocSite) ! {
|
||||||
if actions_dev.len > 1 {
|
if !plbook.max_once(filter: 'docusaurus.dev')! {
|
||||||
return error('Multiple "docusaurus.dev" actions found. Only one is allowed.')
|
return
|
||||||
}
|
}
|
||||||
for mut action in actions_dev {
|
mut action := plbook.get(filter: 'docusaurus.dev')!
|
||||||
mut p := action.params
|
p := action.params
|
||||||
dsite.dev(
|
dsite.dev(
|
||||||
host: p.get_default('host', 'localhost')!
|
host: p.get_default('host', 'localhost')!
|
||||||
port: p.get_int_default('port', 3000)!
|
port: p.get_int_default('port', 3000)!
|
||||||
open: p.get_default_false('open')
|
open: p.get_default_false('open')
|
||||||
)!
|
)!
|
||||||
action.done = true
|
action.done = true
|
||||||
}
|
}
|
||||||
|
|
||||||
plbook.ensure_processed(filter: 'docusaurus.')!
|
fn export_atlas(name string, dir string) ! {
|
||||||
|
if !atlas.exists(name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.print_debug('Auto-exporting Atlas "${name}" to ${dir}')
|
||||||
|
mut a := atlas.get(name)!
|
||||||
|
a.export(destination: dir, reset: true, include: true, redis: false)!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,31 @@ module site
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
// Top-level config
|
// ============================================================================
|
||||||
|
// Sidebar Navigation Models (Domain Types)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
pub struct SideBar {
|
pub struct SideBar {
|
||||||
pub mut:
|
pub mut:
|
||||||
my_sidebar []NavItem
|
my_sidebar []NavItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- Variant Type --------
|
|
||||||
pub type NavItem = NavDoc | NavCat | NavLink
|
pub type NavItem = NavDoc | NavCat | NavLink
|
||||||
|
|
||||||
// --------- DOC ITEM ----------
|
|
||||||
pub struct NavDoc {
|
pub struct NavDoc {
|
||||||
pub:
|
pub:
|
||||||
id string // is the page id
|
id string
|
||||||
label string
|
label string
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------- CATEGORY ----------
|
|
||||||
pub struct NavCat {
|
pub struct NavCat {
|
||||||
pub mut:
|
pub mut:
|
||||||
label string
|
label string
|
||||||
collapsible bool
|
collapsible bool = true
|
||||||
collapsed bool
|
collapsed bool
|
||||||
items []NavItem
|
items []NavItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------- LINK ----------
|
|
||||||
pub struct NavLink {
|
pub struct NavLink {
|
||||||
pub:
|
pub:
|
||||||
label string
|
label string
|
||||||
@@ -35,67 +34,69 @@ pub:
|
|||||||
description string
|
description string
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- JSON SERIALIZATION --------
|
// ============================================================================
|
||||||
|
// JSON Serialization Struct (unified to avoid sum type _type field)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
// NavItemJson is used for JSON export with type discrimination
|
struct SidebarItem {
|
||||||
pub struct NavItemJson {
|
typ string @[json: 'type']
|
||||||
pub mut:
|
|
||||||
type_field string @[json: 'type']
|
|
||||||
// For doc
|
|
||||||
id string @[omitempty]
|
id string @[omitempty]
|
||||||
label string @[omitempty]
|
label string
|
||||||
// For link
|
|
||||||
href string @[omitempty]
|
href string @[omitempty]
|
||||||
description string @[omitempty]
|
description string @[omitempty]
|
||||||
// For category
|
collapsible bool @[json: 'collapsible'; omitempty]
|
||||||
collapsible bool
|
collapsed bool @[json: 'collapsed'; omitempty]
|
||||||
collapsed bool
|
items []SidebarItem @[omitempty]
|
||||||
items []NavItemJson @[omitempty]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a single NavItem to JSON-serializable format
|
// ============================================================================
|
||||||
fn nav_item_to_json(item NavItem) !NavItemJson {
|
// JSON Serialization
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
pub fn (sb SideBar) sidebar_to_json() !string {
|
||||||
|
items := sb.my_sidebar.map(to_sidebar_item(it))
|
||||||
|
return json.encode_pretty(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_sidebar_item(item NavItem) SidebarItem {
|
||||||
return match item {
|
return match item {
|
||||||
NavDoc {
|
NavDoc { from_doc(item) }
|
||||||
NavItemJson{
|
NavLink { from_link(item) }
|
||||||
type_field: 'doc'
|
NavCat { from_category(item) }
|
||||||
id: item.id
|
|
||||||
label: item.label
|
|
||||||
collapsible: false
|
|
||||||
collapsed: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NavLink {
|
|
||||||
NavItemJson{
|
|
||||||
type_field: 'link'
|
|
||||||
label: item.label
|
|
||||||
href: item.href
|
|
||||||
description: item.description
|
|
||||||
collapsible: false
|
|
||||||
collapsed: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NavCat {
|
|
||||||
mut json_items := []NavItemJson{}
|
|
||||||
for sub_item in item.items {
|
|
||||||
json_items << nav_item_to_json(sub_item)!
|
|
||||||
}
|
|
||||||
NavItemJson{
|
|
||||||
type_field: 'category'
|
|
||||||
label: item.label
|
|
||||||
collapsible: item.collapsible
|
|
||||||
collapsed: item.collapsed
|
|
||||||
items: json_items
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert entire NavConfig sidebar to JSON string
|
fn from_doc(doc NavDoc) SidebarItem {
|
||||||
pub fn (nc SideBar) sidebar_to_json() !string {
|
return SidebarItem{
|
||||||
mut result := []NavItemJson{}
|
typ: 'doc'
|
||||||
for item in nc.my_sidebar {
|
id: extract_page_id(doc.id)
|
||||||
result << nav_item_to_json(item)!
|
label: doc.label
|
||||||
}
|
}
|
||||||
return json.encode_pretty(result)
|
}
|
||||||
|
|
||||||
|
fn from_link(link NavLink) SidebarItem {
|
||||||
|
return SidebarItem{
|
||||||
|
typ: 'link'
|
||||||
|
label: link.label
|
||||||
|
href: link.href
|
||||||
|
description: link.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_category(cat NavCat) SidebarItem {
|
||||||
|
return SidebarItem{
|
||||||
|
typ: 'category'
|
||||||
|
label: cat.label
|
||||||
|
collapsible: cat.collapsible
|
||||||
|
collapsed: cat.collapsed
|
||||||
|
items: cat.items.map(to_sidebar_item(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_page_id(id string) string {
|
||||||
|
parts := id.split(':')
|
||||||
|
if parts.len == 2 {
|
||||||
|
return parts[1]
|
||||||
|
}
|
||||||
|
return id
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user