diff --git a/class.go b/class.go index e3bb131..b6cfd34 100644 --- a/class.go +++ b/class.go @@ -3,6 +3,7 @@ package main import ( _ "embed" "fmt" + "io" "log" "os" "path/filepath" @@ -11,7 +12,6 @@ import ( "text/template" "github.com/PuerkitoBio/goquery" - "github.com/davecgh/go-spew/spew" ) //go:embed class.tmpl @@ -79,6 +79,7 @@ func (c *Class) GetOutFile(root string) (*os.File, error) { filename = strings.ReplaceAll(filename, ":", "") filePath := filepath.Join(root, filename) filePath = filepath.Clean(filePath) + log.Printf("Writing file to '%s'", filePath) f, err := os.Create(filePath) if err != nil { return nil, err @@ -91,7 +92,7 @@ func (c *Class) Write(root string, tmpl *template.Template) error { if err != nil { return fmt.Errorf("error creating output file %v: %v", c.ClassName, err) } - spew.Dump(c) + // spew.Dump(c) err = tmpl.Execute(outfile, c) if err != nil { return fmt.Errorf("error writing output file %v: %v", c.ClassName, err) @@ -99,14 +100,26 @@ func (c *Class) Write(root string, tmpl *template.Template) error { return nil } -func ParseClass(file string) (*Class, error) { - log.Printf("Parsing file: '%s'", file) +type ClassParser struct { + log *log.Logger + inputfile string +} + +func NewClassParser(file string) *ClassParser { + return &ClassParser{ + log: log.New(io.MultiWriter(os.Stdout, os.Stderr), fmt.Sprintf("%s[ClassParser] %s%s", GenerateRandomAnsiColor(), file, Reset), log.Lmicroseconds|log.Lshortfile), + inputfile: file, + } +} + +func (p *ClassParser) Parse() (*Class, error) { + p.log.Printf("Parsing file: '%s'", p.inputfile) res := Class{ Fields: []Field{}, Methods: []Method{}, Constructors: []Constructor{}, } - filehandle, err := os.Open(file) + filehandle, err := os.Open(p.inputfile) if err != nil { return nil, fmt.Errorf("error opening file: %w", err) } @@ -116,29 +129,34 @@ func ParseClass(file string) (*Class, error) { if err != nil { return nil, fmt.Errorf("error parsing file: %w", err) } + p.log.Printf("Document loaded") dataContainer := doc.Find("div.floatright") if dataContainer.Length() == 0 { return nil, fmt.Errorf("no data container found") } + p.log.Printf("Data container found") - className, err := getClassName(dataContainer) + className, err := p.getClassName(dataContainer) if err != nil { return nil, fmt.Errorf("error getting class name: %w", err) } res.ClassName = className + p.log.Printf("Class name resolved as '%s'", res.ClassName) - classDescription, err := getClassDescription(dataContainer) + classDescription, err := p.getClassDescription(dataContainer) if err == nil { // return nil, fmt.Errorf("error getting class description: %w", err) res.Description = classDescription + p.log.Printf("Class description resolved as '%s'", res.Description) } - constructor, err := getConstructor(dataContainer) + constructor, err := p.getConstructor(dataContainer) if err != nil { return nil, fmt.Errorf("error getting constructor: %w", err) } res.Constructors = append(res.Constructors, constructor) + p.log.Printf("Constructor resolved") // Doing div+div specifically skips only the constructor which is usually the first div // So the constructor would then be located at h1 + p + div OR h1 + div if there is no description (no p) @@ -146,6 +164,7 @@ func ParseClass(file string) (*Class, error) { if dataElements.Length() == 0 { return nil, fmt.Errorf("no data elements found") } + p.log.Printf("Data elements (%d) found", dataElements.Length()) dataElements.Each(func(i int, s *goquery.Selection) { id, ok := s.Attr("id") @@ -154,7 +173,8 @@ func ParseClass(file string) (*Class, error) { } if id == "Properties" { s.Children().Each(func(i int, s *goquery.Selection) { - field, err := parseField(s) + p.log.Printf("Parsing field") + field, err := p.parseField(s) if err != nil { Error.Printf("Error parsing field: %v", err) return @@ -162,7 +182,8 @@ func ParseClass(file string) (*Class, error) { res.Fields = append(res.Fields, field) }) } else { - method, err := parseMethod(s) + p.log.Printf("Parsing method") + method, err := p.parseMethod(s) if err != nil { Error.Printf("Error parsing method: %v", err) return @@ -171,48 +192,58 @@ func ParseClass(file string) (*Class, error) { } }) + p.log.Printf("Class parsed") return &res, nil } -func getClassName(dataContainer *goquery.Selection) (string, error) { +func (p *ClassParser) getClassName(dataContainer *goquery.Selection) (string, error) { + p.log.Printf("Getting class name") class := dataContainer.ChildrenFiltered("h1") if class.Length() == 0 { return "", fmt.Errorf("no class found") } res := CleanUp(class.Text()) + p.log.Printf("Class name resolved as '%s'", res) return res, nil } var manySpaceRe = regexp.MustCompile(`\s{2,}`) -func getClassDescription(dataContainer *goquery.Selection) (string, error) { +func (p *ClassParser) getClassDescription(dataContainer *goquery.Selection) (string, error) { + p.log.Printf("Getting class description") class := dataContainer.ChildrenFiltered("h1 + p") if class.Length() == 0 { return "", fmt.Errorf("no class description found") } res := CleanUp(class.Text()) + p.log.Printf("Class description resolved as '%s'", res) return res, nil } -func getConstructor(dataContainer *goquery.Selection) (Constructor, error) { +func (p *ClassParser) getConstructor(dataContainer *goquery.Selection) (Constructor, error) { resConstructor := Constructor{} + p.log.Printf("Getting constructor") constructorBlock := dataContainer.Find("h1 + p + div") if constructorBlock.Length() == 0 { + p.log.Printf("No constructor block found, trying fallback (h1 + div)") constructorBlock = dataContainer.Find("h1 + div") if constructorBlock.Length() == 0 { return resConstructor, fmt.Errorf("no constructor found") } + p.log.Printf("Fallback constructor block found") } types := constructorBlock.Find("div.function span.type") if types.Length() == 0 { return resConstructor, fmt.Errorf("no types found") } + p.log.Printf("Constructor types found") params := constructorBlock.Find("div.function span.parameter") if params.Length() == 0 { return resConstructor, fmt.Errorf("no params found") } + p.log.Printf("Constructor params found") types.Each(func(i int, s *goquery.Selection) { resConstructor.Params = append(resConstructor.Params, Param{ @@ -221,11 +252,13 @@ func getConstructor(dataContainer *goquery.Selection) (Constructor, error) { Comment: "", }) }) + p.log.Printf("Constructor params resolved") descriptor := constructorBlock.Find("div:not(.function)") if descriptor.Length() == 0 { return resConstructor, fmt.Errorf("no descriptor found") } + p.log.Printf("Constructor descriptor found") // 0 nothing // 1 parameters @@ -260,6 +293,7 @@ func getConstructor(dataContainer *goquery.Selection) (Constructor, error) { case 1: param := s.ChildrenFiltered("span.parameter").Text() paramTrimmed := CleanUp(param) + p.log.Printf("Parameter '%s' found", paramTrimmed) for i := range resConstructor.Params { cparam := &resConstructor.Params[i] if paramTrimmed == cparam.Name { @@ -269,6 +303,7 @@ func getConstructor(dataContainer *goquery.Selection) (Constructor, error) { } case 2: cleaned := CleanUp(s.Text()) + p.log.Printf("Return value '%s' found", cleaned) if resConstructor.Returns != "" { resConstructor.Returns += "\n" } @@ -277,29 +312,34 @@ func getConstructor(dataContainer *goquery.Selection) (Constructor, error) { } }) + p.log.Printf("Constructor resolved") // spew.Dump(resConstructor) return resConstructor, nil } -func parseField(s *goquery.Selection) (Field, error) { +func (p *ClassParser) parseField(s *goquery.Selection) (Field, error) { res := Field{} + p.log.Printf("Parsing field") id, ok := s.Attr("id") if !ok { return res, fmt.Errorf("no id found") } res.Name = id + p.log.Printf("Field name resolved as '%s'", res.Name) typeElement := s.Find("span.type") if typeElement.Length() == 0 { return res, fmt.Errorf("no type found") } res.Type = MapType(CleanUp(typeElement.Text())) + p.log.Printf("Field type resolved as '%s'", res.Type) readonlyElement := s.Find("td[align='right']") if readonlyElement.Length() != 0 { // return res, fmt.Errorf("no readonly found") res.Comment = CleanUp(readonlyElement.Text()) + p.log.Printf("Field comment resolved as '%s'", res.Comment) } comments := s.ChildrenFiltered("div") @@ -311,6 +351,7 @@ func parseField(s *goquery.Selection) (Field, error) { if text == "" { return } + p.log.Printf("Field comment resolved as '%s'", text) if res.Comment != "" { res.Comment += ". " } @@ -318,17 +359,20 @@ func parseField(s *goquery.Selection) (Field, error) { }) res.Comment = strings.ReplaceAll(res.Comment, "\n--", ". ") + p.log.Printf("Field resolved") return res, nil } -func parseMethod(s *goquery.Selection) (Method, error) { +func (p *ClassParser) parseMethod(s *goquery.Selection) (Method, error) { res := Method{} + p.log.Printf("Parsing method") id, ok := s.Attr("id") if !ok { return res, fmt.Errorf("no id found") } res.Name = id + p.log.Printf("Method name resolved as '%s'", res.Name) signatureData := s.Children().Eq(0) returns := signatureData.Find("span.keyword") @@ -341,6 +385,7 @@ func parseMethod(s *goquery.Selection) (Method, error) { Type: MapType(returnsText), Comment: "", }) + p.log.Printf("Method return value resolved as '%s'", res.Returns[0].Type) } types := signatureData.Find("span.type") @@ -351,6 +396,7 @@ func parseMethod(s *goquery.Selection) (Method, error) { Type: MapType(CleanUp(types.Eq(i).Text())), Comment: "", }) + p.log.Printf("Method parameter resolved as '%s'", res.Params[i].Name) }) // 0 nothing @@ -365,6 +411,7 @@ func parseMethod(s *goquery.Selection) (Method, error) { methodDescription := CleanUp(s.Text()) methodDescription = strings.ReplaceAll(methodDescription, "\n--", "
\n\t---") res.Comment = methodDescription + p.log.Printf("Method description resolved as '%s'", res.Comment) } if i == signatureDetailsChildrenLength-1 { // For some stupid reason the last child is always a mispalced

tag that does not close any open

@@ -401,6 +448,7 @@ func parseMethod(s *goquery.Selection) (Method, error) { line = strings.ReplaceAll(line, "", "") line = strings.ReplaceAll(line, "", "") parameter = line + p.log.Printf("Parameter '%s' found", parameter) continue } if strings.Contains(line, "
") { @@ -409,6 +457,7 @@ func parseMethod(s *goquery.Selection) (Method, error) { for i := range res.Params { if res.Params[i].Name == parameter { res.Params[i].Comment = comment + p.log.Printf("Parameter '%s' comment resolved as '%s'", parameter, comment) break } } @@ -419,6 +468,7 @@ func parseMethod(s *goquery.Selection) (Method, error) { if text == "" { return } + p.log.Printf("Return value comment '%s' found", text) // TODO: Figure out what to do when a function if res.Returns[0].Comment != "" { res.Returns[0].Comment += "\n" @@ -429,6 +479,7 @@ func parseMethod(s *goquery.Selection) (Method, error) { } }) + p.log.Printf("Method resolved") return res, nil } diff --git a/color.go b/color.go new file mode 100644 index 0000000..0887a9e --- /dev/null +++ b/color.go @@ -0,0 +1,99 @@ +package main + +import ( + "fmt" + "math/rand/v2" +) + +const ( + // Reset + Reset = "\033[0m" // Text Reset + + // Regular Colors + Black = "\033[0;30m" // Black + Red = "\033[0;31m" // Red + Green = "\033[0;32m" // Green + Yellow = "\033[0;33m" // Yellow + Blue = "\033[0;34m" // Blue + Purple = "\033[0;35m" // Purple + Cyan = "\033[0;36m" // Cyan + White = "\033[0;37m" // White + + // Bold + BBlack = "\033[1;30m" // Black + BRed = "\033[1;31m" // Red + BGreen = "\033[1;32m" // Green + BYellow = "\033[1;33m" // Yellow + BBlue = "\033[1;34m" // Blue + BPurple = "\033[1;35m" // Purple + BCyan = "\033[1;36m" // Cyan + BWhite = "\033[1;37m" // White + + // Underline + UBlack = "\033[4;30m" // Black + URed = "\033[4;31m" // Red + UGreen = "\033[4;32m" // Green + UYellow = "\033[4;33m" // Yellow + UBlue = "\033[4;34m" // Blue + UPurple = "\033[4;35m" // Purple + UCyan = "\033[4;36m" // Cyan + UWhite = "\033[4;37m" // White + + // Background + On_Black = "\033[40m" // Black + On_Red = "\033[41m" // Red + On_Green = "\033[42m" // Green + On_Yellow = "\033[43m" // Yellow + On_Blue = "\033[44m" // Blue + On_Purple = "\033[45m" // Purple + On_Cyan = "\033[46m" // Cyan + On_White = "\033[47m" // White + + // High Intensty + IBlack = "\033[0;90m" // Black + IRed = "\033[0;91m" // Red + IGreen = "\033[0;92m" // Green + IYellow = "\033[0;93m" // Yellow + IBlue = "\033[0;94m" // Blue + IPurple = "\033[0;95m" // Purple + ICyan = "\033[0;96m" // Cyan + IWhite = "\033[0;97m" // White + + // Bold High Intensty + BIBlack = "\033[1;90m" // Black + BIRed = "\033[1;91m" // Red + BIGreen = "\033[1;92m" // Green + BIYellow = "\033[1;93m" // Yellow + BIBlue = "\033[1;94m" // Blue + BIPurple = "\033[1;95m" // Purple + BICyan = "\033[1;96m" // Cyan + BIWhite = "\033[1;97m" // White + + // High Intensty backgrounds + On_IBlack = "\033[0;100m" // Black + On_IRed = "\033[0;101m" // Red + On_IGreen = "\033[0;102m" // Green + On_IYellow = "\033[0;103m" // Yellow + On_IBlue = "\033[0;104m" // Blue + On_IPurple = "\033[10;95m" // Purple + On_ICyan = "\033[0;106m" // Cyan + On_IWhite = "\033[0;107m" // White +) + +// The acceptable range is [16, 231] but here we remove some very dark colors +// That make text unreadable on a dark terminal +// See https://www.hackitu.de/termcolor256/ +var colors = []int{22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 57, 62, 63, 64, 65, 67, 68, 69, 70, 71, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 148, 149, 150, 151, 152, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 184, 185, 186, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 226, 227, 228, 229, 230} +var colorsIndex int = -1 +var shuffled bool + +func GenerateRandomAnsiColor() string { + if !shuffled { + rand.Shuffle(len(colors), func(i int, j int) { + colors[i], colors[j] = colors[j], colors[i] + }) + shuffled = true + } + colorsIndex++ + return fmt.Sprintf("\033[1;4;38;5;%dm", colors[colorsIndex%len(colors)]) +} \ No newline at end of file diff --git a/main.go b/main.go index 93c46ba..ee0546f 100644 --- a/main.go +++ b/main.go @@ -50,7 +50,8 @@ func main() { wg.Add(1) go func(file string) { defer wg.Done() - class, err := ParseClass(file) + parser := NewClassParser(file) + class, err := parser.Parse() if err != nil { Error.Printf("Error parsing file: %v", err) return