Implement CLI mode and Markdown parsing
- Add CLI mode distinction (gocv vs gocv serve) - Add goldmark dependency for Markdown parsing - Create content reading logic from ./content directory - Implement Markdown to HTML conversion - Add sample content/index.md for testing - Update .gitignore for build artifacts Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,3 +3,7 @@
|
|||||||
data
|
data
|
||||||
config.yaml
|
config.yaml
|
||||||
*.sql
|
*.sql
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
gocv
|
||||||
|
output/
|
||||||
|
|||||||
19
PLAN.md
19
PLAN.md
@@ -1,28 +1,33 @@
|
|||||||
# Implementation Plan
|
# Implementation Plan
|
||||||
|
|
||||||
## Project Constraints
|
## Project Constraints
|
||||||
- [ ] Pure Go binary (no external system dependencies for PDF).
|
- [x] Pure Go binary (no external system dependencies for PDF).
|
||||||
- [ ] Config via `config.yaml` only (no CLI flags).
|
- [x] Config via `config.yaml` only (no CLI flags).
|
||||||
- [ ] Hardcoded paths: `./content` (input), `./output` (build artifacts).
|
- [x] Hardcoded paths: `./content` (input), `./output` (build artifacts).
|
||||||
- [ ] Modes: `gocv` (CLI), `gocv serve` (Daemon).
|
- [x] Modes: `gocv` (CLI), `gocv serve` (Daemon).
|
||||||
- [ ] Commit at every loop iteration. Do not push. Do not tag.
|
- [ ] Commit at every loop iteration. Do not push. Do not tag.
|
||||||
|
|
||||||
## Current Status
|
## Current Status
|
||||||
- [x] Project backbone exists (HTTP server, graceful shutdown, config reading).
|
- [x] Project backbone exists (HTTP server, graceful shutdown, config reading).
|
||||||
- [ ] Markdown parsing logic implemented.
|
- [x] Markdown parsing logic implemented (using goldmark).
|
||||||
- [ ] HTML Template engine integrated (Hugo-like theme selection).
|
- [ ] HTML Template engine integrated (Hugo-like theme selection).
|
||||||
- [ ] PDF Generation implemented (Pure Go library selected and integrated).
|
- [ ] PDF Generation implemented (Pure Go library selected and integrated).
|
||||||
- [ ] CLI Mode (`gocv`) generates static files to `./output` and exits.
|
- [x] CLI Mode (`gocv`) generates static files to `./output` and exits.
|
||||||
- [ ] Serve Mode (`gocv serve`) hosts HTML and serves PDF on demand.
|
- [ ] Serve Mode (`gocv serve`) hosts HTML and serves PDF on demand.
|
||||||
- [ ] File Watcher implemented for live reload in Serve Mode.
|
- [ ] File Watcher implemented for live reload in Serve Mode.
|
||||||
- [ ] Dockerfile created for multi-stage build.
|
- [ ] Dockerfile created for multi-stage build.
|
||||||
|
|
||||||
## Active Task
|
## Active Task
|
||||||
- [ ] Analyze existing backbone code and integrate Markdown parsing.
|
- [x] Analyze existing backbone code and integrate Markdown parsing.
|
||||||
|
- [ ] Integrate HTML template engine with theme selection.
|
||||||
|
|
||||||
## Known Issues / Blockers
|
## Known Issues / Blockers
|
||||||
- [ ] Identify best Pure Go PDF library that supports HTML/CSS (or define CSS subset).
|
- [ ] Identify best Pure Go PDF library that supports HTML/CSS (or define CSS subset).
|
||||||
|
- [ ] Current output is raw HTML fragments without full HTML document structure.
|
||||||
|
|
||||||
## Completed Log
|
## Completed Log
|
||||||
- [x] Initial project structure defined.
|
- [x] Initial project structure defined.
|
||||||
- [x] Basic HTTP server and signal handling implemented.
|
- [x] Basic HTTP server and signal handling implemented.
|
||||||
|
- [x] CLI mode vs Serve mode distinction implemented (checks os.Args[1] for "serve").
|
||||||
|
- [x] Content reading from `./content` directory implemented.
|
||||||
|
- [x] Markdown to HTML conversion using goldmark library.
|
||||||
|
|||||||
108
content.go
Normal file
108
content.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContentFile represents a parsed content file
|
||||||
|
type ContentFile struct {
|
||||||
|
SourcePath string // Original .md file path
|
||||||
|
Name string // File name without extension
|
||||||
|
Content string // Raw markdown content
|
||||||
|
HTML string // Converted HTML
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateOutput reads content from ./content, processes it, and writes to ./output
|
||||||
|
func generateOutput() error {
|
||||||
|
// Ensure content directory exists
|
||||||
|
if _, err := os.Stat(contentPath); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("content directory does not exist: %s", contentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure output directory exists
|
||||||
|
if err := os.MkdirAll(outputPath, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create output directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all markdown files from content directory
|
||||||
|
files, err := readContentFiles()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read content files: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(files) == 0 {
|
||||||
|
fmt.Println("Warning: No markdown files found in content directory")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each file
|
||||||
|
for _, file := range files {
|
||||||
|
fmt.Printf("Processing: %s\n", file.SourcePath)
|
||||||
|
|
||||||
|
// Convert markdown to HTML
|
||||||
|
html, err := markdownToHTML(file.Content)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to convert %s: %w", file.SourcePath, err)
|
||||||
|
}
|
||||||
|
file.HTML = html
|
||||||
|
|
||||||
|
// Write output
|
||||||
|
outputFile := filepath.Join(outputPath, file.Name+".html")
|
||||||
|
if err := os.WriteFile(outputFile, []byte(html), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write %s: %w", outputFile, err)
|
||||||
|
}
|
||||||
|
fmt.Printf(" -> Written: %s\n", outputPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readContentFiles reads all .md files from the content directory
|
||||||
|
func readContentFiles() ([]ContentFile, error) {
|
||||||
|
var files []ContentFile
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(contentPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := entry.Name()
|
||||||
|
if !strings.HasSuffix(name, ".md") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := filepath.Join(contentPath, name)
|
||||||
|
content, err := os.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read %s: %w", fullPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
files = append(files, ContentFile{
|
||||||
|
SourcePath: fullPath,
|
||||||
|
Name: strings.TrimSuffix(name, ".md"),
|
||||||
|
Content: string(content),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// markdownToHTML converts markdown content to HTML using goldmark
|
||||||
|
func markdownToHTML(markdown string) (string, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := goldmark.Convert([]byte(markdown), &buf); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
13
content/index.md
Normal file
13
content/index.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Welcome to Go-CV
|
||||||
|
|
||||||
|
This is a sample markdown file for testing the Go-CV static site generator.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Pure Go implementation
|
||||||
|
- Markdown to HTML conversion
|
||||||
|
- No external dependencies for PDF generation
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
This project generates static HTML from markdown files in the `./content` directory.
|
||||||
2
go.mod
2
go.mod
@@ -6,3 +6,5 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/yuin/goldmark v1.7.16 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -1,5 +1,7 @@
|
|||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||||
|
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
|||||||
42
main.go
42
main.go
@@ -13,15 +13,49 @@ var APP_VERSION string = "latest"
|
|||||||
var COMMIT_ID string = "undefined"
|
var COMMIT_ID string = "undefined"
|
||||||
var ws *WebServer
|
var ws *WebServer
|
||||||
|
|
||||||
func main() {
|
const (
|
||||||
// Create a channel to receive the OS signals
|
contentPath = "./content"
|
||||||
sc := make(chan os.Signal, 1)
|
outputPath = "./output"
|
||||||
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM)
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
// Initialize the WebService structure
|
// Initialize the WebService structure
|
||||||
ws = new(WebServer)
|
ws = new(WebServer)
|
||||||
ws.Initialize()
|
ws.Initialize()
|
||||||
|
|
||||||
|
// Determine mode based on command line argument
|
||||||
|
// No CLI flags - just check os.Args
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "serve" {
|
||||||
|
// Serve mode: Start HTTP server with file watching
|
||||||
|
runServeMode()
|
||||||
|
} else {
|
||||||
|
// CLI mode: Generate static files and exit
|
||||||
|
runCLIMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCLIMode() {
|
||||||
|
fmt.Println("Running in CLI mode...")
|
||||||
|
fmt.Printf("Reading content from: %s\n", contentPath)
|
||||||
|
fmt.Printf("Writing output to: %s\n", outputPath)
|
||||||
|
|
||||||
|
// Read content and generate output
|
||||||
|
err := generateOutput()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error generating output: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Generation complete.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runServeMode() {
|
||||||
|
fmt.Println("Running in Serve mode...")
|
||||||
|
|
||||||
|
// Create a channel to receive the OS signals
|
||||||
|
sc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
// Start the WebService in a separate goroutine
|
// Start the WebService in a separate goroutine
|
||||||
go ws.Start()
|
go ws.Start()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user