From 34149980a4806f6cdef7d4cd9e8eeb5db607c448 Mon Sep 17 00:00:00 2001
From: Aadhavan Srinivasan <aadhavan@twomorecents.org>
Date: Thu, 13 Mar 2025 12:11:48 -0400
Subject: [PATCH] Started working on multiple filename arguments; prefix each
 line with filename containing the line; mostly indentation changes

---
 cmd/main.go | 284 ++++++++++++++++++++++++++++------------------------
 1 file changed, 152 insertions(+), 132 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go
index d3d7be5..2b1f7cb 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"slices"
 
 	"github.com/fatih/color"
 
@@ -25,6 +26,7 @@ func main() {
 	multiLineFlag := flag.Bool("t", false, "Multi-line mode. Treats newline just like any character.")
 	printMatchesFlag := flag.Bool("p", false, "Prints start and end index of each match. Can only be used with '-t' for multi-line mode.")
 	caseInsensitiveFlag := flag.Bool("i", false, "Case-insensitive. Disregard the case of all characters.")
+	recursiveFlag := flag.Bool("r", false, "Recursively search all files in the given directory.")
 	matchNum := flag.Int("m", 0, "Print the match with the given index. Eg. -m 3 prints the third match.")
 	substituteText := flag.String("s", "", "Substitute the contents of each match with the given string. Overrides -o and -v")
 	flag.Parse()
@@ -64,21 +66,30 @@ func main() {
 	// 2. Build NFA from postfix representation (Thompson's algorithm)
 	// 3. Run the string against the NFA
 
-	if len(flag.Args()) < 1 || len(flag.Args()) > 2 { // flag.Args() also strips out program name
+	if len(flag.Args()) < 1 { // flag.Args() also strips out program name
+		fmt.Println("ERROR: Missing cmdline args")
+		os.Exit(22)
+	}
+	if *recursiveFlag && len(flag.Args()) < 2 { // File/Directory must be provided with '-r'
 		fmt.Println("ERROR: Missing cmdline args")
 		os.Exit(22)
 	}
 	var re string
 	re = flag.Args()[0]
-	var inputFile *os.File
+	var inputFiles []*os.File
 	if len(flag.Args()) == 1 || flag.Args()[1] == "-" { // Either no file argument, or file argument is "-"
-		inputFile = os.Stdin
+		if !slices.Contains(inputFiles, os.Stdin) {
+			inputFiles = append(inputFiles, os.Stdin) // os.Stdin cannot be entered more than once into the file list
+		}
 	} else {
-		var err error
-		inputFile, err = os.Open(flag.Args()[1])
-		if err != nil {
-			fmt.Printf("%s: No such file or directory\n", flag.Args()[1])
-			os.Exit(2)
+		inputFilenames := flag.Args()[1:]
+		for _, inputFilename := range inputFilenames {
+			inputFile, err := os.Open(inputFilename)
+			if err != nil {
+				fmt.Printf("%s: No such file or directory\n", flag.Args()[1])
+				os.Exit(2)
+			}
+			inputFiles = append(inputFiles, inputFile)
 		}
 	}
 
@@ -86,160 +97,169 @@ func main() {
 	var err error
 	var linesRead bool // Whether or not we have read the lines in the file
 	lineNum := 0       // Current line number
-	// Create reader for stdin and writer for stdout
-	reader := bufio.NewReader(inputFile)
+	// Create writer for stdout
 	out := bufio.NewWriter(os.Stdout)
-
+	// Compile regex
 	regComp, err := reg.Compile(re, flagsToCompile...)
 	if err != nil {
 		fmt.Println(err)
 		return
 	}
-	for true {
-		if linesRead {
-			break
-		}
-		if !(*multiLineFlag) {
-			// Read every string from stdin until we encounter an error. If the error isn't EOF, panic.
-			test_str, err = reader.ReadString('\n')
-			lineNum++
-			if err != nil {
+
+	for _, inputFile := range inputFiles {
+		reader := bufio.NewReader(inputFile)
+		linesRead = false
+		for true {
+			if !(*lineFlag) && (!*onlyFlag) {
+				color.New(color.FgMagenta).Fprintf(out, "%s: ", inputFile.Name()) // The filename should be printed for every line, _except_ if we're not printing every line. This is the case with the lineFlag and onlyFlag
+			}
+			if linesRead {
+				break
+			}
+			if !(*multiLineFlag) {
+				// Read every string from stdin until we encounter an error. If the error isn't EOF, panic.
+				test_str, err = reader.ReadString('\n')
+				lineNum++
+				if err != nil {
+					if err == io.EOF {
+						linesRead = true
+					} else {
+						panic(err)
+					}
+				}
+				if len(test_str) > 0 && test_str[len(test_str)-1] == '\n' {
+					test_str = test_str[:len(test_str)-1]
+				}
+			} else {
+				// Multi-line mode - read every line of input into a temp. string.
+				// test_str will contain all lines of input (including newline characters)
+				// as one string.
+				var temp string
+				for temp, err = reader.ReadString('\n'); err == nil; temp, err = reader.ReadString('\n') {
+					test_str += temp
+				}
+				// Assuming err != nil
 				if err == io.EOF {
+					if len(temp) > 0 {
+						test_str += temp // Add the last line (if it is non-empty)
+					}
 					linesRead = true
 				} else {
 					panic(err)
 				}
 			}
-			if len(test_str) > 0 && test_str[len(test_str)-1] == '\n' {
-				test_str = test_str[:len(test_str)-1]
-			}
-		} else {
-			// Multi-line mode - read every line of input into a temp. string.
-			// test_str will contain all lines of input (including newline characters)
-			// as one string.
-			var temp string
-			for temp, err = reader.ReadString('\n'); err == nil; temp, err = reader.ReadString('\n') {
-				test_str += temp
-			}
-			// Assuming err != nil
-			if err == io.EOF {
-				if len(temp) > 0 {
-					test_str += temp // Add the last line (if it is non-empty)
+			matchIndices := make([]reg.Match, 0)
+			if matchNumFlagEnabled {
+				tmp, err := regComp.FindNthMatch(test_str, *matchNum)
+				if err == nil {
+					matchIndices = append(matchIndices, tmp)
 				}
-				linesRead = true
 			} else {
-				panic(err)
-			}
-		}
-		matchIndices := make([]reg.Match, 0)
-		if matchNumFlagEnabled {
-			tmp, err := regComp.FindNthMatch(test_str, *matchNum)
-			if err == nil {
-				matchIndices = append(matchIndices, tmp)
+				matchIndices = regComp.FindAllSubmatch(test_str)
 			}
-		} else {
-			matchIndices = regComp.FindAllSubmatch(test_str)
-		}
 
-		test_str_runes := []rune(test_str) // Converting to runes preserves unicode characters
+			test_str_runes := []rune(test_str) // Converting to runes preserves unicode characters
 
-		if *printMatchesFlag {
-			// if we are in single line mode, print the line on which
-			// the matches occur
-			if len(matchIndices) > 0 {
-				if !(*multiLineFlag) {
-					fmt.Fprintf(out, "Line %d:\n", lineNum)
-				}
-				for _, m := range matchIndices {
-					fmt.Fprintf(out, "%s\n", m.String())
-				}
-				err := out.Flush()
-				if err != nil {
-					panic(err)
+			if *printMatchesFlag {
+				// if we are in single line mode, print the line on which
+				// the matches occur
+				if len(matchIndices) > 0 {
+					if !(*multiLineFlag) {
+						fmt.Fprintf(out, "Line %d:\n", lineNum)
+					}
+					for _, m := range matchIndices {
+						fmt.Fprintf(out, "%s\n", m.String())
+					}
+					err := out.Flush()
+					if err != nil {
+						panic(err)
+					}
 				}
+				continue
 			}
-			continue
-		}
-		// Decompose the array of matchIndex structs into a flat unique array of ints - if matchIndex is {4,7}, flat array will contain 4,5,6
-		// This should make checking O(1) instead of O(n)
-		indicesToPrint := new_uniq_arr[int]()
-		for _, idx := range matchIndices {
-			indicesToPrint.add(genRange(idx[0].StartIdx, idx[0].EndIdx)...)
-		}
-		// If we are inverting, then we should print the indices which _didn't_ match
-		// in color.
-		if *invertFlag {
-			oldIndices := indicesToPrint.values()
-			indicesToPrint = new_uniq_arr[int]()
-			// Explanation:
-			// Find all numbers from 0 to len(test_str_runes) that are NOT in oldIndices.
-			// These are the values we want to print, now that we have inverted the match.
-			// Re-initialize indicesToPrint and add all of these values to it.
-			indicesToPrint.add(setDifference(genRange(0, len(test_str_runes)), oldIndices)...)
+			// Decompose the array of matchIndex structs into a flat unique array of ints - if matchIndex is {4,7}, flat array will contain 4,5,6
+			// This should make checking O(1) instead of O(n)
+			indicesToPrint := new_uniq_arr[int]()
+			for _, idx := range matchIndices {
+				indicesToPrint.add(genRange(idx[0].StartIdx, idx[0].EndIdx)...)
+			}
+			// If we are inverting, then we should print the indices which _didn't_ match
+			// in color.
+			if *invertFlag {
+				oldIndices := indicesToPrint.values()
+				indicesToPrint = new_uniq_arr[int]()
+				// Explanation:
+				// Find all numbers from 0 to len(test_str_runes) that are NOT in oldIndices.
+				// These are the values we want to print, now that we have inverted the match.
+				// Re-initialize indicesToPrint and add all of these values to it.
+				indicesToPrint.add(setDifference(genRange(0, len(test_str_runes)), oldIndices)...)
 
-		}
-		// If lineFlag is enabled, we should only print something if:
-		// 		a. We are not inverting, and have at least one match on the current line
-		// 		OR
-		// 		b. We are inverting, and have no matches at all on the current line.
-		// This checks for the inverse, and continues if it is true.
-		if *lineFlag {
-			if !(*invertFlag) && len(matchIndices) == 0 || *invertFlag && len(matchIndices) > 0 {
-				continue
 			}
-		}
+			// If lineFlag is enabled, we should only print something if:
+			// 		a. We are not inverting, and have at least one match on the current line
+			// 		OR
+			// 		b. We are inverting, and have no matches at all on the current line.
+			// This checks for the inverse, and continues if it is true.
+			if *lineFlag {
+				if !(*invertFlag) && len(matchIndices) == 0 || *invertFlag && len(matchIndices) > 0 {
+					continue
+				} else {
+					color.New(color.FgMagenta).Fprintf(out, "%s: ", inputFile.Name()) // Print filename
+				}
+			}
 
-		// If we are substituting, we need a different behavior, as follows:
-		// For every character in the test string:
-		// 		1. Check if the index is the start of any matchIndex
-		// 		2. If so, print the substitute text, and set our index to
-		//			the corresponding end index.
-		// 		3. If not, just print the character.
-		if substituteFlagEnabled {
-			for i := range test_str_runes {
-				inMatchIndex := false
-				for _, m := range matchIndices {
-					if i == m[0].StartIdx {
-						fmt.Fprintf(out, "%s", *substituteText)
-						i = m[0].EndIdx
-						inMatchIndex = true
-						break
+			// If we are substituting, we need a different behavior, as follows:
+			// For every character in the test string:
+			// 		1. Check if the index is the start of any matchIndex
+			// 		2. If so, print the substitute text, and set our index to
+			//			the corresponding end index.
+			// 		3. If not, just print the character.
+			if substituteFlagEnabled {
+				for i := range test_str_runes {
+					inMatchIndex := false
+					for _, m := range matchIndices {
+						if i == m[0].StartIdx {
+							fmt.Fprintf(out, "%s", *substituteText)
+							i = m[0].EndIdx
+							inMatchIndex = true
+							break
+						}
+					}
+					if !inMatchIndex {
+						fmt.Fprintf(out, "%c", test_str_runes[i])
 					}
 				}
-				if !inMatchIndex {
-					fmt.Fprintf(out, "%c", test_str_runes[i])
-				}
-			}
-		} else {
-			for i, c := range test_str_runes {
-				if indicesToPrint.contains(i) {
-					color.New(color.FgRed).Fprintf(out, "%c", c)
-					// Newline after every match - only if -o is enabled and -v is disabled.
-					if *onlyFlag && !(*invertFlag) {
-						for matchIdxNum, idx := range matchIndices {
-							if matchIdxNum < len(matchIndices)-1 { // Only print a newline afte printing a match, if there are multiple matches on the line, and we aren't on the last one. This is because the newline that gets added at the end will take care of that.
-								if i+1 == idx[0].EndIdx { // End index is one more than last index of match
-									fmt.Fprintf(out, "\n")
-									break
+			} else {
+				for i, c := range test_str_runes {
+					if indicesToPrint.contains(i) {
+						color.New(color.FgRed, color.Bold).Fprintf(out, "%c", c)
+						// Newline after every match - only if -o is enabled and -v is disabled.
+						if *onlyFlag && !(*invertFlag) {
+							for matchIdxNum, idx := range matchIndices {
+								if matchIdxNum < len(matchIndices)-1 { // Only print a newline afte printing a match, if there are multiple matches on the line, and we aren't on the last one. This is because the newline that gets added at the end will take care of that.
+									if i+1 == idx[0].EndIdx { // End index is one more than last index of match
+										fmt.Fprintf(out, "\n")
+										break
+									}
 								}
 							}
 						}
-					}
-				} else {
-					if !(*onlyFlag) {
-						fmt.Fprintf(out, "%c", c)
+					} else {
+						if !(*onlyFlag) {
+							fmt.Fprintf(out, "%c", c)
+						}
 					}
 				}
 			}
-		}
-		err = out.Flush()
-		if err != nil {
-			panic(err)
-		}
-		// If the last character in the string wasn't a newline, AND we either have don't -o set or we do (and we've matched something), then print a newline
-		if (len(test_str_runes) > 0 && test_str_runes[len(test_str_runes)-1] != '\n') &&
-			(!*onlyFlag || indicesToPrint.len() > 0) {
-			fmt.Println()
+			err = out.Flush()
+			if err != nil {
+				panic(err)
+			}
+			// If the last character in the string wasn't a newline, AND we either have don't -o set or we do (and we've matched something), then print a newline
+			if (len(test_str_runes) > 0 && test_str_runes[len(test_str_runes)-1] != '\n') &&
+				(!*onlyFlag || indicesToPrint.len() > 0) {
+				fmt.Println()
+			}
 		}
 	}
 }