~kornellapacz/gmnigit

96a1d848e54ddd8ef3aa4a67ab5208528ec52803 — Korneliusz Łapacz 5 months ago 86ac540
refactor; split code into multiple files
4 files changed, 216 insertions(+), 166 deletions(-)

M README.md
A commits.go
A files.go
M main.go
M README.md => README.md +1 -1
@@ 11,7 11,7 @@ Unfortunately because of `replace` directive in `go.mod` you cannot simply get b
```sh
git clone https://git.sr.ht/~kornellapacz/gmnigit
cd gmnigit
go build -o gmnigit main.go
go build -o gmnigit
```

place `gmnigit` binary somewhere in your `$PATH`.

A commits.go => commits.go +80 -0
@@ 0,0 1,80 @@
package main

import (
	"os"
	"path/filepath"
	"text/template"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing/object"
)

var commitTemplate = template.Must(
	template.New("commit").Parse(`## {{.Hash}}
Author:  {{.Author.String}}
Date:    {{.Author.When.Format "Mon Jan 02 15:04:05 2006 -0700"}}
=> {{.Hash}}.patch Patch
Message: {{ .Message }}
`))

func iterateOverCommits(r *git.Repository, callback func(*object.Commit, *object.Patch) error) {
	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 {
		commitTree, err := c.Tree()
		check(err)

		// get direct parrent
		parent, err := c.Parent(0)

		if err == object.ErrParentNotFound {
			// probably first commit, generate patch for that
			emptyTree := r.EmptyTreeObject()

			patch, err := emptyTree.Patch(commitTree)
			check(err)

			return callback(c, patch)
		}

		check(err)

		parentTree, err := parent.Tree()
		check(err)

		patch, err := parentTree.Patch(commitTree)
		check(err)

		return callback(c, patch)
	})

	check(err)
}

func createCommitsFiles(distPath string, repository *git.Repository) {
	commitsPath := filepath.Join(distPath, commitsSubPath)
	check(os.Mkdir(commitsPath, os.ModePerm))

	commitsIndex, err := os.Create(filepath.Join(commitsPath, "index.gmi"))
	check(err)
	commitsIndex.WriteString("# Commits \n\n")

	iterateOverCommits(repository, func(commit *object.Commit, patch *object.Patch) error {
		commitTemplate.Execute(commitsIndex, commit)

		patchFile, err := os.Create(filepath.Join(commitsPath, commit.Hash.String()+".patch"))
		check(err)

		patchFile.WriteString(patch.String())
		check(patchFile.Close())

		return nil
	})

	check(commitsIndex.Close())
	wg.Done()
}

A files.go => files.go +121 -0
@@ 0,0 1,121 @@
package main

import (
	"log"
	"os"
	"path/filepath"
	"strings"

	gemini "git.tdem.in/tdemin/gmnhg"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing/object"
)

var directoryIndexingFiles = make(map[string]*os.File)

func registerDirectory(toRegister, toRegisterOriginal string) {
	if toRegister == "." {
		return
	}

	parentPath := filepath.Dir(toRegister)
	parentIndex, ok := directoryIndexingFiles[parentPath]

	if !ok {
		// if directory below doesn't have index.gmi (empty directory) search even below
		registerDirectory(filepath.Dir(toRegister), toRegisterOriginal)
		return
	}

	parentIndex.WriteString("=> " + toRegisterOriginal + "/\n")
}

func createDirectoriesIndexes(treePath string, tree *object.Tree) {
	tree.Files().ForEach(func(f *object.File) error {
		dir := filepath.Dir(f.Name)

		val, ok := directoryIndexingFiles[dir]
		// if directory doesn't have index.gmi create one
		if !ok {
			// TODO do something if index.gmi is already in directory (as repository file)
			index, err := os.Create(filepath.Join(treePath, dir, "index.gmi"))
			check(err)

			index.WriteString("# directory " + filepath.Join("/", dir) + "\n\n")

			// register directory in folder below
			registerDirectory(dir, dir)

			val = index
			directoryIndexingFiles[dir] = index
		}

		val.WriteString("=> " + filepath.Base(f.Name) + "\n")
		return nil
	})

	for _, file := range directoryIndexingFiles {
		check(file.Close())
	}
}

func createBrowsableTree(distPath, repositoryPath string) {
	treePath := filepath.Join(distPath, treeSubPath)

	repository, err := git.PlainClone(treePath, false, &git.CloneOptions{
		URL: repositoryPath,
	})

	check(err)
	ref, err := repository.Head()
	check(err)
	commit, err := repository.CommitObject(ref.Hash())
	check(err)
	tree, err := commit.Tree()
	check(err)

	createDirectoriesIndexes(treePath, tree)

	check(os.RemoveAll(filepath.Join(treePath, ".git")))
}

func createIndexFile(distPath, repositoryPath string) {
	// check if in bare repository is `url` file with git url for cloning
	url, err := os.ReadFile(filepath.Join(repositoryPath, "url"))

	if err != nil && !os.IsNotExist(err) {
		log.Fatal(err)
	}

	indexFile, err := os.Create(filepath.Join(distPath, "index.gmi"))
	check(err)

	if url != nil {
		indexFile.WriteString("```\ngit clone " + strings.TrimSpace(string(url)) + "\n```\n\n")
	}

	indexFile.WriteString("=> " + commitsSubPath + "/\n")
	indexFile.WriteString("=> " + treeSubPath + "/\n\n")

	// convert README.md to gemini and write it to index.gmi in repository root
	text, err := os.ReadFile(filepath.Join(distPath, treeSubPath, "README.md"))

	if err != nil {
		if os.IsNotExist(err) {
			log.Println("README.md not found in your repository")

			check(indexFile.Close())
			return
		}

		log.Fatal(err)
	}

	check(err)
	geminiContent, _, err := gemini.RenderMarkdown(text, gemini.WithMetadata)
	check(err)

	indexFile.Write(geminiContent)

	check(indexFile.Close())
}

