ppppcg/ppppcg.go

179 lines
3.1 KiB
Go

// ppppcg is a PrePostPrint inspired political compass generator with SVG output.
package main
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
svg "github.com/ajstarks/svgo"
"gopkg.in/yaml.v2"
)
// help is the CLI help text.
const help = `ppppcg [options]
A PrePostPrint inspired political compass generator with SVG output.
Options:
-h output help
`
// config is the user configuration file.
type config struct {
Width int
Height int
Axis struct {
Top string
Right string
Bottom string
Left string
}
Points []struct {
Name string
X int
Y int
}
}
var (
helpFlag bool
configFlag string
// rectMargin is the margin for the compass rectangle.
rectMargin = 50
// fillNoneStrokeBlack is a default calm line style.
fillNoneStrokeBlack = "fill:none;stroke:black"
// axisFontStyle is the default axis font style
axisFontStyle = "font-size: 13pt;font-weight:bold"
// pointFontStyle is the default point font style
pointFontStyle = "font-size: 11pt;font-style:italic"
// lineOverflow is the amount of overflow permitted for each compass line.
lineOverflow = 10
)
// handleCliFlags parses CLI flags.
func handleCliFlags() {
flag.BoolVar(&helpFlag, "h", false, "output help")
flag.StringVar(&configFlag, "c", "ppppcg.yml", "config file")
flag.Parse()
}
// loadYAMLConfig loads a mppppcg YAML user config.
func loadYAMLConfig() (config, error) {
var cfg config
configPath, err := filepath.Abs(configFlag)
if err != nil {
return config{}, fmt.Errorf("loadYAMLConfig: unable to convert %s to an absolute path: %w", configFlag, err)
}
conf, err := os.ReadFile(configPath)
if err != nil {
return config{}, fmt.Errorf("loadYAMLConfig: unable to read %s: %w", configFlag, err)
}
err = yaml.UnmarshalStrict(conf, &cfg)
if err != nil {
return config{}, fmt.Errorf("loadYAMLConfig: unable to unmarshal %s: %w", string(conf), err)
}
return cfg, nil
}
// main is the CLI entrypoint.
func main() {
handleCliFlags()
if helpFlag {
fmt.Print(help)
os.Exit(0)
}
cfg, err := loadYAMLConfig()
if err != nil {
log.Fatal(err)
}
canvas := svg.New(os.Stdout)
canvas.Start(cfg.Width, cfg.Height)
canvas.CenterRect(
cfg.Width/2,
cfg.Height/2,
cfg.Width-rectMargin,
cfg.Width-rectMargin,
fillNoneStrokeBlack,
)
canvas.Line(
cfg.Width/2,
lineOverflow,
cfg.Width/2,
cfg.Width-lineOverflow,
fillNoneStrokeBlack,
)
canvas.Line(
lineOverflow,
cfg.Width/2,
cfg.Width-lineOverflow,
cfg.Width/2,
fillNoneStrokeBlack,
)
canvas.TranslateRotate(
lineOverflow,
cfg.Width/2,
270,
)
canvas.Text(
-15,
35,
cfg.Axis.Left,
axisFontStyle,
)
canvas.Gend()
canvas.Text(
cfg.Width/2+10,
lineOverflow+10,
cfg.Axis.Top,
axisFontStyle,
)
canvas.TranslateRotate(
cfg.Width-lineOverflow,
(cfg.Width/2)-lineOverflow+5,
90,
)
canvas.Text(
35,
-(cfg.Width/2)-3,
cfg.Axis.Right,
axisFontStyle,
)
canvas.Gend()
canvas.Text(
(cfg.Width/2)+lineOverflow,
cfg.Width-lineOverflow,
cfg.Axis.Bottom,
axisFontStyle,
)
for _, p := range cfg.Points {
canvas.Text(p.X, p.Y, p.Name, pointFontStyle)
}
canvas.End()
}