Skip to content

Commit

Permalink
feat: add symlink support
Browse files Browse the repository at this point in the history
  • Loading branch information
boldandbrad authored and JanDeDobbeleer committed Dec 25, 2024
1 parent 903810e commit 27442e3
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/config/unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Aliae struct {
Envs shell.Envs `yaml:"env"`
Paths shell.Paths `yaml:"path"`
Scripts shell.Scripts `yaml:"script"`
Links shell.Links `yaml:"link"`
}

type FuncMap []StringFunc
Expand Down
1 change: 1 addition & 0 deletions src/core/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func Init(configPath, sh string, printOutput bool) string {
aliae.Paths.Render()
aliae.Aliae.Render()
aliae.Scripts.Render()
aliae.Links.Render()

script := shell.DotFile.String()

Expand Down
6 changes: 6 additions & 0 deletions src/shell/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ func (e *Env) cmd() *Env {
return e
}

func (l *Link) cmd() *Link {
template := `os.execute("mklink {{ if eq .Type "hard" }}/H{{ else }}/D{{ end }} {{ .Name }} {{ .Target }}")`
l.template = template
return l
}

func (p *Path) cmd() *Path {
p.template = `os.setenv("PATH", "{{ escapeString .Value }};" .. os.getenv("PATH"))`
return p
Expand Down
73 changes: 73 additions & 0 deletions src/shell/link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package shell

import (
"github.com/jandedobbeleer/aliae/src/context"
)

type Links []*Link

const (
Hard Type = "hard"
Soft Type = "soft"
)

type Link struct {
Name Template `yaml:"name"`
Target Template `yaml:"target"`
If If `yaml:"if"`
Type Type `yaml:"type"`

template string
}

func (l *Link) string() string {
switch context.Current.Shell {
case ZSH, BASH, FISH, XONSH:
return l.zsh().render()
case PWSH, POWERSHELL:
return l.pwsh().render()
case NU:
return l.nu().render()
case TCSH:
return l.tcsh().render()
case CMD:
return l.cmd().render()
default:
return ""
}
}

func (l *Link) render() string {
script, err := parse(l.template, l)
if err != nil {
return err.Error()
}

return script
}

func (l Links) Render() {
if len(l) == 0 {
return
}

first := true
for _, link := range l {
script := link.string()
if len(script) == 0 || link.If.Ignore() {
continue
}

if first && DotFile.Len() > 0 {
DotFile.WriteString("\n\n")
}

if !first {
DotFile.WriteString("\n")
}

DotFile.WriteString(script)

first = false
}
}
139 changes: 139 additions & 0 deletions src/shell/link_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package shell

import (
"testing"

"github.com/jandedobbeleer/aliae/src/context"
"github.com/stretchr/testify/assert"
)

func TestLinkCommand(t *testing.T) {
link := &Link{Name: "foo", Target: "bar"}
cases := []struct {
Case string
Shell string
Expected string
OS string
}{
{
Case: "PWSH",
Shell: PWSH,
Expected: "New-Item -Path \"foo\" -ItemType SymbolicLink -Value \"bar\" -Force",
},
{
Case: "CMD",
Shell: CMD,
Expected: `os.execute("mklink /D foo bar")`,
},
{
Case: "FISH",
Shell: FISH,
Expected: "ln -sf bar foo",
},
{
Case: "NU",
Shell: NU,
Expected: "ln -sf bar foo",
},
{
Case: "NU Windows",
Shell: NU,
OS: context.WINDOWS,
Expected: "mklink /D foo bar",
},
{
Case: "TCSH",
Shell: TCSH,
Expected: "ln -sf bar foo;",
},
{
Case: "XONSH",
Shell: XONSH,
Expected: "ln -sf bar foo",
},
{
Case: "ZSH",
Shell: ZSH,
Expected: `ln -sf bar foo`,
},
{
Case: "BASH",
Shell: BASH,
Expected: `ln -sf bar foo`,
},
}

for _, tc := range cases {
link.template = ""
context.Current = &context.Runtime{Shell: tc.Shell, OS: tc.OS}
assert.Equal(t, tc.Expected, link.string(), tc.Case)
}
}

func TestLinkRender(t *testing.T) {
cases := []struct {
Case string
Links Links
Expected string
}{
{
Case: "Single link",
Links: Links{
&Link{Name: "FOO", Target: "bar"},
},
Expected: `ln -sf bar FOO`,
},
{
Case: "Double link",
Links: Links{
&Link{Name: "FOO", Target: "bar"},
&Link{Name: "BAR", Target: "foo"},
},
Expected: `ln -sf bar FOO
ln -sf foo BAR`,
},
{
Case: "Filtered out",
Links: Links{
&Link{Name: "FOO", Target: "bar", If: `eq .Shell "fish"`},
},
},
}

for _, tc := range cases {
DotFile.Reset()
context.Current = &context.Runtime{Shell: BASH}
tc.Links.Render()
assert.Equal(t, tc.Expected, DotFile.String(), tc.Case)
}
}

func TestLinkWithTemplate(t *testing.T) {
cases := []struct {
Case string
Target Template
Expected string
}{
{
Case: "No template",
Target: "~/dotfiles/zshrc",
Expected: `ln -sf ~/dotfiles/zshrc /tmp/l`,
},
{
Case: "Home in template",
Target: "{{ .Home }}/.aliae.yaml",
Expected: `ln -sf /Users/jan/.aliae.yaml /tmp/l`,
},
{
Case: "Advanced template",
Target: "{{ .Home }}/go/bin/aliae{{ if eq .OS \"windows\" }}.exe{{ end }}",
Expected: `ln -sf /Users/jan/go/bin/aliae.exe /tmp/l`,
},
}

for _, tc := range cases {
link := &Link{Name: "/tmp/l", Target: tc.Target}
context.Current = &context.Runtime{Shell: BASH, Home: "/Users/jan", OS: context.WINDOWS}
assert.Equal(t, tc.Expected, link.string(), tc.Case)
}
}
10 changes: 10 additions & 0 deletions src/shell/nu.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ func (e *Env) nu() *Env {
return e
}

func (l *Link) nu() *Link {
template := `ln -sf {{ .Target }} {{ .Name }}`
if context.Current.OS == context.WINDOWS {
template = `mklink {{ if eq .Type "hard" }}/H{{ else }}/D{{ end }} {{ .Name }} {{ .Target }}`
}

l.template = template
return l
}

func (p *Path) nu() *Path {
template := `$env.%s = ($env.%s | prepend {{ formatString .Value }})`
pathName := "PATH"
Expand Down
6 changes: 6 additions & 0 deletions src/shell/pwsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ func (e *Env) pwsh() *Env {
return e
}

func (l *Link) pwsh() *Link {
template := `New-Item -Path {{ formatString .Name }} -ItemType SymbolicLink -Value {{ formatString .Target }} -Force`
l.template = template
return l
}

func (p *Path) pwsh() *Path {
template := fmt.Sprintf(`$env:PATH = '{{ .Value }}%s' + $env:PATH`, context.PathDelimiter())
p.template = template
Expand Down
6 changes: 6 additions & 0 deletions src/shell/tcsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ func (e *Env) tcsh() *Env {
return e
}

func (l *Link) tcsh() *Link {
template := `ln -sf {{ .Target }} {{ .Name }};`
l.template = template
return l
}

func (p *Path) tcsh() *Path {
p.template = `set path = ( {{ .Value }} $path );`
return p
Expand Down
6 changes: 6 additions & 0 deletions src/shell/zsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ func (e *Env) zsh() *Env {
return e
}

func (l *Link) zsh() *Link {
template := `ln -sf {{ .Target }} {{ .Name }}`
l.template = template
return l
}

func (p *Path) zsh() *Path {
template := fmt.Sprintf(`export PATH="{{ .Value }}%s$PATH"`, context.PathDelimiter())
p.template = template
Expand Down
2 changes: 2 additions & 0 deletions website/docs/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ It allows you to use a single, template enabled `YAML` [configuration][config] t
- [Environment variable][env]
- [PATH entry][path]
- [Script][script]
- [Symbolic Link][link]

## I want in!

Expand All @@ -29,4 +30,5 @@ for your platform and have a look at the alias [configuration][config] page to g
[env]: setup/env.mdx
[path]: setup/path.mdx
[script]: setup/script.mdx
[link]: setup/link.mdx

7 changes: 7 additions & 0 deletions website/docs/setup/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ script:
- value: |
eval "$(oh-my-posh init {{ .Shell }})"
if: match .Shell "bash" "zsh"
link:
- name: ~/.aliae.yaml
target: ~/dotfiles/aliae.yaml
- name: ~/.zshrc
target: $DOTFILES/config/zsh/zshrc
```
You can find out more about the configuration options below.
Expand All @@ -74,8 +79,10 @@ You can find out more about the configuration options below.
- [Environment variable][env]
- [PATH entry][path]
- [Script][script]
- [Symbolic link][link]
[alias]: setup/alias.mdx
[env]: setup/env.mdx
[path]: setup/path.mdx
[script]: setup/script.mdx
[link]: setup/link.mdx
32 changes: 32 additions & 0 deletions website/docs/setup/link.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
id: link
title: Symbolic Link
sidebar_label: 🔗 Symbolic Link
---

Create symlinks to files and directories. Useful for dotfiles.

### Syntax

```yaml
link:
- name: ~/.aliae.yaml
target: ~/dotfiles/aliae.yaml
- name: ~/.zshrc
target: $DOTFILES/config/zsh/zshrc
- name: ~/Brewfile
value: /some/location/Brewfile
if: eq .OS "darwin"
```
### Link
| Name | Type | Description |
| -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | the link name, supports [templating][templates] |
| `target` | `string` | the name of the file or directory to link to, supports [templating][templates] |
| `if` | `string` | golang [template][go-text-template] conditional statement, see [if][if] |

[go-text-template]: https://golang.org/pkg/text/template/
[if]: if.mdx
[templates]: templates.mdx

0 comments on commit 27442e3

Please sign in to comment.