diff --git a/doc/decisions.md b/doc/decisions.md index f8231bda..ef99e930 100644 --- a/doc/decisions.md +++ b/doc/decisions.md @@ -23,7 +23,9 @@ Repo Management Tool, or "marmot" for short. shell commands for a category of repositories as if they are all part of a single unit. - Store meta data about categories externally, instead of in the Git repositories themselves. -## 01: Target Z Shell +## 01: ~~Target Z Shell~~ + +Superseded by: [Implement in Go](#06-implement-in-go). Implement marmot in *nix tools that are widely-available on the platforms I use - e.g. MacOS, Linux, and Windows Subsystem for Linux. Writing it in Z Shell can make it easier to try new ideas, while @@ -50,7 +52,7 @@ back, or even share with teammates. ### Decisions - Store meta data in JSON files. -- Use tools like `jq` and `jo` to query and construct JSON data from marmot. +- ~~Use tools like `jq` and `jo` to query and construct JSON data from marmot.~~ - Store meta data in its own Git repository. ## 03: Directory Structure in the Meta Repo @@ -82,7 +84,9 @@ over what changes merit what kind of version bump. - Minor version: Increment when adding a new feature (e.g. a command or sub-command). - Patch version: Increment when refactoring to prepare for another feature. -## 05: Apply Single Responsibility Principle to scripts +## 05: ~~Apply Single Responsibility Principle to scripts~~ + +Superseded by: [Implement in Go](#06-implement-in-go). Scripts are getting more complex, leading to duplication of concepts and algorithms. Applying the Single Responsibility Principle (SRP) can help manage complexity and avoid unnecessary duplication. @@ -119,3 +123,159 @@ Source: to `source` dependencies and transitive dependencies. - This is approach is intended to avoid any complexities in the same code being sourced twice. I have no idea what could happen then, and I'd rather not have to find out. + +## 06: Implement in Go + +Supersedes: [Target Z Shell](#01-target-z-shell). + +Compartmentalizing and organizing scripts helped with maintenance and extension, but it still became +difficult to split machine- and repository-specific data into separate files. Use of an external +data migration script provided a limited means to detect bugs by including some semi-formal test +automation, but it relied heavily upon the development platform; e.g. it used real Git repositories +on specific paths. + +These factors led to some thinking about which language could replace the shell scripts. It would +need to be capable of targeting the same platforms, while offering a better means to structure data, +look up references, and refactor call sites. It would also need robust tools for test automation +and for creating Command Line Interfaces. Go offers all of those, while currently being a bit +easier to deploy to end users than Python or Ruby. Go also has potentially-compelling libraries +such as [`bubbletea`](https://github.com/charmbracelet/bubbletea), which raises the possibility of +making `marmot` more interactive and easier to use. + +### Decision + +Sprout a new codebase written in Go, until it has enough features to replace the ZSH version. + +## 07: Go package structure + +Developers will need a safe and effective way to add new entities and CLI commands, in order to add +new features. Distinguishing core entities (e.g. repositories and categories) and behavior (e.g. +categorizing Git repositories) from implementation details (e.g. interaction with the file system) +minimizes the amount of existing code that has to be modified in order to add new entities. + +### Decisions + +Structure Go code along these dimensions: + +- Put all code in one repository. Use Go packages to distinguish the parts. +- Create `core` packages like `corerepository` for basic entities, data structures, and interfaces. +- Create `use` packages like `userepository` for operations upon each context. +- Create `svc` packages like `svcfs` for service implementations, such as using the file system. +- Create `main` package(s) like `mainfactory` to create dependencies and wire everything together. + +This leads to the following dependencies (compile-time; runtime dependencies are dashed lines) among +top-level packages: + +```mermaid +graph LR + +%% Core and dependencies +subgraph FunctionalCore [Functional Core] + core(core
Data structures
Service interfaces) + svc(svc
Services) + use(use
Use Cases) + + svc -->|CRUD
implement| core + use -->|CRUD| core + use -.->|runtime| svc +end + +%% Main program +subgraph ImperativeShell [Imperative Shell] + cmd(cmd
CLI) + mainfactory(mainfactory
Factories) + marmot(marmot
Executable) + + cmd -->|CRUD| core + cmd --> use + %%mainfactory -->|create| cmd + mainfactory -->|create| use + mainfactory -->|create| svc + marmot --> cmd + marmot --> mainfactory +end +``` + +Note: The code in the "functional core" is not always necessarily written in a functional style, +although that's an idea worth considering. + +## 08: Go test strategy + +As described in [Implement in Go](#06-implement-in-go), using a script-based architecture did not +offer a sufficiently-granular approach to testing. Prior experiences with formal testing tools–i.e. +[`bats`](https://github.com/sstephenson/bats)–proved impractical for team sizes greater than one. + +Another factor involved in [Go package structure](#07-go-package-structure) relates to test +automation: Separating packages by bounded context also offers a practical means to distinguish test +automation that is more highly rewarding from that which is somewhat less rewarding. In other +words, tests on invariants and core logic tend to be easier to write and survive refactoring, while +tests on wiring and implementation details tend to be harder to write and are more readily thrown +out during refactoring. + +### Decisions + +- Focus test automation on core logic; e.g. "test from the middle". +- For small- to medium-sized tests of regular code: + - **Sources**: Co-locate with production code and package as `_test`, according to Go conventions. + - **Support code**: Add `testsupport` packages as necessary. + - **Test doubles**: Create additional `*mock` packages as necessary, such as `corerepositorymock`. + - **Tools**: Use `ginkgo` to clearly describe behavior. +- For medium- to large-sized tests of user-facing features: + - **Sources**: place sources in separate `cuke*` packages. + - **Support code**: Add `cukesupport` as necessary. + - **Tools**: Use `godog` to clearly describe features in Gherkin. + +### Control Flow + +```mermaid +graph TB + +subgraph MarmotCore [Functional Core] + direction LR + core(core
Data structures) + svc(svc
Services) + use(use
Use Cases) + + svc -.-> core + use -.-> core + use -.-> svc +end + +subgraph Tests + subgraph SmallTests [Small Tests] + direction LR + ginkgotests(_test
Ginkgo tests) + ginkgomocks(*mock
Test doubles) + ginkgosupport(testsupport*
Test support) + + ginkgotests -.-> ginkgomocks + ginkgotests -.-> ginkgosupport + end + + subgraph LargeTests [Large Tests] + direction LR + godogfeatures(cukefeature
godog scenarios) + godogsteps(cukesteps
Step definitions) + godogsupport(cukesupport
Helpers
Hooks) + + godogfeatures -.-> godogsupport + godogfeatures -.-> godogsteps + godogsteps -.-> godogsupport + end +end + +LargeTests -->|validate| MarmotCore +SmallTests -->|verify| svc +SmallTests -->|verify| use +``` + +### Not Tested + +```mermaid +graph LR + +subgraph ImperativeShell [Production Code: Imperative Shell] + cmd(cmd
CLI) + marmot(marmot
Executable) +end +``` diff --git a/src/go/Makefile b/src/go/Makefile index 3795cf1e..7b2df7c4 100644 --- a/src/go/Makefile +++ b/src/go/Makefile @@ -55,8 +55,9 @@ ginkgo-watch: install-tools: go install github.com/go-delve/delve/cmd/dlv@latest go install github.com/onsi/ginkgo/v2/ginkgo - go install mvdan.cc/gofumpt@latest go install github.com/spf13/cobra-cli@latest + go install golang.org/x/tools/cmd/godoc@latest + go install mvdan.cc/gofumpt@latest .PHONY: run run: diff --git a/src/go/cmd/config.go b/src/go/cmd/config.go new file mode 100644 index 00000000..bd8b17cd --- /dev/null +++ b/src/go/cmd/config.go @@ -0,0 +1,76 @@ +package cmd + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/kkrull/marmot/use" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +/* Configuration */ + +func AddFlags(cobraCmd *cobra.Command) error { + addDebugFlag(cobraCmd) + if metaRepoErr := addMetaRepoFlag(cobraCmd); metaRepoErr != nil { + return metaRepoErr + } else { + return nil + } +} + +func addDebugFlag(cobraCmd *cobra.Command) { + cobraCmd.PersistentFlags().Bool("debug", false, "print CLI debugging information") + cobraCmd.PersistentFlags().Lookup("debug").Hidden = true +} + +func addMetaRepoFlag(cobraCmd *cobra.Command) error { + if homeDir, homeErr := os.UserHomeDir(); homeErr != nil { + return fmt.Errorf("failed to locate home directory; %w", homeErr) + } else { + cobraCmd.PersistentFlags().String( + "meta-repo", + filepath.Join(homeDir, "meta"), + "Meta repo to use", + ) + return nil + } +} + +/* Use */ + +func ParseFlags(cobraCmd *cobra.Command) (*Config, error) { + flags := cobraCmd.Flags() + if debug, debugErr := flags.GetBool("debug"); debugErr != nil { + return nil, debugErr + } else if metaRepoPath, metaRepoPathErr := flags.GetString("meta-repo"); metaRepoPathErr != nil { + return nil, metaRepoPathErr + } else { + return &Config{ + AppFactory: *use.NewAppFactory(), + Debug: debug, + MetaRepoPath: metaRepoPath, + flagSet: flags, + }, nil + } +} + +type Config struct { + AppFactory use.AppFactory + Debug bool + MetaRepoPath string + flagSet *pflag.FlagSet +} + +func (config Config) PrintDebug(writer io.Writer) { + fmt.Fprintf(writer, "Flags:\n") + + debugFlag := config.flagSet.Lookup("debug") + fmt.Fprintf(writer, "- debug [%v]: %v\n", debugFlag.DefValue, debugFlag.Value) + + metaRepoFlag := config.flagSet.Lookup("meta-repo") + fmt.Fprintf(writer, "- meta-repo [%v]: %v\n", metaRepoFlag.DefValue, metaRepoFlag.Value) +} diff --git a/src/go/cmd/root_command.go b/src/go/cmd/root_command.go index 2fe5fbca..d7de64e3 100644 --- a/src/go/cmd/root_command.go +++ b/src/go/cmd/root_command.go @@ -1,37 +1,39 @@ package cmd import ( - "fmt" "io" "github.com/spf13/cobra" ) -var ( - debugFlag *bool - rootCmd = &cobra.Command{ - Long: "marmot manages a Meta Repository that organizes content in other (Git) repositories.", - RunE: func(cmd *cobra.Command, args []string) error { - if *debugFlag { - printDebug() - return nil - } else if len(args) == 0 { - return cmd.Help() - } else { - return nil - } - }, - Short: "Meta Repo Management Tool", - Use: "marmot [--help|--version]", +// Configure the root command with the given I/O and version identifier, then return for use. +func NewRootCommand(stdout io.Writer, stderr io.Writer, version string) (*cobra.Command, error) { + rootCmd := &cobra.Command{ + Long: "marmot manages a Meta Repository that organizes content in other (Git) repositories.", + RunE: runRoot, + Short: "Meta Repo Management Tool", + Use: "marmot", + Version: version, } -) -// Configure the root command with the given I/O and version identifier, then return for use. -func NewRootCommand(stdout io.Writer, stderr io.Writer, version string) *cobra.Command { + AddFlags(rootCmd) + addGroups(rootCmd) rootCmd.SetOut(stdout) rootCmd.SetErr(stderr) - rootCmd.Version = version - return rootCmd + return rootCmd, nil +} + +func runRoot(cobraCmd *cobra.Command, args []string) error { + if config, parseErr := ParseFlags(cobraCmd); parseErr != nil { + return parseErr + } else if config.Debug { + config.PrintDebug(cobraCmd.OutOrStdout()) + return nil + } else if len(args) == 0 { + return cobraCmd.Help() + } else { + return nil + } } /* Child commands */ @@ -40,29 +42,11 @@ const ( metaRepoGroup = "meta-repo" ) -func AddMetaRepoCommand(child *cobra.Command) { - child.GroupID = metaRepoGroup - rootCmd.AddCommand(child) -} - -/* Configuration */ - -func init() { - initFlags() - initGroups() -} - -func initFlags() { - debugFlag = rootCmd.PersistentFlags().Bool("debug", false, "print CLI debugging information") - rootCmd.PersistentFlags().Lookup("debug").Hidden = true +func addGroups(cobraCmd *cobra.Command) { + cobraCmd.AddGroup(&cobra.Group{ID: metaRepoGroup, Title: "Meta Repo Commands"}) } -func initGroups() { - rootCmd.AddGroup(&cobra.Group{ID: metaRepoGroup, Title: "Meta Repo Commands"}) -} - -/* Pseudo-commands */ - -func printDebug() { - fmt.Printf("--debug: %v\n", *debugFlag) +func AddMetaRepoCommand(parent *cobra.Command, child cobra.Command) { + child.GroupID = metaRepoGroup + parent.AddCommand(&child) } diff --git a/src/go/cmdinit/init_command.go b/src/go/cmdinit/init_command.go index 868a6c19..f6576833 100644 --- a/src/go/cmdinit/init_command.go +++ b/src/go/cmdinit/init_command.go @@ -3,35 +3,49 @@ package cmdinit import ( "fmt" - "github.com/kkrull/marmot/usemetarepo" + "github.com/kkrull/marmot/cmd" "github.com/spf13/cobra" ) -// Construct a CLI command to initialize a meta repo at the specified path -func NewInitCommand(initApp *usemetarepo.InitCommand, metaRepoHome string) *initCommand { - return &initCommand{ - initApp: initApp, - path: metaRepoHome, - } +// Construct a CLI command to initialize a meta repo +func NewInitCommand() *initCommand { + return &initCommand{} } -type initCommand struct { - initApp *usemetarepo.InitCommand - path string +type initCommand struct{} + +func (cliCmd *initCommand) RegisterWithCobra(parentCmd *cobra.Command) { + cobraCmd := cliCmd.toCobraCommand() + cmd.AddMetaRepoCommand(parentCmd, *cobraCmd) } -func (cliCmd *initCommand) ToCobraCommand() *cobra.Command { +func (cliCmd *initCommand) toCobraCommand() *cobra.Command { return &cobra.Command{ - Long: "Initialize a new Meta Repo in the configured directory, if none is already present.", - RunE: func(cmd *cobra.Command, args []string) error { - if runErr := cliCmd.initApp.Run(cliCmd.path); runErr != nil { - return fmt.Errorf("failed to initialize meta repo at %s; %w", cliCmd.path, runErr) - } else { - fmt.Printf("Initialized meta repo at %s\n", cliCmd.path) - return nil - } - }, - Short: "initialize a meta repo", + Args: cobra.NoArgs, + Long: "Initialize a new Meta Repo, if none is already present.", + RunE: runInit, + Short: "Initialize a meta repo", Use: "init", } } + +func runInit(cobraCmd *cobra.Command, _args []string) error { + if config, parseErr := cmd.ParseFlags(cobraCmd); parseErr != nil { + return parseErr + } else if config.Debug { + config.PrintDebug(cobraCmd.OutOrStdout()) + return nil + } else { + return runInitAppCmd(cobraCmd, config) + } +} + +func runInitAppCmd(cobraCmd *cobra.Command, config *cmd.Config) error { + initAppCmd := config.AppFactory.InitCommand() + if runErr := initAppCmd.Run(config.MetaRepoPath); runErr != nil { + return runErr + } else { + fmt.Fprintf(cobraCmd.OutOrStdout(), "Initialized meta repo at %s\n", config.MetaRepoPath) + return nil + } +} diff --git a/src/go/cukestep/meta_repo_steps.go b/src/go/cukestep/meta_repo_steps.go index 71604bb7..5505a60a 100644 --- a/src/go/cukestep/meta_repo_steps.go +++ b/src/go/cukestep/meta_repo_steps.go @@ -5,7 +5,7 @@ import ( "github.com/cucumber/godog" support "github.com/kkrull/marmot/cukesupport" - main "github.com/kkrull/marmot/mainfactory" + "github.com/kkrull/marmot/use" ) // Add step definitions to manage the life cycle of a meta repo. @@ -17,10 +17,9 @@ func AddMetaRepoSteps(ctx *godog.ScenarioContext) { /* Steps */ func initNewMetaRepo(ctx *godog.ScenarioContext) error { - factory := &main.AppFactory{} - if initCmd, factoryErr := factory.InitCommand(); factoryErr != nil { - return fmt.Errorf("meta_repo_steps: failed to initialize; %w", factoryErr) - } else if thatMetaRepo, initErr := support.InitThatMetaRepo(ctx); initErr != nil { + factory := use.NewAppFactory() + initCmd := factory.InitCommand() + if thatMetaRepo, initErr := support.InitThatMetaRepo(ctx); initErr != nil { return fmt.Errorf("meta_repo_steps: failed to initialize path to meta repo; %w", initErr) } else if runErr := initCmd.Run(thatMetaRepo); runErr != nil { return fmt.Errorf("meta_repo_steps: failed to initialize repository; %w", runErr) diff --git a/src/go/cukestep/repository_steps.go b/src/go/cukestep/repository_steps.go index 10006e5e..96d9ff53 100644 --- a/src/go/cukestep/repository_steps.go +++ b/src/go/cukestep/repository_steps.go @@ -8,7 +8,8 @@ import ( "github.com/cucumber/godog" core "github.com/kkrull/marmot/corerepository" support "github.com/kkrull/marmot/cukesupport" - main "github.com/kkrull/marmot/mainfactory" + "github.com/kkrull/marmot/svcfs" + "github.com/kkrull/marmot/use" . "github.com/onsi/gomega" ) @@ -77,12 +78,11 @@ func registerRemote() error { /* Configuration */ -func factoryForThatMetaRepo() (*main.AppFactory, error) { +func factoryForThatMetaRepo() (*use.AppFactory, error) { if metaRepoPath, pathErr := support.ThatMetaRepo(); pathErr != nil { return nil, fmt.Errorf("repository_steps: failed to configure; %w", pathErr) } else { - factory := &main.AppFactory{} - factory.ForLocalMetaRepo(metaRepoPath) - return factory, nil + factory := use.NewAppFactory() + return factory.WithRepositorySource(svcfs.NewJsonMetaRepo(metaRepoPath)), nil } } diff --git a/src/go/main.go b/src/go/main.go index b94dd41f..fb1407e5 100644 --- a/src/go/main.go +++ b/src/go/main.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "io" "os" @@ -15,15 +14,12 @@ var ( func main() { if err := doMain(); err != nil { - fmt.Fprintln(stderr, err.Error()) os.Exit(1) } } func doMain() error { - if appFactory, appErr := mainfactory.DefaultAppFactory(); appErr != nil { - return appErr - } else if cliFactory, cliErr := newCliFactory(appFactory); cliErr != nil { + if cliFactory, cliErr := newCliFactory(); cliErr != nil { return cliErr } else if rootCmd, buildErr := cliFactory.CommandTree(); buildErr != nil { return buildErr @@ -34,9 +30,9 @@ func doMain() error { } } -func newCliFactory(appFactory *mainfactory.AppFactory) (*mainfactory.CliFactory, error) { +func newCliFactory() (*mainfactory.CliFactory, error) { return mainfactory. - NewCliFactory(appFactory). + NewCliFactory(). WithStdIO(stdout, stderr). ForExecutable() } diff --git a/src/go/mainfactory/app_factory.go b/src/go/mainfactory/app_factory.go deleted file mode 100644 index aadc424f..00000000 --- a/src/go/mainfactory/app_factory.go +++ /dev/null @@ -1,82 +0,0 @@ -package mainfactory - -import ( - "errors" - "fmt" - "os" - "path/filepath" - - "github.com/kkrull/marmot/coremetarepo" - "github.com/kkrull/marmot/corerepository" - "github.com/kkrull/marmot/svcfs" - metarepo "github.com/kkrull/marmot/usemetarepo" - repository "github.com/kkrull/marmot/userepository" -) - -func DefaultAppFactory() (*AppFactory, error) { - if metaRepoPath, pathErr := defaultMetaRepoPath(); pathErr != nil { - return nil, pathErr - } else { - return newAppFactory().ForLocalMetaRepo(metaRepoPath), nil - } -} - -func newAppFactory() *AppFactory { - return &AppFactory{} -} - -func defaultMetaRepoPath() (string, error) { - if homeDir, homeErr := os.UserHomeDir(); homeErr != nil { - return "", fmt.Errorf("failed to locate home directory; %w", homeErr) - } else { - return filepath.Join(homeDir, "meta"), nil - } -} - -// Constructs application commands and queries with configurable services. -type AppFactory struct { - MetaDataAdmin coremetarepo.MetaDataAdmin - metaRepoPath string - RepositorySource corerepository.RepositorySource -} - -// Configure a local, file-based meta repo at the specified path -func (factory *AppFactory) ForLocalMetaRepo(metaRepoPath string) *AppFactory { - factory.metaRepoPath = metaRepoPath - factory.RepositorySource = svcfs.NewJsonMetaRepo(metaRepoPath) - return factory -} - -func (factory *AppFactory) MetaRepoPath() string { - return factory.metaRepoPath -} - -/* Administration */ - -func (factory *AppFactory) InitCommand() (*metarepo.InitCommand, error) { - if factory.MetaDataAdmin == nil { - factory.MetaDataAdmin = svcfs.NewJsonMetaRepoAdmin() - } - - return &metarepo.InitCommand{MetaDataAdmin: factory.MetaDataAdmin}, nil -} - -/* Repositories */ - -func (factory *AppFactory) ListRemoteRepositoriesQuery() (repository.ListRemoteRepositoriesQuery, error) { - if factory.RepositorySource == nil { - return nil, errors.New("CommandFactory: missing RepositorySource") - } - - return factory.RepositorySource.ListRemote, nil -} - -func (factory *AppFactory) RegisterRemoteRepositoriesCommand() ( - *repository.RegisterRemoteRepositoriesCommand, error, -) { - if factory.RepositorySource == nil { - return nil, errors.New("CommandFactory: missing RepositorySource") - } - - return &repository.RegisterRemoteRepositoriesCommand{Source: factory.RepositorySource}, nil -} diff --git a/src/go/mainfactory/cli_factory.go b/src/go/mainfactory/cli_factory.go index 8bd86ced..367af3d2 100644 --- a/src/go/mainfactory/cli_factory.go +++ b/src/go/mainfactory/cli_factory.go @@ -13,16 +13,15 @@ import ( ) // Construct a factory to create CLI commands. -func NewCliFactory(appFactory *AppFactory) *CliFactory { - return &CliFactory{appFactory: appFactory} +func NewCliFactory() *CliFactory { + return &CliFactory{} } // Creates commands for the Command Line Interface (CLI). type CliFactory struct { - appFactory *AppFactory - stdout io.Writer - stderr io.Writer - version string + stdout io.Writer + stderr io.Writer + version string } func (cliFactory *CliFactory) WithStdIO(stdout io.Writer, stderr io.Writer) *CliFactory { @@ -34,12 +33,12 @@ func (cliFactory *CliFactory) WithStdIO(stdout io.Writer, stderr io.Writer) *Cli /* Factory methods */ func (cliFactory *CliFactory) CommandTree() (*cobra.Command, error) { - rootCmd := cmd.NewRootCommand(cliFactory.stdout, cliFactory.stderr, cliFactory.version) - if initAppCmd, appFactoryErr := cliFactory.appFactory.InitCommand(); appFactoryErr != nil { - return nil, appFactoryErr + if rootCmd, rootCmdErr := cmd.NewRootCommand(cliFactory.stdout, cliFactory.stderr, cliFactory.version); rootCmdErr != nil { + return nil, rootCmdErr } else { - initCliCmd := cmdinit.NewInitCommand(initAppCmd, cliFactory.appFactory.MetaRepoPath()) - cmd.AddMetaRepoCommand(initCliCmd.ToCobraCommand()) + cmdinit. + NewInitCommand(). + RegisterWithCobra(rootCmd) return rootCmd, nil } } diff --git a/src/go/use/app_factory.go b/src/go/use/app_factory.go new file mode 100644 index 00000000..39014bc3 --- /dev/null +++ b/src/go/use/app_factory.go @@ -0,0 +1,60 @@ +package use + +import ( + "errors" + + "github.com/kkrull/marmot/coremetarepo" + "github.com/kkrull/marmot/corerepository" + "github.com/kkrull/marmot/svcfs" + metarepo "github.com/kkrull/marmot/usemetarepo" + repository "github.com/kkrull/marmot/userepository" +) + +func NewAppFactory() *AppFactory { + return &AppFactory{MetaDataAdmin: svcfs.NewJsonMetaRepoAdmin()} +} + +// Constructs application commands and queries with configurable services. +type AppFactory struct { + MetaDataAdmin coremetarepo.MetaDataAdmin + RepositorySource corerepository.RepositorySource +} + +func (factory *AppFactory) WithRepositorySource(repositorySource corerepository.RepositorySource) *AppFactory { + factory.RepositorySource = repositorySource + return factory +} + +/* Administration */ + +func (factory *AppFactory) InitCommand() *metarepo.InitCommand { + return &metarepo.InitCommand{MetaDataAdmin: factory.MetaDataAdmin} +} + +/* Repositories */ + +func (factory *AppFactory) ListRemoteRepositoriesQuery() (repository.ListRemoteRepositoriesQuery, error) { + if repositorySource, err := factory.repositorySource(); err != nil { + return nil, err + } else { + return repositorySource.ListRemote, nil + } +} + +func (factory *AppFactory) RegisterRemoteRepositoriesCommand() ( + *repository.RegisterRemoteRepositoriesCommand, error, +) { + if repositorySource, err := factory.repositorySource(); err != nil { + return nil, err + } else { + return &repository.RegisterRemoteRepositoriesCommand{Source: repositorySource}, nil + } +} + +func (factory *AppFactory) repositorySource() (corerepository.RepositorySource, error) { + if factory.RepositorySource == nil { + return nil, errors.New("AppFactory: missing RepositorySource") + } else { + return factory.RepositorySource, nil + } +} diff --git a/src/go/usemetarepo/init_command_test.go b/src/go/usemetarepo/init_command_test.go index ca2d8a02..8fec2f2c 100644 --- a/src/go/usemetarepo/init_command_test.go +++ b/src/go/usemetarepo/init_command_test.go @@ -4,8 +4,8 @@ import ( "errors" mock "github.com/kkrull/marmot/coremetarepomock" - main "github.com/kkrull/marmot/mainfactory" - use "github.com/kkrull/marmot/usemetarepo" + "github.com/kkrull/marmot/use" + "github.com/kkrull/marmot/usemetarepo" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -13,31 +13,28 @@ import ( var _ = Describe("InitCommand", func() { var ( - subject *use.InitCommand - factory *main.AppFactory + subject *usemetarepo.InitCommand metaDataAdmin *mock.MetaDataAdmin ) BeforeEach(func() { metaDataAdmin = &mock.MetaDataAdmin{} - factory = &main.AppFactory{MetaDataAdmin: metaDataAdmin} + factory := &use.AppFactory{MetaDataAdmin: metaDataAdmin} + subject = factory.InitCommand() }) Describe("#Run", func() { It("initializes the given meta data source", func() { - subject, _ = factory.InitCommand() _ = subject.Run("/tmp") metaDataAdmin.CreateExpected("/tmp") }) It("returns nil, when everything succeeds", func() { - subject, _ = factory.InitCommand() Expect(subject.Run("/tmp")).To(BeNil()) }) It("returns an error when failing to initialize the meta data source", func() { metaDataAdmin.CreateError = errors.New("bang!") - subject, _ = factory.InitCommand() Expect(subject.Run("/tmp")).To(MatchError("bang!")) }) }) diff --git a/src/go/userepository/register_remote_repositories_command_test.go b/src/go/userepository/register_remote_repositories_command_test.go index 784c306d..2c090d6c 100644 --- a/src/go/userepository/register_remote_repositories_command_test.go +++ b/src/go/userepository/register_remote_repositories_command_test.go @@ -4,9 +4,9 @@ import ( "net/url" mock "github.com/kkrull/marmot/corerepositorymock" - main "github.com/kkrull/marmot/mainfactory" testdata "github.com/kkrull/marmot/testsupportdata" expect "github.com/kkrull/marmot/testsupportexpect" + "github.com/kkrull/marmot/use" "github.com/kkrull/marmot/userepository" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -14,14 +14,14 @@ import ( var _ = Describe("RegisterRepositoriesCommand", func() { var ( - factory *main.AppFactory + factory *use.AppFactory source *mock.RepositorySource subject *userepository.RegisterRemoteRepositoriesCommand ) BeforeEach(func() { source = mock.NewRepositorySource() - factory = &main.AppFactory{RepositorySource: source} + factory = use.NewAppFactory().WithRepositorySource(source) subject = expect.NoError(factory.RegisterRemoteRepositoriesCommand()) })