csf

package module
v0.0.0-...-ac2da56 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 15, 2025 License: MIT Imports: 3 Imported by: 0

README

csf

csf (context-based string formatting) is a utility library for generating templated strings using named key-value pairs provided by a map. I have most commonly used it when I have some "blob" of data (e.g. deserialized JSON) and I want to generate a human-facing string representation depending on the data included in the blob.

Example

input := map[string]any{ // usually from deserialized files or database
    "legal_name":     "John Smith",
    "preferred_name": "Johnny Apple",
    "email":          "john-smith@example.com",
}

st := csf.NewTemplate(
    csf.C("Author:"), // Constant string to add label
    csf.First( // First will return the first non-empty value in the list
        csf.F("preferred_name"),        // Preferred name has priority, but is optional
        csf.F("legal_name").Required(), // Required will generate error if neither is present
    ),
    csf.F("company").Formatter(func(v any) string { // Only appears if set, otherwise whitespace collapses
        return fmt.Sprintf("@ %s", v) // e.g. "ExampleCo" -> "@ ExampleCo"
    }),
    csf.F("email").Formatter(func(v any) string { // Add email if present, but not required
        return fmt.Sprintf("<%s>", v) // Custom formatter to wrap email in angle brackets
    }),
)

s, err := st.String(input)
if err != nil {
    panic(err) // handle error
}
fmt.Println(s)

// Outputs `Author: Johnny Apple <john-smith@example.com>`

Try editing the input map to remove various fields. When neither name is present, an error is generated. Providing preferred_name will always override legal_name. Note how removing the email will result in only the name being returned (and without any trailing whitespace or empty brackets). Setting the company field will auto suffix the name with where the user works: Author: Johnny Apple @ Microsoft <john-smith@example.com>.

Features

  • Fields may be optional (e.g. csf.F("email")) or required (e.g. csf.F("legal_name").Required()). Required fields will generate an error when missing, whereas optional fields are simply ignored.
  • Custom format functions are supported for stringify various types of fields (e.g. csf.F("email").Formatter(func(v any) string { ... })). csf additionally provides a few basic ones (csf.Value, csf.Array and csf.Const) out of the box.
  • Conditional field logic can be implemented using csf.First to return the first non-empty value in the list. This is useful for providing a fallback value when the primary value is not present, or when fields have a mutually exclusive/overriding relationship.
  • Constants can be placed in the template using csf.C("constant").

Motivation

For better or worse I routinely find myself needing to generate human-readable strings from deserialized map blobs. Usually I reach for text/template, or a bunch of if statements and parsing code in a String() method. This library is an attempt to provide a more structured way to do this, particularly when the data comes in various "shapes" (i.e. variable field inclusion).

Limitations

Is it fast? Not particularly, and I doubt any faster than the equivalent code rolled out. Is it type-safe? Not really. It is also "one shot" meaning you cannot progressively rewrite the string as you go. Don't expect this to replace "deeply" nested logic.

Installation

go get github.com/Cryptkeeper/csf

See the example included above. For more examples, see the test cases.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Value

func Value(v any) string

Value is a default Stringer implementation that returns the string representation of the input value using fmt.Sprintf.

Types

type Constant

type Constant struct {
	// contains filtered or unexported fields
}

Constant is an Eval-confirming implementation that always returns the provided constant string value. This is useful for cases where a fixed string value is needed in the template without evaluating any context map.

func C

func C(v string) *Constant

C creates a new Constant instance with the provided string value. This instance can be used in a template to return a fixed string value without evaluating the context map.

func (*Constant) String

func (c *Constant) String(_ map[string]any) (string, error)

String returns the constant value as a string. It does not evaluate the context map and always returns the same value.

type Eval

type Eval interface {
	// String evaluates the field's value in the context map and returns its
	// string representation. A non-nil error is returned if the implementation
	// cannot evaluate the value (e.g. the field is required and not present in
	// the context map). An empty string indicates the field was not found or
	// its value is nil.
	String(c map[string]any) (string, error)
}

Eval is a generic interface for evaluating the value of a field in a given context map as a string value.

type Field

type Field struct {
	// contains filtered or unexported fields
}

Field represents a single named value in the template conforming to Eval.

func F

func F(id string) *Field

F creates a new Field instance with the provided id and sets the default format to Value. The field instance defaults to being optional (not required) and has no default value.

func (*Field) Default

func (f *Field) Default(v any) *Field

Default sets a default value for the field. If the field is not present in the context map or its value is nil, the default value will be used instead.

func (*Field) Formatter

func (f *Field) Formatter(fmt Stringer) *Field

Formatter sets a custom Stringer function to format the field's value. This allows for custom formatting logic to be applied to the field's value when generating the string representation. If not set, the default Value function is used to convert the value to a string.

func (*Field) Required

func (f *Field) Required() *Field

Required marks the field as required. If the field is not present in the context map and no default value is set, an error will be returned when the template is evaluated.

func (*Field) String

func (f *Field) String(c map[string]any) (string, error)

String evaluates the field's value in the context map and returns its string representation using its provided Stringer. A non-nil error is returned if the field is required and not present in the context map (and no acceptable default value is provided). An empty string indicates the field was not found or its value is nil.

type FirstMatch

type FirstMatch struct {
	// contains filtered or unexported fields
}

FirstMatch is an Eval-confirming implementation that evaluates the given fields in order and returns the first successfully evaluated value as a string, if any.

func First

func First(fields ...Eval) *FirstMatch

First creates an evaluator that returns the first non-nil/non-zero value from a list of field values, otherwise returning an empty string. This is useful for cases where fields have some form of mutually exclusive relationships.

func (*FirstMatch) String

func (f *FirstMatch) String(c map[string]any) (string, error)

String returns the first non-zero/non-nil value from the list of fields provided to First. If no fields are found, an empty string is returned. If an error occurs while evaluating a field, it returns the error directly.

type Stringer

type Stringer func(v any) string

Stringer mimics fmt.Stringer but returns a string for a given value rather than being a method on a type. This is used to provide formatting callbacks when stringifying a field's value in the template.

func Array

func Array(sep string, stringer Stringer) Stringer

Array returns a Stringer conforming function which generates a sep seperated string list as a string from an array input by passing each array item to the provided Stringer for formatting.

func Const

func Const(s string) Stringer

Const returns a Stringer conforming function that always returns the provided string value.

type Template

type Template struct {
	// contains filtered or unexported fields
}

Template represents a list of ordered Eval values that can be used to generate a string representation from an input (a "context map") with conditional inclusion behavior and formatting delegates.

func NewTemplate

func NewTemplate(fields ...Eval) *Template

NewTemplate creates a new Template instance with the provided list of Eval values. The fields are stored in the order they are provided and will be evaluated in that order when generating the string representation.

func (Template) String

func (t Template) String(ctx map[string]any) (string, error)

String generates a string representation of the template using the provided context map inputs. Each template Eval is evaluated in order, returning any non-nil errors. Otherwise, the corresponding string, if non-empty, is concatenated into a single string with a space separator. If no fields are found or all are nil, an empty string is returned.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL