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

Add export in interactive #298

Merged
merged 5 commits into from
Feb 13, 2024
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
78 changes: 78 additions & 0 deletions tui/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"os/exec"
"runtime"
"runtime/debug"
"strconv"
"strings"
"time"

"github.com/dundee/gdu/v5/build"
"github.com/dundee/gdu/v5/pkg/analyze"
Expand Down Expand Up @@ -380,3 +383,78 @@
ui.showErr("Error opening", err)
}
}

func (ui *UI) confirmExport() *tview.Form {
form := tview.NewForm().

Check warning on line 388 in tui/actions.go

View check run for this annotation

Codecov / codecov/patch

tui/actions.go#L386-L388

Added lines #L386 - L388 were not covered by tests
AddInputField("File name", "export.json", 30, nil, func(v string) {
ui.exportName = v
}).
AddButton("Export", ui.exportAnalysis).
SetButtonsAlign(tview.AlignCenter)
form.SetBorder(true).
SetTitle(" Export data to JSON ").
SetInputCapture(func(key *tcell.EventKey) *tcell.EventKey {

Check warning on line 396 in tui/actions.go

View check run for this annotation

Codecov / codecov/patch

tui/actions.go#L390-L396

Added lines #L390 - L396 were not covered by tests
if key.Key() == tcell.KeyEsc {
ui.pages.RemovePage("export")
ui.app.SetFocus(ui.table)
return nil
}
return key
})

Check warning on line 403 in tui/actions.go

View check run for this annotation

Codecov / codecov/patch

tui/actions.go#L399-L403

Added lines #L399 - L403 were not covered by tests
flex := modal(form, 50, 7)
ui.pages.AddPage("export", flex, true, true)
ui.app.SetFocus(form)
return form
}

func (ui *UI) exportAnalysis() {
ui.pages.RemovePage("export")

text := tview.NewTextView().SetText("Export in progress...").SetTextAlign(tview.AlignCenter)
text.SetBorder(true).SetTitle(" Export data to JSON ")
flex := modal(text, 50, 3)
ui.pages.AddPage("exporting", flex, true, true)

go func() {
var err error
defer ui.app.QueueUpdateDraw(func() {
ui.pages.RemovePage("exporting")

Check warning on line 421 in tui/actions.go

View check run for this annotation

Codecov / codecov/patch

tui/actions.go#L421

Added line #L421 was not covered by tests
if err == nil {
ui.app.SetFocus(ui.table)
}
})
if ui.done != nil {
defer func() {
ui.done <- struct{}{}
}()
}

var buff bytes.Buffer

buff.Write([]byte(`[1,2,{"progname":"gdu","progver":"`))
buff.Write([]byte(build.Version))
buff.Write([]byte(`","timestamp":`))
buff.Write([]byte(strconv.FormatInt(time.Now().Unix(), 10)))
buff.Write([]byte("},\n"))

file, err := os.Create(ui.exportName)
if err != nil {
ui.showErrFromGo("Error creating file", err)
return
}

if err = ui.topDir.EncodeJSON(&buff, true); err != nil {
ui.showErrFromGo("Error encoding JSON", err)
return
}

if _, err = buff.Write([]byte("]\n")); err != nil {
ui.showErrFromGo("Error writting to buffer", err)
return
}
if _, err = buff.WriteTo(file); err != nil {
ui.showErrFromGo("Error writting to file", err)
return
}
}()
}
203 changes: 203 additions & 0 deletions tui/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package tui

import (
"bytes"
"os"
"testing"

"github.com/dundee/gdu/v5/internal/testanalyze"
"github.com/dundee/gdu/v5/internal/testapp"
"github.com/dundee/gdu/v5/pkg/analyze"
"github.com/dundee/gdu/v5/pkg/fs"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/stretchr/testify/assert"
)

