urfave/climore_vert Go Last Updated: 2022-08-13T17:34:51Z
urfave/cliclose

cli

GoDoc codebeat Go Report Card codecov

cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.

Usage Documentation

Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the master branch, which is currently v2.

Guides for migrating to newer versions:

Installation

Using this package requires a working Go environment. See the install instructions for Go.

Go Modules are required when using this package. See the go blog guide on using Go Modules.

Using v2 releases

$ go get github.com/urfave/cli/v2

go ... import ( "github.com/urfave/cli/v2" // imports as package "cli" ) ...

Using v1 releases

$ go get github.com/urfave/cli

go ... import ( "github.com/urfave/cli" ) ...

GOPATH

Make sure your PATH includes the $GOPATH/bin directory so your commands can be easily used: export PATH=$PATH:$GOPATH/bin

Supported platforms

cli is tested against multiple versions of Go on Linux, and against the latest released version of Go on OS X and Windows. This project uses Github Actions for builds. To see our currently supported go versions and platforms, look at the ./.github/workflows/cli.yml.

gin-gonic/ginmore_vert Go Last Updated: 2022-08-14T06:31:06Z
gin-gonic/ginclose

Gin Web Framework

Build Status codecov Go Report Card GoDoc Join the chat at https://gitter.im/gin-gonic/gin Sourcegraph Open Source Helpers Release TODOs

Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to httprouter. If you need performance and good productivity, you will love Gin.

Contents

Installation

To install Gin package, you need to install Go and set your Go workspace first.

  1. You first need Go installed (version 1.15+ is required), then you can use the below Go command to install Gin.

sh go get -u github.com/gin-gonic/gin

  1. Import it in your code:

go import "github.com/gin-gonic/gin"

  1. (Optional) Import net/http. This is required for example if using constants such as http.StatusOK.

go import "net/http"

Quick start

```sh

assume the following codes in example.go file

$ cat example.go ```

```go package main

import ( "net/http"

"github.com/gin-gonic/gin" )

func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") } ```

```

run example.go and visit 0.0.0.0:8080/ping (for windows "localhost:8080/ping") on browser

$ go run example.go ```

Benchmarks

Gin uses a custom version of HttpRouter

See all benchmarks

| Benchmark name | (1) | (2) | (3) | (4) | | ------------------------------ | ---------:| ---------------:| ------------:| ---------------:| | BenchmarkGinGithubAll | 43550 | 27364 ns/op | 0 B/op | 0 allocs/op | | BenchmarkAceGithubAll | 40543 | 29670 ns/op | 0 B/op | 0 allocs/op | | BenchmarkAeroGithubAll | 57632 | 20648 ns/op | 0 B/op | 0 allocs/op | | BenchmarkBearGithubAll | 9234 | 216179 ns/op | 86448 B/op | 943 allocs/op | | BenchmarkBeegoGithubAll | 7407 | 243496 ns/op | 71456 B/op | 609 allocs/op | | BenchmarkBoneGithubAll | 420 | 2922835 ns/op | 720160 B/op | 8620 allocs/op | | BenchmarkChiGithubAll | 7620 | 238331 ns/op | 87696 B/op | 609 allocs/op | | BenchmarkDencoGithubAll | 18355 | 64494 ns/op | 20224 B/op | 167 allocs/op | | BenchmarkEchoGithubAll | 31251 | 38479 ns/op | 0 B/op | 0 allocs/op | | BenchmarkGocraftWebGithubAll | 4117 | 300062 ns/op | 131656 B/op | 1686 allocs/op | | BenchmarkGojiGithubAll | 3274 | 416158 ns/op | 56112 B/op | 334 allocs/op | | BenchmarkGojiv2GithubAll | 1402 | 870518 ns/op | 352720 B/op | 4321 allocs/op | | BenchmarkGoJsonRestGithubAll | 2976 | 401507 ns/op | 134371 B/op | 2737 allocs/op | | BenchmarkGoRestfulGithubAll | 410 | 2913158 ns/op | 910144 B/op | 2938 allocs/op | | BenchmarkGorillaMuxGithubAll | 346 | 3384987 ns/op | 251650 B/op | 1994 allocs/op | | BenchmarkGowwwRouterGithubAll | 10000 | 143025 ns/op | 72144 B/op | 501 allocs/op | | BenchmarkHttpRouterGithubAll | 55938 | 21360 ns/op | 0 B/op | 0 allocs/op | | BenchmarkHttpTreeMuxGithubAll | 10000 | 153944 ns/op | 65856 B/op | 671 allocs/op | | BenchmarkKochaGithubAll | 10000 | 106315 ns/op | 23304 B/op | 843 allocs/op | | BenchmarkLARSGithubAll | 47779 | 25084 ns/op | 0 B/op | 0 allocs/op | | BenchmarkMacaronGithubAll | 3266 | 371907 ns/op | 149409 B/op | 1624 allocs/op | | BenchmarkMartiniGithubAll | 331 | 3444706 ns/op | 226551 B/op | 2325 allocs/op | | BenchmarkPatGithubAll | 273 | 4381818 ns/op | 1483152 B/op | 26963 allocs/op | | BenchmarkPossumGithubAll | 10000 | 164367 ns/op | 84448 B/op | 609 allocs/op | | BenchmarkR2routerGithubAll | 10000 | 160220 ns/op | 77328 B/op | 979 allocs/op | | BenchmarkRivetGithubAll | 14625 | 82453 ns/op | 16272 B/op | 167 allocs/op | | BenchmarkTangoGithubAll | 6255 | 279611 ns/op | 63826 B/op | 1618 allocs/op | | BenchmarkTigerTonicGithubAll | 2008 | 687874 ns/op | 193856 B/op | 4474 allocs/op | | BenchmarkTrafficGithubAll | 355 | 3478508 ns/op | 820744 B/op | 14114 allocs/op | | BenchmarkVulcanGithubAll | 6885 | 193333 ns/op | 19894 B/op | 609 allocs/op |

  • (1): Total Repetitions achieved in constant time, higher means more confident result
  • (2): Single Repetition Duration (ns/op), lower is better
  • (3): Heap Memory (B/op), lower is better
  • (4): Average Allocations per Repetition (allocs/op), lower is better

Gin v1. stable

  • [x] Zero allocation router.
  • [x] Still the fastest http router and framework. From routing to writing.
  • [x] Complete suite of unit tests.
  • [x] Battle tested.
  • [x] API frozen, new releases will not break your code.

Build with json replacement

Gin uses encoding/json as default json package but you can change it by build from other tags.

jsoniter

sh go build -tags=jsoniter .

go-json

sh go build -tags=go_json .

sonic (you have to ensure that your cpu support avx instruction.)

sh $ go build -tags="sonic avx" .

Build without MsgPack rendering feature

Gin enables MsgPack rendering feature by default. But you can disable this feature by specifying nomsgpack build tag.

sh go build -tags=nomsgpack .

This is useful to reduce the binary size of executable files. See the detail information.

API Examples

You can find a number of ready-to-run examples at Gin examples repository.

Using GET, POST, PUT, PATCH, DELETE and OPTIONS

```go func main() { // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := gin.Default()

router.GET("/someGet", getting) router.POST("/somePost", posting) router.PUT("/somePut", putting) router.DELETE("/someDelete", deleting) router.PATCH("/somePatch", patching) router.HEAD("/someHead", head) router.OPTIONS("/someOptions", options)

// By default it serves on :8080 unless a // PORT environment variable was defined. router.Run() // router.Run(":3000") for a hard coded port } ```

Parameters in path

```go func main() { router := gin.Default()

// This handler will match /user/john but will not match /user/ or /user router.GET("/user/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) })

// However, this one will match /user/john/ and also /user/john/send // If no other routers match /user/john, it will redirect to /user/john/ router.GET("/user/:name/*action", func(c *gin.Context) { name := c.Param("name") action := c.Param("action") message := name + " is " + action c.String(http.StatusOK, message) })

// For each matched request Context will hold the route definition router.POST("/user/:name/action", func(c *gin.Context) { b := c.FullPath() == "/user/:name/action" // true c.String(http.StatusOK, "%t", b) })

// This handler will add a new router for /user/groups. // Exact routes are resolved before param routes, regardless of the order they were defined. // Routes starting with /user/groups are never interpreted as /user/:name/... routes router.GET("/user/groups", func(c *gin.Context) { c.String(http.StatusOK, "The available groups are [...]") })

router.Run(":8080") } ```

Querystring parameters

```go func main() { router := gin.Default()

// Query string parameters are parsed using the existing underlying request object. // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")

c.String(http.StatusOK, "Hello %s %s", firstname, lastname)

}) router.Run(":8080") } ```

Multipart/Urlencoded Form

```go func main() { router := gin.Default()

router.POST("/form_post", func(c *gin.Context) { message := c.PostForm("message") nick := c.DefaultPostForm("nick", "anonymous")

c.JSON(http.StatusOK, gin.H{
  "status":  "posted",
  "message": message,
  "nick":    nick,
})

}) router.Run(":8080") } ```

Another example: query + post form

```sh POST /post?id=1234&page=1 HTTP/1.1 Content-Type: application/x-www-form-urlencoded

name=manu&message=thisisgreat ```

```go func main() { router := gin.Default()

router.POST("/post", func(c *gin.Context) {

id := c.Query("id")
page := c.DefaultQuery("page", "0")
name := c.PostForm("name")
message := c.PostForm("message")

fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)

}) router.Run(":8080") } ```

sh id: 1234; page: 1; name: manu; message: this_is_great

Map as querystring or postform parameters

```sh POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 Content-Type: application/x-www-form-urlencoded

names[first]=thinkerou&names[second]=tianou ```

```go func main() { router := gin.Default()

router.POST("/post", func(c *gin.Context) {

ids := c.QueryMap("ids")
names := c.PostFormMap("names")

fmt.Printf("ids: %v; names: %v", ids, names)

}) router.Run(":8080") } ```

sh ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou]

Upload files

Single file

References issue #774 and detail example code.

file.Filename SHOULD NOT be trusted. See Content-Disposition on MDN and #1693

The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done.

```go func main() { router := gin.Default() // Set a lower memory limit for multipart forms (default is 32 MiB) router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // Single file file, _ := c.FormFile("file") log.Println(file.Filename)

// Upload the file to specific dst.
c.SaveUploadedFile(file, dst)

c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))

}) router.Run(":8080") } ```

How to curl:

