Skip to content

Commit

Permalink
✨ Add ignore command
Browse files Browse the repository at this point in the history
Ignore command added
It fetches .gitignore template from toptal api.
Compare it to the local gitignore
And append the remaining items into the local gitignore
  • Loading branch information
julien040 committed Dec 30, 2022
1 parent f8d20ec commit 87eba8f
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 27 deletions.
35 changes: 8 additions & 27 deletions cmd/ignore.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ THE SOFTWARE.
package cmd

import (
"fmt"
"github.com/julien040/gut/src/controller"

"github.com/spf13/cobra"
)
Expand All @@ -34,41 +34,22 @@ var ignoreCmd = &cobra.Command{
Long: `Download a .gitignore template from
https://github.com/github/gitignore
and add it to the current repository.
If .gitignore already exists, it will be updated or overwritten.
If .gitignore already exists, it will be updated.
`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("ignore called")
},
}
Args: cobra.MaximumNArgs(1),

var ignoreAddCmd = &cobra.Command{
Use: "add",
Short: "Add a file or a directory to the .gitignore file",
Long: `Add a file or a directory to the .gitignore file`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("ignore add called")
},
Run: controller.Ignore,
Aliases: []string{"ig", "gitignore"},
}

var listTemplatesCmd = &cobra.Command{
Use: "list",
Short: "List all available templates",
Long: `List all available templates`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("ignore list called")
},
Use: "list",
Short: "List all available templates",
Run: controller.IgnoreList,
Aliases: []string{"ls"},
}

func init() {
rootCmd.AddCommand(ignoreCmd)
ignoreCmd.AddCommand(ignoreAddCmd)
ignoreCmd.AddCommand(listTemplatesCmd)
ignoreAddCmd.Flags().BoolP("force", "f", false, "Overwrite the .gitignore file if it already exists")
ignoreAddCmd.Flags().BoolP("update", "u", false, "Update the .gitignore file if it already exists")
ignoreAddCmd.Flags().String("template", "t", "Download a .gitignore template from github.com/github/gitignore")
ignoreAddCmd.Flags().String("filename", "f", "Name of the file to add to the .gitignore file")
ignoreAddCmd.MarkFlagFilename("filename")
ignoreAddCmd.Flags().String("directory", "d", "Name of the directory to add to the .gitignore file")
ignoreAddCmd.MarkFlagDirname("directory")
}
197 changes: 197 additions & 0 deletions src/controller/ignore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package controller

import (
"encoding/json"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"

"github.com/AlecAivazis/survey/v2"
"github.com/briandowns/spinner"
"github.com/julien040/gut/src/print"
"github.com/spf13/cobra"
)

type ignoreTemplate struct {
name string
contents string
}

const gitignoreApiURL = "https://www.toptal.com/developers/gitignore/api/list?format=json"

// Fetch the list of available gitignore templates from constant gitignoreApiURL
func fetchIgnoreList() ([]ignoreTemplate, error) {
// Fetch the list of available gitignore templates
print.Message("Fetching the list of available gitignore templates...", "info")
s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
s.Start()

resp, err := http.Get(gitignoreApiURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var bodyAPI map[string]interface{}

var templates []ignoreTemplate
err = json.Unmarshal(body, &bodyAPI)
if err != nil {
return nil, err
}
for _, v := range bodyAPI {
// Check if it's a map
if template, ok := v.(map[string]interface{}); ok {
templates = append(templates, ignoreTemplate{
name: template["name"].(string),
contents: template["contents"].(string),
})
}
}
s.Stop()
return templates, nil

}

// Ask the user to select a gitignore template
func selectGitignoreTemplate(templates []ignoreTemplate) ignoreTemplate {
// Ask the user to select a gitignore template
var templateNames []string
for _, template := range templates {
templateNames = append(templateNames, template.name)
}
var selectedTemplate int
prompt := &survey.Select{
Message: "Select a gitignore template:",
Options: templateNames,
}
err := survey.AskOne(prompt, &selectedTemplate)
if err != nil {
exitOnError("Sorry, I can't get your input 😓", err)
}
return templates[selectedTemplate]
}

func getGitignoreContentFromPath(path string) string {
// Check if .gitignore already exists
gitignorePath := filepath.Join(path, ".gitignore")
file, err := os.Open(gitignorePath)
if os.IsNotExist(err) {
// Create the file
file, err := os.Create(gitignorePath)
if err != nil {
exitOnError("Sorry, I can't create the .gitignore file 😓. Please, can you check if I have the right permissions?", err)
}
defer file.Close()
return ""
} else if err != nil {
defer file.Close()
exitOnError("Sorry, I can't open the .gitignore file 😓. Please, can you check if I have the right permissions?", err)

} else {
// Read the file
content, err := io.ReadAll(file)
if err != nil {
exitOnError("Sorry, I can't read the .gitignore file 😓. Please, can you check if I have the right permissions?", err)
}
defer file.Close()
return string(content)

}

return ""

}

func splitStringByNewLine(str string) []string {
return strings.Split(str, "\n")
}

// Returns a list of elements that are in b and not in a
func Difference(a, b []string) []string {
// https://stackoverflow.com/a/45428032/15573415

// Create a map of all elements in a.
m := make(map[string]bool)
for _, x := range a {
m[x] = true
}
var diff []string

// Loop over b and check if each element is in the map.
// If not, it means it's not in a and we append it to the diff.
for _, x := range b {
if !m[x] {
// Check if the line is not empty and not a comment
if x != "" && x[:1] != "#" {
diff = append(diff, x)
}
}
}
return diff

}

// Append a list of excluded files to the .gitignore file
func appendToGitignore(path string, content []string, templateName string) {
// Open the file in append mode
file, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
exitOnError("Sorry, I can't open the .gitignore file 😓. Please, can you check if I have the right permissions?", err)
}
defer file.Close()

// Add a comment to the file
_, err = file.WriteString("\n# " + templateName + " template downloaded with gut\n")
if err != nil {
exitOnError("Sorry, I can't write to the .gitignore file 😓. Please, can you check if I have the right permissions?", err)
}

// Append the content to the file
for _, line := range content {
_, err := file.WriteString(line + "\n")
if err != nil {
exitOnError("Sorry, I can't write to the .gitignore file 😓. Please, can you check if I have the right permissions?", err)
}
}
}

// Cmd to download a gitignore template
func Ignore(cmd *cobra.Command, args []string) {
wd, err := os.Getwd()
if err != nil {
exitOnError("Sorry, I can't get your current working directory 😓", err)
}
// Get the local .gitignore content
gitignoreContent := splitStringByNewLine(getGitignoreContentFromPath(wd))

templates, err := fetchIgnoreList()
if err != nil {
exitOnError("Sorry, I couldn't fetch the list of available gitignore templates 😓", err)
}
template := selectGitignoreTemplate(templates)
gitignoreTemplateContent := splitStringByNewLine(template.contents)
// Get the difference between the local .gitignore and the gitignore template
diff := Difference(gitignoreContent, gitignoreTemplateContent)
// Append the difference to the local .gitignore
if len(diff) == 0 {
print.Message("Your .gitignore file is already up to date with the "+template.name+" template 😎", print.Success)
return
} else {
appendToGitignore(filepath.Join(wd, ".gitignore"), diff, template.name)
print.Message("I've updated your .gitignore file with the "+template.name+" template 🎉", print.Success)
}

}

// Cmd to list all available gitignore templates
func IgnoreList(cmd *cobra.Command, args []string) {
// TODO
}

0 comments on commit 87eba8f

Please sign in to comment.