Started working on multiple filename arguments; prefix each line with filename containing the line; mostly indentation changes
This commit is contained in:
		
							
								
								
									
										290
									
								
								cmd/main.go
									
									
									
									
									
								
							
							
						
						
									
										290
									
								
								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 = regComp.FindAllSubmatch(test_str) | ||||
| 			} | ||||
| 		} | ||||
| 		matchIndices := make([]reg.Match, 0) | ||||
| 		if matchNumFlagEnabled { | ||||
| 			tmp, err := regComp.FindNthMatch(test_str, *matchNum) | ||||
| 			if err == nil { | ||||
| 				matchIndices = append(matchIndices, tmp) | ||||
| 			} | ||||
| 		} 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) | ||||
| 				} | ||||
| 			} | ||||
| 			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)...) | ||||
|  | ||||
| 		} | ||||
| 		// 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 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 *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 !inMatchIndex { | ||||
| 					fmt.Fprintf(out, "%c", test_str_runes[i]) | ||||
| 				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)...) | ||||
|  | ||||
| 			} | ||||
| 			// 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 | ||||
| 				} | ||||
| 			} | ||||
| 		} 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 | ||||
|  | ||||
| 			// 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]) | ||||
| 					} | ||||
| 				} | ||||
| 			} 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() | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user