mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 14:28:01 -05:00
- Add GitHub and Buy Me a Coffee funding configuration. - Localize setup prompts and error messages across multiple languages. - Implement helper for localized questions with static environment keys. - Update environment variable builder to handle hyphenated plugin names. - Replace hardcoded console output with localized i18n translation strings. - Expand locale files with comprehensive pattern and strategy translations. - Add new i18n keys for optional and required markers - Remove hardcoded `[required]` markers from description strings - Add custom patterns, Jina AI, YouTube, and language labels - Switch plugin descriptions to use i18n translation keys - Append markers dynamically to setup descriptions in Go code - Remove trailing newlines from plugin question prompt strings - Standardize all locale files with consistent formatting changes
335 lines
7.8 KiB
Go
335 lines
7.8 KiB
Go
package plugins
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/danielmiessler/fabric/internal/i18n"
|
|
)
|
|
|
|
const AnswerReset = "reset"
|
|
const SettingTypeBool = "bool"
|
|
|
|
type Plugin interface {
|
|
GetName() string
|
|
GetSetupDescription() string
|
|
IsConfigured() bool
|
|
Configure() error
|
|
Setup() error
|
|
SetupFillEnvFileContent(*bytes.Buffer)
|
|
}
|
|
|
|
type PluginBase struct {
|
|
Settings
|
|
SetupQuestions
|
|
|
|
Name string
|
|
SetupDescription string
|
|
EnvNamePrefix string
|
|
|
|
ConfigureCustom func() error
|
|
}
|
|
|
|
func (o *PluginBase) GetName() string {
|
|
return o.Name
|
|
}
|
|
|
|
func (o *PluginBase) GetSetupDescription() (ret string) {
|
|
if ret = o.SetupDescription; ret == "" {
|
|
ret = o.GetName()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (o *PluginBase) AddSetting(name string, required bool) (ret *Setting) {
|
|
ret = NewSetting(fmt.Sprintf("%v%v", o.EnvNamePrefix, BuildEnvVariable(name)), required)
|
|
o.Settings = append(o.Settings, ret)
|
|
return
|
|
}
|
|
|
|
func (o *PluginBase) AddSetupQuestion(name string, required bool) (ret *SetupQuestion) {
|
|
return o.AddSetupQuestionCustom(name, required, "")
|
|
}
|
|
|
|
func (o *PluginBase) AddSetupQuestionCustom(name string, required bool, question string) (ret *SetupQuestion) {
|
|
setting := o.AddSetting(name, required)
|
|
ret = &SetupQuestion{Setting: setting, Question: question}
|
|
if ret.Question == "" {
|
|
ret.Question = fmt.Sprintf(i18n.T("plugin_enter_value"), o.Name, strings.ToUpper(name))
|
|
}
|
|
o.SetupQuestions = append(o.SetupQuestions, ret)
|
|
return
|
|
}
|
|
|
|
// AddSetupQuestionWithEnvName creates a setup question with an explicit environment variable name.
|
|
// This is useful when you want the environment variable name to remain constant across languages.
|
|
// The envVarName is used for the environment variable, while the question is localized.
|
|
func (o *PluginBase) AddSetupQuestionWithEnvName(envVarName string, required bool, question string) (ret *SetupQuestion) {
|
|
setting := o.AddSetting(envVarName, required)
|
|
ret = &SetupQuestion{Setting: setting, Question: question}
|
|
o.SetupQuestions = append(o.SetupQuestions, ret)
|
|
return
|
|
}
|
|
|
|
func (o *PluginBase) AddSetupQuestionBool(name string, required bool) (ret *SetupQuestion) {
|
|
return o.AddSetupQuestionCustomBool(name, required, "")
|
|
}
|
|
|
|
func (o *PluginBase) AddSetupQuestionCustomBool(name string, required bool, question string) (ret *SetupQuestion) {
|
|
setting := o.AddSetting(name, required)
|
|
setting.Type = SettingTypeBool
|
|
ret = &SetupQuestion{Setting: setting, Question: question}
|
|
if ret.Question == "" {
|
|
ret.Question = fmt.Sprintf(i18n.T("plugin_enable_bool_question"), o.Name, strings.ToUpper(name))
|
|
}
|
|
o.SetupQuestions = append(o.SetupQuestions, ret)
|
|
return
|
|
}
|
|
|
|
func (o *PluginBase) Configure() (err error) {
|
|
if err = o.Settings.Configure(); err != nil {
|
|
return
|
|
}
|
|
|
|
if o.ConfigureCustom != nil {
|
|
err = o.ConfigureCustom()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (o *PluginBase) Setup() (err error) {
|
|
if err = o.Ask(o.Name); err != nil {
|
|
return
|
|
}
|
|
|
|
// After Setup, run ConfigureCustom if present, but skip re-validation
|
|
// since Ask() already validated user input (or allowed explicit reset)
|
|
if o.ConfigureCustom != nil {
|
|
err = o.ConfigureCustom()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (o *PluginBase) SetupOrSkip() (err error) {
|
|
if err = o.Setup(); err != nil {
|
|
fmt.Printf(i18n.T("plugin_setup_skipped"), o.GetName())
|
|
}
|
|
return
|
|
}
|
|
|
|
func (o *PluginBase) SetupFillEnvFileContent(fileEnvFileContent *bytes.Buffer) {
|
|
o.Settings.FillEnvFileContent(fileEnvFileContent)
|
|
}
|
|
|
|
func NewSetting(envVariable string, required bool) *Setting {
|
|
return &Setting{
|
|
EnvVariable: envVariable,
|
|
Required: required,
|
|
}
|
|
}
|
|
|
|
// In plugins/plugin.go
|
|
|
|
type Setting struct {
|
|
EnvVariable string
|
|
Value string
|
|
Required bool
|
|
Type string // "string" (default), "bool"
|
|
}
|
|
|
|
func (o *Setting) IsValid() bool {
|
|
if o.Type == SettingTypeBool {
|
|
_, err := ParseBool(o.Value)
|
|
return (err == nil) || !o.Required
|
|
}
|
|
return o.IsDefined() || !o.Required
|
|
}
|
|
|
|
func (o *Setting) Print() {
|
|
if o.Type == SettingTypeBool {
|
|
v, _ := ParseBool(o.Value)
|
|
fmt.Printf("%v: %v\n", o.EnvVariable, v)
|
|
} else {
|
|
fmt.Printf("%v: %v\n", o.EnvVariable, o.Value)
|
|
}
|
|
}
|
|
|
|
func (o *Setting) FillEnvFileContent(buffer *bytes.Buffer) {
|
|
if o.IsDefined() {
|
|
buffer.WriteString(o.EnvVariable)
|
|
buffer.WriteString("=")
|
|
if o.Type == SettingTypeBool {
|
|
v, _ := ParseBool(o.Value)
|
|
buffer.WriteString(fmt.Sprintf("%v", v))
|
|
} else {
|
|
buffer.WriteString(o.Value)
|
|
}
|
|
buffer.WriteString("\n")
|
|
}
|
|
}
|
|
|
|
func ParseBoolElseFalse(val string) (ret bool) {
|
|
ret, _ = ParseBool(val)
|
|
return
|
|
}
|
|
|
|
func ParseBool(val string) (bool, error) {
|
|
switch strings.ToLower(strings.TrimSpace(val)) {
|
|
case "1", "true", "yes", "on":
|
|
return true, nil
|
|
case "0", "false", "no", "off":
|
|
return false, nil
|
|
}
|
|
return false, fmt.Errorf(i18n.T("plugin_invalid_bool"), val)
|
|
}
|
|
|
|
type SetupQuestion struct {
|
|
*Setting
|
|
Question string
|
|
}
|
|
|
|
func (o *SetupQuestion) Ask(label string) (err error) {
|
|
var prefix string
|
|
if label != "" {
|
|
prefix = fmt.Sprintf("[%v] ", label)
|
|
} else {
|
|
prefix = ""
|
|
}
|
|
fmt.Println()
|
|
if o.Type == SettingTypeBool {
|
|
current := "false"
|
|
if v, err := ParseBool(o.Value); err == nil && v {
|
|
current = "true"
|
|
}
|
|
fmt.Printf(i18n.T("plugin_question_bool"), prefix, o.Question, current, AnswerReset)
|
|
} else if o.Value != "" {
|
|
fmt.Printf(i18n.T("plugin_question_with_default"), prefix, o.Question, o.Value, AnswerReset)
|
|
} else {
|
|
fmt.Printf(i18n.T("plugin_question_optional"), prefix, o.Question)
|
|
}
|
|
var answer string
|
|
fmt.Scanln(&answer)
|
|
answer = strings.TrimRight(answer, "\n")
|
|
isReset := strings.ToLower(answer) == AnswerReset
|
|
if answer == "" {
|
|
answer = o.Value
|
|
} else if isReset {
|
|
answer = ""
|
|
}
|
|
err = o.OnAnswerWithReset(answer, isReset)
|
|
return
|
|
}
|
|
|
|
func (o *SetupQuestion) OnAnswer(answer string) (err error) {
|
|
return o.OnAnswerWithReset(answer, false)
|
|
}
|
|
|
|
func (o *SetupQuestion) OnAnswerWithReset(answer string, isReset bool) (err error) {
|
|
if o.Type == SettingTypeBool {
|
|
if answer == "" {
|
|
o.Value = ""
|
|
} else {
|
|
_, err := ParseBool(answer)
|
|
if err != nil {
|
|
return fmt.Errorf(i18n.T("plugin_invalid_boolean_value"), answer)
|
|
}
|
|
o.Value = strings.ToLower(answer)
|
|
}
|
|
} else {
|
|
o.Value = answer
|
|
}
|
|
if o.EnvVariable != "" {
|
|
if err = os.Setenv(o.EnvVariable, o.Value); err != nil {
|
|
return
|
|
}
|
|
}
|
|
// Skip validation when explicitly resetting a value - the user intentionally
|
|
// wants to clear the value even if it's required
|
|
if isReset {
|
|
return nil
|
|
}
|
|
err = o.IsValidErr()
|
|
return
|
|
}
|
|
|
|
func (o *Setting) IsValidErr() (err error) {
|
|
if !o.IsValid() {
|
|
err = fmt.Errorf(i18n.T("plugin_setting_not_valid"), o.EnvVariable, o.Value)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (o *Setting) IsDefined() bool {
|
|
return o.Value != ""
|
|
}
|
|
|
|
func (o *Setting) Configure() error {
|
|
envValue := os.Getenv(o.EnvVariable)
|
|
if envValue != "" {
|
|
o.Value = envValue
|
|
}
|
|
return o.IsValidErr()
|
|
}
|
|
|
|
func NewSetupQuestion(question string) *SetupQuestion {
|
|
return &SetupQuestion{Setting: &Setting{}, Question: question}
|
|
}
|
|
|
|
type Settings []*Setting
|
|
|
|
func (o Settings) IsConfigured() (ret bool) {
|
|
ret = true
|
|
for _, setting := range o {
|
|
if ret = setting.IsValid(); !ret {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (o Settings) Configure() (err error) {
|
|
for _, setting := range o {
|
|
if err = setting.Configure(); err != nil {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (o Settings) FillEnvFileContent(buffer *bytes.Buffer) {
|
|
for _, setting := range o {
|
|
setting.FillEnvFileContent(buffer)
|
|
}
|
|
}
|
|
|
|
type SetupQuestions []*SetupQuestion
|
|
|
|
func (o SetupQuestions) Ask(label string) (err error) {
|
|
fmt.Println()
|
|
fmt.Printf("[%v]\n", label)
|
|
for _, question := range o {
|
|
if err = question.Ask(""); err != nil {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func BuildEnvVariablePrefix(name string) (ret string) {
|
|
ret = BuildEnvVariable(name)
|
|
if ret != "" {
|
|
ret += "_"
|
|
}
|
|
return
|
|
}
|
|
|
|
func BuildEnvVariable(name string) string {
|
|
name = strings.TrimSpace(name)
|
|
name = strings.ToUpper(name)
|
|
name = strings.ReplaceAll(name, " ", "_")
|
|
name = strings.ReplaceAll(name, "-", "_")
|
|
return name
|
|
}
|