Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions model/dotfile_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func GetAllAppsMap() map[DotfileAppName]DotfileApp {
type DotfileApp interface {
Name() string
GetConfigPaths() []string
GetIncludeDirectives() []IncludeDirective
CollectDotfiles(ctx context.Context) ([]DotfileItem, error)
IsEqual(ctx context.Context, files map[string]string) (map[string]bool, error)
Backup(ctx context.Context, paths []string, isDryRun bool) error
Expand All @@ -78,6 +79,12 @@ type BaseApp struct {
name string
}

// GetIncludeDirectives returns an empty slice by default.
// Apps that support include directives should override this method.
func (b *BaseApp) GetIncludeDirectives() []IncludeDirective {
return nil
}
Comment on lines +84 to +86
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The comment for GetIncludeDirectives states it returns an "empty slice by default", but the implementation returns nil. While nil slices often behave like empty slices, it's generally a good practice in Go to return an actual empty slice ([]IncludeDirective{}) to ensure consistency and avoid potential nil checks by consumers who might expect a non-nil slice.

Suggested change
func (b *BaseApp) GetIncludeDirectives() []IncludeDirective {
return nil
}
func (b *BaseApp) GetIncludeDirectives() []IncludeDirective {
return []IncludeDirective{}
}


func (b *BaseApp) expandPath(path string) (string, error) {
if strings.HasPrefix(path, "~") {
homeDir, err := os.UserHomeDir()
Expand Down Expand Up @@ -317,8 +324,10 @@ func (b *BaseApp) Save(ctx context.Context, files map[string]string, isDryRun bo

// Read existing content if file exists
var existingContent string
fileExists := false
if existingBytes, err := os.ReadFile(expandedPath); err == nil {
existingContent = string(existingBytes)
fileExists = true
} else if !os.IsNotExist(err) {
slog.Warn("Failed to read existing file", slog.String("path", expandedPath), slog.Any("err", err))
continue
Expand All @@ -328,6 +337,25 @@ func (b *BaseApp) Save(ctx context.Context, files map[string]string, isDryRun bo
continue
}

// For new files, write directly without diff-merge
if !fileExists {
dir := filepath.Dir(expandedPath)
if err := os.MkdirAll(dir, 0755); err != nil {
slog.Warn("Failed to create directory", slog.String("dir", dir), slog.Any("err", err))
continue
}
if isDryRun {
fmt.Printf("\n📄 %s (new file):\n%s\n", expandedPath, newContent)
continue
}
if err := os.WriteFile(expandedPath, []byte(newContent), 0644); err != nil {
slog.Warn("Failed to save file", slog.String("path", expandedPath), slog.Any("err", err))
} else {
slog.Info("Saved new content", slog.String("path", expandedPath))
}
continue
}

localObj, err := dms.ConvertToEncodedObject(existingContent)
if err != nil {
return err
Expand Down
37 changes: 35 additions & 2 deletions model/dotfile_bash.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,40 @@ func (b *BashApp) GetConfigPaths() []string {
}
}

func (b *BashApp) GetIncludeDirectives() []IncludeDirective {
return []IncludeDirective{
{
OriginalPath: "~/.bashrc",
ShelltimePath: "~/.bashrc.shelltime",
IncludeLine: "[[ -f ~/.bashrc.shelltime ]] && source ~/.bashrc.shelltime",
CheckString: ".bashrc.shelltime",
},
{
OriginalPath: "~/.bash_profile",
ShelltimePath: "~/.bash_profile.shelltime",
IncludeLine: "[[ -f ~/.bash_profile.shelltime ]] && source ~/.bash_profile.shelltime",
CheckString: ".bash_profile.shelltime",
},
{
OriginalPath: "~/.bash_aliases",
ShelltimePath: "~/.bash_aliases.shelltime",
IncludeLine: "[[ -f ~/.bash_aliases.shelltime ]] && source ~/.bash_aliases.shelltime",
CheckString: ".bash_aliases.shelltime",
},
{
OriginalPath: "~/.bash_logout",
ShelltimePath: "~/.bash_logout.shelltime",
IncludeLine: "[[ -f ~/.bash_logout.shelltime ]] && source ~/.bash_logout.shelltime",
CheckString: ".bash_logout.shelltime",
},
}
}

func (b *BashApp) CollectDotfiles(ctx context.Context) ([]DotfileItem, error) {
skipIgnored := true
return b.CollectFromPaths(ctx, b.Name(), b.GetConfigPaths(), &skipIgnored)
}
return b.CollectWithIncludeSupport(ctx, b.Name(), b.GetConfigPaths(), &skipIgnored, b.GetIncludeDirectives())
}

func (b *BashApp) Save(ctx context.Context, files map[string]string, isDryRun bool) error {
return b.SaveWithIncludeSupport(ctx, files, isDryRun, b.GetIncludeDirectives())
}
19 changes: 17 additions & 2 deletions model/dotfile_fish.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,22 @@ func (f *FishApp) GetConfigPaths() []string {
}
}

func (f *FishApp) GetIncludeDirectives() []IncludeDirective {
return []IncludeDirective{
{
OriginalPath: "~/.config/fish/config.fish",
ShelltimePath: "~/.config/fish/config.fish.shelltime",
IncludeLine: "test -f ~/.config/fish/config.fish.shelltime; and source ~/.config/fish/config.fish.shelltime",
CheckString: "config.fish.shelltime",
},
}
}

func (f *FishApp) CollectDotfiles(ctx context.Context) ([]DotfileItem, error) {
skipIgnored := true
return f.CollectFromPaths(ctx, f.Name(), f.GetConfigPaths(), &skipIgnored)
}
return f.CollectWithIncludeSupport(ctx, f.Name(), f.GetConfigPaths(), &skipIgnored, f.GetIncludeDirectives())
}

func (f *FishApp) Save(ctx context.Context, files map[string]string, isDryRun bool) error {
return f.SaveWithIncludeSupport(ctx, files, isDryRun, f.GetIncludeDirectives())
}
25 changes: 23 additions & 2 deletions model/dotfile_git.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,28 @@ func (g *GitApp) GetConfigPaths() []string {
}
}

func (g *GitApp) GetIncludeDirectives() []IncludeDirective {
return []IncludeDirective{
{
OriginalPath: "~/.gitconfig",
ShelltimePath: "~/.gitconfig.shelltime",
IncludeLine: "[include]\n path = ~/.gitconfig.shelltime",
CheckString: ".gitconfig.shelltime",
},
{
OriginalPath: "~/.config/git/config",
ShelltimePath: "~/.config/git/config.shelltime",
IncludeLine: "[include]\n path = ~/.config/git/config.shelltime",
CheckString: "git/config.shelltime",
},
}
}

func (g *GitApp) CollectDotfiles(ctx context.Context) ([]DotfileItem, error) {
skipIgnored := true
return g.CollectFromPaths(ctx, g.Name(), g.GetConfigPaths(), &skipIgnored)
}
return g.CollectWithIncludeSupport(ctx, g.Name(), g.GetConfigPaths(), &skipIgnored, g.GetIncludeDirectives())
}

func (g *GitApp) Save(ctx context.Context, files map[string]string, isDryRun bool) error {
return g.SaveWithIncludeSupport(ctx, files, isDryRun, g.GetIncludeDirectives())
}
Loading
Loading