diff --git a/cmd/micro/clean.go b/cmd/micro/clean.go new file mode 100644 index 00000000..c0a8766e --- /dev/null +++ b/cmd/micro/clean.go @@ -0,0 +1,140 @@ +package main + +import ( + "bufio" + "encoding/gob" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/zyedidia/micro/internal/buffer" + "github.com/zyedidia/micro/internal/config" +) + +func shouldContinue() bool { + reader := bufio.NewReader(os.Stdin) + fmt.Print("Continue [Y/n]: ") + text, err := reader.ReadString('\n') + if err != nil { + fmt.Println(err) + return false + } + + if len(text) <= 1 { + // default continue + return true + } + + return strings.ToLower(text)[0] == 'y' +} + +// CleanConfig performs cleanup in the user's configuration directory +func CleanConfig() { + fmt.Println("Cleaning your configuration directory at", config.ConfigDir) + fmt.Printf("Please consider backing up %s before continuing\n", config.ConfigDir) + + if !shouldContinue() { + fmt.Println("Stopping early") + return + } + + // detect unused options + var unusedOptions []string + defaultSettings := config.DefaultAllSettings() + for k := range config.GlobalSettings { + if _, ok := defaultSettings[k]; !ok { + valid := false + for _, p := range config.Plugins { + if strings.HasPrefix(k, p.Name+".") || k == p.Name { + valid = true + } + } + if !valid { + unusedOptions = append(unusedOptions, k) + } + } + } + + if len(unusedOptions) > 0 { + fmt.Println("The following options are unused:") + + sort.Strings(unusedOptions) + + for _, s := range unusedOptions { + fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s]) + } + + fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json")) + + if shouldContinue() { + for _, s := range unusedOptions { + delete(config.GlobalSettings, s) + } + + err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json")) + if err != nil { + fmt.Println("Error writing settings.json file: " + err.Error()) + } + + fmt.Println("Removed unused options") + fmt.Print("\n\n") + } + } + + // detect incorrectly formatted buffer/ files + files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers")) + if err == nil { + var badFiles []string + var buffer buffer.SerializedBuffer + for _, f := range files { + fname := filepath.Join(config.ConfigDir, "buffers", f.Name()) + file, err := os.Open(fname) + defer file.Close() + + decoder := gob.NewDecoder(file) + err = decoder.Decode(&buffer) + + if err != nil { + badFiles = append(badFiles, fname) + } + } + + if len(badFiles) > 0 { + fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers")) + fmt.Println("These files store cursor and undo history.") + fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers")) + + if shouldContinue() { + for _, f := range badFiles { + err := os.Remove(f) + if err != nil { + fmt.Println(err) + continue + } + } + + fmt.Println("Removed badly formatted files") + fmt.Print("\n\n") + } + } + } + + // detect plugins/ directory + plugins := filepath.Join(config.ConfigDir, "plugins") + if stat, err := os.Stat(plugins); err == nil && stat.IsDir() { + fmt.Printf("Found directory %s\n", plugins) + fmt.Printf("Plugins should now be stored in %s\n", filepath.Join(config.ConfigDir, "plug")) + fmt.Printf("Removing %s\n", plugins) + + if shouldContinue() { + os.RemoveAll(plugins) + } + + fmt.Print("\n\n") + } + + fmt.Println("Done cleaning") +} diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 4bcf99cb..f6360255 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -31,12 +31,15 @@ var ( flagOptions = flag.Bool("options", false, "Show all option help") flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)") flagPlugin = flag.String("plugin", "", "Plugin command") + flagClean = flag.Bool("clean", false, "Clean configuration directory") optionFlags map[string]*string ) func InitFlags() { flag.Usage = func() { fmt.Println("Usage: micro [OPTIONS] [FILE]...") + fmt.Println("-clean") + fmt.Println(" \tCleans the configuration directory") fmt.Println("-config-dir dir") fmt.Println(" \tSpecify a custom location for the configuration directory") fmt.Println("[FILE]:LINE:COL") @@ -106,14 +109,18 @@ func InitFlags() { } } -// DoPluginFlags parses and executes any -plugin flags +// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean) func DoPluginFlags() { - if *flagPlugin != "" { + if *flagClean || *flagPlugin != "" { config.LoadAllPlugins() - args := flag.Args() + if *flagPlugin != "" { + args := flag.Args() - config.PluginCommand(os.Stdout, *flagPlugin, args) + config.PluginCommand(os.Stdout, *flagPlugin, args) + } else if *flagClean { + CleanConfig() + } os.Exit(0) } diff --git a/internal/buffer/serialize.go b/internal/buffer/serialize.go index ae1d411f..b11b7322 100644 --- a/internal/buffer/serialize.go +++ b/internal/buffer/serialize.go @@ -5,6 +5,7 @@ import ( "errors" "io" "os" + "path/filepath" "time" "golang.org/x/text/encoding" @@ -49,7 +50,7 @@ func (b *Buffer) Unserialize() error { if b.Path == "" { return nil } - file, err := os.Open(config.ConfigDir + "/buffers/" + util.EscapePath(b.AbsPath)) + file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))) defer file.Close() if err == nil { var buffer SerializedBuffer diff --git a/internal/config/settings.go b/internal/config/settings.go index 76f11771..c5e3f011 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -5,6 +5,7 @@ import ( "errors" "io/ioutil" "os" + "path/filepath" "reflect" "strconv" "strings" @@ -45,7 +46,7 @@ var optionValidators = map[string]optionValidator{ } func ReadSettings() error { - filename := ConfigDir + "/settings.json" + filename := filepath.Join(ConfigDir, "settings.json") if _, e := os.Stat(filename); e == nil { input, err := ioutil.ReadFile(filename) if err != nil { @@ -119,13 +120,22 @@ func WriteSettings(filename string) error { return err } +func OverwriteSettings(filename string) error { + var err error + if _, e := os.Stat(ConfigDir); e == nil { + txt, _ := json.MarshalIndent(GlobalSettings, "", " ") + err = ioutil.WriteFile(filename, append(txt, '\n'), 0644) + } + return err +} + // RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options. func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error { name = pl + "." + name if v, ok := GlobalSettings[name]; !ok { defaultCommonSettings[name] = defaultvalue GlobalSettings[name] = defaultvalue - err := WriteSettings(ConfigDir + "/settings.json") + err := WriteSettings(filepath.Join(ConfigDir, "/settings.json")) if err != nil { return errors.New("Error writing settings.json file: " + err.Error()) } @@ -145,7 +155,7 @@ func RegisterGlobalOption(name string, defaultvalue interface{}) error { if v, ok := GlobalSettings[name]; !ok { defaultGlobalSettings[name] = defaultvalue GlobalSettings[name] = defaultvalue - err := WriteSettings(ConfigDir + "/settings.json") + err := WriteSettings(filepath.Join(ConfigDir, "settings.json")) if err != nil { return errors.New("Error writing settings.json file: " + err.Error()) }