summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md22
-rw-r--r--up.go125
2 files changed, 117 insertions, 30 deletions
diff --git a/README.md b/README.md
index d6c0100..d74312f 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,16 @@ then:
Wireless interface Centrino Advanced-N 6235
Ethernet interface RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller
+ - **WARNING: Please be careful when using it! It could be dangerous.**
+ In particular, writing "rm" or "dd" into it could be like running around
+ with a chainsaw. But you'd be careful writing "rm" anywhere in Linux
+ anyway, no? Also, why would you want to pipe something into "rm"? Other
+ than that, I don't really have good ideas how to protect against cases
+ like this. And in the other, non-dangerous cases, I find the tool
+ immensely useful. If you have some ideas how to
+ try to protect, [please share!](https://github.com/akavel/up/issues)
+ That said, a tool wouldn't be really Unixy if you couldn't hurt yourself
+ with it, right? ;P
- when you are satisfied with the result, you can **press *Ctrl-X* to exit**
the Ultimate Plumber, and the command you built will be **written into
`up1.sh` file** in the current working directory (or, if it already existed,
@@ -61,6 +71,18 @@ then:
you reach this limit, a `+` character should get displayed in the top-left
corner of the screen. (This is intended to be changed to a
dynamically/manually growable buffer in a future version of *up*.)
+- **MacOSX support:** I don't have a Mac, thus I have no idea if it works on
+ one. You are welcome to try, and also to send PRs. If you're interested in
+ me providing some kind of official-like support for MacOSX, please consider
+ trying to find a way to send me some usable-enough Mac computer. Please note
+ I'm not trying to "take advantage" of you by this, as I'm actually not at all
+ interested in achieving a Mac otherwise. (Also, trying to commit to this kind
+ of support will be an extra burden and obligation on me. Knowing someone out
+ there cares enough to do a fancy physical gesture would really help alleviate
+ this.) If you're serious enough to consider this option, please contact me by
+ email (mailto:czapkofan@gmail.com) or keybase (https://keybase.io/akavel), so
+ that we could try to research possible ways to achieve this.
+ Thanks for understanding!
- **Prior art:** I was surprised no one seemed to write a similar tool before,
that I could find. It should have been possible to write this since the dawn
of Unix already, or earlier! And indeed, after I announced *up*, I got enough
diff --git a/up.go b/up.go
index 0bd7a3f..7e17bc9 100644
--- a/up.go
+++ b/up.go
@@ -34,11 +34,14 @@ import (
"github.com/spf13/pflag"
)
+const version = "0.2 (2018-10-25)"
+
+// TODO: [#4] in case of error, show it in red (bg?), then below show again initial normal output
+// TODO: F1 should display help, and it should be multi-line, and scrolling licensing credits
// TODO: some key shortcut to increase stdin capture buffer size (unless EOF already reached)
// TODO: show status infos:
// - red fg + "up: process returned with error code %d" -- when subprocess returned an error
// - yellow fg -- when process is still not finished
-// TODO: readme, asciinema
// TODO: on github: add issues, incl. up-for-grabs / help-wanted
// TODO: [LATER] make it work on Windows; maybe with mattn/go-shellwords ?
// TODO: [LATER] Ctrl-O shows input via `less` or $PAGER
@@ -47,7 +50,6 @@ import (
// TODO: [LATER] allow adding more elements of pipeline (initially, just writing `foo | bar` should work)
// TODO: [LATER] allow invocation with partial command, like: `up grep -i`
// TODO: [LATER][MAYBE] allow reading upN.sh scripts
-// TODO: [LATER] auto-save and/or save on Ctrl-S or something
// TODO: [MUCH LATER] readline-like rich editing support? and completion?
// TODO: [MUCH LATER] integration with fzf? and pindexis/marker?
// TODO: [LATER] forking and unforking pipelines
@@ -60,10 +62,19 @@ import (
// TODO: [LATER] become pluggable into http://luna-lang.org
// TODO: [LATER][MAYBE] allow "plugins" ("combos" - commands with default options) e.g. for Lua `lua -e`+auto-quote, etc.
// TODO: [LATER] make it more friendly to infrequent Linux users by providing "descriptive" commands like "search" etc.
-// TODO: [LATER] advertise on: HN, r/programming, r/golang, r/commandline, r/linux, up-for-grabs.net; data exploration? data science?
+// TODO: [LATER] advertise on some reddits for data exploration / data science
+// TODO: [LATER] undo/redo - history of commands
+// TODO: [LATER] if writing upN.sh failed, write to $TMP/up.$RANDOM.sh (a.k.a. tempfile())
+// TODO: [LATER] jump between buffers saved from earlier pipe fragments; OR: allow saving/recalling "snapshots" of (cmd, results) pairs
+// TODO: [LATER] on ^C, print version & commandline on stderr
+// TODO: [LATER] ^-, U -- to switch to "unsafe mode"? -u to switch back? + some visual marker
var (
- debugMode = pflag.Bool("debug", false, "debug mode")
+ // TODO: dangerous? immediate? raw? unsafe? ...
+ // FIXME(akavel): mark the unsafe mode vs. safe mode with some colour or status; also inform/mark what command's results are displayed...
+ unsafeMode = pflag.Bool("unsafe-full-throttle", false, "enable mode in which command is executed immediately after any change")
+ outputScript = pflag.StringP("output-script", "o", "", "save the command to specified `file` if Ctrl-X is pressed (default: up<N>.sh)")
+ debugMode = pflag.Bool("debug", false, "debug mode")
)
func main() {
@@ -91,7 +102,7 @@ func main() {
// The rest of the screen is a view of the results of the command
commandOutput = BufView{}
// Sometimes, a message may be displayed at the bottom of the screen, with help or other info
- message = `^X exit (^C nosave) PgUp/PgDn/Up/Dn/^</^> scroll ^S pause (^Q end) [Ultimate Plumber v0.1 by akavel]`
+ message = `Enter runs ^X exit (^C nosave) PgUp/PgDn/Up/Dn/^</^> scroll ^S pause (^Q end) [Ultimate Plumber v` + version + ` by akavel]`
)
// Initialize main data flow
@@ -109,11 +120,12 @@ func main() {
// Main loop
lastCommand := ""
+ restart := false
for {
// If user edited the command, immediately run it in background, and
// kill the previously running command.
command := commandEditor.String()
- if command != lastCommand {
+ if restart || (*unsafeMode && command != lastCommand) {
commandSubprocess.Kill()
if command != "" {
commandSubprocess = StartSubprocess(command, stdinCapture, func() { triggerRefresh(tui) })
@@ -123,13 +135,18 @@ func main() {
commandSubprocess = nil
commandOutput.Buf = stdinCapture
}
+ restart = false
+ lastCommand = command
}
- lastCommand = command
// Draw UI
w, h := tui.Size()
- stdinCapture.DrawStatus(TuiRegion(tui, 0, 0, 1, 1))
- commandEditor.DrawTo(TuiRegion(tui, 1, 0, w-1, 1),
+ style := whiteOnBlue
+ if command == lastCommand {
+ style = whiteOnDBlue
+ }
+ stdinCapture.DrawStatus(TuiRegion(tui, 0, 0, 1, 1), style)
+ commandEditor.DrawTo(TuiRegion(tui, 1, 0, w-1, 1), style,
func(x, y int) { tui.ShowCursor(x+1, 0) })
commandOutput.DrawTo(TuiRegion(tui, 0, 1, w, h-1))
drawText(TuiRegion(tui, 0, h-1, w, 1), whiteOnBlue, message)
@@ -151,6 +168,12 @@ func main() {
}
// Some other global key combinations
switch getKey(ev) {
+ case key(tcell.KeyEnter):
+ restart = true
+ case key(tcell.KeyCtrlUnderscore),
+ ctrlKey(tcell.KeyCtrlUnderscore):
+ // TODO: ask for another character to trigger command-line option, like in `less`
+
case key(tcell.KeyCtrlS),
ctrlKey(tcell.KeyCtrlS):
stdinCapture.Pause(true)
@@ -164,11 +187,14 @@ func main() {
key(tcell.KeyCtrlD),
ctrlKey(tcell.KeyCtrlD):
// Quit
- // TODO: print the command in case user did this accidentally
+ tui.Fini()
+ os.Stderr.WriteString("up: Ultimate Plumber v" + version + " https://github.com/akavel/up\n")
+ os.Stderr.WriteString("up: | " + commandEditor.String() + "\n")
return
case key(tcell.KeyCtrlX),
ctrlKey(tcell.KeyCtrlX):
// Write script 'upN.sh' and quit
+ tui.Fini()
writeScript(commandEditor.String(), tui)
return
}
@@ -220,9 +246,8 @@ type Editor struct {
func (e *Editor) String() string { return string(e.value) }
-func (e *Editor) DrawTo(region Region, setcursor func(x, y int)) {
+func (e *Editor) DrawTo(region Region, style tcell.Style, setcursor func(x, y int)) {
// Draw prompt & the edited value - use white letters on blue background
- style := whiteOnBlue
for i, ch := range e.prompt {
region.SetCell(i, 0, style, ch)
}
@@ -516,7 +541,7 @@ func (b *Buf) Pause(pause bool) {
b.mu.Unlock()
}
-func (b *Buf) DrawStatus(region Region) {
+func (b *Buf) DrawStatus(region Region, style tcell.Style) {
status := '~' // default: still reading input
b.mu.Lock()
@@ -530,7 +555,7 @@ func (b *Buf) DrawStatus(region Region) {
}
b.mu.Unlock()
- region.SetCell(0, 0, whiteOnBlue, status)
+ region.SetCell(0, 0, style, status)
}
func (b *Buf) NewReader(blocking bool) io.Reader {
@@ -610,33 +635,72 @@ func altKey(base tcell.Key) key { return key(tcell.ModAlt)<<16 + key(base) }
func ctrlKey(base tcell.Key) key { return key(tcell.ModCtrl)<<16 + key(base) }
func writeScript(command string, tui tcell.Screen) {
+ os.Stderr.WriteString("up: Ultimate Plumber v" + version + " https://github.com/akavel/up\n")
var f *os.File
var err error
- // TODO: if we hit loop end, panic with some message
+ if *outputScript != "" {
+ os.Stderr.WriteString("up: writing " + *outputScript)
+ f, err = os.OpenFile(*outputScript, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
+ if err != nil {
+ goto fallback_tmp
+ }
+ goto try_file
+ }
+
+ os.Stderr.WriteString("up: writing: .")
for i := 1; i < 1000; i++ {
f, err = os.OpenFile(fmt.Sprintf("up%d.sh", i), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0755)
- if err != nil {
- if os.IsExist(err) {
- continue
- }
- // FIXME: don't panic, instead show error and let user try to copy & paste visually
- panic(err)
- } else {
- break
+ switch {
+ case os.IsExist(err):
+ continue
+ case err != nil:
+ goto fallback_tmp
+ default:
+ os.Stderr.WriteString("/" + f.Name())
+ goto try_file
}
}
+ os.Stderr.WriteString(" - error: up1.sh-up999.sh already exist\n")
+ goto fallback_tmp
+
+try_file:
_, err = fmt.Fprintf(f, "#!/bin/bash\n%s\n", command)
if err != nil {
- // FIXME: don't panic, instead show error and let user try to copy & paste visually
- panic(err)
+ goto fallback_tmp
}
err = f.Close()
if err != nil {
- // FIXME: don't panic, instead show error and let user try to copy & paste visually
- panic(err)
+ goto fallback_tmp
+ }
+ os.Stderr.WriteString(" - OK\n")
+ return
+
+fallback_tmp:
+ // TODO: test if the fallbacks etc. protections actually work
+ os.Stderr.WriteString(" - error: " + err.Error() + "\n")
+ f, err = ioutil.TempFile("", "up-*.sh")
+ if err != nil {
+ goto fallback_print
+ }
+ _, err = fmt.Fprintf(f, "#!/bin/bash\n%s\n", command)
+ if err != nil {
+ goto fallback_print
+ }
+ err = f.Close()
+ if err != nil {
+ goto fallback_print
+ }
+ os.Stderr.WriteString("up: writing: " + f.Name() + " - OK\n")
+ os.Chmod(f.Name(), 0755)
+ return
+
+fallback_print:
+ fname := "TMP"
+ if f != nil {
+ fname = f.Name()
}
- tui.Fini()
- fmt.Printf("up: command written to: %s\n", f.Name())
+ os.Stderr.WriteString("up: writing: " + fname + " - error: " + err.Error() + "\n")
+ os.Stderr.WriteString("up: | " + command + "\n")
}
type Region struct {
@@ -656,7 +720,8 @@ func TuiRegion(tui tcell.Screen, x, y, w, h int) Region {
}
var (
- whiteOnBlue = tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorBlue)
+ whiteOnBlue = tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorBlue)
+ whiteOnDBlue = tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorNavy)
)
func drawText(region Region, style tcell.Style, text string) {