...
This commit is contained in:
10
examples/webtools/docusaurus_example.vsh
Executable file
10
examples/webtools/docusaurus_example.vsh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
|
||||
// Create a new docusaurus factory
|
||||
mut docs := docusaurus.new(
|
||||
build_path: '/tmp/docusaurus_build'
|
||||
)!
|
||||
|
||||
|
||||
BIN
examples/webtools/docusaurus_example_complete
Executable file
BIN
examples/webtools/docusaurus_example_complete
Executable file
Binary file not shown.
239
examples/webtools/docusaurus_example_complete.vsh
Executable file
239
examples/webtools/docusaurus_example_complete.vsh
Executable file
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import os
|
||||
|
||||
fn main() {
|
||||
println('Starting Docusaurus Example with HeroScript')
|
||||
|
||||
// Define the HeroScript that configures our Docusaurus site
|
||||
hero_script := '
|
||||
!!docusaurus.config
|
||||
name:"my-documentation"
|
||||
title:"My Documentation Site"
|
||||
tagline:"Documentation made simple with V and Docusaurus"
|
||||
url:"https://docs.example.com"
|
||||
url_home:"docs/"
|
||||
base_url:"/"
|
||||
favicon:"img/favicon.png"
|
||||
image:"img/hero.png"
|
||||
copyright:"© 2025 Example Organization"
|
||||
|
||||
!!docusaurus.config_meta
|
||||
description:"Comprehensive documentation for our amazing project"
|
||||
image:"https://docs.example.com/img/social-card.png"
|
||||
title:"My Documentation | Official Docs"
|
||||
|
||||
!!docusaurus.ssh_connection
|
||||
name:"production"
|
||||
host:"example.com"
|
||||
login:"deploy"
|
||||
port:22
|
||||
key_path:"~/.ssh/id_rsa"
|
||||
|
||||
!!docusaurus.build_dest
|
||||
ssh_name:"production"
|
||||
path:"/var/www/docs"
|
||||
|
||||
!!docusaurus.navbar
|
||||
title:"My Project"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"Documentation"
|
||||
href:"/docs"
|
||||
position:"left"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"API"
|
||||
href:"/api"
|
||||
position:"left"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"GitHub"
|
||||
href:"https://github.com/example/repo"
|
||||
position:"right"
|
||||
|
||||
!!docusaurus.footer
|
||||
style:"dark"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Documentation"
|
||||
label:"Introduction"
|
||||
to:"/docs"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Documentation"
|
||||
label:"API Reference"
|
||||
to:"/api"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Community"
|
||||
label:"GitHub"
|
||||
href:"https://github.com/example/repo"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"Community"
|
||||
label:"Discord"
|
||||
href:"https://discord.gg/example"
|
||||
|
||||
!!docusaurus.footer_item
|
||||
title:"More"
|
||||
label:"Blog"
|
||||
href:"https://blog.example.com"
|
||||
|
||||
!!docusaurus.import_source
|
||||
url:"https://github.com/example/external-docs"
|
||||
dest:"external"
|
||||
replace:"PROJECT_NAME:My Project, VERSION:1.0.0"
|
||||
'
|
||||
|
||||
mut docs := docusaurus.new(
|
||||
build_path: os.join_path(os.home_dir(), 'hero/var/docusaurus_demo1')
|
||||
update: true // Update the templates
|
||||
heroscript: hero_script
|
||||
) or {
|
||||
eprintln('Error creating docusaurus factory with inline script: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
|
||||
// Create a site directory if it doesn't exist
|
||||
site_path := os.join_path(os.home_dir(), 'hero/var/docusaurus_demo_src')
|
||||
os.mkdir_all(site_path) or {
|
||||
eprintln('Error creating site directory: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Get or create a site using the factory
|
||||
println('Creating site...')
|
||||
mut site := docs.get(
|
||||
name: 'my-documentation'
|
||||
path: site_path
|
||||
init: true // Create if it doesn't exist
|
||||
// Note: The site will use the config from the previously processed HeroScript
|
||||
) or {
|
||||
eprintln('Error creating site: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Generate a sample markdown file for the docs
|
||||
println('Creating sample markdown content...')
|
||||
mut docs_dir := pathlib.get_dir(path: os.join_path(site_path, 'docs'), create: true) or {
|
||||
eprintln('Error creating docs directory: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Create intro.md file
|
||||
mut intro_file := docs_dir.file_get_new('intro.md') or {
|
||||
eprintln('Error creating intro file: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
intro_content := '---
|
||||
title: Introduction
|
||||
slug: /
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Welcome to My Documentation
|
||||
|
||||
This is a sample documentation site created with Docusaurus and HeroLib V using HeroScript configuration.
|
||||
|
||||
## Features
|
||||
|
||||
- Easy to use
|
||||
- Markdown support
|
||||
- Customizable
|
||||
- Search functionality
|
||||
|
||||
## Getting Started
|
||||
|
||||
Follow these steps to get started:
|
||||
|
||||
1. Installation
|
||||
2. Configuration
|
||||
3. Adding content
|
||||
4. Deployment
|
||||
'
|
||||
intro_file.write(intro_content) or {
|
||||
eprintln('Error writing to intro file: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Create quick-start.md file
|
||||
mut quickstart_file := docs_dir.file_get_new('quick-start.md') or {
|
||||
eprintln('Error creating quickstart file: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
quickstart_content := '---
|
||||
title: Quick Start
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Quick Start Guide
|
||||
|
||||
This guide will help you get up and running quickly.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ npm install my-project
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```javascript
|
||||
import { myFunction } from "my-project";
|
||||
|
||||
// Use the function
|
||||
const result = myFunction();
|
||||
console.log(result);
|
||||
```
|
||||
'
|
||||
quickstart_file.write(quickstart_content) or {
|
||||
eprintln('Error writing to quickstart file: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Generate the site
|
||||
println('Generating site...')
|
||||
site.generate() or {
|
||||
eprintln('Error generating site: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
println('Site generated successfully!')
|
||||
|
||||
// Choose which operation to perform:
|
||||
|
||||
// Option 1: Run in development mode
|
||||
// This will start a development server in a screen session
|
||||
println('Starting development server...')
|
||||
site.dev() or {
|
||||
eprintln('Error starting development server: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Option 2: Build for production (uncomment to use)
|
||||
/*
|
||||
println('Building site for production...')
|
||||
site.build() or {
|
||||
eprintln('Error building site: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
println('Site built successfully!')
|
||||
*/
|
||||
|
||||
// Option 3: Build and publish to the remote server (uncomment to use)
|
||||
/*
|
||||
println('Building and publishing site...')
|
||||
site.build_publish() or {
|
||||
eprintln('Error publishing site: ${err}')
|
||||
exit(1)
|
||||
}
|
||||
println('Site published successfully!')
|
||||
*/
|
||||
}
|
||||
@@ -3,7 +3,8 @@ module docusaurus
|
||||
import os
|
||||
import strings
|
||||
|
||||
pub fn (mut site DocSite) clean(args ErrorArgs) ! {
|
||||
// clean removes temporary files and build artifacts from the site directory
|
||||
pub fn (mut site DocSite) clean(args ...ErrorArgs) ! {
|
||||
toclean := '
|
||||
/node_modules
|
||||
|
||||
|
||||
52
lib/web/docusaurus/code_review.md
Normal file
52
lib/web/docusaurus/code_review.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Docusaurus Library Code Review
|
||||
|
||||
This document outlines potential issues and suggested improvements for the Docusaurus library.
|
||||
|
||||
## Critical Issues
|
||||
|
||||
1. **Unexpected Program Termination**
|
||||
- In `dsite.v` line 205, there's an `exit(0)` call in the `process_md` method.
|
||||
- This will terminate the entire program unexpectedly when processing markdown files.
|
||||
- **Recommendation**: Remove the `exit(0)` call and allow the function to complete normally.
|
||||
|
||||
2. **Function Signature Mismatch**
|
||||
- In `clean.v` line 6, the `clean` method requires an `ErrorArgs` parameter: `pub fn (mut site DocSite) clean(args ErrorArgs) !`
|
||||
- In `dsite.v` line 62, the function is called without arguments: `s.clean()!`
|
||||
- **Recommendation**: Either make the parameter optional or update all calling code to provide the required argument.
|
||||
|
||||
## General Improvements
|
||||
|
||||
1. **Incomplete Example**
|
||||
- The example file `docusaurus_example.vsh` is incomplete, only showing initialization.
|
||||
- **Recommendation**: Complete the example with site creation, configuration, and building/development operations.
|
||||
|
||||
2. **Commented Code**
|
||||
- There are several instances of commented-out code, such as in the `factory.v` file.
|
||||
- **Recommendation**: Either complete the implementation of these features or remove the commented code for clarity.
|
||||
|
||||
3. **Debug Statements**
|
||||
- The `process_md` method contains debug print statements (`println(myfm)` and `println(mymd.markdown()!)`)
|
||||
- **Recommendation**: Replace with a proper logging system or remove if not needed for production.
|
||||
|
||||
4. **Error Handling**
|
||||
- Some error handling could be improved with more descriptive error messages.
|
||||
- **Recommendation**: Add more context to error messages, especially in file operations.
|
||||
|
||||
5. **Documentation**
|
||||
- While the config structures have some documentation, many methods lack proper documentation.
|
||||
- **Recommendation**: Add proper V-doc style comments to all public methods and structures.
|
||||
|
||||
## Architectural Suggestions
|
||||
|
||||
1. **Configuration Management**
|
||||
- Consider providing a more fluent API for configuration, rather than requiring JSON file manipulation.
|
||||
|
||||
2. **Dependency Injection**
|
||||
- The factory pattern is well-implemented, but consider making dependencies more explicit.
|
||||
|
||||
3. **Testing**
|
||||
- No tests were found in the codebase.
|
||||
- **Recommendation**: Add unit tests for critical functions.
|
||||
|
||||
4. **Logging**
|
||||
- Replace direct console output with a proper logging system that can be configured based on environment.
|
||||
@@ -199,10 +199,10 @@ fn (mut site DocSite) process_md(mut path pathlib.Path, args MyImport) ! {
|
||||
if !args.visible {
|
||||
myfm.args['draft'] = 'true'
|
||||
}
|
||||
println(myfm)
|
||||
println(mymd.markdown()!)
|
||||
// println(myfm)
|
||||
// println(mymd.markdown()!)
|
||||
mydesto.write(mymd.markdown()!)!
|
||||
exit(0)
|
||||
// Note: exit(0) was removed to prevent unexpected program termination
|
||||
}
|
||||
|
||||
fn (mut site DocSite) template_install() ! {
|
||||
|
||||
@@ -50,9 +50,13 @@ pub fn (mut f DocusaurusFactory) get(args_ DSiteGetArgs) !&DocSite {
|
||||
)!
|
||||
mut template_path := r.patho()!
|
||||
|
||||
// First, check if the new site args provides a configuration that can be written instead of template cfg dir
|
||||
// First, check if the new site args provides a configuration
|
||||
if cfg := args.config {
|
||||
// Use the provided config
|
||||
cfg.write('${args.path}/cfg')!
|
||||
} else if f.config.main.title != '' {
|
||||
// Use the factory's config from heroscript if available
|
||||
f.config.write('${args.path}/cfg')!
|
||||
} else {
|
||||
// Then ensure cfg directory exists in src,
|
||||
if !os.exists('${args.path}/cfg') {
|
||||
@@ -68,8 +72,23 @@ pub fn (mut f DocusaurusFactory) get(args_ DSiteGetArgs) !&DocSite {
|
||||
|
||||
if !os.exists('${args.path}/docs') {
|
||||
if args.init {
|
||||
mut template_cfg := template_path.dir_get('docs')!
|
||||
template_cfg.copy(dest: '${args.path}/docs')!
|
||||
// Create docs directory if it doesn't exist in template or site
|
||||
os.mkdir_all('${args.path}/docs')!
|
||||
|
||||
// Create a default docs/intro.md file
|
||||
intro_content := '---
|
||||
title: Introduction
|
||||
slug: /
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
Welcome to the documentation site.
|
||||
|
||||
This is a default page created by the Docusaurus site generator.
|
||||
'
|
||||
os.write_file('${args.path}/docs/intro.md', intro_content)!
|
||||
} else {
|
||||
return error("Can't find docs dir in chosen docusaurus location: ${args.path}")
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ pub mut:
|
||||
path_build pathlib.Path
|
||||
// path_publish pathlib.Path
|
||||
args DocusaurusArgs
|
||||
config Config // Stores configuration from HeroScript if provided
|
||||
}
|
||||
|
||||
@[params]
|
||||
@@ -23,6 +24,8 @@ pub mut:
|
||||
build_path string
|
||||
production bool
|
||||
update bool
|
||||
heroscript string
|
||||
heroscript_path string
|
||||
}
|
||||
|
||||
pub fn new(args_ DocusaurusArgs) !&DocusaurusFactory {
|
||||
@@ -32,13 +35,24 @@ pub fn new(args_ DocusaurusArgs) !&DocusaurusFactory {
|
||||
}
|
||||
// if args.publish_path == ""{
|
||||
// args.publish_path = "${os.home_dir()}/hero/var/docusaurus/publish"
|
||||
// }
|
||||
// }
|
||||
|
||||
// Create the factory instance
|
||||
mut ds := &DocusaurusFactory{
|
||||
args: args_
|
||||
path_build: pathlib.get_dir(path: args.build_path, create: true)!
|
||||
// path_publish: pathlib.get_dir(path: args_.publish_path, create: true)!
|
||||
}
|
||||
|
||||
// Process HeroScript if provided
|
||||
if args.heroscript != '' || args.heroscript_path != '' {
|
||||
// Use the play function to process the HeroScript
|
||||
ds.config = play(
|
||||
heroscript: args.heroscript
|
||||
heroscript_path: args.heroscript_path
|
||||
)!
|
||||
}
|
||||
|
||||
ds.template_install(install: true, template_update: args.update, delete: true)!
|
||||
|
||||
return ds
|
||||
|
||||
@@ -1,18 +1,41 @@
|
||||
module docusaurus
|
||||
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook }
|
||||
import time
|
||||
import os
|
||||
|
||||
@[params]
|
||||
pub struct PlayArgs {
|
||||
pub mut:
|
||||
heroscript string // if filled in then playbook will be made out of it
|
||||
plbook ?PlayBook
|
||||
reset bool
|
||||
heroscript string // if filled in then playbook will be made out of it
|
||||
heroscript_path string // path to a file containing heroscript
|
||||
plbook ?PlayBook
|
||||
reset bool
|
||||
}
|
||||
|
||||
// Process the heroscript and return a filled Config object
|
||||
pub fn play(args_ PlayArgs) ! {
|
||||
mut plbook := playbook.new(text: args_.heroscript)!
|
||||
pub fn play(args_ PlayArgs) !Config {
|
||||
mut heroscript_text := args_.heroscript
|
||||
|
||||
// If heroscript_path is provided, read the script from the file
|
||||
if args_.heroscript_path != '' && heroscript_text == '' {
|
||||
heroscript_text = os.read_file(args_.heroscript_path) or {
|
||||
return error('Failed to read heroscript from ${args_.heroscript_path}: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
// If no heroscript is provided, return an empty config
|
||||
if heroscript_text == '' && args_.plbook == none {
|
||||
return Config{}
|
||||
}
|
||||
|
||||
// Create playbook from the heroscript text
|
||||
mut plbook := if pb := args_.plbook {
|
||||
pb
|
||||
} else {
|
||||
playbook.new(text: heroscript_text)!
|
||||
}
|
||||
|
||||
mut config := Config{}
|
||||
|
||||
play_config(mut plbook, mut config)!
|
||||
@@ -22,20 +45,27 @@ pub fn play(args_ PlayArgs) ! {
|
||||
play_build_dest(mut plbook, mut config)!
|
||||
play_navbar(mut plbook, mut config)!
|
||||
play_footer(mut plbook, mut config)!
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
fn play_config(mut plbook PlayBook, mut config Config) ! {
|
||||
config_actions := plbook.find(filter: 'docusaurus.config')!
|
||||
for action in config_actions {
|
||||
mut p := action.params
|
||||
// Get optional name parameter or use base_url as fallback
|
||||
name := p.get_default('name', 'docusaurus-site')!
|
||||
|
||||
config.main = Main{
|
||||
title: p.get_default('title', 'Internet Geek')!
|
||||
tagline: p.get_default('tagline', 'Internet Geek')!
|
||||
favicon: p.get_default('favicon', 'img/favicon.png')!
|
||||
url: p.get_default('url', 'https://friends.threefold.info')!
|
||||
url_home: p.get_default('url_home', 'docs/')!
|
||||
base_url: p.get_default('base_url', '/testsite/')!
|
||||
image: p.get_default('image', 'img/tf_graph.png')!
|
||||
name: name
|
||||
title: p.get_default('title', 'Documentation Site')!
|
||||
tagline: p.get_default('tagline', 'Your awesome documentation')!
|
||||
favicon: p.get_default('favicon', 'img/favicon.png')!
|
||||
url: p.get_default('url', 'https://docs.example.com')!
|
||||
url_home: p.get_default('url_home', 'docs/')!
|
||||
base_url: p.get_default('base_url', '/')!
|
||||
image: p.get_default('image', 'img/hero.png')!
|
||||
copyright: p.get_default('copyright', '© ' + time.now().year.str() + ' Example Organization')!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,9 +75,9 @@ fn play_config_meta(mut plbook PlayBook, mut config Config) ! {
|
||||
for action in meta_actions {
|
||||
mut p := action.params
|
||||
config.main.metadata = MainMetadata{
|
||||
description: p.get_default('description', 'ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet.')!
|
||||
image: p.get_default('image', 'https://threefold.info/something/img/tf_graph.png')!
|
||||
title: p.get_default('title', 'ThreeFold Technology Vision')!
|
||||
description: p.get_default('description', 'Comprehensive documentation built with Docusaurus.')!
|
||||
image: p.get_default('image', 'https://docs.example.com/img/social-card.png')!
|
||||
title: p.get_default('title', 'Documentation | ' + config.main.title)!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,15 +138,15 @@ fn play_navbar(mut plbook PlayBook, mut config Config) ! {
|
||||
navbar_actions := plbook.find(filter: 'docusaurus.navbar')!
|
||||
for action in navbar_actions {
|
||||
mut p := action.params
|
||||
config.navbar.title = p.get_default('title', 'Chief Executive Geek')!
|
||||
config.navbar.title = p.get_default('title', config.main.title)!
|
||||
}
|
||||
|
||||
navbar_item_actions := plbook.find(filter: 'docusaurus.navbar_item')!
|
||||
for action in navbar_item_actions {
|
||||
mut p := action.params
|
||||
mut item := NavbarItem{
|
||||
label: p.get_default('label', 'ThreeFold Technology')!
|
||||
href: p.get_default('href', 'https://threefold.info/tech')!
|
||||
label: p.get_default('label', 'Documentation')!
|
||||
href: p.get_default('href', '/docs')!
|
||||
position: p.get_default('position', 'right')!
|
||||
}
|
||||
config.navbar.items << item
|
||||
|
||||
226
lib/web/docusaurus/readme.md
Normal file
226
lib/web/docusaurus/readme.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# Docusaurus Library for V
|
||||
|
||||
A V language library for creating, configuring, and managing [Docusaurus](https://docusaurus.io/) documentation sites with minimal effort.
|
||||
|
||||
## Overview
|
||||
|
||||
This library provides a convenient wrapper around Docusaurus, a powerful static website generator maintained by Meta. It allows you to:
|
||||
|
||||
- Create and manage multiple documentation sites
|
||||
- Configure sites through structured JSON or HeroScript
|
||||
- Build sites for production
|
||||
- Set up development environments with hot reloading
|
||||
- Handle file watching for automatic rebuilding
|
||||
- Deploy sites to remote servers
|
||||
- Import content from Git repositories
|
||||
|
||||
## Features
|
||||
|
||||
- **Simple API**: Create and manage Docusaurus sites with a few lines of V code
|
||||
- **Flexible Configuration**: Configure sites using JSON files or programmatically
|
||||
- **Development Mode**: Built-in development server with file watching
|
||||
- **Remote Deployment**: Push to remote servers via SSH
|
||||
- **Content Import**: Import content from Git repositories
|
||||
- **Template Management**: Uses standard Docusaurus templates with custom configuration
|
||||
|
||||
## Installation
|
||||
|
||||
The library is part of the HeroLib package and can be imported as follows:
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
|
||||
// Create a new docusaurus factory
|
||||
mut docs := docusaurus.new(
|
||||
build_path: '~/docusaurus_projects' // Optional, defaults to ~/hero/var/docusaurus
|
||||
)!
|
||||
|
||||
// Get or create a site
|
||||
mut site := docs.get(
|
||||
name: 'my-docs',
|
||||
path: '~/my-docs-source',
|
||||
init: true // Create if doesn't exist
|
||||
)!
|
||||
|
||||
// Run development server
|
||||
site.dev()!
|
||||
|
||||
// Or build for production
|
||||
// site.build()!
|
||||
|
||||
// Or build and publish
|
||||
// site.build_publish()!
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Configuration is done via JSON files in the `cfg` directory of your site:
|
||||
|
||||
- `main.json`: Main site configuration (title, URL, metadata, etc.)
|
||||
- `navbar.json`: Navigation bar configuration
|
||||
- `footer.json`: Footer configuration
|
||||
|
||||
Example `main.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "My Documentation",
|
||||
"tagline": "Documentation Made Easy",
|
||||
"favicon": "img/favicon.png",
|
||||
"url": "https://docs.example.com",
|
||||
"url_home": "docs/",
|
||||
"baseUrl": "/",
|
||||
"image": "img/logo.png",
|
||||
"metadata": {
|
||||
"description": "Comprehensive documentation for my project",
|
||||
"image": "https://docs.example.com/img/logo.png",
|
||||
"title": "My Documentation"
|
||||
},
|
||||
"buildDest": ["user@server:/path/to/deployment"],
|
||||
"buildDestDev": ["user@server:/path/to/dev-deployment"]
|
||||
}
|
||||
```
|
||||
|
||||
### Directory Structure
|
||||
|
||||
A typical Docusaurus site managed by this library has the following structure:
|
||||
|
||||
```
|
||||
my-site/
|
||||
├── cfg/
|
||||
│ ├── main.json
|
||||
│ ├── navbar.json
|
||||
│ └── footer.json
|
||||
├── docs/
|
||||
│ ├── intro.md
|
||||
│ └── ...
|
||||
├── src/
|
||||
│ └── ...
|
||||
└── static/
|
||||
└── img/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. Create a new site using `docs.get()`
|
||||
2. Configure your site via JSON files or programmatically
|
||||
3. Run `site.dev()` to start development server
|
||||
4. Edit files in the `docs` directory
|
||||
5. When ready, run `site.build()` or `site.build_publish()` to deploy
|
||||
|
||||
## Features in Detail
|
||||
|
||||
### File Watching
|
||||
|
||||
The library includes a file watcher that automatically updates the build directory when files change in the source directory. This enables a smooth development experience with hot reloading.
|
||||
|
||||
### Remote Deployment
|
||||
|
||||
Sites can be deployed to remote servers via SSH. Configure the deployment destinations in `main.json`:
|
||||
|
||||
```json
|
||||
"buildDest": ["user@server:/path/to/deployment"],
|
||||
"buildDestDev": ["user@server:/path/to/dev-deployment"]
|
||||
```
|
||||
|
||||
### Content Import
|
||||
|
||||
You can import content from Git repositories:
|
||||
|
||||
```json
|
||||
"import": [
|
||||
{
|
||||
"url": "https://github.com/username/repo",
|
||||
"dest": "external-docs",
|
||||
"visible": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Advanced Usage with HeroScript
|
||||
|
||||
You can configure your Docusaurus site using HeroScript in multiple ways:
|
||||
|
||||
### Option 1: Provide HeroScript directly to the factory
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
|
||||
// Create a factory with inline HeroScript
|
||||
mut docs := docusaurus.new(
|
||||
build_path: '~/docusaurus_sites'
|
||||
heroscript: '
|
||||
!!docusaurus.config
|
||||
name:"my-docs"
|
||||
title:"My Documentation"
|
||||
tagline:"Documentation Made Easy"
|
||||
url:"https://docs.example.com"
|
||||
base_url:"/"
|
||||
|
||||
!!docusaurus.navbar
|
||||
title:"My Project"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"GitHub"
|
||||
href:"https://github.com/username/repo"
|
||||
position:"right"
|
||||
'
|
||||
)!
|
||||
```
|
||||
|
||||
### Option 2: Load HeroScript from a file
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
|
||||
// Create a factory using HeroScript from a file
|
||||
mut docs := docusaurus.new(
|
||||
build_path: '~/docusaurus_sites'
|
||||
heroscript_path: '~/my_docusaurus_config.hero'
|
||||
)!
|
||||
```
|
||||
|
||||
### Option 3: Process HeroScript separately
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
|
||||
mut script := '
|
||||
!!docusaurus.config
|
||||
title:"My Documentation"
|
||||
tagline:"Documentation Made Easy"
|
||||
url:"https://docs.example.com"
|
||||
base_url:"/"
|
||||
|
||||
!!docusaurus.navbar
|
||||
title:"My Project"
|
||||
|
||||
!!docusaurus.navbar_item
|
||||
label:"GitHub"
|
||||
href:"https://github.com/username/repo"
|
||||
position:"right"
|
||||
'
|
||||
|
||||
// Process the HeroScript to get a Config object
|
||||
mut config := docusaurus.play(heroscript: script)!
|
||||
|
||||
// Use the config when creating a site
|
||||
mut site := docs.get(
|
||||
name: 'my-site',
|
||||
path: '~/my-site-source',
|
||||
config: config
|
||||
)!
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This library is part of the HeroLib project and follows its licensing terms.
|
||||
@@ -3,7 +3,10 @@ module docusaurus
|
||||
import freeflowuniverse.herolib.develop.gittools
|
||||
import freeflowuniverse.herolib.osal
|
||||
import freeflowuniverse.herolib.installers.web.bun
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
@[params]
|
||||
struct TemplateInstallArgs {
|
||||
@@ -21,24 +24,17 @@ fn (mut self DocusaurusFactory) template_install(args TemplateInstallArgs) ! {
|
||||
)!
|
||||
mut template_path := r.patho()!
|
||||
|
||||
for item in ['package.json', 'sidebars.ts', 'tsconfig.json'] {
|
||||
mut aa := template_path.file_get(item)!
|
||||
aa.copy(dest: '${self.path_build.path}/${item}')!
|
||||
}
|
||||
|
||||
// always start from template first
|
||||
// always start from template first for static assets and source files
|
||||
for item in ['src', 'static'] {
|
||||
mut aa := template_path.dir_get(item)!
|
||||
aa.copy(dest: '${self.path_build.path}/${item}', delete: args.delete)!
|
||||
}
|
||||
|
||||
for item in ['package.json', 'sidebars.ts', 'tsconfig.json', 'docusaurus.config.ts'] {
|
||||
src_path := os.join_path(template_path.path, item)
|
||||
dest_path := os.join_path(self.path_build.path, item)
|
||||
os.cp(src_path, dest_path) or {
|
||||
return error('Failed to copy ${item} to build path: ${err}')
|
||||
}
|
||||
}
|
||||
// Generate config files dynamically from config
|
||||
self.generate_package_json()!
|
||||
self.generate_tsconfig_json()!
|
||||
self.generate_sidebars_ts()!
|
||||
self.generate_docusaurus_config_ts()!
|
||||
|
||||
if args.install {
|
||||
// install bun
|
||||
@@ -55,6 +51,251 @@ fn (mut self DocusaurusFactory) template_install(args TemplateInstallArgs) ! {
|
||||
)!
|
||||
}
|
||||
|
||||
mut aa := template_path.dir_get('docs') or { return }
|
||||
aa.delete()!
|
||||
// Only try to delete docs if it exists in the template
|
||||
if os.exists(os.join_path(template_path.path, 'docs')) {
|
||||
mut aa := template_path.dir_get('docs')!
|
||||
aa.delete()!
|
||||
}
|
||||
}
|
||||
|
||||
// Generate package.json based on the configuration
|
||||
fn (mut self DocusaurusFactory) generate_package_json() ! {
|
||||
// Build package.json content as a structured JSON string
|
||||
mut name := 'docusaurus-site'
|
||||
if self.config.main.name != '' {
|
||||
name = self.config.main.name
|
||||
} else if self.config.navbar.title != '' {
|
||||
name = self.config.navbar.title.to_lower().replace(' ', '-')
|
||||
}
|
||||
|
||||
// Create the JSON structure manually
|
||||
package_json := '{
|
||||
"name": "${name}",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.1.0",
|
||||
"@docusaurus/preset-classic": "^3.1.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.1.0",
|
||||
"@docusaurus/tsconfig": "^3.1.0",
|
||||
"@docusaurus/types": "^3.1.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [">0.5%", "not dead", "not op_mini all"],
|
||||
"development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
}
|
||||
}'
|
||||
|
||||
// Write to file
|
||||
mut package_file := pathlib.get_file(path: os.join_path(self.path_build.path, 'package.json'), create: true)!
|
||||
package_file.write(package_json)!
|
||||
}
|
||||
|
||||
// Generate tsconfig.json based on the configuration
|
||||
fn (mut self DocusaurusFactory) generate_tsconfig_json() ! {
|
||||
// Create tsconfig.json as a manual JSON string
|
||||
tsconfig_json := '{
|
||||
"extends": "@docusaurus/tsconfig",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*", "docusaurus.config.ts"]
|
||||
}'
|
||||
|
||||
// Write to file
|
||||
mut tsconfig_file := pathlib.get_file(path: os.join_path(self.path_build.path, 'tsconfig.json'), create: true)!
|
||||
tsconfig_file.write(tsconfig_json)!
|
||||
}
|
||||
|
||||
// Generate sidebars.ts based on the configuration
|
||||
fn (mut self DocusaurusFactory) generate_sidebars_ts() ! {
|
||||
// Simple default sidebar structure
|
||||
sidebar_content := "import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
const sidebars: SidebarsConfig = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||
};
|
||||
|
||||
export default sidebars;
|
||||
"
|
||||
mut sidebars_file := pathlib.get_file(path: os.join_path(self.path_build.path, 'sidebars.ts'), create: true)!
|
||||
sidebars_file.write(sidebar_content)!
|
||||
}
|
||||
|
||||
// Generate docusaurus.config.ts based on the configuration
|
||||
fn (mut self DocusaurusFactory) generate_docusaurus_config_ts() ! {
|
||||
// Use config values with fallbacks
|
||||
title := if self.config.main.title != '' { self.config.main.title } else { 'Docusaurus Site' }
|
||||
tagline := if self.config.main.tagline != '' { self.config.main.tagline } else { 'Documentation Site' }
|
||||
url := if self.config.main.url != '' { self.config.main.url } else { 'https://example.com' }
|
||||
base_url := if self.config.main.base_url != '' { self.config.main.base_url } else { '/' }
|
||||
favicon := if self.config.main.favicon != '' { self.config.main.favicon } else { 'img/favicon.png' }
|
||||
|
||||
// Format navbar items from config
|
||||
mut navbar_items := []string{}
|
||||
for item in self.config.navbar.items {
|
||||
navbar_items << "{
|
||||
label: '${item.label}',
|
||||
href: '${item.href}',
|
||||
position: '${item.position}'
|
||||
}"
|
||||
}
|
||||
|
||||
navbar_items_str := navbar_items.join(',\n ')
|
||||
|
||||
// Generate footer links if available
|
||||
mut footer_links := []string{}
|
||||
for link in self.config.footer.links {
|
||||
mut items := []string{}
|
||||
for item in link.items {
|
||||
mut item_str := "{"
|
||||
if item.label != '' {
|
||||
item_str += "label: '${item.label}', "
|
||||
}
|
||||
|
||||
// Ensure only one of 'to', 'href', or 'html' is used
|
||||
// Priority: href > to > html
|
||||
if item.href != '' {
|
||||
item_str += "href: '${item.href}'"
|
||||
} else if item.to != '' {
|
||||
item_str += "to: '${item.to}'"
|
||||
} else {
|
||||
// Default to linking to docs if nothing specified
|
||||
item_str += "to: '/docs'"
|
||||
}
|
||||
|
||||
item_str += "}"
|
||||
items << item_str
|
||||
}
|
||||
|
||||
footer_links << "{
|
||||
title: '${link.title}',
|
||||
items: [
|
||||
${items.join(',\n ')}
|
||||
]
|
||||
}"
|
||||
}
|
||||
|
||||
footer_links_str := footer_links.join(',\n ')
|
||||
|
||||
// Year for copyright
|
||||
year := time.now().year.str()
|
||||
|
||||
copyright := if self.config.main.copyright != '' {
|
||||
self.config.main.copyright
|
||||
} else {
|
||||
"Copyright © ${year} ${title}"
|
||||
}
|
||||
|
||||
// Construct the full config file content
|
||||
config_content := "import {themes as prismThemes} from 'prism-react-renderer';
|
||||
import type {Config} from '@docusaurus/types';
|
||||
import type * as Preset from '@docusaurus/preset-classic';
|
||||
|
||||
const config: Config = {
|
||||
title: '${title}',
|
||||
tagline: '${tagline}',
|
||||
favicon: '${favicon}',
|
||||
|
||||
// Set the production url of your site here
|
||||
url: '${url}',
|
||||
// Set the /<baseUrl>/ pathname under which your site is served
|
||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: '${base_url}',
|
||||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: 'freeflowuniverse', // Usually your GitHub org/user name.
|
||||
projectName: '${self.config.main.name}', // Usually your repo name.
|
||||
|
||||
onBrokenLinks: 'warn',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
|
||||
// Enable for i18n
|
||||
// i18n: {
|
||||
// defaultLocale: 'en',
|
||||
// locales: ['en'],
|
||||
// },
|
||||
|
||||
presets: [
|
||||
[
|
||||
'classic',
|
||||
{
|
||||
docs: {
|
||||
sidebarPath: './sidebars.ts',
|
||||
},
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
},
|
||||
} satisfies Preset.Options,
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
// Replace with your project's social card
|
||||
image: 'img/docusaurus-social-card.jpg',
|
||||
navbar: {
|
||||
title: '${self.config.navbar.title}',
|
||||
logo: {
|
||||
alt: 'Logo',
|
||||
src: 'img/logo.svg',
|
||||
},
|
||||
items: [
|
||||
${navbar_items_str}
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: '${self.config.footer.style}',
|
||||
links: [
|
||||
${footer_links_str}
|
||||
],
|
||||
copyright: '${copyright}',
|
||||
},
|
||||
prism: {
|
||||
theme: prismThemes.github,
|
||||
darkTheme: prismThemes.dracula,
|
||||
},
|
||||
} satisfies Preset.ThemeConfig,
|
||||
};
|
||||
|
||||
export default config;
|
||||
"
|
||||
|
||||
mut config_file := pathlib.get_file(path: os.join_path(self.path_build.path, 'docusaurus.config.ts'), create: true)!
|
||||
config_file.write(config_content)!
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user