pax_global_header00006660000000000000000000000064147716404000014515gustar00rootroot0000000000000052 comment=3f7bf906d396ff14dd921000d13c69e0c49b6c3b pio-0.0.14/000077500000000000000000000000001477164040000123665ustar00rootroot00000000000000pio-0.0.14/.gitattributes000066400000000000000000000001271477164040000152610ustar00rootroot00000000000000*.go linguist-language=Go vendor/* linguist-vendored _examples/* linguist-documentationpio-0.0.14/.github/000077500000000000000000000000001477164040000137265ustar00rootroot00000000000000pio-0.0.14/.github/FUNDING.yml000066400000000000000000000001431477164040000155410ustar00rootroot00000000000000# These are supported funding model platforms github: kataras # custom: http://iris-go.com/donate pio-0.0.14/.github/ISSUE_TEMPLATE/000077500000000000000000000000001477164040000161115ustar00rootroot00000000000000pio-0.0.14/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000012071477164040000206030ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: "[BUG]" labels: bug assignees: kataras --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. Windows] - Version [e.g. 10] **Additional context** Add any other context about the problem here. pio-0.0.14/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011511477164040000216340ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for pio title: "[FEATURE REQUEST]" labels: enhancement assignees: kataras --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. pio-0.0.14/.github/dependabot.yml000066400000000000000000000001531477164040000165550ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily"pio-0.0.14/.github/workflows/000077500000000000000000000000001477164040000157635ustar00rootroot00000000000000pio-0.0.14/.github/workflows/ci.yml000066400000000000000000000010501477164040000170750ustar00rootroot00000000000000name: CI on: push: branches: [ master ] pull_request: branches: [ master ] jobs: test: name: Test runs-on: ubuntu-latest strategy: matrix: go_version: [1.24.x] steps: - name: Set up Go 1.x uses: actions/setup-go@v5 with: go-version: ${{ matrix.go_version }} - name: Check out code into the Go module directory uses: actions/checkout@v4 - name: Test # continue-on-error: true # working-directory: _examples run: | go test -v -race ./... pio-0.0.14/.gitignore000066400000000000000000000000121477164040000143470ustar00rootroot00000000000000.DS_STORE pio-0.0.14/AUTHORS000066400000000000000000000001641477164040000134370ustar00rootroot00000000000000# This is the official list of PIO authors for copyright # purposes. Gerasimos Maropoulos pio-0.0.14/LICENSE000066400000000000000000000027111477164040000133740ustar00rootroot00000000000000Copyright (c) 2017-2025 Gerasimos Maropoulos. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of PIO nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.pio-0.0.14/README.md000066400000000000000000000040651477164040000136520ustar00rootroot00000000000000# 💊 PIO PIO [_Pill for Input/Output_] tries to cure headaches by solving problems where [go](https://golang.org) applications use different kinds of print targets or even loggers. [![build status](https://img.shields.io/github/actions/workflow/status/kataras/pio/ci.yml?style=flat-square)](https://github.com/kataras/pio/actions) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/pio) [![godocs](https://img.shields.io/badge/online-documentation-0366d6.svg?style=flat-square)](https://pkg.go.dev/github.com/kataras/pio) [![github issues](https://img.shields.io/github/issues/kataras/pio.svg?style=flat-square)](https://github.com/kataras/pio/issues?q=is%3Aopen+is%3Aissue) [![github closed issues](https://img.shields.io/github/issues-closed-raw/kataras/pio.svg?style=flat-square)](https://github.com/kataras/pio/issues?q=is%3Aissue+is%3Aclosed) - it can combine all your loggers as one - it can scan from any source and print - opossite is possible too, it use one or more sources to output when printing Navigate through [_examples](_examples/) and [integrations](_examples/integrations/) to learn if can cure yours too. ### 🚀 Installation The only requirement is the [Go Programming Language](https://go.dev/dl/). ```bash $ go get github.com/kataras/pio ``` > PIO is built on top of the standard library, it has zero external dependencies. ### 👥 Contributing If you find that something is not working as expected please open an [issue](https://github.com/kataras/pio/issues). If you have any previous experience on this field your [PR](https://github.com/kataras/pio/pulls) means gold to me! ### 📦 Projects using PIO | Package | Author | Description | | -----------|--------|-------------| | [golog](https://github.com/kataras/golog) | [Gerasimos Maropoulos](https://github.com/kataras) | Simple, fast and easy-to-use level-based logger written entirely in [GoLang](https://golang.org). | > Do not hesitate to put your package on this list via [PR](https://github.com/kataras/pio/pulls)! pio-0.0.14/_examples/000077500000000000000000000000001477164040000143435ustar00rootroot00000000000000pio-0.0.14/_examples/color/000077500000000000000000000000001477164040000154615ustar00rootroot00000000000000pio-0.0.14/_examples/color/main.go000066400000000000000000000011001477164040000167240ustar00rootroot00000000000000package main import ( "os" "github.com/kataras/pio" ) func main() { // pio will automatically detect the operating system/terminal which // is running under and will provide color support. p := pio.NewTextPrinter("color", os.Stdout) p.Println(pio.Rich("this is a blue text", pio.Blue)) p.Println(pio.Rich("this is a gray text", pio.Gray)) p.Println(pio.Rich("this is a red text", pio.Red)) p.Println(pio.Rich("this is a purple text", pio.Magenta)) p.Println(pio.Rich("this is a yellow text", pio.Yellow)) p.Println(pio.Rich("this is a green text", pio.Green)) } pio-0.0.14/_examples/handler/000077500000000000000000000000001477164040000157605ustar00rootroot00000000000000pio-0.0.14/_examples/handler/main.go000066400000000000000000000020021477164040000172250ustar00rootroot00000000000000package main import ( "fmt" "os" "time" "github.com/kataras/pio" ) type message struct { Datetime string `xml:"Date"` Message string `xml:"Message"` } func main() { p := pio.NewPrinter("default", os.Stdout).WithMarshalers(pio.Text, pio.XMLIndent) // Handle registers a handler // a handler is always runs in sync AFTER a print operation. // It accepts the complete result as its argument // and it's able to start other jobs based on that, // i.e: printing by other tool, send errors to the cloud logger and etc... // Although is possible to add one or more output, or more than one printer // per message type, but this is the easy way of doing these things: p.Handle(func(result pio.PrintResult) { if result.IsOK() { fmt.Printf("original value was: %#v\n", result.Value) } }) pio.RegisterPrinter(p) // or just use the p.Println... pio.Println(message{ Datetime: time.Now().Format("2006/01/02 - 15:04:05"), Message: "this is an xml message", }) pio.Println("this is a normal text") } pio-0.0.14/_examples/hijacker/000077500000000000000000000000001477164040000161235ustar00rootroot00000000000000pio-0.0.14/_examples/hijacker/main.go000066400000000000000000000017711477164040000174040ustar00rootroot00000000000000package main import ( "os" "github.com/kataras/pio" ) // Hijackers can be used to intercept a print operation (per printer), // they can cancel the print or they can marshal the value // and make use of the []byte to decide whenever to cancel the print operation, // if a marshaler is already callled then the printer will not marshal it again // so it costs nothing. // // Below we'll see a simple example, which skips all integer values. func main() { pio.Register("default", os.Stdout).Marshal(pio.Text).Hijack(func(ctx *pio.Ctx) { // check if the given value is integer // if yes, then cancel the print from that "default" printer. if _, ok := ctx.Value.(int); ok { ctx.Cancel() return } // ctx.Printer -> gives access to the current printer, // at this case the, named as, "default" Printer. // ctx.Next() -> to continue to the next hijacker, if available. }) // this should not: pio.Print(42) pio.Print("this should be the only printed") // this should not: pio.Print(93) } pio-0.0.14/_examples/integrations/000077500000000000000000000000001477164040000170515ustar00rootroot00000000000000pio-0.0.14/_examples/integrations/logrus/000077500000000000000000000000001477164040000203645ustar00rootroot00000000000000pio-0.0.14/_examples/integrations/logrus/logrus.go000066400000000000000000000025511477164040000222310ustar00rootroot00000000000000/* Package logrus registers the global logrus logger to the pio ecosystem, install logrus first: $ go get github.com/ Example Code: package main import ( "github.com/kataras/pio" _ "github.com/kataras/pio/_examples/integrations/logrus" ) func main() { pio.Print("This is an info message that will be printed to the logrus' output") } */ package logrus import ( "github.com/kataras/pio" "github.com/sirupsen/logrus" ) // Name of this printer. const Name = "logrus" func init() { Register(logrus.Infof) } // sirupsen/logrus/entry.go#96 // func (h *hook) Fire(e *logrus.Entry) // no f. it. It doesn't work as expected // they don't made it to be able to stop a // specific print operation or change its buffer // because the entry's buffer is being initialized // after the hook's Fire, so we can't // create it with a specific hook, do it // with the pio's own way: // Register registers a specific logrus printf-compatible function signature // to the pio registry. // // pio can take only one by-design because it is not based on any log levels // but, you can extend it by calling its Hijack function // to determinate what to log. func Register(printf func(string, ...interface{})) *pio.Printer { return pio.Register("logrus", pio.Wrap(printf)).Marshal(pio.Text) } // Remove removes the logrus printer from the pio. func Remove() { pio.Remove("logrus") } pio-0.0.14/_examples/integrations/logrus/print/000077500000000000000000000000001477164040000215205ustar00rootroot00000000000000pio-0.0.14/_examples/integrations/logrus/print/errorf/000077500000000000000000000000001477164040000230175ustar00rootroot00000000000000pio-0.0.14/_examples/integrations/logrus/print/errorf/main.go000066400000000000000000000010301477164040000242640ustar00rootroot00000000000000package main import ( "fmt" "time" "github.com/kataras/pio" "github.com/sirupsen/logrus" ) func init() { // take an output from a print function output := pio.OutputFrom.Printf(logrus.Errorf) // register a new printer with name "logrus" // which will be able to read text and print as string. pio.Register("logrus", output).Marshal(pio.Text) } func main() { for i := 1; i <= 5; i++ { <-time.After(time.Second) pio.Print(fmt.Sprintf("[%d] This is an error message that will be printed to the logrus' printer", i)) } } pio-0.0.14/_examples/integrations/logrus/print/main.go000066400000000000000000000003201477164040000227660ustar00rootroot00000000000000package main import ( "github.com/kataras/pio" _ "github.com/kataras/pio/_examples/integrations/logrus" ) func main() { pio.Print("This is an info message that will be printed to the logrus' printer") } pio-0.0.14/_examples/integrations/logrus/print/multi/000077500000000000000000000000001477164040000226525ustar00rootroot00000000000000pio-0.0.14/_examples/integrations/logrus/print/multi/main.go000066400000000000000000000006431477164040000241300ustar00rootroot00000000000000package main import ( "fmt" "os" "time" "github.com/kataras/pio" _ "github.com/kataras/pio/_examples/integrations/logrus" ) func main() { var ( delay = 2 * time.Second times = 5 ) pio.Get("logrus").AddOutput(os.Stdout) i := 1 for range time.Tick(delay) { pio.Print(fmt.Sprintf("[%d] Printing %d\n", i, time.Now().Second())) if i == times { break } i++ } <-time.After(1 * time.Second) } pio-0.0.14/_examples/integrations/logrus/scan/000077500000000000000000000000001477164040000213105ustar00rootroot00000000000000pio-0.0.14/_examples/integrations/logrus/scan/main.go000066400000000000000000000007331477164040000225660ustar00rootroot00000000000000package main import ( "io" "os" "time" "github.com/kataras/pio" "github.com/sirupsen/logrus" ) func main() { var ( delay = 2 * time.Second times = 5 ) pio.Register("default", os.Stdout) reader, writer := io.Pipe() logrus.SetOutput(writer) cancel := pio.Scan(reader, true) i := 1 for range time.Tick(delay) { logrus.Printf("[%d] Printing %d", i, time.Now().Second()) if i == times { break } i++ } <-time.After(1 * time.Second) cancel() } pio-0.0.14/_examples/marshaler/000077500000000000000000000000001477164040000163215ustar00rootroot00000000000000pio-0.0.14/_examples/marshaler/main.go000066400000000000000000000032611477164040000175760ustar00rootroot00000000000000package main import ( "os" "time" "github.com/kataras/pio" ) type message struct { From string `json:"printer_name"` // fields should be exported, as you already know. Order int `json:"order"` Datetime string `json:"date"` Message string `json:"message"` } func main() { // showcase json println("-----------") printWith("json", pio.JSONIndent) // showcase xml println("-----------") printWith("xml", pio.XMLIndent) // show case text println("-----------") pio.Register("text", os.Stderr).Marshal(pio.Text) pio.Println("this is a text message, from text printer that has been registered inline") print("-----------") } func printWith(printerName string, marshaler pio.Marshaler) { // this "json" printer is responsible to print // text (string) and json structs. // use `pio#JSON` or `pio#JSONIdent` for nicer print format // and `encoding/json#Marshal` is the same thing, // pio is fully compatible with standard marshal functions. p := pio.NewPrinter(printerName, os.Stderr). Marshal(marshaler) p.Println(message{ From: printerName, Order: 1, Datetime: time.Now().Format("2006/01/02 - 15:04:05"), Message: "This is our structed error log message", }) p.Println(message{ From: printerName, Order: 2, Datetime: time.Now().Format("2006/01/02 - 15:04:05"), Message: "This is our second structed error log message", }) // this will print only xml because we use a single printer to print // // p := pio.Register("xml", os.Stderr).WithMarshalers(pio.XMLIndent) // p.Print(message{ // Order: 3, // Datetime: time.Now().Format("2006/01/02 - 15:04:05"), // Message: "This is our second structed error log message", // }) } pio-0.0.14/_examples/wrapper/000077500000000000000000000000001477164040000160235ustar00rootroot00000000000000pio-0.0.14/_examples/wrapper/main.go000066400000000000000000000012401477164040000172730ustar00rootroot00000000000000package main import ( "fmt" "time" "github.com/kataras/pio" "github.com/sirupsen/logrus" ) /* go get -u github.com/sirupsen/logrus */ func init() { // take an output from a print function output := pio.Wrap(logrus.Errorf) // register a new printer with name "logrus" // which will be able to read text and print as string. pio.Register("logrus", output).Marshal(pio.Text) // p := pio.Register("logrus", output).Marshal(pio.Text) // p.Print("using the logrus printer only") } func main() { for i := 1; i <= 5; i++ { <-time.After(time.Second) pio.Print(fmt.Sprintf("[%d] This is an error message that will be printed to the logrus' printer", i)) } } pio-0.0.14/adapter.go000066400000000000000000000063141477164040000143410ustar00rootroot00000000000000package pio import ( "io" ) // Available sources. type ( printFunc func(interface{}) printVariadicFunc func(...interface{}) printfFunc func(string, ...interface{}) printlnFunc func(string) ) type writerFunc func([]byte) (int, error) func (w writerFunc) Write(p []byte) (n int, err error) { return w(p) } // Wrap returns a new output based on the "printfFn" // if not a compatible output found then it will // return a writer which writes nothing. // // To check if the wrapping worked // you can check if the result `io.Writer` // `IsNop`, i.e: // std's log.Panicf is not a compatible interface // // output := Output(log.Panicf) // // if IsNop(output) { // // conversation failed, do something or panic. // } func Wrap(printFn interface{}) io.Writer { switch printFn.(type) { case io.Writer: return printFn.(io.Writer) case writerFunc: return printFn.(io.Writer) case printFunc: return OutputFrom.Print(printFn.(printFunc)) case printVariadicFunc: return OutputFrom.PrintVardiadic(printFn.(printVariadicFunc)) case printfFunc: return OutputFrom.Printf(printFn.(printfFunc)) case printlnFunc: return OutputFrom.Println(printFn.(printlnFunc), false) } return NopOutput() } // OutputFrom is a variable // which contains some helpers that can // convert some forms of output to compatible `io.Writer` // in order to be passed to the `NewPrinter` or `Register` functions. var OutputFrom = OutputAdapters{} // OutputAdapters is a struct // which contains some forms of output // and convert them to a compatible `io.Writer` // in order to be passed to the `NewPrinter` or `Register` functions. type OutputAdapters struct{} // Print converts a func(v interface{}) to a compatible `io.Writer`. func (a *OutputAdapters) Print(print func(v interface{})) io.Writer { return &printAdapter{ print: print, } } // PrintVardiadic converts a func(v ...interface{}) to a compatible `io.Writer`. func (a *OutputAdapters) PrintVardiadic(print func(v ...interface{})) io.Writer { return &printVariadicAdapter{ printVariadic: print, } } // Printf converts a func(string, ...interface{}) to a compatible `io.Writer`. func (a *OutputAdapters) Printf(printf func(format string, args ...interface{})) io.Writer { return &printfAdapter{ printf: printf, } } // Println converts a func(string) to a compatible `io.Writer`. // if "newLine" is true then "\n" will be appended to the "s". func (a *OutputAdapters) Println(println func(s string), newLine bool) io.Writer { return &printlnAdapter{ println: println, newLine: newLine, } } type ( printAdapter struct { print printFunc } printVariadicAdapter struct { printVariadic printVariadicFunc } printfAdapter struct { printf printfFunc } printlnAdapter struct { println printlnFunc newLine bool } ) func (p *printAdapter) Write(b []byte) (int, error) { p.print(string(b)) return len(b), nil } func (p *printVariadicAdapter) Write(b []byte) (int, error) { p.printVariadic(string(b)) return len(b), nil } func (p *printfAdapter) Write(b []byte) (int, error) { p.printf(string(b)) return len(b), nil } func (p *printlnAdapter) Write(b []byte) (int, error) { if p.newLine { b = append(b, NewLine...) } p.println(string(b)) return len(b), nil } pio-0.0.14/color.go000066400000000000000000000061461477164040000140420ustar00rootroot00000000000000package pio import ( "fmt" "io" "strings" ) // Standard color codes, any color code can be passed to `Rich` package-level function, // when the destination terminal supports. const ( Black = 30 + iota Red Green Yellow Blue Magenta Cyan White Gray = White ColorReset = 0 ) const ( toBase8 = "\x1b[%dm%s\x1b[0m" toBase16Bright = "\x1b[%d;1m%s\x1b[0m" toBase256 = "\x1b[38;5;%dm%s\x1b[0m" toBase256Bright = "\x1b[38;5;%d;1m%s\x1b[0m" ) // RichOption declares a function which can be passed to the `Rich` function // to modify a text. // // Builtin options are defined below: // - `Bright` // - `Background` // - `Bold` // - `Underline` and // - `Reversed`. type RichOption func(s *string, colorCode *int, format *string) // Rich accepts "s" text and a "colorCode" (e.g. `Black`, `Green`, `Magenta`, `Cyan`...) // and optional formatters and returns a colorized (and decorated) string text that it's ready // to be printed through a compatible terminal. // // Look: // - Bright // - Background // - Bold // - Underline // - Reversed func Rich(s string, colorCode int, options ...RichOption) string { if s == "" { return "" } format := toBase8 if colorCode < Black || colorCode > 10+White { format = toBase256 } for _, opt := range options { opt(&s, &colorCode, &format) } return fmt.Sprintf(format, colorCode, s) } // WriteRich same as `Rich` but it accepts an `io.Writer` to write to, e.g. a `StringBuilder` or a `pio.Printer`. func WriteRich(w io.Writer, s string, colorCode int, options ...RichOption) { if s == "" { return } if p, ok := w.(*Printer); ok { var ( richBytes, rawBytes []byte ) for _, output := range p.outputs { if SupportColors(output) { if len(richBytes) == 0 { richBytes = []byte(Rich(s, colorCode, options...)) // no strToBytes; colors are conflicting with --race detector. } _, _ = output.Write(richBytes) } else { if len(rawBytes) == 0 { rawBytes = []byte(s) } _, _ = output.Write(rawBytes) } } return } if SupportColors(w) { s = Rich(s, colorCode, options...) } _, _ = fmt.Fprint(w, s) } // Bright sets a "bright" or "bold" style to the colorful text. func Bright(s *string, colorCode *int, format *string) { if strings.Contains(*format, "38;5;%d") { *format = toBase256Bright return } *format = toBase16Bright } // Background marks the color to background. // See `Reversed` too. func Background(s *string, colorCode *int, format *string) { *colorCode += 10 } // Bold adds a "bold" decoration to the colorful text. // See `Underline` and `Reversed` too. func Bold(s *string, colorCode *int, format *string) { *s = "\x1b[1m" + *s } // Underline adds an "underline" decoration to the colorful text. // See `Bold` and `Reversed` too. func Underline(s *string, colorCode *int, format *string) { *s = "\x1b[4m" + *s } // Reversed adds a "reversed" decoration to the colorful text. // This means that the background will be the foreground and the // foreground will be the background color. // // See `Bold` and `Underline` too. func Reversed(s *string, colorCode *int, format *string) { *s = "\x1b[7m" + *s } pio-0.0.14/color_test.go000066400000000000000000000020601477164040000150700ustar00rootroot00000000000000package pio import ( "fmt" ) func ExampleRich() { fmt.Println(Rich("black", Black)) fmt.Println(Rich("cyan", Cyan)) fmt.Println(Rich("background cyan", Cyan, Background, )) fmt.Println(Rich("bright yellow", Yellow, Bright, )) fmt.Println(Rich("bright bold magenta", Magenta, Bright, Bold, )) fmt.Println(Rich("bold cyan", Cyan, Bold, )) fmt.Println(Rich("bold underline reversed bright cyan", Cyan, Bright, Bold, Underline, Reversed, )) fmt.Println(Rich("extended 256-color custom color: 153 (blue-ish)", 153)) fmt.Println(Rich("extended 256-color custom bright reversed color: 153 (blue-ish)", 153, Bright, Reversed, )) // Output: // black // cyan // background cyan // bright yellow // bright bold magenta // bold cyan // bold underline reversed bright cyan // extended 256-color custom color: 153 (blue-ish) // extended 256-color custom bright reversed color: 153 (blue-ish) } pio-0.0.14/go.mod000066400000000000000000000001111477164040000134650ustar00rootroot00000000000000module github.com/kataras/pio go 1.24 require golang.org/x/sys v0.31.0 pio-0.0.14/go.sum000066400000000000000000000002311477164040000135150ustar00rootroot00000000000000golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= pio-0.0.14/hijacker.go000066400000000000000000000051371477164040000145030ustar00rootroot00000000000000package pio import ( "errors" "sync" ) // Hijacker is the signature implemented by callers // that want to hijack the Print method. // // Look `Printer#Hijack` for more. type Hijacker func(*Ctx) var ( // ErrCanceled is returned when a hijacker canceled a specific print action. ErrCanceled = errors.New("canceled") // ErrSkipped it returned from marshaler or hijacker // when the content should be skipped and printer should avoid printing it. ErrSkipped = errors.New("skipped") // ErrHandled can be returned from a hijacker to specify // that the hijacker handled the write operation itself, // therefore pio does not need to do anything else. ErrHandled = errors.New("handled") ) var cPool = sync.Pool{New: func() interface{} { return &Ctx{} }} func acquireCtx(v interface{}, printer *Printer) *Ctx { ctx := cPool.Get().(*Ctx) ctx.Printer = printer ctx.Value = v ctx.marshalResult.b = ctx.marshalResult.b[0:0] ctx.marshalResult.err = nil ctx.canceled = false ctx.continueToNext = false return ctx } func releaseCtx(ctx *Ctx) { cPool.Put(ctx) } // Ctx is the current context of the Printer's hijacker, // should not be used inside goroutines, // exiting this hijacker allows the Printer to continue its execution. type Ctx struct { // Printer is the current Printer which this ctx is owned by. Printer *Printer // Value is the argument passed to the `Printer#Print`. // // Value shoult not be changed. Value interface{} marshalResult struct { b []byte err error } continueToNext bool canceled bool } // MarshalValue marshals the `Value` // and skips the marshal operation on the `Printer#Print` state. // // Remember that if `MarshalValue` called after a `SetResult` // then it will not operate a marshaling and return the // stored result instead. func (ctx *Ctx) MarshalValue() ([]byte, error) { if len(ctx.marshalResult.b) > 0 { return ctx.marshalResult.b, ctx.marshalResult.err } if ctx.Printer.marshal == nil { return nil, ErrSkipped } b, err := ctx.Printer.marshal(ctx.Value) ctx.marshalResult.b = b ctx.marshalResult.err = err return b, err } // Store bypasses the marshaler and sets the result explicitly. // If any of the next hijackers try to call the `MarshalValue` then it will // return the results that had set here. func (ctx *Ctx) Store(result []byte, err error) { ctx.marshalResult.b = result ctx.marshalResult.err = err } // Cancel cancels the printing of this `Value`. func (ctx *Ctx) Cancel() { ctx.canceled = true } // Next allows to continue to the next hijacker,if available, when this hijacker finished. func (ctx *Ctx) Next() { ctx.continueToNext = true } pio-0.0.14/marshaler.go000066400000000000000000000050361477164040000146770ustar00rootroot00000000000000package pio import ( "encoding/json" "encoding/xml" "errors" ) // Marshaler is the interface implemented by types that // can marshal themselves into valid output. type Marshaler interface { Marshal(v interface{}) ([]byte, error) } // Marshaled or (especially British, marshalled) is an interface which // is implemented by values that can marshal theirselves. // // It's like Marshaler but it doesn't takes an argument. type Marshaled interface { Marshal() ([]byte, error) } func fromMarshaled(self Marshaled) Marshaler { return MarshalerFunc(func(v interface{}) ([]byte, error) { return self.Marshal() }) } // MarshalerFunc is the signature implemented by callers that // are responsible to marshal "v" into valid printable result. // // Look `Printer#Marshal` for more. type MarshalerFunc func(v interface{}) ([]byte, error) // Marshal makes the Marshaler compatible with the // standard golang's marshalers, so a marshaler // created for a Printer, can be used on std packages as well. func (m MarshalerFunc) Marshal(v interface{}) ([]byte, error) { return m(v) } // ErrMarshalNotResponsible retruns from a marshaler // when it's not responsible and should continue to the next marshaler. var ErrMarshalNotResponsible = errors.New("this marshaler is not responsible for this type of data") // ErrMarshalNotFound or ErrSkipped can be used to skip a specific // printer's output operation. var ErrMarshalNotFound = errors.New("no marshaler found for this type of dat") // Text is a Text marshaler, it converts // string to a compatible form of []byte. var Text = MarshalerFunc(func(v interface{}) ([]byte, error) { if b, ok := v.([]byte); ok { return b, nil } if s, ok := v.(string); ok { return []byte(s), nil // maybe 0101010 010110 here, but can be overridden by fmt.Sprintf("%s", v) } return nil, ErrMarshalNotResponsible }) var ( // JSON returns the JSON encoding of Printer#Print%v. // A shortcut for `encoding/json#Marshal` JSON = MarshalerFunc(json.Marshal) // JSONIndent returns the JSON encoding of Printer#Print%v. // A shortcut for `encoding/json#MarshalIndent(v, ""," ")` JSONIndent = MarshalerFunc(func(v interface{}) ([]byte, error) { return json.MarshalIndent(v, "", " ") }) // XML returns the XML encoding of Printer#Print%v. // A shortcut for `encoding/xml#Marshal` XML = MarshalerFunc(xml.Marshal) // XMLIndent returns the XML encoding of Printer#Print%v. // A shortcut for `encoding/xml#MarshalIndent(v, ""," ")` XMLIndent = MarshalerFunc(func(v interface{}) ([]byte, error) { return xml.MarshalIndent(v, "", " ") }) ) pio-0.0.14/nop.go000066400000000000000000000014531477164040000135140ustar00rootroot00000000000000package pio import ( "io" ) // IsNop can check wether an `w` io.Writer // is a NopOutput. func IsNop(w io.Writer) bool { if isN, ok := w.(interface { IsNop() bool }); ok { return isN.IsNop() } return false } type nopOutput struct{} func (w *nopOutput) Write(b []byte) (n int, err error) { // return the actual length in order to `AddPrinter(...)` to be work with io.MultiWriter return len(b), nil } // IsNop defines this wrriter as a nop writer. func (w *nopOutput) IsNop() bool { return true } // NopOutput returns an `io.Writer` which writes nothing. func NopOutput() io.Writer { return &nopOutput{} } type nopCloser struct{} func (c *nopCloser) Close() error { return nil } // NopCloser returns an `io.Closer` which // does nothing. func NopCloser() io.Closer { return &nopCloser{} } pio-0.0.14/pio.go000066400000000000000000000042561477164040000135130ustar00rootroot00000000000000package pio import ( "io" ) // Version is the current PIO version. const Version = "0.0.10" // NewLine is a slice of bytes which controls the // how a new line should be presented. // // Defaults to \n. var NewLine = []byte("\n") // Default returns the default, package-level registry instance. var Default = NewRegistry() // RegisterPrinter registers an already-created printer to the // registry. // // If a printer with the same `Name` is already // registered then it will be overridden by // this new "printer". // // Returns the Registry, therefore it can be used as builder. func RegisterPrinter(p *Printer) *Registry { return Default.RegisterPrinter(p) } // Register creates and registers a new Printer // based on a name(string) and an "output"(io.Writer). // // If a printer with the same `Name` is already // registered then it will be overridden by // this new "printer". // // Look `OutputFrom` too. // // Returns the just created Printer. func Register(printerName string, output io.Writer) *Printer { return Default.Register(printerName, output) } // Get returns a Printer based on the "printerName". // If printer with this name can't be found then // this function will return nil, so a check for // nil is always a good practice. func Get(printerName string) *Printer { return Default.Get(printerName) } // Remove deletes a printer item from the printers collection // by its name. func Remove(printerName string) { Default.Remove(printerName) } // Print accepts a value of "v", // tries to marshal its contents and flushes the result // to all available printers. func Print(v interface{}) (int, error) { return Default.Print(v) } // Println accepts a value of "v", // tries to marshal its contents and flushes the result // to all available printers, it adds a new line at the ending, // the result doesn't contain this new line, therefore result's contnets kept as expected. func Println(v interface{}) (int, error) { return Default.Println(v) } // Scan scans everything from "r" and prints // its new contents to the printers, // forever or until the returning "cancel" is fired, once. func Scan(r io.Reader, addNewLine bool) (cancel func()) { return Default.Scan(r, addNewLine) } pio-0.0.14/printer.go000066400000000000000000000343211477164040000144030ustar00rootroot00000000000000package pio import ( "bufio" "bytes" "io" "io/ioutil" "strconv" "sync" "sync/atomic" "github.com/kataras/pio/terminal" ) type ( // Handler is the signature implemented by callers // that want to be notified about the results // that are being printed to the Printer's output. // // Look `Printer#Handle` for more. Handler func(PrintResult) ) // Printer is responsible to print the end result. type Printer struct { Name string IsTerminal bool priority int // higher means try to print first from this printer, from `Registry#Print` // if Chained is true then the parent `Registry#Print` // will continue to search for a compatible printer // even if this printer succeed to print the contents. Chained bool Output io.Writer // The "outputs" field holds the total writers, // the "Output" and any writer added by an `AddOutput` call. // The `AddOutput` method sets the Output to a new `io.MultiWriter` // but the underline struct is unexported therefore we don't have access to // to the total writers. However sometimes, we need those writers. // E.g. for the `WriteRich` feature; // in order to write color symbols in the writers that support 256-bit colors // and write raw text to those that do not (e.g. files). // Note: we could implementing our own multi writer and skip the use // of the std package, but let's don't do that unless is requested. outputs []*outputWriter mu sync.Mutex marshal MarshalerFunc hijack Hijacker handlers []Handler // These three will complete the interface of the: // https://golang.org/pkg/io/#ReadWriteCloser // in order to make possible to use everything inside the `io` package. // i.e // https://golang.org/pkg/io/#example_MultiWriter // https://golang.org/pkg/io/#example_TeeReader (piping) io.Reader io.Writer io.Closer // DirectOutput will output the contents and flush them as fast as possible, // without storing them to the buffer to complete the `ReadWriteCloser` std interface. // Enable this if you need performance and you don't use the standard functions like `TeeReader`. DirectOutput bool sync bool // See `SetSync`. } var ( // TotalPrinters holds the number of // the total printers created, either by // `NewPrinter`, `NewTextPrinter`, `Register` or `RegisterPrinter` TotalPrinters int32 ) // NewPrinter returns a new named printer // if "output" is nil then it doesn't prints anywhere. // // If "name" is empty then it will be filled with // "printer_$printers.len". // // If the marshaler is nil, meaning that this writer's // result will never being proceed, caller should // add a marshaler using the `Marshal` function. // // Look `OutputFrom` too. func NewPrinter(name string, output io.Writer) *Printer { if output == nil { output = NopOutput() } atomic.AddInt32(&TotalPrinters, 1) if name == "" { totalPrinters := atomic.LoadInt32(&TotalPrinters) lens := strconv.Itoa(int(totalPrinters)) name = "printer_" + lens } buf := &bytes.Buffer{} isOuputTerminal := SupportColors(output) p := &Printer{ Name: name, Output: output, outputs: wrapWriters(output), Writer: buf, Reader: buf, Closer: NopCloser(), IsTerminal: isOuputTerminal, } // If "output" is terminal then a text marshaler will be // added to the Printer's marshalers. // // if p.IsTerminal { // p.Marshal(Text) // } // // let's think of it // if a user don't want it we can't force this printer // to print texts too, the end-developer // may have split his logic about logging // so don't do it automatically, instead // create a new function which will return a text printer // and allow this printer to accept more than one marshalers. return p } // NewTextPrinter same as NewPrinter but registers // a text marshaler, no matter what kind of "output", // which converts string type // to a compatible form of slice of bytes. // // If "name" is empty then it will be filled with // "printer_$printers.len". // // Look `OutputFrom` too. func NewTextPrinter(name string, output io.Writer) *Printer { p := NewPrinter(name, output) p.Marshal(Text) return p } // Priority changes the order of this printer. // Higher value means that the `Registry#Print` // will try to print first from this printer. // Default order is 0 for all printers. // // Returns it self. func (p *Printer) Priority(prio int) *Printer { p.mu.Lock() p.priority = prio p.mu.Unlock() return p } // Marshal adds a "marshaler" to the printer. // Returns itself. func (p *Printer) Marshal(marshaler Marshaler) *Printer { return p.MarshalFunc(marshaler.Marshal) } // MarshalFunc adds a "marshaler" to the printer. // Returns itself. func (p *Printer) MarshalFunc(marshaler func(v interface{}) ([]byte, error)) *Printer { p.mu.Lock() defer p.mu.Unlock() if p.marshal == nil { p.marshal = marshaler return p } oldM := p.marshal newM := marshaler // false on first failure p.marshal = func(v interface{}) ([]byte, error) { b, err := oldM(v) // check if we can continue to the next marshal func if err != nil && err.Error() == ErrMarshalNotResponsible.Error() { b, err = newM(v) } // if no data return but err is nil, then something went wrong if len(b) <= 0 && err == nil { return b, ErrSkipped } return b, err // p.addNewLineInneed(b), err } return p } // WithMarshalers same as `Marshal` but accepts more than one marshalers // and returns the Printer itself in order to be used side by side with the creational // function. func (p *Printer) WithMarshalers(marshalers ...Marshaler) *Printer { if len(marshalers) == 0 { return p } for _, marshaler := range marshalers { p.Marshal(marshaler) } return p } // AddOutput adds one or more io.Writer to the Printer. // Returns itself. // // Look `OutputFrom` and `Wrap` too. func (p *Printer) AddOutput(writers ...io.Writer) *Printer { p.mu.Lock() defer p.mu.Unlock() for _, w := range writers { // set is terminal to false // if at least one of the writers // is not a terminal-based. if !terminal.IsTerminal(w) { p.IsTerminal = false break } } p.outputs = append(p.outputs, wrapWriters(writers...)...) w := io.MultiWriter(append(writers, p.Output)...) p.Output = w return p } // SetOutput sets accepts one or more io.Writer // and set a multi-writter instance to the Printer's Output. // Returns itself. // // Look `OutputFrom` too. func (p *Printer) SetOutput(writers ...io.Writer) *Printer { var w io.Writer if l := len(writers); l == 0 { return p } else if l == 1 { w = writers[0] } else { w = io.MultiWriter(writers...) } p.mu.Lock() p.outputs = wrapWriters(writers...) p.Output = w p.IsTerminal = terminal.IsTerminal(w) p.mu.Unlock() return p } // EnableDirectOutput will output the contents and flush them as fast as possible, // without storing them to the buffer to complete the `ReadWriteCloser` std interface. // Enable this if you need performance and you don't use the standard functions like `TeeReader`. // Returns itself. func (p *Printer) EnableDirectOutput() *Printer { p.mu.Lock() p.DirectOutput = true p.mu.Unlock() return p } // SetSync protects the output writer(s) with a lock. func (p *Printer) SetSync(useLocks bool) *Printer { p.mu.Lock() p.sync = true p.mu.Unlock() return p } func (p *Printer) lock() { if p.sync { p.mu.Lock() } } func (p *Printer) unlock() { if p.sync { p.mu.Unlock() } } // Print of a Printer accepts a value of "v", // tries to marshal its contents and flushes the result // to the Printer's output. // // If "v" implements the `Marshaler` type, then this marshaler // is called automatically, first. // // Print -> Store[Marshal -> err != nil && result -> Hijack(result) -> Write(result)] -> Flush[Printer.Write(buf) and Handle(buf)] // // Returns how much written and an error on failure. func (p *Printer) Print(v interface{}) (int, error) { return p.print(v, false) } // Println accepts a value of "v", // tries to marshal its contents and flushes the result // to this "p" Printer, it adds a new line at the ending, // the result doesn't contain this new line, therefore result's contents kept as expected. func (p *Printer) Println(v interface{}) (int, error) { return p.print(v, true) } func (p *Printer) print(v interface{}, appendNewLine bool) (int, error) { var ( b []byte err error ) if p.DirectOutput { b, err = p.WriteTo(v, p.Output, appendNewLine) } else { _, err = p.WriteTo(v, p.Writer, appendNewLine) if err != nil { return -1, err } b, err = p.Flush() } // flush error return last, // we should call handlers even if the result is a failure. if len(p.handlers) > 0 { // create the print result instance // only when printer uses handlers, so we can reduce the factory calls. res := withValue(v).withErr(err).withContents(b) for _, h := range p.handlers { // do NOT run each handler on its own goroutine because we need sync with the messages. // let end-developer decide the pattern. h(res) } } return len(b), err } func (p *Printer) readAndConsume() ([]byte, error) { b, err := ioutil.ReadAll(p.Reader) if err != nil && err != io.EOF { return b, err } return b, nil } // Flush will consume and flush the Printer's current contents. func (p *Printer) Flush() ([]byte, error) { p.lock() defer p.unlock() b, err := p.readAndConsume() if err != nil { return nil, err } _, err = p.Output.Write(b) return b, err } // Store will store-only the contents of "v". // Returns a PrintResult type in order to the final contents // be accessible by third-party tools. // // If you want to Print and Flush to the Printer's Output use `Print` instead. // // If "appendNewLine" is true then it writes a new line to the // Printer's output. Note that it doesn't concat it to the // returning PrintResult, therefore the "appendNewLine" it is not affect the rest // of the implementation like custom hijackers and handlers. func (p *Printer) Store(v interface{}, appendNewLine bool) error { _, err := p.WriteTo(v, p.Writer, appendNewLine) return err } // Write implements the io.Writer for the `Printer`. func (p *Printer) Write(b []byte) (n int, err error) { p.lock() if p.DirectOutput { n, err = p.Output.Write(b) } else { n, err = p.Writer.Write(b) } p.unlock() return } // WriteTo marshals and writes the "v" to the "w" writer. // // Returns this WriteTo's result information such as error, written. func (p *Printer) WriteTo(v interface{}, w io.Writer, appendNewLine bool) ([]byte, error) { var ( b []byte err error ) if hijack := p.hijack; hijack != nil { ctx := acquireCtx(v, p) defer releaseCtx(ctx) hijack(ctx) if ctx.canceled { return nil, ErrCanceled } b, err = ctx.marshalResult.b, ctx.marshalResult.err if err != nil { if err == ErrHandled { return b, nil } return b, err } } // needs marshal if len(b) == 0 { var marshaler Marshaler // check if implements the Marshaled if m, ok := v.(Marshaled); ok { marshaler = fromMarshaled(m) // check if implements the Marshaler } else if m, ok := v.(Marshaler); ok { marshaler = m // otherwise make check if printer has a marshaler // if not skip this WriteTo operation, // else set the marshaler to that (most common). } else { if p.marshal != nil { marshaler = p.marshal } } if marshaler == nil { return nil, ErrSkipped } b, err = marshaler.Marshal(v) if err != nil { return b, err } } p.lock() _, err = w.Write(b) if appendNewLine && err == nil { w.Write(NewLine) // we don't care about this error. } p.unlock() return b, err } // Hijack registers a callback which is executed // when ever `Print` or `WriteTo` is called, // this callback can intercept the final result // which will be written or be printed. // // Returns itself. func (p *Printer) Hijack(cb func(ctx *Ctx)) *Printer { p.mu.Lock() defer p.mu.Unlock() if p.hijack == nil { p.hijack = cb return p } oldCb := p.hijack newCb := cb // return the first failure p.hijack = func(ctx *Ctx) { oldCb(ctx) if ctx.continueToNext { newCb(ctx) } } return p } // PrintResult contains some useful information for a `Print` or `WriteTo` action that // are available inside handlers. type PrintResult struct { Written int Error error Contents []byte Value interface{} } // IsOK returns true if result's content is available, // otherwise false. func (p PrintResult) IsOK() bool { return p.Error == nil && len(p.Contents) > 0 } // IsFailure returns true if result's content is not safe to read or it's available, // otherwise false. func (p PrintResult) IsFailure() bool { return !p.IsOK() } var printResult = PrintResult{} func withValue(v interface{}) PrintResult { printResult.Value = v return printResult } func (p PrintResult) withErr(err error) PrintResult { if err != nil { p.Written = -1 } p.Error = err return p } func (p PrintResult) withContents(b []byte) PrintResult { if p.Error != nil { p.Written = -1 } else { p.Written = len(b) p.Contents = b } return p } // Handle adds a callback which is called // whenever a `Print` is successfully executed, it's being executed // after the contents are written to its output. // // The callback accepts the final result, // can be used as an easy, pluggable, access to all the logs passed to the `Print`. // i.e: `Handle(func(result PrintResult){ fmt.Printf("%s\n", result.Contents)})` // // Returns itself. func (p *Printer) Handle(h func(PrintResult)) *Printer { p.mu.Lock() p.handlers = append(p.handlers, h) p.mu.Unlock() return p } func (p *Printer) restore(b []byte) { p.Writer.Write(b) } // Scan scans everything from "r" and prints // its new contents to the "p" Printer, // forever or until the returning "cancel" is fired, once. func (p *Printer) Scan(r io.Reader, addNewLine bool) (cancel func()) { var canceled uint32 shouldCancel := func() bool { return atomic.LoadUint32(&canceled) > 0 } cancel = func() { atomic.StoreUint32(&canceled, 1) } go func() { scanner := bufio.NewScanner(r) for { if shouldCancel() { break } if scanner.Scan() { if shouldCancel() { // re-store the bytes? p.restore(scanner.Bytes()) break } text := scanner.Bytes() if addNewLine { text = append(text, NewLine...) } p.Print(text) } if err := scanner.Err(); err != nil { // TODO: do something with that or ignore it. } } }() return cancel } pio-0.0.14/registry.go000066400000000000000000000111231477164040000145630ustar00rootroot00000000000000package pio import ( "errors" "io" "sort" "sync" ) // Registry is the Printer(s) container. // // It can be used as follows: // reg := NewRegistry(). // // RegisterPrinter(NewPrinter("err", os.Stderr)). // RegisterPrinter(NewPrinter("default", os.Stdout)). // Print("something") type Registry struct { // can change via `Register` or `RegisterPrinter` with mutex. // whenever a tool needs an `io.Writer` to do something // end-developers can pass this `Printer`. printers []*Printer mu sync.Mutex once sync.Once } // NewRegistry returns an empty printer Registry. // // Note that: // Registry have a zero value, so it can be // declared with a simple `var` keyword and without pointer. func NewRegistry() *Registry { return new(Registry) } // RegisterPrinter registers an already-created printer to the // registry. // // If `Printer#Name` is empty then it will be filled with // "printer_$printers.len". // // If a printer with the same `Printer#Name` is already // registered then it will be overridden by // this new "printer". // // Returns this Registry, therefore it can be used as builder. func (reg *Registry) RegisterPrinter(printer *Printer) *Registry { // if exists then remove first and then add the new one. if printerName := printer.Name; reg.Get(printerName) != nil { reg.Remove(printerName) } reg.mu.Lock() // no printer.Handle(s.handlers...) reg.printers = append(reg.printers, printer) reg.mu.Unlock() return reg } // Register creates and registers a new Printer // based on a name(string) and an "output"(io.Writer). // // If "printerName" is empty then it will be filled with // "printer_$printers.len". // // If a printer with the same `Printer#Name` is already // registered then it will be overridden by // this new "printer". // // Look `OutputFrom` too. // // Returns the just created Printer. func (reg *Registry) Register(printerName string, output io.Writer) *Printer { p := NewPrinter(printerName, output) reg.RegisterPrinter(p) return p } // Get returns a Printer based on the "printerName". // If printer with this name can't be found then // this function will return nil, so a check for // nil is always a good practice. func (reg *Registry) Get(printerName string) *Printer { reg.mu.Lock() defer reg.mu.Unlock() for _, p := range reg.printers { if p.Name == printerName { return p } } return nil } // Remove deletes a printer item from the printers collection // by its name. // // Returns this Registry, so it can be used as builder. func (reg *Registry) Remove(printerName string) *Registry { reg.mu.Lock() for i, p := range reg.printers { if p.Name == printerName { reg.printers = append(reg.printers[:i], reg.printers[i+1:]...) break } } reg.mu.Unlock() return reg } // Print accepts a value of "v", // tries to marshal its contents and flushes the result // to all available printers. func (reg *Registry) Print(v interface{}) (n int, err error) { return reg.printAll(v, false) } // Println accepts a value of "v", // tries to marshal its contents and flushes the result // to all available printers, it adds a new line at the ending, // the result doesn't contain this new line, therefore result's contents kept as expected. func (reg *Registry) Println(v interface{}) (n int, err error) { return reg.printAll(v, true) } func (reg *Registry) printAll(v interface{}, appendNewLine bool) (n int, err error) { // order once at first print. reg.once.Do(func() { reg.mu.Lock() sort.Slice(reg.printers, func(i, j int) bool { return reg.printers[i].priority > reg.printers[j].priority }) reg.mu.Unlock() }) for _, p := range reg.printers { prevErr := err printFunc := p.Print if appendNewLine { printFunc = p.Println } n, err = printFunc(v) if !p.Chained && n > 0 { break } n, err = combineOutputResult(n, err, prevErr) } return } func combineOutputResult(n int, err error, prevErr error) (totalN int, totalErr error) { if err != nil { if prevErr != nil { totalErr = errors.New(prevErr.Error() + string(NewLine) + err.Error()) } } totalN += n return } // Scan scans everything from "r" and prints // its new contents to the printers, // forever or until the returning "cancel" is fired, once. func (reg *Registry) Scan(r io.Reader, addNewLine bool) (cancel func()) { lp := len(reg.printers) if lp == 0 { return func() {} } cancelFuncs := make([]func(), lp, lp) cancel = func() { for _, c := range cancelFuncs { c() } } for i, p := range reg.printers { cancelFuncs[i] = p.Scan(r, addNewLine) } return cancel } func (reg *Registry) restore(b []byte) { for _, p := range reg.printers { p.restore(b) } } pio-0.0.14/terminal.go000066400000000000000000000020031477164040000145230ustar00rootroot00000000000000package pio import ( "io" "runtime" "github.com/kataras/pio/terminal" ) // outputWriter just caches the "supportColors" // in order to reduce syscalls for known printers. type outputWriter struct { io.Writer supportColors bool } func wrapWriters(output ...io.Writer) []*outputWriter { outs := make([]*outputWriter, 0, len(output)) for _, w := range output { outs = append(outs, &outputWriter{ Writer: w, supportColors: SupportColors(w), }) } return outs } // SupportColors reports whether the "w" io.Writer is not a file and it does support colors. func SupportColors(w io.Writer) bool { if w == nil { return false } if sc, ok := w.(*outputWriter); ok { return sc.supportColors } isTerminal := !IsNop(w) && terminal.IsTerminal(w) if isTerminal && runtime.GOOS == "windows" { // if on windows then return true only when it does support 256-bit colors, // this is why we initially do that terminal check for the "w" writer. return terminal.SupportColors } return isTerminal } pio-0.0.14/terminal/000077500000000000000000000000001477164040000142015ustar00rootroot00000000000000pio-0.0.14/terminal/LICENSE000066400000000000000000000027061477164040000152130ustar00rootroot00000000000000Copyright (c) 2013 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.pio-0.0.14/terminal/terminal.go000066400000000000000000000002651477164040000163460ustar00rootroot00000000000000package terminal // SupportColors reports whether a windows terminal platform can support 256 colors. // See terminal_windows.go#init for further details. var SupportColors = true pio-0.0.14/terminal/terminal_appengine.go000066400000000000000000000003021477164040000203640ustar00rootroot00000000000000//go:build appengine // +build appengine package terminal import "io" // IsTerminal returns true if stderr's file descriptor is a terminal. func IsTerminal(f io.Writer) bool { return true } pio-0.0.14/terminal/terminal_bsd.go000066400000000000000000000004031477164040000171700ustar00rootroot00000000000000//go:build (darwin || freebsd || openbsd || netbsd || dragonfly) && !appengine // +build darwin freebsd openbsd netbsd dragonfly // +build !appengine package terminal import "syscall" const ioctlReadTermios = syscall.TIOCGETA type Termios syscall.Termios pio-0.0.14/terminal/terminal_linux.go000066400000000000000000000002261477164040000175620ustar00rootroot00000000000000//go:build !appengine // +build !appengine package terminal import "syscall" const ioctlReadTermios = syscall.TCGETS type Termios syscall.Termios pio-0.0.14/terminal/terminal_notwindows.go000066400000000000000000000010761477164040000206420ustar00rootroot00000000000000//go:build (linux || darwin || freebsd || openbsd || netbsd || dragonfly) && !appengine // +build linux darwin freebsd openbsd netbsd dragonfly // +build !appengine package terminal import ( "io" "os" "syscall" "unsafe" ) // IsTerminal returns true if stderr's file descriptor is a terminal. func IsTerminal(f io.Writer) bool { var termios Termios switch v := f.(type) { case *os.File: _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) return err == 0 default: return false } } pio-0.0.14/terminal/terminal_solaris.go000066400000000000000000000006031477164040000200760ustar00rootroot00000000000000//go:build solaris && !appengine // +build solaris,!appengine package terminal import ( "io" "os" "golang.org/x/sys/unix" ) // IsTerminal returns true if the given file descriptor is a terminal. func IsTerminal(f io.Writer) bool { switch v := f.(type) { case *os.File: _, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA) return err == nil default: return false } } pio-0.0.14/terminal/terminal_windows.go000066400000000000000000000047161477164040000201250ustar00rootroot00000000000000//go:build windows && !appengine // +build windows,!appengine package terminal import ( "bytes" "errors" "io" "os" "os/exec" "strconv" "strings" "syscall" "unsafe" ) var kernel32 = syscall.NewLazyDLL("kernel32.dll") var ( procGetConsoleMode = kernel32.NewProc("GetConsoleMode") procSetConsoleMode = kernel32.NewProc("SetConsoleMode") ) const ( enableProcessedOutput = 0x0001 enableWrapAtEolOutput = 0x0002 enableVirtualTerminalProcessing = 0x0004 ) func getVersion() (float64, int, error) { stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} cmd := exec.Command("cmd", "ver") cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} cmd.Stdout = stdout cmd.Stderr = stderr err := cmd.Run() if err != nil { return -1, -1, err } errCanNotDetermineVersion := errors.New("can't determine Windows version") lines := stdout.String() start := strings.IndexByte(lines, '[') end := strings.IndexByte(lines, ']') if start == -1 || end == -1 { return -1, -1, errCanNotDetermineVersion } winLine := lines[start+1 : end] if len(winLine) < 10 { return -1, -1, errCanNotDetermineVersion } // Version 10.0.15063 versionsLine := winLine[strings.IndexByte(winLine, ' ')+1:] // 10.0.15063 versionSems := strings.Split(versionsLine, ".") // 10 // 0 // 15063 if len(versionSems) < 3 { return -1, -1, errCanNotDetermineVersion } buildNumber, _ := strconv.Atoi(versionSems[2]) major, err := strconv.ParseFloat(versionSems[0], 64) return major, buildNumber, err } func init() { // modifies the "SupportColors" package-level variable. SupportColors = false major, buildNumber, err := getVersion() if err != nil { return } // Activate Virtual Processing for Windows CMD // Info: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx if major >= 10 { handle := syscall.Handle(os.Stderr.Fd()) procSetConsoleMode.Call(uintptr(handle), enableProcessedOutput|enableWrapAtEolOutput|enableVirtualTerminalProcessing) // check specific for windows operating system // versions, after windows 10 microsoft // gave support for 256-color console. SupportColors = buildNumber >= 10586 } } // IsTerminal returns true if stderr's file descriptor is a terminal. func IsTerminal(f io.Writer) bool { switch v := f.(type) { case *os.File: var st uint32 r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0) return r != 0 && e == 0 default: return false } }