Skip to content

Commit

Permalink
Merge pull request #71 from dogmatiq/reusable-registry
Browse files Browse the repository at this point in the history
Reusable registries
  • Loading branch information
jmalloc authored Jun 11, 2023
2 parents a034914 + d01de80 commit bdae1fd
Show file tree
Hide file tree
Showing 54 changed files with 806 additions and 488 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ The format is based on [Keep a Changelog], and this project adheres to

## Unreleased

### Added

- Added `Registry` and `NewRegistry()`
- Added `RegistryOption` and `WithDocumentationURL()`

### Changed

- Changed `WithRegistry()` to accept a `ferrite.Registry` instead of the experimental `variable.Registry` type
- Passing `WithRegistry()` to `Init()` is now additive, instead of replacing the default registry

### Removed

- Removed the experimental `maybe` and `variable` sub-packages
Expand Down
13 changes: 6 additions & 7 deletions builder_kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,12 @@ func (b *KubernetesServiceBuilder) Required(options ...RequiredOption) Required[
}

host := variable.Register(
cfg.Registry,
cfg.Registries,
b.hostBuilder.Done(b.hostSchema),
)

port := variable.Register(
cfg.Registry,
cfg.Registries,
b.portBuilder.Done(b.portSchema),
)

Expand Down Expand Up @@ -213,12 +213,11 @@ func (b *KubernetesServiceBuilder) Optional(options ...OptionalOption) Optional[
}

host := variable.Register(
cfg.Registry,
cfg.Registries,
b.hostBuilder.Done(b.hostSchema),
)

port := variable.Register(
cfg.Registry,
cfg.Registries,
b.portBuilder.Done(b.portSchema),
)

Expand All @@ -242,12 +241,12 @@ func (b *KubernetesServiceBuilder) Deprecated(options ...DeprecatedOption) Depre
}

host := variable.Register(
cfg.Registry,
cfg.Registries,
b.hostBuilder.Done(b.hostSchema),
)

port := variable.Register(
cfg.Registry,
cfg.Registries,
b.portBuilder.Done(b.portSchema),
)

Expand Down
7 changes: 5 additions & 2 deletions init.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package ferrite

import (
"fmt"
"os"

"github.com/dogmatiq/ferrite/internal/environment"
"github.com/dogmatiq/ferrite/internal/mode"
"github.com/dogmatiq/ferrite/internal/mode/export/dotenv"
"github.com/dogmatiq/ferrite/internal/mode/usage/markdown"
"github.com/dogmatiq/ferrite/internal/mode/validate"
"github.com/dogmatiq/ferrite/internal/variable"
)

// Init initializes Ferrite.
Expand All @@ -33,11 +34,13 @@ func Init(options ...InitOption) {
mode.DefaultConfig,
}

cfg.ModeConfig.Registries.Add(variable.DefaultRegistry)

for _, opt := range options {
opt.applyInitOption(&cfg)
}