bash curl -X POST http://localhost:8080/upload \ -F "file=@/Users/appleboy/test.zip" \ -H "Content-Type: multipart/form-data"

Multiple files

See the detail example code.

```go func main() { router := gin.Default() // Set a lower memory limit for multipart forms (default is 32 MiB) router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // Multipart form form, _ := c.MultipartForm() files := form.File["upload[]"]

for _, file := range files {
  log.Println(file.Filename)

  // Upload the file to specific dst.
  c.SaveUploadedFile(file, dst)
}
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))

}) router.Run(":8080") } ```

How to curl:

bash curl -X POST http://localhost:8080/upload \ -F "upload[]=@/Users/appleboy/test1.zip" \ -F "upload[]=@/Users/appleboy/test2.zip" \ -H "Content-Type: multipart/form-data"

Grouping routes

```go func main() { router := gin.Default()

// Simple group: v1 v1 := router.Group("/v1") { v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint) }

// Simple group: v2 v2 := router.Group("/v2") { v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint) }

router.Run(":8080") } ```

Blank Gin without middleware by default

Use

go r := gin.New()

instead of

go // Default With the Logger and Recovery middleware already attached r := gin.Default()

Using middleware

```go func main() { // Creates a router without any middleware by default r := gin.New()

// Global middleware // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. // By default gin.DefaultWriter = os.Stdout r.Use(gin.Logger())

// Recovery middleware recovers from any panics and writes a 500 if there was one. r.Use(gin.Recovery())

// Per route middleware, you can add as many as you desire. r.GET("/benchmark", MyBenchLogger(), benchEndpoint)

// Authorization group // authorized := r.Group("/", AuthRequired()) // exactly the same as: authorized := r.Group("/") // per group middleware! in this case we use the custom created // AuthRequired() middleware just in the "authorized" group. authorized.Use(AuthRequired()) { authorized.POST("/login", loginEndpoint) authorized.POST("/submit", submitEndpoint) authorized.POST("/read", readEndpoint)

// nested group
testing := authorized.Group("testing")
// visit 0.0.0.0:8080/testing/analytics
testing.GET("/analytics", analyticsEndpoint)

}

// Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ```

Custom Recovery behavior

```go func main() { // Creates a router without any middleware by default r := gin.New()

// Global middleware // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. // By default gin.DefaultWriter = os.Stdout r.Use(gin.Logger())

// Recovery middleware recovers from any panics and writes a 500 if there was one. r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { if err, ok := recovered.(string); ok { c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) } c.AbortWithStatus(http.StatusInternalServerError) }))

r.GET("/panic", func(c *gin.Context) { // panic with a string -- the custom middleware could save this to a database or report it to the user panic("foo") })

r.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "ohai") })

// Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ```

How to write log file

```go func main() { // Disable Console Color, you don't need console color when writing the logs to file. gin.DisableConsoleColor()

// Logging to a file.
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)

// Use the following code if you need to write the logs to file and console at the same time.
// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
})

   router.Run(":8080") } ```

Custom Log Format

```go func main() { router := gin.New()

// LoggerWithFormatter middleware will write the logs to gin.DefaultWriter // By default gin.DefaultWriter = os.Stdout router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {

// your custom format
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
    param.ClientIP,
    param.TimeStamp.Format(time.RFC1123),
    param.Method,
    param.Path,
    param.Request.Proto,
    param.StatusCode,
    param.Latency,
    param.Request.UserAgent(),
    param.ErrorMessage,
)

})) router.Use(gin.Recovery())

router.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") })

router.Run(":8080") } ```

Sample Output

sh ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "

Controlling Log output coloring

By default, logs output on console should be colorized depending on the detected TTY.

Never colorize logs:

```go func main() { // Disable log's color gin.DisableConsoleColor()

// Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware
router := gin.Default()

router.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
})

router.Run(":8080")

} ```

Always colorize logs:

```go func main() { // Force log's color gin.ForceConsoleColor()

// Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware
router := gin.Default()

router.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
})

router.Run(":8080")

} ```

Model binding and validation

To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).

Gin uses go-playground/validator/v10 for validation. Check the full docs on tags usage here.

Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set json:"fieldname".

Also, Gin provides two sets of methods for binding:

  • Type - Must bind
    • Methods - Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader, BindTOML
    • Behavior - These methods use MustBindWith under the hood. If there is a binding error, the request is aborted with c.AbortWithError(400, err).SetType(ErrorTypeBind). This sets the response status code to 400 and the Content-Type header is set to text/plain; charset=utf-8. Note that if you try to set the response code after this, it will result in a warning [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422. If you wish to have greater control over the behavior, consider using the ShouldBind equivalent method.
  • Type - Should bind
    • Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindHeader, ShouldBindTOML,
    • Behavior - These methods use ShouldBindWith under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.

When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use MustBindWith or ShouldBindWith.

You can also specify that specific fields are required. If a field is decorated with binding:"required" and has a empty value when binding, an error will be returned.

```go // Binding from JSON type Login struct { User string form:"user" json:"user" xml:"user" binding:"required" Password string form:"password" json:"password" xml:"password" binding:"required" }

func main() { router := gin.Default()

// Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login if err := c.ShouldBindJSON(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return }

if json.User != "manu" || json.Password != "123" {
  c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
  return
}

c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})

})

// Example for binding XML ( // // // manu // 123 // ) router.POST("/loginXML", func(c *gin.Context) { var xml Login if err := c.ShouldBindXML(&xml); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return }

if xml.User != "manu" || xml.Password != "123" {
  c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
  return
}

c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})

})

// Example for binding a HTML form (user=manu&password=123) router.POST("/loginForm", func(c *gin.Context) { var form Login // This will infer what binder to use depending on the content-type header. if err := c.ShouldBind(&form); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return }

if form.User != "manu" || form.Password != "123" {
  c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
  return
}

c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})

})

// Listen and serve on 0.0.0.0:8080 router.Run(":8080") } ```

Sample request

```sh $ curl -v -X POST \ http://localhost:8080/loginJSON \ -H 'content-type: application/json' \ -d '{ "user": "manu" }'

POST /loginJSON HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.51.0 Accept: / content-type: application/json Content-Length: 18

  • upload completely sent off: 18 out of 18 bytes < HTTP/1.1 400 Bad Request < Content-Type: application/json; charset=utf-8 < Date: Fri, 04 Aug 2017 03:51:31 GMT < Content-Length: 100 < {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} ```

Skip validate: when running the above example using the above the curl command, it returns error. Because the example use binding:"required" for Password. If use binding:"-" for Password, then it will not return error when running the above example again.

Custom Validators

It is also possible to register custom validators. See the example code.

```go package main

import ( "net/http" "time"

"github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/go-playground/validator/v10" )

// Booking contains binded and validated data. type Booking struct { CheckIn time.Time form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02" CheckOut time.Time form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02" }

var bookableDate validator.Func = func(fl validator.FieldLevel) bool { date, ok := fl.Field().Interface().(time.Time) if ok { today := time.Now() if today.After(date) { return false } } return true }

func main() { route := gin.Default()

if v, ok := binding.Validator.Engine().(*validator.Validate); ok { v.RegisterValidation("bookabledate", bookableDate) }

route.GET("/bookable", getBookable) route.Run(":8085") }

func getBookable(c *gin.Context) { var b Booking if err := c.ShouldBindWith(&b, binding.Query); err == nil { c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } } ```

```console $ curl "localhost:8085/bookable?checkin=2030-04-16&checkout=2030-04-17" {"message":"Booking dates are valid!"}

$ curl "localhost:8085/bookable?checkin=2030-03-10&checkout=2030-03-09" {"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}

$ curl "localhost:8085/bookable?checkin=2000-03-09&checkout=2000-03-10" {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}% ```

Struct level validations can also be registered this way. See the struct-lvl-validation example to learn more.

Only Bind Query String

ShouldBindQuery function only binds the query params and not the post data. See the detail information.

```go package main

import ( "log" "net/http"

"github.com/gin-gonic/gin" )

type Person struct { Name string form:"name" Address string form:"address" }

func main() { route := gin.Default() route.Any("/testing", startPage) route.Run(":8085") }

func startPage(c *gin.Context) { var person Person if c.ShouldBindQuery(&person) == nil { log.Println("====== Only Bind By Query String ======") log.Println(person.Name) log.Println(person.Address) } c.String(http.StatusOK, "Success") }

```

Bind Query String or Post Data

See the detail information.

```go package main

import ( "log" "net/http" "time"

"github.com/gin-gonic/gin" )

type Person struct { Name string form:"name" Address string form:"address" Birthday time.Time form:"birthday" time_format:"2006-01-02" time_utc:"1" CreateTime time.Time form:"createTime" time_format:"unixNano" UnixTime time.Time form:"unixTime" time_format:"unix" }

func main() { route := gin.Default() route.GET("/testing", startPage) route.Run(":8085") }

func startPage(c *gin.Context) { var person Person // If GET, only Form binding engine (query) used. // If POST, first checks the content-type for JSON or XML, then uses Form (form-data). // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88 if c.ShouldBind(&person) == nil { log.Println(person.Name) log.Println(person.Address) log.Println(person.Birthday) log.Println(person.CreateTime) log.Println(person.UnixTime) }

c.String(http.StatusOK, "Success") } ```

Test it with:

sh curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"

Bind Uri

See the detail information.

```go package main

import ( "net/http"

"github.com/gin-gonic/gin" )

type Person struct { ID string uri:"id" binding:"required,uuid" Name string uri:"name" binding:"required" }

func main() { route := gin.Default() route.GET("/:name/:id", func(c *gin.Context) { var person Person if err := c.ShouldBindUri(&person); err != nil { c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID}) }) route.Run(":8088") } ```

Test it with:

sh curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 curl -v localhost:8088/thinkerou/not-uuid

Bind Header

```go package main

import ( "fmt" "net/http"

"github.com/gin-gonic/gin" )

type testHeader struct { Rate int header:"Rate" Domain string header:"Domain" }

func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { h := testHeader{}

if err := c.ShouldBindHeader(&h); err != nil {
  c.JSON(http.StatusOK, err)
}

fmt.Printf("%#v\n", h)
c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain})

})

r.Run()

// client // curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ // output // {"Domain":"music","Rate":300} } ```

Bind HTML checkboxes

See the detail information

main.go

```go ...

type myForm struct { Colors []string form:"colors[]" }

...

func formHandler(c *gin.Context) { var fakeForm myForm c.ShouldBind(&fakeForm) c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors}) }

...

```

form.html

```html

Check some colors

```

result:

json {"color":["red","green","blue"]}

Multipart/Urlencoded binding

```go type ProfileForm struct { Name string form:"name" binding:"required" Avatar *multipart.FileHeader form:"avatar" binding:"required"

// or for multiple files // Avatars []*multipart.FileHeader form:"avatar" binding:"required" }

func main() { router := gin.Default() router.POST("/profile", func(c *gin.Context) { // you can bind multipart form with explicit binding declaration: // c.ShouldBindWith(&form, binding.Form) // or you can simply use autobinding with ShouldBind method: var form ProfileForm // in this case proper binding will be automatically selected if err := c.ShouldBind(&form); err != nil { c.String(http.StatusBadRequest, "bad request") return }

err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename)
if err != nil {
  c.String(http.StatusInternalServerError, "unknown error")
  return
}

// db.Save(&form)

c.String(http.StatusOK, "ok")

}) router.Run(":8080") } ```

Test it with:

sh curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile

XML, JSON, YAML and ProtoBuf rendering

```go func main() { r := gin.Default()

// gin.H is a shortcut for map[string]interface{} r.GET("/someJSON", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) })

r.GET("/moreJSON", func(c *gin.Context) { // You also can use a struct var msg struct { Name string json:"user" Message string Number int } msg.Name = "Lena" msg.Message = "hey" msg.Number = 123 // Note that msg.Name becomes "user" in the JSON // Will output : {"user": "Lena", "Message": "hey", "Number": 123} c.JSON(http.StatusOK, msg) })

r.GET("/someXML", func(c *gin.Context) { c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) })

r.GET("/someYAML", func(c *gin.Context) { c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) })

r.GET("/someProtoBuf", func(c *gin.Context) { reps := []int64{int64(1), int64(2)} label := "test" // The specific definition of protobuf is written in the testdata/protoexample file. data := &protoexample.Test{ Label: &label, Reps: reps, } // Note that data becomes binary data in the response // Will output protoexample.Test protobuf serialized data c.ProtoBuf(http.StatusOK, data) })

// Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ```

SecureJSON

Using SecureJSON to prevent json hijacking. Default prepends "while(1)," to response body if the given struct is array values.

```go func main() { r := gin.Default()

// You can also use your own secure json prefix // r.SecureJsonPrefix(")]}',\n")

r.GET("/someJSON", func(c *gin.Context) { names := []string{"lena", "austin", "foo"}

// Will output  :   while(1);["lena","austin","foo"]
c.SecureJSON(http.StatusOK, names)

})

// Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ```

JSONP

Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists.

```go func main() { r := gin.Default()

r.GET("/JSONP", func(c *gin.Context) { data := gin.H{ "foo": "bar", }

//callback is x
// Will output  :   x({\"foo\":\"bar\"})
c.JSONP(http.StatusOK, data)

})

// Listen and serve on 0.0.0.0:8080 r.Run(":8080")

    // client
    // curl http://127.0.0.1:8080/JSONP?callback=x

} ```

AsciiJSON

Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters.

```go func main() { r := gin.Default()

r.GET("/someJSON", func(c *gin.Context) { data := gin.H{ "lang": "GO语言", "tag": "
", }

// will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
c.AsciiJSON(http.StatusOK, data)

})

// Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ```

PureJSON

Normally, JSON replaces special HTML characters with their unicode entities, e.g. < becomes \u003c. If you want to encode such characters literally, you can use PureJSON instead. This feature is unavailable in Go 1.6 and lower.

```go func main() { r := gin.Default()

// Serves unicode entities r.GET("/json", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "html": "Hello, world!", }) })

// Serves literal characters r.GET("/purejson", func(c *gin.Context) { c.PureJSON(http.StatusOK, gin.H{ "html": "Hello, world!", }) })

// listen and serve on 0.0.0.0:8080 r.Run(":8080") } ```

Serving static files

```go func main() { router := gin.Default() router.Static("/assets", "./assets") router.StaticFS("/morestatic", http.Dir("myfilesystem")) router.StaticFile("/favicon.ico", "./resources/favicon.ico") router.StaticFileFS("/morefavicon.ico", "morefavicon.ico", http.Dir("myfile_system"))

// Listen and serve on 0.0.0.0:8080 router.Run(":8080") } ```

Serving data from file

```go func main() { router := gin.Default()

router.GET("/local/file", func(c *gin.Context) { c.File("local/file.go") })

var fs http.FileSystem = // ... router.GET("/fs/file", func(c *gin.Context) { c.FileFromFS("fs/file.go", fs) }) }

```

Serving data from reader

```go func main() { router := gin.Default() router.GET("/someDataFromReader", func(c *gin.Context) { response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png") if err != nil || response.StatusCode != http.StatusOK { c.Status(http.StatusServiceUnavailable) return }

reader := response.Body
 defer reader.Close()
contentLength := response.ContentLength
contentType := response.Header.Get("Content-Type")

extraHeaders := map[string]string{
  "Content-Disposition": `attachment; filename="gopher.png"`,
}

c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)

}) router.Run(":8080") } ```

HTML rendering

Using LoadHTMLGlob() or LoadHTMLFiles()

go func main() { router := gin.Default() router.LoadHTMLGlob("templates/*") //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") router.GET("/index", func(c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl", gin.H{ "title": "Main website", }) }) router.Run(":8080") }

templates/index.tmpl

```html

{{ .title }}

```

Using templates with same name in different directories

go func main() { router := gin.Default() router.LoadHTMLGlob("templates/**/*") router.GET("/posts/index", func(c *gin.Context) { c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ "title": "Posts", }) }) router.GET("/users/index", func(c *gin.Context) { c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ "title": "Users", }) }) router.Run(":8080") }

