fixed flags + added detect-languages flag

This commit is contained in:
Chen Asraf
2022-05-25 19:08:58 +03:00
parent 7206965384
commit f461ae274c
8 changed files with 136 additions and 84 deletions

View File

@@ -64,6 +64,7 @@ You may pass additional flags to `gi_gen`. These are the currently available fla
| `-clean-output` \| `-c` | Perform cleanup on the output .gitignore file, removing any unused patterns |
| `-append` \| `-a` | Append to .gitignore file if it already exists |
| `-overwrite` \| `-w` | Overwrite .gitignore file if it already exists |
| `-detect-languages` | Outputs the automatically-detected languages, separated by newlines, and exits. Useful for outside tools detection. |
| `-clear-cache` | Clear the .gitignore cache directory, for troubleshooting or for removing trace files of this program.<br />Exits after running, so other flags will be ignored. |
| `-help` \| `-h` | Display help message |

View File

@@ -6,6 +6,7 @@ import (
"strings"
"github.com/chenasraf/gi_gen/internal"
"github.com/chenasraf/gi_gen/internal/utils"
)
func RunMainCmd() {
@@ -15,6 +16,11 @@ func RunMainCmd() {
flag.Parse()
shouldReturn = detectLanguageCommand()
if shouldReturn {
return
}
shouldReturn = cleanCommand()
if shouldReturn {
return
@@ -22,10 +28,13 @@ func RunMainCmd() {
flagLangs := getLangsFromArgs()
internal.GIGen(&internal.GIGenOptions{
Languages: &flagLangs,
CleanOutput: &cleanOutput,
OverwriteFile: &overwriteFile,
AppendFile: &appendFile,
Languages: &flagLangs,
CleanOutput: &cleanOutput,
CleanOutputUsed: isFlagPassed("clean-output") || isFlagPassed("c"),
OverwriteFile: &overwriteFile,
OverwriteFileUsed: isFlagPassed("overwrite") || isFlagPassed("w"),
AppendFile: &appendFile,
AppendFileUsed: isFlagPassed("append") || isFlagPassed("a"),
})
}
@@ -34,6 +43,7 @@ var cleanCache bool = false
var cleanOutput bool
var overwriteFile bool
var appendFile bool
var detectLanguage bool
func shorthand(msg string) string {
return ""
@@ -41,13 +51,15 @@ func shorthand(msg string) string {
}
func initFlags() {
appendUsage := "Append to .gitignore file if it already exists"
langsUsage := "List the languages you want to use as templates.\n" +
"To add multiple templates, use commas as separators, e.g.: -languages Node,Python"
cleanOutputUsage := "Perform cleanup on the output .gitignore file, removing any unused patterns"
appendUsage := "Append to .gitignore file if it already exists"
overwriteUsage := "Overwrite .gitignore file if it already exists"
clearCacheUsage := "Clear the .gitignore cache directory, for troubleshooting or for removing trace files of this " +
"program. Exits after running, so other flags will be ignored."
cleanOutputUsage := "Perform cleanup on the output .gitignore file, removing any unused patterns"
overwriteUsage := "Overwrite .gitignore file if it already exists"
detectLanguagesUsage := "Outputs the automatically-detected languages, separated by newlines, and exits. Useful " +
"for outside tools detection."
flag.Bool("help", false, "Display help message")
flag.BoolVar(&cleanCache, "clear-cache", false, clearCacheUsage)
@@ -57,10 +69,21 @@ func initFlags() {
flag.BoolVar(&overwriteFile, "overwrite", false, overwriteUsage)
flag.BoolVar(&appendFile, "a", false, shorthand(appendUsage))
flag.BoolVar(&appendFile, "append", false, appendUsage)
flag.BoolVar(&detectLanguage, "detect-languages", false, detectLanguagesUsage)
flag.StringVar(&langsRaw, "l", langsRaw, shorthand(langsUsage))
flag.StringVar(&langsRaw, "languages", langsRaw, langsUsage)
}
func isFlagPassed(name string) bool {
found := false
flag.Visit(func(f *flag.Flag) {
if f.Name == name {
found = true
}
})
return found
}
func getLangsFromArgs() []string {
return strings.Split(langsRaw, ",")
}
@@ -73,6 +96,17 @@ func cleanCommand() bool {
return false
}
func detectLanguageCommand() bool {
if detectLanguage {
allFiles, err := internal.InitCache()
discovery, _ := internal.AutoDiscover(allFiles)
utils.HandleErr(err)
fmt.Println(strings.Join(discovery, "\n"))
return true
}
return false
}
func initHelpCommand() {
flag.Usage = func() {
w := flag.CommandLine.Output()

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/chenasraf/gi_gen/internal/utils"
"golang.org/x/exp/maps"
)
@@ -41,7 +42,7 @@ func askCleanup() bool {
}
func askYesNo(message string, defaultValue bool) bool {
return askSelection(message, []string{"Yes", "No"}, ternary(defaultValue, "Yes", "No")) == "Yes"
return askSelection(message, []string{"Yes", "No"}, utils.Ternary(defaultValue, "Yes", "No")) == "Yes"
}
func askMulti(message string, options []string) []string {
@@ -54,7 +55,7 @@ func askMulti(message string, options []string) []string {
survey.AskOne(langPrompt, &selections)
if selections == nil {
keyInterrupt()
utils.KeyInterrupt()
}
return selections
@@ -71,7 +72,7 @@ func askSelection(message string, options []string, defaultValue string) string
survey.AskOne(langPrompt, &selection)
if selection == "" {
keyInterrupt()
utils.KeyInterrupt()
}
return selection

View File

@@ -4,18 +4,20 @@ import (
"fmt"
"os"
"path/filepath"
"github.com/chenasraf/gi_gen/internal/utils"
)
func InitCache() ([]string, error) {
gitignoresDir := GetCacheDir()
if !fileExists(gitignoresDir) {
if !utils.FileExists(gitignoresDir) {
fmt.Println("Getting gitignore files...")
runCmd("git", "clone", "--depth=2", repoUrl, gitignoresDir)
utils.RunCmd("git", "clone", "--depth=2", utils.RepoUrl, gitignoresDir)
fmt.Println()
} else if isCacheNeedsUpdate() {
fmt.Println("Updating gitignore files...")
runCmd("git", "-C", gitignoresDir, "pull", "origin", "main")
utils.RunCmd("git", "-C", gitignoresDir, "pull", "origin", "main")
fmt.Println()
}

View File

@@ -6,25 +6,29 @@ import (
"path/filepath"
"strings"
"github.com/chenasraf/gi_gen/internal/utils"
"golang.org/x/exp/maps"
)
type GIGenOptions struct {
Languages *[]string
CleanOutput *bool
OverwriteFile *bool
AppendFile *bool
Languages *[]string
CleanOutput *bool
CleanOutputUsed bool
OverwriteFile *bool
OverwriteFileUsed bool
AppendFile *bool
AppendFileUsed bool
}
func GIGen(options *GIGenOptions) {
wd, err := os.Getwd()
handleErr(err)
opts := ternary(options != nil, *options, GIGenOptions{})
utils.HandleErr(err)
opts := utils.Ternary(options != nil, *options, GIGenOptions{})
outFile := filepath.Join(wd, ".gitignore")
allFiles, err := InitCache()
cacheDir := GetCacheDir()
handleErr(err)
utils.HandleErr(err)
var fileNames []string
var files map[string]string
@@ -39,25 +43,25 @@ func GIGen(options *GIGenOptions) {
cleanupSelection := getCleanupSelection(opts)
outContents := processFileOutput(cleanupSelection, selectedContents, selectedKeys)
if fileExists(outFile) {
getOverwriteSelection := newFunction(opts)
handleFileOverwrite(outFile, outContents, getOverwriteSelection)
if utils.FileExists(outFile) {
overwriteSelection := getOverwriteSelection(opts)
utils.HandleFileOverwrite(outFile, outContents, overwriteSelection)
} else {
fmt.Println()
fmt.Printf("Writing to %s\n", outFile)
writeFile(outFile, outContents, true)
utils.WriteFile(outFile, outContents, true)
}
fmt.Println()
fmt.Println("Done.")
}
func newFunction(opts GIGenOptions) string {
func getOverwriteSelection(opts GIGenOptions) string {
var overwriteSelection string
if opts.OverwriteFile != nil || opts.AppendFile != nil {
overwriteSelection = ternary(opts.OverwriteFile != nil, "Overwrite", "Append")
if opts.OverwriteFileUsed || opts.AppendFileUsed {
overwriteSelection = utils.Ternary(opts.OverwriteFileUsed, "Overwrite", "Append")
} else {
askOverwrite()
return askOverwrite()
}
return overwriteSelection
}
@@ -74,7 +78,7 @@ func processFileOutput(cleanupSelection bool, selectedContents []string, selecte
func getCleanupSelection(opts GIGenOptions) bool {
var cleanupSelection bool
if opts.CleanOutput != nil {
if opts.CleanOutputUsed {
cleanupSelection = *opts.CleanOutput
} else {
cleanupSelection = askCleanup()
@@ -87,16 +91,16 @@ func getProcessFiles(
) ([]string, []string, map[string]string) {
mappedFileNames := []string{}
if len(*opts.Languages) > 0 {
if len(*opts.Languages) > 0 && (*opts.Languages)[0] != "" {
for _, lng := range *opts.Languages {
filePath := filepath.Join(cacheDir, lng+".gitignore")
if fileExists(filePath) {
if utils.FileExists(filePath) {
mappedFileNames = append(mappedFileNames, filePath)
}
}
fileNames, files = mappedFileNames, getAllFiles(mappedFileNames)
} else {
fileNames, files = autoDiscover(allFiles)
fileNames, files = readFromSelections(allFiles)
}
return mappedFileNames, fileNames, files
}

View File

@@ -5,22 +5,11 @@ import (
"path/filepath"
"strings"
"github.com/chenasraf/gi_gen/internal/utils"
"golang.org/x/exp/maps"
)
func autoDiscover(allFiles []string) ([]string, map[string]string) {
answer := askDiscovery()
if !answer {
baseNames := []string{}
for _, fn := range allFiles {
basename := filepath.Base(fn)
langName := basename[:strings.Index(basename, ".")]
baseNames = append(baseNames, langName)
}
return baseNames, getAllFiles(allFiles)
}
func AutoDiscover(allFiles []string) ([]string, map[string]string) {
list := discoverByExplicitProjectType()
if len(list) == 0 {
@@ -30,11 +19,31 @@ func autoDiscover(allFiles []string) ([]string, map[string]string) {
return maps.Keys(list), list
}
func readFromSelections(allFiles []string) ([]string, map[string]string) {
answer := askDiscovery()
if !answer {
return readAllFiles(allFiles)
}
return AutoDiscover(allFiles)
}
func readAllFiles(allFiles []string) ([]string, map[string]string) {
baseNames := []string{}
for _, fn := range allFiles {
basename := filepath.Base(fn)
langName := basename[:strings.Index(basename, ".")]
baseNames = append(baseNames, langName)
}
return baseNames, getAllFiles(allFiles)
}
func getAllFiles(allFiles []string) map[string]string {
files := make(map[string]string)
for _, filename := range allFiles {
contents := readFile(filename)
contents := utils.ReadFile(filename)
basename := filepath.Base(filename)
langName := basename[:strings.Index(basename, ".")]
@@ -48,7 +57,7 @@ func discoverByExistingPatterns(allFiles []string) map[string]string {
files := make(map[string]string)
for _, filename := range allFiles {
contents := readFile(filename)
contents := utils.ReadFile(filename)
basename := filepath.Base(filename)
langName := basename[:strings.Index(basename, ".")]
@@ -61,7 +70,7 @@ func discoverByExistingPatterns(allFiles []string) map[string]string {
func discoverByExplicitProjectType() map[string]string {
wd, err := os.Getwd()
handleErr(err)
utils.HandleErr(err)
discoveryMap := make(map[string]string)
@@ -142,8 +151,8 @@ func discoverByExplicitProjectType() map[string]string {
checkFile := filepath.Join(wd, key)
_, keyExists := results[langName]
if !keyExists && globExists(checkFile) {
results[langName] = readFile(ignoreFile)
if !keyExists && utils.GlobExists(checkFile) {
results[langName] = utils.ReadFile(ignoreFile)
}
}

View File

@@ -7,6 +7,7 @@ import (
"path/filepath"
"strings"
"github.com/chenasraf/gi_gen/internal/utils"
"golang.org/x/exp/maps"
)
@@ -17,7 +18,7 @@ func getGitignoreFiles(sourceDir string) ([]string, error) {
func isCacheNeedsUpdate() bool {
gitignoresDir := GetCacheDir()
localBytes, localErr := exec.Command("git", "-C", gitignoresDir, "rev-list", "--count", "HEAD..@{u}").Output()
handleErr(localErr)
utils.HandleErr(localErr)
localStr := strings.TrimSpace(string(localBytes))
return localStr != "0"
@@ -50,10 +51,10 @@ func findPatternFileMatches(patterns string) bool {
line = strings.TrimSpace(line[0:idx])
}
if len(line) == 0 || contains(ignoreLines, line) {
if len(line) == 0 || utils.Contains(ignoreLines, line) {
continue
}
if globExists(filepath.Join(wd, line)) {
if utils.GlobExists(filepath.Join(wd, line)) {
return true
}
}
@@ -76,8 +77,8 @@ func removeUnusedPatterns(contents string) string {
continue
}
if globExists(filepath.Join(wd, trimmed)) {
if contains(patternCache, trimmed) {
if utils.GlobExists(filepath.Join(wd, trimmed)) {
if utils.Contains(patternCache, trimmed) {
continue
}
@@ -113,7 +114,7 @@ func gatherPreviousCommentGroup(i int, lastTakenIdx int, lines []string, keep []
if len(cur) > 0 && cur[0] == '#' {
foundComment = true
}
comments = insert(comments, 0, cur)
comments = utils.Insert(comments, 0, cur)
}
j++
}
@@ -151,7 +152,7 @@ func langHeader(langName string) string {
func getAllRaw(selected []string, selectedKeys []string) string {
for i, selection := range selected {
header := ternary(len(selected) > 1, langHeader(selectedKeys[i]), "")
header := utils.Ternary(len(selected) > 1, langHeader(selectedKeys[i]), "")
selected[i] = header + selection
}
return strings.Join(selected, "\n")
@@ -164,8 +165,8 @@ func cleanupMultipleFiles(files []string, langKeys []string) string {
if strings.TrimSpace(cleanSelection) == "" {
continue
}
header := ternary(len(files) > 1, langHeader(langKeys[i]), "")
prefixNewline := ternary(i > 0, "\n", "")
header := utils.Ternary(len(files) > 1, langHeader(langKeys[i]), "")
prefixNewline := utils.Ternary(i > 0, "\n", "")
contents := prefixNewline + header + cleanSelection
out = append(out, contents)
}

View File

@@ -1,4 +1,4 @@
package internal
package utils
import (
"fmt"
@@ -7,47 +7,47 @@ import (
"path/filepath"
)
var repoUrl = "https://github.com/github/gitignore"
var RepoUrl = "https://github.com/github/gitignore"
func fileExists(path string) bool {
func FileExists(path string) bool {
_, err := os.Stat(path)
exists := !os.IsNotExist(err)
return exists
}
func globExists(path string) bool {
func GlobExists(path string) bool {
res, err := filepath.Glob(path)
handleErr(err)
HandleErr(err)
return res != nil
}
func runCmd(cmd string, args ...string) (string, error) {
func RunCmd(cmd string, args ...string) (string, error) {
res, err := exec.Command(cmd, args...).Output()
return string(res), err
}
func readFile(path string) string {
func ReadFile(path string) string {
res, err := os.ReadFile(path)
handleErr(err)
HandleErr(err)
return string(res)
}
func writeFile(path string, data string, overwrite bool) bool {
func WriteFile(path string, data string, overwrite bool) bool {
var err error
if overwrite {
err = os.WriteFile(path, []byte(data), 0644)
handleErr(err)
HandleErr(err)
} else {
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
handleErr(err)
HandleErr(err)
defer f.Close()
_, err = f.WriteString("\n" + data)
handleErr(err)
HandleErr(err)
}
return true
}
func handleErr(err error) {
func HandleErr(err error) {
if err != nil {
fmt.Println("Encountered an error while running gi_gen:")
fmt.Println(err)
@@ -55,11 +55,11 @@ func handleErr(err error) {
}
}
func insert[T any](a []T, i int, item T) []T {
func Insert[T any](a []T, i int, item T) []T {
return append(a[:i], append([]T{item}, a[i:]...)...)
}
func contains[T comparable](list []T, item T) bool {
func Contains[T comparable](list []T, item T) bool {
for _, v := range list {
if v == item {
return true
@@ -68,41 +68,41 @@ func contains[T comparable](list []T, item T) bool {
return false
}
func ternary[T any](cond bool, whenTrue T, whenFalse T) T {
func Ternary[T any](cond bool, whenTrue T, whenFalse T) T {
if cond {
return whenTrue
}
return whenFalse
}
func toString[T any](obj T) string {
func ToString[T any](obj T) string {
return fmt.Sprint(obj)
}
func handleFileOverwrite(path string, contents string, selection string) {
func HandleFileOverwrite(path string, contents string, selection string) {
switch selection {
case "Skip":
quit("Nothing to do, exiting")
Quit("Nothing to do, exiting")
return
case "Overwrite":
fmt.Println()
fmt.Printf("Writing to %s\n", path)
writeFile(path, contents, true)
WriteFile(path, contents, true)
return
case "Append":
fmt.Println()
fmt.Printf("Appending to %s\n", path)
writeFile(path, contents, false)
WriteFile(path, contents, false)
return
}
quit("Bad selection")
Quit("Bad selection")
}
func keyInterrupt() {
quit("KeyInterrupt: Quitting")
func KeyInterrupt() {
Quit("KeyInterrupt: Quitting")
}
func quit(message string) {
func Quit(message string) {
fmt.Println()
fmt.Println(message)
os.Exit(1)