Added shell execution

This commit is contained in:
Eduard Urbach 2023-08-18 17:01:42 +02:00
parent 23d6ad2ab9
commit 49e4cb7bd2
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
5 changed files with 193 additions and 63 deletions

22
core/ConfigDir.go Normal file
View File

@ -0,0 +1,22 @@
package core
import (
"os"
"path/filepath"
)
func ConfigDir() string {
home := os.Getenv("XDG_CONFIG_HOME")
if home != "" {
return filepath.Join(home, "dash")
}
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
return filepath.Join(home, ".config", "dash")
}

28
core/Process.go Normal file
View File

@ -0,0 +1,28 @@
package core
import (
"os/exec"
"regexp"
"strings"
)
var (
sh = regexp.MustCompile("\\{([^\\}]+)\\}")
)
func Process(source string) string {
source = sh.ReplaceAllStringFunc(source, func(match string) string {
cmd := sh.FindStringSubmatch(match)[1]
args := strings.Fields(cmd)
shellArgs := []string{"-c", strings.Join(args, " ")}
output, err := exec.Command("sh", shellArgs...).CombinedOutput()
if err != nil {
return err.Error()
}
return strings.Trim(string(output), "\n")
})
return source
}

View File

@ -2,60 +2,155 @@ package editor
import ( import (
"os" "os"
"path/filepath"
"git.akyoto.dev/cli/dash/core"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/rivo/tview" "github.com/rivo/tview"
) )
func New(app *tview.Application) *tview.TextArea { type Editor struct {
view := tview.NewTextArea() Pages *tview.Pages
view.SetBackgroundColor(tcell.ColorDefault) Source *tview.TextArea
view.SetTextStyle(tcell.StyleDefault) Result *tview.TextView
view.SetBorderPadding(0, 0, 1, 1) FilePath string
source := "Having been erased,\nThe document you're seeking\nMust now be retyped." IsDirty bool
}
home, err := os.UserHomeDir() func New(app *tview.Application, filePath string) *Editor {
e := &Editor{
if err != nil { FilePath: filePath,
panic(err)
} }
data, err := os.ReadFile(filepath.Join(home, ".dash")) e.Pages = tview.NewPages()
e.Pages.SetBackgroundColor(tcell.ColorDefault)
if err == nil { source := e.newSourceEditor()
source = string(data) result := e.newResult()
e.Pages.AddPage("Source", source, true, false)
e.Pages.AddPage("Result", result, true, true)
return e
}
func (e *Editor) toggle() {
front, _ := e.Pages.GetFrontPage()
switch front {
case "Source":
e.Pages.SwitchToPage("Result")
case "Result":
e.Pages.SwitchToPage("Source")
} }
}
view.SetText(source, true) func (e *Editor) newSourceEditor() *tview.TextArea {
e.Source = tview.NewTextArea()
e.Source.SetBackgroundColor(tcell.ColorDefault)
e.Source.SetTextStyle(tcell.StyleDefault)
e.Source.SetBorderPadding(0, 0, 1, 1)
e.load()
view.SetChangedFunc(func() { e.Source.SetChangedFunc(func() {
source = view.GetText() e.setDirty(true)
markDirty(view, true)
}) })
view.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { e.Source.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyESC {
e.updateResult()
e.toggle()
return nil
}
if event.Key() == tcell.KeyCtrlS { if event.Key() == tcell.KeyCtrlS {
err := os.WriteFile(filepath.Join(home, ".dash"), []byte(source), 0644) e.save()
e.updateResult()
if err != nil { e.toggle()
panic(err)
}
markDirty(view, false)
return nil return nil
} }
return event return event
}) })
return view return e.Source
} }
func markDirty(view *tview.TextArea, dirty bool) { func (e *Editor) newResult() *tview.TextView {
e.Result = tview.NewTextView()
e.Result.SetBackgroundColor(tcell.ColorDefault)
e.Result.SetTextStyle(tcell.StyleDefault)
e.Result.SetBorderPadding(0, 0, 1, 1)
e.Result.SetMouseCapture(func(action tview.MouseAction, event *tcell.EventMouse) (tview.MouseAction, *tcell.EventMouse) {
if action == tview.MouseLeftClick {
e.toggle()
}
return action, event
})
e.Result.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEnter:
e.toggle()
return nil
case tcell.KeyRune:
if event.Rune() == 'i' {
e.toggle()
return nil
}
case tcell.KeyCtrlS:
e.save()
return nil
}
return event
})
e.updateResult()
return e.Result
}
func (e *Editor) updateResult() {
source := e.Source.GetText()
result := core.Process(source)
e.Result.SetText(result)
}
func (e *Editor) setDirty(dirty bool) {
e.IsDirty = dirty
if dirty { if dirty {
view.SetBackgroundColor(tcell.ColorRed) e.Source.SetBackgroundColor(tcell.ColorRed)
} else { } else {
view.SetBackgroundColor(tcell.ColorDefault) e.Source.SetBackgroundColor(tcell.ColorDefault)
} }
} }
func (e *Editor) load() {
source := "Press Enter to edit.\nPress Esc to preview.\nPress Ctrl + S to save.\n\n{whoami}@{hostname}"
data, err := os.ReadFile(e.FilePath)
if err == nil {
source = string(data)
}
e.Source.SetText(source, true)
}
func (e *Editor) save() {
if !e.IsDirty {
return
}
source := e.Source.GetText()
err := os.WriteFile(e.FilePath, []byte(source), 0600)
if err != nil {
panic(err)
}
e.setDirty(false)
}

