Skip to content
quarry Artifact [a9892637a6]
Login

Artifact [a9892637a6]

Artifact a9892637a647299ffed73f2b78a5ba2e53819d77ef258790a1c2dfe7f08ea250:


// quarry is a multi-repository forge for zit. See docs/rfcs/.
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/events"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ingest"
	"craftdesign.group/quarry/internal/ops"
	"craftdesign.group/quarry/internal/web"
	"craftdesign.group/quarry/internal/zitcli"
)

// version is stamped at build time; every page footer and the API report it
// (0001 Principle 1).
var version = "0.0.0-dev"

func main() {
	if len(os.Args) > 1 {
		switch os.Args[1] {
		case "version":
			fmt.Println("quarry", version)
			return
		case "spec":
			if err := printSpec(os.Stdout); err != nil {
				fmt.Fprintln(os.Stderr, "quarry:", err)
				os.Exit(1)
			}
			return
		case "serve":
			if err := serveCmd(os.Args[2:]); err != nil {
				fmt.Fprintln(os.Stderr, "quarry:", err)
				os.Exit(1)
			}
			return
		}
	}
	fmt.Fprintln(os.Stderr, "usage: quarry version|spec|serve")
	os.Exit(2)
}

// serveCmd runs the HTTP surface: the op-generated API plus the HTML
// pages, both consumers of one registry. With --repo, a watcher ingests
// the store into a derived index (0003 phase 1: one store, one node).
func serveCmd(args []string) error {
	fs := flag.NewFlagSet("serve", flag.ContinueOnError)
	addr := fs.String("addr", "127.0.0.1:8080", "listen address")
	repo := fs.String("repo", "", "zit repository to serve (checkout dir or .zit file)")
	data := fs.String("data", "", "derived-index directory (default: <repo>/.quarry)")
	interval := fs.Duration("sync-interval", 2*time.Second, "store poll interval")
	if err := fs.Parse(args); err != nil {
		return err
	}

	deps := api.Deps{Version: version}
	if *repo != "" {
		dir := *data
		if dir == "" {
			dir = filepath.Join(*repo, ".quarry")
		}
		if err := os.MkdirAll(dir, 0o755); err != nil {
			return err
		}
		ev, err := events.Start(filepath.Join(dir, "nats"))
		if err != nil {
			return err
		}
		defer ev.Close()
		ix, err := index.Open(filepath.Join(dir, "index.db"))
		if err != nil {
			return err
		}
		defer ix.Close()
		deps.Index = ix

		pipe := ingest.NewPipeline(zitcli.New(*repo), ev, ix, "store")
		if n, err := pipe.Sync(); err != nil {
			return fmt.Errorf("initial ingest: %w", err)
		} else {
			fmt.Printf("ingested %d artifacts from %s\n", n, *repo)
		}
		go func() {
			for range time.Tick(*interval) {
				if _, err := pipe.Sync(); err != nil {
					log.Printf("ingest: %v", err)
				}
			}
		}()
	}

	mux, err := buildHandler(deps)
	if err != nil {
		return err
	}
	fmt.Println("quarry", version, "listening on", *addr)
	return http.ListenAndServe(*addr, mux)
}

// buildHandler wires the two registry consumers onto one mux.
func buildHandler(deps api.Deps) (http.Handler, error) {
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, deps); err != nil {
		return nil, err
	}
	apiRouter, err := api.Router(r, deps.Version)
	if err != nil {
		return nil, err
	}
	mux := http.NewServeMux()
	mux.Handle("/api/", apiRouter)
	mux.Handle("/", web.Handler(r))
	return mux, nil
}

// printSpec emits the OpenAPI document generated from the full registry —
// the same registry serve will run, so the spec cannot drift (0001 §9).
func printSpec(w *os.File) error {
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, api.Deps{Version: version}); err != nil {
		return err
	}
	spec, err := ops.Spec(r, "quarry", version)
	if err != nil {
		return err
	}
	enc := json.NewEncoder(w)
	enc.SetIndent("", "  ")
	return enc.Encode(spec)
}