Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 53 additions & 21 deletions cmd/jlv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package main

import (
"context"
"errors"
"flag"
"fmt"
"io"
"io/fs"
"os"
"path"

Expand All @@ -25,48 +28,80 @@ func main() {
printVersion := flag.Bool("version", false, "Print version")
flag.Parse()

if *printVersion {
err := runApp(applicationArguments{
Stdout: os.Stdout,
Stdin: os.Stdin,

ConfigPath: *configPath,
PrintVersion: *printVersion,
Args: flag.Args(),

RunProgram: func(p *tea.Program) (tea.Model, error) {
return p.Run()
},
})
if err != nil {
fmt.Fprintln(os.Stderr, "Error: "+err.Error())
os.Exit(1)
}
}

type applicationArguments struct {
Stdout io.Writer
Stdin fs.File

ConfigPath string
PrintVersion bool
Args []string

RunProgram func(*tea.Program) (tea.Model, error)
}

func runApp(args applicationArguments) (err error) {
if args.PrintVersion {
// nolint: forbidigo // Version command.
print("github.com/hedhyw/json-log-viewer@" + version + "\n")
fmt.Fprintln(args.Stdout, "github.com/hedhyw/json-log-viewer@"+version)

return
return nil
}

cfg, err := readConfig(*configPath)
cfg, err := readConfig(args.ConfigPath)
if err != nil {
fatalf("Error reading config: %s\n", err)
return fmt.Errorf("reading config: %w", err)
}

fileName := ""
var inputSource *source.Source

switch flag.NArg() {
switch len(args.Args) {
case 0:
// Tee stdin to a temp file, so that we can
// lazy load the log entries using random access.
fileName = "-"

stdIn, err := getStdinReader(os.Stdin)
stdin, err := getStdinReader(args.Stdin)
if err != nil {
fatalf("Stdin: %s\n", err)
return fmt.Errorf("getting stdin: %w", err)
}

inputSource, err = source.Reader(stdIn, cfg)
inputSource, err = source.Reader(stdin, cfg)
if err != nil {
fatalf("Could not create temp flie: %s\n", err)
return fmt.Errorf("creating a temporary file: %w", err)
}
defer inputSource.Close()

defer func() { err = errors.Join(err, inputSource.Close()) }()
case 1:
fileName = flag.Arg(0)
fileName = args.Args[0]

inputSource, err = source.File(fileName, cfg)
if err != nil {
fatalf("Could not create temp flie: %s\n", err)
return fmt.Errorf("reading file: %w", err)
}
defer inputSource.Close()

defer func() { err = errors.Join(err, inputSource.Close()) }()
default:
fatalf("Invalid arguments, usage: %s file.log\n", os.Args[0])
// nolint: err113 // One time case.
return fmt.Errorf("invalid arguments, usage: %s file.log", os.Args[0])
}

appModel := app.NewModel(fileName, cfg, version)
Expand All @@ -80,14 +115,11 @@ func main() {
}
})

if _, err := program.Run(); err != nil {
fatalf("Error running program: %s\n", err)
if _, err := args.RunProgram(program); err != nil {
return fmt.Errorf("running program: %w", err)
}
}

func fatalf(message string, args ...any) {
fmt.Fprintf(os.Stderr, message, args...)
os.Exit(1)
return nil
}

// readConfig tries to read config from working directory or home directory.
Expand Down
140 changes: 140 additions & 0 deletions cmd/jlv/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,150 @@ import (
"os"
"testing"

"github.com/hedhyw/json-log-viewer/internal/app"
"github.com/hedhyw/json-log-viewer/internal/pkg/config"
"github.com/hedhyw/json-log-viewer/internal/pkg/tests"

tea "github.com/charmbracelet/bubbletea"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRunAppVersion(t *testing.T) {
t.Parallel()

var outputBuf bytes.Buffer

err := runApp(applicationArguments{
Stdout: &outputBuf,
PrintVersion: true,
})
require.NoError(t, err)

assert.Contains(t, outputBuf.String(), version)
}

func TestRunAppRunProgramFailed(t *testing.T) {
t.Parallel()

fileName := tests.RequireCreateFile(t, []byte(t.Name()))

err := runApp(applicationArguments{
Args: []string{fileName},
RunProgram: func(*tea.Program) (tea.Model, error) {
return nil, tests.ErrTest
},
})
require.ErrorIs(t, err, tests.ErrTest)
}

func TestRunAppRunProgramReadConfigInvalid(t *testing.T) {
t.Parallel()

configPath := tests.RequireCreateFile(t, []byte("invalid config"))

err := runApp(applicationArguments{
ConfigPath: configPath,
RunProgram: func(*tea.Program) (tea.Model, error) {
t.Fatal("Should not run")

return app.NewModel("", config.GetDefaultConfig(), version), nil
},
})
require.Error(t, err)
}

func TestRunAppUnexpectedNumberOfArgs(t *testing.T) {
t.Parallel()

err := runApp(applicationArguments{
Args: []string{"1", "2", "3"},
})
require.Error(t, err)
}

func TestRunAppReadFileSuccess(t *testing.T) {
t.Parallel()

fileName := tests.RequireCreateFile(t, []byte(t.Name()))

var isStarted bool

err := runApp(applicationArguments{
Args: []string{fileName},
RunProgram: func(p *tea.Program) (tea.Model, error) {
assert.NotNil(t, p)
isStarted = true

return app.NewModel("", config.GetDefaultConfig(), version), nil
},
})
require.NoError(t, err)

assert.True(t, isStarted)
}

func TestRunAppReadFileNotFound(t *testing.T) {
t.Parallel()

err := runApp(applicationArguments{
Args: []string{t.Name() + "not found"},
RunProgram: func(*tea.Program) (tea.Model, error) {
t.Fatal("Should not run")

return app.NewModel("", config.GetDefaultConfig(), version), nil
},
})
require.Error(t, err)
}

func TestRunAppReadStdinSuccess(t *testing.T) {
t.Parallel()

fakeStdin := fakeFile{
Reader: bytes.NewReader([]byte(t.Name())),
StatFileInfo: fakeFileInfo{
FileMode: os.ModeNamedPipe,
},
}

var isStarted bool

err := runApp(applicationArguments{
Args: []string{},
Stdin: fakeStdin,
RunProgram: func(p *tea.Program) (tea.Model, error) {
assert.NotNil(t, p)
isStarted = true

return app.NewModel("", config.GetDefaultConfig(), version), nil
},
})
require.NoError(t, err)

assert.True(t, isStarted)
}

func TestRunAppReadStdinStatFailed(t *testing.T) {
t.Parallel()

fakeStdin := fakeFile{
Reader: bytes.NewReader([]byte(t.Name())),
ErrStat: tests.ErrTest,
}

err := runApp(applicationArguments{
Args: []string{},
Stdin: fakeStdin,
RunProgram: func(*tea.Program) (tea.Model, error) {
t.Fatal("Should not run")

return app.NewModel("", config.GetDefaultConfig(), version), nil
},
})
require.ErrorIs(t, err, tests.ErrTest)
}

func TestGetStdinSource(t *testing.T) {
t.Parallel()

Expand Down
30 changes: 0 additions & 30 deletions cmd/jlv/stdin_reader_mock.go

This file was deleted.

2 changes: 0 additions & 2 deletions cmd/jlv/stdin_reader.go → cmd/jlv/stdinreader.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build !mock_stdin

package main

import (
Expand Down
Loading