Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add labels option for single file #23

Merged
merged 11 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion cmd/tgcom/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ import (
func main() {
fileFlag := flag.String("file", "", "The file to process")
lineFlag := flag.String("line", "", "The line number or range to modify (e.g., 4 or 10-20)")
startLabelFlag := flag.String("start-label", "", "The start label for a section")
endLabelFlag := flag.String("end-label", "", "The end label for a section")
actionFlag := flag.String("action", "", "can be comment, uncomment or toggle")
dryRunFlag := flag.Bool("dry-run", false, "Perform a dry run without modifying the files")

flag.Parse()

filename := *fileFlag
lineStr := *lineFlag
startLabel := *startLabelFlag
endLabel := *endLabelFlag
action := *actionFlag
dryRun := *dryRunFlag
var modFunc func(string, string) string
Expand All @@ -46,6 +50,19 @@ func main() {
return
}

if startLabel == "" && endLabel != "" {
fmt.Println("Error: 'startLabel' is required when 'endLabel' is provided.")
return
} else if startLabel != "" && endLabel == "" {
fmt.Println("Error: 'endLabel' is required when 'startLabel' is provided.")
return
}

if startLabel != "" && lineStr != "" {
fmt.Println("Error: Specify either line number/range OR label, not both.")
return
}

if strings.Contains(filename, ",") {
if err := file.ProcessMultipleFiles(filename, dryRun); err != nil {
fmt.Println("Error processing files:", err)
Expand All @@ -60,7 +77,8 @@ func main() {
filename = parts[0]
lineStr = parts[1]
}
if err := file.ProcessSingleFile(filename, lineStr, modFunc, dryRun); err != nil {

if err := file.ProcessSingleFile(filename, lineStr, startLabel, endLabel, modFunc, dryRun); err != nil {
fmt.Println("Error processing file:", err)
}
}
Expand Down
76 changes: 54 additions & 22 deletions internal/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
)

// ProcessFile processes a single file.
func ProcessFile(filename string, lineNum [2]int, commentChars string, modFunc func(string, string) string, dryRun bool) error {
func ProcessFile(filename string, lineNum [2]int, startLabel, endLabel string, commentChars string, modFunc func(string, string) string, dryRun bool) error {
inputFile, err := os.Open(filename)
if err != nil {
return err
Expand All @@ -24,7 +24,7 @@ func ProcessFile(filename string, lineNum [2]int, commentChars string, modFunc f

if dryRun {
// Perform a dry run: print the changes instead of writing them
return printChanges(inputFile, lineNum, commentChars, modFunc)
return printChanges(inputFile, lineNum, startLabel, endLabel, commentChars, modFunc)
}

// Create a backup of the original file
Expand All @@ -49,7 +49,7 @@ func ProcessFile(filename string, lineNum [2]int, commentChars string, modFunc f
return err
}

err = writeChanges(inputFile, tmpFile, lineNum, commentChars, modFunc)
err = writeChanges(inputFile, tmpFile, lineNum, startLabel, endLabel, commentChars, modFunc)
if err != nil {
restoreBackup(filename, backupFilename)
tmpFile.Close()
Expand Down Expand Up @@ -81,56 +81,85 @@ func ProcessFile(filename string, lineNum [2]int, commentChars string, modFunc f

return nil
}
func shouldProcessLine(currentLine int, lineNum [2]int, startLabel, endLabel string, inSection bool) bool {
if startLabel != "" && endLabel != "" {
return inSection
}
return lineNum[0] <= currentLine && currentLine <= lineNum[1]
}

func writeChanges(inputFile *os.File, outputFile *os.File, lineNum [2]int, commentChars string, modFunc func(string, string) string) error {
func writeChanges(inputFile *os.File, outputFile *os.File, lineNum [2]int, startLabel, endLabel string, commentChars string, modFunc func(string, string) string) error {
scanner := bufio.NewScanner(inputFile)
writer := bufio.NewWriter(outputFile)
currentLine := 1
inSection := false
var err error

for scanner.Scan() {
lineContent := scanner.Text()
if lineNum[0] <= currentLine && currentLine <= lineNum[1] {

if strings.Contains(lineContent, endLabel) {
inSection = false
}

if shouldProcessLine(currentLine, lineNum, startLabel, endLabel, inSection) {
lineContent = modFunc(lineContent, commentChars)
}

if _, err := writer.WriteString(lineContent + "\n"); err != nil {
if strings.Contains(lineContent, startLabel) {
inSection = true
}

if _, err = writer.WriteString(lineContent + "\n"); err != nil {
return err
}

currentLine++
}

if lineNum[1] > currentLine {
if lineNum[1] > currentLine && startLabel == "" && endLabel == "" {
return errors.New("line number is out of range")
}

if err := scanner.Err(); err != nil {
if err = scanner.Err(); err != nil {
Comment on lines +91 to +124
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation for writing changes based on labels is correct. Consider refactoring to reduce code duplication between writeChanges and printChanges.

+ // Use a helper function to determine if a line should be processed.
+ func shouldProcessLine(lineContent string, currentLine int, lineNum [2]int, startLabel, endLabel string) bool {
+     if startLabel != "" && endLabel != "" {
+         return strings.Contains(lineContent, startLabel) || strings.Contains(lineContent, endLabel)
+     }
+     return lineNum[0] <= currentLine && currentLine <= lineNum[1]
+ }

Committable suggestion was skipped due to low confidence.

return err
}

return writer.Flush()
}

func printChanges(inputFile *os.File, lineNum [2]int, commentChars string, modFunc func(string, string) string) error {
func printChanges(inputFile *os.File, lineNum [2]int, startLabel, endLabel, commentChars string, modFunc func(string, string) string) error {
scanner := bufio.NewScanner(inputFile)
currentLine := 1
inSection := false

for scanner.Scan() {

lineContent := scanner.Text()
if lineNum[0] <= currentLine && currentLine <= lineNum[1] {

if strings.Contains(lineContent, endLabel) {
inSection = false
}

if shouldProcessLine(currentLine, lineNum, startLabel, endLabel, inSection) {
modified := modFunc(lineContent, commentChars)
fmt.Printf("%d: %s -> %s\n", currentLine, lineContent, modified)
}

if strings.Contains(lineContent, startLabel) {
inSection = true
}

currentLine++
}

if lineNum[1] > currentLine {
if lineNum[1] > currentLine && startLabel == "" && endLabel == "" {
Comment on lines +131 to +156
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function for printing changes during a dry run is implemented correctly. Ensure to handle potential errors from the scanner to prevent silent failures.

+ if err := scanner.Err(); err != nil {
+     log.Printf("Failed to scan file: %v", err)
+ }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func printChanges(inputFile *os.File, lineNum [2]int, startLabel, endLabel, commentChars string, modFunc func(string, string) string) error {
scanner := bufio.NewScanner(inputFile)
currentLine := 1
inSection := false
for scanner.Scan() {
lineContent := scanner.Text()
if lineNum[0] <= currentLine && currentLine <= lineNum[1] {
if strings.Contains(lineContent, endLabel) {
inSection = false
}
if shouldProcessLine(currentLine, lineNum, startLabel, endLabel, inSection) {
modified := modFunc(lineContent, commentChars)
fmt.Printf("%d: %s -> %s\n", currentLine, lineContent, modified)
}
if strings.Contains(lineContent, startLabel) {
inSection = true
}
currentLine++
}
if lineNum[1] > currentLine {
if lineNum[1] > currentLine && startLabel == "" && endLabel == "" {
func printChanges(inputFile *os.File, lineNum [2]int, startLabel, endLabel, commentChars string, modFunc func(string, string) string) error {
scanner := bufio.NewScanner(inputFile)
currentLine := 1
inSection := false
for scanner.Scan() {
lineContent := scanner.Text()
if strings.Contains(lineContent, endLabel) {
inSection = false
}
if shouldProcessLine(currentLine, lineNum, startLabel, endLabel, inSection) {
modified := modFunc(lineContent, commentChars)
fmt.Printf("%d: %s -> %s\n", currentLine, lineContent, modified)
}
if strings.Contains(lineContent, startLabel) {
inSection = true
}
currentLine++
}
if err := scanner.Err(); err != nil {
log.Printf("Failed to scan file: %v", err)
}
if lineNum[1] > currentLine && startLabel == "" && endLabel == "" {
Tools
golangci-lint

139-139: File is not goimports-ed (goimports)


136-136: unnecessary leading newline (whitespace)

return errors.New("line number is out of range")
}

return scanner.Err()
}

func createBackup(filename, backupFilename string) error {
inputFile, err := os.Open(filename)
if err != nil {
Expand Down Expand Up @@ -160,19 +189,22 @@ func restoreBackup(filename, backupFilename string) {
}

// ProcessSingleFile processes a single file specified by filename.
func ProcessSingleFile(filename string, lineStr string, modFunc func(string, string) string, dryRun bool) error {
startLine, endLine, err := extractLines(lineStr)
func ProcessSingleFile(filename string, lineStr, startLabel, endLabel string, modFunc func(string, string) string, dryRun bool) error {
commentChars, err := selectCommentChars(filename)
if err != nil {
return err
return fmt.Errorf("error selecting comment characters: %w", err)
}
lineNum := [2]int{startLine, endLine}

commentChars, err := selectCommentChars(filename)
if err != nil {
return err
var lineNum [2]int
if startLabel == "" && endLabel == "" {
startLine, endLine, err := extractLines(lineStr)
if err != nil {
return fmt.Errorf("error extracting line numbers: %w", err)
}
lineNum = [2]int{startLine, endLine}
}

return ProcessFile(filename, lineNum, commentChars, modFunc, dryRun)
return ProcessFile(filename, lineNum, startLabel, endLabel, commentChars, modFunc, dryRun)
Comment on lines +192 to +207
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updates to handle labels and improve error messages enhance the function's usability and robustness. Consider using consistent error handling across all file operations.

+ if err := os.Remove(tmpFilename); err != nil {
+     log.Printf("Failed to remove temporary file: %v", err)
+ }

Committable suggestion was skipped due to low confidence.

}

// ProcessMultipleFiles processes multiple files specified by comma-separated filenames.
Expand All @@ -199,16 +231,16 @@ func processFileWithLines(fileInfo string, dryRun bool) error {
file, lineString := sub[0], sub[1]
startLine, endLine, err := extractLines(lineString)
if err != nil {
return err
return fmt.Errorf("error extracting line numbers: %w", err)
}
lineNum := [2]int{startLine, endLine}

commentChars, err := selectCommentChars(file)
if err != nil {
return err
return fmt.Errorf("error selecting comment characters: %w", err)
}

return ProcessFile(file, lineNum, commentChars, comment.ToggleComments, dryRun)
return ProcessFile(file, lineNum, "", "", commentChars, comment.ToggleComments, dryRun)
}

func extractLines(lineStr string) (startLine, endLine int, err error) {
Expand Down
29 changes: 21 additions & 8 deletions internal/file/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestProcessFile(t *testing.T) {
defer cleanup()

lineNum := [2]int{2, 2}
if err := ProcessFile(tmpFile.Name(), lineNum, commentChars, modFunc, dryRun); err != nil {
if err := ProcessFile(tmpFile.Name(), lineNum, "", "", commentChars, modFunc, dryRun); err != nil {
t.Fatalf("ProcessFile failed: %v", err)
}

Expand All @@ -33,7 +33,7 @@ func TestProcessFile(t *testing.T) {
t.Run("NonExistingFile", func(t *testing.T) {
nonExistingFile := "non_existing_file.txt"
lineNum := [2]int{2, 2}
err := ProcessFile(nonExistingFile, lineNum, commentChars, modFunc, dryRun)
err := ProcessFile(nonExistingFile, lineNum, "", "", commentChars, modFunc, dryRun)
if err == nil {
t.Fatalf("Expected error for non-existing file, got nil")
}
Expand All @@ -44,7 +44,7 @@ func TestProcessFile(t *testing.T) {
defer cleanup()

lineNum := [2]int{2, 2}
err := ProcessFile(emptyFile.Name(), lineNum, commentChars, modFunc, dryRun)
err := ProcessFile(emptyFile.Name(), lineNum, "", "", commentChars, modFunc, dryRun)
if err == nil {
t.Fatalf("Expected error for empty file, got nil")
}
Expand All @@ -56,7 +56,7 @@ func TestProcessFile(t *testing.T) {
defer cleanup()

lineNum := [2]int{1, 3}
if err := ProcessFile(tmpFile.Name(), lineNum, commentChars, comment.Comment, dryRun); err != nil {
if err := ProcessFile(tmpFile.Name(), lineNum, "", "", commentChars, comment.Comment, dryRun); err != nil {
t.Fatalf("ProcessFile failed: %v", err)
}

Expand All @@ -71,7 +71,7 @@ func TestProcessFile(t *testing.T) {
}

lineNum := [2]int{3, 10}
if err := ProcessFile(tmpFile.Name(), lineNum, commentChars, modFunc, dryRun); err == nil {
if err := ProcessFile(tmpFile.Name(), lineNum, "", "", commentChars, modFunc, dryRun); err == nil {
t.Fatal("Expected error, got nil")
}

Expand Down Expand Up @@ -99,7 +99,7 @@ func TestProcessFile(t *testing.T) {
r, w, _ := os.Pipe()
os.Stdout = w

if err := ProcessFile(tmpFile.Name(), lineNum, commentChars, modFunc, dryRun); err != nil {
if err := ProcessFile(tmpFile.Name(), lineNum, "", "", commentChars, modFunc, dryRun); err != nil {
t.Fatalf("ProcessFile failed: %v", err)
}

Expand All @@ -111,11 +111,24 @@ func TestProcessFile(t *testing.T) {
os.Stdout = old

got := buf.String()
expected := "2: Line 2 -> +++ Line 2"
expected := "2: Line 2 -> +++ Line 2\n"
if got != expected {
t.Errorf("Dry run log does not match.\nExpected: %s\nGot: %s", expected, got)
}
})
t.Run("Labels", func(t *testing.T) {
tmpFile, cleanup := createTempFile(t, "Line 0\nStart Label\nLine 1\nLine 2\nLine 3\nEnd Label\nLine 5")
defer cleanup()

startLabel := "Start Label"
endLabel := "End Label"
if err := ProcessFile(tmpFile.Name(), [2]int{}, startLabel, endLabel, commentChars, modFunc, dryRun); err != nil {
t.Fatalf("ProcessFile failed: %v", err)
}

expected := "Line 0\nStart Label\n+++ Line 1\n+++ Line 2\n+++ Line 3\nEnd Label\nLine 5\n"
assertFileContent(t, tmpFile.Name(), expected)
})
}

func TestProcessSingleFile(t *testing.T) {
Expand Down Expand Up @@ -164,7 +177,7 @@ line 12
return commentChars + " " + line
}

err := ProcessSingleFile(tt.filename, tt.lines, modFunc, dryRun)
err := ProcessSingleFile(tt.filename, tt.lines, "", "", modFunc, dryRun)
if (err != nil) != tt.shouldErr {
t.Errorf("ProcessSingleFile(%s, %s) error = %v", tt.filename, tt.lines, err)
}
Expand Down
Loading