Skip to content
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

Wrapper for long lines in help #770

Merged
merged 11 commits into from
Nov 10, 2024
Merged

Wrapper for long lines in help #770

merged 11 commits into from
Nov 10, 2024

Conversation

Cerebrovinny
Copy link
Collaborator

@Cerebrovinny Cerebrovinny commented Nov 9, 2024

what

  • Implemented a new terminal-aware text wrapping system for CLI help output
  • Added responsive width handling based on terminal size with fallback values
  • Introduced custom usage template handling for consistent help text formatting
  • Created dedicated terminal writer component for automatic text wrapping

why

  • Improves readability of CLI help text by ensuring content fits within terminal width
  • Provides better user experience with dynamic text wrapping based on terminal size
  • Standardizes help text formatting across all commands
  • Fixes potential issues with text overflow in narrow terminal windows

references

This PR is also inspired from these PR/issues below:

Before:
Screenshot 2024-11-09 at 16 15 26
Screenshot 2024-11-09 at 16 31 54

After:
Screenshot 2024-11-09 at 18 19 56

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced command-line interface with improved help information formatting.
    • Introduced a responsive terminal writer for better text output handling based on terminal width.
    • Custom usage templates for commands to improve clarity and usability.
  • Bug Fixes

    • Improved error handling for configuration file loading and help command execution, including graceful handling of missing files.
  • Documentation

    • Updated help command output to include a styled logo and better usage instructions.

These updates aim to enhance user experience by providing clearer command usage and more robust error management.

@Cerebrovinny Cerebrovinny changed the title WIP: Atmos needs to wrap long lines in help Wrapper for long lines in help Nov 9, 2024
@Cerebrovinny Cerebrovinny marked this pull request as ready for review November 9, 2024 18:48
@Cerebrovinny Cerebrovinny requested review from a team as code owners November 9, 2024 18:48
Copy link
Contributor

coderabbitai bot commented Nov 9, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The changes in this pull request primarily enhance the command-line interface functionality within the cmd/root.go file and introduce new components for managing command usage and help output. Key updates include improved error handling, custom template integration, and the addition of new types and methods for formatting help information. New files such as help_printer.go, templater.go, and term_writer.go introduce structured ways to manage command usage templates and terminal-aware text output.

Changes

File Path Change Summary
cmd/root.go - Added import for templates.
- Updated PersistentPreRun for command silence behavior.
- Refined error handling in Execute for help flag.
- Enhanced initialization of CLI configurations.
- Expanded init function for wrapped flag usages and custom usage template.
- Modified help function to display usage after logo.
go.mod - Added dependencies: github.com/mitchellh/go-wordwrap v1.0.0, golang.org/x/term v0.26.0.
- Removed: github.com/mitchellh/go-wordwrap v1.0.0, golang.org/x/sys v0.26.0, golang.org/x/term v0.25.0.
- Updated: golang.org/x/sys to v0.27.0, golang.org/x/term to v0.26.0.
internal/tui/templates/help_printer.go - Added HelpFlagPrinter type with NewHelpFlagPrinter constructor and PrintHelpFlag method for formatting help information.
internal/tui/templates/templater.go - Introduced Templater struct with methods for setting custom usage templates and generating main usage templates.
internal/tui/templates/term/term_writer.go - Added TerminalWriter type with NewResponsiveWriter constructor and methods for automatic line wrapping based on terminal width.

Possibly related PRs

Suggested labels

minor

Suggested reviewers

  • aknysh
  • osterman

📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 194f692 and f9f33e6.

📒 Files selected for processing (3)
  • cmd/root.go (3 hunks)
  • internal/tui/templates/help_printer.go (1 hunks)
  • internal/tui/templates/templater.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • cmd/root.go
  • internal/tui/templates/help_printer.go
  • internal/tui/templates/templater.go

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or @auto-summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @auto-title anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (8)
internal/tui/templates/term/term_writer.go (3)

11-22: Consider enhancing the documentation with width constants explanation.

The type documentation is good, but adding comments for the width constants would help future maintainers understand the reasoning behind these specific values.

 const (
+	// maxWidth is the maximum allowed line width, suitable for wide terminals
 	maxWidth    = 120
+	// mediumWidth is the standard terminal width
 	mediumWidth = 100
+	// minWidth is the minimum preferred width before falling back to actual terminal width
 	minWidth    = 80
 )

24-59: Consider wrapping errors for better debugging context.

