refactor: extract parser/descriptor

This commit is contained in:
2024-09-02 02:16:10 +03:00
parent b25bce9892
commit 0a6c5fdc5e
4 changed files with 230 additions and 206 deletions

View File

@@ -1,7 +1,7 @@
package main
const (
LE string = "\n"
// LE string = "\n"
LE_UNIX string = "\n"
LE_WIN string = "\r\n"
)

135
describe.go Normal file
View File

@@ -0,0 +1,135 @@
package main
import (
"os"
"strings"
)
func helpText() strings.Builder {
LE := getLE()
var builder strings.Builder
builder.WriteString("Usage: treelike [OPTIONS] [PATH]" + LE)
builder.WriteString("Prints a tree-like representation of the input." + LE)
builder.WriteString("" + LE)
builder.WriteString("Options:" + LE)
builder.WriteString(" -h, --help Show this help message and exit" + LE)
builder.WriteString(" -f, --file FILE Read from FILE" + LE)
builder.WriteString(" -, --stdin Read from stdin" + LE)
builder.WriteString(" -c, --charset CHARSET Use CHARSET to display characters (utf-8, ascii)" + LE)
builder.WriteString(" -s, --trailing-slash Display trailing slash on directory" + LE)
builder.WriteString(" -p, --full-path Display full path" + LE)
builder.WriteString(" -D, --no-root-dot Do not display a root element" + LE)
return builder
}
func describeTree(node *Node, opts *Options) string {
lines := []string{getTreeLine(node, opts)}
LE := getLE()
for _, child := range node.children {
next := describeTree(child, opts)
for _, line := range strings.Split(next, LE) {
if strings.TrimSpace(line) != "" {
lines = append(lines, line)
}
}
}
var nonBlankLines []string
for _, line := range lines {
if strings.TrimSpace(line) != "" {
nonBlankLines = append(nonBlankLines, line)
}
}
return strings.Join(nonBlankLines, LE)
}
func getPrefixes(opts *Options) (string, string, string, string) {
if opts.charset == "ascii" {
return ASCII_CHILD, ASCII_LAST_CHILD, ASCII_DIRECTORY, ASCII_EMPTY
}
return UTF8_CHILD, UTF8_LAST_CHILD, UTF8_DIRECTORY, UTF8_EMPTY
}
func getLE() string {
if os.IsPathSeparator('\\') {
return LE_WIN
}
return LE_UNIX
}
func getTreeLine(node *Node, opts *Options) string {
if node.parent == nil {
if opts.rootDot {
return node.name
} else {
return ""
}
}
CHILD, LAST_CHILD, DIRECTORY, EMPTY := getPrefixes(opts)
var chunks strings.Builder
if isLastChild(node) {
chunks.WriteString(LAST_CHILD)
} else {
chunks.WriteString(CHILD)
}
chunks.WriteString(getName(node, opts))
str := chunks.String()
current := node.parent
for current != nil && current.parent != nil {
if isLastChild(current) {
str = EMPTY + str
} else {
str = DIRECTORY + str
}
current = current.parent
}
if opts.rootDot {
return str
}
return removePrefix(str, opts)
}
func getName(node *Node, opts *Options) string {
var chunks strings.Builder
chunks.WriteString(node.name)
if opts.trailingSlash && len(node.children) > 0 && node.name[len(node.name)-1] != '/' {
chunks.WriteString("/")
}
str := chunks.String()
if opts.fullPath && node.parent != nil {
newOpts := Options{false, "", strings.Builder{}, opts.charset, true, opts.fullPath, opts.rootDot}
str = getName(node.parent, &newOpts) + str
}
return str
}
func isLastChild(node *Node) bool {
return node.parent != nil && node.parent.children[len(node.parent.children)-1] == node
}
func removePrefix(str string, opts *Options) string {
CHILD, LAST_CHILD, DIRECTORY, EMPTY := getPrefixes(opts)
prefixes := []string{CHILD, LAST_CHILD, DIRECTORY, EMPTY}
for _, prefix := range prefixes {
if strings.HasPrefix(str, prefix) {
return strings.Replace(str, prefix, "", 1)
}
}
return str
}

90
parse.go Normal file
View File

@@ -0,0 +1,90 @@
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func parseDepth(line string, indentSize int) int {
depth := 0
for j := 0; j < len(line); j++ {
if line[j] != ' ' && line[j] != '\t' {
break
}
depth++
}
if indentSize > 0 {
depth /= indentSize
}
return depth
}
func parseInput(input string) *Node {
input = strings.Replace(input, "\r", "", -1)
root := &Node{".", 0, []*Node{}, nil}
current := root
indentSize := 0
LE := LE_UNIX
for _, line := range strings.Split(input, LE) {
if line == "" {
continue
}
depth := parseDepth(line, indentSize)
if depth > 0 && indentSize == 0 {
indentSize = depth
depth /= indentSize
}
if depth < 0 {
depth = 0
}
name := line[depth*indentSize:]
if depth <= current.depth && current.parent != nil {
for current != nil && current.depth >= depth {
current = current.parent
}
}
if current == nil {
current = root
}
current.children = append(current.children, &Node{name, depth, []*Node{}, current})
current = current.children[len(current.children)-1]
}
return root
}
func parseRawInput(opts *Options) (strings.Builder, error, int) {
var input strings.Builder
if opts.fromStdin {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
input.WriteString(scanner.Text() + "\n")
}
if err := scanner.Err(); err != nil {
return input, fmt.Errorf("error reading from stdin: %w", err), 1
}
} else if opts.fromFile != "" {
file, err := os.Open(opts.fromFile)
if err != nil {
return input, fmt.Errorf("error opening file %s: %w", opts.fromFile, err), 1
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
input.WriteString(scanner.Text() + "\n")
}
if err := scanner.Err(); err != nil {
return input, fmt.Errorf("error reading from file: %w", err), 1
}
} else if opts.extra.Len() > 0 {
input.WriteString(opts.extra.String())
} else {
help := helpText()
return input, fmt.Errorf(help.String()), 2
}
return input, nil, 0
}

