Adds solo repository support
This commit is contained in:
parent
912e575463
commit
d710717489
|
@ -8,7 +8,7 @@ An easy way to install, remove, and update [slope](https://git.rawtext.club/slop
|
|||
slp deps [[-g]] [file] # install the dependencies of 'file'
|
||||
slp docs [[-g]] [module] # open a module's readme in $PAGER
|
||||
slp fetch # fetches the updated registry
|
||||
slp gen # creates new module dir/skeleton
|
||||
slp gen [[-s|--solo]] # creates new module dir/skeleton
|
||||
slp help # print usage information
|
||||
slp install [[-g]] [module...] # installs module(s)
|
||||
slp installed [[-g]] # lists all installed packages
|
||||
|
@ -23,6 +23,10 @@ slp update-all [[-g]] [module...] # updates all installed modules
|
|||
|
||||
The above options are more or less self-explanatory with the exception of `local`. `local` will install a module that you have on your system, but not on the slope module path. This is useful if, for example, a person has made their module available but it is not in the slp registry. In which case you can clone their repo and run `slp local ~/path/to/their-module`. Once installed in this manner the slp `remove`, `installed`, and `docs` commands will be able to operate on the module based on its folder name. `update`, however, requires the registry to know what git tag is the current/newest tag.
|
||||
|
||||
## VCS Choices
|
||||
|
||||
`slp gen` supports git repositories (the default) or [solo](https://slope.colorfield.space/solo/repos/solo) repositories. To create the new module folder as a solo repository instead of a git repository simply pass the `-s` or `--solo`. For example: `slp gen -s` or `slp gen --solo`. If you opt to use solo repositories be sure that the repository link in your `module.json` file is a link to the `[reponame].tar.gz` file that soloweb creates when pushing a repository. Linking to the index page of the soloweb view of the repository will not do the job. If you are not sure where the correct file is, but have pushed your repository to a remote using `solo push [credentials]`, you can visit the web page and right click on the `download` link in the navigation menu and choose `copy link`. That will add the correct link to your clipboard, and it can then be added to your `module.json` file as needed.
|
||||
|
||||
## Global installs
|
||||
|
||||
Operations that accept a -g flag will attempt to install a module systemwide (this may require root access). A --global flag may be passed in lieu of a -g flag if desired for clarity.
|
||||
|
|
10
main.go
10
main.go
|
@ -17,7 +17,7 @@ const (
|
|||
helptext string = `
|
||||
slp deps [[-g]] [file] # install the dependencies of 'file'
|
||||
slp fetch # fetches the updated registry
|
||||
slp gen # creates new module dir/skeleton
|
||||
slp gen [[-s|--solo]] # creates new module dir/skeleton
|
||||
slp help # print usage information
|
||||
slp install [[-g]] [module...] # installs module(s)
|
||||
slp installed [[-g]] # lists all installed packages
|
||||
|
@ -32,6 +32,8 @@ slp version # print the current slp version
|
|||
|
||||
Operations that accept a -g flag will attempt to install a module systemwide (this may require root access). A --global flag may be passed in lieu of a -g flag if desired for clarity.
|
||||
|
||||
slp gen will automatically init a git repository in the created folder. The -s/--solo flag will change that behavior to create a solo repository instead.
|
||||
|
||||
The install location for global modules is: /usr/local/lib/slope/modules
|
||||
|
||||
Globals modules must be dealt with separately from local modules and cannot be combined in a single command.`
|
||||
|
@ -108,7 +110,11 @@ MainSwitch:
|
|||
os.Exit(1)
|
||||
}
|
||||
case "gen":
|
||||
err := operators.Generate()
|
||||
solo := false
|
||||
if len(arg) > 2 && arg[2] == "-s" || arg[2] == "--solo" {
|
||||
solo = true
|
||||
}
|
||||
err := operators.Generate(solo)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
os.Exit(1)
|
||||
|
|
|
@ -52,7 +52,7 @@ func (g genOpts) String() string {
|
|||
|
||||
// Generate generates a package
|
||||
// by cloning the package template repo
|
||||
func Generate() error {
|
||||
func Generate(solo bool) error {
|
||||
opts, err := promptOptions()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -70,9 +70,27 @@ func Generate() error {
|
|||
|
||||
dir := filepath.Join(pwd, opts.Title)
|
||||
|
||||
_, err = git.PlainInit(dir, false)
|
||||
if err != nil {
|
||||
return err
|
||||
if solo {
|
||||
err = os.MkdirAll(filepath.Join(dir, ".solo", "tree", "master"), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(filepath.Join(dir, ".solo", "tags"), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.Create(filepath.Join(dir, ".solo", "solo.conf"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
host, _ := os.Hostname()
|
||||
f.WriteString(fmt.Sprintf(`(("heads" ()) ("branch" "master") ("tags" ()) ("description" "%s") ("author" "%s@%s") ("version" (0 3 0)) ("remote" #f) ("floating?" #f))`, opts.Description, opts.Author, host))
|
||||
} else {
|
||||
_, err = git.PlainInit(dir, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
jf, err := os.Create(filepath.Join(dir, "module.json"))
|
||||
|
@ -92,7 +110,7 @@ func Generate() error {
|
|||
sf.WriteString(fmt.Sprintf(";;; Author: %s\n", opts.Author))
|
||||
sf.WriteString(fmt.Sprintf(";;; Version: %s\n", opts.Version))
|
||||
sf.WriteString(";;;\n\n; display \"Hello, World!\"\n")
|
||||
sf.WriteString(fmt.Sprintf("(define %s-hello-world (lambda ()\n", opts.Title))
|
||||
sf.WriteString("(define hello-world (lambda ()\n")
|
||||
sf.WriteString(fmt.Sprintf(" (display \"'Hello, world!' from %s\\n\")))\n\n; vim: ts=2 sw=2 expandtab ft=slope\n", opts.Title))
|
||||
|
||||
rm, err := os.Create(filepath.Join(dir, "README.md"))
|
||||
|
|
|
@ -51,33 +51,39 @@ func Install(pkg string, global bool) error {
|
|||
for k, _ := range depList {
|
||||
fmt.Printf(" \033[2m├\033[0m staging %q\n", k)
|
||||
p := packages[k]
|
||||
repository, err := git.PlainClone(filepath.Join(stagingDir, p.Title), false, &git.CloneOptions{
|
||||
URL: p.Repository,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
failedInstall = append(failedInstall, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if p.Tag != "" {
|
||||
wt, err := repository.Worktree()
|
||||
if err != nil {
|
||||
if strings.HasSuffix(p.Repository, "tar.gz") {
|
||||
if err := InstallFromSolo(p.Title, p.Repository, p.Tag, stagingDir); err != nil {
|
||||
failedInstall = append(failedInstall, err.Error())
|
||||
continue
|
||||
}
|
||||
err = wt.Checkout(&git.CheckoutOptions{
|
||||
Branch: plumbing.NewTagReferenceName(p.Tag),
|
||||
} else {
|
||||
repository, err := git.PlainClone(filepath.Join(stagingDir, p.Title), false, &git.CloneOptions{
|
||||
URL: p.Repository,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
failedInstall = append(failedInstall, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
_, err = os.Stat(filepath.Join(stagingDir, pkg, "main.slo"))
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
failedInstall = append(failedInstall, fmt.Sprintf(" \033[2m└\033[0m \033[91mError:\033[0m package %q does not contain a valid 'main.slo' file", pkg))
|
||||
if p.Tag != "" {
|
||||
wt, err := repository.Worktree()
|
||||
if err != nil {
|
||||
failedInstall = append(failedInstall, err.Error())
|
||||
continue
|
||||
}
|
||||
err = wt.Checkout(&git.CheckoutOptions{
|
||||
Branch: plumbing.NewTagReferenceName(p.Tag),
|
||||
})
|
||||
if err != nil {
|
||||
failedInstall = append(failedInstall, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
_, err = os.Stat(filepath.Join(stagingDir, pkg, "main.slo"))
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
failedInstall = append(failedInstall, fmt.Sprintf(" \033[2m└\033[0m \033[91mError:\033[0m package %q does not contain a valid 'main.slo' file", pkg))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(failedInstall) > 0 {
|
||||
|
@ -88,13 +94,19 @@ func Install(pkg string, global bool) error {
|
|||
return fmt.Errorf(strings.Join(failedInstall, "\n"))
|
||||
}
|
||||
fmt.Println(" \033[2m├\033[0m moving staged items to modules folder")
|
||||
cmd := exec.Command("sh", "-c", fmt.Sprintf("mv %s %s", filepath.Join(stagingDir, "*"), modDir))
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
if global {
|
||||
return fmt.Errorf("Unable to write modules to final location. Do you have access?")
|
||||
globPattern := filepath.Join(stagingDir, "*")
|
||||
glob, _ := filepath.Glob(globPattern)
|
||||
if len(glob) > 0 {
|
||||
cmd := exec.Command("sh", "-c", fmt.Sprintf("mv %s %s", globPattern, modDir))
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
if global {
|
||||
return fmt.Errorf("Unable to write modules to final location. Do you have access?")
|
||||
}
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
package operators
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func inStringSlice(slice []string, value string) bool {
|
||||
for i := range slice {
|
||||
if slice[i] == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func InstallFromSolo(repoName, repo, tag, targetDir string) error {
|
||||
// Get the repo
|
||||
tarGzName, err := getSoloTarGz(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tarGzName)
|
||||
|
||||
// Un-gzip the repo
|
||||
tarName, err := unGzip(tarGzName, filepath.Dir(tarGzName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tarName)
|
||||
|
||||
repoFolder, err := unTar(tarName, filepath.Dir(tarName))
|
||||
if err != nil {
|
||||
// return err
|
||||
return fmt.Errorf("3")
|
||||
}
|
||||
defer os.RemoveAll(repoFolder)
|
||||
tagFile, err := os.ReadFile(filepath.Join(repoFolder, ".solo", "tags", tag))
|
||||
if err != nil {
|
||||
return fmt.Errorf("The tag %q does not exist", tag)
|
||||
}
|
||||
re := regexp.MustCompile(`\("([^"\n]+)"\s+"([^"\n"]+)"[^\)]*\)`)
|
||||
matches := re.FindAllStringSubmatch(string(tagFile), 1)
|
||||
if len(matches) == 0 || len(matches[0]) < 3 {
|
||||
return fmt.Errorf("The tag %q is corrupted and cannot be used", tag)
|
||||
}
|
||||
branch := string(matches[0][1])
|
||||
snap := string(matches[0][2])
|
||||
if branch == "" || snap == "" {
|
||||
return fmt.Errorf("The tag %q contains invalid references and cannot be used", tag)
|
||||
}
|
||||
folderRoot := filepath.Join(repoFolder, ".solo", "tree", branch, snap, "data")
|
||||
files, _ := filepath.Glob(filepath.Join(folderRoot, "*"))
|
||||
if len(files) == 0 {
|
||||
return fmt.Errorf("Invalid repository")
|
||||
}
|
||||
if !inStringSlice(files, filepath.Join(folderRoot, "main.slo")) || !inStringSlice(files, filepath.Join(folderRoot, "module.json")) {
|
||||
return fmt.Errorf("Invalid repository structure")
|
||||
}
|
||||
return os.Rename(folderRoot, filepath.Join(targetDir, repoName))
|
||||
}
|
||||
|
||||
func getSoloTarGz(u string) (string, error) {
|
||||
if !strings.HasSuffix(u, ".tar.gz") {
|
||||
return "", fmt.Errorf("Invalid repository URL")
|
||||
}
|
||||
resp, err := http.Get(u)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
f, err := os.CreateTemp("", path.Base(u))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
f.Write(body)
|
||||
return f.Name(), nil
|
||||
}
|
||||
|
||||
func unTar(tarPath, destPath string) (string, error) {
|
||||
reader, err := os.Open(tarPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer reader.Close()
|
||||
tarReader := tar.NewReader(reader)
|
||||
root := ""
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
path := filepath.Join(destPath, header.Name)
|
||||
info := header.FileInfo()
|
||||
if info.IsDir() {
|
||||
if err = os.MkdirAll(path, info.Mode()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if root == "" {
|
||||
root = path
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = io.Copy(file, tarReader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func unGzip(source, target string) (string, error) {
|
||||
reader, err := os.Open(source)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
archive, err := gzip.NewReader(reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer archive.Close()
|
||||
splitInd := strings.Index(source, ".gz")
|
||||
target = filepath.Join(target, filepath.Base(source)[:splitInd])
|
||||
writer, err := os.Create(target)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer writer.Close()
|
||||
|
||||
_, err = io.Copy(writer, archive)
|
||||
return target, err
|
||||
}
|
Loading…
Reference in New Issue