mirror of
https://github.com/chenasraf/gi_gen.git
synced 2026-05-17 17:48:01 +00:00
fixed flags + added detect-languages flag
This commit is contained in:
@@ -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 |
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user