templates/posts/index.tmpl

```html {{ define "posts/index.tmpl" }}

{{ .title }}

Using posts/index.tmpl

{{ end }} ```

templates/users/index.tmpl

```html {{ define "users/index.tmpl" }}

{{ .title }}

Using users/index.tmpl

{{ end }} ```

Custom Template renderer

You can also use your own html template render

```go import "html/template"

func main() { router := gin.Default() html := template.Must(template.ParseFiles("file1", "file2")) router.SetHTMLTemplate(html) router.Run(":8080") } ```

Custom Delimiters

You may use custom delims

go r := gin.Default() r.Delims("{[{", "}]}") r.LoadHTMLGlob("/path/to/templates")

Custom Template Funcs

See the detail example code.

main.go

```go import ( "fmt" "html/template" "net/http" "time"

"github.com/gin-gonic/gin"

)

func formatAsDate(t time.Time) string { year, month, day := t.Date() return fmt.Sprintf("%d/%02d/%02d", year, month, day) }

func main() { router := gin.Default() router.Delims("{[{", "}]}") router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) router.LoadHTMLFiles("./testdata/template/raw.tmpl")

router.GET("/raw", func(c *gin.Context) {
    c.HTML(http.StatusOK, "raw.tmpl", gin.H{
        "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
    })
})

router.Run(":8080")

}

```

raw.tmpl

html Date: {[{.now | formatAsDate}]}

Result:

sh Date: 2017/07/01

Multitemplate

Gin allow by default use only one html.Template. Check a multitemplate render for using features like go 1.6 block template.

Redirects

Issuing a HTTP redirect is easy. Both internal and external locations are supported.

go r.GET("/test", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") })

Issuing a HTTP redirect from POST. Refer to issue: #444

go r.POST("/test", func(c *gin.Context) { c.Redirect(http.StatusFound, "/foo") })

Issuing a Router redirect, use HandleContext like below.

go r.GET("/test", func(c *gin.Context) { c.Request.URL.Path = "/test2" r.HandleContext(c) }) r.GET("/test2", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"hello": "world"}) })

Custom Middleware

```go func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now()

// Set example variable
c.Set("example", "12345")

// before request

c.Next()

// after request
latency := time.Since(t)
log.Print(latency)

// access the status we are sending
status := c.Writer.Status()
log.Println(status)

} }

func main() { r := gin.New() r.Use(Logger())

r.GET("/test", func(c *gin.Context) { example := c.MustGet("example").(string)

// it would print: "12345"
log.Println(example)

})

// Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ```

Using BasicAuth() middleware

```go // simulate some private data var secrets = gin.H{ "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, "austin": gin.H{"email": "austin@example.com", "phone": "666"}, "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, }

func main() { r := gin.Default()

// Group using gin.BasicAuth() middleware // gin.Accounts is a shortcut for map[string]string authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ "foo": "bar", "austin": "1234", "lena": "hello2", "manu": "4321", }))

// /admin/secrets endpoint // hit "localhost:8080/admin/secrets authorized.GET("/secrets", func(c *gin.Context) { // get user, it was set by the BasicAuth middleware user := c.MustGet(gin.AuthUserKey).(string) if secret, ok := secrets[user]; ok { c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) } else { c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) } })

// Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ```

Goroutines inside a middleware

When starting new Goroutines inside a middleware or handler, you SHOULD NOT use the original context inside it, you have to use a read-only copy.

```go func main() { r := gin.Default()

r.GET("/long_async", func(c *gin.Context) { // create copy to be used inside the goroutine cCp := c.Copy() go func() { // simulate a long task with time.Sleep(). 5 seconds time.Sleep(5 * time.Second)

  // note that you are using the copied context "cCp", IMPORTANT
  log.Println("Done! in path " + cCp.Request.URL.Path)
}()

})

r.GET("/long_sync", func(c *gin.Context) { // simulate a long task with time.Sleep(). 5 seconds time.Sleep(5 * time.Second)

// since we are NOT using a goroutine, we do not have to copy the context
log.Println("Done! in path " + c.Request.URL.Path)

})

// Listen and serve on 0.0.0.0:8080 r.Run(":8080") } ```

Custom HTTP configuration

Use http.ListenAndServe() directly, like this:

go func main() { router := gin.Default() http.ListenAndServe(":8080", router) }

or

```go func main() { router := gin.Default()

s := &http.Server{ Addr: ":8080", Handler: router, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } s.ListenAndServe() } ```

Support Let's Encrypt

example for 1-line LetsEncrypt HTTPS servers.

```go package main

import ( "log" "net/http"

"github.com/gin-gonic/autotls" "github.com/gin-gonic/gin" )

func main() { r := gin.Default()

// Ping handler r.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") })

log.Fatal(autotls.Run(r, "example1.com", "example2.com")) } ```

example for custom autocert manager.

