336 lines
8.0 KiB
Go
336 lines
8.0 KiB
Go
package progress
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
obrimProgressTypeCountable = "countable"
|
|
obrimProgressTypeUncountable = "uncountable"
|
|
|
|
obrimProgressStateStarted = "started"
|
|
obrimProgressStateRunning = "running"
|
|
obrimProgressStateCompleted = "completed"
|
|
obrimProgressStateCanceled = "canceled"
|
|
|
|
obrimProgressSuccessStarted = "SUCCESS_PROGRESS_STARTED"
|
|
obrimProgressSuccessRunning = "SUCCESS_PROGRESS_RUNNING"
|
|
obrimProgressSuccessCompleted = "SUCCESS_PROGRESS_COMPLETED"
|
|
obrimProgressSuccessCanceled = "SUCCESS_PROGRESS_CANCELED"
|
|
|
|
obrimProgressFailedMisconfigured = "FAILED_PROGRESS_MISCONFIGURED"
|
|
|
|
obrimProgressVisualWidth = 20
|
|
)
|
|
|
|
var (
|
|
// obrimProgressMutex protects progress state transitions from corruption.
|
|
obrimProgressMutex sync.Mutex
|
|
)
|
|
|
|
// ObrimProgressConfig defines progress configuration.
|
|
type ObrimProgressConfig struct {
|
|
State string `json:"state"`
|
|
Current int `json:"current,omitempty"`
|
|
Target int `json:"target,omitempty"`
|
|
Placeholder string `json:"placeholder,omitempty"`
|
|
}
|
|
|
|
// ObrimProgressResponse defines standardized utility responses.
|
|
type ObrimProgressResponse struct {
|
|
Status bool `json:"status"`
|
|
Code string `json:"code"`
|
|
Payload interface{} `json:"payload"`
|
|
}
|
|
|
|
// ObrimProgressCountablePayload defines countable progress payload.
|
|
type ObrimProgressCountablePayload struct {
|
|
State string `json:"state"`
|
|
Current int `json:"current"`
|
|
Target int `json:"target"`
|
|
Percentage int `json:"percentage"`
|
|
Message string `json:"message"`
|
|
Visual string `json:"visual"`
|
|
}
|
|
|
|
// ObrimProgressUncountablePayload defines uncountable progress payload.
|
|
type ObrimProgressUncountablePayload struct {
|
|
State string `json:"state"`
|
|
Placeholder string `json:"placeholder"`
|
|
Message string `json:"message"`
|
|
Visual string `json:"visual"`
|
|
}
|
|
|
|
// ObrimProgress manages lifecycle-aware countable and uncountable progress tracking operations.
|
|
func ObrimProgress(progressType string, config ObrimProgressConfig) ObrimProgressResponse {
|
|
obrimProgressMutex.Lock()
|
|
defer obrimProgressMutex.Unlock()
|
|
|
|
if err := obrimProgressValidate(progressType, config); err != nil {
|
|
return obrimProgressBuildResponse(false, obrimProgressFailedMisconfigured, nil)
|
|
}
|
|
|
|
stateCode := obrimProgressHandleState(config.State)
|
|
|
|
switch progressType {
|
|
case obrimProgressTypeCountable:
|
|
return obrimProgressCountable(config, stateCode)
|
|
|
|
case obrimProgressTypeUncountable:
|
|
return obrimProgressUncountable(config, stateCode)
|
|
|
|
default:
|
|
return obrimProgressBuildResponse(false, obrimProgressFailedMisconfigured, nil)
|
|
}
|
|
}
|
|
|
|
// obrimProgressValidate validates input parameters and configuration consistency.
|
|
func obrimProgressValidate(progressType string, config ObrimProgressConfig) error {
|
|
switch progressType {
|
|
case obrimProgressTypeCountable:
|
|
if config.Target <= 0 {
|
|
return fmt.Errorf("invalid target")
|
|
}
|
|
|
|
case obrimProgressTypeUncountable:
|
|
if strings.TrimSpace(config.Placeholder) == "" {
|
|
return fmt.Errorf("missing placeholder")
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("invalid type")
|
|
}
|
|
|
|
switch config.State {
|
|
case obrimProgressStateStarted,
|
|
obrimProgressStateRunning,
|
|
obrimProgressStateCompleted,
|
|
obrimProgressStateCanceled:
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("invalid state")
|
|
}
|
|
}
|
|
|
|
// obrimProgressHandleState processes lifecycle state transitions.
|
|
func obrimProgressHandleState(state string) string {
|
|
switch state {
|
|
case obrimProgressStateStarted:
|
|
log.Printf(`{"feature":"progress","event":"started"}`)
|
|
return obrimProgressSuccessStarted
|
|
|
|
case obrimProgressStateRunning:
|
|
log.Printf(`{"feature":"progress","event":"running"}`)
|
|
return obrimProgressSuccessRunning
|
|
|
|
case obrimProgressStateCompleted:
|
|
log.Printf(`{"feature":"progress","event":"completed"}`)
|
|
return obrimProgressSuccessCompleted
|
|
|
|
case obrimProgressStateCanceled:
|
|
log.Printf(`{"feature":"progress","event":"canceled"}`)
|
|
return obrimProgressSuccessCanceled
|
|
|
|
default:
|
|
return obrimProgressFailedMisconfigured
|
|
}
|
|
}
|
|
|
|
// obrimProgressCountable processes countable progress tracking workflows.
|
|
func obrimProgressCountable(
|
|
config ObrimProgressConfig,
|
|
code string,
|
|
) ObrimProgressResponse {
|
|
percentage := obrimProgressCalculatePercentage(
|
|
config.Current,
|
|
config.Target,
|
|
)
|
|
|
|
message := obrimProgressGenerateMessage(
|
|
obrimProgressTypeCountable,
|
|
config.State,
|
|
percentage,
|
|
"",
|
|
)
|
|
|
|
visual := obrimProgressGenerateVisual(
|
|
obrimProgressTypeCountable,
|
|
percentage,
|
|
"",
|
|
)
|
|
|
|
payload := ObrimProgressCountablePayload{
|
|
State: config.State,
|
|
Current: config.Current,
|
|
Target: config.Target,
|
|
Percentage: percentage,
|
|
Message: message,
|
|
Visual: visual,
|
|
}
|
|
|
|
return obrimProgressBuildResponse(true, code, payload)
|
|
}
|
|
|
|
// obrimProgressCalculatePercentage calculates and normalizes completion percentages.
|
|
func obrimProgressCalculatePercentage(current int, target int) int {
|
|
if current < 0 {
|
|
return 0
|
|
}
|
|
|
|
if target <= 0 {
|
|
return 0
|
|
}
|
|
|
|
percentage := int(
|
|
math.Round(
|
|
(float64(current) / float64(target)) * 100,
|
|
),
|
|
)
|
|
|
|
if percentage > 100 {
|
|
return 100
|
|
}
|
|
|
|
if percentage < 0 {
|
|
return 0
|
|
}
|
|
|
|
return percentage
|
|
}
|
|
|
|
// obrimProgressUncountable processes uncountable progress tracking workflows.
|
|
func obrimProgressUncountable(
|
|
config ObrimProgressConfig,
|
|
code string,
|
|
) ObrimProgressResponse {
|
|
placeholder := obrimProgressResolvePlaceholder(
|
|
config.Placeholder,
|
|
)
|
|
|
|
message := obrimProgressGenerateMessage(
|
|
obrimProgressTypeUncountable,
|
|
config.State,
|
|
0,
|
|
placeholder,
|
|
)
|
|
|
|
visual := obrimProgressGenerateVisual(
|
|
obrimProgressTypeUncountable,
|
|
0,
|
|
placeholder,
|
|
)
|
|
|
|
payload := ObrimProgressUncountablePayload{
|
|
State: config.State,
|
|
Placeholder: placeholder,
|
|
Message: message,
|
|
Visual: visual,
|
|
}
|
|
|
|
return obrimProgressBuildResponse(true, code, payload)
|
|
}
|
|
|
|
// obrimProgressResolvePlaceholder validates and prepares placeholder activity messages.
|
|
func obrimProgressResolvePlaceholder(placeholder string) string {
|
|
return strings.TrimSpace(placeholder)
|
|
}
|
|
|
|
// obrimProgressGenerateMessage generates human-readable progress messages.
|
|
func obrimProgressGenerateMessage(
|
|
progressType string,
|
|
state string,
|
|
percentage int,
|
|
placeholder string,
|
|
) string {
|
|
switch progressType {
|
|
case obrimProgressTypeCountable:
|
|
switch state {
|
|
case obrimProgressStateStarted:
|
|
return fmt.Sprintf("Progress started (%d%% complete).", percentage)
|
|
|
|
case obrimProgressStateRunning:
|
|
return fmt.Sprintf("Progress running (%d%% complete).", percentage)
|
|
|
|
case obrimProgressStateCompleted:
|
|
return "Progress completed successfully."
|
|
|
|
case obrimProgressStateCanceled:
|
|
return "Progress canceled."
|
|
}
|
|
|
|
case obrimProgressTypeUncountable:
|
|
switch state {
|
|
case obrimProgressStateStarted:
|
|
return fmt.Sprintf("Activity started: %s", placeholder)
|
|
|
|
case obrimProgressStateRunning:
|
|
return fmt.Sprintf("Activity running: %s", placeholder)
|
|
|
|
case obrimProgressStateCompleted:
|
|
return fmt.Sprintf("Activity completed: %s", placeholder)
|
|
|
|
case obrimProgressStateCanceled:
|
|
return fmt.Sprintf("Activity canceled: %s", placeholder)
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// obrimProgressGenerateVisual generates CLI-friendly visual progress representations.
|
|
func obrimProgressGenerateVisual(
|
|
progressType string,
|
|
percentage int,
|
|
placeholder string,
|
|
) string {
|
|
switch progressType {
|
|
case obrimProgressTypeCountable:
|
|
filled := int(
|
|
math.Round(
|
|
(float64(percentage) / 100.0) * obrimProgressVisualWidth,
|
|
),
|
|
)
|
|
|
|
if filled > obrimProgressVisualWidth {
|
|
filled = obrimProgressVisualWidth
|
|
}
|
|
|
|
if filled < 0 {
|
|
filled = 0
|
|
}
|
|
|
|
return fmt.Sprintf(
|
|
"[%s%s] %d%%",
|
|
strings.Repeat("=", filled),
|
|
strings.Repeat(" ", obrimProgressVisualWidth-filled),
|
|
percentage,
|
|
)
|
|
|
|
case obrimProgressTypeUncountable:
|
|
return fmt.Sprintf("[...] %s", placeholder)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// obrimProgressBuildResponse constructs standardized utility response payloads.
|
|
func obrimProgressBuildResponse(
|
|
status bool,
|
|
code string,
|
|
payload interface{},
|
|
) ObrimProgressResponse {
|
|
if !status {
|
|
payload = nil
|
|
}
|
|
|
|
return ObrimProgressResponse{
|
|
Status: status,
|
|
Code: code,
|
|
Payload: payload,
|
|
}
|
|
}
|