Skip to content

Commit

Permalink
refactor: updates Package Structure, Add Interfaces, and Enhance Test…
Browse files Browse the repository at this point in the history
…ing (#29)

* refactor: remove main_test.go

Removed main_test.go as its functionality is now integrated into more specific test files.

* refactor: remove main.go

Removed main.go file to migrate functionality to the more appropriately named cli/main.go.

* feat: define options configuration struct

Define a configuration struct for command line options to manage and parse command line arguments effectively.

* feat: introduce content and clipboard interfaces

Introduce interfaces for content reading and clipboard writing to enhance flexibility and testability.

* refactor: restructure command line argument handling

Refactor command line argument handling into a dedicated CLI package for improved organization and clarity.

* feat: add ClipboardWriter as a parameter to Run

- Modify the Run function to accept a ClipboardWriter as a parameter instead of creating a DefaultClipboardWriter directly. This would enable easier mocking of the clipboard for testing.

* feat: update clipper.Run() with the default clipboard writer

* test: add comprehensive unit tests for clipper package

This commit introduces a new `tests/clipper_test.go` file, which includes comprehensive unit tests for the clipper package. The tests cover various components including:
- FileContentReader: Reads content from a file.
- StdinContentReader: Reads content from standard input.
- DefaultClipboardWriter: Writes content to the clipboard.
- ParseContent: Parses content from multiple sources, including direct text, files, and invalid inputs.

Additionally, the tests use helper functions for creating temporary files and replacing stdin, improving readability and maintainability.

* chore: bump release to 1.5.0

* build: update build target to the cli folder

* tests: skips DefaultClipboardWriter test on CI

Skipping clipboard test in short mode. Helps avoid errors when on CI environments.

* refactor: modify Run() to return (string, error) instead of printing and exiting

- Refactored the `Run` function to return a `string` (representing the success message or version information) and an `error`.
- This change allows the caller of `Run` to handle the output and errors gracefully, rather than relying on `Run` to print messages and exit directly.
- Updated the error handling to use `fmt.Errorf` with error wrapping (%w) for better context.
- This improvement enhances the flexibility and testability of the `clipper` tool.

* refactor: update main function to handle Run's (string, error) return values

- Modified the `main` function to call `clipper.Run` and handle both the returned message and error.
- Prints the success message if no error occurred, or an error message if an error was encountered.
- Exits with a status code of 0 for success or 1 for errors.
- This change ensures that the `main` function gracefully handles the output and errors generated by the `Run` function.

* refactor: extract GetReaders function to improve code organization

- Introduced a new function `GetReaders` in `clipper.go` to handle the logic of creating `ContentReader` instances based on command-line arguments.
- This refactoring improves the readability and maintainability of the `Run` function by separating out a specific task.
- The `Run` function now calls `GetReaders` to obtain the necessary readers, making its logic more focused and concise.
  • Loading branch information
supitsdu authored Jun 28, 2024
1 parent 0951d39 commit 3301698
Show file tree
Hide file tree
Showing 7 changed files with 459 additions and 344 deletions.
113 changes: 113 additions & 0 deletions cli/clipper/clipper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package clipper

import (
"fmt"
"io"
"os"
"strings"

"github.com/atotto/clipboard"
"github.com/supitsdu/clipper/cli/options"
)

// ContentReader defines an interface for reading content from various sources.
type ContentReader interface {
Read() (string, error)
}

// ClipboardWriter defines an interface for writing content to the clipboard.
type ClipboardWriter interface {
Write(content string) error
}

// FileContentReader reads content from a specified file path.
type FileContentReader struct {
FilePath string
}

// Read reads the content from the file specified in FileContentReader.
func (f FileContentReader) Read() (string, error) {
content, err := os.ReadFile(f.FilePath)
if err != nil {
return "", fmt.Errorf("error reading file '%s': %w", f.FilePath, err)
}
return string(content), nil
}

// StdinContentReader reads content from the standard input (stdin).
type StdinContentReader struct{}

// Read reads the content from stdin.
func (s StdinContentReader) Read() (string, error) {
input, err := io.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("error reading from stdin: %w", err)
}
return string(input), nil
}

// DefaultClipboardWriter writes content to the clipboard using the default clipboard implementation.
type DefaultClipboardWriter struct{}

// Write writes the given content to the clipboard.
func (c DefaultClipboardWriter) Write(content string) error {
return clipboard.WriteAll(content)
}

// ParseContent aggregates content from the provided readers, or returns the direct text if provided.
func ParseContent(directText *string, readers ...ContentReader) (string, error) {
if directText != nil && *directText != "" {
return *directText, nil
}

if len(readers) == 0 {
return "", fmt.Errorf("no content readers provided")
}

var sb strings.Builder
for _, reader := range readers {
content, err := reader.Read()
if err != nil {
return "", err
}
sb.WriteString(content + "\n")
}

return sb.String(), nil
}

func GetReaders(targets []string) []ContentReader {
if len(targets) == 0 {
// If no file paths are provided, use StdinContentReader to read from stdin.
return []ContentReader{StdinContentReader{}}
} else {
// If file paths are provided as arguments, create FileContentReader instances for each.
var readers []ContentReader
for _, filePath := range targets {
readers = append(readers, FileContentReader{FilePath: filePath})
}
return readers
}
}

// Run executes the clipper tool logic based on the provided configuration.
func Run(config *options.Config, writer ClipboardWriter) (string, error) {
if *config.ShowVersion {
return options.Version, nil
}

readers := GetReaders(config.Args)

// Aggregate the content from the provided sources.
content, err := ParseContent(config.DirectText, readers...)
if err != nil {
return "", fmt.Errorf("parsing content: %w", err)
}

// Write the parsed content to the provided clipboard.
if err = writer.Write(content); err != nil {
return "", fmt.Errorf("copying content to clipboard: %w", err)
}

return "updated clipboard successfully. Ready to paste!", nil
}
25 changes: 25 additions & 0 deletions cli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"fmt"
"os"

"github.com/supitsdu/clipper/cli/clipper"
"github.com/supitsdu/clipper/cli/options"
)

func main() {
config := options.ParseFlags()
writer := clipper.DefaultClipboardWriter{}

msg, err := clipper.Run(config, writer)
if err != nil {
fmt.Printf("Error %s\n", err)
os.Exit(1)
}

if msg != "" {
fmt.Printf("Clipper %s\n", msg)
os.Exit(0)
}
}
38 changes: 38 additions & 0 deletions cli/options/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package options

import (
"flag"
"fmt"
)

type Config struct {
DirectText *string
ShowVersion *bool
Args []string
}

const Version = "1.5.0"

// ParseFlags parses the command-line flags and arguments.
func ParseFlags() *Config {
directText := flag.String("c", "", "Copy text directly from command line argument")
showVersion := flag.Bool("v", false, "Show the current version of the clipper tool")

flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Clipper is a lightweight command-line tool for copying contents to the clipboard.\n")
fmt.Fprintf(flag.CommandLine.Output(), "\nUsage:\n")
fmt.Fprintf(flag.CommandLine.Output(), " clipper [arguments] [file ...]\n")
fmt.Fprintf(flag.CommandLine.Output(), "\nArguments:\n")
fmt.Fprintf(flag.CommandLine.Output(), " -c <string> Copy text directly from command line argument\n")
fmt.Fprintf(flag.CommandLine.Output(), " -v Show the current version of the clipper tool\n")
fmt.Fprintf(flag.CommandLine.Output(), "\nIf no file or text is provided, reads from standard input.\n")
}

flag.Parse()

return &Config{
DirectText: directText,
ShowVersion: showVersion,
Args: flag.Args(),
}
}
103 changes: 0 additions & 103 deletions main.go

This file was deleted.

Loading

0 comments on commit 3301698

Please sign in to comment.