461 lines
10 KiB
Go
461 lines
10 KiB
Go
/*
|
|
|--------------------------------------------------------------------------
|
|
| Metadata
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Name:
|
|
| - Progress
|
|
|
|
|
| Purpose:
|
|
| - Manage, monitor, and report task progress through standardized
|
|
| lifecycle-aware progress tracking.
|
|
|
|
|
| Guideline:
|
|
| - Use "countable" type when progress can be measured using current
|
|
| and target numeric values.
|
|
| - Use "uncountable" type when progress is activity-based and cannot
|
|
| be represented by a completion percentage.
|
|
| - Always provide a valid lifecycle state before executing the utility.
|
|
| - Provide "current" and "target" values for countable progress
|
|
| tracking operations.
|
|
| - Provide a non-empty "placeholder" value for uncountable progress
|
|
| tracking operations.
|
|
| - Use returned payload data for CLI rendering, status reporting,
|
|
| and workflow monitoring.
|
|
| - Check the returned status and code before consuming payload data.
|
|
|
|
|
| Example:
|
|
| - ObrimProgress(
|
|
| "countable",
|
|
| map[string]any{
|
|
| "state": "running",
|
|
| "current": 45,
|
|
| "target": 100,
|
|
| },
|
|
| )
|
|
|
|
|
| - ObrimProgress(
|
|
| "uncountable",
|
|
| map[string]any{
|
|
| "state": "running",
|
|
| "placeholder": "Processing records...",
|
|
| },
|
|
| )
|
|
|
|
|
| - result := ObrimProgress(
|
|
| "countable",
|
|
| map[string]any{
|
|
| "state": "completed",
|
|
| "current": 100,
|
|
| "target": 100,
|
|
| },
|
|
| )
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Credit
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Contributor:
|
|
| - Rajon Ahmed
|
|
| - Scionite
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
package progress
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
obrimProgressTypeCountable = "countable"
|
|
obrimProgressTypeUncountable = "uncountable"
|
|
|
|
obrimProgressStateStarted = "started"
|
|
obrimProgressStateRunning = "running"
|
|
obrimProgressStateCompleted = "completed"
|
|
obrimProgressStateCanceled = "canceled"
|
|
|
|
ObrimProgressCodeSuccessStarted = "SUCCESS_PROGRESS_STARTED"
|
|
ObrimProgressCodeSuccessRunning = "SUCCESS_PROGRESS_RUNNING"
|
|
ObrimProgressCodeSuccessCompleted = "SUCCESS_PROGRESS_COMPLETED"
|
|
ObrimProgressCodeSuccessCanceled = "SUCCESS_PROGRESS_CANCELED"
|
|
|
|
ObrimProgressCodeFailedMisconfigured = "FAILED_PROGRESS_MISCONFIGURED"
|
|
|
|
obrimProgressVisualLength = 20
|
|
)
|
|
|
|
// obrimProgressStateMutex prevents state corruption during rapid updates.
|
|
var obrimProgressStateMutex sync.Mutex
|
|
|
|
// ObrimProgressCountablePayload defines countable progress payload data.
|
|
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 data.
|
|
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 map[string]any) map[string]any {
|
|
obrimProgressStateMutex.Lock()
|
|
defer obrimProgressStateMutex.Unlock()
|
|
|
|
if !obrimProgressValidate(progressType, config) {
|
|
return obrimProgressBuildResponse(
|
|
false,
|
|
ObrimProgressCodeFailedMisconfigured,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
code := obrimProgressHandleState(config["state"].(string))
|
|
|
|
switch progressType {
|
|
case obrimProgressTypeCountable:
|
|
return obrimProgressCountable(config, code)
|
|
|
|
case obrimProgressTypeUncountable:
|
|
return obrimProgressUncountable(config, code)
|
|
}
|
|
|
|
return obrimProgressBuildResponse(
|
|
false,
|
|
ObrimProgressCodeFailedMisconfigured,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
// obrimProgressValidate validates input parameters and configuration consistency.
|
|
func obrimProgressValidate(
|
|
progressType string,
|
|
config map[string]any,
|
|
) bool {
|
|
if config == nil {
|
|
return false
|
|
}
|
|
|
|
stateValue, exists := config["state"]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
state, ok := stateValue.(string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
switch state {
|
|
case obrimProgressStateStarted,
|
|
obrimProgressStateRunning,
|
|
obrimProgressStateCompleted,
|
|
obrimProgressStateCanceled:
|
|
|
|
default:
|
|
return false
|
|
}
|
|
|
|
switch progressType {
|
|
case obrimProgressTypeCountable:
|
|
currentValue, currentExists := config["current"]
|
|
targetValue, targetExists := config["target"]
|
|
|
|
if !currentExists || !targetExists {
|
|
return false
|
|
}
|
|
|
|
current, currentOk := currentValue.(int)
|
|
target, targetOk := targetValue.(int)
|
|
|
|
if !currentOk || !targetOk {
|
|
return false
|
|
}
|
|
|
|
if target <= 0 {
|
|
return false
|
|
}
|
|
|
|
_ = current
|
|
|
|
return true
|
|
|
|
case obrimProgressTypeUncountable:
|
|
placeholderValue, exists := config["placeholder"]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
placeholder, ok := placeholderValue.(string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
return strings.TrimSpace(placeholder) != ""
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// obrimProgressHandleState processes lifecycle state transitions.
|
|
func obrimProgressHandleState(state string) string {
|
|
switch state {
|
|
case obrimProgressStateStarted:
|
|
return ObrimProgressCodeSuccessStarted
|
|
|
|
case obrimProgressStateRunning:
|
|
return ObrimProgressCodeSuccessRunning
|
|
|
|
case obrimProgressStateCompleted:
|
|
return ObrimProgressCodeSuccessCompleted
|
|
|
|
case obrimProgressStateCanceled:
|
|
return ObrimProgressCodeSuccessCanceled
|
|
}
|
|
|
|
return ObrimProgressCodeFailedMisconfigured
|
|
}
|
|
|
|
// obrimProgressGenerateMessage generates human-readable progress messages.
|
|
func obrimProgressGenerateMessage(
|
|
progressType string,
|
|
state string,
|
|
payload map[string]any,
|
|
) string {
|
|
switch progressType {
|
|
case obrimProgressTypeCountable:
|
|
percentage := payload["percentage"].(int)
|
|
|
|
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 fmt.Sprintf(
|
|
"Progress completed (%d%% complete).",
|
|
percentage,
|
|
)
|
|
|
|
case obrimProgressStateCanceled:
|
|
return fmt.Sprintf(
|
|
"Progress canceled (%d%% complete).",
|
|
percentage,
|
|
)
|
|
}
|
|
|
|
case obrimProgressTypeUncountable:
|
|
placeholder := payload["placeholder"].(string)
|
|
|
|
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,
|
|
payload map[string]any,
|
|
) string {
|
|
switch progressType {
|
|
case obrimProgressTypeCountable:
|
|
percentage := payload["percentage"].(int)
|
|
|
|
filled := int(math.Round(
|
|
(float64(percentage) / 100.0) * float64(obrimProgressVisualLength),
|
|
))
|
|
|
|
if filled > obrimProgressVisualLength {
|
|
filled = obrimProgressVisualLength
|
|
}
|
|
|
|
if filled < 0 {
|
|
filled = 0
|
|
}
|
|
|
|
return fmt.Sprintf(
|
|
"[%s%s] %d%%",
|
|
strings.Repeat("=", filled),
|
|
strings.Repeat(" ", obrimProgressVisualLength-filled),
|
|
percentage,
|
|
)
|
|
|
|
case obrimProgressTypeUncountable:
|
|
return "[....................]"
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// obrimProgressBuildResponse constructs standardized utility response payloads.
|
|
func obrimProgressBuildResponse(
|
|
status bool,
|
|
code string,
|
|
payload any,
|
|
) map[string]any {
|
|
return map[string]any{
|
|
"status": status,
|
|
"code": code,
|
|
"payload": payload,
|
|
}
|
|
}
|
|
|
|
// obrimProgressCountable processes countable progress tracking workflows.
|
|
func obrimProgressCountable(
|
|
config map[string]any,
|
|
code string,
|
|
) map[string]any {
|
|
current := config["current"].(int)
|
|
target := config["target"].(int)
|
|
state := config["state"].(string)
|
|
|
|
percentage := obrimProgressCalculatePercentage(
|
|
current,
|
|
target,
|
|
)
|
|
|
|
payloadData := map[string]any{
|
|
"percentage": percentage,
|
|
}
|
|
|
|
payload := ObrimProgressCountablePayload{
|
|
State: state,
|
|
Current: current,
|
|
Target: target,
|
|
Percentage: percentage,
|
|
Message: obrimProgressGenerateMessage(
|
|
obrimProgressTypeCountable,
|
|
state,
|
|
payloadData,
|
|
),
|
|
Visual: obrimProgressGenerateVisual(
|
|
obrimProgressTypeCountable,
|
|
payloadData,
|
|
),
|
|
}
|
|
|
|
return obrimProgressBuildResponse(
|
|
true,
|
|
code,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
// obrimProgressCalculatePercentage calculates and normalizes completion percentages.
|
|
func obrimProgressCalculatePercentage(
|
|
current int,
|
|
target int,
|
|
) int {
|
|
if current < 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 map[string]any,
|
|
code string,
|
|
) map[string]any {
|
|
state := config["state"].(string)
|
|
|
|
placeholder := obrimProgressResolvePlaceholder(
|
|
config["placeholder"].(string),
|
|
)
|
|
|
|
payloadData := map[string]any{
|
|
"placeholder": placeholder,
|
|
}
|
|
|
|
payload := ObrimProgressUncountablePayload{
|
|
State: state,
|
|
Placeholder: placeholder,
|
|
Message: obrimProgressGenerateMessage(
|
|
obrimProgressTypeUncountable,
|
|
state,
|
|
payloadData,
|
|
),
|
|
Visual: obrimProgressGenerateVisual(
|
|
obrimProgressTypeUncountable,
|
|
payloadData,
|
|
),
|
|
}
|
|
|
|
return obrimProgressBuildResponse(
|
|
true,
|
|
code,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
// obrimProgressResolvePlaceholder validates and prepares placeholder activity messages.
|
|
func obrimProgressResolvePlaceholder(
|
|
placeholder string,
|
|
) string {
|
|
return strings.TrimSpace(placeholder)
|
|
}
|