/progress

🏗 progress, steps, completion patterns for golang

Primary LanguageGoOtherNOASSERTION

progress

😄 progress

go.dev reference License GitHub release Made by Manfred Touron

Go PR GolangCI codecov Go Report Card CodeFactor

Gitpod ready-to-code

Usage

TYPES

type Progress struct {
	Steps     []*Step   `json:"steps,omitempty"`
	CreatedAt time.Time `json:"created_at,omitempty"`

	// Has unexported fields.
}
    Progress is the top-level object of the 'progress' library.

func New() *Progress
    New creates and returns a new Progress.

func (p *Progress) AddStep(id string) *Step
    AddStep creates and returns a new Step with the provided 'id'. A non-empty,
    unique 'id' is required, else it will panic.

func (p *Progress) Get(id string) *Step
    Get retrieves a Step by its 'id'. A non-empty 'id' is required, else it will
    panic. If 'id' does not match an existing step, nil is returned.

func (p *Progress) MarshalJSON() ([]byte, error)
    MarshalJSON is a custom JSON marshaler that automatically computes and
    append the current snapshot.

func (p *Progress) Percent() float64
    Percent returns the current completion percentage, it's a faster alternative
    to Progress.Snapshot().Percent.

func (p *Progress) SafeAddStep(id string) (*Step, error)
    SafeAddStep is equivalent to AddStep with but returns error instead of
    panicking.

func (p *Progress) Snapshot() Snapshot
    Snapshot computes and returns the current stats of the Progress.

func (p *Progress) Subscribe(subscriber chan *Step)
    Subscribe register a provided chan as a target called each time a step is
    changed.

type Snapshot struct {
	State              State         `json:"state,omitempty"`
	Doing              string        `json:"doing,omitempty"`
	NotStarted         int           `json:"not_started,omitempty"`
	InProgress         int           `json:"in_progress,omitempty"`
	Completed          int           `json:"completed,omitempty"`
	Total              int           `json:"total,omitempty"`
	Percent            float64       `json:"percent,omitempty"`
	TotalDuration      time.Duration `json:"total_duration,omitempty"`
	StepDuration       time.Duration `json:"step_duration,omitempty"`
	CompletionEstimate time.Duration `json:"completion_estimate,omitempty"`
	DoneAt             *time.Time    `json:"done_at,omitempty"`
	StartedAt          *time.Time    `json:"started_at,omitempty"`
}
    Snapshot represents info and stats about a progress at a given time.

type State string

const (
	StateNotStarted State = "not started"
	StateInProgress State = "in progress"
	StateDone       State = "done"
)
type Step struct {
	ID          string      `json:"id,omitempty"`
	Description string      `json:"description,omitempty"`
	StartedAt   *time.Time  `json:"started_at,omitempty"`
	DoneAt      *time.Time  `json:"done_at,omitempty"`
	State       State       `json:"state,omitempty"`
	Data        interface{} `json:"data,omitempty"`

	// Has unexported fields.
}
    Step represents a progress step. It always have an 'id' and can be
    customized using helpers.

func (s *Step) Done()
    Done marks a step as done. If the step was already done, it panics.

func (s *Step) Duration() time.Duration
    Duration computes the step duration.

func (s *Step) MarshalJSON() ([]byte, error)
    MarshalJSON is a custom JSON marshaler that automatically computes and
    append some runtime metadata.

func (s *Step) SetAsCurrent()
    SetAsCurrent stops all in-progress steps and start this one.

func (s *Step) SetData(data interface{}) *Step
    SetData sets a custom step data. It returns itself (*Step) for chaining.

func (s *Step) SetDescription(desc string) *Step
    SetDescription sets a custom step description. It returns itself (*Step) for
    chaining.

func (s *Step) Start()
    Start marks a step as started. If a step was already InProgress or Done, it
    panics.

Example

import (
	"fmt"
	"time"

	"moul.io/progress"
	"moul.io/u"
)