```go package main

import ( "log" "net/http"

"github.com/gin-gonic/autotls" "github.com/gin-gonic/gin" "golang.org/x/crypto/acme/autocert" )

func main() { r := gin.Default()

// Ping handler r.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") })

m := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), Cache: autocert.DirCache("/var/www/.cache"), }

log.Fatal(autotls.RunWithManager(r, &m)) } ```

Run multiple service using Gin

See the question and try the following example:

```go package main

import ( "log" "net/http" "time"

"github.com/gin-gonic/gin" "golang.org/x/sync/errgroup" )

var ( g errgroup.Group )

func router01() http.Handler { e := gin.New() e.Use(gin.Recovery()) e.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "error": "Welcome server 01", }, ) })

return e }

func router02() http.Handler { e := gin.New() e.Use(gin.Recovery()) e.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "error": "Welcome server 02", }, ) })

return e }

func main() { server01 := &http.Server{ Addr: ":8080", Handler: router01(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, }

server02 := &http.Server{ Addr: ":8081", Handler: router02(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, }

g.Go(func() error { err := server01.ListenAndServe() if err != nil && err != http.ErrServerClosed { log.Fatal(err) } return err })

g.Go(func() error { err := server02.ListenAndServe() if err != nil && err != http.ErrServerClosed { log.Fatal(err) } return err })

if err := g.Wait(); err != nil { log.Fatal(err) } } ```

Graceful shutdown or restart

There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages.

Third-party packages

We can use fvbock/endless to replace the default ListenAndServe. Refer to issue #296 for more details.

go router := gin.Default() router.GET("/", handler) // [...] endless.ListenAndServe(":4242", router)

Alternatives:

  • grace: Graceful restart & zero downtime deploy for Go servers.
  • graceful: Graceful is a Go package enabling graceful shutdown of an http.Handler server.
  • manners: A polite Go HTTP server that shuts down gracefully.

Manually

In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using http.Server's built-in Shutdown() method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin here.

```go // +build go1.8

package main

import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time"

"github.com/gin-gonic/gin" )

func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, "Welcome Gin Server") })

srv := &http.Server{ Addr: ":8080", Handler: router, }

// Initializing the server in a goroutine so that // it won't block the graceful shutdown handling below go func() { if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) { log.Printf("listen: %s\n", err) } }()

// Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal) // kill (no param) default send syscall.SIGTERM // kill -2 is syscall.SIGINT // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...")

// The context is used to inform the server it has 5 seconds to finish // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel()

if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown:", err) }

log.Println("Server exiting") } ```

Build a single binary with templates

You can build a server into a single binary containing templates by using go-assets.

```go func main() { r := gin.New()

t, err := loadTemplate() if err != nil { panic(err) } r.SetHTMLTemplate(t)

r.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "/html/index.tmpl",nil) }) r.Run(":8080") }

// loadTemplate loads templates embedded by go-assets-builder func loadTemplate() (*template.Template, error) { t := template.New("") for name, file := range Assets.Files { defer file.Close() if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { continue } h, err := ioutil.ReadAll(file) if err != nil { return nil, err } t, err = t.New(name).Parse(string(h)) if err != nil { return nil, err } } return t, nil } ```

See a complete example in the https://github.com/gin-gonic/examples/tree/master/assets-in-binary directory.

Bind form-data request with custom struct

The follow example using custom struct:

```go type StructA struct { FieldA string form:"field_a" }

type StructB struct { NestedStruct StructA FieldB string form:"field_b" }

type StructC struct { NestedStructPointer *StructA FieldC string form:"field_c" }

type StructD struct { NestedAnonyStruct struct { FieldX string form:"field_x" } FieldD string form:"field_d" }

func GetDataB(c *gin.Context) { var b StructB c.Bind(&b) c.JSON(http.StatusOK, gin.H{ "a": b.NestedStruct, "b": b.FieldB, }) }

func GetDataC(c *gin.Context) { var b StructC c.Bind(&b) c.JSON(http.StatusOK, gin.H{ "a": b.NestedStructPointer, "c": b.FieldC, }) }

func GetDataD(c *gin.Context) { var b StructD c.Bind(&b) c.JSON(http.StatusOK, gin.H{ "x": b.NestedAnonyStruct, "d": b.FieldD, }) }

func main() { r := gin.Default() r.GET("/getb", GetDataB) r.GET("/getc", GetDataC) r.GET("/getd", GetDataD)

r.Run()

} ```

Using the command curl command result:

sh $ curl "http://localhost:8080/getb?field_a=hello&field_b=world" {"a":{"FieldA":"hello"},"b":"world"} $ curl "http://localhost:8080/getc?field_a=hello&field_c=world" {"a":{"FieldA":"hello"},"c":"world"} $ curl "http://localhost:8080/getd?field_x=hello&field_d=world" {"d":"world","x":{"FieldX":"hello"}}

Try to bind body into different structs

The normal methods for binding request body consumes c.Request.Body and they cannot be called multiple times.

```go type formA struct { Foo string json:"foo" xml:"foo" binding:"required" }

type formB struct { Bar string json:"bar" xml:"bar" binding:"required" }

func SomeHandler(c *gin.Context) { objA := formA{} objB := formB{} // This c.ShouldBind consumes c.Request.Body and it cannot be reused. if errA := c.ShouldBind(&objA); errA == nil { c.String(http.StatusOK, the body should be formA) // Always an error is occurred by this because c.Request.Body is EOF now. } else if errB := c.ShouldBind(&objB); errB == nil { c.String(http.StatusOK, the body should be formB) } else { ... } } ```

For this, you can use c.ShouldBindBodyWith.

