Skip to content

Commit

Permalink
Enhance logging and documentation generation (#893)
Browse files Browse the repository at this point in the history
**Key Changes:**
- Refactored PrettyHandler for improved color support and flexibility
- Updated docGeneration to use go/packages for more robust parsing
- Enhanced error handling and logging across multiple packages
- Improved test coverage for logging and documentation generation

**Added:**
- JSON output option for logging
- Debug logging for file system operations in doc generation
- Helper functions for common test setup tasks in docGeneration_test.go
- New OutputType enum to support different logging output formats

**Changed:**
- Updated PrettyHandler to support both color and JSON output
- Refactored CreatePackageDocs to use go/packages for improved parsing
- Enhanced error handling in various functions, using errors.New() consistently
- Improved test cases for PrettyHandler and docGeneration
- Updated LogAndReturnError to use errors.New() for consistency

**Removed:**
- Unused code in docGeneration.go, including redundant type info extraction
- Deprecated outputToFile function in PrettyHandler
  • Loading branch information
l50 authored Oct 21, 2024
1 parent 468c413 commit 3fe7738
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 147 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ coverage-all.out

# File created for debugging with vscode
mage_output_file.go
TODO
3 changes: 1 addition & 2 deletions dev/mage/mageutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,7 @@ func InstallVSCodeModules() error {
}

if err := InstallGoDeps(vscodeDeps); err != nil {
return fmt.Errorf(
color.RedString("failed to install vscode-go dependencies: %v", err))
return fmt.Errorf("failed to install vscode-go dependencies: %w", err)
}

return nil
Expand Down
83 changes: 48 additions & 35 deletions docs/docGeneration.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"go/types"
"os"
"path/filepath"
"sort"
Expand All @@ -16,6 +16,7 @@ import (

gitutils "github.com/l50/goutils/v2/git"
"github.com/spf13/afero"
"golang.org/x/tools/go/packages"
)

// PackageDoc holds the documentation for a Go package.
Expand Down Expand Up @@ -148,7 +149,11 @@ func generateReadmeFromTemplate(fs afero.Fs, pkgDoc *PackageDoc, path string, te

repoRoot, err := gitutils.RepoRoot()
if err != nil {
return fmt.Errorf("error finding repo root: %w", err)
// Fallback to current working directory if not in a git repo
repoRoot, err = os.Getwd()
if err != nil {
return fmt.Errorf("error getting current working directory: %w", err)
}
}
rootReadmePath := filepath.Join(repoRoot, "README.md")

Expand Down Expand Up @@ -293,41 +298,51 @@ func directoryContainsGoFiles(fs afero.Fs, path string) (bool, error) {
}

func processGoFiles(fs afero.Fs, path string, repo Repo, tmplPath string, excludedPackagesMap map[string]struct{}) error {
fset := token.NewFileSet()
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo,
Dir: path,
Fset: token.NewFileSet(),
Tests: false,
}

// Create a temporary directory
tempDir, err := os.MkdirTemp("", "parserTemp")
// Use nonTestFilter to exclude test files
files, err := afero.ReadDir(fs, path)
if err != nil {
return err
return fmt.Errorf("error reading directory: %w", err)
}
defer os.RemoveAll(tempDir) // Ensure cleanup

// Copy files from afero to the temporary directory
aferoFiles, _ := afero.ReadDir(fs, path)
for _, file := range aferoFiles {
data, _ := afero.ReadFile(fs, filepath.Join(path, file.Name()))
if err := os.WriteFile(filepath.Join(tempDir, file.Name()), data, file.Mode()); err != nil {
return err
var goFiles []string
for _, file := range files {
if nonTestFilter(file) && strings.HasSuffix(file.Name(), ".go") {
goFiles = append(goFiles, filepath.Join(path, file.Name()))
}
}

cfg.Overlay = make(map[string][]byte)
for _, file := range goFiles {
content, err := afero.ReadFile(fs, file)
if err != nil {
return fmt.Errorf("error reading file %s: %w", file, err)
}
cfg.Overlay[file] = content
}

// Point the parser to the temporary directory
pkgs, err := parser.ParseDir(fset, tempDir, nonTestFilter, parser.ParseComments)
// Use "." to represent the current package
patterns := []string{"."}

pkgs, err := packages.Load(cfg, patterns...)
if err != nil {
return err
return fmt.Errorf("error loading packages: %w", err)
}

for _, pkg := range pkgs {
if filepath.Base(path) == "magefiles" && pkg.Name == "main" {
// treat magefiles as a separate package for documentation
pkg.Name = "magefiles"
}

// check if the package name is in the excluded packages list
// Ensure the package is not excluded
if _, exists := excludedPackagesMap[pkg.Name]; exists {
continue // if so, skip this package
continue
}
if err := generateReadmeForPackage(fs, path, fset, pkg, repo, tmplPath); err != nil {

// Generate the README for the package
if err := generateReadmeForPackage(fs, path, pkg, repo, tmplPath); err != nil {
return err
}
}
Expand All @@ -339,15 +354,15 @@ func nonTestFilter(info os.FileInfo) bool {
return !strings.HasSuffix(info.Name(), "_test.go")
}

func generateReadmeForPackage(fs afero.Fs, path string, fset *token.FileSet, pkg *ast.Package, repo Repo, templatePath string) error {
func generateReadmeForPackage(fs afero.Fs, path string, pkg *packages.Package, repo Repo, templatePath string) error {
pkgDoc := &PackageDoc{
PackageName: pkg.Name,
GoGetPath: fmt.Sprintf("github.com/%s/%s/%s", repo.Owner, repo.Name, pkg.Name),
Functions: []FunctionDoc{},
}

for _, file := range pkg.Files {
err := processFileDeclarations(fset, pkgDoc, file)
for _, file := range pkg.Syntax {
err := processFileDeclarations(pkg.Fset, pkgDoc, file, pkg.TypesInfo)
if err != nil {
return err
}
Expand All @@ -359,38 +374,36 @@ func generateReadmeForPackage(fs afero.Fs, path string, fset *token.FileSet, pkg
})

return generateReadmeFromTemplate(fs, pkgDoc, filepath.Join(path, "README.md"), templatePath)

}

func processFileDeclarations(fset *token.FileSet, pkgDoc *PackageDoc, file *ast.File) error {
func processFileDeclarations(fset *token.FileSet, pkgDoc *PackageDoc, file *ast.File, info *types.Info) error {
for _, decl := range file.Decls {
if fn, isFn := decl.(*ast.FuncDecl); isFn {
if !fn.Name.IsExported() || strings.HasPrefix(fn.Name.Name, "Test") {
continue
}

fnDoc, err := createFunctionDoc(fset, fn)
fnDoc, err := createFunctionDoc(fset, fn, info)
if err != nil {
return err
}

// Using fnDoc.Name instead of fn.Name.Name
// fnDoc.Name will be a unique identifier for each method or function
pkgDoc.Functions = append(pkgDoc.Functions, fnDoc)
}
}
return nil
}

func createFunctionDoc(fset *token.FileSet, fn *ast.FuncDecl) (FunctionDoc, error) {
func createFunctionDoc(fset *token.FileSet, fn *ast.FuncDecl, info *types.Info) (FunctionDoc, error) {
var params, results, structName string
var err error

// Extract parameters and results
if fn.Type.Params != nil {
params, err = formatNode(fset, fn.Type.Params)
if err != nil {
return FunctionDoc{}, fmt.Errorf("error formatting function parameters: %w", err)
}

}
if fn.Type.Results != nil {
results, err = formatNode(fset, fn.Type.Results)
Expand All @@ -409,7 +422,7 @@ func createFunctionDoc(fset *token.FileSet, fn *ast.FuncDecl) (FunctionDoc, erro
}

signature := fmt.Sprintf("%s(%s) %s", fn.Name.Name, params, results)
signature = strings.TrimRight(signature, " ") // Trim trailing space
signature = strings.TrimRight(signature, " ")

// Split the signature if it's too long
const maxLineLength = 80
Expand Down
Loading

0 comments on commit 3fe7738

Please sign in to comment.