diff --git a/.gitignore b/.gitignore index 09b1955..04a1072 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ data config.yaml *.sql + +# Build artifacts +gocv +output/ diff --git a/PLAN.md b/PLAN.md index 820cfbe..2bddf56 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,28 +1,33 @@ # Implementation Plan ## Project Constraints -- [ ] Pure Go binary (no external system dependencies for PDF). -- [ ] Config via `config.yaml` only (no CLI flags). -- [ ] Hardcoded paths: `./content` (input), `./output` (build artifacts). -- [ ] Modes: `gocv` (CLI), `gocv serve` (Daemon). +- [x] Pure Go binary (no external system dependencies for PDF). +- [x] Config via `config.yaml` only (no CLI flags). +- [x] Hardcoded paths: `./content` (input), `./output` (build artifacts). +- [x] Modes: `gocv` (CLI), `gocv serve` (Daemon). - [ ] Commit at every loop iteration. Do not push. Do not tag. ## Current Status - [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). - [ ] 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. - [ ] File Watcher implemented for live reload in Serve Mode. - [ ] Dockerfile created for multi-stage build. ## 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 - [ ] 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 - [x] Initial project structure defined. - [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. diff --git a/content.go b/content.go new file mode 100644 index 0000000..4153d58 --- /dev/null +++ b/content.go @@ -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 +} diff --git a/content/index.md b/content/index.md new file mode 100644 index 0000000..e3cd6bd --- /dev/null +++ b/content/index.md @@ -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. \ No newline at end of file diff --git a/go.mod b/go.mod index 3ea1dda..5ea7465 100644 --- a/go.mod +++ b/go.mod @@ -6,3 +6,5 @@ require ( github.com/gorilla/mux v1.8.1 gopkg.in/yaml.v2 v2.4.0 ) + +require github.com/yuin/goldmark v1.7.16 // indirect diff --git a/go.sum b/go.sum index 215de0d..ed41eb4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/main.go b/main.go index 81367e3..795a5d5 100644 --- a/main.go +++ b/main.go @@ -13,15 +13,49 @@ var APP_VERSION string = "latest" var COMMIT_ID string = "undefined" var ws *WebServer -func main() { - // Create a channel to receive the OS signals - sc := make(chan os.Signal, 1) - signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM) +const ( + contentPath = "./content" + outputPath = "./output" +) +func main() { // Initialize the WebService structure ws = new(WebServer) 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 go ws.Start()