-
Notifications
You must be signed in to change notification settings - Fork 3
Added xlsx writer #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Added output file name and output sheet name to output to xlsx. If an output file name with an xlsx extension is specified, an xlsx file will be output.
WalkthroughThe update enhances a command-line tool to support XLSX file output. It introduces new writer types and functions for handling XLSX formats, along with additional CLI flags for file and sheet management. The default output format is now inferred, and the tool can clear existing sheets in XLSX files before writing new data. Changes
TipsChat with CodeRabbit Bot (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 6
Configuration used: CodeRabbit UI
Files selected for processing (3)
- cmd/query.go (2 hunks)
- cmd/root.go (1 hunks)
- writer.go (1 hunks)
Additional comments: 6
cmd/root.go (1)
- 59-76: The introduction of new global variables and corresponding flags for handling XLSX output is a significant enhancement. However, it's important to ensure that these variables are used consistently throughout the codebase and that their introduction doesn't cause any side effects or conflicts with existing functionality.
- The
OutFormatdefault value change from "CSV" to "GUESS" could potentially affect existing users who rely on the default behavior. It's important to communicate this change clearly in the release notes or documentation to avoid confusion.- The new flags for specifying the output file name and sheet name (
--out-xlsxand--out-sheet) are clear and align with the naming convention of other flags.- The
--clear-sheetflag is a useful addition for users who need to clear the sheet before outputting new data.Ensure that the new flags are documented and that any necessary error handling is in place for scenarios where the user might provide invalid file names or sheet names.
cmd/query.go (4)
3-13: The new imports introduced here are necessary for the added functionality. Ensure that the
xlsxsqlpackage is properly documented and maintained, as it is critical for the new feature.17-33: The
execfunction has been updated to use the newsetWriterfunction to determine the appropriate writer based on the output format. This is a logical change that aligns with the new feature of supporting XLSX output.62-79: The
stdWriterfunction is well-implemented to handle non-XLSX formats. It correctly checks if a file name is provided and creates a file or defaults toos.Stdout. The use oftrdsql.OutputFormatto parse theOutFormatis also appropriate.17-84: > Note: This review was outside of the patch, so it was mapped to the patch with the greatest overlap. Original lines [82-90]
The
queryCmdcommand documentation has been updated to reflect the new functionality. Ensure that the help text and long description are clear and informative for the end-user.writer.go (1)
- 3-9: The imports are correctly organized, with standard library imports followed by third-party packages. Ensure that the
github.com/noborus/trdsqlandgithub.com/xuri/excelize/v2packages are compatible with the rest of the project and are up-to-date.
| func setWriter(fileName string) (trdsql.Writer, error) { | ||
| dotExt := strings.TrimLeft(filepath.Ext(fileName), ".") | ||
| if OutFormat == "GUESS" && dotExt != "" { | ||
| OutFormat = strings.ToUpper(dotExt) | ||
| } | ||
|
|
||
| if strings.ToUpper(OutFormat) != "XLSX" { | ||
| return stdWriter(fileName) | ||
| } | ||
|
|
||
| // XLSX Writer | ||
| if fileName == "" { | ||
| return nil, fmt.Errorf("file name is required to output with XLSX") | ||
| } | ||
|
|
||
| if OutSheetName == "" { | ||
| OutSheetName = "Sheet1" | ||
| } | ||
| writer, err := xlsxsql.NewXLSXWriter(os.Stdout, OutFileName, OutSheetName, ClearSheet) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return writer, nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The setWriter function correctly handles the logic to determine whether to use a standard writer or the new xlsxsql writer based on the OutFormat. However, the error message on line 48 should be more descriptive to help users understand that the file name is required specifically for XLSX output.
- return nil, fmt.Errorf("file name is required to output with XLSX")
+ return nil, fmt.Errorf("an output file name must be provided when exporting to XLSX format")Commitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
| func setWriter(fileName string) (trdsql.Writer, error) { | |
| dotExt := strings.TrimLeft(filepath.Ext(fileName), ".") | |
| if OutFormat == "GUESS" && dotExt != "" { | |
| OutFormat = strings.ToUpper(dotExt) | |
| } | |
| if strings.ToUpper(OutFormat) != "XLSX" { | |
| return stdWriter(fileName) | |
| } | |
| // XLSX Writer | |
| if fileName == "" { | |
| return nil, fmt.Errorf("file name is required to output with XLSX") | |
| } | |
| if OutSheetName == "" { | |
| OutSheetName = "Sheet1" | |
| } | |
| writer, err := xlsxsql.NewXLSXWriter(os.Stdout, OutFileName, OutSheetName, ClearSheet) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return writer, nil | |
| } | |
| func setWriter(fileName string) (trdsql.Writer, error) { | |
| dotExt := strings.TrimLeft(filepath.Ext(fileName), ".") | |
| if OutFormat == "GUESS" && dotExt != "" { | |
| OutFormat = strings.ToUpper(dotExt) | |
| } | |
| if strings.ToUpper(OutFormat) != "XLSX" { | |
| return stdWriter(fileName) | |
| } | |
| // XLSX Writer | |
| if fileName == "" { | |
| return nil, fmt.Errorf("an output file name must be provided when exporting to XLSX format") | |
| } | |
| if OutSheetName == "" { | |
| OutSheetName = "Sheet1" | |
| } | |
| writer, err := xlsxsql.NewXLSXWriter(os.Stdout, OutFileName, OutSheetName, ClearSheet) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return writer, nil | |
| } |
writer.go
Outdated
| func clear(f *excelize.File, sheet string) error { | ||
| rows, err := f.GetRows(sheet) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| for i, row := range rows { | ||
| for j := range row { | ||
| axis, err := excelize.CoordinatesToCellName(j+1, i+1) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| f.SetCellStr(sheet, axis, "") | ||
| } | ||
| } | ||
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The clear function iterates over all cells in a sheet and sets their value to an empty string. This is a potentially expensive operation for large sheets and could be optimized. Consider if the library provides a method to clear an entire sheet at once, which would be more efficient.
writer.go
Outdated
| // NewXLSXWriter function takes an io.Writer and trdsql.WriteOpts, and returns a new XLSXWriter. | ||
| func NewXLSXWriter(writer io.Writer, fileName string, sheet string, clearSheet bool) (trdsql.Writer, error) { | ||
| var f *excelize.File | ||
| var err error | ||
|
|
||
| if _, err = os.Stat(fileName); os.IsNotExist(err) { | ||
| // File does not exist, create a new one | ||
| f = excelize.NewFile() | ||
| } else { | ||
| // File exists, open it | ||
| f, err = excelize.OpenFile(fileName) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| n, err := f.GetSheetIndex(sheet) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if clearSheet { | ||
| if n >= 0 { | ||
| // Sheet exists,clear it | ||
| if err := clear(f, sheet); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
| } | ||
| if n < 0 { | ||
| // Sheet does not exist, create a new one | ||
| if _, err := f.NewSheet(sheet); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| return &XLSXWriter{ | ||
| fileName: fileName, | ||
| f: f, | ||
| sheet: sheet, | ||
| }, nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The NewXLSXWriter function correctly handles the creation of a new file or opening an existing one. It also checks if the sheet exists and clears it if required. However, there is a potential issue with the error handling in lines 42-50. If os.Stat fails for reasons other than the file not existing (e.g., permission issues), the error is not handled, and the function proceeds to create a new file. This could lead to unexpected behavior.
+ if err != nil && !os.IsNotExist(err) {
+ return nil, err
+ }
+ if os.IsNotExist(err) {Commitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
| // NewXLSXWriter function takes an io.Writer and trdsql.WriteOpts, and returns a new XLSXWriter. | |
| func NewXLSXWriter(writer io.Writer, fileName string, sheet string, clearSheet bool) (trdsql.Writer, error) { | |
| var f *excelize.File | |
| var err error | |
| if _, err = os.Stat(fileName); os.IsNotExist(err) { | |
| // File does not exist, create a new one | |
| f = excelize.NewFile() | |
| } else { | |
| // File exists, open it | |
| f, err = excelize.OpenFile(fileName) | |
| if err != nil { | |
| return nil, err | |
| } | |
| } | |
| n, err := f.GetSheetIndex(sheet) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if clearSheet { | |
| if n >= 0 { | |
| // Sheet exists,clear it | |
| if err := clear(f, sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| } | |
| if n < 0 { | |
| // Sheet does not exist, create a new one | |
| if _, err := f.NewSheet(sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| return &XLSXWriter{ | |
| fileName: fileName, | |
| f: f, | |
| sheet: sheet, | |
| }, nil | |
| } | |
| func NewXLSXWriter(writer io.Writer, fileName string, sheet string, clearSheet bool) (trdsql.Writer, error) { | |
| var f *excelize.File | |
| var err error | |
| if _, err = os.Stat(fileName); err != nil && !os.IsNotExist(err) { | |
| return nil, err | |
| } | |
| if os.IsNotExist(err) { | |
| // File does not exist, create a new one | |
| f = excelize.NewFile() | |
| } else { | |
| // File exists, open it | |
| f, err = excelize.OpenFile(fileName) | |
| if err != nil { | |
| return nil, err | |
| } | |
| } | |
| n, err := f.GetSheetIndex(sheet) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if clearSheet { | |
| if n >= 0 { | |
| // Sheet exists,clear it | |
| if err := clear(f, sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| } | |
| if n < 0 { | |
| // Sheet does not exist, create a new one | |
| if _, err := f.NewSheet(sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| return &XLSXWriter{ | |
| fileName: fileName, | |
| f: f, | |
| sheet: sheet, | |
| }, nil | |
| } |
| func (w *XLSXWriter) PreWrite(columns []string, types []string) error { | ||
| for i, v := range columns { | ||
| cell, error := excelize.CoordinatesToCellName(i+1, 1) | ||
| if error != nil { | ||
| return error | ||
| } | ||
| w.f.SetCellValue(w.sheet, cell, v) | ||
| } | ||
| w.rowID++ | ||
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PreWrite method sets up the column headers in the first row of the sheet. The variable error should be named err to follow Go's convention. Also, incrementing w.rowID here assumes that headers are always written, which is a valid assumption, but it should be documented or checked.
- if error != nil {
- return error
+ cell, err := excelize.CoordinatesToCellName(i+1, 1)
+ if err != nil {
+ return errCommitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
| func (w *XLSXWriter) PreWrite(columns []string, types []string) error { | |
| for i, v := range columns { | |
| cell, error := excelize.CoordinatesToCellName(i+1, 1) | |
| if error != nil { | |
| return error | |
| } | |
| w.f.SetCellValue(w.sheet, cell, v) | |
| } | |
| w.rowID++ | |
| return nil | |
| func (w *XLSXWriter) PreWrite(columns []string, types []string) error { | |
| for i, v := range columns { | |
| cell, err := excelize.CoordinatesToCellName(i+1, 1) | |
| if err != nil { | |
| return err | |
| } | |
| w.f.SetCellValue(w.sheet, cell, v) | |
| } | |
| w.rowID++ | |
| return nil |
| // WriteRow function writes a row to the XLSXWriter. | ||
| func (w *XLSXWriter) WriteRow(row []interface{}, columns []string) error { | ||
| w.rowID++ | ||
| for i, v := range row { | ||
| if v == nil { | ||
| continue | ||
| } | ||
| cell, error := excelize.CoordinatesToCellName(i+1, w.rowID) | ||
| if error != nil { | ||
| return error | ||
| } | ||
| w.f.SetCellValue(w.sheet, cell, v) | ||
| } | ||
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The WriteRow method writes a row to the XLSX file. It correctly increments w.rowID before writing the row, which is good for keeping track of the current row index. However, it skips nil values without writing them, which might lead to misaligned columns if nil is a valid value in some columns. This behavior should be documented or reconsidered.
NewXLSXWriter changed argument to writeOpts. Added option to specify Cell. Header can now be specified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 4
Configuration used: CodeRabbit UI
Files selected for processing (3)
- cmd/query.go (2 hunks)
- cmd/root.go (1 hunks)
- writer.go (1 hunks)
Additional comments: 2
cmd/query.go (2)
3-13: The import statements have been updated to include the necessary packages for the new functionality. Ensure that all new dependencies are properly managed (e.g., added to
go.mod) and that there are no unused imports after the changes.17-33: The
execfunction has been updated to use the newsetWriterfunction. Ensure that the error handling is consistent and that all possible error scenarios are accounted for. It's also important to verify that thetrdsql.NewTRDSQLfunction's error handling is robust, as it's not visible in this hunk.
| // OutHeader is output header. | ||
| var OutHeader bool | ||
|
|
||
| // Skip is skip lines. | ||
| var Skip int | ||
|
|
||
| // Header is input header. | ||
| var Header bool | ||
|
|
||
| // OutFileName is output file name. | ||
| var OutFileName string | ||
|
|
||
| // OutSheetName is output sheet name. | ||
| var OutSheetName string | ||
|
|
||
| // OutCell is output cell name. | ||
| var OutCell string | ||
|
|
||
| // ClearSheet is clear sheet if true. | ||
| var ClearSheet bool | ||
|
|
||
| func init() { | ||
| rootCmd.PersistentFlags().BoolVarP(&Ver, "version", "v", false, "display version information") | ||
| rootCmd.PersistentFlags().BoolVarP(&Debug, "debug", "", false, "debug mode") | ||
| rootCmd.PersistentFlags().IntVarP(&Skip, "skip", "s", 0, "Skip the number of lines") | ||
| rootCmd.PersistentFlags().BoolVarP(&Header, "header", "H", false, "Input header") | ||
| rootCmd.PersistentFlags().BoolVarP(&OutHeader, "out-header", "O", false, "Output header") | ||
| rootCmd.PersistentFlags().StringVarP(&OutFormat, "out", "o", "CSV", "Output Format[CSV|AT|LTSV|JSON|JSONL|TBLN|RAW|MD|VF|YAML]") | ||
| rootCmd.PersistentFlags().StringVarP(&OutFormat, "out", "o", "GUESS", "Output Format[CSV|AT|LTSV|JSON|JSONL|TBLN|RAW|MD|VF|YAML]") | ||
| rootCmd.PersistentFlags().StringVarP(&OutFileName, "out-xlsx", "x", "", "File name to output to xlsx file") | ||
| rootCmd.PersistentFlags().StringVarP(&OutSheetName, "out-sheet", "S", "", "Sheet name to output to xlsx file") | ||
| rootCmd.PersistentFlags().StringVarP(&OutCell, "out-cell", "C", "", "Cell name to output to xlsx file") | ||
| rootCmd.PersistentFlags().BoolVarP(&ClearSheet, "clear-sheet", "D", false, "Clear sheet when outputting to xlsx file") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The introduction of new global variables and flags for handling XLSX file output is a significant change. It is important to ensure that the documentation and help messages for the tool are updated to reflect these new options. Additionally, the change in the default value of OutFormat from "CSV" to "GUESS" could potentially affect existing users who rely on the default behavior. It would be prudent to highlight this change in the release notes or documentation to inform users of the new behavior.
Moreover, the use of global variables for command-line flags can lead to potential issues with maintainability and testing. It might be beneficial to encapsulate these settings within a configuration struct that can be passed around, which would make it easier to manage the state and dependencies of the application.
Lastly, ensure that the new flags have been thoroughly tested, especially the ClearSheet flag, as it has the potential to modify or delete user data. It is crucial to confirm that the behavior is as expected and that there are safeguards in place to prevent accidental data loss.
| // NewXLSXWriter function takes an io.Writer and trdsql.WriteOpts, and returns a new XLSXWriter. | ||
| func NewXLSXWriter(options ...WriteOpt) (*XLSXWriter, error) { | ||
| writeOpts := &WriteOpts{ | ||
| ErrStream: os.Stderr, | ||
| FileName: "", | ||
| Sheet: "Sheet1", | ||
| Cell: "", | ||
| ClearSheet: false, | ||
| Header: true, | ||
| } | ||
| for _, option := range options { | ||
| option(writeOpts) | ||
| } | ||
|
|
||
| var f *excelize.File | ||
| { | ||
| var err error | ||
| if _, err = os.Stat(writeOpts.FileName); err != nil && !os.IsNotExist(err) { | ||
| return nil, err | ||
| } | ||
| if os.IsNotExist(err) { | ||
| // File does not exist, create a new one | ||
| f = excelize.NewFile() | ||
| } else { | ||
| // File exists, open it | ||
| f, err = excelize.OpenFile(writeOpts.FileName) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
| } | ||
|
|
||
| cellX, cellY := getCell(writeOpts.Cell) | ||
|
|
||
| n, err := f.GetSheetIndex(writeOpts.Sheet) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| if writeOpts.ClearSheet { | ||
| if n >= 0 { | ||
| // Sheet exists,clear it | ||
| if err := clearSheet(f, writeOpts.Sheet); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
| } | ||
| if n < 0 { | ||
| // Sheet does not exist, create a new one | ||
| if _, err := f.NewSheet(writeOpts.Sheet); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| return &XLSXWriter{ | ||
| fileName: writeOpts.FileName, | ||
| f: f, | ||
| cellX: cellX, | ||
| cellY: cellY, | ||
| sheet: writeOpts.Sheet, | ||
| header: writeOpts.Header, | ||
| }, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The NewXLSXWriter function is responsible for creating a new XLSX file or opening an existing one. It also handles the case where the user wants to clear an existing sheet. However, there is a potential issue with the way the sheet index is checked and the sheet is cleared. If the sheet exists and the ClearSheet option is true, the sheet is cleared, but if the sheet does not exist, it is created. The problem arises when the sheet does not exist, and the ClearSheet option is true; the code will create the sheet but not clear it, which is inconsistent. The ClearSheet option should only be relevant if the sheet already exists. Consider revising the logic to ensure that the ClearSheet option is only applied to existing sheets and that the creation of a new sheet is handled separately.
+ // Only attempt to clear the sheet if it exists
+ if writeOpts.ClearSheet && n >= 0 {
+ // Sheet exists, clear it
+ if err := clearSheet(f, writeOpts.Sheet); err != nil {
+ return nil, err
+ }
+ }Commitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
| // NewXLSXWriter function takes an io.Writer and trdsql.WriteOpts, and returns a new XLSXWriter. | |
| func NewXLSXWriter(options ...WriteOpt) (*XLSXWriter, error) { | |
| writeOpts := &WriteOpts{ | |
| ErrStream: os.Stderr, | |
| FileName: "", | |
| Sheet: "Sheet1", | |
| Cell: "", | |
| ClearSheet: false, | |
| Header: true, | |
| } | |
| for _, option := range options { | |
| option(writeOpts) | |
| } | |
| var f *excelize.File | |
| { | |
| var err error | |
| if _, err = os.Stat(writeOpts.FileName); err != nil && !os.IsNotExist(err) { | |
| return nil, err | |
| } | |
| if os.IsNotExist(err) { | |
| // File does not exist, create a new one | |
| f = excelize.NewFile() | |
| } else { | |
| // File exists, open it | |
| f, err = excelize.OpenFile(writeOpts.FileName) | |
| if err != nil { | |
| return nil, err | |
| } | |
| } | |
| } | |
| cellX, cellY := getCell(writeOpts.Cell) | |
| n, err := f.GetSheetIndex(writeOpts.Sheet) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if writeOpts.ClearSheet { | |
| if n >= 0 { | |
| // Sheet exists,clear it | |
| if err := clearSheet(f, writeOpts.Sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| } | |
| if n < 0 { | |
| // Sheet does not exist, create a new one | |
| if _, err := f.NewSheet(writeOpts.Sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| return &XLSXWriter{ | |
| fileName: writeOpts.FileName, | |
| f: f, | |
| cellX: cellX, | |
| cellY: cellY, | |
| sheet: writeOpts.Sheet, | |
| header: writeOpts.Header, | |
| }, nil | |
| func NewXLSXWriter(options ...WriteOpt) (*XLSXWriter, error) { | |
| writeOpts := &WriteOpts{ | |
| ErrStream: os.Stderr, | |
| FileName: "", | |
| Sheet: "Sheet1", | |
| Cell: "", | |
| ClearSheet: false, | |
| Header: true, | |
| } | |
| for _, option := range options { | |
| option(writeOpts) | |
| } | |
| var f *excelize.File | |
| { | |
| var err error | |
| if _, err = os.Stat(writeOpts.FileName); err != nil && !os.IsNotExist(err) { | |
| return nil, err | |
| } | |
| if os.IsNotExist(err) { | |
| // File does not exist, create a new one | |
| f = excelize.NewFile() | |
| } else { | |
| // File exists, open it | |
| f, err = excelize.OpenFile(writeOpts.FileName) | |
| if err != nil { | |
| return nil, err | |
| } | |
| } | |
| } | |
| cellX, cellY := getCell(writeOpts.Cell) | |
| n, err := f.GetSheetIndex(writeOpts.Sheet) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Only attempt to clear the sheet if it exists | |
| if writeOpts.ClearSheet && n >= 0 { | |
| // Sheet exists, clear it | |
| if err := clearSheet(f, writeOpts.Sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| if n < 0 { | |
| // Sheet does not exist, create a new one | |
| if _, err := f.NewSheet(writeOpts.Sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| return &XLSXWriter{ | |
| fileName: writeOpts.FileName, | |
| f: f, | |
| cellX: cellX, | |
| cellY: cellY, | |
| sheet: writeOpts.Sheet, | |
| header: writeOpts.Header, | |
| }, nil |
| func setWriter(fileName string) (trdsql.Writer, error) { | ||
| dotExt := strings.TrimLeft(filepath.Ext(fileName), ".") | ||
| if OutFormat == "GUESS" && dotExt != "" { | ||
| OutFormat = strings.ToUpper(dotExt) | ||
| } | ||
|
|
||
| if strings.ToUpper(OutFormat) != "XLSX" { | ||
| return stdWriter(fileName) | ||
| } | ||
|
|
||
| // XLSX Writer | ||
| if fileName == "" { | ||
| return nil, fmt.Errorf("file name is required to output with XLSX") | ||
| } | ||
|
|
||
| if OutSheetName == "" { | ||
| OutSheetName = "Sheet1" | ||
| } | ||
|
|
||
| writer, err := xlsxsql.NewXLSXWriter( | ||
| xlsxsql.FileName(fileName), | ||
| xlsxsql.Sheet(OutSheetName), | ||
| xlsxsql.Cell(OutCell), | ||
| xlsxsql.ClearSheet(ClearSheet), | ||
| xlsxsql.Header(OutHeader), | ||
| ) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return writer, nil | ||
| } | ||
|
|
||
| func stdWriter(fileName string) (trdsql.Writer, error) { | ||
| var file io.Writer | ||
| if fileName == "" { | ||
| file = os.Stdout | ||
| } else { | ||
| f, err := os.Create(fileName) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| file = f | ||
| } | ||
| format := trdsql.OutputFormat(strings.ToUpper(OutFormat)) | ||
| w := trdsql.NewWriter( | ||
| trdsql.OutStream(file), | ||
| trdsql.OutHeader(OutHeader), | ||
| trdsql.OutFormat(format), | ||
| ) | ||
| return w, nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The setWriter and stdWriter functions have been introduced to handle output format determination and writer instantiation. This is a good approach for separating concerns and improving maintainability. However, there are a few points to consider:
- The error message in line 48 should be more descriptive. It's good practice to provide clear error messages that can help users understand what went wrong.
- The
stdWriterfunction creates a new file for writing without checking if the file already exists and without providing a way to append to or overwrite the existing file. This could lead to data loss if the file already contains data. - The
stdWriterfunction does not close the file it opens. This can lead to file descriptor leaks. Ensure that files are properly closed after their use.
Here is a proposed change for the error message and file handling:
if fileName == "" {
- return nil, fmt.Errorf("file name is required to output with XLSX")
+ return nil, fmt.Errorf("a valid file name is required to output in XLSX format")
}
...
f, err := os.Create(fileName)
+ defer f.Close()
if err != nil {
return nil, err
}Commitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
| func setWriter(fileName string) (trdsql.Writer, error) { | |
| dotExt := strings.TrimLeft(filepath.Ext(fileName), ".") | |
| if OutFormat == "GUESS" && dotExt != "" { | |
| OutFormat = strings.ToUpper(dotExt) | |
| } | |
| if strings.ToUpper(OutFormat) != "XLSX" { | |
| return stdWriter(fileName) | |
| } | |
| // XLSX Writer | |
| if fileName == "" { | |
| return nil, fmt.Errorf("file name is required to output with XLSX") | |
| } | |
| if OutSheetName == "" { | |
| OutSheetName = "Sheet1" | |
| } | |
| writer, err := xlsxsql.NewXLSXWriter( | |
| xlsxsql.FileName(fileName), | |
| xlsxsql.Sheet(OutSheetName), | |
| xlsxsql.Cell(OutCell), | |
| xlsxsql.ClearSheet(ClearSheet), | |
| xlsxsql.Header(OutHeader), | |
| ) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return writer, nil | |
| } | |
| func stdWriter(fileName string) (trdsql.Writer, error) { | |
| var file io.Writer | |
| if fileName == "" { | |
| file = os.Stdout | |
| } else { | |
| f, err := os.Create(fileName) | |
| if err != nil { | |
| return nil, err | |
| } | |
| file = f | |
| } | |
| format := trdsql.OutputFormat(strings.ToUpper(OutFormat)) | |
| w := trdsql.NewWriter( | |
| trdsql.OutStream(file), | |
| trdsql.OutHeader(OutHeader), | |
| trdsql.OutFormat(format), | |
| ) | |
| return w, nil | |
| } | |
| func setWriter(fileName string) (trdsql.Writer, error) { | |
| dotExt := strings.TrimLeft(filepath.Ext(fileName), ".") | |
| if OutFormat == "GUESS" && dotExt != "" { | |
| OutFormat = strings.ToUpper(dotExt) | |
| } | |
| if strings.ToUpper(OutFormat) != "XLSX" { | |
| return stdWriter(fileName) | |
| } | |
| // XLSX Writer | |
| if fileName == "" { | |
| return nil, fmt.Errorf("a valid file name is required to output in XLSX format") | |
| } | |
| if OutSheetName == "" { | |
| OutSheetName = "Sheet1" | |
| } | |
| writer, err := xlsxsql.NewXLSXWriter( | |
| xlsxsql.FileName(fileName), | |
| xlsxsql.Sheet(OutSheetName), | |
| xlsxsql.Cell(OutCell), | |
| xlsxsql.ClearSheet(ClearSheet), | |
| xlsxsql.Header(OutHeader), | |
| ) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return writer, nil | |
| } | |
| func stdWriter(fileName string) (trdsql.Writer, error) { | |
| var file io.Writer | |
| if fileName == "" { | |
| file = os.Stdout | |
| } else { | |
| f, err := os.Create(fileName) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer f.Close() | |
| file = f | |
| } | |
| format := trdsql.OutputFormat(strings.ToUpper(OutFormat)) | |
| w := trdsql.NewWriter( | |
| trdsql.OutStream(file), | |
| trdsql.OutHeader(OutHeader), | |
| trdsql.OutFormat(format), | |
| ) | |
| return w, nil | |
| } |
writer.go
Outdated
| // PostWrite function closes the XLSXWriter. | ||
| func (w *XLSXWriter) PostWrite() error { | ||
| if err := w.f.SaveAs(w.fileName); err != nil { | ||
| return err | ||
| } | ||
| return w.f.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PostWrite method attempts to save and close the XLSX file. However, if the SaveAs method fails, the file is not closed, which could lead to a resource leak. It's important to ensure that the file is closed regardless of whether SaveAs succeeds or fails. This can be achieved by using a defer statement to close the file, which will execute when the function returns.
+ defer w.f.Close()
if err := w.f.SaveAs(w.fileName); err != nil {
return err
}
- return w.f.Close()
+ return nilCommitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
| // PostWrite function closes the XLSXWriter. | |
| func (w *XLSXWriter) PostWrite() error { | |
| if err := w.f.SaveAs(w.fileName); err != nil { | |
| return err | |
| } | |
| return w.f.Close() | |
| func (w *XLSXWriter) PostWrite() error { | |
| defer w.f.Close() | |
| if err := w.f.SaveAs(w.fileName); err != nil { | |
| return err | |
| } | |
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| func TestXLSXWriter_WriteRow(t *testing.T) { | ||
| type opts struct { | ||
| fileName string | ||
| f *excelize.File | ||
| sheet string | ||
| cellName string | ||
| } | ||
| type args struct { | ||
| row []interface{} | ||
| columns []string | ||
| types []string | ||
| } | ||
| tests := []struct { | ||
| name string | ||
| fields opts | ||
| args args | ||
| wantErr bool | ||
| wantRows [][]string | ||
| }{ | ||
| { | ||
| name: "test1", | ||
| fields: opts{ | ||
| fileName: "dummy.xlsx", | ||
| f: excelize.NewFile(), | ||
| sheet: "Sheet1", | ||
| cellName: "A1", | ||
| }, | ||
| args: args{ | ||
| row: []interface{}{"a", "b", "c"}, | ||
| columns: []string{"A", "B", "C"}, | ||
| types: []string{"string", "string", "string"}, | ||
| }, | ||
| wantErr: false, | ||
| wantRows: [][]string{ | ||
| {"A", "B", "C"}, | ||
| {"a", "b", "c"}, | ||
| }, | ||
| }, | ||
| { | ||
| name: "test2", | ||
| fields: opts{ | ||
| fileName: "dummy.xlsx", | ||
| f: excelize.NewFile(), | ||
| sheet: "Sheet1", | ||
| cellName: "A2", | ||
| }, | ||
| args: args{ | ||
| row: []interface{}{"a", "b", "c"}, | ||
| columns: []string{"A", "B", "C"}, | ||
| types: []string{"string", "string", "string"}, | ||
| }, | ||
| wantErr: false, | ||
| wantRows: [][]string{ | ||
| nil, | ||
| {"A", "B", "C"}, | ||
| {"a", "b", "c"}, | ||
| }, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| w, err := NewXLSXWriter( | ||
| FileName(tt.fields.fileName), | ||
| Sheet(tt.fields.sheet), | ||
| Cell(tt.fields.cellName), | ||
| ClearSheet(true), | ||
| ) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if err := w.PreWrite(tt.args.columns, tt.args.types); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if err := w.WriteRow(tt.args.row, tt.args.columns); (err != nil) != tt.wantErr { | ||
| t.Errorf("XLSXWriter.WriteRow() error = %v, wantErr %v", err, tt.wantErr) | ||
| } | ||
| rows, err := w.f.GetRows(tt.fields.sheet) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| if !reflect.DeepEqual(rows, tt.wantRows) { | ||
| t.Errorf("XLSXWriter.WriteRow() rows = %#v, wantRows %#v", rows, tt.wantRows) | ||
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test cases in TestXLSXWriter_WriteRow are well-structured and cover different scenarios for writing rows to an XLSX file. However, there are a few points to consider:
- The
optsstruct contains afileNamefield that is not used in the test. If the file name is not necessary for the test's logic, it should be removed to avoid confusion. - The
ClearSheetoption is set totruein theNewXLSXWritercall, but the test does not verify if the sheet is actually cleared before writing. This should be tested to ensure theClearSheetfunctionality works as expected. - The test cases do not cover error scenarios extensively. It would be beneficial to add test cases that expect an error, such as passing invalid cell names or types, to ensure the error handling is robust.
- The test assumes that the
PreWritemethod will always succeed as it callst.Fatal(err)if an error occurs. It might be worth adding a test case wherePreWriteis expected to fail to ensure that it handles error conditions correctly.
Consider addressing these points to improve the test coverage and reliability.
- fileName stringCommitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
| func TestXLSXWriter_WriteRow(t *testing.T) { | |
| type opts struct { | |
| fileName string | |
| f *excelize.File | |
| sheet string | |
| cellName string | |
| } | |
| type args struct { | |
| row []interface{} | |
| columns []string | |
| types []string | |
| } | |
| tests := []struct { | |
| name string | |
| fields opts | |
| args args | |
| wantErr bool | |
| wantRows [][]string | |
| }{ | |
| { | |
| name: "test1", | |
| fields: opts{ | |
| fileName: "dummy.xlsx", | |
| f: excelize.NewFile(), | |
| sheet: "Sheet1", | |
| cellName: "A1", | |
| }, | |
| args: args{ | |
| row: []interface{}{"a", "b", "c"}, | |
| columns: []string{"A", "B", "C"}, | |
| types: []string{"string", "string", "string"}, | |
| }, | |
| wantErr: false, | |
| wantRows: [][]string{ | |
| {"A", "B", "C"}, | |
| {"a", "b", "c"}, | |
| }, | |
| }, | |
| { | |
| name: "test2", | |
| fields: opts{ | |
| fileName: "dummy.xlsx", | |
| f: excelize.NewFile(), | |
| sheet: "Sheet1", | |
| cellName: "A2", | |
| }, | |
| args: args{ | |
| row: []interface{}{"a", "b", "c"}, | |
| columns: []string{"A", "B", "C"}, | |
| types: []string{"string", "string", "string"}, | |
| }, | |
| wantErr: false, | |
| wantRows: [][]string{ | |
| nil, | |
| {"A", "B", "C"}, | |
| {"a", "b", "c"}, | |
| }, | |
| }, | |
| } | |
| for _, tt := range tests { | |
| t.Run(tt.name, func(t *testing.T) { | |
| w, err := NewXLSXWriter( | |
| FileName(tt.fields.fileName), | |
| Sheet(tt.fields.sheet), | |
| Cell(tt.fields.cellName), | |
| ClearSheet(true), | |
| ) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := w.PreWrite(tt.args.columns, tt.args.types); err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := w.WriteRow(tt.args.row, tt.args.columns); (err != nil) != tt.wantErr { | |
| t.Errorf("XLSXWriter.WriteRow() error = %v, wantErr %v", err, tt.wantErr) | |
| } | |
| rows, err := w.f.GetRows(tt.fields.sheet) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if !reflect.DeepEqual(rows, tt.wantRows) { | |
| t.Errorf("XLSXWriter.WriteRow() rows = %#v, wantRows %#v", rows, tt.wantRows) | |
| } | |
| }) | |
| } | |
| } | |
| func TestXLSXWriter_WriteRow(t *testing.T) { | |
| type opts struct { | |
| f *excelize.File | |
| sheet string | |
| cellName string | |
| } | |
| type args struct { | |
| row []interface{} | |
| columns []string | |
| types []string | |
| } | |
| tests := []struct { | |
| name string | |
| fields opts | |
| args args | |
| wantErr bool | |
| wantRows [][]string | |
| }{ | |
| { | |
| name: "test1", | |
| fields: opts{ | |
| f: excelize.NewFile(), | |
| sheet: "Sheet1", | |
| cellName: "A1", | |
| }, | |
| args: args{ | |
| row: []interface{}{"a", "b", "c"}, | |
| columns: []string{"A", "B", "C"}, | |
| types: []string{"string", "string", "string"}, | |
| }, | |
| wantErr: false, | |
| wantRows: [][]string{ | |
| {"A", "B", "C"}, | |
| {"a", "b", "c"}, | |
| }, | |
| }, | |
| { | |
| name: "test2", | |
| fields: opts{ | |
| f: excelize.NewFile(), | |
| sheet: "Sheet1", | |
| cellName: "A2", | |
| }, | |
| args: args{ | |
| row: []interface{}{"a", "b", "c"}, | |
| columns: []string{"A", "B", "C"}, | |
| types: []string{"string", "string", "string"}, | |
| }, | |
| wantErr: false, | |
| wantRows: [][]string{ | |
| nil, | |
| {"A", "B", "C"}, | |
| {"a", "b", "c"}, | |
| }, | |
| }, | |
| } | |
| for _, tt := range tests { | |
| t.Run(tt.name, func(t *testing.T) { | |
| w, err := NewXLSXWriter( | |
| Sheet(tt.fields.sheet), | |
| Cell(tt.fields.cellName), | |
| ClearSheet(true), | |
| ) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := w.PreWrite(tt.args.columns, tt.args.types); err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := w.WriteRow(tt.args.row, tt.args.columns); (err != nil) != tt.wantErr { | |
| t.Errorf("XLSXWriter.WriteRow() error = %v, wantErr %v", err, tt.wantErr) | |
| } | |
| rows, err := w.f.GetRows(tt.fields.sheet) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if !reflect.DeepEqual(rows, tt.wantRows) { | |
| t.Errorf("XLSXWriter.WriteRow() rows = %#v, wantRows %#v", rows, tt.wantRows) | |
| } | |
| }) | |
| } | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 4
Configuration used: CodeRabbit UI
Files selected for processing (2)
- cmd/query.go (2 hunks)
- writer.go (1 hunks)
Additional comments: 6
cmd/query.go (4)
3-13: The new import
github.com/noborus/xlsxsqlis introduced to support XLSX file output. Ensure that this package is properly vetted for security, performance, and compatibility with the existing codebase.17-33: The
execfunction has been updated to use the newsetWriterfunction. Ensure that the rest of the codebase and any documentation are updated to reflect the changes in how writers are set, especially if there are any external integrations or scripts that rely on the previous behavior.51-53: The default sheet name is set to "Sheet1" if not provided. Ensure that this default value is documented and communicated to users, as it affects the output when the sheet name is not explicitly specified.
69-86: In the
stdWriterfunction, the output format is determined and a new writer is created. It is important to ensure that theOutFormatis validated against a list of supported formats to prevent any unexpected behavior if an unsupported format is provided.writer.go (2)
3-8: The import of the
excelize/v2package is correct, but ensure that the version of theexcelizepackage used is compatible with the rest of the codebase and that it supports all the necessary features required by theXLSXWriter. If the package has been updated recently, check for any breaking changes that might affect the implementation.214-217: The
PostWritefunction is responsible for saving the XLSX file after all writing operations are complete. It correctly returns any error that occurs during the save operation. This is a critical step as it ensures that any issues with file saving are reported back to the caller. No issues are found in this part of the code.
| func setWriter(fileName string) (trdsql.Writer, error) { | ||
| dotExt := strings.TrimLeft(filepath.Ext(fileName), ".") | ||
| if OutFormat == "GUESS" && dotExt != "" { | ||
| OutFormat = strings.ToUpper(dotExt) | ||
| } | ||
|
|
||
| if strings.ToUpper(OutFormat) != "XLSX" { | ||
| return stdWriter(fileName) | ||
| } | ||
|
|
||
| // XLSX Writer | ||
| if fileName == "" { | ||
| return nil, fmt.Errorf("a valid file name is required to output in XLSX format") | ||
| } | ||
|
|
||
| if OutSheetName == "" { | ||
| OutSheetName = "Sheet1" | ||
| } | ||
|
|
||
| writer, err := xlsxsql.NewXLSXWriter( | ||
| xlsxsql.FileName(fileName), | ||
| xlsxsql.Sheet(OutSheetName), | ||
| xlsxsql.Cell(OutCell), | ||
| xlsxsql.ClearSheet(ClearSheet), | ||
| xlsxsql.Header(OutHeader), | ||
| ) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return writer, nil | ||
| } | ||
|
|
||
| func stdWriter(fileName string) (trdsql.Writer, error) { | ||
| var file io.Writer | ||
| if fileName == "" { | ||
| file = os.Stdout | ||
| } else { | ||
| f, err := os.Create(fileName) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| file = f | ||
| } | ||
| format := trdsql.OutputFormat(strings.ToUpper(OutFormat)) | ||
| w := trdsql.NewWriter( | ||
| trdsql.OutStream(file), | ||
| trdsql.OutHeader(OutHeader), | ||
| trdsql.OutFormat(format), | ||
| ) | ||
| return w, nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The setWriter and stdWriter functions have been introduced to handle output formatting based on the file extension or specified output format. It is important to ensure that the OutFormat global variable is properly sanitized and validated before use to prevent any potential security risks or logical errors. Additionally, the error handling in setWriter when the file name is empty or an error occurs while creating a new XLSX writer is appropriate. However, in stdWriter, when creating a file with os.Create, the file is not being closed, which can lead to file descriptor leaks. It is recommended to defer the close operation after opening the file.
f, err := os.Create(fileName)
if err != nil {
return nil, err
}
+ defer f.Close()Commitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
| func setWriter(fileName string) (trdsql.Writer, error) { | |
| dotExt := strings.TrimLeft(filepath.Ext(fileName), ".") | |
| if OutFormat == "GUESS" && dotExt != "" { | |
| OutFormat = strings.ToUpper(dotExt) | |
| } | |
| if strings.ToUpper(OutFormat) != "XLSX" { | |
| return stdWriter(fileName) | |
| } | |
| // XLSX Writer | |
| if fileName == "" { | |
| return nil, fmt.Errorf("a valid file name is required to output in XLSX format") | |
| } | |
| if OutSheetName == "" { | |
| OutSheetName = "Sheet1" | |
| } | |
| writer, err := xlsxsql.NewXLSXWriter( | |
| xlsxsql.FileName(fileName), | |
| xlsxsql.Sheet(OutSheetName), | |
| xlsxsql.Cell(OutCell), | |
| xlsxsql.ClearSheet(ClearSheet), | |
| xlsxsql.Header(OutHeader), | |
| ) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return writer, nil | |
| } | |
| func stdWriter(fileName string) (trdsql.Writer, error) { | |
| var file io.Writer | |
| if fileName == "" { | |
| file = os.Stdout | |
| } else { | |
| f, err := os.Create(fileName) | |
| if err != nil { | |
| return nil, err | |
| } | |
| file = f | |
| } | |
| format := trdsql.OutputFormat(strings.ToUpper(OutFormat)) | |
| w := trdsql.NewWriter( | |
| trdsql.OutStream(file), | |
| trdsql.OutHeader(OutHeader), | |
| trdsql.OutFormat(format), | |
| ) | |
| return w, nil | |
| } | |
| func stdWriter(fileName string) (trdsql.Writer, error) { | |
| var file io.Writer | |
| if fileName == "" { | |
| file = os.Stdout | |
| } else { | |
| f, err := os.Create(fileName) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer f.Close() | |
| file = f | |
| } | |
| format := trdsql.OutputFormat(strings.ToUpper(OutFormat)) | |
| w := trdsql.NewWriter( | |
| trdsql.OutStream(file), | |
| trdsql.OutHeader(OutHeader), | |
| trdsql.OutFormat(format), | |
| ) | |
| return w, nil | |
| } |
| if fileName == "" { | ||
| return nil, fmt.Errorf("a valid file name is required to output in XLSX format") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message for an empty file name when outputting in XLSX format is clear and user-friendly. However, consider also validating the file name for any illegal characters or patterns that might cause issues on the file system.
| // NewXLSXWriter function takes an io.Writer and trdsql.WriteOpts, and returns a new XLSXWriter. | ||
| func NewXLSXWriter(options ...WriteOpt) (*XLSXWriter, error) { | ||
| writeOpts := &WriteOpts{ | ||
| ErrStream: os.Stderr, | ||
| FileName: "", | ||
| Sheet: "Sheet1", | ||
| Cell: "", | ||
| ClearSheet: false, | ||
| Header: true, | ||
| } | ||
| for _, option := range options { | ||
| option(writeOpts) | ||
| } | ||
|
|
||
| f, err := openXLSXFile(writeOpts.FileName) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| cellX, cellY := getCell(writeOpts.Cell) | ||
|
|
||
| n, err := f.GetSheetIndex(writeOpts.Sheet) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| // Only attempt to clear the sheet if it exists. | ||
| if writeOpts.ClearSheet && n >= 0 { | ||
| // Sheet exists,clear it | ||
| if err := clearSheet(f, writeOpts.Sheet); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| if n < 0 { | ||
| // Sheet does not exist, create a new one | ||
| if _, err := f.NewSheet(writeOpts.Sheet); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| return &XLSXWriter{ | ||
| fileName: writeOpts.FileName, | ||
| f: f, | ||
| cellX: cellX, | ||
| cellY: cellY, | ||
| sheet: writeOpts.Sheet, | ||
| header: writeOpts.Header, | ||
| }, nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The NewXLSXWriter function is responsible for creating a new XLSXWriter instance. It correctly applies the provided options and handles the creation or opening of the XLSX file. However, there is a potential issue with the handling of the Sheet option. If the sheet does not exist and ClearSheet is true, the code attempts to clear a non-existent sheet before creating it. The order of operations should be adjusted to create the sheet first if it does not exist, and then clear it if necessary.
- if n < 0 {
- // Sheet does not exist, create a new one
- if _, err := f.NewSheet(writeOpts.Sheet); err != nil {
- return nil, err
- }
- }
- // Only attempt to clear the sheet if it exists.
- if writeOpts.ClearSheet && n >= 0 {
+ if n < 0 {
+ // Sheet does not exist, create a new one
+ if _, err := f.NewSheet(writeOpts.Sheet); err != nil {
+ return nil, err
+ }
+ } else if writeOpts.ClearSheet {
+ // Sheet exists, clear itCommitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
| // NewXLSXWriter function takes an io.Writer and trdsql.WriteOpts, and returns a new XLSXWriter. | |
| func NewXLSXWriter(options ...WriteOpt) (*XLSXWriter, error) { | |
| writeOpts := &WriteOpts{ | |
| ErrStream: os.Stderr, | |
| FileName: "", | |
| Sheet: "Sheet1", | |
| Cell: "", | |
| ClearSheet: false, | |
| Header: true, | |
| } | |
| for _, option := range options { | |
| option(writeOpts) | |
| } | |
| f, err := openXLSXFile(writeOpts.FileName) | |
| if err != nil { | |
| return nil, err | |
| } | |
| cellX, cellY := getCell(writeOpts.Cell) | |
| n, err := f.GetSheetIndex(writeOpts.Sheet) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Only attempt to clear the sheet if it exists. | |
| if writeOpts.ClearSheet && n >= 0 { | |
| // Sheet exists,clear it | |
| if err := clearSheet(f, writeOpts.Sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| if n < 0 { | |
| // Sheet does not exist, create a new one | |
| if _, err := f.NewSheet(writeOpts.Sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| return &XLSXWriter{ | |
| fileName: writeOpts.FileName, | |
| f: f, | |
| cellX: cellX, | |
| cellY: cellY, | |
| sheet: writeOpts.Sheet, | |
| header: writeOpts.Header, | |
| }, nil | |
| } | |
| func NewXLSXWriter(options ...WriteOpt) (*XLSXWriter, error) { | |
| writeOpts := &WriteOpts{ | |
| ErrStream: os.Stderr, | |
| FileName: "", | |
| Sheet: "Sheet1", | |
| Cell: "", | |
| ClearSheet: false, | |
| Header: true, | |
| } | |
| for _, option := range options { | |
| option(writeOpts) | |
| } | |
| f, err := openXLSXFile(writeOpts.FileName) | |
| if err != nil { | |
| return nil, err | |
| } | |
| cellX, cellY := getCell(writeOpts.Cell) | |
| n, err := f.GetSheetIndex(writeOpts.Sheet) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if n < 0 { | |
| // Sheet does not exist, create a new one | |
| if _, err := f.NewSheet(writeOpts.Sheet); err != nil { | |
| return nil, err | |
| } | |
| } else if writeOpts.ClearSheet { | |
| // Sheet exists, clear it | |
| if err := clearSheet(f, writeOpts.Sheet); err != nil { | |
| return nil, err | |
| } | |
| } | |
| return &XLSXWriter{ | |
| fileName: writeOpts.FileName, | |
| f: f, | |
| cellX: cellX, | |
| cellY: cellY, | |
| sheet: writeOpts.Sheet, | |
| header: writeOpts.Header, | |
| }, nil | |
| } |
| // PreWrite function opens the XLSXWriter. | ||
| func (w *XLSXWriter) PreWrite(columns []string, types []string) error { | ||
| if !w.header { | ||
| return nil | ||
| } | ||
| // Write header | ||
| for i, v := range columns { | ||
| cell, err := excelize.CoordinatesToCellName(w.cellX+i+1, w.cellY+1) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| w.f.SetCellValue(w.sheet, cell, v) | ||
| } | ||
| w.rowID++ | ||
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PreWrite function is designed to write the header to the XLSX file if the header flag is set. It correctly increments the rowID after writing the header. However, the function does not handle the case where the header writing fails. If an error occurs during the header writing process, it should be returned immediately to prevent further operations on a potentially corrupted file.
- cell, err := excelize.CoordinatesToCellName(w.cellX+i+1, w.cellY+1)
- if err != nil {
- return err
- }
- w.f.SetCellValue(w.sheet, cell, v)
+ cell, err := excelize.CoordinatesToCellName(w.cellX+i+1, w.cellY+1)
+ if err != nil {
+ return err
+ }
+ if err := w.f.SetCellValue(w.sheet, cell, v); err != nil {
+ return err
+ }Commitable suggestion
[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.
| // PreWrite function opens the XLSXWriter. | |
| func (w *XLSXWriter) PreWrite(columns []string, types []string) error { | |
| if !w.header { | |
| return nil | |
| } | |
| // Write header | |
| for i, v := range columns { | |
| cell, err := excelize.CoordinatesToCellName(w.cellX+i+1, w.cellY+1) | |
| if err != nil { | |
| return err | |
| } | |
| w.f.SetCellValue(w.sheet, cell, v) | |
| } | |
| w.rowID++ | |
| return nil | |
| // PreWrite function opens the XLSXWriter. | |
| func (w *XLSXWriter) PreWrite(columns []string, types []string) error { | |
| if !w.header { | |
| return nil | |
| } | |
| // Write header | |
| for i, v := range columns { | |
| cell, err := excelize.CoordinatesToCellName(w.cellX+i+1, w.cellY+1) | |
| if err != nil { | |
| return err | |
| } | |
| if err := w.f.SetCellValue(w.sheet, cell, v); err != nil { | |
| return err | |
| } | |
| } | |
| w.rowID++ | |
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Status
Actionable comments generated: 6
Configuration used: CodeRabbit UI
Files selected for processing (1)
- writer.go (1 hunks)
Additional comments: 1
writer.go (1)
- 199-215: The
WriteRowfunction writes a row to the XLSX file. It currently skipsnilvalues without writing them to the file. Depending on the use case, it might be necessary to write empty cells fornilvalues to maintain the structure of the data in the XLSX file. Consider whethernilvalues should be represented as empty cells in the output and adjust the code accordingly.
| // NewXLSXWriter function takes an io.Writer and trdsql.WriteOpts, and returns a new XLSXWriter. | ||
| func NewXLSXWriter(options ...WriteOpt) (*XLSXWriter, error) { | ||
| writeOpts := &WriteOpts{ | ||
| ErrStream: os.Stderr, | ||
| FileName: "", | ||
| Sheet: "Sheet1", | ||
| Cell: "", | ||
| ClearSheet: false, | ||
| Header: true, | ||
| } | ||
| for _, option := range options { | ||
| option(writeOpts) | ||
| } | ||
|
|
||
| f, err := openXLSXFile(writeOpts.FileName) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| cellX, cellY := getCell(writeOpts.Cell) | ||
|
|
||
| n, err := f.GetSheetIndex(writeOpts.Sheet) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| // Only attempt to clear the sheet if it exists. | ||
| if writeOpts.ClearSheet && n >= 0 { | ||
| // Sheet exists,clear it | ||
| if err := clearSheet(f, writeOpts.Sheet); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| if n < 0 { | ||
| // Sheet does not exist, create a new one | ||
| if _, err := f.NewSheet(writeOpts.Sheet); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| return &XLSXWriter{ | ||
| fileName: writeOpts.FileName, | ||
| f: f, | ||
| cellX: cellX, | ||
| cellY: cellY, | ||
| sheet: writeOpts.Sheet, | ||
| header: writeOpts.Header, | ||
| }, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The NewXLSXWriter function is responsible for creating a new XLSXWriter instance. It is important to ensure that the error stream (ErrStream) is properly used throughout the XLSXWriter methods to report errors. Currently, the ErrStream is set in the WriteOpts but not used anywhere in the XLSXWriter methods. This could lead to a situation where errors are not reported or logged as expected, which can make debugging difficult. Consider using the ErrStream in methods like PreWrite, WriteRow, and PostWrite to handle and log errors appropriately.
| // openXLSXFile function opens the XLSX file. | ||
| func openXLSXFile(fileName string) (*excelize.File, error) { | ||
| var f *excelize.File | ||
| var err error | ||
| if _, err = os.Stat(fileName); err != nil && !os.IsNotExist(err) { | ||
| return nil, err | ||
| } | ||
| if os.IsNotExist(err) { | ||
| // File does not exist, create a new one | ||
| f = excelize.NewFile() | ||
| } else { | ||
| // File exists, open it | ||
| f, err = excelize.OpenFile(fileName) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
| return f, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The openXLSXFile function checks if the file exists and either creates a new file or opens an existing one. However, there is no check to ensure that the file is not a directory, which could lead to unexpected behavior if a directory with the expected file name exists. It's recommended to add a check to ensure that the specified fileName is not a directory before proceeding with file operations.
| func getCell(cellName string) (int, int) { | ||
| if cellName == "" { | ||
| return 0, 0 | ||
| } | ||
| x, y, err := excelize.CellNameToCoordinates(cellName) | ||
| if err != nil { | ||
| return 0, 0 | ||
| } | ||
| return x - 1, y - 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getCell function returns (0, 0) when an error occurs in excelize.CellNameToCoordinates. This could lead to silent failures where the caller of getCell may not realize that an invalid cell name was provided. It would be better to return the error to the caller so that it can be handled appropriately. This change would require updating the getCell function signature to return an error and modifying the calling code to handle the error.
| // clearSheet function clears the sheet. | ||
| func clearSheet(f *excelize.File, sheet string) error { | ||
| rows, err := f.GetRows(sheet) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| for i, row := range rows { | ||
| for j := range row { | ||
| axis, err := excelize.CoordinatesToCellName(j+1, i+1) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| f.SetCellStr(sheet, axis, "") | ||
| } | ||
| } | ||
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The clearSheet function iterates over all rows and columns to clear the sheet by setting each cell to an empty string. This approach can be inefficient for large sheets as it involves a lot of individual write operations. A more efficient approach would be to delete the sheet and recreate it, which can be done with the excelize library's DeleteSheet and NewSheet methods. This would reduce the number of operations and potentially improve performance.
| // PreWrite function opens the XLSXWriter. | ||
| func (w *XLSXWriter) PreWrite(columns []string, types []string) error { | ||
| if !w.header { | ||
| return nil | ||
| } | ||
| // Write header | ||
| for i, v := range columns { | ||
| cell, err := excelize.CoordinatesToCellName(w.cellX+i+1, w.cellY+1) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if err := w.f.SetCellValue(w.sheet, cell, v); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| w.rowID++ | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PreWrite function writes the header to the XLSX file if the header flag is set. However, there is no check to ensure that the columns slice is not empty before attempting to write the header. Writing an empty header could be considered a logical error, so it's recommended to add a check for an empty columns slice and handle it accordingly, either by returning an error or skipping the header writing process.
| // PostWrite function closes the XLSXWriter. | ||
| func (w *XLSXWriter) PostWrite() error { | ||
| return w.f.SaveAs(w.fileName) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PostWrite function saves the XLSX file using the SaveAs method. It's important to ensure that the fileName is not empty before attempting to save the file, as an empty file name could lead to an error or unexpected behavior. Consider adding a check for an empty fileName and handling the error if necessary.
Added output file name and output sheet name to output to xlsx.
If an output file name with an xlsx extension is specified, an xlsx file will be output.
Summary by CodeRabbit
New Features
Enhancements
Bug Fixes