Author: LakeFox
Email: [email protected]
Date: Tue, 15 Apr 2025 15:21:35 -0600
output/main.go
Changes
Commits
Diff
diff --git a/output/main.go b/output/main.go
index d91b72a..bb880ca 100644
--- a/output/main.go
+++ b/output/main.go
@@ -4 +4 @@ import (
-	"bytes"
+	"encoding/csv"
@@ -6 +5,0 @@ import (
-	"encoding/xml"
@@ -8 +7,4 @@ import (
-	"reflect"
+	"logc/parser"
+	"os"
+	"regexp"
+	"sort"
@@ -10 +11,0 @@ import (
-	"text/template"
@@ -13,11 +14,12 @@ import (
-// TextEncoder converts a struct to plain text based on struct tags, handling nested structs with indentation.
-func Text(v interface{}) string {
-	var result strings.Builder
-	val := reflect.ValueOf(v)
-	typ := val.Type()
-
-	// Handle pointer types
-	if val.Kind() == reflect.Ptr {
-		val = val.Elem()
-		typ = val.Type()
-	}
+func limitLines(lines []string, length int) []string {
+	var result []string
+	for _, line := range lines {
+		for len(line) > length {
+			// Find the last space within the allowed length
+			cut := length
+			for i := length; i > 0; i-- {
+				if line[i] == ' ' {
+					cut = i
+					break
+				}
+			}
@@ -25 +27,10 @@ func Text(v interface{}) string {
-	processValue(val, typ, 0, &result)
+			// If there's no space within the limit, break at the exact length
+			if cut == length {
+				result = append(result, line[:cut])
+				line = line[cut:]
+			} else {
+				// Otherwise, break at the last space and remove it
+				result = append(result, line[:cut])
+				line = line[cut+1:] // Skip the space
+			}
+		}
@@ -27 +38,9 @@ func Text(v interface{}) string {
-	return result.String()
+		// Append any remaining text
+		if len(line) > 0 {
+			result = append(result, line)
+		}
+	}
+	for i, v := range result {
+		result[i] = strings.TrimSpace(v)
+	}
+	return result
@@ -30,2 +49,3 @@ func Text(v interface{}) string {
-func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *strings.Builder) {
-	indent := strings.Repeat("\t", indentLevel)
+// Group the comments by their tags and format the output with colors
+func FormatText(comments []parser.Comment, lineLength int) string {
+	grouped := make(map[string][]parser.Comment)
@@ -33,5 +53,4 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
-	switch val.Kind() {
-	case reflect.Struct:
-		for i := 0; i < val.NumField(); i++ {
-			field := val.Field(i)
-			fieldType := typ.Field(i)
+	// Group comments by tag
+	for _, comment := range comments {
+		grouped[comment.Headers[0]] = append(grouped[comment.Headers[0]], comment)
+	}
@@ -39,5 +58,4 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
-			// Get the 'text' tag, fallback to 'xml' tag if not present
-			tag := fieldType.Tag.Get("text")
-			if tag == "" {
-				tag = fieldType.Tag.Get("xml")
-			}
+	groupedSorted := []string{}
+	for h := range grouped {
+		groupedSorted = append(groupedSorted, h)
+	}
@@ -45,4 +63 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
-			// Skip if tag is "-"
-			if tag == "-" {
-				continue
-			}
+	sort.Strings(groupedSorted)
@@ -50,4 +65,14 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
-			// Use field name if no tag
-			fieldName := fieldType.Name
-			if tag == "" {
-				tag = fieldName
+	// Format the grouped comments for display
+	var output strings.Builder
+	for _, tag := range groupedSorted {
+		tagComments := grouped[tag]
+		output.WriteString(fmt.Sprintf("%s\n", tag)) // Tag with customizable color
+		output.WriteString(strings.Repeat("-", len(tag)+5) + "\n")
+		for _, comment := range tagComments {
+			output.WriteString(fmt.Sprintf("\tFile: %s:%d\n", comment.File, comment.Line)) // File path with customizable color
+			output.WriteString(fmt.Sprintf("\t\t%s\n", strings.Join(limitLines(strings.Split(comment.Data[0], "\n"), lineLength-2), "\n\t\t")))
+
+			// !MAN: This is a test
+			for i := 1; i < len(comment.Headers); i++ {
+				output.WriteString(fmt.Sprintf("\t\t%s\n", comment.Headers[i])) // File path with customizable color
+				output.WriteString(fmt.Sprintf("\t\t\t%s\n\n", strings.Join(limitLines(strings.Split(comment.Data[i], "\n"), lineLength-3), "\n\t\t")))
@@ -54,0 +80,6 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
+			output.WriteString("\n")
+			// output.WriteString(fmt.Sprintf("\t%s\n", comment.Capture))
+		}
+	}
+	return output.String()
+}
@@ -56,21 +87,10 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
-			currentTag := tag
-
-			// Handle slices
-			if field.Kind() == reflect.Slice {
-				for j := 0; j < field.Len(); j++ {
-					item := field.Index(j)
-					itemType := item.Type()
-
-					if item.Kind() == reflect.Struct || (item.Kind() == reflect.Ptr && item.Elem().Kind() == reflect.Struct) {
-						// Nested struct or pointer to struct in slice
-						if strings.Contains(tag, "omitempty") && isEmptyValue(item) {
-							continue
-						} else {
-							currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "")
-							result.WriteString(fmt.Sprintf("%s%s:\n", indent, currentTagWithoutOmit))
-							if item.Kind() == reflect.Ptr {
-								processValue(item.Elem(), itemType.Elem(), indentLevel+1, result)
-							} else {
-								processValue(item, itemType, indentLevel+1, result)
-							}
-						}
+// !ISSUE: Remove []
+func GetHeaders(comments []parser.Comment) []string {
+	headerMap := map[string]int{}
+	for _, v := range comments {
+		for _, h := range v.Headers {
+			if h[0] == '[' {
+				tags := strings.Split(h[1:strings.Index(h, "]")], ",")
+				for _, t := range tags {
+					if t[0] == '!' {
+						headerMap[t[1:]] = 0
@@ -78,22 +98 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
-						lines := strings.TrimSpace(fmt.Sprintf("%v", item.Interface()))
-
-						if strings.Contains(tag, "omitempty") && lines == "" {
-							continue
-						} else {
-							currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "")
-							result.WriteString(fmt.Sprintf("%s%s: %v\n", indent, currentTagWithoutOmit, lines))
-						}
-					}
-				}
-			} else if field.Kind() == reflect.Struct || (field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct) {
-				// Nested struct or pointer to struct
-				if strings.Contains(tag, "omitempty") && isEmptyValue(field) {
-					continue
-				} else {
-					currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "")
-					result.WriteString(fmt.Sprintf("%s%s:\n", indent, currentTagWithoutOmit))
-					fieldTypeToPass := fieldType.Type
-					fieldValToPass := field
-					if field.Kind() == reflect.Ptr {
-						fieldTypeToPass = fieldType.Type.Elem()
-						fieldValToPass = field.Elem()
+						headerMap[t] = 0
@@ -101 +99,0 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
-					processValue(fieldValToPass, fieldTypeToPass, indentLevel+1, result)
@@ -104,9 +102 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
-				// Basic type
-				lines := strings.TrimSpace(fmt.Sprintf("%v", field.Interface()))
-
-				if strings.Contains(tag, "omitempty") && lines == "" {
-					continue
-				} else {
-					currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "")
-					result.WriteString(fmt.Sprintf("%s%s: %v\n", indent, currentTagWithoutOmit, lines))
-				}
+				headerMap[h] = 0
@@ -113,0 +104 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
+
@@ -115,2 +105,0 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
-	default:
-		result.WriteString(fmt.Sprintf("%v", val.Interface()))
@@ -117,0 +107,20 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
+	headers := []string{}
+	for k := range headerMap {
+		headers = append(headers, k)
+	}
+	return headers
+}
+
+// Output function for text
+func Text(comments []parser.Comment) {
+	fmt.Println(FormatText(comments, 90))
+}
+
+// Output function for JSON
+func JSON(comments []parser.Comment) {
+	data, err := json.MarshalIndent(comments, "", "  ")
+	if err != nil {
+		fmt.Println("Error generating JSON:", err)
+		return
+	}
+	fmt.Println(string(data))
@@ -120,18 +129,31 @@ func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *
-func isEmptyValue(v reflect.Value) bool {
-	switch v.Kind() {
-	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
-		return v.Len() == 0
-	case reflect.Bool:
-		return !v.Bool()
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return v.Int() == 0
-	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
-		return v.Uint() == 0
-	case reflect.Float32, reflect.Float64:
-		return v.Float() == 0
-	case reflect.Interface, reflect.Ptr:
-		return v.IsNil()
-	case reflect.Struct:
-		for i := 0; i < v.NumField(); i++ {
-			if !isEmptyValue(v.Field(i)) {
-				return false
+// !TODO: Add markdown Output
+func HTML(comments []parser.Comment) string {
+	grouped := make(map[string][]parser.Comment)
+
+	// Group comments by tag
+	for _, comment := range comments {
+		grouped[comment.Headers[0]] = append(grouped[comment.Headers[0]], comment)
+	}
+
+	groupedSorted := []string{}
+	for h := range grouped {
+		groupedSorted = append(groupedSorted, h)
+	}
+
+	sort.Strings(groupedSorted)
+
+	// Format the grouped comments for display
+	var output strings.Builder
+	for _, tag := range groupedSorted {
+		tagComments := grouped[tag]
+		output.WriteString(fmt.Sprintf("

%s

\n", tag)) + for _, comment := range tagComments { + output.WriteString(fmt.Sprintf("

File: %s:%d

\n", comment.File, comment.Line)) // File path with customizable color + output.WriteString(fmt.Sprintf("

%s

\n", strings.ReplaceAll(comment.Data[0], "\n", "
"))) + + // !MAN: This is a test + output.WriteString("
    ") + + for i := 1; i < len(comment.Headers); i++ { + output.WriteString(fmt.Sprintf("
  • \n%s\n", comment.Headers[i])) // File path with customizable color + output.WriteString(fmt.Sprintf("%s\n
  • \n\n", strings.ReplaceAll(comment.Data[i], "\n", "
    "))) @@ -138,0 +161,2 @@ func isEmptyValue(v reflect.Value) bool { + output.WriteString("
\n") + @@ -140 +163,0 @@ func isEmptyValue(v reflect.Value) bool { - return true @@ -142 +165,2 @@ func isEmptyValue(v reflect.Value) bool { - return false + + return output.String() @@ -145,3 +169,21 @@ func isEmptyValue(v reflect.Value) bool { -func JSON(v interface{}) string { - b, _ := json.Marshal(v) - return string(b) +func GroupByHeaders(comments []parser.Comment, headers string) map[string][]parser.Comment { + grouped := make(map[string][]parser.Comment) + headersSplit := strings.Split(headers, ",") + // Group comments by tag + for _, comment := range comments { + for _, header := range comment.Headers { + if len(headersSplit) > 1 { + for _, rqh := range headersSplit { + if rqh == header { + grouped[header] = append(grouped[header], comment) + } + } + } else { + + grouped[header] = append(grouped[header], comment) + } + + } + } + + return grouped @@ -148,0 +191,7 @@ func JSON(v interface{}) string { +// !OUTPUT: Used by the -format=csv flag to transform data into CSV +// + just some more text +// + Usage: Pass a slice of comments to output.CSV and it will write a formatted version to +// + the console +// + [!MAN]Def: [141:168](DEVMAN) +// + [DEVMAN]Note: Here the loop breaks early to prevent runaway +// + [DEVMAN,MAN]INFO: Just some more information @@ -150,3 +199,27 @@ func JSON(v interface{}) string { -func XML(v interface{}) string { - b, _ := xml.Marshal(v) - return string(b) +func CSV(comments []parser.Comment) { + writer := csv.NewWriter(os.Stdout) + defer writer.Flush() + // !DEVMAN: Text + headers := GetHeaders(comments) + // !ERROR: this will break the capture + writer.Write(headers) + for _, comment := range comments { + out := []string{} + for _, v := range headers { + index := -1 + for i, h := range comment.Headers { + if h == v { + index = i + break + } + } + // add a line + + if index == -1 { + out = append(out, "") + } else { + out = append(out, comment.Data[index]) + } + } + writer.Write(out) + } @@ -155,2 +228,10 @@ func XML(v interface{}) string { -func FormatPath(path string, data interface{}) string { - var output bytes.Buffer +// ANSI color codes for different tab levels +var colors = []string{ + "\033[0m", // Default + "\033[34m", // Blue + "\033[32m", // Green + "\033[33m", // Yellow + "\033[31m", // Red + "\033[35m", // Magenta + "\033[36m", // Cyan +} @@ -158,2 +239,3 @@ func FormatPath(path string, data interface{}) string { - templ := template.Must(template.New("fp").Parse(path)) - templ.Execute(&output, data) +func HighlightText(paragraph string) string { + lines := strings.Split(paragraph, "\n") + re := regexp.MustCompile(`^(\t*)`) // Regex to match leading tabs @@ -161 +243,18 @@ func FormatPath(path string, data interface{}) string { - return output.String() + var result strings.Builder + for _, line := range lines { + matches := re.FindString(line) + tabCount := len(matches) + + // Apply color based on tab count, ensuring it doesn't exceed available colors + var color string + if tabCount < len(colors) { + color = colors[tabCount] + } else { + color = colors[len(colors)-1] + } + reset := "\033[0m" + + // Apply color to the entire line + result.WriteString(color + line + reset + "\n") + } + return result.String()
package output

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"reflect"
	"strings"
	"text/template"
)

// TextEncoder converts a struct to plain text based on struct tags, handling nested structs with indentation.
func Text(v interface{}) string {
	var result strings.Builder
	val := reflect.ValueOf(v)
	typ := val.Type()

	// Handle pointer types
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
		typ = val.Type()
	}

	processValue(val, typ, 0, &result)

	return result.String()
}

func processValue(val reflect.Value, typ reflect.Type, indentLevel int, result *strings.Builder) {
	indent := strings.Repeat("\t", indentLevel)

	switch val.Kind() {
	case reflect.Struct:
		for i := 0; i < val.NumField(); i++ {
			field := val.Field(i)
			fieldType := typ.Field(i)

			// Get the 'text' tag, fallback to 'xml' tag if not present
			tag := fieldType.Tag.Get("text")
			if tag == "" {
				tag = fieldType.Tag.Get("xml")
			}

			// Skip if tag is "-"
			if tag == "-" {
				continue
			}

			// Use field name if no tag
			fieldName := fieldType.Name
			if tag == "" {
				tag = fieldName
			}

			currentTag := tag

			// Handle slices
			if field.Kind() == reflect.Slice {
				for j := 0; j < field.Len(); j++ {
					item := field.Index(j)
					itemType := item.Type()

					if item.Kind() == reflect.Struct || (item.Kind() == reflect.Ptr && item.Elem().Kind() == reflect.Struct) {
						// Nested struct or pointer to struct in slice
						if strings.Contains(tag, "omitempty") && isEmptyValue(item) {
							continue
						} else {
							currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "")
							result.WriteString(fmt.Sprintf("%s%s:\n", indent, currentTagWithoutOmit))
							if item.Kind() == reflect.Ptr {
								processValue(item.Elem(), itemType.Elem(), indentLevel+1, result)
							} else {
								processValue(item, itemType, indentLevel+1, result)
							}
						}
					} else {
						lines := strings.TrimSpace(fmt.Sprintf("%v", item.Interface()))

						if strings.Contains(tag, "omitempty") && lines == "" {
							continue
						} else {
							currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "")
							result.WriteString(fmt.Sprintf("%s%s: %v\n", indent, currentTagWithoutOmit, lines))
						}
					}
				}
			} else if field.Kind() == reflect.Struct || (field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct) {
				// Nested struct or pointer to struct
				if strings.Contains(tag, "omitempty") && isEmptyValue(field) {
					continue
				} else {
					currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "")
					result.WriteString(fmt.Sprintf("%s%s:\n", indent, currentTagWithoutOmit))
					fieldTypeToPass := fieldType.Type
					fieldValToPass := field
					if field.Kind() == reflect.Ptr {
						fieldTypeToPass = fieldType.Type.Elem()
						fieldValToPass = field.Elem()
					}
					processValue(fieldValToPass, fieldTypeToPass, indentLevel+1, result)
				}
			} else {
				// Basic type
				lines := strings.TrimSpace(fmt.Sprintf("%v", field.Interface()))

				if strings.Contains(tag, "omitempty") && lines == "" {
					continue
				} else {
					currentTagWithoutOmit := strings.ReplaceAll(currentTag, ",omitempty", "")
					result.WriteString(fmt.Sprintf("%s%s: %v\n", indent, currentTagWithoutOmit, lines))
				}
			}
		}
	default:
		result.WriteString(fmt.Sprintf("%v", val.Interface()))
	}
}

func isEmptyValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
		return v.Len() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Interface, reflect.Ptr:
		return v.IsNil()
	case reflect.Struct:
		for i := 0; i < v.NumField(); i++ {
			if !isEmptyValue(v.Field(i)) {
				return false
			}
		}
		return true
	}
	return false
}

func JSON(v interface{}) string {
	b, _ := json.Marshal(v)
	return string(b)
}

func XML(v interface{}) string {
	b, _ := xml.Marshal(v)
	return string(b)
}

func FormatPath(path string, data interface{}) string {
	var output bytes.Buffer

	templ := template.Must(template.New("fp").Parse(path))
	templ.Execute(&output, data)

	return output.String()
}