refactor: file structure

This commit is contained in:
Chen Asraf
2022-05-19 12:17:33 +03:00
parent 629df2adf6
commit dbd121848c
9 changed files with 290 additions and 272 deletions

1
.gitignore vendored
View File

@@ -1 +0,0 @@

28
.vscode/launch.json vendored
View File

@@ -1,15 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "gi_gen.go"
}
]
}
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "main.go"
}
]
}

33
cmd/gi_gen.go Normal file
View File

@@ -0,0 +1,33 @@
package cmd
import (
"log"
"os"
"path/filepath"
. "github.com/chenasraf/gi_gen/internal"
)
func RunMainCmd() {
wd, err := os.Getwd()
HandleErr(err)
outFile := filepath.Join(wd, ".gitignore")
allFiles, err := PrepareGitignores()
HandleErr(err)
fileNames, files := GetRelevantFiles(allFiles)
log.Println("Done.")
selected, selectedKeys := GetLanguageSelections(files, fileNames)
cleanupSelection := GetCleanupSelection()
outContents := Ternary(cleanupSelection, CleanupMultiple(selected, selectedKeys), GetAllRaw(selected, selectedKeys))
if FileExists(outFile) {
HandleFileOverwrite(outFile, outContents)
} else {
log.Printf("Writing to %s", outFile)
WriteFile(outFile, outContents, true)
}
}

View File

@@ -1,54 +0,0 @@
package main
import (
"log"
"os"
"path/filepath"
"strings"
)
func main() {
wd, err := os.Getwd()
handleErr(err)
outFile := filepath.Join(wd, ".gitignore")
allFiles, err := prepareGitignores()
handleErr(err)
fileNames, files := getPossibleFiles(allFiles)
log.Println("Done.")
selected, selectedKeys := getLanguages(files, fileNames)
cleanupSelection := getCleanupSelection()
var outContents string
if cleanupSelection {
out := []string{}
for i, selection := range selected {
cleanSelection := removeUnusedPatterns(selection)
if strings.TrimSpace(cleanSelection) == "" {
continue
}
header := langHeader(selectedKeys[i])
prefixNewline := ternary(i > 0, "\n", "")
contents := prefixNewline + header + cleanSelection
out = append(out, contents)
}
outContents = strings.Join(out, "\n")
} else {
for i, selection := range selected {
header := langHeader(selectedKeys[i])
selected[i] = header + selection
}
outContents = strings.Join(selected, "\n")
}
if fileExists(outFile) {
handleFileOverwrite(outFile, outContents)
} else {
log.Printf("Writing to %s", outFile)
writeFile(outFile, outContents, true)
}
}

View File

@@ -1,151 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"golang.org/x/exp/maps"
)
func prepareGitignores() ([]string, error) {
gitignoresDir := getCacheDir()
if !fileExists(gitignoresDir) {
log.Println("Getting gitignore files...")
runCmd("git", "clone", "--depth=1", repoUrl, gitignoresDir)
}
if getNeedsUpdate() {
log.Println("Updating gitignore files...")
runCmd("git", "pull", "origin", "master")
os.RemoveAll(filepath.Join(gitignoresDir, ".git"))
}
return getGitignores(gitignoresDir)
}
func getCacheDir() string {
homeDir, _ := os.UserHomeDir()
gitignoresDir := filepath.Join(homeDir, ".github.gitignore")
return gitignoresDir
}
func getGitignores(sourceDir string) ([]string, error) {
return filepath.Glob(filepath.Join(sourceDir, "*.gitignore"))
}
var ignoreLines = []string{
"/*",
".",
".vscode",
".vscode/*",
".idea",
".idea/*",
}
func findFileMatches(patterns string) bool {
lines := strings.Split(patterns, "\n")
wd, _ := os.Getwd()
for _, line := range lines {
line = strings.TrimSpace(line)
// ignore empty lines / comments
if len(line) == 0 || strings.ToLower(line)[0] == '#' {
continue
}
idx := strings.Index(line, "#")
// ignore comments at end of line
if idx > -1 && (idx == 0 || line[idx-1] != '\\') {
line = strings.TrimSpace(line[0:idx])
}
if len(line) == 0 || contains(ignoreLines, line) {
continue
}
if globExists(filepath.Join(wd, line)) {
return true
}
}
return false
}
var patternCache []string = []string{}
func removeUnusedPatterns(contents string) string {
wd, _ := os.Getwd()
lines := strings.Split(contents, "\n")
keep := []string{}
lastTakenIdx := -1
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if len(trimmed) == 0 || trimmed[0] == '#' {
continue
}
if globExists(filepath.Join(wd, trimmed)) {
if contains(patternCache, trimmed) {
continue
}
patternCache = append(patternCache, trimmed)
if i > 0 {
j := 1
foundComment := false
comments := []string{}
for {
if i-j < 0 || i-j <= lastTakenIdx {
break
}
cur := lines[i-j]
if len(cur) > 0 && cur[0] != '#' {
if !foundComment {
} else {
break
}
} else {
lastTakenIdx = i - j
if len(cur) > 0 && cur[0] == '#' {
foundComment = true
}
comments = insert(comments, 0, cur)
}
j++
}
for _, v := range comments {
keep = append(keep, v)
}
}
keep = append(keep, line)
}
}
return strings.Join(keep, "\n")
}
func getLanguages(files map[string]string, fileNames []string) ([]string, []string) {
selected := []string{}
allKeys := maps.Keys(files)
selectedKeys := maps.Keys(files)
if len(allKeys) > 1 {
selected, selectedKeys = getLanguageSelections(fileNames, selected, files)
} else {
selected = []string{files[allKeys[0]]}
}
return selected, selectedKeys
}
func langHeader(langName string) string {
sep := "#========================================================================\n"
header := fmt.Sprintf(sep+"# %s\n"+sep+"\n", langName)
return header
}