go func SomeHandler(c *gin.Context) { objA := formA{} objB := formB{} // This reads c.Request.Body and stores the result into the context. if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil { c.String(http.StatusOK, `the body should be formA`) // At this time, it reuses body stored in the context. } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { c.String(http.StatusOK, `the body should be formB JSON`) // And it can accepts other formats } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { c.String(http.StatusOK, `the body should be formB XML`) } else { ... } }

  1. c.ShouldBindBodyWith stores body into the context before binding. This has a slight impact to performance, so you should not use this method if you are enough to call binding at once.
  2. This feature is only needed for some formats -- JSON, XML, MsgPack, ProtoBuf. For other formats, Query, Form, FormPost, FormMultipart, can be called by c.ShouldBind() multiple times without any damage to performance (See #1341).

Bind form-data request with custom struct and custom tag

```go const ( customerTag = "url" defaultMemory = 32 << 20 )

type customerBinding struct {}

func (customerBinding) Name() string { return "form" }

func (customerBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseForm(); err != nil { return err } if err := req.ParseMultipartForm(defaultMemory); err != nil { if err != http.ErrNotMultipart { return err } } if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil { return err } return validate(obj) }

func validate(obj interface{}) error { if binding.Validator == nil { return nil } return binding.Validator.ValidateStruct(obj) }

// Now we can do this!!! // FormA is a external type that we can't modify it's tag type FormA struct { FieldA string url:"field_a" }

func ListHandler(s *Service) func(ctx *gin.Context) { return func(ctx *gin.Context) { var urlBinding = customerBinding{} var opt FormA err := ctx.MustBindWith(&opt, urlBinding) if err != nil { ... } ... } } ```

http2 server push

http.Pusher is supported only go1.8+. See the golang blog for detail information.

```go package main

import ( "html/template" "log" "net/http"

"github.com/gin-gonic/gin" )

var html = template.Must(template.New("https").Parse(` Https Test

Welcome, Ginner!

`))

func main() { r := gin.Default() r.Static("/assets", "./assets") r.SetHTMLTemplate(html)

r.GET("/", func(c *gin.Context) { if pusher := c.Writer.Pusher(); pusher != nil { // use pusher.Push() to do server push if err := pusher.Push("/assets/app.js", nil); err != nil { log.Printf("Failed to push: %v", err) } } c.HTML(http.StatusOK, "https", gin.H{ "status": "success", }) })

// Listen and Server in https://127.0.0.1:8080 r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") } ```

Define format for the log of routes

The default log of routes is:

sh [GIN-debug] POST /foo --> main.main.func1 (3 handlers) [GIN-debug] GET /bar --> main.main.func2 (3 handlers) [GIN-debug] GET /status --> main.main.func3 (3 handlers)

If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with gin.DebugPrintRouteFunc. In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs.

```go import ( "log" "net/http"

"github.com/gin-gonic/gin" )

func main() { r := gin.Default() gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) }

r.POST("/foo", func(c *gin.Context) { c.JSON(http.StatusOK, "foo") })

r.GET("/bar", func(c *gin.Context) { c.JSON(http.StatusOK, "bar") })

r.GET("/status", func(c *gin.Context) { c.JSON(http.StatusOK, "ok") })

// Listen and Server in http://0.0.0.0:8080 r.Run() } ```

Set and get a cookie

```go import ( "fmt"

"github.com/gin-gonic/gin"

)

func main() {

router := gin.Default()

router.GET("/cookie", func(c *gin.Context) {

    cookie, err := c.Cookie("gin_cookie")

    if err != nil {
        cookie = "NotSet"
        c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
    }

    fmt.Printf("Cookie value: %s \n", cookie)
})

router.Run()

} ```

Don't trust all proxies

Gin lets you specify which headers to hold the real client IP (if any), as well as specifying which proxies (or direct clients) you trust to specify one of these headers.

Use function SetTrustedProxies() on your gin.Engine to specify network addresses or network CIDRs from where clients which their request headers related to client IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs.

Attention: Gin trust all proxies by default if you don't specify a trusted proxy using the function above, this is NOT safe. At the same time, if you don't use any proxy, you can disable this feature by using Engine.SetTrustedProxies(nil), then Context.ClientIP() will return the remote address directly to avoid some unnecessary computation.

```go import ( "fmt"

"github.com/gin-gonic/gin" )

func main() {

router := gin.Default() router.SetTrustedProxies([]string{"192.168.1.2"})

router.GET("/", func(c *gin.Context) { // If the client is 192.168.1.2, use the X-Forwarded-For // header to deduce the original client IP from the trust- // worthy parts of that header. // Otherwise, simply return the direct client IP fmt.Printf("ClientIP: %s\n", c.ClientIP()) }) router.Run() } ```

Notice: If you are using a CDN service, you can set the Engine.TrustedPlatform to skip TrustedProxies check, it has a higher priority than TrustedProxies. Look at the example below:

```go import ( "fmt"

"github.com/gin-gonic/gin" )

func main() {

router := gin.Default() // Use predefined header gin.PlatformXXX router.TrustedPlatform = gin.PlatformGoogleAppEngine // Or set your own trusted request header for another trusted proxy service // Don't set it to any suspect request header, it's unsafe router.TrustedPlatform = "X-CDN-IP"

router.GET("/", func(c *gin.Context) { // If you set TrustedPlatform, ClientIP() will resolve the // corresponding header and return IP directly fmt.Printf("ClientIP: %s\n", c.ClientIP()) }) router.Run() } ```

Testing

The net/http/httptest package is preferable way for HTTP testing.

```go package main

import ( "net/http"

"github.com/gin-gonic/gin" )

func setupRouter() *gin.Engine { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "pong") }) return r }

func main() { r := setupRouter() r.Run(":8080") } ```

Test for code example above:

```go package main

import ( "net/http" "net/http/httptest" "testing"

"github.com/stretchr/testify/assert" )

func TestPingRoute(t *testing.T) { router := setupRouter()

w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/ping", nil) router.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "pong", w.Body.String()) } ```

Users

Awesome project lists using Gin web framework.

  • gorush: A push notification server written in Go.
  • fnproject: The container native, cloud agnostic serverless platform.
  • photoprism: Personal photo management powered by Go and Google TensorFlow.
  • krakend: Ultra performant API Gateway with middlewares.
  • picfit: An image resizing server written in Go.
  • brigade: Event-based Scripting for Kubernetes.
  • dkron: Distributed, fault tolerant job scheduling system.

pterm/ptermmore_vert Go Last Updated: 2022-08-13T20:22:30Z
pterm/ptermclose

💻 PTerm | Pretty Terminal Printer

A modern Go framework to make beautiful CLIs

Latest Release Stars Forks License: MIT Downloads Forks
Downloads

PTerm

Show Demo Code


PTerm.sh | Installation | Getting Started | Documentation | Examples | Q&A | Discord


📦 Installation

To make PTerm available in your project, you can run the following command.\ Make sure to run this command inside your project, when you're using go modules 😉

sh go get github.com/pterm/pterm

If you want to create a CLI tool, make sure to check out our cli-template, which features automatic website generation, automatic deployments, a custom CI-System and much more!

⭐ Main Features

|Feature|Description| |-------|-----------| |🪀 Easy to use |Our first priority is to keep PTerm as easy to use as possible.
With many examples for each individual component, getting started with PTerm is extremely easy.
All components are similar in design and implement interfaces to simplify mixing individual components together.| |🤹‍♀️ Cross-Platform |We take special precautions to ensure that PTerm works on as many operating systems and terminals as possible.
Whether it's Windows CMD, macOS iTerm2 or in the backend (for example inside a GitHub Action or other CI systems), PTerm guarantees beautiful output!| |🧪 Well tested |PTerm has a 100% test coverage, which means that every line of code inside PTerm gets tested automatically
We test PTerm continuously. However, since a human cannot test everything all the time, we have our own test system with which we currently run 28774automated tests to ensure that PTerm has no bugs. | |✨ Consistent Colors|PTerm uses the ANSI color scheme which is widely used by terminals to ensure consistent colors in different terminal themes.
If that's not enough, PTerm can be used to access the full RGB color scheme (16 million colors) in terminals that support TrueColor.| |📚 Component system|PTerm consists of many components, called Printers, which can be used individually or together to generate pretty console output.| |🛠 Configurable|PTerm can be used by without any configuration. However, you can easily configure each component with little code, so everyone has the freedom to design their own terminal output.| |✏ Documentation |To view the official documentation of the latest release, you can go to the automatically generated page of pkg.go.dev This documentation is very technical and includes every method that can be used in PTerm.
For an easy start we recommend that you take a look at the examples section. Here you can see pretty much every feature of PTerm with example code. The animations of the examples are automatically updated as soon as something changes in PTerm.|

### Printers (Components) |Feature|Examples| - |Feature|Examples| |-------|--------|---|-----|--------| |Bar Charts|[Examples](https://github.com/pterm/pterm/tree/master/_examples/barchart)|-|RGB|[Examples](https://github.com/pterm/pterm/tree/master/_examples/coloring)| |BigText|[Examples](https://github.com/pterm/pterm/tree/master/_examples/bigtext)|-|Sections|[Examples](https://github.com/pterm/pterm/tree/master/_examples/section)| |Box|[Examples](https://github.com/pterm/pterm/tree/master/_examples/box)|-|Spinners|[Examples](https://github.com/pterm/pterm/tree/master/_examples/spinner)| |Bullet Lists|[Examples](https://github.com/pterm/pterm/tree/master/_examples/bulletlist)|-|Trees|[Examples](https://github.com/pterm/pterm/tree/master/_examples/tree)| |Centered|[Examples](https://github.com/pterm/pterm/tree/master/_examples/center)|-|Theming|[Examples](https://github.com/pterm/pterm/tree/master/_examples/theme)| |Colors|[Examples](https://github.com/pterm/pterm/tree/master/_examples/coloring)|-|Tables|[Examples](https://github.com/pterm/pterm/tree/master/_examples/table)| |Headers|[Examples](https://github.com/pterm/pterm/tree/master/_examples/header)|-|Styles|[Examples](https://github.com/pterm/pterm/tree/master/_examples/style)| |Panels|[Examples](https://github.com/pterm/pterm/tree/master/_examples/panel)|-|Area|[Examples](https://github.com/pterm/pterm/tree/master/_examples/area)| |Paragraphs|[Examples](https://github.com/pterm/pterm/tree/master/_examples/paragraph)|-||| |Prefixes|[Examples](https://github.com/pterm/pterm/tree/master/_examples/prefix)|-||| |Progress Bars|[Examples](https://github.com/pterm/pterm/tree/master/_examples/progressbar)|-||| ### 🦸‍♂️ Supporters |-|User|💸| |---|---|---| |![Jens Lauterbach](https://avatars.githubusercontent.com/u/1292368?s=25)|[@jenslauterbach](https://github.com/jenslauterbach)|25$|

🧪 Examples


‼️ You can find all the examples, in a much better structure and their source code, in "_examples" ‼️
Click on the link above to show the examples folder.

### area/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/area/demo/animation.svg)
SHOW SOURCE ```go package main import ( "time" "github.com/pterm/pterm" ) func main() { pterm.Info.Println("The previous text will stay in place, while the area updates.") pterm.Print("\n\n") // Add two new lines as spacer. area, _ := pterm.DefaultArea.WithCenter().Start() // Start the Area printer, with the Center option. for i := 0; i < 10; i++ { str, _ := pterm.DefaultBigText.WithLetters(pterm.NewLettersFromString(time.Now().Format("15:04:05"))).Srender() // Save current time in str. area.Update(str) // Update Area contents. time.Sleep(time.Second) } area.Stop() } ```
### barchart/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/barchart/demo/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" ) func main() { positiveBars := pterm.Bars{ pterm.Bar{ Label: "Bar 1", Value: 5, }, pterm.Bar{ Label: "Bar 2", Value: 3, }, pterm.Bar{ Label: "Longer Label", Value: 7, }, } pterm.Info.Println("Chart example with positive only values (bars use 100% of chart area)") _ = pterm.DefaultBarChart.WithBars(positiveBars).Render() _ = pterm.DefaultBarChart.WithHorizontal().WithBars(positiveBars).Render() } ```
### barchart/mixed-values ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/barchart/mixed-values/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" ) func main() { mixedBars := pterm.Bars{ pterm.Bar{ Label: "Bar 1", Value: 2, }, pterm.Bar{ Label: "Bar 2", Value: -3, }, pterm.Bar{ Label: "Bar 3", Value: -2, }, pterm.Bar{ Label: "Bar 4", Value: 5, }, pterm.Bar{ Label: "Longer Label", Value: 7, }, } pterm.DefaultSection.Println("Chart example with mixed values (note screen space usage in case when ABSOLUTE values of negative and positive parts are differ too much)") _ = pterm.DefaultBarChart.WithBars(mixedBars).WithShowValue().Render() _ = pterm.DefaultBarChart.WithHorizontal().WithBars(mixedBars).WithShowValue().Render() } ```
### barchart/negative-values ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/barchart/negative-values/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" ) func main() { negativeBars := pterm.Bars{ pterm.Bar{ Label: "Bar 1", Value: -5, }, pterm.Bar{ Label: "Bar 2", Value: -3, }, pterm.Bar{ Label: "Longer Label", Value: -7, }, } pterm.Info.Println("Chart example with negative only values (bars use 100% of chart area)") _ = pterm.DefaultBarChart.WithBars(negativeBars).WithShowValue().Render() _ = pterm.DefaultBarChart.WithHorizontal().WithBars(negativeBars).WithShowValue().Render() } ```
### basictext/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/basictext/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // A BasicText printer is used to print text, without special formatting. // As it implements the TextPrinter interface, you can use it in combination with other printers. pterm.DefaultBasicText.Println("Default basic text printer.") pterm.DefaultBasicText.Println("Can be used in any" + pterm.LightMagenta(" TextPrinter ") + "context.") pterm.DefaultBasicText.Println("For example to resolve progressbars and spinners.") // If you just want to print text, you should use this instead: // pterm.Println("Hello, World!") } ```
### bigtext/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/bigtext/demo/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" "github.com/pterm/pterm/putils" ) func main() { // Print a large text with the LetterStyle from the standard theme. // Useful for title screens. pterm.DefaultBigText.WithLetters(putils.LettersFromString("PTerm")).Render() // Print a large text with differently colored letters. pterm.DefaultBigText.WithLetters( putils.LettersFromStringWithStyle("P", pterm.NewStyle(pterm.FgCyan)), putils.LettersFromStringWithStyle("Term", pterm.NewStyle(pterm.FgLightMagenta))). Render() // LettersFromStringWithRGB can be used to create a large text with a specific RGB color. pterm.DefaultBigText.WithLetters( putils.LettersFromStringWithRGB("PTerm", pterm.NewRGB(255, 215, 0))). Render() } ```
### box/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/box/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { pterm.Info.Println("This might not be rendered correctly on GitHub,\nbut it will work in a real terminal.\nThis is because GitHub does not use a monospaced font by default for SVGs") panel1 := pterm.DefaultBox.Sprint("Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit,\nsed do eiusmod tempor incididunt\nut labore et dolore\nmagna aliqua.") panel2 := pterm.DefaultBox.WithTitle("title").Sprint("Ut enim ad minim veniam,\nquis nostrud exercitation\nullamco laboris\nnisi ut aliquip\nex ea commodo\nconsequat.") panel3 := pterm.DefaultBox.WithTitle("bottom center title").WithTitleBottomCenter().Sprint("Duis aute irure\ndolor in reprehenderit\nin voluptate velit esse cillum\ndolore eu fugiat\nnulla pariatur.") panels, _ := pterm.DefaultPanel.WithPanels(pterm.Panels{ {{Data: panel1}, {Data: panel2}}, {{Data: panel3}}, }).Srender() pterm.DefaultBox.WithTitle("Lorem Ipsum").WithTitleBottomRight().WithRightPadding(0).WithBottomPadding(0).Println(panels) } ```
### bulletlist/customized ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/bulletlist/customized/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" ) func main() { // Print a customized list with different styles and levels. pterm.DefaultBulletList.WithItems([]pterm.BulletListItem{ {Level: 0, Text: "Blue", TextStyle: pterm.NewStyle(pterm.FgBlue), BulletStyle: pterm.NewStyle(pterm.FgRed)}, {Level: 1, Text: "Green", TextStyle: pterm.NewStyle(pterm.FgGreen), Bullet: "-", BulletStyle: pterm.NewStyle(pterm.FgLightWhite)}, {Level: 2, Text: "Cyan", TextStyle: pterm.NewStyle(pterm.FgCyan), Bullet: ">", BulletStyle: pterm.NewStyle(pterm.FgYellow)}, }).Render() } ```
### bulletlist/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/bulletlist/demo/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" "github.com/pterm/pterm/putils" ) func main() { // Print a list with different levels. // Useful to generate lists automatically from data. pterm.DefaultBulletList.WithItems([]pterm.BulletListItem{ {Level: 0, Text: "Level 0"}, {Level: 1, Text: "Level 1"}, {Level: 2, Text: "Level 2"}, }).Render() // Convert a text to a list and print it. putils.BulletListFromString(`0 1 2 3`, " ").Render() } ```
### center/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/center/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { pterm.DefaultCenter.Println("This text is centered!\nIt centeres the whole block by default.\nIn that way you can do stuff like this:") // Generate BigLetters s, _ := pterm.DefaultBigText.WithLetters(pterm.NewLettersFromString("PTerm")).Srender() pterm.DefaultCenter.Println(s) // Print BigLetters with the default CenterPrinter pterm.DefaultCenter.WithCenterEachLineSeparately().Println("This text is centered!\nBut each line is\ncentered\nseparately") } ```
### coloring/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/coloring/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Print all colors pterm.DefaultTable.WithData([][]string{ {pterm.FgBlack.Sprint("Black"), pterm.FgRed.Sprint("Red"), pterm.FgGreen.Sprint("Green"), pterm.FgYellow.Sprint("Yellow")}, {"", pterm.FgLightRed.Sprint("Light Red"), pterm.FgLightGreen.Sprint("Light Green"), pterm.FgLightYellow.Sprint("Light Yellow")}, {pterm.BgBlack.Sprint("Black"), pterm.BgRed.Sprint("Red"), pterm.BgGreen.Sprint("Green"), pterm.BgYellow.Sprint("Yellow")}, {"", pterm.BgLightRed.Sprint("Light Red"), pterm.BgLightGreen.Sprint("Light Green"), pterm.BgLightYellow.Sprint("Light Yellow")}, {pterm.FgBlue.Sprint("Blue"), pterm.FgMagenta.Sprint("Magenta"), pterm.FgCyan.Sprint("Cyan"), pterm.FgWhite.Sprint("White")}, {pterm.FgLightBlue.Sprint("Light Blue"), pterm.FgLightMagenta.Sprint("Light Magenta"), pterm.FgLightCyan.Sprint("Light Cyan"), pterm.FgLightWhite.Sprint("Light White")}, {pterm.BgBlue.Sprint("Blue"), pterm.BgMagenta.Sprint("Magenta"), pterm.BgCyan.Sprint("Cyan"), pterm.BgWhite.Sprint("White")}, {pterm.BgLightBlue.Sprint("Light Blue"), pterm.BgLightMagenta.Sprint("Light Magenta"), pterm.BgLightCyan.Sprint("Light Cyan"), pterm.BgLightWhite.Sprint("Light White")}, }).Render() pterm.Println() // Print different colored words. pterm.Println(pterm.Red("Hello, ") + pterm.Green("World") + pterm.Cyan("!")) pterm.Println(pterm.Red("Even " + pterm.Cyan("nested ") + pterm.Green("colors ") + "are supported!")) pterm.Println() // Or print colors as a style style := pterm.NewStyle(pterm.BgRed, pterm.FgLightGreen, pterm.Bold) style.Println("This text uses a style and is bold and light green with a red background!") } ```
### coloring/disable-output ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/coloring/disable-output/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { for i := 0; i < 15; i++ { switch i { case 5: pterm.Info.Println("Disabled Output!") pterm.DisableOutput() case 10: pterm.EnableOutput() pterm.Info.Println("Enabled Output!") } pterm.Printf("Printing something... [%d/%d]\n", i, 15) } } ```
### coloring/fade-colors ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/coloring/fade-colors/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" ) func main() { // Print info. pterm.Info.Println("RGB colors only work in Terminals which support TrueColor.") from := pterm.NewRGB(0, 255, 255) // This RGB value is used as the gradients start point. to := pterm.NewRGB(255, 0, 255) // This RGB value is used as the gradients end point. // For loop over the range of the terminal height. for i := 0; i < pterm.GetTerminalHeight()-2; i++ { // Print string which is colored with the faded RGB value. from.Fade(0, float32(pterm.GetTerminalHeight()-2), float32(i), to).Println("Hello, World!") } } ```
### coloring/fade-multiple-colors ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/coloring/fade-multiple-colors/animation.svg)
SHOW SOURCE ```go package main import ( "strings" "github.com/pterm/pterm" ) func main() { from := pterm.NewRGB(0, 255, 255) // This RGB value is used as the gradients start point. to := pterm.NewRGB(255, 0, 255) // This RGB value is used as the gradients first point. to2 := pterm.NewRGB(255, 0, 0) // This RGB value is used as the gradients second point. to3 := pterm.NewRGB(0, 255, 0) // This RGB value is used as the gradients third point. to4 := pterm.NewRGB(255, 255, 255) // This RGB value is used as the gradients end point. str := "RGB colors only work in Terminals which support TrueColor." strs := strings.Split(str, "") var fadeInfo string // String which will be used to print info. // For loop over the range of the string length. for i := 0; i < len(str); i++ { // Append faded letter to info string. fadeInfo += from.Fade(0, float32(len(str)), float32(i), to).Sprint(strs[i]) } // Print info. pterm.Info.Println(fadeInfo) // For loop over the range of the terminal height. for i := 0; i < pterm.GetTerminalHeight()-2; i++ { // Print string which is colored with the faded RGB value. from.Fade(0, float32(pterm.GetTerminalHeight()-2), float32(i), to, to2, to3, to4).Println("Hello, World!") } } ```
### coloring/override-default-printers ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/coloring/override-default-printers/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Print default error. pterm.Error.Println("This is the default Error") // Customize default error. pterm.Error.Prefix = pterm.Prefix{ Text: "OVERRIDE", Style: pterm.NewStyle(pterm.BgCyan, pterm.FgRed), } // Print new default error. pterm.Error.Println("This is the default Error after the prefix was overridden") } ```
### coloring/print-color-rgb ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/coloring/print-color-rgb/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Print strings with a custom RGB color. // NOTICE: This only works with terminals which support TrueColor. pterm.NewRGB(178, 44, 199).Println("This text is printed with a custom RGB!") pterm.NewRGB(15, 199, 209).Println("This text is printed with a custom RGB!") pterm.NewRGB(201, 144, 30).Println("This text is printed with a custom RGB!") } ```
### demo/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/demo/demo/animation.svg)
SHOW SOURCE ```go package main import ( "flag" "math/rand" "reflect" "strconv" "strings" "time" "github.com/pterm/pterm" "github.com/pterm/pterm/putils" ) // Speed the demo up, by setting this flag. // Usefull for debugging. // Example: // go run main.go -speedup var speedup = flag.Bool("speedup", false, "Speed up the demo") var skipIntro = flag.Bool("skip-intro", false, "Skips the intro") var second = time.Second var pseudoProgramList = strings.Split("pseudo-excel pseudo-photoshop pseudo-chrome pseudo-outlook pseudo-explorer "+ "pseudo-git pseudo-vsc pseudo-intellij pseudo-minecraft pseudo-scoop pseudo-chocolatey", " ") func main() { setup() // Setup the demo (flags etc.) // Show intro if !*skipIntro { introScreen() clear() } showcase("Progress bar", 2, func() { pb, _ := pterm.DefaultProgressbar.WithTotal(len(pseudoProgramList)).WithTitle("Installing stuff").Start() for i := 0; i < pb.Total; i++ { pb.UpdateTitle("Installing " + pseudoProgramList[i]) if pseudoProgramList[i] == "pseudo-minecraft" { pterm.Warning.Println("Could not install pseudo-minecraft\nThe company policy forbids games.") } else { pterm.Success.Println("Installing " + pseudoProgramList[i]) } pb.Increment() time.Sleep(second / 2) } pb.Stop() }) showcase("Spinner", 2, func() { list := pseudoProgramList[7:] spinner, _ := pterm.DefaultSpinner.Start("Installing stuff") for i := 0; i < len(list); i++ { spinner.UpdateText("Installing " + list[i]) if list[i] == "pseudo-minecraft" { pterm.Warning.Println("Could not install pseudo-minecraft\nThe company policy forbids games.") } else { pterm.Success.Println("Installing " + list[i]) } time.Sleep(second) } spinner.Success() }) showcase("Live Output", 2, func() { pterm.Info.Println("You can use an Area to display changing output:") pterm.Println() area, _ := pterm.DefaultArea.WithCenter().Start() // Start the Area printer, with the Center option. for i := 0; i < 10; i++ { str, _ := pterm.DefaultBigText.WithLetters(putils.LettersFromString(time.Now().Format("15:04:05"))).Srender() // Save current time in str. area.Update(str) // Update Area contents. time.Sleep(time.Second) } area.Stop() }) showcase("Tables", 4, func() { for i := 0; i < 3; i++ { pterm.Println() } td := [][]string{ {"Library", "Description"}, {"PTerm", "Make beautiful CLIs"}, {"Testza", "Programmer friendly test framework"}, {"Cursor", "Move the cursor around the terminal"}, } table, _ := pterm.DefaultTable.WithHasHeader().WithData(td).Srender() boxedTable, _ := pterm.DefaultTable.WithHasHeader().WithData(td).WithBoxed().Srender() pterm.DefaultCenter.Println(table) pterm.DefaultCenter.Println(boxedTable) }) showcase("Default Prefix Printers", 5, func() { // Enable debug messages. pterm.EnableDebugMessages() // Temporarily set debug output to true, to display the debug printer. pterm.Debug.Println("Hello, World!") // Print Debug. time.Sleep(second / 2) pterm.Info.Println("Hello, World!") // Print Info. time.Sleep(second / 2) pterm.Success.Println("Hello, World!") // Print Success. time.Sleep(second / 2) pterm.Warning.Println("Hello, World!") // Print Warning. time.Sleep(second / 2) pterm.Error.Println("Errors show the filename and linenumber inside the terminal!") // Print Error. time.Sleep(second / 2) pterm.Info.WithShowLineNumber().Println("Other PrefixPrinters can do that too!") // Print Error. time.Sleep(second / 2) // Temporarily set Fatal to false, so that the CI won't panic. pterm.Fatal.WithFatal(false).Println("Hello, World!") // Print Fatal. pterm.DisableDebugMessages() // Disable debug output again. }) showcase("TrueColor Support", 7, func() { from := pterm.NewRGB(0, 255, 255) // This RGB value is used as the gradients start point. to := pterm.NewRGB(255, 0, 255) // This RGB value is used as the gradients first point. str := "If your terminal has TrueColor support, you can use RGB colors!\nYou can even fade them :)\n\nLorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." strs := strings.Split(str, "") var fadeInfo string // String which will be used to print info. // For loop over the range of the string length. for i := 0; i < len(str); i++ { // Append faded letter to info string. fadeInfo += from.Fade(0, float32(len(str)), float32(i), to).Sprint(strs[i]) } pterm.DefaultCenter.WithCenterEachLineSeparately().Println(fadeInfo) }) showcase("Themes", 2, func() { pterm.Info.Println("You can change the color theme of PTerm easily to fit your needs!\nThis is the default one:") time.Sleep(second / 2) // Print every value of the default theme with its own style. v := reflect.ValueOf(pterm.ThemeDefault) typeOfS := v.Type() if typeOfS == reflect.TypeOf(pterm.Theme{}) { for i := 0; i < v.NumField(); i++ { field, ok := v.Field(i).Interface().(pterm.Style) if ok { field.Println(typeOfS.Field(i).Name) } time.Sleep(time.Millisecond * 250) } } }) showcase("Fully Customizale", 2, func() { for i := 0; i < 4; i++ { pterm.Println() } text := "All printers are fully customizable!" area := pterm.DefaultArea.WithCenter() area.Update(pterm.DefaultBox.Sprintln(text)) time.Sleep(second) area.Update(pterm.DefaultBox.WithTopPadding(1).Sprintln(text)) time.Sleep(second / 3) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).Sprintln(text)) time.Sleep(second / 3) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).Sprintln(text)) time.Sleep(second / 3) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).WithRightPadding(1).Sprintln(text)) time.Sleep(second / 3) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).WithRightPadding(1).WithTitle("Some title!").WithTitleTopLeft().Sprintln(text)) time.Sleep(second / 3) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).WithRightPadding(1).WithTitle("Some title!").WithTitleTopCenter().Sprintln(text)) time.Sleep(second / 3) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).WithRightPadding(1).WithTitle("Some title!").WithTitleTopRight().Sprintln(text)) time.Sleep(second / 3) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).WithRightPadding(1).WithTitle("Some title!").WithTitleBottomRight().Sprintln(text)) time.Sleep(second / 3) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).WithRightPadding(1).WithTitle("Some title!").WithTitleBottomCenter().Sprintln(text)) time.Sleep(second / 3) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).WithRightPadding(1).WithTitle("Some title!").WithTitleBottomLeft().Sprintln(text)) time.Sleep(second / 3) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).WithRightPadding(1).WithBoxStyle(pterm.NewStyle(pterm.FgCyan)).Sprintln(text)) time.Sleep(second / 5) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).WithRightPadding(1).WithBoxStyle(pterm.NewStyle(pterm.FgRed)).Sprintln(text)) time.Sleep(second / 5) area.Update(pterm.DefaultBox.WithTopPadding(1).WithBottomPadding(1).WithLeftPadding(1).WithRightPadding(1).WithBoxStyle(pterm.NewStyle(pterm.FgGreen)).Sprintln(text)) time.Sleep(second / 5) area.Update(pterm.DefaultBox.WithTopPadding(1). WithBottomPadding(1). WithLeftPadding(1). WithRightPadding(1). WithHorizontalString("═"). WithVerticalString("║"). WithBottomLeftCornerString("╗"). WithBottomRightCornerString("╔"). WithTopLeftCornerString("╝"). WithTopRightCornerString("╚"). Sprintln(text)) area.Stop() }) showcase("And much more!", 3, func() { for i := 0; i < 4; i++ { pterm.Println() } box := pterm.DefaultBox. WithBottomPadding(1). WithTopPadding(1). WithLeftPadding(3). WithRightPadding(3). Sprintf("Have fun exploring %s!", pterm.Cyan("PTerm")) pterm.DefaultCenter.Println(box) }) } func setup() { flag.Parse() if *speedup { second = time.Millisecond * 200 } } func introScreen() { ptermLogo, _ := pterm.DefaultBigText.WithLetters( putils.LettersFromStringWithStyle("P", pterm.NewStyle(pterm.FgLightCyan)), putils.LettersFromStringWithStyle("Term", pterm.NewStyle(pterm.FgLightMagenta))). Srender() pterm.DefaultCenter.Print(ptermLogo) pterm.DefaultCenter.Print(pterm.DefaultHeader.WithFullWidth().WithBackgroundStyle(pterm.NewStyle(pterm.BgLightBlue)).WithMargin(10).Sprint("PTDP - PTerm Demo Program")) pterm.Info.Println("This animation was generated with the latest version of PTerm!" + "\nPTerm works on nearly every terminal and operating system." + "\nIt's super easy to use!" + "\nIf you want, you can customize everything :)" + "\nYou can see the code of this demo in the " + pterm.LightMagenta("./_examples/demo") + " directory." + "\n" + "\nThis demo was updated at: " + pterm.Green(time.Now().Format("02 Jan 2006 - 15:04:05 MST"))) pterm.Println() introSpinner, _ := pterm.DefaultSpinner.WithShowTimer(false).WithRemoveWhenDone(true).Start("Waiting for 15 seconds...") time.Sleep(second) for i := 14; i > 0; i-- { if i > 1 { introSpinner.UpdateText("Waiting for " + strconv.Itoa(i) + " seconds...") } else { introSpinner.UpdateText("Waiting for " + strconv.Itoa(i) + " second...") } time.Sleep(second) } introSpinner.Stop() } func clear() { print("\033[H\033[2J") } func showcase(title string, seconds int, content func()) { pterm.DefaultHeader.WithBackgroundStyle(pterm.NewStyle(pterm.BgLightBlue)).WithFullWidth().Println(title) pterm.Println() time.Sleep(second / 2) content() time.Sleep(second * time.Duration(seconds)) print("\033[H\033[2J") } func randomInt(min, max int) int { rand.Seed(time.Now().UnixNano()) return rand.Intn(max-min+1) + min } ```
### header/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/header/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Print a default header. pterm.DefaultHeader.Println("This is the default header!") pterm.Println() // spacer pterm.DefaultHeader.WithFullWidth().Println("This is a full-width header.") } ```
### header-custom/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/header-custom/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // All available options: https://pkg.go.dev/github.com/pterm/pterm#HeaderPrinter // Build on top of DefaultHeader pterm.DefaultHeader. // Use DefaultHeader as base WithMargin(15). WithBackgroundStyle(pterm.NewStyle(pterm.BgCyan)). WithTextStyle(pterm.NewStyle(pterm.FgBlack)). Println("This is a custom header!") // Instead of printing the header you can set it to a variable. // You can then reuse your custom header. // Making a completely new HeaderPrinter newHeader := pterm.HeaderPrinter{ TextStyle: pterm.NewStyle(pterm.FgBlack), BackgroundStyle: pterm.NewStyle(pterm.BgRed), Margin: 20, } // Print header. newHeader.Println("This is a custom header!") } ```
### interactive_confirm/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_confirm/demo/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" ) func main() { result, _ := pterm.DefaultInteractiveConfirm.Show() pterm.Println() // Blank line pterm.Info.Printfln("You answered: %s", boolToText(result)) } func boolToText(b bool) string { if b { return pterm.Green("Yes") } return pterm.Red("No") } ```
### interactive_multiselect/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_multiselect/demo/animation.svg)
SHOW SOURCE ```go package main import ( "fmt" "github.com/pterm/pterm" ) func main() { var options []string for i := 0; i < 100; i++ { options = append(options, fmt.Sprintf("Option %d", i)) } for i := 0; i < 5; i++ { options = append(options, fmt.Sprintf("You can use fuzzy searching (%d)", i)) } selectedOptions, _ := pterm.DefaultInteractiveMultiselect.WithOptions(options).Show() pterm.Info.Printfln("Selected options: %s", pterm.Green(selectedOptions)) } ```
### interactive_select/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_select/demo/animation.svg)
SHOW SOURCE ```go package main import ( "fmt" "github.com/pterm/pterm" ) func main() { var options []string for i := 0; i < 100; i++ { options = append(options, fmt.Sprintf("Option %d", i)) } for i := 0; i < 5; i++ { options = append(options, fmt.Sprintf("You can use fuzzy searching (%d)", i)) } selectedOption, _ := pterm.DefaultInteractiveSelect.WithOptions(options).Show() pterm.Info.Printfln("Selected option: %s", pterm.Green(selectedOption)) } ```
### interactive_textinput/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_textinput/demo/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" ) func main() { result, _ := pterm.DefaultInteractiveTextInput.WithMultiLine(false).Show() pterm.Println() // Blank line pterm.Info.Printfln("You answered: %s", result) } ```
### interactive_textinput/multi-line ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_textinput/multi-line/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" ) func main() { result, _ := pterm.DefaultInteractiveTextInput.WithMultiLine().Show() // Text input with multi line enabled pterm.Println() // Blank line pterm.Info.Printfln("You answered: %s", result) } ```
### panel/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/panel/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Declare panels in a two dimensional grid system. panels := pterm.Panels{ {{Data: "This is the first panel"}, {Data: pterm.DefaultHeader.Sprint("Hello, World!")}, {Data: "This\npanel\ncontains\nmultiple\nlines"}}, {{Data: pterm.Red("This is another\npanel line")}, {Data: "This is the second panel\nwith a new line"}}, } // Print panels. _ = pterm.DefaultPanel.WithPanels(panels).WithPadding(5).Render() } ```
### paragraph/customized ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/paragraph/customized/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Print a paragraph with a custom maximal width. pterm.DefaultParagraph.WithMaxWidth(60).Println("This is a custom paragraph printer. As you can see, no words are separated, " + "but the text is split at the spaces. This is useful for continuous text of all kinds. You can manually change the line width if you want to." + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam") // Print one line space. pterm.Println() // Print text without a paragraph printer. pterm.Println("This text is written with the default Println() function. No intelligent splitting here." + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam") } ```
### paragraph/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/paragraph/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Print long text with default paragraph printer. pterm.DefaultParagraph.Println("This is the default paragraph printer. As you can see, no words are separated, " + "but the text is split at the spaces. This is useful for continuous text of all kinds. You can manually change the line width if you want to." + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam") // Print one line space. pterm.Println() // Print long text without paragraph printer. pterm.Println("This text is written with the default Println() function. No intelligent splitting here." + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam") } ```
### prefix/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/prefix/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Enable debug messages. pterm.EnableDebugMessages() pterm.Debug.Println("Hello, World!") // Print Debug. pterm.Info.Println("Hello, World!") // Print Info. pterm.Success.Println("Hello, World!") // Print Success. pterm.Warning.Println("Hello, World!") // Print Warning. pterm.Error.Println("Errors show the filename and linenumber inside the terminal!") // Print Error. pterm.Info.WithShowLineNumber().Println("Other PrefixPrinters can do that too!") // Print Error. // Temporarily set Fatal to false, so that the CI won't crash. pterm.Fatal.WithFatal(false).Println("Hello, World!") // Print Fatal. } ```
### progressbar/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/progressbar/demo/animation.svg)
SHOW SOURCE ```go package main import ( "strings" "time" "github.com/pterm/pterm" ) // Slice of strings with placeholder text. var fakeInstallList = strings.Split("pseudo-excel pseudo-photoshop pseudo-chrome pseudo-outlook pseudo-explorer "+ "pseudo-dops pseudo-git pseudo-vsc pseudo-intellij pseudo-minecraft pseudo-scoop pseudo-chocolatey", " ") func main() { // Create progressbar as fork from the default progressbar. p, _ := pterm.DefaultProgressbar.WithTotal(len(fakeInstallList)).WithTitle("Downloading stuff").Start() for i := 0; i < p.Total; i++ { p.UpdateTitle("Downloading " + fakeInstallList[i]) // Update the title of the progressbar. pterm.Success.Println("Downloading " + fakeInstallList[i]) // If a progressbar is running, each print will be printed above the progressbar. p.Increment() // Increment the progressbar by one. Use Add(x int) to increment by a custom amount. time.Sleep(time.Millisecond * 350) // Sleep 350 milliseconds. } } ```
### section/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/section/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Print a section with level one. pterm.DefaultSection.Println("This is a section!") // Print placeholder. pterm.Info.Println("And here is some text.\nThis text could be anything.\nBasically it's just a placeholder") // Print a section with level two. pterm.DefaultSection.WithLevel(2).Println("This is another section!") // Print placeholder. pterm.Info.Println("And this is\nmore placeholder text") } ```
### spinner/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/spinner/demo/animation.svg)
SHOW SOURCE ```go package main import ( "time" "github.com/pterm/pterm" ) func main() { // Create and start a fork of the default spinner. spinnerInfo, _ := pterm.DefaultSpinner.Start("Some informational action...") time.Sleep(time.Second * 2) // Simulate 3 seconds of processing something. spinnerInfo.Info() // Resolve spinner with error message. // Create and start a fork of the default spinner. spinnerSuccess, _ := pterm.DefaultSpinner.Start("Doing something important... (will succeed)") time.Sleep(time.Second * 2) // Simulate 3 seconds of processing something. spinnerSuccess.Success() // Resolve spinner with success message. // Create and start a fork of the default spinner. spinnerWarning, _ := pterm.DefaultSpinner.Start("Doing something important... (will warn)") time.Sleep(time.Second * 2) // Simulate 3 seconds of processing something. spinnerWarning.Warning() // Resolve spinner with warning message. // Create and start a fork of the default spinner. spinnerFail, _ := pterm.DefaultSpinner.Start("Doing something important... (will fail)") time.Sleep(time.Second * 2) // Simulate 3 seconds of processing something. spinnerFail.Fail() // Resolve spinner with error message. // Create and start a fork of the default spinner. spinnerNochange, _ := pterm.DefaultSpinner.Start("Checking something important... (will result in no change)") // Replace the InfoPrinter with a custom "NOCHG" one spinnerNochange.InfoPrinter = &pterm.PrefixPrinter{ MessageStyle: &pterm.Style{pterm.FgLightBlue}, Prefix: pterm.Prefix{ Style: &pterm.Style{pterm.FgBlack, pterm.BgLightBlue}, Text: " NOCHG ", }, } time.Sleep(time.Second * 2) // Simulate 3 seconds of processing something. spinnerNochange.Info("No change were required") // Resolve spinner with error message. // Create and start a fork of the default spinner. spinnerLiveText, _ := pterm.DefaultSpinner.Start("Doing a lot of stuff...") time.Sleep(time.Second) // Simulate 2 seconds of processing something. spinnerLiveText.UpdateText("It's really much") // Update spinner text. time.Sleep(time.Second) // Simulate 2 seconds of processing something. spinnerLiveText.UpdateText("We're nearly done!") // Update spinner text. time.Sleep(time.Second) // Simulate 2 seconds of processing something. spinnerLiveText.Success("Finally!") // Resolve spinner with success message. } ```
### style/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/style/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Create styles as new variables primary := pterm.NewStyle(pterm.FgLightCyan, pterm.BgGray, pterm.Bold) secondary := pterm.NewStyle(pterm.FgLightGreen, pterm.BgWhite, pterm.Italic) // Use created styles primary.Println("Hello, World!") secondary.Println("Hello, World!") } ```
### table/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/table/demo/animation.svg)
SHOW SOURCE ```go package main import "github.com/pterm/pterm" func main() { // Create a fork of the default table, fill it with data and print it. // Data can also be generated and inserted later. pterm.DefaultTable.WithHasHeader().WithData(pterm.TableData{ {"Firstname", "Lastname", "Email", "Note"}, {"Paul", "Dean", "nisi.dictum.augue@velitAliquam.co.uk", ""}, {"Callie", "Mckay", "egestas.nunc.sed@est.com", "这是一个测试, haha!"}, {"Libby", "Camacho", "aliquet.lobortis@semper.com", "just a test, hey!"}, }).Render() pterm.Println() // Blank line // Create a table with right alignment. pterm.DefaultTable.WithHasHeader().WithData(pterm.TableData{ {"Firstname", "Lastname", "Email"}, {"Paul", "Dean", "nisi.dictum.augue@velitAliquam.co.uk"}, {"Callie", "Mckay", "egestas.nunc.sed@est.com"}, {"Libby", "Camacho", "aliquet.lobortis@semper.com"}, {"张", "小宝", "zhang@example.com"}, }).WithRightAlignment().Render() } ```
### theme/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/theme/demo/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" "reflect" "time" ) func main() { // Print info. pterm.Info.Println("These are the default theme styles.\n" + "You can modify them easily to your personal preference,\n" + "or create new themes from scratch :)") pterm.Println() // Print one line space. // Print every value of the default theme with its own style. v := reflect.ValueOf(pterm.ThemeDefault) typeOfS := v.Type() if typeOfS == reflect.TypeOf(pterm.Theme{}) { for i := 0; i < v.NumField(); i++ { field, ok := v.Field(i).Interface().(pterm.Style) if ok { field.Println(typeOfS.Field(i).Name) } time.Sleep(time.Millisecond * 250) } } } ```
### tree/demo ![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/tree/demo/animation.svg)
SHOW SOURCE ```go package main import ( "github.com/pterm/pterm" "github.com/pterm/pterm/putils" ) func main() { // You can use a LeveledList here, for easy generation. leveledList := pterm.LeveledList{ pterm.LeveledListItem{Level: 0, Text: "C:"}, pterm.LeveledListItem{Level: 1, Text: "Users"}, pterm.LeveledListItem{Level: 1, Text: "Windows"}, pterm.LeveledListItem{Level: 1, Text: "Programs"}, pterm.LeveledListItem{Level: 1, Text: "Programs(x86)"}, pterm.LeveledListItem{Level: 1, Text: "dev"}, pterm.LeveledListItem{Level: 0, Text: "D:"}, pterm.LeveledListItem{Level: 0, Text: "E:"}, pterm.LeveledListItem{Level: 1, Text: "Movies"}, pterm.LeveledListItem{Level: 1, Text: "Music"}, pterm.LeveledListItem{Level: 2, Text: "LinkinPark"}, pterm.LeveledListItem{Level: 1, Text: "Games"}, pterm.LeveledListItem{Level: 2, Text: "Shooter"}, pterm.LeveledListItem{Level: 3, Text: "CallOfDuty"}, pterm.LeveledListItem{Level: 3, Text: "CS:GO"}, pterm.LeveledListItem{Level: 3, Text: "Battlefield"}, pterm.LeveledListItem{Level: 4, Text: "Battlefield 1"}, pterm.LeveledListItem{Level: 4, Text: "Battlefield 2"}, pterm.LeveledListItem{Level: 0, Text: "F:"}, pterm.LeveledListItem{Level: 1, Text: "dev"}, pterm.LeveledListItem{Level: 2, Text: "dops"}, pterm.LeveledListItem{Level: 2, Text: "PTerm"}, } // Generate tree from LeveledList. root := putils.TreeFromLeveledList(leveledList) // Render TreePrinter pterm.DefaultTree.WithRoot(root).Render() } ```

GitHub @pterm  ·  Author @MarvinJWendt | PTerm.sh