From 1156a39858ba7a30e2f698b6d74cf08c945a322e Mon Sep 17 00:00:00 2001 From: "P. Barrett Little" Date: Tue, 10 Sep 2024 10:12:47 -0400 Subject: [PATCH] docs: Enhance code documentation and clarity Improve documentation across multiple packages to enhance code readability and maintainability. Add package-level comments, function descriptions, and inline explanations for key components. Clarify variable names and refine error handling to provide more context. These changes aim to make the codebase more self-explanatory and easier for developers to understand and maintain. --- internal/calculators/target_calculator.go | 6 +++++- internal/parsers/shot_data_parser.go | 18 ++++++++++++++---- internal/writer/writer.go | 8 +++++++- main.go | 16 +++++++++++----- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/internal/calculators/target_calculator.go b/internal/calculators/target_calculator.go index 2ed8cee..e8c692e 100644 --- a/internal/calculators/target_calculator.go +++ b/internal/calculators/target_calculator.go @@ -1,3 +1,4 @@ +// Package calculators provides functions for performing calculations on shot data. package calculators import ( @@ -7,7 +8,8 @@ import ( ) // CalculateTargets computes the median target distance for each club type -// and updates the Target field in each ShotData struct. +// and updates the Target field in each ProcessedShotData struct. +// This function modifies the input slice in-place. func CalculateTargets(shotData *[]models.ProcessedShotData) { // Group shots by club type clubShots := make(map[string][]float64) @@ -30,6 +32,8 @@ func CalculateTargets(shotData *[]models.ProcessedShotData) { // calculateMedian computes the median value from a slice of float64 numbers. // It handles both odd and even-length slices. +// The function sorts the input slice and returns the middle value (or average of two middle values). +// If the input slice is empty, it returns 0. func calculateMedian(numbers []float64) float64 { sort.Float64s(numbers) length := len(numbers) diff --git a/internal/parsers/shot_data_parser.go b/internal/parsers/shot_data_parser.go index 383f172..b96d2a2 100644 --- a/internal/parsers/shot_data_parser.go +++ b/internal/parsers/shot_data_parser.go @@ -1,3 +1,4 @@ +// Package parsers provides functionality for parsing shot data from various launch monitor types. package parsers import ( @@ -13,21 +14,26 @@ import ( "albatross/internal/reader" ) +// headerPattern is a regular expression used to identify header rows in the CSV file. var headerPattern = regexp.MustCompile(`(?i)(club type|total distance|side carry)`) -// ProcessShotData reads and processes shot data from a CSV file +// ProcessShotData reads and processes shot data from a CSV file. +// It supports different launch monitor types and returns a slice of ProcessedShotData. func ProcessShotData(inputFile string, launchMonitorType string) ([]models.ProcessedShotData, error) { + // Open the input file file, err := os.Open(inputFile) if err != nil { return nil, fmt.Errorf("opening file: %w", err) } defer file.Close() + // Set up CSV reader csvReader := csv.NewReader(file) csvReader.Comma = ',' // Using comma as separator csvReader.LazyQuotes = true csvReader.FieldsPerRecord = -1 // Allow variable number of fields + // Create appropriate launch monitor based on the type var launchMonitor models.LaunchMonitor switch launchMonitorType { case "mlm2pro": @@ -40,6 +46,7 @@ func ProcessShotData(inputFile string, launchMonitorType string) ([]models.Proce var headers []string inDataBlock := false + // Read and process each row of the CSV file for { row, err := csvReader.Read() if err != nil { @@ -53,6 +60,7 @@ func ProcessShotData(inputFile string, launchMonitorType string) ([]models.Proce continue } + // Check if the current row is a header row if isHeader(row) { headers = normalizeHeaders(row) inDataBlock = true @@ -64,11 +72,13 @@ func ProcessShotData(inputFile string, launchMonitorType string) ([]models.Proce continue } + // Check if we've reached the end of the data block if isEmptyRow(row) || strings.HasPrefix(strings.ToLower(row[0]), "average") { inDataBlock = false continue } + // Parse and process the row data rawData, err := launchMonitor.ParseRow(row, headers) if err != nil { log.Printf("Skipping row due to error: %v", err) @@ -86,7 +96,7 @@ func ProcessShotData(inputFile string, launchMonitorType string) ([]models.Proce return shotData, nil } -// isHeader checks if a row is a header row +// isHeader checks if a row is a header row by matching against the headerPattern. func isHeader(row []string) bool { if len(row) == 0 { return false @@ -99,7 +109,7 @@ func isHeader(row []string) bool { return false } -// normalizeHeaders standardizes header names +// normalizeHeaders standardizes header names by converting them to lowercase and trimming whitespace. func normalizeHeaders(row []string) []string { normalized := make([]string, len(row)) for i, header := range row { @@ -108,7 +118,7 @@ func normalizeHeaders(row []string) []string { return normalized } -// isEmptyRow checks if a row is empty +// isEmptyRow checks if a row is empty by verifying that all cells are empty strings when trimmed. func isEmptyRow(row []string) bool { for _, cell := range row { if strings.TrimSpace(cell) != "" { diff --git a/internal/writer/writer.go b/internal/writer/writer.go index 8396ef3..bbfd481 100644 --- a/internal/writer/writer.go +++ b/internal/writer/writer.go @@ -1,8 +1,14 @@ +// Package writer provides interfaces and implementations for writing processed shot data to various output formats. package writer import "albatross/internal/models" -// Writer interface defines the method that any writer should implement +// Writer interface defines the method that any writer should implement. +// This interface allows for different output formats to be used interchangeably, +// following the Strategy pattern. type Writer interface { + // Write takes a filename and a slice of ProcessedShotData, and writes the data to the specified file. + // The exact format of the output is determined by the specific implementation of the Writer interface. + // It returns an error if the writing process encounters any issues. Write(filename string, data []models.ProcessedShotData) error } diff --git a/main.go b/main.go index f16cced..b67fda6 100755 --- a/main.go +++ b/main.go @@ -1,3 +1,5 @@ +// Package main is the entry point for the Albatross application. +// It processes shot data from various launch monitors and calculates targets. package main import ( @@ -11,6 +13,8 @@ import ( "albatross/utils" ) +// main is the entry point of the application. It handles command-line arguments, +// processes shot data, calculates targets, and writes the results to a file. func main() { // Parse command-line arguments if len(os.Args) != 3 { @@ -25,7 +29,7 @@ func main() { log.Fatalf("Error: Invalid launch monitor type '%s'. Supported type is mlm2pro.", os.Args[1]) } - // Process shot data + // Process shot data from the input file shotData, err := parsers.ProcessShotData(inputFile, launchMonitorType) if err != nil { log.Fatalf("Error processing shot data: %v", err) @@ -33,12 +37,12 @@ func main() { log.Printf("Processed shot data: %+v", shotData) - // Calculate targets + // Calculate targets based on the processed shot data calculators.CalculateTargets(&shotData) log.Printf("Calculated targets: %+v", shotData) - // Write processed data to output file + // Write processed data to an output file outputFile := utils.ReplaceFileExtension(inputFile, "_processed.csv") writer := writer.ShotPatternWriter{} if err := writer.Write(outputFile, shotData); err != nil { @@ -48,12 +52,14 @@ func main() { log.Printf("Successfully processed %d shots and saved results to %s", len(shotData), outputFile) } -// normalizeLaunchMonitorType converts the launch monitor type to lowercase for consistency +// normalizeLaunchMonitorType converts the launch monitor type to lowercase for consistency. +// This ensures that the type check is case-insensitive. func normalizeLaunchMonitorType(launchMonitorType string) string { return strings.ToLower(launchMonitorType) } -// isValidLaunchMonitorType checks if the provided launch monitor type is supported +// isValidLaunchMonitorType checks if the provided launch monitor type is supported. +// Currently, only "mlm2pro" is supported. func isValidLaunchMonitorType(launchMonitorType string) bool { return launchMonitorType == "mlm2pro" }