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" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"slices" | ||||||
|  |  | ||||||
| 	"github.com/fatih/color" | 	"github.com/fatih/color" | ||||||
|  |  | ||||||
| @@ -25,6 +26,7 @@ func main() { | |||||||
| 	multiLineFlag := flag.Bool("t", false, "Multi-line mode. Treats newline just like any character.") | 	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.") | 	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.") | 	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.") | 	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") | 	substituteText := flag.String("s", "", "Substitute the contents of each match with the given string. Overrides -o and -v") | ||||||
| 	flag.Parse() | 	flag.Parse() | ||||||
| @@ -64,21 +66,30 @@ func main() { | |||||||
| 	// 2. Build NFA from postfix representation (Thompson's algorithm) | 	// 2. Build NFA from postfix representation (Thompson's algorithm) | ||||||
| 	// 3. Run the string against the NFA | 	// 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") | 		fmt.Println("ERROR: Missing cmdline args") | ||||||
| 		os.Exit(22) | 		os.Exit(22) | ||||||
| 	} | 	} | ||||||
| 	var re string | 	var re string | ||||||
| 	re = flag.Args()[0] | 	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 "-" | 	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 { | 	} else { | ||||||
| 		var err error | 		inputFilenames := flag.Args()[1:] | ||||||
| 		inputFile, err = os.Open(flag.Args()[1]) | 		for _, inputFilename := range inputFilenames { | ||||||
| 		if err != nil { | 			inputFile, err := os.Open(inputFilename) | ||||||
| 			fmt.Printf("%s: No such file or directory\n", flag.Args()[1]) | 			if err != nil { | ||||||
| 			os.Exit(2) | 				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 err error | ||||||
| 	var linesRead bool // Whether or not we have read the lines in the file | 	var linesRead bool // Whether or not we have read the lines in the file | ||||||
| 	lineNum := 0       // Current line number | 	lineNum := 0       // Current line number | ||||||
| 	// Create reader for stdin and writer for stdout | 	// Create writer for stdout | ||||||
| 	reader := bufio.NewReader(inputFile) |  | ||||||
| 	out := bufio.NewWriter(os.Stdout) | 	out := bufio.NewWriter(os.Stdout) | ||||||
|  | 	// Compile regex | ||||||
| 	regComp, err := reg.Compile(re, flagsToCompile...) | 	regComp, err := reg.Compile(re, flagsToCompile...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		fmt.Println(err) | 		fmt.Println(err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	for true { |  | ||||||
| 		if linesRead { | 	for _, inputFile := range inputFiles { | ||||||
| 			break | 		reader := bufio.NewReader(inputFile) | ||||||
| 		} | 		linesRead = false | ||||||
| 		if !(*multiLineFlag) { | 		for true { | ||||||
| 			// Read every string from stdin until we encounter an error. If the error isn't EOF, panic. | 			if !(*lineFlag) && (!*onlyFlag) { | ||||||
| 			test_str, err = reader.ReadString('\n') | 				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 | ||||||
| 			lineNum++ | 			} | ||||||
| 			if err != nil { | 			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 err == io.EOF { | ||||||
|  | 					if len(temp) > 0 { | ||||||
|  | 						test_str += temp // Add the last line (if it is non-empty) | ||||||
|  | 					} | ||||||
| 					linesRead = true | 					linesRead = true | ||||||
| 				} else { | 				} else { | ||||||
| 					panic(err) | 					panic(err) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			if len(test_str) > 0 && test_str[len(test_str)-1] == '\n' { | 			matchIndices := make([]reg.Match, 0) | ||||||
| 				test_str = test_str[:len(test_str)-1] | 			if matchNumFlagEnabled { | ||||||
| 			} | 				tmp, err := regComp.FindNthMatch(test_str, *matchNum) | ||||||
| 		} else { | 				if err == nil { | ||||||
| 			// Multi-line mode - read every line of input into a temp. string. | 					matchIndices = append(matchIndices, tmp) | ||||||
| 			// 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 { | 			} 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 *printMatchesFlag { | ||||||
| 			// if we are in single line mode, print the line on which | 				// if we are in single line mode, print the line on which | ||||||
| 			// the matches occur | 				// the matches occur | ||||||
| 			if len(matchIndices) > 0 { | 				if len(matchIndices) > 0 { | ||||||
| 				if !(*multiLineFlag) { | 					if !(*multiLineFlag) { | ||||||
| 					fmt.Fprintf(out, "Line %d:\n", lineNum) | 						fmt.Fprintf(out, "Line %d:\n", lineNum) | ||||||
| 				} | 					} | ||||||
| 				for _, m := range matchIndices { | 					for _, m := range matchIndices { | ||||||
| 					fmt.Fprintf(out, "%s\n", m.String()) | 						fmt.Fprintf(out, "%s\n", m.String()) | ||||||
| 				} | 					} | ||||||
| 				err := out.Flush() | 					err := out.Flush() | ||||||
| 				if err != nil { | 					if err != nil { | ||||||
| 					panic(err) | 						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 !inMatchIndex { | 				continue | ||||||
| 					fmt.Fprintf(out, "%c", test_str_runes[i]) | 			} | ||||||
|  | 			// 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 we are substituting, we need a different behavior, as follows: | ||||||
| 				if indicesToPrint.contains(i) { | 			// For every character in the test string: | ||||||
| 					color.New(color.FgRed).Fprintf(out, "%c", c) | 			// 		1. Check if the index is the start of any matchIndex | ||||||
| 					// Newline after every match - only if -o is enabled and -v is disabled. | 			// 		2. If so, print the substitute text, and set our index to | ||||||
| 					if *onlyFlag && !(*invertFlag) { | 			//			the corresponding end index. | ||||||
| 						for matchIdxNum, idx := range matchIndices { | 			// 		3. If not, just print the character. | ||||||
| 							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 substituteFlagEnabled { | ||||||
| 								if i+1 == idx[0].EndIdx { // End index is one more than last index of match | 				for i := range test_str_runes { | ||||||
| 									fmt.Fprintf(out, "\n") | 					inMatchIndex := false | ||||||
| 									break | 					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 { | ||||||
| 				} else { | 						if !(*onlyFlag) { | ||||||
| 					if !(*onlyFlag) { | 							fmt.Fprintf(out, "%c", c) | ||||||
| 						fmt.Fprintf(out, "%c", c) | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 			err = out.Flush() | ||||||
| 		err = out.Flush() | 			if err != nil { | ||||||
| 		if err != nil { | 				panic(err) | ||||||
| 			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 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') && | ||||||
| 		if (len(test_str_runes) > 0 && test_str_runes[len(test_str_runes)-1] != '\n') && | 				(!*onlyFlag || indicesToPrint.len() > 0) { | ||||||
| 			(!*onlyFlag || indicesToPrint.len() > 0) { | 				fmt.Println() | ||||||
| 			fmt.Println() | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user