You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
118 lines
3.3 KiB
Go
118 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// fileExists returns true if the given file exists, and false if it
|
|
// doesn't. It panics if an error is encountered.
|
|
func fileExists(filename string) bool {
|
|
if _, err := os.Stat(filename); err == nil {
|
|
return true
|
|
} else if errors.Is(err, os.ErrNotExist) {
|
|
return false
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// mustExist can be called to ensure that a file exists; it panics if
|
|
// the file doesn't exist.
|
|
func mustExist(filename string) {
|
|
if fileExists(filename) != true {
|
|
panic(os.ErrNotExist)
|
|
}
|
|
}
|
|
|
|
// getConfig fetches the config file name for the given file extension.
|
|
// It returns two values: the first is true if the config file exists.
|
|
// If it does, the second value is the config filename.
|
|
// If it doesn't, the second value is blank and can be ignored.
|
|
func getConfig(extension string) (bool, string) {
|
|
if extension == "" {
|
|
return false, ""
|
|
}
|
|
// Assuming the file has an extension
|
|
fileName := "config/" + extension[1:] + ".conf"
|
|
if exists := fileExists(fileName); exists == false {
|
|
return false, ""
|
|
} else {
|
|
return true, fileName
|
|
}
|
|
}
|
|
|
|
// printFile is used when no config file can be found for the file extension
|
|
// It prints out the file as it reads it, with no modifications applied. Essentially
|
|
// works like 'cat'.
|
|
func printFile(fileName string) {
|
|
mustExist(fileName)
|
|
data, err := os.ReadFile(fileName)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Print(string(data))
|
|
return
|
|
}
|
|
|
|
func main() {
|
|
|
|
// Check if user has provided a file name
|
|
if len(os.Args) != 2 {
|
|
panic("ERROR: Invalid number of arguments")
|
|
}
|
|
fileName := os.Args[1]
|
|
mustExist(fileName)
|
|
|
|
extension := filepath.Ext(fileName)
|
|
configExists, configFilename := getConfig(extension)
|
|
// If the given file has no corresponding config, print it out
|
|
// and exit.
|
|
if configExists == false {
|
|
printFile(fileName)
|
|
return
|
|
}
|
|
// If the given file has a config, load the config into a stack of regColors.
|
|
regColorStack, err := loadConfig(configFilename)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Load the input file into a colorunit slice (units) and a byte slice (data)
|
|
units, data := loadInputFile(fileName)
|
|
|
|
// For each regular expression in the stack, apply it to the byte slice. Find
|
|
// the first and last index of all matches of the regex. Then apply the corresponding color
|
|
// to every character within these indices.
|
|
//
|
|
// The infinite for loop exists, because I couldn't figure out a way to pop an element from
|
|
// the stack inside the 'for' statement. The loop exits when the 'pop' call returns 'false',
|
|
// indicating that the stack is empty.
|
|
for {
|
|
regclr, ok := regColorStack.Pop()
|
|
// regColorStack.Pop() returns false when there are no more elements to pop
|
|
if ok != true {
|
|
break
|
|
}
|
|
re := regclr.re
|
|
clr := regclr.clr
|
|
// Returns an int double-slice, where each slice contains the start and end indices
|
|
// of the match. In this case, I am finding all the matches of 're' in 'data'.
|
|
matches := re.FindAllSubmatchIndex(data, -1)
|
|
if matches == nil {
|
|
continue
|
|
}
|
|
// For each match, apply the corresponding color to all characters in the match.
|
|
for _, match := range matches {
|
|
units = applyColor(units, match[0], match[1], clr)
|
|
}
|
|
}
|
|
|
|
// After all possible regexes have been matched, print out the contents of 'units'.
|
|
for _, unit := range units {
|
|
unit.print()
|
|
}
|
|
}
|