🚧 Link command
This commit is contained in:
23
src/app.go
Normal file
23
src/app.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"alma/command"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func run(commands []command.Command, args []string) {
|
||||
command, found := lo.Find(commands, func(c command.Command) bool { return c.GetName() == args[0] })
|
||||
|
||||
if !found {
|
||||
println("Command not found")
|
||||
return
|
||||
}
|
||||
|
||||
commandArgs := args[1:]
|
||||
if lo.ContainsBy(commandArgs, func(item string) bool { return (item == "-h" || item == "--help") }) {
|
||||
command.GetHelpText()
|
||||
return
|
||||
}
|
||||
command.Run(commandArgs)
|
||||
}
|
||||
8
src/command/command.go
Normal file
8
src/command/command.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package command
|
||||
|
||||
type Command interface {
|
||||
GetHelpText()
|
||||
GetName() string
|
||||
Run(args []string)
|
||||
}
|
||||
|
||||
196
src/command/link.go
Normal file
196
src/command/link.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"alma/config"
|
||||
"alma/helpers"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type LinkCommand struct {
|
||||
}
|
||||
|
||||
type itemToLink struct {
|
||||
source string
|
||||
target string
|
||||
}
|
||||
|
||||
func (LinkCommand) GetName() string {
|
||||
return "link"
|
||||
}
|
||||
|
||||
func (LinkCommand) GetHelpText() {
|
||||
println(
|
||||
`Usage:
|
||||
alma link [module]
|
||||
alma link [repository] [module]
|
||||
|
||||
Options:
|
||||
--help Show this message
|
||||
-d, --dry-run Show what would be linked without actually linking`)
|
||||
}
|
||||
|
||||
func (LinkCommand) Run(args []string) {
|
||||
repoName, moduleName := helpers.GetRepositoryAndModuleName(args)
|
||||
|
||||
if moduleName == nil {
|
||||
println("Module name is required")
|
||||
return
|
||||
}
|
||||
|
||||
dryRun := lo.ContainsBy(args, func(item string) bool { return (item == "-d" || item == "--dry-run") })
|
||||
|
||||
sourceDirectory, targetDirectory := helpers.GetRepositorySourceAndTargetDirectory(repoName)
|
||||
if sourceDirectory == nil {
|
||||
println("Source directory not exists")
|
||||
return
|
||||
}
|
||||
|
||||
sourceDirectoryFolderInfo, err := os.Stat(*sourceDirectory)
|
||||
if err != nil || !sourceDirectoryFolderInfo.IsDir() {
|
||||
println("Source directory not exists", *sourceDirectory)
|
||||
return
|
||||
}
|
||||
|
||||
moduleNameAsPath := strings.ReplaceAll((*moduleName), "/", string(filepath.Separator))
|
||||
moduleDirectory := filepath.Join(*sourceDirectory, moduleNameAsPath)
|
||||
|
||||
moduleDirectoryFolderInfo, err := os.Stat(moduleDirectory)
|
||||
if err != nil || !moduleDirectoryFolderInfo.IsDir() {
|
||||
println("Module directory not exists", moduleDirectory)
|
||||
return
|
||||
}
|
||||
|
||||
almaConfigFilePath, err := os.Stat(filepath.Join(moduleDirectory, ".alma-config.json"))
|
||||
moduleConfiguration := &config.ModuleConfiguration{}
|
||||
if err == nil && !almaConfigFilePath.IsDir() {
|
||||
moduleConfiguration = config.LoadModuleConfiguration(filepath.Join(moduleDirectory, ".alma-config.json"))
|
||||
targetDirectory1 := helpers.ResolvePath(moduleConfiguration.Target)
|
||||
targetDirectory = &targetDirectory1
|
||||
}
|
||||
itemsToLink := TraverseTree(
|
||||
&moduleDirectory,
|
||||
targetDirectory,
|
||||
&moduleDirectory,
|
||||
targetDirectory,
|
||||
moduleConfiguration,
|
||||
)
|
||||
|
||||
filteredItemsToLink := lo.Filter(itemsToLink, func(item itemToLink, index int) bool {
|
||||
for _, exclude := range moduleConfiguration.Exclude {
|
||||
if strings.HasPrefix(item.source, exclude) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if dryRun {
|
||||
println("Dry run. No links will be created. The following links would be created:")
|
||||
}
|
||||
|
||||
linkItems(filteredItemsToLink, dryRun)
|
||||
|
||||
// Not yet used things
|
||||
|
||||
_ = targetDirectory
|
||||
_ = moduleConfiguration
|
||||
|
||||
}
|
||||
|
||||
func TraverseTree(
|
||||
currentDirectory *string,
|
||||
currentTargetDirectory *string,
|
||||
moduleDirectory *string,
|
||||
targetDirectory *string,
|
||||
moduleConfiguration *config.ModuleConfiguration) []itemToLink {
|
||||
content, err := os.ReadDir(*currentDirectory)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
itemsToLink := make([]itemToLink, 0, len(content))
|
||||
for _, item := range content {
|
||||
if item.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if currentDirectory == moduleDirectory && item.Name() == ".alma-config.json" {
|
||||
continue
|
||||
}
|
||||
|
||||
itemConfigTargetPath := moduleConfiguration.Links[item.Name()]
|
||||
|
||||
var targetPath string
|
||||
if itemConfigTargetPath != "" {
|
||||
targetPath = helpers.ResolvePathWithDefault(moduleConfiguration.Links[item.Name()], *targetDirectory)
|
||||
} else {
|
||||
targetPath = filepath.Join(*currentTargetDirectory, item.Name())
|
||||
}
|
||||
|
||||
itemsToLink = append(itemsToLink, itemToLink{
|
||||
source: filepath.Join(*currentDirectory, item.Name()),
|
||||
target: targetPath,
|
||||
})
|
||||
}
|
||||
|
||||
for _, item := range content {
|
||||
if !item.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
relativePath := getRelativePath(filepath.Join(*currentDirectory, item.Name()), *moduleDirectory)
|
||||
|
||||
itemConfigTargetPath := moduleConfiguration.Links[relativePath]
|
||||
|
||||
if itemConfigTargetPath != "" {
|
||||
itemsToLink = append(itemsToLink, itemToLink{
|
||||
source: filepath.Join(*currentDirectory, item.Name()),
|
||||
target: helpers.ResolvePathWithDefault(itemConfigTargetPath, *targetDirectory),
|
||||
})
|
||||
} else {
|
||||
newCurrentDirectory := filepath.Join(*currentDirectory, item.Name())
|
||||
newTargetDirectory := filepath.Join(*currentTargetDirectory, item.Name())
|
||||
items := TraverseTree(
|
||||
&newCurrentDirectory,
|
||||
&newTargetDirectory,
|
||||
moduleDirectory,
|
||||
targetDirectory,
|
||||
moduleConfiguration,
|
||||
)
|
||||
|
||||
if items != nil {
|
||||
itemsToLink = append(itemsToLink, items...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return itemsToLink
|
||||
}
|
||||
|
||||
func linkItems(itemsToLink []itemToLink, dryRun bool) {
|
||||
for _, item := range itemsToLink {
|
||||
_, err := os.Stat(item.target)
|
||||
if err == nil {
|
||||
println("Target already exists", item.target)
|
||||
continue
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
println("Linking", item.source, item.target)
|
||||
continue
|
||||
}
|
||||
|
||||
err = os.Symlink(item.source, item.target)
|
||||
if err != nil {
|
||||
println("Error while linking", item.source, item.target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getRelativePath(full string, parent string) string {
|
||||
return strings.TrimPrefix(full[len(parent):], string(filepath.Separator))
|
||||
}
|
||||
81
src/config/moduleConfiguration.go
Normal file
81
src/config/moduleConfiguration.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type ModuleConfiguration struct {
|
||||
Target string `json:"target"`
|
||||
Links map[string]string `json:"links"`
|
||||
Exclude []string `json:"exclude"`
|
||||
ExcludeReadme bool `json:"excludeReadme"`
|
||||
Install string `json:"install"`
|
||||
Configure string `json:"configure"`
|
||||
}
|
||||
|
||||
func LoadModuleConfiguration(moduleConfigPath string) *ModuleConfiguration {
|
||||
jsonFile, err := os.Open(moduleConfigPath)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer jsonFile.Close()
|
||||
|
||||
byteValue, _ := io.ReadAll(jsonFile)
|
||||
var moduleConfigurationRoot map[string]*ModuleConfiguration
|
||||
err = json.Unmarshal(byteValue, &moduleConfigurationRoot)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
platformKeys := make([]string, 0, len(moduleConfigurationRoot))
|
||||
|
||||
for key := range moduleConfigurationRoot {
|
||||
if key != "default" {
|
||||
platformKeys = append(platformKeys, key)
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(platformKeys, func(i, j string) int {
|
||||
return cmp.Compare(i, j)
|
||||
})
|
||||
|
||||
var moduleConfiguration *ModuleConfiguration
|
||||
if moduleConfigurationRoot["default"] != nil {
|
||||
moduleConfiguration = moduleConfigurationRoot["default"]
|
||||
}
|
||||
|
||||
for _, platformKey := range platformKeys {
|
||||
moduleConfiguration.Merge(moduleConfigurationRoot[platformKey])
|
||||
}
|
||||
|
||||
return moduleConfiguration
|
||||
}
|
||||
|
||||
func (moduleConfig *ModuleConfiguration) Merge(mergeConfig *ModuleConfiguration) {
|
||||
mergedLinks := make(map[string]string, len(moduleConfig.Links)+len(mergeConfig.Links))
|
||||
for key, value := range moduleConfig.Links {
|
||||
mergedLinks[key] = value
|
||||
}
|
||||
|
||||
for key, value := range mergeConfig.Links {
|
||||
mergedLinks[key] = value
|
||||
}
|
||||
|
||||
moduleConfig.Links = mergedLinks
|
||||
|
||||
if mergeConfig.Target != "" {
|
||||
moduleConfig.Target = mergeConfig.Target
|
||||
}
|
||||
if mergeConfig.Install != "" {
|
||||
moduleConfig.Install = mergeConfig.Install
|
||||
}
|
||||
if mergeConfig.Configure != "" {
|
||||
moduleConfig.Configure = mergeConfig.Configure
|
||||
}
|
||||
}
|
||||
8
src/go.mod
Normal file
8
src/go.mod
Normal file
@@ -0,0 +1,8 @@
|
||||
module alma
|
||||
|
||||
go 1.21.7
|
||||
|
||||
require (
|
||||
github.com/samber/lo v1.39.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||
)
|
||||
4
src/go.sum
Normal file
4
src/go.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
||||
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||
28
src/helpers/path.go
Normal file
28
src/helpers/path.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ResolvePath(path string) string {
|
||||
return ResolvePathWithDefault(path, "")
|
||||
}
|
||||
func ResolvePathWithDefault(path string, currentDirectory string) string {
|
||||
skipCombiningCurrentDirectory := false
|
||||
if strings.Contains(path, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
path = strings.ReplaceAll(path, "~", home)
|
||||
skipCombiningCurrentDirectory = true
|
||||
}
|
||||
}
|
||||
|
||||
if currentDirectory != "" && !skipCombiningCurrentDirectory {
|
||||
path = filepath.Join(currentDirectory, path)
|
||||
}
|
||||
|
||||
path = filepath.FromSlash(path)
|
||||
return path
|
||||
}
|
||||
44
src/helpers/repo.go
Normal file
44
src/helpers/repo.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func GetRepositoryAndModuleName(args []string) (*string, *string) {
|
||||
return getRepositoryAndModuleName(args, false)
|
||||
}
|
||||
|
||||
func getRepositoryAndModuleName(args []string, singleParamIsRepository bool) (*string, *string) {
|
||||
var repositoryName, moduleName *string = nil, nil
|
||||
|
||||
usefulArgs := lo.Filter(args, func(arg string, index int) bool { return !strings.HasPrefix(arg, "-") })
|
||||
if len(usefulArgs) == 1 {
|
||||
if singleParamIsRepository {
|
||||
repositoryName = &usefulArgs[0]
|
||||
} else {
|
||||
moduleName = &usefulArgs[0]
|
||||
}
|
||||
} else if len(usefulArgs) >= 1 {
|
||||
repositoryName = &usefulArgs[0]
|
||||
moduleName = &usefulArgs[1]
|
||||
}
|
||||
|
||||
return repositoryName, moduleName
|
||||
}
|
||||
|
||||
func GetRepositorySourceAndTargetDirectory(repoName *string) (*string, *string) {
|
||||
repoSourceDirectory, _ := os.Getwd()
|
||||
repoTargetDirectory, _ := os.Getwd()
|
||||
repoTargetDirectory = path.Join(repoTargetDirectory, "..")
|
||||
|
||||
return GetRepositorySourceAndTargetDirectoryWithFallback(repoName, &repoSourceDirectory, &repoTargetDirectory)
|
||||
}
|
||||
|
||||
func GetRepositorySourceAndTargetDirectoryWithFallback(repoName *string, sourceFallback *string, targetFallback *string) (*string, *string) {
|
||||
//TODO: Use repository configuration
|
||||
return sourceFallback, targetFallback
|
||||
}
|
||||
14
src/main.go
Normal file
14
src/main.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"alma/command"
|
||||
)
|
||||
|
||||
func main() {
|
||||
commands := []command.Command{
|
||||
command.LinkCommand{},
|
||||
}
|
||||
|
||||
run(commands, os.Args[1:])
|
||||
}
|
||||
Reference in New Issue
Block a user