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.
ccat/main.go

118 lines
3.3 KiB
Go

5 months ago
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()
}
}