The constructor's logic is solid, but error handling could be enhanced by wrapping the terminal size error with additional context.

 	width, _, err := term.GetSize(int(file.Fd()))
 	if err != nil {
-		return w
+		return w // Consider: fmt.Errorf("failed to get terminal size: %w", err)
 	}

61-79: LGTM! Consider clarifying the io.Writer contract implementation.

The Write method correctly implements the io.Writer interface, particularly in maintaining the original length return value. The implementation is clean and handles edge cases well.

Consider adding a more detailed comment about the io.Writer contract:

-	// Preserving the original length for correct return value
+	// io.Writer contract requires returning the length of p regardless of
+	// how many bytes were actually written to the underlying writer
 	originalLen := len(p)
internal/tui/templates/help_printer.go (2)

19-38: Consider enhancing error handling in the constructor.

While the constructor works well, it could benefit from explicit error handling when terminal width detection fails.

Consider this enhancement:

-func NewHelpFlagPrinter(out io.Writer, wrapLimit uint) *HelpFlagPrinter {
+func NewHelpFlagPrinter(out io.Writer, wrapLimit uint) (*HelpFlagPrinter, error) {
 	termWriter := term.NewResponsiveWriter(out)
 
 	// Get the actual terminal width from the writer
 	if tw, ok := termWriter.(*term.TerminalWriter); ok {
-		wrapLimit = tw.GetWidth()
+		width, err := tw.GetWidth()
+		if err != nil {
+			return nil, fmt.Errorf("failed to get terminal width: %w", err)
+		}
+		wrapLimit = width
 	} else if wrapLimit < minWidth {
 		wrapLimit = minWidth
 	}
 
-	return &HelpFlagPrinter{
+	return &HelpFlagPrinter{
 		wrapLimit: wrapLimit,
 		out:       termWriter,
-	}
+	}, nil
}

62-78: Consider optimizing string operations for better performance.

While the wrapping logic works correctly, we can optimize the string operations for better performance, especially when handling many flags.

Consider these optimizations:

+	var sb strings.Builder
 	wrapped := wordwrap.WrapString(description, uint(descWidth))
 	lines := strings.Split(wrapped, "\n")
 
-	fmt.Fprintf(p.out, "%s%s%s\n",
-		prefix.String(),
-		strings.Repeat(" ", 4),
-		lines[0])
+	sb.WriteString(prefix.String())
+	sb.WriteString("    ")
+	sb.WriteString(lines[0])
+	sb.WriteRune('\n')
 
 	if len(lines) > 1 {
 		indent := strings.Repeat(" ", prefixLen+4)
 		for _, line := range lines[1:] {
-			fmt.Fprintf(p.out, "%s%s\n", indent, line)
+			sb.WriteString(indent)
+			sb.WriteString(line)
+			sb.WriteRune('\n')
 		}
 	}
 
-	fmt.Fprintln(p.out)
+	sb.WriteRune('\n')
+	_, _ = p.out.Write([]byte(sb.String()))
internal/tui/templates/templater.go (2)

12-29: Consider enhancing documentation and parameter usage.

While the implementation is solid, there are two areas for improvement:

  1. The Templater struct's documentation could be more detailed about its purpose and usage.
  2. The b *boa.Boa parameter in SetCustomUsageFunc is currently unused.

Consider adding more detailed documentation:

-// Templater handles the generation and management of command usage templates.
+// Templater handles the generation and management of command usage templates.
+// It provides a structured way to customize how command usage information is
+// displayed to users in the CLI, ensuring consistent formatting and presentation.

Also, if the boa parameter isn't needed, consider removing it:

-func SetCustomUsageFunc(cmd *cobra.Command, b *boa.Boa) error {
+func SetCustomUsageFunc(cmd *cobra.Command) error {

31-57: Consider modularizing the template definition.

The template is comprehensive but could benefit from better maintainability.

Consider these improvements:

  1. Break down the template into smaller, reusable components:
var (
    usageTemplate = `Usage:{{if .Runnable}}...{{end}}`
    aliasTemplate = `Aliases:...`
    // ... more components
)

func MainUsageTemplate() string {
    return strings.Join([]string{
        usageTemplate,
        aliasTemplate,
        // ... more components
    }, "\n\n")
}
  1. Add template validation to catch syntax errors early:
func validateTemplate(tmpl string) error {
    _, err := template.New("test").Parse(tmpl)
    return err
}
cmd/root.go (1)

111-121: Consider adding error handling, brave developer!

While the template function integration is solid, the SetCustomUsageFunc call could benefit from error handling to maintain robustness.

Consider updating the code like this:

 // Set custom usage template
-templates.SetCustomUsageFunc(RootCmd, nil)
+if err := templates.SetCustomUsageFunc(RootCmd, nil); err != nil {
+    u.LogErrorAndExit(schema.CliConfiguration{}, fmt.Errorf("failed to set custom usage template: %w", err))
+}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3342e8e and af1a9a6.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • cmd/root.go (3 hunks)
  • go.mod (3 hunks)
  • internal/tui/templates/help_printer.go (1 hunks)
  • internal/tui/templates/templater.go (1 hunks)
  • internal/tui/templates/term/term_writer.go (1 hunks)
🔇 Additional comments (10)
internal/tui/templates/term/term_writer.go (2)

3-9: Well-chosen dependencies!

The imports demonstrate good choices for terminal handling and text wrapping functionality. Using golang.org/x/term for terminal operations and mitchellh/go-wordwrap for text wrapping is a solid approach.


1-79: Consider adding unit tests for edge cases.

The implementation looks solid! To ensure reliability, consider adding unit tests for:

  1. Non-terminal writer cases
  2. Terminal size detection failures
  3. Various width scenarios (max, medium, min, custom)
  4. Zero-width edge case

Let's check if tests exist:

Would you like me to help generate a test suite for this implementation?

internal/tui/templates/help_printer.go (2)

1-17: LGTM! Well-structured imports and sensible constants.

The package structure and constant definitions provide a solid foundation for the help text formatting functionality.


40-60: LGTM! Well-structured flag formatting logic.

The method handles all flag variations (shorthand, long form, boolean, non-boolean) with proper formatting and default value handling.

internal/tui/templates/templater.go (1)

1-10: LGTM! Clean package structure with focused imports.

The package structure is well-organized with clear dependency choices for CLI functionality.

cmd/root.go (3)

14-14: LGTM! Clean import addition, warrior!

The templates package import is well-placed and aligns perfectly with the PR's objective to enhance help text formatting.


Line range hint 26-37: Well-crafted battle strategy for help handling!

The PersistentPreRun implementation shows excellent attention to detail:

  • Properly detects both help command and help flag
  • Intelligently toggles silence behavior
  • Clear and maintainable logic

139-141: Verify the necessity of additional Usage call.

The command.Usage() call after b.HelpFunc might be redundant as the help function typically handles usage display already.

Let's check how other commands handle this:

go.mod (2)

33-33: Dependencies align well with the PR objectives!

The new dependencies support the terminal-aware text wrapping system:

  • go-wordwrap: Provides efficient text wrapping capabilities
  • golang.org/x/term: Enables terminal width detection

These additions are precisely what we need for implementing responsive CLI help formatting.

Also applies to: 45-45


245-245: System dependency update looks good!

The update to golang.org/x/sys v0.27.0 is a stable release and maintains compatibility with the new terminal features.

internal/tui/templates/templater.go Outdated Show resolved Hide resolved
@osterman
Copy link
Member

osterman commented Nov 9, 2024

Can we align the columns

image

@Cerebrovinny
Copy link
Collaborator Author

Can we align the columns

image

columns should be aligned now @osterman

Screenshot 2024-11-09 at 21 23 37

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (4)
internal/tui/templates/help_printer.go (2)

13-15: Consider reducing the minWidth constant for better compatibility.

The current minWidth of 80 characters might be too wide for some terminal windows. Consider reducing it to 60 characters to better support:

  • Mobile terminals
  • Split terminal windows
  • Smaller viewport sizes
const (
	defaultOffset = 10
-	minWidth      = 80
+	minWidth      = 60
	flagIndent    = "    "
)

32-51: Enhance helper function clarity and maintainability.

The calculateMaxFlagLength function works well but could be improved:

  1. Extract the padding value (4) as a constant
  2. Add documentation explaining the length calculation
+const flagLengthPadding = 4
+
+// calculateMaxFlagLength determines the maximum length needed to display any flag
+// in the FlagSet. It accounts for:
+// - The initial indent
+// - Short flag format (-x)
+// - Long flag format (--xxx)
+// - Type information for non-boolean flags
+// - Additional padding for alignment
func calculateMaxFlagLength(flags *pflag.FlagSet) int {
	maxLen := 0
	flags.VisitAll(func(flag *pflag.Flag) {
		length := len(flagIndent)
		if len(flag.Shorthand) > 0 {
			length += len(fmt.Sprintf("-%s, --%s", flag.Shorthand, flag.Name))
		} else {
			length += len(fmt.Sprintf("    --%s", flag.Name))
		}

		if flag.Value.Type() != "bool" {
			length += len(fmt.Sprintf(" %s", flag.Value.Type()))
		}

		if length > maxLen {
			maxLen = length
		}
	})
-	return maxLen + 4
+	return maxLen + flagLengthPadding
}
internal/tui/templates/templater.go (2)

12-15: Consider enhancing struct documentation, warrior!

While the struct itself is well-documented, the UsageTemplate field would benefit from a description of its purpose and expected format.

 // Templater handles the generation and management of command usage templates.
 type Templater struct {
+    // UsageTemplate defines the format string used to generate command usage output.
+    // It supports cobra's template syntax for command documentation.
     UsageTemplate string
 }

32-57: Consider improving template maintainability, noble template crafter!

While the template is comprehensive, breaking it into smaller, named constants would improve maintainability and make updates easier.

+const (
+    usageSection = `Usage:{{if .Runnable}}
+  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
+  {{.CommandPath}} [command]{{end}}`
+
+    aliasesSection = `{{if gt (len .Aliases) 0}}
+
+Aliases:
+  {{.NameAndAliases}}{{end}}`
+    // ... other sections ...
+)
+
 func MainUsageTemplate() string {
-    return `Usage:{{if .Runnable}}
-  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
-  {{.CommandPath}} [command]{{end}}...`
+    return strings.Join([]string{
+        usageSection,
+        aliasesSection,
+        // ... other sections ...
+    }, "")
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between af1a9a6 and 9b72ca7.

📒 Files selected for processing (2)
  • internal/tui/templates/help_printer.go (1 hunks)
  • internal/tui/templates/templater.go (1 hunks)
🔇 Additional comments (2)
internal/tui/templates/help_printer.go (1)

1-91: Overall implementation looks solid! 💪

The code successfully implements terminal-aware text wrapping for CLI help output, aligning perfectly with the PR objectives. The structure is clean, and the logic is sound. While I've suggested some improvements above, they're enhancements rather than blockers.

internal/tui/templates/templater.go (1)

63-74: Verify HelpFlagPrinter implementation, fellow code warrior!

Let's verify the implementation of NewHelpFlagPrinter to ensure it handles flag formatting correctly.

✅ Verification successful

HelpFlagPrinter implementation is battle-ready, warrior!

The implementation is solid and well-structured:

  • Properly handles both short and long flag formats
  • Correctly calculates spacing and indentation
  • Handles flag types and default values
  • Implements word wrapping for descriptions
  • Uses consistent formatting with proper alignment

The lack of tests is not ideal, but the implementation itself is robust and follows good practices. The NewHelpFlagPrinter constructor and its usage in WrappedFlagUsages are correctly integrated.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for NewHelpFlagPrinter implementation
echo "Searching for HelpFlagPrinter implementation..."
rg -t go "type.*HelpFlagPrinter.*struct" 
rg -t go "func.*NewHelpFlagPrinter.*"

# Search for any tests covering this functionality
echo "Searching for related tests..."
rg -t go "Test.*HelpFlagPrinter.*"

Length of output: 499


Script:

#!/bin/bash
# Let's examine the HelpFlagPrinter implementation
echo "=== HelpFlagPrinter Implementation ==="
cat internal/tui/templates/help_printer.go

# Check for any test files in the same directory
echo -e "\n=== Related Test Files ==="
fd -e go test internal/tui/templates/

Length of output: 2327

internal/tui/templates/help_printer.go Outdated Show resolved Hide resolved
internal/tui/templates/help_printer.go Outdated Show resolved Hide resolved
internal/tui/templates/templater.go Outdated Show resolved Hide resolved
@osterman
Copy link
Member

osterman commented Nov 9, 2024

@Cerebrovinny let's not put the type (string) into it's own column because it uses up a lot of white space. Put the type in the same column as the argument.

@Cerebrovinny
Copy link
Collaborator Author

@Cerebrovinny let's not put the type (string) into it's own column because it uses up a lot of white space. Put the type in the same column as the argument.

implemented this now
CleanShot 2024-11-10 at 16 42 06

@aknysh aknysh added the minor New features that do not break anything label Nov 10, 2024
Copy link
Member

@aknysh aknysh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @Cerebrovinny

@aknysh aknysh merged commit ffb64eb into main Nov 10, 2024
24 checks passed
@aknysh aknysh deleted the wrap-long-lines branch November 10, 2024 19:57
Copy link

These changes were released in v1.102.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
minor New features that do not break anything
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants