Skip to content

Commit

Permalink
feat: Update logs and enhance ContentReader.ReadAll logic (#42)
Browse files Browse the repository at this point in the history
* refact: improve overall log messsages and more

- Improve the logic inside ContentReader.ReadAll Function
- Add more description info for each error
- Add success message to describe the size of the contents copied to
  the clipboard
- Improve the format of the Usage page

* chore: update package deps

* test(reader): update reader.go tests

* fix: tackles lint errors report by linter

* refact(options): replace new line escape codes

Replace new line escape codes with
fmt.Fprintf(flag.CommandLine.Output()) for better readability as suggest
by @ccoVeile

* tests: improves the integration with testify

* refact: upload package to store error messages

Update some logical and structural problems in the code following the
recommendations made by @ccoVeille

* refact: Update cli/console/errors.go

Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com>

* test(reader): add descriptive error messages for better readability and debugging

- Added detailed error messages to `require` and `assert` statements in `TestReadAll`, `TestReadable`, `TestIOReader`, `TestCreateContent`, and `TestJoinAll`
- Improved context in test assertions to facilitate easier debugging and maintenance

* refact(tests): strip declaration of unused var

* chore: improve ErrFileNotFound message

---------

Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com>
  • Loading branch information
supitsdu and ccoVeille authored Aug 2, 2024
1 parent 434a90d commit d2d26b6
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 86 deletions.
8 changes: 4 additions & 4 deletions cli/clipper/clipper.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ func (c DefaultClipboardWriter) Write(content string) error {
}

// Run executes the core logic of the Clipper tool.
func Run(reader reader.ContentReader, writer ClipboardWriter) (string, error) {
func Run(reader reader.ContentReader, writer ClipboardWriter) (int, error) {
// Aggregate the content from the provided sources.
content, err := reader.ReadAll()
if err != nil {
return "", err
return 0, 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 0, fmt.Errorf("failed copying to clipboard %w", err)
}

return "Updated clipboard successfully. Ready to paste!", nil
return len(content), nil
}
9 changes: 9 additions & 0 deletions cli/console/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package console

import "errors"

var (
ErrFileNotFound = errors.New("file does not exist or cannot be accessed")
ErrReadingDirectories = errors.New("reading from directories is not currently supported")
ErrPermissionDenied = errors.New("permission denied")
)
8 changes: 5 additions & 3 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"os"

"github.com/dustin/go-humanize"

"github.com/supitsdu/clipper/cli/clipper"
"github.com/supitsdu/clipper/cli/options"
"github.com/supitsdu/clipper/cli/reader"
Expand All @@ -21,11 +23,11 @@ func main() {

reader := reader.ContentReader{Config: config}

msg, err := clipper.Run(reader, writer) // Run the main Clipper logic
bytesLength, err := clipper.Run(reader, writer) // Run the main Clipper logic
if err != nil {
fmt.Printf("Error %s\n", err)
fmt.Printf("Error %s.\n", err)
os.Exit(1)
}

fmt.Println(msg)
fmt.Printf("Copied %s to the clipboard. Ready to paste!\n", humanize.Bytes(uint64(bytesLength)))
}
15 changes: 10 additions & 5 deletions cli/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,17 @@ func ParseFlags() *Config {
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.Fprintln(flag.CommandLine.Output(), "Clipper is a lightweight command-line tool for copying contents to the clipboard.")
fmt.Fprintln(flag.CommandLine.Output())
fmt.Fprintln(flag.CommandLine.Output(), "Usage:")
fmt.Fprintln(flag.CommandLine.Output())
fmt.Fprintln(flag.CommandLine.Output(), " clipper [arguments] [file ...]")
fmt.Fprintln(flag.CommandLine.Output())
fmt.Fprintln(flag.CommandLine.Output(), "Arguments:")
fmt.Fprintln(flag.CommandLine.Output())
flag.PrintDefaults()
fmt.Fprintf(flag.CommandLine.Output(), "\nIf no file or text is provided, reads from standard input.\n")
fmt.Fprintln(flag.CommandLine.Output())
fmt.Fprintln(flag.CommandLine.Output(), "If no file or text is provided, reads from standard input.")
}

flag.CommandLine.SetOutput(os.Stderr)
Expand Down
44 changes: 32 additions & 12 deletions cli/reader/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sync"

"github.com/gabriel-vasile/mimetype"
"github.com/supitsdu/clipper/cli/console"
"github.com/supitsdu/clipper/cli/options"
)

Expand All @@ -16,22 +17,36 @@ type ContentReader struct {
Config *options.Config // Configuration options for content reading and formatting.
}

// ReadAll reads content from multiple files concurrently or from standard input if no files are specified.
// It returns the aggregated content as a single string.
// ReadAll reads content from multiple sources: command-line argument, standard input (stdin),
// and specified files. It aggregates the content into a single string.
// It returns the aggregated content as a string and any error encountered during the process.
func (cr ContentReader) ReadAll() (string, error) {
paths := cr.Config.FilePaths
var results []string

// If no file paths are specified, read from standard input.
if len(paths) == 0 {
return cr.IOReader(os.Stdin, "")
// Add text from command-line argument, if provided
if len(cr.Config.Text) > 0 {
results = append(results, cr.Config.Text)
}

// Read content from all specified files concurrently.
results, err := cr.ReadFilesAsync(paths)
// Read from stdin if data is available
if stat, err := os.Stdin.Stat(); err == nil && (stat.Mode()&os.ModeCharDevice) == 0 {
stdinContents, err := cr.IOReader(os.Stdin, "")
if err != nil {
return "", err
}
if len(stdinContents) > 0 {
results = append(results, stdinContents)
}
}

// Read from specified files asynchronously
fileContents, err := cr.ReadFilesAsync(cr.Config.FilePaths)
if err != nil {
return "", err
}

results = append(results, fileContents...)

// Join all results into a single string.
return cr.JoinAll(results), nil
}
Expand All @@ -54,7 +69,7 @@ func (cr ContentReader) ReadFilesAsync(paths []string) ([]string, error) {

if err != nil {
// Send the error to the error channel and return early.
errChan <- fmt.Errorf("error reading file '%s': %w", filepath, err)
errChan <- fmt.Errorf("reading file '%s': %w", filepath, err)
return
}

Expand Down Expand Up @@ -119,17 +134,17 @@ func (cr ContentReader) Readable(filePath string) error {
fileInfo, err := os.Stat(filePath)
if err != nil {
// File doesn't exist or can't be accessed.
return fmt.Errorf("file does not exist or can't be accessed")
return console.ErrFileNotFound
}

// Check if it's a regular file (not a directory or other type).
if !fileInfo.Mode().IsRegular() {
return fmt.Errorf("path is not of a regular file, perhaps a directory or other type")
return console.ErrReadingDirectories
}

// Check if it's readable.
if fileInfo.Mode().Perm()&0400 == 0 { // 0400 corresponds to read permission.
return fmt.Errorf("you don't have access to read the file")
return console.ErrPermissionDenied
}

return nil
Expand All @@ -143,6 +158,11 @@ func (cr ContentReader) IOReader(source io.Reader, filepath string) (string, err
return "", err
}

// Check if stdin is empty
if len(data) == 0 {
return "", nil
}

return cr.CreateContent(filepath, data)
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22.3

require (
github.com/atotto/clipboard v0.1.4
github.com/dustin/go-humanize v1.0.1
github.com/gabriel-vasile/mimetype v1.4.4
github.com/stretchr/testify v1.9.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
Loading

0 comments on commit d2d26b6

Please sign in to comment.