Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: Render CV to PDF format #6

Merged
merged 7 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
6 changes: 4 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
"cwd": "${workspaceFolder}",
"program": "./cmd/cvwonder",
"args": [
"serve",
"generate",
// "serve",
"--input=cv.yml",
"--output=generated/",
"--theme=default",
"--watch",
"--format=pdf",
// "--watch",
// "--verbose",
],
"debugAdapter": "dlv-dap",
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ doc-install:
poetry --directory docs/ lock && poetry --directory docs/ install

doc:
poetry --directory docs/ run python -m mkdocs serve --config-file docs/mkdocs.yml
poetry --directory docs/ run mkdocs serve --config-file docs/mkdocs.yml
17 changes: 10 additions & 7 deletions cmd/cvwonder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ func main() {
logrus.Info(" Input file: ", inputFile.RelativePath)
logrus.Info(" Output directory: ", outputDir.RelativePath)
logrus.Info(" Theme: ", utils.CliArgs.ThemeName)
logrus.Info(" Format: ", utils.CliArgs.Format)
logrus.Info()

content, err := cvparser.ParseFile(inputFile.FullPath)
utils.CheckError(err)

cvrender.Render(content, outputDir.FullPath, inputFile.FullPath, utils.CliArgs.ThemeName)
cvrender.Render(content, outputDir.FullPath, inputFile.FullPath, utils.CliArgs.ThemeName, utils.CliArgs.Format)
utils.CheckError(err)
},
}
Expand All @@ -81,27 +82,29 @@ func main() {
logrus.Info(" Input file: ", inputFile.RelativePath)
logrus.Info(" Output directory: ", outputDir.RelativePath)
logrus.Info(" Theme: ", utils.CliArgs.ThemeName)
logrus.Info(" Format: ", utils.CliArgs.Format)
logrus.Info(" Watch: ", utils.CliArgs.Watch)
logrus.Info()

content, err := cvparser.ParseFile(inputFile.FullPath)
utils.CheckError(err)

cvrender.Render(content, outputDir.FullPath, inputFile.FullPath, utils.CliArgs.ThemeName)
cvrender.Render(content, outputDir.FullPath, inputFile.FullPath, utils.CliArgs.ThemeName, utils.CliArgs.Format)
utils.CheckError(err)

if utils.CliArgs.Watch {
go watcher.ObserveFileEvents(outputDir.FullPath, inputFile.FullPath, utils.CliArgs.ThemeName)
go watcher.ObserveFileEvents(outputDir.FullPath, inputFile.FullPath, utils.CliArgs.ThemeName, utils.CliArgs.Format)
}
cvserve.OpenBrowser(outputDir.FullPath, inputFile.FullPath)
cvserve.StartLiveReloader(outputDir.FullPath, inputFile.FullPath)
},
}

rootCmd.PersistentFlags().StringVarP(&utils.CliArgs.InputFile, "input", "i", "cv.yml", "Input file in YAML format (required)")
rootCmd.PersistentFlags().StringVarP(&utils.CliArgs.OutputDirectory, "output", "o", "generated/", "Output directory (optional)")
rootCmd.PersistentFlags().StringVarP(&utils.CliArgs.ThemeName, "theme", "t", "default", "Name of the theme (optional)")
rootCmd.PersistentFlags().BoolVarP(&utils.CliArgs.Verbose, "verbose", "v", false, "Verbose mode")
rootCmd.PersistentFlags().StringVarP(&utils.CliArgs.InputFile, "input", "i", "cv.yml", "Input file in YAML format (required). Default is 'cv.yml'")
rootCmd.PersistentFlags().StringVarP(&utils.CliArgs.OutputDirectory, "output", "o", "generated/", "Output directory (optional). Default is 'generated/'")
rootCmd.PersistentFlags().StringVarP(&utils.CliArgs.ThemeName, "theme", "t", "default", "Name of the theme (optional). Default is 'default'.")
rootCmd.PersistentFlags().StringVarP(&utils.CliArgs.Format, "format", "f", "html", "Format for the export (optional). Default is 'html'.")
rootCmd.PersistentFlags().BoolVarP(&utils.CliArgs.Verbose, "verbose", "v", false, "Verbose mode.")
rootCmd.AddCommand(generateCmd)
rootCmd.AddCommand(serveCmd)
serveCmd.PersistentFlags().BoolVarP(&utils.CliArgs.Watch, "watch", "w", false, "Watch for file changes")
Expand Down
4 changes: 4 additions & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ nav:
- Getting Started:
- Write your CV : getting-started/write-cv.md
- Generate your CV: getting-started/generate-cv.md
- Format:
- HTML: format/html.md
- PDF: format/pdf.md
- Others: format/missing-format.md
- Themes:
- Overview: themes/overview.md
- Use a theme: themes/use-theme.md
Expand Down
229 changes: 108 additions & 121 deletions docs/poetry.lock

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions docs/readthedocs/format/html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# HTML format

