Files
gists/tools/go/gobetween/gobetween.go
2024-08-01 16:22:47 +02:00

141 lines
3.9 KiB
Go

package main
import (
"bufio"
"flag"
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
)
func printUsage() {
fmt.Fprintf(os.Stderr, "Usage: %s [options] <range specification> [file]\n", os.Args[0])
fmt.Fprintln(os.Stderr, "Options:")
flag.PrintDefaults()
fmt.Fprintln(os.Stderr, "\nRange specification examples:")
fmt.Fprintln(os.Stderr, "10,20 Reads lines 10 to 20 from the input")
fmt.Fprintln(os.Stderr, "15:+5 Reads 5 lines starting from line 15")
fmt.Fprintln(os.Stderr, "3 Reads from line 3 to the end of the file")
fmt.Fprintln(os.Stderr, "+2 Reads the first 2 lines")
fmt.Fprintln(os.Stderr, "Please ensure you input valid range specifications and file paths.")
os.Exit(1)
}
func parseArgs(args []string) (int, int, string, error) {
var filename string
var start, length, end int
found := false
// Concatenate all arguments to a single string for easier parsing
joinedArgs := strings.Join(args, " ")
// Regular expression to match ranges and numbers
rangeRegex := regexp.MustCompile(`((\d+)?([ :,;-]))(\+)?(\d+)`)
range2Regex := regexp.MustCompile(`((\d+)([ :,;-]))`)
range3Regex := regexp.MustCompile(`(\+)?(\d+)`)
matches := rangeRegex.FindStringSubmatch(joinedArgs)
if matches != nil {
//check if start was defined
if matches[2] != "" {
start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
} else {
start = 1
}
if matches[4] == "+" { // Check if it's a relative length
length, _ = strconv.Atoi(matches[5]) // Convert length to integer
end = start + length - 1
} else {
end, _ = strconv.Atoi(matches[5]) // Convert end line to integer
}
// Remove the matched part from the arguments
joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
found = true
} else {
matches = range2Regex.FindStringSubmatch(joinedArgs)
if matches != nil {
start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
end = -1
// Remove the matched part from the arguments
joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
found = true
} else {
matches = range3Regex.FindStringSubmatch(joinedArgs)
if matches != nil {
if matches[1] == "+" { // Check if it's a relative length
length, _ := strconv.Atoi(matches[2]) // Convert length to integer
start = 1
end = start + length - 1
} else { // Otherwise convert it to an absolute length
start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
end = -1
// Remove the matched part from the arguments
}
joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
found = true
} else {
found = false
printUsage()
}
}
}
// Clean up and identify the filename, if present
joinedArgs = strings.TrimSpace(joinedArgs)
if joinedArgs != "" && !found {
// If we didn't find numbers, interpret the remaining as filename
filename = joinedArgs
} else if joinedArgs != "" {
// Otherwise, interpret any non-empty remaining part as filename
filename = joinedArgs
}
if !found {
return 0, 0, "", fmt.Errorf("no valid range or line number found")
}
return start, end, filename, nil
}
func main() {
flag.Parse()
args := flag.Args()
if len(args) < 1 {
printUsage()
os.Exit(1)
}
startIndex, endIndex, filename, err := parseArgs(args)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing arguments: %v\n", err)
os.Exit(1)
}
// Detemine the input source (file or stdin)
inputSource := os.Stdin
if filename != "" {
inputSource, err = os.Open(filename)
if err != nil {
log.Fatalf("Failed to open file: %v\n", err)
}
defer inputSource.Close()
}
scanner := bufio.NewScanner(inputSource)
//print all lines from line buner start to linenumber end unless linenumber end is -1
for i := 1; scanner.Scan(); i++ {
if startIndex <= i && (i <= endIndex || endIndex == -1) {
fmt.Println(scanner.Text())
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}