gtx


Branch: develop

Author
thewhodidthis <thewhodidthis@fastmail.com>
Date
Nov. 17 '22 06:32:04
Commit
f2a316d63151d667e0fa6b21885496831bf2129b
Parent
c00da332d01178c1a073a18beee5a87c7e596000
Changes
diff --git a/main.go b/main.go
index 4f81195..e48a23b 100644
--- a/main.go
+++ b/main.go
@@ -1,50 +1,18 @@
 package main
 
 import (
-	"crypto/sha1"
 	_ "embed"
-	"encoding/hex"
-	"errors"
+	"encoding/json"
 	"flag"
 	"fmt"
-	"html/template"
 	"io"
-	"io/fs"
 	"log"
 	"os"
 	"path/filepath"
-
-	"github.com/go-git/go-billy/v5/memfs"
-	"github.com/go-git/go-git/v5"
-	"github.com/go-git/go-git/v5/plumbing"
-	"github.com/go-git/go-git/v5/plumbing/object"
-	"github.com/go-git/go-git/v5/storage/memory"
+	"reflect"
+	"strings"
 )
 
-// CONFIG_FILE=".ht_git2html"
-const configFile = ".config"
-
-/*
-show_progress=1
-force_rebuild=0
-*/
-const showProgress = true
-const forceRebuild = false
-
-// TODO: add log.Debug
-/*
- progress()
- {
-   if test x"$show_progress" = x1
-   then
-     echo "$@"
-   fi
- }
-*/
-
-//go:embed config.tmpl
-var cTmpl string
-
 //go:embed repo.html.tmpl
 var rTmpl string
 
@@ -55,12 +23,26 @@ var bTmpl string
 var iTmpl string
 
 type options struct {
-	project  string
-	repo     string
-	link     string
-	branches string
-	quiet    bool
-	force    bool
+	name     string
+	Project  string `json:"project"`
+	Repo     string `json:"repo"`
+	URL      string `json:"url"`
+	Branches string `json:"branches"`
+	Quiet    bool   `json:"quiet"`
+	Force    bool   `json:"force"`
+}
+
+// Helps store options into a JSON config file.
+func (o *options) save(out string) {
+	bs, err := json.MarshalIndent(o, "", "  ")
+
+	if err != nil {
+		log.Fatalf("unable to encode config file: %v", err)
+	}
+
+	if err := os.WriteFile(filepath.Join(out, o.name), bs, 0644); err != nil {
+		log.Fatalf("unable to save config file: %v", err)
+	}
 }
 
 func init() {
@@ -70,116 +52,106 @@ func init() {
 		fmt.Fprintln(flag.CommandLine.Output(), "usage:", os.Args[0], "[<options>] <path>")
 		flag.PrintDefaults()
 	}
+
+	// Swap default logger timestamps for a custom prefix.
+	log.SetFlags(log.Lmsgprefix)
+	log.SetPrefix("jimmy: ")
 }
 