switch m := os.Getenv("FERRITE_MODE"); m {
switch m := environment.Get("FERRITE_MODE"); m {
case "validate", "":
validate.Run(cfg.ModeConfig)
case "usage/markdown":
Expand Down
2 changes: 1 addition & 1 deletion init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func example() func() {
// tearDown resets the environemnt and Ferrite global state after a test.
func tearDown() {
mode.ResetDefaultConfig()
variable.DefaultRegistry.Reset()
variable.ResetDefaultRegistry()

for _, env := range os.Environ() {
if strings.HasPrefix(env, "FERRITE_") {
Expand Down
3 changes: 3 additions & 0 deletions internal/environment/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package environment is an abstraction that Ferrite uses to interact with the
// environment.
package environment
13 changes: 13 additions & 0 deletions internal/environment/name.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package environment

// EqualNames returns true if the given names refer to the same environment
// variable.
func EqualNames(a, b string) bool {
return normalizeName(a) == normalizeName(b)
}

// NormalizeName normalizes an environment variable name for use as a key within
// a map.
func NormalizeName(n string) string {
return normalizeName(n)
}
11 changes: 11 additions & 0 deletions internal/environment/name_caseinsensitive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build windows

package environment

import "strings"

// normalizeName normalizes an environment variable name, such that it compares
// as equals to other variables with the same name.
func normalizeName(n string) string {
return strings.ToUpper(n)
}
9 changes: 9 additions & 0 deletions internal/environment/name_casesensitive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//go:build !windows

package environment

// normalizeName normalizes an environment variable name, such that it compares
// as equals to other variables with the same name.
func normalizeName(n string) string {
return n
}
43 changes: 43 additions & 0 deletions internal/environment/rw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package environment

import (
"os"
"strings"
)

// Get returns the value of the environment variable with the given name.
func Get(n string) string {
return os.Getenv(n)
}

// Set sets the value of the environment variable with the given name, or panics
// if unable to do so.
func Set(n, v string) {
if err := os.Setenv(n, v); err != nil {
panic(err)
}
}

// Unset unsets the environment variable with the given name, or panics if
// unable to do so.
func Unset(n string) {
if err := os.Unsetenv(n); err != nil {
panic(err)
}
}

// Range calls fn for each environment variable.
//
// It stops iterating if fn returns false. It returns true if iteration
// reached the end.
func Range(fn func(n, v string) bool) {
for _, env := range os.Environ() {
i := strings.IndexByte(env, '=')
n := env[:i]
v := env[i+1:]

if !fn(n, v) {
return
}
}
}
35 changes: 35 additions & 0 deletions internal/environment/snapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package environment

// Snapshot is a snapshot of the environment.
type Snapshot struct {
variables []variable
}

type variable struct {
Name, Value string
}

// TakeSnapshot takes a snapshot of the variables within the environment.
func TakeSnapshot() *Snapshot {
s := &Snapshot{}

Range(func(name, value string) bool {
s.variables = append(s.variables, variable{name, value})
return true
})

return s
}

// RestoreSnapshot restores the environment to the state it was in when the
// given snapshot was taken.
func RestoreSnapshot(s *Snapshot) {
Range(func(name, _ string) bool {
Unset(name)
return true
})

for _, v := range s.variables {
Set(v.Name, v.Value)
}
}
19 changes: 9 additions & 10 deletions internal/mode/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (

// Config is the configuration used when running a mode.
type Config struct {
Registry *variable.Registry
Args []string
Out io.Writer
Err io.Writer
Exit func(int)
Registries variable.RegistrySet
Args []string
Out io.Writer
Err io.Writer
Exit func(int)
}

// DefaultConfig is the default configuration for running a mode.
Expand All @@ -23,11 +23,10 @@ var DefaultConfig Config
// largely intended for tearing down tests.
func ResetDefaultConfig() {
DefaultConfig = Config{
&variable.DefaultRegistry,
os.Args,
os.Stdout,
os.Stderr,
os.Exit,
Args: os.Args,
Out: os.Stdout,
Err: os.Stderr,
Exit: os.Exit,
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/mode/export/dotenv/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
// Run generates and env file describing the environment variables and their
// current values.
func Run(cfg mode.Config) {
for i, v := range cfg.Registry.Variables() {
for i, v := range cfg.Registries.Variables() {
s := v.Spec()

if i > 0 {
Expand Down
5 changes: 2 additions & 3 deletions internal/mode/usage/markdown/complete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package markdown_test

import (
"github.com/dogmatiq/ferrite"
"github.com/dogmatiq/ferrite/internal/variable"
. "github.com/onsi/ginkgo/v2"
)

Expand All @@ -12,12 +11,12 @@ var _ = DescribeTable(
Entry(
"no variables",
"empty.md",
func(reg *variable.Registry) {},
func(reg ferrite.Registry) {},
),
Entry(
"non-normative examples",
"non-normative.md",
func(reg *variable.Registry) {
func(reg ferrite.Registry) {
ferrite.
String("READ_DSN", "database connection string for read-models").
Required(ferrite.WithRegistry(reg))
Expand Down
4 changes: 2 additions & 2 deletions internal/mode/usage/markdown/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
// hasNonNormativeExamples returns true if any of the specs have non-normative
// examples.
func (r *renderer) hasNonNormativeExamples() bool {
for _, s := range r.Specs {
for _, eg := range s.Examples() {
for _, v := range r.Variables {
for _, eg := range v.Spec().Examples() {
if !eg.IsNormative {
return true
}
Expand Down
60 changes: 41 additions & 19 deletions internal/mode/usage/markdown/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,53 @@ import (
)

func (r *renderer) renderIndex() {
if len(r.Specs) == 0 {
if len(r.Variables) == 0 {
r.line("**There do not appear to be any environment variables.**")
} else {
var t table
return
}

hasImportColumn := false
for _, v := range r.Variables {
if !v.Registry.IsDefault {
hasImportColumn = true
break
}
}

var t table

if hasImportColumn {
t.AddRow("Name", "Optionality", "Description", "Imported From")
} else {
t.AddRow("Name", "Optionality", "Description")
}

for _, s := range r.Specs {
name := r.linkToSpec(s)
optionality := "required"

if s.IsDeprecated() {
name = "~~" + name + "~~"
optionality = "optional, deprecated"
} else if def, ok := s.Default(); ok {
optionality = fmt.Sprintf("defaults to `%s`", def.Quote())
} else if !s.IsRequired() {
optionality = "optional"
} else if len(variable.Relationships[variable.DependsOn](s)) != 0 {
optionality = "conditional"
}
for _, v := range r.Variables {
s := v.Spec()
name := r.linkToSpec(s)
optionality := "required"

if s.IsDeprecated() {
name = "~~" + name + "~~"
optionality = "optional, deprecated"
} else if def, ok := s.Default(); ok {
optionality = fmt.Sprintf("defaults to `%s`", def.Quote())
} else if !s.IsRequired() {
optionality = "optional"
} else if len(variable.Relationships[variable.DependsOn](s)) != 0 {
optionality = "conditional"
}

if hasImportColumn {
reg := ""
if !v.Registry.IsDefault {
reg = r.linkToRegistry(v.Registry)
}
t.AddRow(name, optionality, s.Description(), reg)
} else {
t.AddRow(name, optionality, s.Description())
}

t.WriteTo(r.Output)
}

t.WriteTo(r.Output)
}
Loading

0 comments on commit bdae1fd

Please sign in to comment.