View File

@ -1,13 +0,0 @@
package empty
import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
func New(app *tview.Application) *tview.Box {
view := tview.NewBox()
view.SetBackgroundColor(tcell.ColorDefault)
view.SetBorderPadding(1, 1, 1, 1)
return view
}

38
main.go
View File

@ -1,9 +1,12 @@
package main package main
import ( import (
"os"
"path/filepath"
"git.akyoto.dev/cli/dash/clock" "git.akyoto.dev/cli/dash/clock"
"git.akyoto.dev/cli/dash/core"
"git.akyoto.dev/cli/dash/editor" "git.akyoto.dev/cli/dash/editor"
//"git.akyoto.dev/cli/dash/empty"
"git.akyoto.dev/cli/dash/osinfo" "git.akyoto.dev/cli/dash/osinfo"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/rivo/tview" "github.com/rivo/tview"
@ -12,37 +15,32 @@ import (
func main() { func main() {
app := tview.NewApplication() app := tview.NewApplication()
header := clock.New(app)
main := editor.New(app)
footer := osinfo.New(app)
//left := empty.New(app)
//right := empty.New(app)
grid := tview.NewGrid() grid := tview.NewGrid()
grid.SetRows(3, 0, 3) grid.SetRows(3, 0, 3)
grid.SetColumns(0, 80, 0) grid.SetColumns(0, 80, 0)
grid.SetBorders(false) grid.SetBorders(false)
grid.SetBackgroundColor(tcell.ColorDefault) grid.SetBackgroundColor(tcell.ColorDefault)
dataDir := core.ConfigDir()
err := os.MkdirAll(dataDir, 0700)
if err != nil {
panic(err)
}
header := clock.New(app)
main := editor.New(app, filepath.Join(dataDir, "main"))
footer := osinfo.New(app)
grid.AddItem(header, 0, 0, 1, 3, 0, 0, false) grid.AddItem(header, 0, 0, 1, 3, 0, 0, false)
grid.AddItem(main, 1, 1, 1, 1, 0, 0, false) grid.AddItem(main.Pages, 1, 1, 1, 1, 0, 0, false)
grid.AddItem(footer, 2, 0, 1, 3, 0, 0, false) grid.AddItem(footer, 2, 0, 1, 3, 0, 0, false)
// Layout for screens narrower than 100 cells (menu and side bar are hidden).
//grid.AddItem(left, 0, 0, 0, 0, 0, 0, false)
//grid.AddItem(main, 1, 0, 1, 3, 0, 0, false)
//grid.AddItem(right, 0, 0, 0, 0, 0, 0, false)
// Layout for screens wider than 80 cells.
//grid.AddItem(left, 1, 0, 1, 1, 0, 80, false)
//grid.AddItem(main, 1, 1, 1, 1, 0, 80, false)
//grid.AddItem(right, 1, 2, 1, 1, 0, 80, false)
app.EnableMouse(true) app.EnableMouse(true)
app.SetRoot(grid, true) app.SetRoot(grid, true)
app.SetFocus(main) app.SetFocus(main.Pages)
err := app.Run() err = app.Run()
if err != nil { if err != nil {
panic(err) panic(err)