-/*
-usage()
-	{
-	  echo "Usage $0 [-prlbq] TARGET"
-	  echo "Generate static HTML pages in TARGET for the specified git repository."
-	  echo
-	  echo "  -p  Project's name"
-	  echo "  -r  Repository to clone from."
-	  echo "  -l  Public repository link, e.g., 'http://host.org/project.git'"
-	  echo "  -b  List of branches to process (default: all)."
-	  echo "  -q  Be quiet."
-	  echo "  -f  Force rebuilding of all pages."
-	  exit $1
-	}
-*/
 func main() {
-	/*
-	   while getopts ":p:r:l:b:qf" opt
-	   do
-	     case $opt in
-	       p)
-	         PROJECT=$OPTARG
-	         ;;
-	       r)
-	         # Directory containing the repository.
-	         REPOSITORY=$OPTARG
-	         ;;
-	       l)
-	         PUBLIC_REPOSITORY=$OPTARG
-	         ;;
-	       b)
-	         BRANCHES=$OPTARG
-	         ;;
-	       q)
-	         show_progress=0
-	         ;;
-	       f)
-	         force_rebuild=1
-	         ;;
-	       \?)
-	         echo "Invalid option: -$OPTARG" >&2
-	         usage
-	         ;;
-	     esac
-	   done
-	   shift $(($OPTIND - 1))
-	*/
-	opts := &options{}
-
-	flag.StringVar(&opts.project, "p", "My project", "Project name")
-	flag.StringVar(&opts.repo, "r", "", "Target repo")
-	flag.StringVar(&opts.link, "l", "http://host.org/project.git", "Repo link")
-	flag.StringVar(&opts.branches, "b", "all", "Target branches")
-	flag.BoolVar(&opts.quiet, "q", false, "Be quiet")
-	flag.BoolVar(&opts.force, "f", false, "Force rebuilding of all pages")
+	opt := &options{
+		name: ".jimmy.json",
+	}
+
+	flag.StringVar(&opt.Project, "p", "Jimbo", "Project title")
+	flag.StringVar(&opt.Repo, "r", "", "Target repo")
+	flag.StringVar(&opt.URL, "u", "https://host.net/project.git", "Repo public URL")
+	// TODO: Allow for passing multiple values.
+	flag.StringVar(&opt.Branches, "b", "all", "Target branches")
+	flag.BoolVar(&opt.Quiet, "q", false, "Be quiet")
+	flag.BoolVar(&opt.Force, "f", false, "Force rebuilding of all pages")
 	flag.Parse()
 
-	// Collect flags provided. Note these need to come before
-	// the target directory argument.
+	if opt.Quiet {
+		log.SetOutput(io.Discard)
+	}
+
+	cwd, err := os.Getwd()
+
+	if err != nil {
+		log.Fatalf("unable to get current working directory: %v", err)
+	}
+
+	// Defaults to the current working directory if no argument present.
+	out := flag.Arg(0)
+
+	// Make sure `out` is an absolute path.
+	if ok := filepath.IsAbs(out); !ok {
+		out = filepath.Join(cwd, out)
+	}
+
+	// Attempt to read saved settings.
+	obs, err := os.ReadFile(filepath.Join(out, opt.name))
+
+	if err != nil {
+		log.Printf("unable to read config file: %v", err)
+	}
+
+	// Create a separate options instance for reading config file values into.
+	// NOTE: Need dereference to safely copy.
+	cnf := *opt
+
+	// If a config file exists and an option has not been set, override default to match.
+	if err := json.Unmarshal(obs, &cnf); err != nil {
+		log.Printf("unable to parse config file: %v", err)
+	}
+
+	// Collect flags provided.
 	flagset := make(map[string]bool)
 
+	// NOTE: These need to come before the output directory argument.
 	flag.Visit(func(f *flag.Flag) {
 		flagset[f.Name] = true
 	})
 
-	// TODO: Log these one by one unless quiet.
-	// log.Printf("+%v", opts)
+	ref := reflect.ValueOf(cnf)
 
-	// The repo flag is required, print usage and quit if none given or
-	// unless a single target directory is provided.
-	if !flagset["r"] || flag.NArg() != 1 {
-		flag.Usage()
-		os.Exit(1)
-	}
+	// Log current settings.
+	flag.VisitAll(func(f *flag.Flag) {
+		if !flagset[f.Name] {
+			// Attempt to override default settings where necessary.
+			v := ref.FieldByNameFunc(func(n string) bool {
+				return strings.HasPrefix(strings.ToLower(n), f.Name)
+			})
 
-	target := flag.Arg(0)
+			// This has the nice side effect of magically overriding `opt` fields.
+			flag.Set(f.Name, v.String())
+		}
 
-	// Make sure `target` is an absolute path.
-	if ok := filepath.IsAbs(target); !ok {
-		cwd, err := os.Getwd()
+		log.Printf("%v/%v: %v\n", f.Name, f.Usage, f.Value)
+	})
 
-		if err != nil {
-			log.Fatalf("jimmy: unable to get current working directory %v", err)
-		}
+	// The repo flag is required at this point.
+	// NOTE: Being able to handle non-local repos would be nice.
+	if ok := filepath.IsAbs(opt.Repo); !ok {
+		flag.Usage()
+		os.Exit(1)
+	}
 
-		target = filepath.Join(cwd, target)
+	// Option considered repo-like if it contains a hidden `.git` dir.
+	if _, err := os.Stat(filepath.Join(opt.Repo, ".git")); os.IsNotExist(err) {
+		flag.Usage()
+		os.Exit(1)
 	}
 
-	// Make sure `target` exists.
-	if err := os.MkdirAll(target, 0750); err != nil {
-		log.Fatalf("jimmy: unable to create target directory: %v", err)
+	// Make sure `out` exists.
+	if err := os.MkdirAll(out, 0750); err != nil {
+		log.Fatalf("unable to create output directory: %v", err)
 	}
 
-	// Read the configuration file.
-	/*
-	   if test -e "$TARGET/$CONFIG_FILE"
-	   then
-	     . "$TARGET/$CONFIG_FILE"
-	   fi
-	*/
-	// TODO: Read config file
-	writeConfigFile(target, opts)
-	createDirectories(target, opts.force)
-
-	// NOTE: I believe this check is too limiting and we should
-	// allow for cloning no local repos as well. Once the repo
-	// has been copied or downloaded, we should then check if it
-	// contains a hidden `.git` folder to verify true repo status?
+	// Save current settings for future use.
+	opt.save(out)
+
 	/*
 	   if test ! -d "$REPOSITORY"
 	   then
@@ -187,10 +159,10 @@ func main() {
 	     exit 1
 	   fi
 	*/
-	setUpRepo(target, opts)
-	setGitConfig()
+	createDirectories(out, opt.Force)
+	setUpRepo(out, opt)
 
-	cleanBranches := cleanUpBranches(opts.branches)
+	cleanBranches := cleanUpBranches(opt.Branches)
 
 	fetchBranches(cleanBranches)
 	writeIndex()
@@ -198,67 +170,6 @@ func main() {
 	writeIndexFooter()
 }
 
-func writeConfigFile(target string, opts *options) {
-	/*
-	   # The output version
-	   CURRENT_TEMPLATE="$(sha1sum "$0")"
-	   if test "x$CURRENT_TEMPLATE" != "x$TEMPLATE"
-	   then
-	     progress "Rebuilding all pages as output template changed."
-	     force_rebuild=1
-	   fi
-	   TEMPLATE="$CURRENT_TEMPLATE"
-	*/
-	configTmpl := template.Must(template.New("default").Parse(cTmpl))
-
-	// TODO: Check file permissions are set to 0666.
-	// TODO: Read file if it exists.
-	outFile, err := os.Create(filepath.Join(target, configFile))
-
-	if err != nil {
-		log.Fatalf("jimmy: unable to create config file: %v", err)
-	}
-
-	h := sha1.New()
-
-	// (spike): why did we do this step?
-	if _, err := io.Copy(h, outFile); err != nil {
-		log.Fatal(err)
-	}
-
-	/*
-	   {
-	     save()
-	     {
-	       # Prefer environment variables and arguments to the configuration file.
-	       echo "$1=\"\${$1:-\"$2\"}\""
-	     }
-	     save "PROJECT" "$PROJECT"
-	     save "REPOSITORY" "$REPOSITORY"
-	     save "PUBLIC_REPOSITORY" "$PUBLIC_REPOSITORY"
-	     save "TARGET" "$TARGET"
-	     save "BRANCHES" "$BRANCHES"
-	     save "TEMPLATE" "$TEMPLATE"
-	   } > "$TARGET/$CONFIG_FILE"
-	*/
-	configTmpl.Execute(outFile, struct {
-		Project          string
-		Repository       string
-		PublicRepository string
-		Target           string
-		Branches         string
-		// SHA1SUM
-		Template string
-	}{
-		Project:          opts.project,
-		Repository:       opts.repo,
-		PublicRepository: opts.link,
-		Target:           target,
-		Branches:         opts.branches,
-		Template:         hex.EncodeToString(h.Sum(nil)),
-	})
-}
-
 func createDirectories(target string, force bool) {
 	//# Ensure that some directories we need exist.
 	/*
@@ -292,196 +203,17 @@ func createDirectories(target string, force bool) {
 		// Clear existing dirs if force true.
 		if force && dir != "branches" {
 			if err := os.RemoveAll(d); err != nil {
-				log.Printf("jimmy: unable to remove directory: %v", err)
+				log.Printf("unable to remove directory: %v", err)
 			}
 		}
 
 		if err := os.MkdirAll(d, os.ModePerm); err != nil {
-			log.Printf("jimmy: unable to create directory: %v", err)
-		}
-	}
-}
-
-func setUpRepo(target string, opts *options) {
-	mfs := memfs.New()
-	// Clones the given repository in memory, creating the remote, the local
-	// branches and fetching the objects, exactly as:
-	r, err := git.Clone(memory.NewStorage(), mfs, &git.CloneOptions{
-		URL: opts.repo,
-	})
-
-	check(err)
-
-	refs, _ := r.References()
-	refs.ForEach(func(ref *plumbing.Reference) error {
-		bc, err := r.Branch("refs/head/experimental")
-
-		if err != nil {
-			return err
-		}
-
-		log.Printf("reference: %v %v", ref.Type(), ref.Name())
-		log.Printf("branch: %v", bc.Name)
-
-		return nil
-	})
-
-	branches, err := r.Branches()
-
-	check(err)
-
-	// ... retrieves the branch pointed by HEAD
-	// ref, err := r.Head()
-	//
-	// check(err)
-	//
-	// cIter, err := r.Log(&git.LogOptions{From: ref.Hash()})
-	//
-	// check(err)
-
-	// err = cIter.ForEach(func(c *object.Commit) error {
-	// 	// log.Print(c)
-	//
-	// 	return nil
-	// })
-
-	branches.ForEach(func(b *plumbing.Reference) error {
-		log.Printf("branch: %v", b)
-
-		return nil
-	})
-
-	check(err)
-
-	var pathError *fs.PathError
-	repoPath := filepath.Join(target, "repository")
-
-	_, err = os.Stat(repoPath)
-
-	if errors.As(err, &pathError) {
-		localRepo, err := git.PlainClone(repoPath, false, &git.CloneOptions{
-			URL:          opts.repo,
-			SingleBranch: false,
-			NoCheckout:   true,
-			// NOTE: This will screw things up if piping output to a file.
-			// Progress: os.Stdout,
-		})
-
-		commitObjects, err := localRepo.CommitObjects()
-
-		if err != nil {
-			log.Printf("%v", err)
-		}
-
-		var commitList []*object.Commit
-
-		commitObjects.ForEach(func(c *object.Commit) error {
-			commitList = append(commitList, c)
-
-			return nil
-		})
-
-		// it := template.Must(template.New("default").Parse(iTmpl))
-		//
-		// cdata := struct {
-		// 	List  []*object.Commit
-		// 	Title string
-		// }{
-		// 	List:  commitList,
-		// 	Title: opts.project,
-		// }
-		//
-		// if err := it.Execute(os.Stdout, cdata); err != nil {
-		// 	log.Fatalf("jimmy: unable to fill index template: %v", err)
-		// }
-
-		localBranches, err := localRepo.Branches()
-
-		if err != nil {
-			log.Printf("%v", err)
-		}
-
-		var branchList []*plumbing.Reference
-
-		localBranches.ForEach(func(b *plumbing.Reference) error {
-			branchList = append(branchList, b)
-
-			return nil
-		})
-
-		rt := template.Must(template.New("default").Parse(rTmpl))
-
-		rconf, err := localRepo.Config()
-
-		check(err)
-
-		for _, b := range rconf.Branches {
-			log.Print(b.Name)
-		}
-
-		log.Printf("config/branches: %v", rconf.Branches)
-		log.Printf("config/remotes: %v", rconf.Remotes)
-
-		bdata := struct {
-			Description string
-			Link        string
-			List        []*plumbing.Reference
-			Title       string
-		}{
-			Description: "",
-			Link:        opts.link,
-			List:        branchList,
-			Title:       opts.project,
-		}
-
-		if err := rt.Execute(os.Stdout, bdata); err != nil {
-			log.Fatalf("jimmy: unable to fill home template: %v", err)
+			log.Printf("unable to create directory: %v", err)
 		}
-
-		// branch, err := localBranches.Next()
-		//
-		// log.Printf("branch: %v %s", branch.Name(), branch.String())
-		//
-		// if err != nil {
-		// 	log.Printf("jimmy: failed to list branches: %v", err)
-		// }
-		//
-		// ref := plumbing.NewHashReference(branch.Name(), branch.Hash())
-		//
-		// if err != nil {
-		// 	log.Printf("jimmy: failed to create ref: %v", err)
-		// }
-
-		// workTree, err := localRepo.Worktree()
-		//
-		// if err != nil {
-		// 	log.Printf("jimmy: failed to open worktree: %v", err)
-		// }
-		//
-		// err = workTree.Checkout(&git.CheckoutOptions{
-		// 	Hash: ref.Hash(),
-		// })
-		//
-		// if err != nil {
-		// 	log.Printf("jimmy: failed to checkout detached HEAD: %v", err)
-		// }
-		//
-		// err = localRepo.Storer.RemoveReference(ref.Name())
-		//
-		// if err != nil {
-		// 	log.Printf("jimmy: failed to delete branch: %v", err)
-		// }
 	}
 }
 
-// TODO: implement!
-// NOTE: This may not be required, there is no merge calls anywhere.
-func setGitConfig() {
-	/*
-	   # git merge fails if there are not set.  Fake them.
-	   git config user.email "git2html@git2html"
-	   git config user.name "git2html"
-	*/
+func setUpRepo(target string, opt *options) {
 }
 
 // TODO: implement!
@@ -930,9 +662,3 @@ func htmlFooter() {
 	   }
 	*/
 }
-
-func check(err error) {
-	if err != nil {
-		log.Fatalf("jimmy: %v", err)
-	}
-}