## Getting started

!!! info "Default format"

The default format for the generated CV is HTML.

You can generate a HTML version of your CV by adding the flag `--format=html` to the `cvwonder` command.

```bash
cvwonder generate --input=cv.yml --output=generated/ --format=pdf
```

As the HTML format is the default format, you can omit the `--format=html` flag.
3 changes: 3 additions & 0 deletions docs/readthedocs/format/missing-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# A format is missing?

Open an issue on the [GitHub repository](https://github.com/germainlefebvre4/CvWonder/issues/new/choose) to request the addition of the missing format.
15 changes: 15 additions & 0 deletions docs/readthedocs/format/pdf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# PDF format

## Getting started

You can generate a PDF version of your CV by adding the flag `--format=pdf` to the `cvwonder` command.

```bash
cvwonder generate --input=cv.yml --output=generated/ --format=pdf
```

## Behind the scenes

The PDF format is generated using the `rod` Go package. The package is a high-level API for the Chrome DevTools Protocol. Formerly it opens a headless browser, load the HTML file, and save the PDF file.

To generate the PDF, `cvwonder` generates the HTML file then generates the PDF file.
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.23.4

require (
github.com/fsnotify/fsnotify v1.8.0
github.com/go-rod/rod v0.116.2
github.com/jaschaephraim/lrserver v0.0.0-20240306232639-afed386b3640
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
Expand All @@ -19,6 +20,11 @@ require (
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/sys v0.21.0 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.9.0 // indirect
golang.org/x/sys v0.26.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
)
20 changes: 18 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
Expand Down Expand Up @@ -42,9 +44,23 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
github.com/ysmood/gop v0.2.0 h1:+tFrG0TWPxT6p9ZaZs+VY+opCvHU8/3Fk6BaNv6kqKg=
github.com/ysmood/gop v0.2.0/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q=
github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg=
github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY=
github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
2 changes: 1 addition & 1 deletion internal/cvrender/html/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/sirupsen/logrus"
)

func GenerateFormatHTML(cv model.CV, outputDirectory string, inputFilename string, themeName string) error {
func RenderFormatHTML(cv model.CV, outputDirectory string, inputFilename string, themeName string) error {
logrus.Debug("Generating HTML")

// Theme directory
Expand Down
46 changes: 46 additions & 0 deletions internal/cvrender/pdf/pdf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package render_pdf

import (
"fmt"
"os"
"path/filepath"

"github.com/germainlefebvre4/cvwonder/internal/cvserve"
"github.com/germainlefebvre4/cvwonder/internal/model"
"github.com/germainlefebvre4/cvwonder/internal/utils"

"github.com/go-rod/rod"
"github.com/sirupsen/logrus"
)

func RenderFormatPDF(cv model.CV, outputDirectory string, inputFilename string, themeName string) error {
logrus.Debug("Generating PDF")

// Output file
outputDirectory, err := filepath.Abs(outputDirectory)
utils.CheckError(err)
outputFilename := filepath.Base(inputFilename) + ".pdf"
outputFilePath := outputDirectory + "/" + outputFilename
w, err := os.Create(outputFilePath)
utils.CheckError(err)
defer w.Close()

localServerUrl := fmt.Sprintf("http://localhost:%d", utils.CliArgs.Port)

// Run the server to output the HTML
logrus.Info("Starting a temporary server at address ", localServerUrl)
go func() {
cvserve.StartServer(outputDirectory)

}()
// Open the browser and convert the page to PDF
err = rod.Try(func() {
rod.New().MustConnect().MustPage(localServerUrl).MustWaitLoad().MustPDF(outputFilePath)
})
if err != nil {
message := fmt.Sprintf("ERROR: Failed to connect to the server %s", localServerUrl)
logrus.Fatal(message)
}

return nil
}
14 changes: 12 additions & 2 deletions internal/cvrender/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,29 @@ import (
"path"

render_html "github.com/germainlefebvre4/cvwonder/internal/cvrender/html"
render_pdf "github.com/germainlefebvre4/cvwonder/internal/cvrender/pdf"
"github.com/germainlefebvre4/cvwonder/internal/model"
"github.com/germainlefebvre4/cvwonder/internal/utils"

"github.com/sirupsen/logrus"
)

// CVRender renders the CV based on html template located at internal/templates/index.html
func Render(cv model.CV, outputDirectory string, inputFilePath string, themeName string) {
func Render(cv model.CV, outputDirectory string, inputFilePath string, themeName string, exportFormat string) {
logrus.Debug("Rendering CV")

inputFilenameExt := path.Base(inputFilePath)
inputFilename := inputFilenameExt[:len(inputFilenameExt)-len(path.Ext(inputFilenameExt))]

err := render_html.GenerateFormatHTML(cv, outputDirectory, inputFilename, themeName)
// Generate HTML
err := render_html.RenderFormatHTML(cv, outputDirectory, inputFilename, themeName)
utils.CheckError(err)

if exportFormat == "pdf" {
// Generate PDF
err := render_pdf.RenderFormatPDF(cv, outputDirectory, inputFilename, themeName)
utils.CheckError(err)
}

logrus.Info("CV rendered successfully")
}
4 changes: 4 additions & 0 deletions internal/cvserve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ func StartLiveReloader(outputDirectory string, inputFilePath string) {
}

// Start serving html
StartServer(outputDirectory)
}

func StartServer(outputDirectory string) {
logrus.Debug(fmt.Sprintf("Listening on: http://localhost:%d", utils.CliArgs.Port))
http.Handle("/", http.FileServer(http.Dir(outputDirectory)))
listeningPort := fmt.Sprintf(":%d", utils.CliArgs.Port)
Expand Down
1 change: 1 addition & 0 deletions internal/utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type Configuration struct {
InputFile string `mapstructure:"INPUT_FILE"`
OutputDirectory string `mapstructure:"OUTPUT_DIRECTORY"`
ThemeName string `mapstructure:"THEME_NAME"`
Format string `mapstructure:"FORMAT"`
Watch bool `mapstructure:"WATCH"`
Verbose bool `mapstructure:"VERBOSE"`
Port int `mapstructure:"PORT"`
Expand Down
4 changes: 2 additions & 2 deletions internal/watcher/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/sirupsen/logrus"
)

func ObserveFileEvents(outputDirectory string, inputFilePath string, themeName string) {
func ObserveFileEvents(outputDirectory string, inputFilePath string, themeName string, exportFormat string) {
// setup watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
Expand All @@ -32,7 +32,7 @@ func ObserveFileEvents(outputDirectory string, inputFilePath string, themeName s
content, err := cvparser.ParseFile(inputFilePath)
utils.CheckError(err)

cvrender.Render(content, outputDirectory, inputFilePath, themeName)
cvrender.Render(content, outputDirectory, inputFilePath, themeName, exportFormat)
utils.CheckError(err)
}
case err := <-watcher.Errors:
Expand Down
5 changes: 5 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"release-type": "go",
"include-component-in-tag": false
}
5 changes: 1 addition & 4 deletions themes/default/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@
<title>{{ .Person.Name }}'s CV</title>
<meta name="author" content="{{ .Person.Name }}"/>
<meta name="description" content="{{ .Person.Name }}'s CV"/>
<link rel="shortcut icon" href="/favicon.png">

<link href="/css/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="/css/cv1.css" rel="stylesheet">
<link href="/css/flags/flags.css" rel="stylesheet">
<link href="/css/default.css" rel="stylesheet">

<link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" rel="stylesheet" media="all"/>

Expand Down