mirror of
https://github.com/yarlson/lnk.git
synced 2025-09-02 18:12:33 +02:00
feat(cmd): add 'list' command to display managed files
Implements a new 'list' command that shows all files and directories managed by lnk, improving visibility and user experience. fixes #4
This commit is contained in:
@@ -52,6 +52,9 @@ lnk init -r git@github.com:user/dotfiles.git
|
|||||||
# Add files/directories
|
# Add files/directories
|
||||||
lnk add ~/.vimrc ~/.config/nvim ~/.gitconfig
|
lnk add ~/.vimrc ~/.config/nvim ~/.gitconfig
|
||||||
|
|
||||||
|
# List managed files
|
||||||
|
lnk list
|
||||||
|
|
||||||
# Check status
|
# Check status
|
||||||
lnk status
|
lnk status
|
||||||
|
|
||||||
@@ -99,6 +102,7 @@ lnk pull # auto-creates symlinks
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
vim ~/.vimrc # edit normally
|
vim ~/.vimrc # edit normally
|
||||||
|
lnk list # see what's managed
|
||||||
lnk status # check what changed
|
lnk status # check what changed
|
||||||
lnk push "new plugins" # commit & push
|
lnk push "new plugins" # commit & push
|
||||||
```
|
```
|
||||||
@@ -108,6 +112,7 @@ lnk push "new plugins" # commit & push
|
|||||||
- `lnk init [-r remote]` - Create repo
|
- `lnk init [-r remote]` - Create repo
|
||||||
- `lnk add <files>` - Move files to repo, create symlinks
|
- `lnk add <files>` - Move files to repo, create symlinks
|
||||||
- `lnk rm <files>` - Move files back, remove symlinks
|
- `lnk rm <files>` - Move files back, remove symlinks
|
||||||
|
- `lnk list` - List files managed by lnk
|
||||||
- `lnk status` - Git status + sync info
|
- `lnk status` - Git status + sync info
|
||||||
- `lnk push [msg]` - Stage all, commit, push
|
- `lnk push [msg]` - Stage all, commit, push
|
||||||
- `lnk pull` - Pull + restore missing symlinks
|
- `lnk pull` - Pull + restore missing symlinks
|
||||||
|
43
cmd/list.go
Normal file
43
cmd/list.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/yarlson/lnk/internal/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newListCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "📋 List files managed by lnk",
|
||||||
|
Long: "Display all files and directories currently managed by lnk.",
|
||||||
|
SilenceUsage: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
lnk := core.NewLnk()
|
||||||
|
managedItems, err := lnk.List()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list managed items: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(managedItems) == 0 {
|
||||||
|
printf(cmd, "📋 \033[1mNo files currently managed by lnk\033[0m\n")
|
||||||
|
printf(cmd, " 💡 Use \033[1mlnk add <file>\033[0m to start managing files\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(cmd, "📋 \033[1mFiles managed by lnk\033[0m (\033[36m%d item", len(managedItems))
|
||||||
|
if len(managedItems) > 1 {
|
||||||
|
printf(cmd, "s")
|
||||||
|
}
|
||||||
|
printf(cmd, "\033[0m):\n\n")
|
||||||
|
|
||||||
|
for _, item := range managedItems {
|
||||||
|
printf(cmd, " 🔗 \033[36m%s\033[0m\n", item)
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(cmd, "\n💡 Use \033[1mlnk status\033[0m to check sync status\n")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@@ -38,6 +38,7 @@ That's it.
|
|||||||
rootCmd.AddCommand(newInitCmd())
|
rootCmd.AddCommand(newInitCmd())
|
||||||
rootCmd.AddCommand(newAddCmd())
|
rootCmd.AddCommand(newAddCmd())
|
||||||
rootCmd.AddCommand(newRemoveCmd())
|
rootCmd.AddCommand(newRemoveCmd())
|
||||||
|
rootCmd.AddCommand(newListCmd())
|
||||||
rootCmd.AddCommand(newStatusCmd())
|
rootCmd.AddCommand(newStatusCmd())
|
||||||
rootCmd.AddCommand(newPushCmd())
|
rootCmd.AddCommand(newPushCmd())
|
||||||
rootCmd.AddCommand(newPullCmd())
|
rootCmd.AddCommand(newPullCmd())
|
||||||
|
@@ -163,6 +163,60 @@ func (suite *CLITestSuite) TestStatusCommand() {
|
|||||||
suite.Contains(err.Error(), "no remote configured")
|
suite.Contains(err.Error(), "no remote configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *CLITestSuite) TestListCommand() {
|
||||||
|
// Test list without init - should fail
|
||||||
|
err := suite.runCommand("list")
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Contains(err.Error(), "Lnk repository not initialized")
|
||||||
|
|
||||||
|
// Initialize first
|
||||||
|
err = suite.runCommand("init")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.stdout.Reset()
|
||||||
|
|
||||||
|
// Test list with no managed files
|
||||||
|
err = suite.runCommand("list")
|
||||||
|
suite.NoError(err)
|
||||||
|
output := suite.stdout.String()
|
||||||
|
suite.Contains(output, "No files currently managed by lnk")
|
||||||
|
suite.Contains(output, "lnk add <file>")
|
||||||
|
suite.stdout.Reset()
|
||||||
|
|
||||||
|
// Add a file
|
||||||
|
testFile := filepath.Join(suite.tempDir, ".bashrc")
|
||||||
|
err = os.WriteFile(testFile, []byte("export PATH=/usr/local/bin:$PATH"), 0644)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
err = suite.runCommand("add", testFile)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.stdout.Reset()
|
||||||
|
|
||||||
|
// Test list with one managed file
|
||||||
|
err = suite.runCommand("list")
|
||||||
|
suite.NoError(err)
|
||||||
|
output = suite.stdout.String()
|
||||||
|
suite.Contains(output, "Files managed by lnk")
|
||||||
|
suite.Contains(output, "1 item")
|
||||||
|
suite.Contains(output, ".bashrc")
|
||||||
|
suite.stdout.Reset()
|
||||||
|
|
||||||
|
// Add another file
|
||||||
|
testFile2 := filepath.Join(suite.tempDir, ".vimrc")
|
||||||
|
err = os.WriteFile(testFile2, []byte("set number"), 0644)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
err = suite.runCommand("add", testFile2)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.stdout.Reset()
|
||||||
|
|
||||||
|
// Test list with multiple managed files
|
||||||
|
err = suite.runCommand("list")
|
||||||
|
suite.NoError(err)
|
||||||
|
output = suite.stdout.String()
|
||||||
|
suite.Contains(output, "Files managed by lnk")
|
||||||
|
suite.Contains(output, "2 items")
|
||||||
|
suite.Contains(output, ".bashrc")
|
||||||
|
suite.Contains(output, ".vimrc")
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *CLITestSuite) TestErrorHandling() {
|
func (suite *CLITestSuite) TestErrorHandling() {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -207,6 +261,12 @@ func (suite *CLITestSuite) TestErrorHandling() {
|
|||||||
wantErr: false,
|
wantErr: false,
|
||||||
outContains: "Moves a file to the lnk repository",
|
outContains: "Moves a file to the lnk repository",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "list help",
|
||||||
|
args: []string{"list", "--help"},
|
||||||
|
wantErr: false,
|
||||||
|
outContains: "Display all files and directories",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@@ -428,6 +428,22 @@ func (l *Lnk) Pull() ([]string, error) {
|
|||||||
return restored, nil
|
return restored, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List returns the list of files and directories currently managed by lnk
|
||||||
|
func (l *Lnk) List() ([]string, error) {
|
||||||
|
// Check if repository is initialized
|
||||||
|
if !l.git.IsGitRepository() {
|
||||||
|
return nil, fmt.Errorf("❌ Lnk repository not initialized\n 💡 Run \033[1mlnk init\033[0m first")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get managed items from .lnk file
|
||||||
|
managedItems, err := l.getManagedItems()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get managed items: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return managedItems, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RestoreSymlinks finds all managed items from .lnk file and ensures they have proper symlinks
|
// RestoreSymlinks finds all managed items from .lnk file and ensures they have proper symlinks
|
||||||
func (l *Lnk) RestoreSymlinks() ([]string, error) {
|
func (l *Lnk) RestoreSymlinks() ([]string, error) {
|
||||||
var restored []string
|
var restored []string
|
||||||
|
@@ -516,6 +516,76 @@ func (suite *CoreTestSuite) TestStatusDetectsDirtyRepo() {
|
|||||||
suite.True(status.Dirty, "Repository should be dirty after editing managed file")
|
suite.True(status.Dirty, "Repository should be dirty after editing managed file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test list functionality
|
||||||
|
func (suite *CoreTestSuite) TestListManagedItems() {
|
||||||
|
// Test list without init - should fail
|
||||||
|
_, err := suite.lnk.List()
|
||||||
|
suite.Error(err)
|
||||||
|
suite.Contains(err.Error(), "Lnk repository not initialized")
|
||||||
|
|
||||||
|
// Initialize repository
|
||||||
|
err = suite.lnk.Init()
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// Test list with no managed files
|
||||||
|
items, err := suite.lnk.List()
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Empty(items)
|
||||||
|
|
||||||
|
// Add a file
|
||||||
|
testFile := filepath.Join(suite.tempDir, ".bashrc")
|
||||||
|
content := "export PATH=$PATH:/usr/local/bin"
|
||||||
|
err = os.WriteFile(testFile, []byte(content), 0644)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
err = suite.lnk.Add(testFile)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// Test list with one managed file
|
||||||
|
items, err = suite.lnk.List()
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Len(items, 1)
|
||||||
|
suite.Contains(items[0], ".bashrc")
|
||||||
|
|
||||||
|
// Add a directory
|
||||||
|
testDir := filepath.Join(suite.tempDir, ".config")
|
||||||
|
err = os.MkdirAll(testDir, 0755)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
configFile := filepath.Join(testDir, "app.conf")
|
||||||
|
err = os.WriteFile(configFile, []byte("setting=value"), 0644)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
err = suite.lnk.Add(testDir)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// Test list with multiple managed items
|
||||||
|
items, err = suite.lnk.List()
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Len(items, 2)
|
||||||
|
|
||||||
|
// Check that both items are present
|
||||||
|
found := make(map[string]bool)
|
||||||
|
for _, item := range items {
|
||||||
|
if strings.Contains(item, ".bashrc") {
|
||||||
|
found[".bashrc"] = true
|
||||||
|
}
|
||||||
|
if strings.Contains(item, ".config") {
|
||||||
|
found[".config"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
suite.True(found[".bashrc"], "Should contain .bashrc")
|
||||||
|
suite.True(found[".config"], "Should contain .config")
|
||||||
|
|
||||||
|
// Remove one item and verify list is updated
|
||||||
|
err = suite.lnk.Remove(testFile)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
items, err = suite.lnk.List()
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Len(items, 1)
|
||||||
|
suite.Contains(items[0], ".config")
|
||||||
|
}
|
||||||
|
|
||||||
func TestCoreSuite(t *testing.T) {
|
func TestCoreSuite(t *testing.T) {
|
||||||
suite.Run(t, new(CoreTestSuite))
|
suite.Run(t, new(CoreTestSuite))
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user