func Example() {
	// initialize a new progress.Progress
	prog := progress.New()
	prog.AddStep("init").SetDescription("initialize")
	prog.AddStep("step1").SetDescription("step 1")
	prog.AddStep("step2").SetData([]string{"hello", "world"}).SetDescription("step 2")
	prog.AddStep("step3")
	prog.AddStep("finish")

	// automatically mark the last step as done when the function quit
	defer prog.Get("finish").Done()

	// mark init as Done
	prog.Get("init").Done()

	// mark step1 as started
	prog.Get("step1").SetData(42).Start()

	// then, mark it as done + attach custom data
	prog.Get("step1").SetData(1337).Done()

	// mark step2 as started
	prog.Get("step2").Start()

	fmt.Println(u.PrettyJSON(prog))

	// outputs something like this:
	// {
	//  "steps": [
	//    {
	//      "id": "init",
	//      "description": "initialize",
	//      "started_at": "2020-12-22T20:26:05.717427484+01:00",
	//      "done_at": "2020-12-22T20:26:05.717427484+01:00",
	//      "state": "done"
	//    },
	//    {
	//      "id": "step1",
	//      "description": "step 1",
	//      "started_at": "2020-12-22T20:26:05.71742797+01:00",
	//      "done_at": "2020-12-22T20:26:05.717428258+01:00",
	//      "state": "done",
	//      "data": 1337,
	//      "duration": 286
	//    },
	//    {
	//      "id": "step2",
	//      "description": "step 2",
	//      "started_at": "2020-12-22T20:26:05.71742865+01:00",
	//      "state": "in progress",
	//      "data": [
	//        "hello",
	//        "world"
	//      ],
	//      "duration": 496251
	//    },
	//    {
	//      "id": "step3"
	//    },
	//    {
	//      "id": "finish"
	//    }
	//  ],
	//  "created_at": "2020-12-22T20:26:05.717423018+01:00",
	//  "snapshot": {
	//    "state": "in progress",
	//    "doing": "step 2",
	//    "not_started": 2,
	//    "in_progress": 1,
	//    "completed": 2,
	//    "total": 5,
	//    "percent": 50,
	//    "total_duration": 25935,
	//    "started_at": "2020-12-22T20:26:05.717427484+01:00"
	//  }
	//}
}

func ExampleProgressSubscribe() {
	prog := progress.New()
	done := make(chan bool)
	ch := make(chan *progress.Step, 0)
	prog.Subscribe(ch)
	go func() {
		idx := 0
		for step := range ch {
			if step == nil {
				break
			}
			fmt.Println(idx, step.ID, step.State)
			idx++
		}
		done <- true
	}()
	time.Sleep(10 * time.Millisecond)
	prog.AddStep("step1").SetDescription("hello")
	prog.AddStep("step2")
	prog.Get("step1").Start()
	prog.Get("step2").Done()
	prog.AddStep("step3")
	prog.Get("step3").Start()
	prog.Get("step1").Done()
	prog.Get("step3").Done()
	// fmt.Println(u.PrettyJSON(prog))
	<-done
	close(ch)

	// Output:
	// 0 step1 not started
	// 1 step1 not started
	// 2 step2 not started
	// 3 step1 in progress
	// 4 step2 done
	// 5 step3 not started
	// 6 step3 in progress
	// 7 step1 done
	// 8 step3 done
}

Install

Using go

go get moul.io/progress

Contribute

Contribute <3

I really welcome contributions. Your input is the most precious material. I'm well aware of that and I thank you in advance. Everyone is encouraged to look at what they can do on their own scale; no effort is too small.

Everything on contribution is sum up here: CONTRIBUTING.md

Contributors ✨

All Contributors

Thanks goes to these wonderful people (emoji key):


Manfred Touron

🚧 📖 ⚠️ 💻

moul-bot

🚧

This project follows the all-contributors specification. Contributions of any kind welcome!

Stargazers over time

Stargazers over time

License

© 2020 Manfred Touron

Licensed under the Apache License, Version 2.0 (LICENSE-APACHE) or the MIT license (LICENSE-MIT), at your option. See the COPYRIGHT file for more details.

SPDX-License-Identifier: (Apache-2.0 OR MIT)