View File

@@ -1,218 +1,17 @@
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func helpText() {
fmt.Println("Usage: treelike [OPTIONS] [PATH]")
fmt.Println("Prints a tree-like representation of the input.")
fmt.Println("")
fmt.Println("Options:")
fmt.Println(" -h, --help Show this help message and exit")
fmt.Println(" -f, --file FILE Read from FILE")
fmt.Println(" -, --stdin Read from stdin")
fmt.Println(" -c, --charset CHARSET Use CHARSET to display characters (utf-8, ascii)")
fmt.Println(" -s, --trailing-slash Display trailing slash on directory")
fmt.Println(" -p, --full-path Display full path")
fmt.Println(" -D, --no-root-dot Do not display a root element")
}
func parseDepth(line string, indentSize int) int {
depth := 0
for j := 0; j < len(line); j++ {
if line[j] != ' ' && line[j] != '\t' {
break
}
depth++
}
if indentSize > 0 {
depth /= indentSize
}
return depth
}
func parseInput(input string) *Node {
input = strings.Replace(input, "\r", "", -1)
root := &Node{".", 0, []*Node{}, nil}
current := root
indentSize := 0
for _, line := range strings.Split(input, LE) {
if line == "" {
continue
}
depth := parseDepth(line, indentSize)
if depth > 0 && indentSize == 0 {
indentSize = depth
depth /= indentSize
}
if depth < 0 {
depth = 0
}
name := line[depth*indentSize:]
if depth <= current.depth && current.parent != nil {
for current != nil && current.depth >= depth {
current = current.parent
}
}
if current == nil {
current = root
}
current.children = append(current.children, &Node{name, depth, []*Node{}, current})
current = current.children[len(current.children)-1]
}
return root
}
func describeTree(node *Node, opts *Options) string {
lines := []string{getTreeLine(node, opts)}
for _, child := range node.children {
next := describeTree(child, opts)
for _, line := range strings.Split(next, LE) {
if strings.TrimSpace(line) != "" {
lines = append(lines, line)
}
}
}
var nonBlankLines []string
for _, line := range lines {
if strings.TrimSpace(line) != "" {
nonBlankLines = append(nonBlankLines, line)
}
}
return strings.Join(nonBlankLines, LE)
}
func getPrefixes(opts *Options) (string, string, string, string) {
if opts.charset == "ascii" {
return ASCII_CHILD, ASCII_LAST_CHILD, ASCII_DIRECTORY, ASCII_EMPTY
}
return UTF8_CHILD, UTF8_LAST_CHILD, UTF8_DIRECTORY, UTF8_EMPTY
}
// func getLE(input string) string {
// if strings.Contains(input, LE_WIN) || os.IsPathSeparator('\\') {
// return LE_WIN
// }
// return LE_UNIX
// }
func getTreeLine(node *Node, opts *Options) string {
if node.parent == nil {
if opts.rootDot {
return node.name
} else {
return ""
}
}
CHILD, LAST_CHILD, DIRECTORY, EMPTY := getPrefixes(opts)
var chunks strings.Builder
if isLastChild(node) {
chunks.WriteString(LAST_CHILD)
} else {
chunks.WriteString(CHILD)
}
chunks.WriteString(getName(node, opts))
str := chunks.String()
current := node.parent
for current != nil && current.parent != nil {
if isLastChild(current) {
str = EMPTY + str
} else {
str = DIRECTORY + str
}
current = current.parent
}
if opts.rootDot {
return str
}
return removePrefix(str, opts)
}
func getName(node *Node, opts *Options) string {
var chunks strings.Builder
chunks.WriteString(node.name)
if opts.trailingSlash && len(node.children) > 0 && node.name[len(node.name)-1] != '/' {
chunks.WriteString("/")
}
str := chunks.String()
if opts.fullPath && node.parent != nil {
newOpts := Options{false, "", strings.Builder{}, opts.charset, true, opts.fullPath, opts.rootDot}
str = getName(node.parent, &newOpts) + str
}
return str
}
func isLastChild(node *Node) bool {
return node.parent != nil && node.parent.children[len(node.parent.children)-1] == node
}
func removePrefix(str string, opts *Options) string {
CHILD, LAST_CHILD, DIRECTORY, EMPTY := getPrefixes(opts)
prefixes := []string{CHILD, LAST_CHILD, DIRECTORY, EMPTY}
for _, prefix := range prefixes {
if strings.HasPrefix(str, prefix) {
return strings.Replace(str, prefix, "", 1)
}
}
return str
}
func main() {
var input strings.Builder
opts := getOpts()
if opts.fromStdin {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
input.WriteString(scanner.Text() + "\n")
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "Error reading from stdin:", err)
os.Exit(1)
}
} else if opts.fromFile != "" {
file, err := os.Open(opts.fromFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening file %s: %v\n", opts.fromFile, err)
os.Exit(1)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
input.WriteString(scanner.Text() + "\n")
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "Error reading from file:", err)
os.Exit(1)
}
} else if opts.extra.Len() > 0 {
input.WriteString(opts.extra.String())
} else {
helpText()
os.Exit(1)
input, err, code := parseRawInput(&opts)
if err != nil {
fmt.Println(err)
os.Exit(code)
}
node := parseInput(input.String())
fmt.Println(describeTree(node, &opts))