mirror of
https://github.com/ivuorinen/gibidify.git
synced 2026-01-26 03:24:05 +00:00
feat: more features, output formats, configs, etc
This commit is contained in:
@@ -5,7 +5,7 @@ end_of_line = lf
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = tab
|
||||||
tab_width = 2
|
tab_width = 2
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,2 +1,9 @@
|
|||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
gibidify
|
gibidify
|
||||||
|
gibidify.json
|
||||||
gibidify.txt
|
gibidify.txt
|
||||||
|
gibidify.yaml
|
||||||
|
output.json
|
||||||
|
output.txt
|
||||||
|
output.yaml
|
||||||
|
|||||||
@@ -9,4 +9,3 @@ RUN chmod +x /usr/local/bin/gibidify
|
|||||||
|
|
||||||
# Set the entrypoint
|
# Set the entrypoint
|
||||||
ENTRYPOINT ["/usr/local/bin/gibidify"]
|
ENTRYPOINT ["/usr/local/bin/gibidify"]
|
||||||
|
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
MIT License Copyright (c) 2021 Ben Boyter
|
MIT License Copyright (c) 2025 Ismo Vuorinen
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
|||||||
@@ -3,25 +3,34 @@ package fileproc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteRequest represents the content to be written.
|
// WriteRequest represents the content to be written.
|
||||||
type WriteRequest struct {
|
type WriteRequest struct {
|
||||||
|
Path string
|
||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessFile reads the file at filePath and sends a formatted output to outCh.
|
// ProcessFile reads the file at filePath and sends a formatted output to outCh.
|
||||||
// The optional wg parameter is used when the caller wants to wait on file-level processing.
|
func ProcessFile(filePath string, outCh chan<- WriteRequest, rootPath string) {
|
||||||
func ProcessFile(filePath string, outCh chan<- WriteRequest, wg *interface{}) {
|
content, err := os.ReadFile(filePath)
|
||||||
content, err := ioutil.ReadFile(filePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Failed to read file %s: %v", filePath, err)
|
logrus.Errorf("Failed to read file %s: %v", filePath, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Format: separator, file path, then content.
|
|
||||||
formatted := fmt.Sprintf("\n---\n%s\n%s\n", filePath, string(content))
|
// Compute path relative to rootPath, so /a/b/c/d.c becomes c/d.c
|
||||||
outCh <- WriteRequest{Content: formatted}
|
relPath, err := filepath.Rel(rootPath, filePath)
|
||||||
|
if err != nil {
|
||||||
|
// Fallback if something unexpected happens
|
||||||
|
relPath = filePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format: separator, then relative path, then content
|
||||||
|
formatted := fmt.Sprintf("\n---\n%s\n%s\n", relPath, string(content))
|
||||||
|
outCh <- WriteRequest{Path: relPath, Content: formatted}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,20 +13,29 @@ func TestProcessFile(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.Remove(tmpFile.Name())
|
defer func(name string) {
|
||||||
|
err := os.Remove(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}(tmpFile.Name())
|
||||||
|
|
||||||
content := "Test content"
|
content := "Test content"
|
||||||
if _, err := tmpFile.WriteString(content); err != nil {
|
if _, err := tmpFile.WriteString(content); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
tmpFile.Close()
|
errTmpFile := tmpFile.Close()
|
||||||
|
if errTmpFile != nil {
|
||||||
|
t.Fatal(errTmpFile)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ch := make(chan WriteRequest, 1)
|
ch := make(chan WriteRequest, 1)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
ProcessFile(tmpFile.Name(), ch, nil)
|
ProcessFile(tmpFile.Name(), ch, "")
|
||||||
}()
|
}()
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(ch)
|
close(ch)
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
package fileproc
|
package fileproc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/boyter/gocodewalker"
|
"os"
|
||||||
"github.com/sirupsen/logrus"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ivuorinen/gibidify/config"
|
||||||
|
ignore "github.com/sabhiram/go-gitignore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Walker defines an interface for scanning directories.
|
// Walker defines an interface for scanning directories.
|
||||||
@@ -11,30 +15,148 @@ type Walker interface {
|
|||||||
Walk(root string) ([]string, error)
|
Walk(root string) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProdWalker implements Walker using gocodewalker.
|
// ProdWalker implements Walker using a custom directory walker that
|
||||||
|
// respects .gitignore and .ignore files, configuration-defined ignore directories,
|
||||||
|
// and ignores binary and image files by default.
|
||||||
type ProdWalker struct{}
|
type ProdWalker struct{}
|
||||||
|
|
||||||
// Walk scans the given root directory using gocodewalker and returns a slice of file paths.
|
// ignoreRule holds an ignore matcher along with the base directory where it was loaded.
|
||||||
func (pw ProdWalker) Walk(root string) ([]string, error) {
|
type ignoreRule struct {
|
||||||
fileListQueue := make(chan *gocodewalker.File, 100)
|
base string
|
||||||
fileWalker := gocodewalker.NewFileWalker(root, fileListQueue)
|
gi *ignore.GitIgnore
|
||||||
|
}
|
||||||
|
|
||||||
errorHandler := func(err error) bool {
|
// Walk scans the given root directory recursively and returns a slice of file paths
|
||||||
logrus.Errorf("error walking directory: %s", err.Error())
|
// that are not ignored based on .gitignore/.ignore files, the configuration, or the default binary/image filter.
|
||||||
|
func (pw ProdWalker) Walk(root string) ([]string, error) {
|
||||||
|
absRoot, err := filepath.Abs(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return walkDir(absRoot, absRoot, []ignoreRule{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// walkDir recursively walks the directory tree starting at currentDir.
|
||||||
|
// It loads any .gitignore and .ignore files found in each directory and
|
||||||
|
// appends the corresponding rules to the inherited list. Each file/directory is
|
||||||
|
// then checked against the accumulated ignore rules, the configuration's list of ignored directories,
|
||||||
|
// and a default filter that ignores binary and image files.
|
||||||
|
func walkDir(root string, currentDir string, parentRules []ignoreRule) ([]string, error) {
|
||||||
|
var results []string
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(currentDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with the parent's ignore rules.
|
||||||
|
rules := make([]ignoreRule, len(parentRules))
|
||||||
|
copy(rules, parentRules)
|
||||||
|
|
||||||
|
// Check for .gitignore and .ignore files in the current directory.
|
||||||
|
for _, fileName := range []string{".gitignore", ".ignore"} {
|
||||||
|
ignorePath := filepath.Join(currentDir, fileName)
|
||||||
|
if info, err := os.Stat(ignorePath); err == nil && !info.IsDir() {
|
||||||
|
gi, err := ignore.CompileIgnoreFile(ignorePath)
|
||||||
|
if err == nil {
|
||||||
|
rules = append(rules, ignoreRule{
|
||||||
|
base: currentDir,
|
||||||
|
gi: gi,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the list of directories to ignore from configuration.
|
||||||
|
ignoredDirs := config.GetIgnoredDirectories()
|
||||||
|
sizeLimit := config.GetFileSizeLimit() // e.g., 5242880 for 5 MB
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
fullPath := filepath.Join(currentDir, entry.Name())
|
||||||
|
|
||||||
|
// For directories, check if its name is in the config ignore list.
|
||||||
|
if entry.IsDir() {
|
||||||
|
for _, d := range ignoredDirs {
|
||||||
|
if entry.Name() == d {
|
||||||
|
// Skip this directory entirely.
|
||||||
|
goto SkipEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if file exceeds the configured size limit.
|
||||||
|
info, err := entry.Info()
|
||||||
|
if err == nil && info.Size() > sizeLimit {
|
||||||
|
goto SkipEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// For files, apply the default filter to ignore binary and image files.
|
||||||
|
if isBinaryOrImage(fullPath) {
|
||||||
|
goto SkipEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check accumulated ignore rules.
|
||||||
|
for _, rule := range rules {
|
||||||
|
// Compute the path relative to the base where the ignore rule was defined.
|
||||||
|
rel, err := filepath.Rel(rule.base, fullPath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If the rule matches, skip this entry.
|
||||||
|
if rule.gi.MatchesPath(rel) {
|
||||||
|
goto SkipEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not ignored, then process the entry.
|
||||||
|
if entry.IsDir() {
|
||||||
|
subFiles, err := walkDir(root, fullPath, rules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results = append(results, subFiles...)
|
||||||
|
} else {
|
||||||
|
results = append(results, fullPath)
|
||||||
|
}
|
||||||
|
SkipEntry:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isBinaryOrImage checks if a file should be considered binary or an image based on its extension.
|
||||||
|
// The check is case-insensitive.
|
||||||
|
func isBinaryOrImage(filePath string) bool {
|
||||||
|
ext := strings.ToLower(filepath.Ext(filePath))
|
||||||
|
// Common image file extensions.
|
||||||
|
imageExtensions := map[string]bool{
|
||||||
|
".png": true,
|
||||||
|
".jpg": true,
|
||||||
|
".jpeg": true,
|
||||||
|
".gif": true,
|
||||||
|
".bmp": true,
|
||||||
|
".tiff": true,
|
||||||
|
".ico": true,
|
||||||
|
".svg": true,
|
||||||
|
".webp": true,
|
||||||
|
}
|
||||||
|
// Common binary file extensions.
|
||||||
|
binaryExtensions := map[string]bool{
|
||||||
|
".exe": true,
|
||||||
|
".dll": true,
|
||||||
|
".so": true,
|
||||||
|
".bin": true,
|
||||||
|
".dat": true,
|
||||||
|
".zip": true,
|
||||||
|
".tar": true,
|
||||||
|
".gz": true,
|
||||||
|
".7z": true,
|
||||||
|
".rar": true,
|
||||||
|
".DS_Store": true,
|
||||||
|
}
|
||||||
|
if imageExtensions[ext] || binaryExtensions[ext] {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
fileWalker.SetErrorHandler(errorHandler)
|
return false
|
||||||
go func() {
|
|
||||||
err := fileWalker.Start()
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("error walking directory: %s", err.Error())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var files []string
|
|
||||||
for f := range fileListQueue {
|
|
||||||
files = append(files, f.Location)
|
|
||||||
}
|
|
||||||
|
|
||||||
return files, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,94 @@
|
|||||||
// Package fileproc provides functions for writing file contents concurrently.
|
|
||||||
package fileproc
|
package fileproc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartWriter listens on the write channel and writes content to outFile.
|
// FileData represents a single file's path and content.
|
||||||
// When finished, it signals on the done channel.
|
type FileData struct {
|
||||||
func StartWriter(outFile *os.File, writeCh <-chan WriteRequest, done chan<- struct{}) {
|
Path string `json:"path" yaml:"path"`
|
||||||
writer := io.Writer(outFile)
|
Content string `json:"content" yaml:"content"`
|
||||||
for req := range writeCh {
|
}
|
||||||
if _, err := writer.Write([]byte(req.Content)); err != nil {
|
|
||||||
logrus.Errorf("Error writing to file: %v", err)
|
// OutputData represents the full output structure.
|
||||||
}
|
type OutputData struct {
|
||||||
}
|
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||||
done <- struct{}{}
|
Files []FileData `json:"files" yaml:"files"`
|
||||||
|
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartWriter writes the output in the specified format.
|
||||||
|
func StartWriter(outFile *os.File, writeCh <-chan WriteRequest, done chan<- struct{}, format string, prefix, suffix string) {
|
||||||
|
var files []FileData
|
||||||
|
|
||||||
|
// Read from channel until closed
|
||||||
|
for req := range writeCh {
|
||||||
|
files = append(files, FileData{Path: req.Path, Content: req.Content})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create output struct
|
||||||
|
output := OutputData{Prefix: prefix, Files: files, Suffix: suffix}
|
||||||
|
|
||||||
|
// Serialize based on format
|
||||||
|
var outputData []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case "json":
|
||||||
|
outputData, err = json.MarshalIndent(output, "", " ")
|
||||||
|
case "yaml":
|
||||||
|
outputData, err = yaml.Marshal(output)
|
||||||
|
case "markdown":
|
||||||
|
outputData = []byte(formatMarkdown(output))
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unsupported format: %s", format)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Error encoding output: %v", err)
|
||||||
|
close(done)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
if _, err := outFile.Write(outputData); err != nil {
|
||||||
|
logrus.Errorf("Error writing to file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
close(done)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMarkdown(output OutputData) string {
|
||||||
|
markdown := "# " + output.Prefix + "\n\n"
|
||||||
|
|
||||||
|
for _, file := range output.Files {
|
||||||
|
markdown += fmt.Sprintf("## File: `%s`\n```%s\n%s\n```\n\n", file.Path, detectLanguage(file.Path), file.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
markdown += "# " + output.Suffix
|
||||||
|
return markdown
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectLanguage tries to infer code block language from file extension.
|
||||||
|
func detectLanguage(filename string) string {
|
||||||
|
if len(filename) < 3 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case len(filename) >= 3 && filename[len(filename)-3:] == ".go":
|
||||||
|
return "go"
|
||||||
|
case len(filename) >= 3 && filename[len(filename)-3:] == ".py":
|
||||||
|
return "python"
|
||||||
|
case len(filename) >= 2 && filename[len(filename)-2:] == ".c":
|
||||||
|
return "c"
|
||||||
|
case len(filename) >= 3 && filename[len(filename)-3:] == ".js":
|
||||||
|
return "javascript"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,45 @@
|
|||||||
package fileproc
|
package fileproc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"encoding/json"
|
||||||
"sync"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStartWriter(t *testing.T) {
|
func TestStartWriter_JSONOutput(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
outFile, err := os.CreateTemp("", "output.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func(name string) {
|
||||||
|
err := os.Remove(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}(outFile.Name())
|
||||||
|
|
||||||
writeCh := make(chan WriteRequest)
|
writeCh := make(chan WriteRequest)
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
|
|
||||||
go StartWriter(&buf, writeCh, done)
|
go StartWriter(outFile, writeCh, done, "json", "Prefix", "Suffix")
|
||||||
|
|
||||||
|
writeCh <- WriteRequest{Path: "file1.go", Content: "package main"}
|
||||||
|
writeCh <- WriteRequest{Path: "file2.py", Content: "def hello(): print('Hello')"}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
writeCh <- WriteRequest{Content: "Hello"}
|
|
||||||
writeCh <- WriteRequest{Content: " World"}
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
|
||||||
close(writeCh)
|
close(writeCh)
|
||||||
<-done
|
<-done
|
||||||
|
|
||||||
if buf.String() != "Hello World" {
|
data, err := os.ReadFile(outFile.Name())
|
||||||
t.Errorf("Expected 'Hello World', got '%s'", buf.String())
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var output OutputData
|
||||||
|
if err := json.Unmarshal(data, &output); err != nil {
|
||||||
|
t.Fatalf("JSON output is invalid: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output.Files) != 2 {
|
||||||
|
t.Errorf("Expected 2 files, got %d", len(output.Files))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
go.mod
12
go.mod
@@ -3,21 +3,18 @@ module github.com/ivuorinen/gibidify
|
|||||||
go 1.23
|
go 1.23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/boyter/gocodewalker v1.4.0
|
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
|
||||||
github.com/schollz/progressbar/v3 v3.18.0
|
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/viper v1.19.0
|
github.com/spf13/viper v1.19.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
@@ -27,11 +24,10 @@ require (
|
|||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
golang.org/x/crypto v0.21.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/net v0.23.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/term v0.28.0 // indirect
|
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
25
go.sum
25
go.sum
@@ -1,9 +1,3 @@
|
|||||||
github.com/boyter/gocodewalker v1.4.0 h1:fVmFeQxKpj5tlpjPcyTtJ96btgaHYd9yn6m+T/66et4=
|
|
||||||
github.com/boyter/gocodewalker v1.4.0/go.mod h1:hXG8xzR1uURS+99P5/3xh3uWHjaV2XfoMMmvPyhrCDg=
|
|
||||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
|
||||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
@@ -22,10 +16,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
@@ -33,16 +23,14 @@ github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
|
||||||
|
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
|
||||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
|
|
||||||
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
@@ -60,6 +48,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
|||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
@@ -72,15 +61,15 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
|||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||||
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
// TestIntegrationFullCLI simulates a full run of the CLI application using adaptive concurrency.
|
// TestIntegrationFullCLI simulates a full run of the CLI application using adaptive concurrency.
|
||||||
func TestIntegrationFullCLI(t *testing.T) {
|
func TestIntegrationFullCLI(t *testing.T) {
|
||||||
// Create a temporary source directory and populate it with test files.
|
// Create a temporary source directory and populate it with test files.
|
||||||
srcDir, err := ioutil.TempDir("", "gibidi_src")
|
srcDir, err := ioutil.TempDir("", "gibidify_src")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp source directory: %v", err)
|
t.Fatalf("Failed to create temp source directory: %v", err)
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ func TestIntegrationFullCLI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a temporary output file.
|
// Create a temporary output file.
|
||||||
outFile, err := ioutil.TempFile("", "gibidi_output.txt")
|
outFile, err := ioutil.TempFile("", "gibidify_output.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp output file: %v", err)
|
t.Fatalf("Failed to create temp output file: %v", err)
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ func TestIntegrationFullCLI(t *testing.T) {
|
|||||||
// TestIntegrationCancellation verifies that the application correctly cancels processing when the context times out.
|
// TestIntegrationCancellation verifies that the application correctly cancels processing when the context times out.
|
||||||
func TestIntegrationCancellation(t *testing.T) {
|
func TestIntegrationCancellation(t *testing.T) {
|
||||||
// Create a temporary source directory with many files to simulate a long-running process.
|
// Create a temporary source directory with many files to simulate a long-running process.
|
||||||
srcDir, err := ioutil.TempDir("", "gibidi_src_long")
|
srcDir, err := ioutil.TempDir("", "gibidify_src_long")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp source directory: %v", err)
|
t.Fatalf("Failed to create temp source directory: %v", err)
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ func TestIntegrationCancellation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a temporary output file.
|
// Create a temporary output file.
|
||||||
outFile, err := ioutil.TempFile("", "gibidi_output.txt")
|
outFile, err := ioutil.TempFile("", "gibidify_output.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create temp output file: %v", err)
|
t.Fatalf("Failed to create temp output file: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
107
main.go
107
main.go
@@ -7,12 +7,12 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ivuorinen/gibidify/config"
|
"github.com/ivuorinen/gibidify/config"
|
||||||
"github.com/ivuorinen/gibidify/fileproc"
|
"github.com/ivuorinen/gibidify/fileproc"
|
||||||
"github.com/schollz/progressbar/v3"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ var (
|
|||||||
prefix string
|
prefix string
|
||||||
suffix string
|
suffix string
|
||||||
concurrency int
|
concurrency int
|
||||||
|
format string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -29,6 +30,7 @@ func init() {
|
|||||||
flag.StringVar(&destination, "destination", "", "Output file to write aggregated code")
|
flag.StringVar(&destination, "destination", "", "Output file to write aggregated code")
|
||||||
flag.StringVar(&prefix, "prefix", "", "Text to add at the beginning of the output file")
|
flag.StringVar(&prefix, "prefix", "", "Text to add at the beginning of the output file")
|
||||||
flag.StringVar(&suffix, "suffix", "", "Text to add at the end of the output file")
|
flag.StringVar(&suffix, "suffix", "", "Text to add at the end of the output file")
|
||||||
|
flag.StringVar(&format, "format", "json", "Output format (json, markdown, yaml)")
|
||||||
flag.IntVar(&concurrency, "concurrency", runtime.NumCPU(), "Number of concurrent workers (default: number of CPU cores)")
|
flag.IntVar(&concurrency, "concurrency", runtime.NumCPU(), "Number of concurrent workers (default: number of CPU cores)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,116 +38,97 @@ func init() {
|
|||||||
func Run(ctx context.Context) error {
|
func Run(ctx context.Context) error {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if sourceDir == "" || destination == "" {
|
// We need at least a source directory
|
||||||
return fmt.Errorf(
|
if sourceDir == "" {
|
||||||
"usage: gibidify " +
|
return fmt.Errorf("usage: gibidify -source <source_directory> [--destination <output_file>] [--format=json|yaml|markdown] ")
|
||||||
"-source <source_directory> " +
|
}
|
||||||
"-destination <output_file> " +
|
|
||||||
"[--prefix=\"...\"] " +
|
// If destination is not specified, auto-generate it using the base name of sourceDir + "." + format
|
||||||
"[--suffix=\"...\"] " +
|
if destination == "" {
|
||||||
"[-concurrency=<num>]",
|
absRoot, err := filepath.Abs(sourceDir)
|
||||||
)
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get absolute path for %s: %w", sourceDir, err)
|
||||||
|
}
|
||||||
|
baseName := filepath.Base(absRoot)
|
||||||
|
// If sourceDir ends with a slash, baseName might be "." so handle that case as needed
|
||||||
|
if baseName == "." || baseName == "" {
|
||||||
|
baseName = "output"
|
||||||
|
}
|
||||||
|
destination = baseName + "." + format
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load configuration using Viper.
|
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
|
|
||||||
logrus.Infof(
|
logrus.Infof("Starting gibidify. Format: %s, Source: %s, Destination: %s, Workers: %d", format, sourceDir, destination, concurrency)
|
||||||
"Starting gibidify. Source: %s, Destination: %s, Workers: %d",
|
|
||||||
sourceDir,
|
|
||||||
destination,
|
|
||||||
concurrency,
|
|
||||||
)
|
|
||||||
|
|
||||||
// 1. Collect files using the file walker (ProdWalker).
|
// Collect files
|
||||||
files, err := fileproc.CollectFiles(sourceDir)
|
files, err := fileproc.CollectFiles(sourceDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error collecting files: %w", err)
|
return fmt.Errorf("error collecting files: %w", err)
|
||||||
}
|
}
|
||||||
logrus.Infof("Found %d files to process", len(files))
|
logrus.Infof("Found %d files to process", len(files))
|
||||||
|
|
||||||
// 2. Open the destination file and write the header.
|
// Open output file
|
||||||
outFile, err := os.Create(destination)
|
outFile, err := os.Create(destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create output file %s: %w", destination, err)
|
return fmt.Errorf("failed to create output file %s: %w", destination, err)
|
||||||
}
|
}
|
||||||
defer func(outFile *os.File) {
|
defer func(outFile *os.File) {
|
||||||
err := outFile.Close()
|
if err := outFile.Close(); err != nil {
|
||||||
if err != nil {
|
logrus.Errorf("Error closing output file: %v", err)
|
||||||
logrus.Errorf("failed to close output file %s: %v", destination, err)
|
|
||||||
}
|
}
|
||||||
}(outFile)
|
}(outFile)
|
||||||
|
|
||||||
header := prefix + "\n" +
|
// Create channels
|
||||||
"The following text is a Git repository with code. " +
|
|
||||||
"The structure of the text are sections that begin with ----, " +
|
|
||||||
"followed by a single line containing the file path and file name, " +
|
|
||||||
"followed by a variable amount of lines containing the file contents. " +
|
|
||||||
"The text representing the Git repository ends when the symbols --END-- are encountered.\n"
|
|
||||||
|
|
||||||
if _, err := outFile.WriteString(header); err != nil {
|
|
||||||
return fmt.Errorf("failed to write header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Set up channels and a worker pool for processing files.
|
|
||||||
fileCh := make(chan string)
|
fileCh := make(chan string)
|
||||||
writeCh := make(chan fileproc.WriteRequest)
|
writeCh := make(chan fileproc.WriteRequest)
|
||||||
|
writerDone := make(chan struct{})
|
||||||
|
|
||||||
|
// Start writer goroutine
|
||||||
|
go fileproc.StartWriter(outFile, writeCh, writerDone, format, prefix, suffix)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
// Start the writer goroutine.
|
// Start worker goroutines with context cancellation
|
||||||
writerDone := make(chan struct{})
|
|
||||||
go fileproc.StartWriter(outFile, writeCh, writerDone)
|
|
||||||
|
|
||||||
// Start worker goroutines.
|
|
||||||
for i := 0; i < concurrency; i++ {
|
for i := 0; i < concurrency; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case fp, ok := <-fileCh:
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case filePath, ok := <-fileCh:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Process the file.
|
// Pass sourceDir to ProcessFile so it knows the 'root'
|
||||||
fileproc.ProcessFile(fp, writeCh, nil)
|
absRoot, err := filepath.Abs(sourceDir)
|
||||||
case <-ctx.Done():
|
if err != nil {
|
||||||
return
|
logrus.Errorf("Failed to get absolute path for %s: %v", sourceDir, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileproc.ProcessFile(filePath, writeCh, absRoot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feed file paths to the worker pool with progress bar feedback.
|
// Feed files to worker pool while checking for cancellation
|
||||||
bar := progressbar.Default(int64(len(files)))
|
|
||||||
loop:
|
|
||||||
for _, fp := range files {
|
for _, fp := range files {
|
||||||
select {
|
select {
|
||||||
case fileCh <- fp:
|
|
||||||
_ = bar.Add(1)
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
close(fileCh)
|
close(fileCh)
|
||||||
break loop
|
return ctx.Err()
|
||||||
|
case fileCh <- fp:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(fileCh)
|
close(fileCh)
|
||||||
|
|
||||||
// Wait for all workers to finish.
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(writeCh)
|
close(writeCh)
|
||||||
<-writerDone
|
<-writerDone
|
||||||
|
|
||||||
// Check for context cancellation.
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Write footer.
|
|
||||||
footer := "--END--\n" + suffix
|
|
||||||
if _, err := outFile.WriteString(footer); err != nil {
|
|
||||||
return fmt.Errorf("failed to write footer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Infof("Processing completed. Output saved to %s", destination)
|
logrus.Infof("Processing completed. Output saved to %s", destination)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user