Initial lookbook implementation
Pinterest-style visual bookmarking app with: - URL metadata extraction (OG/Twitter meta, oEmbed fallback) - Image caching in Postgres with 480px thumbnails - Multi-tag filtering with Ctrl/Cmd for OR mode - Fuzzy tag suggestions and inline tag editing - Browser console auth() with first-use password setup - Brutalist UI with Commit Mono font and Pico CSS - Light/dark mode via browser preference
This commit is contained in:
commit
fc625fb9cf
486 changed files with 195373 additions and 0 deletions
213
vendor/github.com/mfridman/interpolate/interpolate.go
generated
vendored
Normal file
213
vendor/github.com/mfridman/interpolate/interpolate.go
generated
vendored
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
package interpolate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Interpolate takes a set of environment and interpolates it into the provided string using shell
|
||||
// script expansions
|
||||
func Interpolate(env Env, str string) (string, error) {
|
||||
if env == nil {
|
||||
env = NewSliceEnv(nil)
|
||||
}
|
||||
expr, err := NewParser(str).Parse()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return expr.Expand(env)
|
||||
}
|
||||
|
||||
// Indentifiers parses the identifiers from any expansions in the provided string
|
||||
func Identifiers(str string) ([]string, error) {
|
||||
expr, err := NewParser(str).Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return expr.Identifiers(), nil
|
||||
}
|
||||
|
||||
// An expansion is something that takes in ENV and returns a string or an error
|
||||
type Expansion interface {
|
||||
Expand(env Env) (string, error)
|
||||
Identifiers() []string
|
||||
}
|
||||
|
||||
// VariableExpansion represents either $VAR or ${VAR}, our simplest expansion
|
||||
type VariableExpansion struct {
|
||||
Identifier string
|
||||
}
|
||||
|
||||
func (e VariableExpansion) Identifiers() []string {
|
||||
return []string{e.Identifier}
|
||||
}
|
||||
|
||||
func (e VariableExpansion) Expand(env Env) (string, error) {
|
||||
val, _ := env.Get(e.Identifier)
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// EmptyValueExpansion returns either the value of an env, or a default value if it's unset or null
|
||||
type EmptyValueExpansion struct {
|
||||
Identifier string
|
||||
Content Expression
|
||||
}
|
||||
|
||||
func (e EmptyValueExpansion) Identifiers() []string {
|
||||
return append([]string{e.Identifier}, e.Content.Identifiers()...)
|
||||
}
|
||||
|
||||
func (e EmptyValueExpansion) Expand(env Env) (string, error) {
|
||||
val, _ := env.Get(e.Identifier)
|
||||
if val == "" {
|
||||
return e.Content.Expand(env)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// UnsetValueExpansion returns either the value of an env, or a default value if it's unset
|
||||
type UnsetValueExpansion struct {
|
||||
Identifier string
|
||||
Content Expression
|
||||
}
|
||||
|
||||
func (e UnsetValueExpansion) Identifiers() []string {
|
||||
return []string{e.Identifier}
|
||||
}
|
||||
|
||||
func (e UnsetValueExpansion) Expand(env Env) (string, error) {
|
||||
val, ok := env.Get(e.Identifier)
|
||||
if !ok {
|
||||
return e.Content.Expand(env)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// SubstringExpansion returns a substring (or slice) of the env
|
||||
type SubstringExpansion struct {
|
||||
Identifier string
|
||||
Offset int
|
||||
Length int
|
||||
HasLength bool
|
||||
}
|
||||
|
||||
func (e SubstringExpansion) Identifiers() []string {
|
||||
return []string{e.Identifier}
|
||||
}
|
||||
|
||||
func (e SubstringExpansion) Expand(env Env) (string, error) {
|
||||
val, _ := env.Get(e.Identifier)
|
||||
|
||||
from := e.Offset
|
||||
|
||||
// Negative offsets = from end
|
||||
if from < 0 {
|
||||
from += len(val)
|
||||
}
|
||||
|
||||
// Still negative = too far from end? Truncate to start.
|
||||
if from < 0 {
|
||||
from = 0
|
||||
}
|
||||
|
||||
// Beyond end? Truncate to end.
|
||||
if from > len(val) {
|
||||
from = len(val)
|
||||
}
|
||||
|
||||
if !e.HasLength {
|
||||
return val[from:], nil
|
||||
}
|
||||
|
||||
to := e.Length
|
||||
|
||||
if to >= 0 {
|
||||
// Positive length = from offset
|
||||
to += from
|
||||
} else {
|
||||
// Negative length = from end
|
||||
to += len(val)
|
||||
|
||||
// Too far? Truncate to offset.
|
||||
if to < from {
|
||||
to = from
|
||||
}
|
||||
}
|
||||
|
||||
// Beyond end? Truncate to end.
|
||||
if to > len(val) {
|
||||
to = len(val)
|
||||
}
|
||||
|
||||
return val[from:to], nil
|
||||
}
|
||||
|
||||
// RequiredExpansion returns an env value, or an error if it is unset
|
||||
type RequiredExpansion struct {
|
||||
Identifier string
|
||||
Message Expression
|
||||
}
|
||||
|
||||
func (e RequiredExpansion) Identifiers() []string {
|
||||
return []string{e.Identifier}
|
||||
}
|
||||
|
||||
func (e RequiredExpansion) Expand(env Env) (string, error) {
|
||||
val, ok := env.Get(e.Identifier)
|
||||
if !ok {
|
||||
msg, err := e.Message.Expand(env)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if msg == "" {
|
||||
msg = "not set"
|
||||
}
|
||||
return "", fmt.Errorf("$%s: %s", e.Identifier, msg)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Expression is a collection of either Text or Expansions
|
||||
type Expression []ExpressionItem
|
||||
|
||||
func (e Expression) Identifiers() []string {
|
||||
identifiers := []string{}
|
||||
for _, item := range e {
|
||||
if item.Expansion != nil {
|
||||
identifiers = append(identifiers, item.Expansion.Identifiers()...)
|
||||
}
|
||||
}
|
||||
return identifiers
|
||||
}
|
||||
|
||||
func (e Expression) Expand(env Env) (string, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
for _, item := range e {
|
||||
if item.Expansion != nil {
|
||||
result, err := item.Expansion.Expand(env)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, _ = buf.WriteString(result)
|
||||
} else {
|
||||
_, _ = buf.WriteString(item.Text)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// ExpressionItem models either an Expansion or Text. Either/Or, never both.
|
||||
type ExpressionItem struct {
|
||||
Text string
|
||||
// -- or --
|
||||
Expansion Expansion
|
||||
}
|
||||
|
||||
func (i ExpressionItem) String() string {
|
||||
if i.Expansion != nil {
|
||||
return fmt.Sprintf("%#v", i.Expansion)
|
||||
}
|
||||
return fmt.Sprintf("%q", i.Text)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue