156 lines
4.8 KiB
Go
Executable File
156 lines
4.8 KiB
Go
Executable File
// Package quill is the "Scribe" of the Coven — the interactive wizard that
|
|
// interviews a developer and emits a new spell (DETAILS, DEPENDS, BUILD,
|
|
// CONFIGURE) into the Grimoire.
|
|
//
|
|
// Replacing the original Bash Quill, this Go version uses text/template
|
|
// for type-safe file generation and an HTTP-friendly SpellData struct so
|
|
// the WebUI can drive the same interview through a modal form.
|
|
package quill
|
|
|
|
import (
|
|
"crypto/sha512"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
// SpellData is everything the interview collects.
|
|
type SpellData struct {
|
|
Name string
|
|
Version string
|
|
SourceURL string
|
|
Hash string
|
|
Website string
|
|
License string
|
|
Description string
|
|
LongDesc string
|
|
Dependencies []string
|
|
Date string
|
|
}
|
|
|
|
// GenerateSpell writes a new spell directory under `targetDir` containing
|
|
// DETAILS, DEPENDS, BUILD and CONFIGURE. Atomic "buffer-then-commit": no
|
|
// file is written until the template parses successfully.
|
|
func GenerateSpell(data SpellData, targetDir string) error {
|
|
if data.Date == "" {
|
|
data.Date = time.Now().Format("20060102")
|
|
}
|
|
if data.Hash == "" {
|
|
// "Smart Quill" — auto-hash the upstream source.
|
|
h, err := autoHash(data.SourceURL)
|
|
if err == nil {
|
|
data.Hash = h
|
|
}
|
|
}
|
|
|
|
tmpl, err := template.New("details").Parse(detailsTmpl)
|
|
if err != nil {
|
|
return fmt.Errorf("quill: template parse: %w", err)
|
|
}
|
|
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
|
return err
|
|
}
|
|
f, err := os.Create(filepath.Join(targetDir, "DETAILS"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
if err := tmpl.Execute(f, data); err != nil {
|
|
return fmt.Errorf("quill: template exec: %w", err)
|
|
}
|
|
|
|
// DEPENDS
|
|
depTmpl, err := template.New("depends").Parse(dependsTmpl)
|
|
if err != nil {
|
|
return fmt.Errorf("quill: parse depends template: %w", err)
|
|
}
|
|
df, err := os.Create(filepath.Join(targetDir, "DEPENDS"))
|
|
if err != nil {
|
|
return fmt.Errorf("quill: create DEPENDS: %w", err)
|
|
}
|
|
defer df.Close()
|
|
if err := depTmpl.Execute(df, data); err != nil {
|
|
return fmt.Errorf("quill: exec depends template: %w", err)
|
|
}
|
|
|
|
// BUILD
|
|
bf, err := os.Create(filepath.Join(targetDir, "BUILD"))
|
|
if err != nil {
|
|
return fmt.Errorf("quill: create BUILD: %w", err)
|
|
}
|
|
defer bf.Close()
|
|
if _, err := bf.WriteString(buildTmpl); err != nil {
|
|
return fmt.Errorf("quill: write BUILD: %w", err)
|
|
}
|
|
|
|
// CONFIGURE
|
|
cf, err := os.Create(filepath.Join(targetDir, "CONFIGURE"))
|
|
if err != nil {
|
|
return fmt.Errorf("quill: create CONFIGURE: %w", err)
|
|
}
|
|
defer cf.Close()
|
|
if _, err := cf.WriteString(configureTmpl); err != nil {
|
|
return fmt.Errorf("quill: write CONFIGURE: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// autoHash downloads the source URL and computes its SHA-512 — Quill's
|
|
// "Smart" mode that saves the developer from running sha512sum by hand.
|
|
func autoHash(url string) (string, error) {
|
|
if url == "" {
|
|
return "", fmt.Errorf("quill: no source URL")
|
|
}
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", fmt.Errorf("quill: HTTP %d fetching %s", resp.StatusCode, url)
|
|
}
|
|
h := sha512.New()
|
|
if _, err := io.Copy(h, resp.Body); err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(h.Sum(nil)), nil
|
|
}
|
|
|
|
const detailsTmpl = `SPELL={{.Name}}
|
|
VERSION={{.Version}}
|
|
SOURCE=${SPELL}-${VERSION}.tar.gz
|
|
SOURCE_URL[0]={{.SourceURL}}
|
|
SOURCE_HASH=sha512:{{.Hash}}
|
|
SOURCE_DIRECTORY="${BUILD_DIRECTORY}/${SPELL}-${VERSION}"
|
|
WEB_SITE={{.Website}}
|
|
ENTERED={{.Date}}
|
|
LICENSE[0]={{.License}}
|
|
SHORT="{{.Description}}"
|
|
cat << EOF
|
|
{{.LongDesc}}
|
|
EOF
|
|
`
|
|
|
|
const dependsTmpl = `{{range .Dependencies}}depends {{.}} ""
|
|
{{end}}`
|
|
|
|
const buildTmpl = `#!/bin/bash
|
|
# Standard BUILD script — edit as needed.
|
|
cd "$SOURCE_DIRECTORY" &&
|
|
./configure --prefix=/usr "$@" &&
|
|
make &&
|
|
make install
|
|
`
|
|
|
|
const configureTmpl = `#!/bin/bash
|
|
# CONFIGURE — interactive queries go here. The Go ICE engine reads these.
|
|
# config_query WGET_SSL "Enable SSL support?" y
|
|
`
|