mirror of
https://github.com/Warky-Devs/go-mdtopdf-helper.git
synced 2025-05-18 15:17:29 +00:00
Port from pv repo
This commit is contained in:
parent
90a01c63b9
commit
11513f541b
31
.github/workflows/ci.yaml
vendored
Normal file
31
.github/workflows/ci.yaml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Run tests
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Run linting
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
with:
|
||||
version: latest
|
29
.github/workflows/release.yml
vendored
Normal file
29
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
0
.goreleaser.yml
Normal file
0
.goreleaser.yml
Normal file
123
README.md
123
README.md
@ -1,2 +1,123 @@
|
||||
# go-mdtopdf-helper
|
||||
MD to PDF helper in Go
|
||||
|
||||
A command-line tool that automatically converts Markdown files to PDF using wkhtmltopdf. Perfect for maintaining PDF documentation alongside your Markdown files in Git repositories.
|
||||
|
||||
## Features
|
||||
|
||||
- Converts Markdown files to high-quality PDFs
|
||||
- Can run as a Git pre-commit hook
|
||||
- Recursive directory scanning
|
||||
- Parallel file processing
|
||||
- Cross-platform support (Windows, Linux, macOS)
|
||||
- Automatic detection of wkhtmltopdf installation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This tool requires [wkhtmltopdf](https://wkhtmltopdf.org/) to be installed on your system. The application will check for its presence in standard installation locations:
|
||||
|
||||
- Windows: `C:\Program Files\wkhtmltopdf\bin`
|
||||
- Linux: `/usr/local/bin/wkhtmltopdf` or `/usr/bin/wkhtmltopdf`
|
||||
- macOS: `/usr/local/bin/wkhtmltopdf` or via Homebrew
|
||||
|
||||
If wkhtmltopdf is not found, you will be prompted to install it.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/Warky-Devs/go-mdtopdf-helper
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Convert Markdown files in the current directory:
|
||||
|
||||
```bash
|
||||
go-mdtopdf-helper
|
||||
```
|
||||
|
||||
### Command Line Options
|
||||
|
||||
```bash
|
||||
go-mdtopdf-helper [options]
|
||||
|
||||
Options:
|
||||
-dir string
|
||||
Directory to scan for markdown files (default ".")
|
||||
-recursive
|
||||
Scan directories recursively (default true)
|
||||
-parallel
|
||||
Convert files in parallel (default true)
|
||||
-hook
|
||||
Run as git pre-commit hook
|
||||
```
|
||||
|
||||
### Git Pre-commit Hook
|
||||
|
||||
To use as a Git pre-commit hook:
|
||||
|
||||
1. Create a file named `pre-commit` in your repository's `.git/hooks/` directory
|
||||
2. Add the following content:
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
go-mdtopdf-helper -hook
|
||||
```
|
||||
|
||||
3. Make the hook executable:
|
||||
|
||||
```bash
|
||||
chmod +x .git/hooks/pre-commit
|
||||
```
|
||||
|
||||
When enabled as a pre-commit hook, the tool will:
|
||||
1. Detect staged Markdown files
|
||||
2. Ask for confirmation before conversion
|
||||
3. Convert files to PDF
|
||||
4. Automatically stage the generated PDFs
|
||||
|
||||
## PDF Output Configuration
|
||||
|
||||
The generated PDFs are configured with:
|
||||
- 300 DPI resolution
|
||||
- 15mm margins on all sides
|
||||
- Local file access enabled for images
|
||||
- Support for common Markdown extensions
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request. Here's how you can contribute:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
||||
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
### Development Setup
|
||||
|
||||
1. Clone the repository
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
go mod download
|
||||
```
|
||||
3. Make your changes
|
||||
4. Run tests:
|
||||
```bash
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [go-wkhtmltopdf](https://github.com/SebastiaanKlippert/go-wkhtmltopdf) - Go wrapper for wkhtmltopdf
|
||||
- [gomarkdown](https://github.com/gomarkdown/markdown) - Markdown parser and HTML renderer
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- Thanks to [SebastiaanKlippert](https://github.com/SebastiaanKlippert) for the go-wkhtmltopdf library
|
||||
- Thanks to the gomarkdown team for their Markdown parser
|
BIN
README.pdf
Normal file
BIN
README.pdf
Normal file
Binary file not shown.
2
examples/dot.git/hooks/pre-commit
Normal file
2
examples/dot.git/hooks/pre-commit
Normal file
@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
go-mdtopdf-helper -hook
|
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
||||
module github.com/Warky-Devs/go-mdtopdf-helper.git
|
||||
|
||||
go 1.22.5
|
||||
|
||||
require (
|
||||
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.3
|
||||
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62
|
||||
)
|
12
go.sum
Normal file
12
go.sum
Normal file
@ -0,0 +1,12 @@
|
||||
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.3 h1:vrA6+R1BMLKMTbos8jAeuBrImHPGtY4gTlcue3OIej8=
|
||||
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.3/go.mod h1:SQq4xfIdvf6WYKSDxAJc+xOJdolt+/bc1jnQKMtPMvQ=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62 h1:pbAFUZisjG4s6sxvRJvf2N7vhpCvx2Oxb3PmS6pDO1g=
|
||||
github.com/gomarkdown/markdown v0.0.0-20241205020045-f7e15b2f3e62/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
265
main.go
Normal file
265
main.go
Normal file
@ -0,0 +1,265 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/SebastiaanKlippert/go-wkhtmltopdf"
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
)
|
||||
|
||||
type Converter struct {
|
||||
inputDir string
|
||||
recursive bool
|
||||
parallel bool
|
||||
hookMode bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
if err := ensureWkhtmltopdfInPath(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error setting up PATH: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func ensureWkhtmltopdfInPath() error {
|
||||
wkhtmlPath := `C:\Program Files\wkhtmltopdf\bin`
|
||||
|
||||
if _, err := os.Stat(wkhtmlPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("wkhtmltopdf directory not found at: %s", wkhtmlPath)
|
||||
}
|
||||
|
||||
currentPath := os.Getenv("PATH")
|
||||
|
||||
if strings.Contains(currentPath, wkhtmlPath) {
|
||||
return nil // Already in PATH
|
||||
}
|
||||
|
||||
newPath := currentPath + ";" + wkhtmlPath
|
||||
if err := os.Setenv("PATH", newPath); err != nil {
|
||||
return fmt.Errorf("failed to update PATH: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Added wkhtmltopdf to PATH: %s\n", wkhtmlPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
conv := &Converter{}
|
||||
|
||||
// Parse command line flags
|
||||
flag.StringVar(&conv.inputDir, "dir", ".", "Directory to scan for markdown files")
|
||||
flag.BoolVar(&conv.recursive, "recursive", true, "Scan directories recursively")
|
||||
flag.BoolVar(&conv.parallel, "parallel", true, "Convert files in parallel")
|
||||
flag.BoolVar(&conv.hookMode, "hook", false, "Run as git pre-commit hook")
|
||||
flag.Parse()
|
||||
|
||||
if conv.hookMode {
|
||||
if err := conv.runAsHook(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Hook error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
if err := conv.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Converter) runAsHook() error {
|
||||
// Ask user if they want to proceed
|
||||
if !c.confirmConversion() {
|
||||
fmt.Println("Skipping PDF conversion")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get staged markdown files
|
||||
files, err := c.getStagedMarkdownFiles()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get staged files: %w", err)
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert files
|
||||
if err := c.convertFiles(files); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Stage generated PDFs
|
||||
return c.stageGeneratedPDFs(files)
|
||||
}
|
||||
|
||||
func (c *Converter) confirmConversion() bool {
|
||||
fmt.Print("Convert Markdown files to PDF? [Y/n] ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
return response == "" || response == "y" || response == "yes"
|
||||
}
|
||||
|
||||
func (c *Converter) getStagedMarkdownFiles() ([]string, error) {
|
||||
cmd := exec.Command("git", "diff", "--cached", "--name-only", "--diff-filter=d")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var files []string
|
||||
scanner := bufio.NewScanner(bytes.NewReader(output))
|
||||
for scanner.Scan() {
|
||||
file := scanner.Text()
|
||||
if strings.HasSuffix(file, ".md") || strings.HasSuffix(file, ".markdown") {
|
||||
files = append(files, file)
|
||||
}
|
||||
}
|
||||
|
||||
return files, scanner.Err()
|
||||
}
|
||||
|
||||
func (c *Converter) stageGeneratedPDFs(files []string) error {
|
||||
for _, file := range files {
|
||||
pdfFile := strings.TrimSuffix(file, filepath.Ext(file)) + ".pdf"
|
||||
cmd := exec.Command("git", "add", pdfFile)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Printf("Warning: Could not stage %s\n", pdfFile)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Converter) Run() error {
|
||||
files, err := c.findMarkdownFiles()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find markdown files: %w", err)
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
fmt.Println("No markdown files found")
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.convertFiles(files)
|
||||
}
|
||||
|
||||
func (c *Converter) convertFiles(files []string) error {
|
||||
if c.parallel {
|
||||
return c.convertFilesParallel(files)
|
||||
}
|
||||
return c.convertFilesSerial(files)
|
||||
}
|
||||
|
||||
func (c *Converter) findMarkdownFiles() ([]string, error) {
|
||||
var files []string
|
||||
|
||||
walkFn := func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() && !c.recursive && path != c.inputDir {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if !info.IsDir() && (strings.HasSuffix(path, ".md") || strings.HasSuffix(path, ".markdown")) {
|
||||
files = append(files, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := filepath.Walk(c.inputDir, walkFn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (c *Converter) convertFilesParallel(files []string) error {
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, len(files))
|
||||
|
||||
for _, file := range files {
|
||||
wg.Add(1)
|
||||
go func(f string) {
|
||||
defer wg.Done()
|
||||
if err := c.convertFile(f); err != nil {
|
||||
errors <- fmt.Errorf("failed to convert %s: %w", f, err)
|
||||
}
|
||||
}(file)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
for err := range errors {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Converter) convertFilesSerial(files []string) error {
|
||||
for _, file := range files {
|
||||
if err := c.convertFile(file); err != nil {
|
||||
return fmt.Errorf("failed to convert %s: %w", file, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Converter) convertFile(inputFile string) error {
|
||||
mdContent, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
extensions := parser.CommonExtensions | parser.AutoHeadingIDs
|
||||
p := parser.NewWithExtensions(extensions)
|
||||
html := markdown.ToHTML(mdContent, p, nil)
|
||||
|
||||
outputFile := strings.TrimSuffix(inputFile, filepath.Ext(inputFile)) + ".pdf"
|
||||
|
||||
pdfg, err := wkhtmltopdf.NewPDFGenerator()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create PDF generator: %w", err)
|
||||
}
|
||||
|
||||
page := wkhtmltopdf.NewPageReader(strings.NewReader(string(html)))
|
||||
page.EnableLocalFileAccess.Set(true)
|
||||
pdfg.AddPage(page)
|
||||
|
||||
pdfg.Dpi.Set(300)
|
||||
pdfg.MarginTop.Set(15)
|
||||
pdfg.MarginBottom.Set(15)
|
||||
pdfg.MarginLeft.Set(15)
|
||||
pdfg.MarginRight.Set(15)
|
||||
|
||||
if err := pdfg.Create(); err != nil {
|
||||
return fmt.Errorf("failed to create PDF: %w", err)
|
||||
}
|
||||
|
||||
if err := pdfg.WriteFile(outputFile); err != nil {
|
||||
return fmt.Errorf("failed to write PDF file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully converted %s to %s\n", inputFile, outputFile)
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user