Skip to content

Commit

Permalink
export analysis to file as JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
dundee committed Jul 16, 2021
1 parent 1ea6de5 commit a883793
Show file tree
Hide file tree
Showing 9 changed files with 509 additions and 4 deletions.
30 changes: 27 additions & 3 deletions cmd/gdu/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
log "github.com/sirupsen/logrus"

"github.com/dundee/gdu/v5/build"
"github.com/dundee/gdu/v5/report"
"github.com/dundee/gdu/v5/internal/common"
"github.com/dundee/gdu/v5/pkg/analyze"
"github.com/dundee/gdu/v5/pkg/device"
Expand All @@ -31,6 +32,7 @@ type UI interface {
// Flags define flags accepted by Run
type Flags struct {
LogFile string
OutputFile string
IgnoreDirs []string
IgnoreDirPatterns []string
MaxCores int
Expand Down Expand Up @@ -71,7 +73,10 @@ func (a *App) Run() error {
log.SetOutput(f)

path := a.getPath()
ui := a.createUI()
ui, err := a.createUI()
if err != nil {
return err
}

if err := a.setNoCross(path); err != nil {
return err
Expand Down Expand Up @@ -116,9 +121,28 @@ func (a *App) setMaxProcs() {
log.Printf("Max cores set to %d", runtime.GOMAXPROCS(0))
}

func (a *App) createUI() UI {
func (a *App) createUI() (UI, error) {
var ui UI

if a.Flags.OutputFile != "" {
var output io.Writer
var err error
if a.Flags.OutputFile == "-" {
output = os.Stdout
} else {
output, err = os.OpenFile(a.Flags.OutputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return nil, fmt.Errorf("opening output file: %w", err)
}
}
ui = report.CreateExportUI(
a.Writer,
output,
!a.Flags.NoProgress && a.Istty,
)
return ui, nil
}

if a.Flags.NonInteractive || !a.Istty {
ui = stdout.CreateStdoutUI(
a.Writer,
Expand All @@ -134,7 +158,7 @@ func (a *App) createUI() UI {
}
tview.Styles.BorderColor = tcell.ColorDefault
}
return ui
return ui, nil
}

func (a *App) setNoCross(path string) error {
Expand Down
12 changes: 12 additions & 0 deletions cmd/gdu/app/app_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,15 @@ func TestLogError(t *testing.T) {
assert.Empty(t, out)
assert.Contains(t, err.Error(), "permission denied")
}

func TestOutputFileError(t *testing.T) {
out, err := runApp(
&Flags{LogFile: "/dev/null", OutputFile: "/xyzxyz"},
[]string{},
false,
testdev.DevicesInfoGetterMock{},
)

assert.Empty(t, out)
assert.Contains(t, err.Error(), "permission denied")
}
37 changes: 37 additions & 0 deletions cmd/gdu/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"bytes"
"os"
"runtime"
"strings"
"testing"
Expand Down Expand Up @@ -99,6 +100,24 @@ func TestAnalyzePathWithGui(t *testing.T) {
assert.Nil(t, err)
}

func TestAnalyzePathWithExport(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()
defer func() {
os.Remove("output.json")
}()

out, err := runApp(
&Flags{LogFile: "/dev/null", OutputFile: "output.json"},
[]string{"test_dir"},
true,
testdev.DevicesInfoGetterMock{},
)

assert.NotEmpty(t, out)
assert.Nil(t, err)
}

func TestAnalyzePathWithErr(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()
Expand Down Expand Up @@ -144,6 +163,24 @@ func TestListDevices(t *testing.T) {
assert.Nil(t, err)
}

func TestListDevicesToFile(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()
defer func() {
os.Remove("output.json")
}()

out, err := runApp(
&Flags{LogFile: "/dev/null", ShowDisks: true, OutputFile: "output.json"},
[]string{},
false,
testdev.DevicesInfoGetterMock{},
)

assert.Equal(t, "", out)
assert.Contains(t, err.Error(), "not supported")
}

func TestListDevicesWithGui(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()
Expand Down
3 changes: 2 additions & 1 deletion cmd/gdu/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func init() {
af = &app.Flags{}
flags := rootCmd.Flags()
flags.StringVarP(&af.LogFile, "log-file", "l", "/dev/null", "Path to a logfile")
flags.StringVarP(&af.OutputFile, "output-file", "o", "", "Export analysis into file as JSON")
flags.IntVarP(&af.MaxCores, "max-cores", "m", runtime.NumCPU(), fmt.Sprintf("Set max cores that GDU will use. %d cores available", runtime.NumCPU()))
flags.BoolVarP(&af.ShowVersion, "version", "v", false, "Print version")

Expand Down Expand Up @@ -61,7 +62,7 @@ func runE(command *cobra.Command, args []string) error {

var termApp *tview.Application

if !af.ShowVersion && !af.NonInteractive && istty {
if !af.ShowVersion && !af.NonInteractive && istty && af.OutputFile == "" {
screen, err := tcell.NewScreen()
if err != nil {
return fmt.Errorf("Error creating screen: %w", err)
Expand Down
80 changes: 80 additions & 0 deletions pkg/analyze/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package analyze

import (
"encoding/json"
"fmt"
"io"
)

// EncodeJSON writes JSON representation of dir
func (f *Dir) EncodeJSON(writer io.Writer, topLevel bool) error {
buff := make([]byte, 0, 20)

buff = append(buff, []byte(`[{"name":`)...)

if topLevel {
if err := addString(&buff, f.GetPath()); err != nil {
return err
}
} else {
if err := addString(&buff, f.GetName()); err != nil {
return err
}
}

buff = append(buff, '}')
if f.Files.Len() > 0 {
buff = append(buff, ',')
}
buff = append(buff, '\n')

if _, err := writer.Write(buff); err != nil {
return err
}

for i, item := range f.Files {
if i > 0 {
if _, err := writer.Write([]byte(",\n")); err != nil {
return err
}
}
err := item.EncodeJSON(writer, false)
if err != nil {
return err
}
}

if _, err := writer.Write([]byte("]")); err != nil {
return err
}
return nil
}

// EncodeJSON writes JSON representation of file
func (f *File) EncodeJSON(writer io.Writer, topLevel bool) error {
buff := make([]byte, 0, 20)

buff = append(buff, []byte(`{"name":`)...)
if err := addString(&buff, f.GetName()); err != nil {
return err
}
buff = append(buff, []byte(`,"asize":`)...)
buff = append(buff, []byte(fmt.Sprint(f.GetSize()))...)
buff = append(buff, []byte(`,"dsize":`)...)
buff = append(buff, []byte(fmt.Sprint(f.GetUsage()))...)
buff = append(buff, '}')

if _, err := writer.Write(buff); err != nil {
return err
}
return nil
}

func addString(buff *[]byte, val string) error {
b, err := json.Marshal(val)
if err != nil {
return err
}
*buff = append(*buff, b...)
return err
}
55 changes: 55 additions & 0 deletions pkg/analyze/encode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package analyze

import (
"bytes"
"testing"

log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func init() {
log.SetLevel(log.WarnLevel)
}

func TestEncode(t *testing.T) {
dir := &Dir{
File: &File{
Name: "test_dir",
Size: 10,
Usage: 18,
},
ItemCount: 4,
BasePath: ".",
}

subdir := &Dir{
File: &File{
Name: "nested",
Size: 9,
Usage: 14,
Parent: dir,
},
ItemCount: 3,
}
file := &File{
Name: "file2",
Size: 3,
Usage: 4,
Parent: subdir,
}
file2 := &File{
Name: "file",
Size: 5,
Usage: 6,
Parent: subdir,
}
dir.Files = Files{subdir}
subdir.Files = Files{file, file2}

var buff bytes.Buffer
err := dir.EncodeJSON(&buff, true)

assert.Nil(t, err)
assert.Contains(t, buff.String(), `"name":"nested"`)
}
2 changes: 2 additions & 0 deletions pkg/analyze/file.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package analyze

import (
"io"
"os"
"path/filepath"
)
Expand All @@ -19,6 +20,7 @@ type Item interface {
GetUsage() int64
GetItemCount() int
GetParent() *Dir
EncodeJSON(writer io.Writer, topLevel bool) error
getItemStats(links AlreadyCountedHardlinks) (int, int64, int64)
}

Expand Down
Loading

0 comments on commit a883793

Please sign in to comment.