diff --git a/.scripts/packages.list b/.scripts/packages.list index 59828deb..bc0afc76 100644 --- a/.scripts/packages.list +++ b/.scripts/packages.list @@ -31,6 +31,7 @@ * + mathutil L + netutil * + options +* + pager * + passwd * + path * + pid @@ -53,7 +54,8 @@ L - system/process * - system/procname L + system/sensors * - terminal -* ! terminal/window +* ! terminal/tty +* - terminal/window * + timeutil * + tmp * + usage diff --git a/CHANGELOG.md b/CHANGELOG.md index 394531af..2c623566 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Changelog +### 12.88.0 + +* `[pager]` Added new package for pager (`less`/`more`) setup +* `[terminal/tty]` Added new package for working with TTY +* `[fmtc]` Added method `IsColorsSupported` + ### 12.87.0 * `[fmtc]` Added tag for italic text (`{&}`) diff --git a/README.md b/README.md index c718a8b9..7605a375 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ If you are using SublimeText 4 (`4075+`), we strongly recommend that you install * [`mathutil`](https://kaos.sh/g/ek.v12/mathutil) — Package provides some additional math methods * [`netutil`](https://kaos.sh/g/ek.v12/netutil) — Package provides methods for working with network * [`options`](https://kaos.sh/g/ek.v12/options) — Package provides methods for working with command-line options +* [`pager`](https://kaos.sh/g/ek.v12/pager) — Package provides methods for pager setup (more/less) * [`passwd`](https://kaos.sh/g/ek.v12/passwd) — Package contains methods for working with passwords * [`path`](https://kaos.sh/g/ek.v12/path) — Package for working with paths (fully compatible with base path package) * [`pid`](https://kaos.sh/g/ek.v12/pid) — Package for working with PID files @@ -99,7 +100,7 @@ If you are using SublimeText 4 (`4075+`), we strongly recommend that you install * [`system/sensors`](https://kaos.sh/g/ek.v12/system/sensors) — Package provide methods for collecting sensors information * [`system`](https://kaos.sh/g/ek.v12/system) — Package provides methods for working with system data (metrics/users) * [`terminal`](https://kaos.sh/g/ek.v12/terminal) — Package provides methods for working with user input -* [`terminal/window`](https://kaos.sh/g/ek.v12/terminal/window) — Package provides methods for working terminal window +* [`terminal/tty`](https://kaos.sh/g/ek.v12/terminal/tty) — Package provides methods for working with TTY * [`timeutil`](https://kaos.sh/g/ek.v12/timeutil) — Package provides methods for working with time and date * [`tmp`](https://kaos.sh/g/ek.v12/tmp) — Package provides methods for working with temporary data * [`usage`](https://kaos.sh/g/ek.v12/usage) — Package usage provides methods and structs for generating usage info for command-line tools diff --git a/ek.go b/ek.go index ddb3ddf9..5595009f 100644 --- a/ek.go +++ b/ek.go @@ -20,7 +20,7 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // // VERSION is current ek package version -const VERSION = "12.87.0" +const VERSION = "12.88.0" // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/fmtc/examples_test.go b/fmtc/examples_test.go index 09d68a1b..1d1c08e4 100644 --- a/fmtc/examples_test.go +++ b/fmtc/examples_test.go @@ -337,6 +337,10 @@ func ExampleClean() { // Output: Text } +func ExampleIsColorsSupported() { + fmt.Printf("16 Colors Supported: %t\n", IsColorsSupported()) +} + func ExampleIs256ColorsSupported() { fmt.Printf("256 Colors Supported: %t\n", Is256ColorsSupported()) } diff --git a/fmtc/fmtc.go b/fmtc/fmtc.go index 0a68225a..0309552f 100644 --- a/fmtc/fmtc.go +++ b/fmtc/fmtc.go @@ -75,8 +75,9 @@ var DisableColors = os.Getenv("NO_COLOR") != "" // ////////////////////////////////////////////////////////////////////////////////// // -var colors256Supported bool -var colorsTCSupported bool +var colorsSupported bool // 16 colors support +var colors256Supported bool // 256 colors support +var colorsTCSupported bool // 24bit (TrueColor) colors support var colorsSupportChecked bool var colorsMap *sync.Map @@ -328,6 +329,17 @@ func Bell() { fmt.Print(_CODE_BELL) } +// IsColorsSupported returns true if 16 colors is supported by terminal +func IsColorsSupported() bool { + if colorsSupportChecked { + return colorsSupported + } + + checkForColorsSupport() + + return colorsSupported +} + // Is256ColorsSupported returns true if 256 colors is supported by terminal func Is256ColorsSupported() bool { if colorsSupportChecked { @@ -644,6 +656,13 @@ func isValidNamedTag(tag string) bool { } func checkForColorsSupport() { + switch { + case strings.Contains(term, "xterm"), + strings.Contains(term, "color"), + term == "screen": + colorsSupported = true + } + if strings.Contains(term, "256color") { colors256Supported = true } diff --git a/fmtc/fmtc_test.go b/fmtc/fmtc_test.go index ef3f7d8e..0e8775e5 100644 --- a/fmtc/fmtc_test.go +++ b/fmtc/fmtc_test.go @@ -146,6 +146,7 @@ func (s *FormatSuite) Test24BitColors(c *C) { c.Assert(IsTrueColorSupported(), Equals, true) c.Assert(Is256ColorsSupported(), Equals, true) + c.Assert(IsColorsSupported(), Equals, true) colorsSupportChecked = false colors256Supported = false diff --git a/pager/example_test.go b/pager/example_test.go new file mode 100644 index 00000000..b56b2b89 --- /dev/null +++ b/pager/example_test.go @@ -0,0 +1,20 @@ +package pager + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +func ExampleSetup() { + // Use pager from PAGER env var or default (more) + Setup("") + + // Or provide specific command. + Setup("less -MQR") + + // Complete must be called at the end of the program work. You can call it with defer + // in your main function. + defer Complete() +} diff --git a/pager/pager.go b/pager/pager.go new file mode 100644 index 00000000..1e542aa6 --- /dev/null +++ b/pager/pager.go @@ -0,0 +1,95 @@ +// Package pager provides methods for pager setup (more/less) +package pager + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "errors" + "os" + "os/exec" + "strings" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +// DEFAULT is default pager command +const DEFAULT = "more" + +// ////////////////////////////////////////////////////////////////////////////////// // + +var pagerCmd *exec.Cmd +var pagerOut *os.File + +var stdout *os.File +var stderr *os.File + +// ////////////////////////////////////////////////////////////////////////////////// // + +var ErrAlreadySet = errors.New("Pager already set") + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Setup set up pager for work. After calling this method, any data sent to Stdout and +// Stderr (using fmt, fmtc, or terminal packages) will go to the pager. +func Setup(pager string) error { + if pagerCmd != nil { + return ErrAlreadySet + } + + pagerCmd = getPagerCommand(pager) + + pagerCmd.Stdout, stdout = os.Stdout, os.Stdout + pagerCmd.Stderr, stderr = os.Stderr, os.Stderr + + w, err := pagerCmd.StdinPipe() + + if err != nil { + return err + } + + pagerOut = w.(*os.File) + os.Stdout = pagerOut + + return pagerCmd.Start() +} + +// Complete finishes pager work +func Complete() { + if pagerOut != nil { + pagerOut.Close() + pagerOut = nil + } + + if pagerCmd != nil { + pagerCmd.Wait() + pagerCmd = nil + } + + os.Stdout = stdout + os.Stderr = stderr +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// getPagerCommand creates command for pager +func getPagerCommand(pager string) *exec.Cmd { + if pager == "" { + pager = os.Getenv("PAGER") + } + + if pager == "" { + pager = DEFAULT + } + + if strings.Contains(pager, " ") { + cmdSlice := strings.Fields(pager) + return exec.Command(cmdSlice[0], cmdSlice[1:]...) + } + + return exec.Command(pager) +} diff --git a/pager/pager_test.go b/pager/pager_test.go new file mode 100644 index 00000000..6f7d818e --- /dev/null +++ b/pager/pager_test.go @@ -0,0 +1,55 @@ +package pager + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "testing" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +type PagerSuite struct{} + +// ////////////////////////////////////////////////////////////////////////////////// // + +var _ = Suite(&PagerSuite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *PagerSuite) TearDownSuite(c *C) { + Complete() +} + +func (s *PagerSuite) TestPager(c *C) { + c.Assert(Setup("cat"), IsNil) + c.Assert(Setup("cat"), NotNil) + + Complete() + + c.Assert(pagerCmd, IsNil) + c.Assert(pagerOut, IsNil) +} + +func (s *PagerSuite) TestPagerSearch(c *C) { + os.Setenv("PAGER", "") + + cmd := getPagerCommand("cat") + c.Assert(cmd.Args, DeepEquals, []string{"cat"}) + + cmd = getPagerCommand("") + c.Assert(cmd.Args, DeepEquals, []string{"more"}) + + os.Setenv("PAGER", "less -MQR") + cmd = getPagerCommand("") + c.Assert(cmd.Args, DeepEquals, []string{"less", "-MQR"}) +} diff --git a/pager/pager_windows.go b/pager/pager_windows.go new file mode 100644 index 00000000..97717e7b --- /dev/null +++ b/pager/pager_windows.go @@ -0,0 +1,33 @@ +// Package pager provides methods for pager setup (more/less) +package pager + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import "errors" + +// ////////////////////////////////////////////////////////////////////////////////// // + +// DEFAULT is default pager command +const DEFAULT = "more" + +// ////////////////////////////////////////////////////////////////////////////////// // + +var ErrAlreadySet = errors.New("Pager already set") + +// ////////////////////////////////////////////////////////////////////////////////// // + +// ❗ Setup set up pager for work. After calling this method, any data sent to Stdout and +// Stderr (using fmt, fmtc, or terminal packages) will go to the pager. +func Setup(pager string) error { + return nil +} + +// ❗ Complete finishes pager work +func Complete() { + return +} diff --git a/terminal/window/example_test.go b/terminal/tty/example_test.go similarity index 98% rename from terminal/window/example_test.go rename to terminal/tty/example_test.go index 7c17cc0e..5885118f 100644 --- a/terminal/window/example_test.go +++ b/terminal/tty/example_test.go @@ -1,4 +1,4 @@ -package window +package tty // ////////////////////////////////////////////////////////////////////////////////// // // // diff --git a/terminal/tty/size_posix.go b/terminal/tty/size_posix.go new file mode 100644 index 00000000..6a765c80 --- /dev/null +++ b/terminal/tty/size_posix.go @@ -0,0 +1,64 @@ +//go:build !windows +// +build !windows + +package tty + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "syscall" + "unsafe" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +type winsize struct { + rows uint16 + cols uint16 + xpixels uint16 + ypixels uint16 +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// tty is a path to TTY device file +var tty = "/dev/tty" + +// ////////////////////////////////////////////////////////////////////////////////// // + +// GetSize returns window width and height +func GetSize() (int, int) { + t, err := os.OpenFile(tty, syscall.O_RDONLY, 0) + + if err != nil { + return -1, -1 + } + + var sz winsize + + _, _, _ = syscall.Syscall( + syscall.SYS_IOCTL, t.Fd(), + uintptr(syscall.TIOCGWINSZ), + uintptr(unsafe.Pointer(&sz)), + ) + + return int(sz.cols), int(sz.rows) +} + +// GetWidth returns window width +func GetWidth() int { + w, _ := GetSize() + return w +} + +// GetHeight returns window height +func GetHeight() int { + _, h := GetSize() + return h +} diff --git a/terminal/tty/tty_posix.go b/terminal/tty/tty_posix.go new file mode 100644 index 00000000..f89f666b --- /dev/null +++ b/terminal/tty/tty_posix.go @@ -0,0 +1,39 @@ +// Package tty provides methods for working with TTY +package tty + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +var stdin = os.Stdin +var stdout = os.Stdout + +// ////////////////////////////////////////////////////////////////////////////////// // + +// IsTTY returns true if current output device is TTY +func IsTTY() bool { + si, _ := stdin.Stat() + so, _ := stdout.Stat() + + if si.Mode()&os.ModeCharDevice != 0 && + so.Mode()&os.ModeCharDevice == 0 && + !IsFakeTTY() { + return false + } + + return true +} + +// IsFakeTTY returns true is fake TTY is used +func IsFakeTTY() bool { + return os.Getenv("FAKETTY") != "" +} diff --git a/terminal/window/size_test.go b/terminal/tty/tty_test.go similarity index 78% rename from terminal/window/size_test.go rename to terminal/tty/tty_test.go index c662b093..d8a1d6e0 100644 --- a/terminal/window/size_test.go +++ b/terminal/tty/tty_test.go @@ -1,4 +1,4 @@ -package window +package tty // ////////////////////////////////////////////////////////////////////////////////// // // // @@ -8,6 +8,7 @@ package window // ////////////////////////////////////////////////////////////////////////////////// // import ( + "os" "testing" . "github.com/essentialkaos/check" @@ -17,15 +18,23 @@ import ( func Test(t *testing.T) { TestingT(t) } -type WindowSuite struct{} +type TTYSuite struct{} // ////////////////////////////////////////////////////////////////////////////////// // -var _ = Suite(&WindowSuite{}) +var _ = Suite(&TTYSuite{}) // ////////////////////////////////////////////////////////////////////////////////// // -func (s *WindowSuite) TestGetSize(c *C) { +func (s *TTYSuite) TestIsTTY(c *C) { + IsTTY() + + os.Setenv("FAKETTY", "1") + c.Assert(IsFakeTTY(), Equals, true) + os.Setenv("FAKETTY", "") +} + +func (s *TTYSuite) TestGetSize(c *C) { w, h := GetSize() c.Assert(w, Not(Equals), -1) @@ -34,17 +43,17 @@ func (s *WindowSuite) TestGetSize(c *C) { c.Assert(h, Not(Equals), 0) } -func (s *WindowSuite) TestGetWidth(c *C) { +func (s *TTYSuite) TestGetWidth(c *C) { c.Assert(GetWidth(), Not(Equals), -1) c.Assert(GetWidth(), Not(Equals), 0) } -func (s *WindowSuite) TestGetHeight(c *C) { +func (s *TTYSuite) TestGetHeight(c *C) { c.Assert(GetHeight(), Not(Equals), -1) c.Assert(GetHeight(), Not(Equals), 0) } -func (s *WindowSuite) TestErrors(c *C) { +func (s *TTYSuite) TestErrors(c *C) { tty = "/non-exist" w, h := GetSize() diff --git a/terminal/tty/tty_windows.go b/terminal/tty/tty_windows.go new file mode 100644 index 00000000..780f81e3 --- /dev/null +++ b/terminal/tty/tty_windows.go @@ -0,0 +1,38 @@ +package tty + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +// ❗ IsTTY returns true if current output device is TTY +func IsTTY() bool { + panic("UNSUPPORTED") + return false +} + +// ❗ IsFakeTTY returns true is fake TTY is used +func IsFakeTTY() bool { + panic("UNSUPPORTED") + return false +} + +// ❗ GetSize returns window width and height +func GetSize() (int, int) { + panic("UNSUPPORTED") + return -1, -1 +} + +// ❗ GetWidth returns window width +func GetWidth() int { + panic("UNSUPPORTED") + return -1 +} + +// ❗ GetHeight returns window height +func GetHeight() int { + panic("UNSUPPORTED") + return -1 +} diff --git a/terminal/window/size_posix.go b/terminal/window/size_posix.go index 55ea1535..92da7d0c 100644 --- a/terminal/window/size_posix.go +++ b/terminal/window/size_posix.go @@ -12,53 +12,29 @@ package window // ////////////////////////////////////////////////////////////////////////////////// // import ( - "os" - "syscall" - "unsafe" + "github.com/essentialkaos/ek/v12/terminal/tty" ) // ////////////////////////////////////////////////////////////////////////////////// // -type winsize struct { - rows uint16 - cols uint16 - xpixels uint16 - ypixels uint16 -} - -// ////////////////////////////////////////////////////////////////////////////////// // - -// tty is a path to TTY device file -var tty = "/dev/tty" - -// ////////////////////////////////////////////////////////////////////////////////// // - // GetSize returns window width and height +// +// Deprecated: Use method package tty instead func GetSize() (int, int) { - t, err := os.OpenFile(tty, syscall.O_RDONLY, 0) - - if err != nil { - return -1, -1 - } - - var sz winsize - - _, _, _ = syscall.Syscall( - syscall.SYS_IOCTL, t.Fd(), - uintptr(syscall.TIOCGWINSZ), - uintptr(unsafe.Pointer(&sz)), - ) - - return int(sz.cols), int(sz.rows) + return tty.GetSize() } // GetWidth returns window width +// +// Deprecated: Use method package tty instead func GetWidth() int { w, _ := GetSize() return w } // GetHeight returns window height +// +// Deprecated: Use method package tty instead func GetHeight() int { _, h := GetSize() return h