View File

@@ -1,33 +1,31 @@
package main
package internal
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
)
func handleFileOverwrite(path string, contents string) {
overwriteSelection := getOverwriteSelection()
func HandleFileOverwrite(path string, contents string) {
overwriteSelection := AskOverwrite()
switch overwriteSelection {
case "":
quit()
Quit()
break
case "Overwrite":
log.Printf("Writing to %s", path)
writeFile(path, contents, true)
WriteFile(path, contents, true)
break
case "Append":
log.Printf("Appending to %s", path)
writeFile(path, contents, false)
WriteFile(path, contents, false)
break
}
}
func getOverwriteSelection() string {
func AskOverwrite() string {
overwritePrompt := &survey.Select{
Message: ".gitignore file found in this directory. Please pick an option:",
Options: []string{"Overwrite", "Append", "Skip"},
@@ -37,7 +35,7 @@ func getOverwriteSelection() string {
return overwriteSelection
}
func getCleanupSelection() bool {
func GetCleanupSelection() bool {
cleanupPrompt := &survey.Confirm{
Message: "Do you want to remove patterns not existing in your project?",
Default: true,
@@ -48,7 +46,7 @@ func getCleanupSelection() bool {
return cleanupSelection
}
func getLanguageSelections(fileNames []string, selected []string, files map[string]string) ([]string, []string) {
func AskLanguage(fileNames []string, selected []string, files map[string]string) ([]string, []string) {
langPrompt := &survey.MultiSelect{
Message: "Found " + fmt.Sprint(len(fileNames)) +
" possible matches in your project for gitignore files.\n" +
@@ -59,7 +57,7 @@ func getLanguageSelections(fileNames []string, selected []string, files map[stri
var langSelections []string
survey.AskOne(langPrompt, &langSelections)
if langSelections == nil {
quit()
Quit()
}
keys := []string{}
for _, selection := range langSelections {
@@ -70,10 +68,7 @@ func getLanguageSelections(fileNames []string, selected []string, files map[stri
return selected, keys
}
func getPossibleFiles(allFiles []string) ([]string, map[string]string) {
files := make(map[string]string)
fileNames := []string{}
func AskDiscovery() bool {
prompt := &survey.Confirm{
Message: "Would you like to try to scan for available templates automatically?\n" +
"Select 'No' ('n') to see all available templates",
@@ -81,26 +76,9 @@ func getPossibleFiles(allFiles []string) ([]string, map[string]string) {
}
var answer bool
survey.AskOne(prompt, &answer)
for _, filename := range allFiles {
contents := readFile(filename)
basename := filepath.Base(filename)
langName := basename[:strings.Index(basename, ".")]
if answer {
if findFileMatches(contents) {
files[langName] = contents
fileNames = append(fileNames, langName)
}
} else {
files[langName] = contents
fileNames = append(fileNames, langName)
}
}
return fileNames, files
return answer
}
func quit() {
func Quit() {
os.Exit(1)
}

204
internal/ignore_files.go Normal file
View File

@@ -0,0 +1,204 @@
package internal
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"golang.org/x/exp/maps"
)
func PrepareGitignores() ([]string, error) {
gitignoresDir := GetCacheDir()
if !FileExists(gitignoresDir) {
log.Println("Getting gitignore files...")
RunCmd("git", "clone", "--depth=1", repoUrl, gitignoresDir)
}
if GetNeedsUpdate() {
log.Println("Updating gitignore files...")
RunCmd("git", "pull", "origin", "master")
os.RemoveAll(filepath.Join(gitignoresDir, ".git"))
}
return GetGitignores(gitignoresDir)
}
func GetCacheDir() string {
homeDir, _ := os.UserHomeDir()
gitignoresDir := filepath.Join(homeDir, ".github.gitignore")
return gitignoresDir
}
func GetGitignores(sourceDir string) ([]string, error) {
return filepath.Glob(filepath.Join(sourceDir, "*.gitignore"))
}
var ignoreLines = []string{
"/*",
".",
".vscode",
".vscode/*",
".idea",
".idea/*",
}
func FindFileMatches(patterns string) bool {
lines := strings.Split(patterns, "\n")
wd, _ := os.Getwd()
for _, line := range lines {
line = strings.TrimSpace(line)
// ignore empty lines / comments
if len(line) == 0 || strings.ToLower(line)[0] == '#' {
continue
}
idx := strings.Index(line, "#")
// ignore comments at end of line
if idx > -1 && (idx == 0 || line[idx-1] != '\\') {
line = strings.TrimSpace(line[0:idx])
}
if len(line) == 0 || Contains(ignoreLines, line) {
continue
}
if GlobExists(filepath.Join(wd, line)) {
return true
}
}
return false
}
var patternCache []string = []string{}
func RemoveUnusedPatterns(contents string) string {
wd, _ := os.Getwd()
lines := strings.Split(contents, "\n")
keep := []string{}
lastTakenIdx := -1
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if len(trimmed) == 0 || trimmed[0] == '#' {
continue
}
if GlobExists(filepath.Join(wd, trimmed)) {
if Contains(patternCache, trimmed) {
continue
}
patternCache = append(patternCache, trimmed)
if i > 0 {
keep = GatherPreviousCommentGroup(i, lastTakenIdx, lines, keep)
}
keep = append(keep, line)
}
}
return strings.Join(keep, "\n")
}
func GatherPreviousCommentGroup(i int, lastTakenIdx int, lines []string, keep []string) []string {
j := 1
foundComment := false
comments := []string{}
for {
if i-j < 0 || i-j <= lastTakenIdx {
break
}
cur := lines[i-j]
if len(cur) > 0 && cur[0] != '#' {
if !foundComment {
} else {
break
}
} else {
lastTakenIdx = i - j
if len(cur) > 0 && cur[0] == '#' {
foundComment = true
}
comments = Insert(comments, 0, cur)
}
j++
}
for _, v := range comments {
keep = append(keep, v)
}
return keep
}
func GetLanguageSelections(files map[string]string, fileNames []string) ([]string, []string) {
selected := []string{}
allKeys := maps.Keys(files)
selectedKeys := maps.Keys(files)
if len(allKeys) > 1 {
selected, selectedKeys = AskLanguage(fileNames, selected, files)
} else {
selected = []string{files[allKeys[0]]}
}
return selected, selectedKeys
}
func GetRelevantFiles(allFiles []string) ([]string, map[string]string) {
files := make(map[string]string)
fileNames := []string{}
answer := AskDiscovery()
for _, filename := range allFiles {
contents := ReadFile(filename)
basename := filepath.Base(filename)
langName := basename[:strings.Index(basename, ".")]
if answer {
if FindFileMatches(contents) {
files[langName] = contents
fileNames = append(fileNames, langName)
}
} else {
files[langName] = contents
fileNames = append(fileNames, langName)
}
}
return fileNames, files
}
func LangHeader(langName string) string {
sep := "#========================================================================\n"
header := fmt.Sprintf(sep+"# %s\n"+sep+"\n", langName)
return header
}
func GetAllRaw(selected []string, selectedKeys []string) string {
for i, selection := range selected {
header := LangHeader(selectedKeys[i])
selected[i] = header + selection
}
return strings.Join(selected, "\n")
}
func CleanupMultiple(selected []string, keys []string) string {
out := []string{}
for i, selection := range selected {
cleanSelection := RemoveUnusedPatterns(selection)
if strings.TrimSpace(cleanSelection) == "" {
continue
}
header := LangHeader(keys[i])
prefixNewline := Ternary(i > 0, "\n", "")
contents := prefixNewline + header + cleanSelection
out = append(out, contents)
}
return strings.Join(out, "\n")
}

View File

@@ -1,4 +1,4 @@
package main
package internal
import (
"fmt"
@@ -14,19 +14,19 @@ func UNUSED(x ...interface{}) {}
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 getNeedsUpdate() bool {
func GetNeedsUpdate() bool {
localBytes, localErr := exec.Command("git", "rev-parse", "@").Output()
baseBytes, baseErr := exec.Command("git", "merge-base", "@", "@{u}").Output()
if localErr != nil {
@@ -43,46 +43,46 @@ func getNeedsUpdate() bool {
return localStr == baseStr
}
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 {
// os.Create(path)
err = os.WriteFile(path, []byte(data), fs.ModeAppend)
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 {
log.Println("Encountered an error while running gi_gen:")
log.Fatalln(err)
}
}
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
@@ -91,13 +91,13 @@ 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 toS[T any](obj T) string {
func ToString[T any](obj T) string {
return fmt.Sprint(obj)
}

9
main.go Normal file
View File

@@ -0,0 +1,9 @@
package main
import (
"github.com/chenasraf/gi_gen/cmd"
)
func main() {
cmd.RunMainCmd()
}