M main.go => main.go +14 -165
@@ 3,13 3,16 @@ package main
import (
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"sync"

	gemini "git.tdem.in/tdemin/gmnhg"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing/object"
)

var wg sync.WaitGroup

const (
	treeSubPath    = "tree"
	commitsSubPath = "commits"
)

func check(err error) {


@@ 18,52 21,6 @@ func check(err error) {
	}
}

func iterateOverCommits(r *git.Repository, callback func(*object.Commit, *object.Patch) error) {
	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 {
		commitTree, err := c.Tree()
		check(err)

		// get direct parrent
		parent, err := c.Parent(0)

		if err == object.ErrParentNotFound {
			// probably first commit, generate patch for that
			emptyTree := r.EmptyTreeObject()

			patch, err := emptyTree.Patch(commitTree)
			check(err)

			return callback(c, patch)
		}

		check(err)

		parentTree, err := parent.Tree()
		check(err)

		patch, err := parentTree.Patch(commitTree)
		check(err)

		return callback(c, patch)
	})

	check(err)
}

var commitTemplate = template.Must(
	template.New("commit").Parse(`## {{.Hash}}
Author:  {{.Author.String}}
Date:    {{.Author.When.Format "Mon Jan 02 15:04:05 2006 -0700"}}
=> {{.Hash}}.patch Patch
Message: {{ .Message }}
`))

func main() {
	repositoryPath := os.Args[1]
	distPath := os.Args[2]


@@ 76,125 33,17 @@ func main() {
		log.Fatalf("cannot create dist directory (%s)", err)
	}

	r, err := git.PlainOpen(repositoryPath)
	repository, err := git.PlainOpen(repositoryPath)

	if err != nil {
		log.Fatalf("cannot open repository with given path: %s, %s", repositoryPath, err)
	}

	commitsPath := filepath.Join(distPath, "commits")
	check(os.Mkdir(commitsPath, os.ModePerm))

	commitsIndex, err := os.Create(filepath.Join(commitsPath, "index.gmi"))
	check(err)
	commitsIndex.WriteString("# Commits \n\n")

	iterateOverCommits(r, func(commit *object.Commit, patch *object.Patch) error {
		commitTemplate.Execute(commitsIndex, commit)

		patchFile, err := os.Create(filepath.Join(commitsPath, commit.Hash.String()+".patch"))
		check(err)

		patchFile.WriteString(patch.String())
		check(patchFile.Close())

		return nil
	})

	check(commitsIndex.Close())

	filesPath := filepath.Join(distPath, "files")
	r, err = git.PlainClone(filesPath, false, &git.CloneOptions{
		URL: repositoryPath,
	})
	check(err)
	ref, err := r.Head()
	check(err)
	commit, err := r.CommitObject(ref.Hash())
	check(err)
	tree, err := commit.Tree()
	check(err)

	indexFiles := make(map[string]*os.File)

	var tellParent func(string, string)
	tellParent = func(myPath string, originalDir string) {
		if myPath == "." {
			return
		}

		parentPath := filepath.Dir(myPath)
		parentIndex, ok := indexFiles[parentPath]

		if !ok {
			tellParent(filepath.Dir(myPath), originalDir)
			return
		}

		parentIndex.WriteString("=> " + originalDir + "/\n")
	}

	tree.Files().ForEach(func(f *object.File) error {
		dir := filepath.Dir(f.Name)

		val, ok := indexFiles[dir]
		if !ok {
			// TODO do something if index.gmi is already in directory (as repository file)
			index, err := os.Create(filepath.Join(filesPath, dir, "index.gmi"))
			check(err)

			index.WriteString("# directory " + filepath.Join("/", dir) + "\n\n")

			tellParent(dir, dir)

			val = index
			indexFiles[dir] = index
		}

		val.WriteString("=> " + filepath.Base(f.Name) + "\n")
		return nil
	})

	for _, file := range indexFiles {
		check(file.Close())
	}

	check(os.RemoveAll(filepath.Join(filesPath, ".git")))

	url, err := os.ReadFile(filepath.Join(repositoryPath, "url"))

	if err != nil && !os.IsNotExist(err) {
		log.Fatal(err)
	}

	indexFile, err := os.Create(filepath.Join(distPath, "index.gmi"))
	check(err)

	if url != nil {
		indexFile.WriteString("```\ngit clone " + strings.TrimSpace(string(url)) + "\n```\n\n")
	}

	indexFile.WriteString("=> commits/\n=> files/\n\n")

	// convert README.md to gemini and write it to index.gmi in repository root
	text, err := os.ReadFile(filepath.Join(filesPath, "README.md"))

	if err != nil {
		if os.IsNotExist(err) {
			log.Println("README.md not found in your repository")

			check(indexFile.Close())
			return
		}

		log.Fatal(err)
	}

	check(err)
	geminiContent, _, err := gemini.RenderMarkdown(text, gemini.WithMetadata)
	check(err)
	wg.Add(1)

	indexFile.Write(geminiContent)
	go createCommitsFiles(distPath, repository)
	createBrowsableTree(distPath, repositoryPath)
	createIndexFile(distPath, repositoryPath)

	check(indexFile.Close())
	wg.Wait()
}