func TestConfirmExport(t *testing.T) {
simScreen := testapp.CreateSimScreen()
defer simScreen.Fini()

app := testapp.CreateMockedApp(true)
ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false)
ui.done = make(chan struct{})
ui.Analyzer = &testanalyze.MockedAnalyzer{}

ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, 'E', 0))

assert.True(t, ui.pages.HasPage("export"))

ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, 'n', 0))
ui.keyPressed(tcell.NewEventKey(tcell.KeyEnter, 0, 0))

assert.True(t, ui.pages.HasPage("export"))
}

func TestExportAnalysis(t *testing.T) {
parentDir := &analyze.Dir{
File: &analyze.File{
Name: "parent",
},
Files: make([]fs.Item, 0, 1),
}
currentDir := &analyze.Dir{
File: &analyze.File{
Name: "sub",
Parent: parentDir,
},
}

simScreen := testapp.CreateSimScreen()
defer simScreen.Fini()

app := testapp.CreateMockedApp(true)
ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false)
ui.done = make(chan struct{})
ui.Analyzer = &testanalyze.MockedAnalyzer{}
ui.currentDir = currentDir
ui.topDir = parentDir

ui.exportAnalysis()

assert.True(t, ui.pages.HasPage("exporting"))

<-ui.done

assert.FileExists(t, "export.json")
err := os.Remove("export.json")
assert.NoError(t, err)

for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
f()
}
}

func TestExportAnalysisEsc(t *testing.T) {
parentDir := &analyze.Dir{
File: &analyze.File{
Name: "parent",
},
Files: make([]fs.Item, 0, 1),
}
currentDir := &analyze.Dir{
File: &analyze.File{
Name: "sub",
Parent: parentDir,
},
}

simScreen := testapp.CreateSimScreen()
defer simScreen.Fini()

app := testapp.CreateMockedApp(true)
ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false)
ui.done = make(chan struct{})
ui.Analyzer = &testanalyze.MockedAnalyzer{}
ui.currentDir = currentDir
ui.topDir = parentDir

form := ui.confirmExport()
formInputFn := form.GetInputCapture()

assert.True(t, ui.pages.HasPage("export"))

formInputFn(tcell.NewEventKey(tcell.KeyEsc, 0, 0))

assert.False(t, ui.pages.HasPage("export"))
}

func TestExportAnalysisWithName(t *testing.T) {
parentDir := &analyze.Dir{
File: &analyze.File{
Name: "parent",
},
Files: make([]fs.Item, 0, 1),
}
currentDir := &analyze.Dir{
File: &analyze.File{
Name: "sub",
Parent: parentDir,
},
}

simScreen := testapp.CreateSimScreen()
defer simScreen.Fini()

app := testapp.CreateMockedApp(true)
ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false)
ui.done = make(chan struct{})
ui.Analyzer = &testanalyze.MockedAnalyzer{}
ui.currentDir = currentDir
ui.topDir = parentDir

form := ui.confirmExport()
// formInputFn := form.GetInputCapture()
item := form.GetFormItemByLabel("File name")
inputFn := item.(*tview.InputField).InputHandler()

// send 'n' to input
inputFn(tcell.NewEventKey(tcell.KeyRune, 'n', 0), nil)
assert.Equal(t, "export.jsonn", ui.exportName)

assert.True(t, ui.pages.HasPage("export"))

form.GetButton(0).InputHandler()(tcell.NewEventKey(tcell.KeyEnter, 0, 0), nil)

assert.True(t, ui.pages.HasPage("exporting"))

<-ui.done

assert.FileExists(t, "export.jsonn")
err := os.Remove("export.jsonn")
assert.NoError(t, err)

for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
f()
}
}

func TestExportAnalysisWithoutRights(t *testing.T) {
parentDir := &analyze.Dir{
File: &analyze.File{
Name: "parent",
},
Files: make([]fs.Item, 0, 1),
}
currentDir := &analyze.Dir{
File: &analyze.File{
Name: "sub",
Parent: parentDir,
},
}

_, err := os.Create("export.json")
assert.NoError(t, err)
err = os.Chmod("export.json", 0)
assert.NoError(t, err)
defer func() {
err = os.Chmod("export.json", 0755)
assert.Nil(t, err)
err = os.Remove("export.json")
assert.NoError(t, err)
}()

simScreen := testapp.CreateSimScreen()
defer simScreen.Fini()

app := testapp.CreateMockedApp(true)
ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, false)
ui.done = make(chan struct{})
ui.Analyzer = &testanalyze.MockedAnalyzer{}
ui.currentDir = currentDir
ui.topDir = parentDir

ui.exportAnalysis()

<-ui.done

for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
f()
}

assert.True(t, ui.pages.HasPage("error"))
}
5 changes: 4 additions & 1 deletion tui/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func (ui *UI) keyPressed(key *tcell.EventKey) *tcell.EventKey {
return nil
}

if ui.pages.HasPage("file") {
if ui.pages.HasPage("file") || ui.pages.HasPage("export") {
return key // send event to primitive
}
if ui.filtering {
Expand Down Expand Up @@ -231,6 +231,9 @@ func (ui *UI) handleMainActions(key *tcell.EventKey) *tcell.EventKey {
if ui.currentDir != nil {
ui.rescanDir()
}
case 'E':
ui.confirmExport()
return nil
case 's':
ui.setSorting("size")
case 'C':
Expand Down
8 changes: 8 additions & 0 deletions tui/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const helpText = ` [::b]up/down, k/j [white:black:-]Move cursor up/down
[::b]left, h [white:black:-]Go to parent directory

[::b]r [white:black:-]Rescan current directory
[::b]E [white:black:-]Export analysis data to file as JSON
[::b]/ [white:black:-]Search items by name
[::b]a [white:black:-]Toggle between showing disk usage and apparent size
[::b]B [white:black:-]Toggle bar alignment to biggest file or directory
Expand Down Expand Up @@ -233,6 +234,13 @@ func (ui *UI) showErr(msg string, err error) {
}

ui.pages.AddPage("error", modal, true, true)
ui.app.SetFocus(modal)
}

func (ui *UI) showErrFromGo(msg string, err error) {
ui.app.QueueUpdateDraw(func() {
ui.showErr(msg, err)
})
}

func (ui *UI) showHelp() {
Expand Down
2 changes: 2 additions & 0 deletions tui/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type UI struct {
defaultSortBy string
defaultSortOrder string
markedRows map[int]struct{}
exportName string
}

// Option is optional function customizing the bahaviour of UI
Expand Down Expand Up @@ -95,6 +96,7 @@ func CreateUI(
defaultSortBy: "size",
defaultSortOrder: "desc",
markedRows: make(map[int]struct{}),
exportName: "export.json",
}
for _, o := range opts {
o(ui)
Expand Down
4 changes: 2 additions & 2 deletions tui/tui_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func TestHelp(t *testing.T) {

b, _, _ := simScreen.GetContents()

cells := b[406 : 406+9]
cells := b[456 : 456+9]

text := []byte("directory")
for i, r := range cells {
Expand All @@ -114,7 +114,7 @@ func TestHelpBw(t *testing.T) {

b, _, _ := simScreen.GetContents()

cells := b[406 : 406+9]
cells := b[456 : 456+9]

text := []byte("directory")
for i, r := range cells {
Expand Down
11 changes: 11 additions & 0 deletions tui/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tui

import (
"github.com/dundee/gdu/v5/pkg/device"
"github.com/rivo/tview"
)

var (
Expand Down Expand Up @@ -65,3 +66,13 @@ func min(a, b int) int {
}
return b
}

func modal(p tview.Primitive, width, height int) tview.Primitive {
return tview.NewFlex().
AddItem(nil, 0, 1, false).
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(nil, 0, 1, false).
AddItem(p, height, 1, true).
AddItem(nil, 0, 1, false), width, 1, true).
AddItem(nil, 0, 1, false)
}
Loading