diff --git a/.golangci.yml b/.golangci.yml index bcd59a4..6419f13 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,8 +18,12 @@ linters-settings: - ifElseChain - singleCaseSwitch - commentFormatting + importas: + no-unaliased: true + alias: + - pkg: github.com/foomo/posh/internal/(\w+) + alias: int$1 linters: - enable: # Enabled by default linters: - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false] diff --git a/.goreleaser.yml b/.goreleaser.yml index fe5c9e6..b68f7d5 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -5,7 +5,7 @@ builds: - binary: posh main: ./main.go env: - - CGO_ENABLED=1 + - CGO_ENABLED=0 ldflags: - -s -w - -X github.com/foomo/posh/internal/version.Version={{.Version}} diff --git a/cmd/brew.go b/cmd/brew.go index d7234c1..e54e978 100644 --- a/cmd/brew.go +++ b/cmd/brew.go @@ -2,7 +2,6 @@ package cmd import ( intconfig "github.com/foomo/posh/internal/config" - intplugin "github.com/foomo/posh/internal/plugin" "github.com/foomo/posh/pkg/config" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -31,7 +30,7 @@ var brewCmd = &cobra.Command{ } cfg.Dry = brewCmdFlagDry - plg, err := intplugin.Load(cmd.Context(), l) + plg, err := pluginProvider(l) if err != nil { return err } @@ -39,8 +38,3 @@ var brewCmd = &cobra.Command{ return plg.Brew(cmd.Context(), cfg) }, } - -func init() { - rootCmd.AddCommand(brewCmd) - brewCmd.Flags().BoolVar(&brewCmdFlagDry, "dry", false, "don't execute scripts") -} diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..2576200 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,68 @@ +package cmd + +import ( + "context" + "os" + "os/signal" + + intenv "github.com/foomo/posh/internal/env" + intlog "github.com/foomo/posh/internal/log" + "github.com/foomo/posh/pkg/plugin" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func Init(provider plugin.Provider) { + pluginProvider = provider + cobra.OnInitialize(func() { + l = intlog.Init(flagLevel, flagNoColor) + l.Must(intenv.Init()) + }) + rootCmd.PersistentFlags().BoolVar(&flagNoColor, "no-color", false, "disabled colors (default is false)") + rootCmd.PersistentFlags().StringVar(&flagLevel, "level", "info", "set log level (default is warn)") + rootCmd.AddCommand( + configCmd, + versionCmd, + ) + + if provider != nil { + rootCmd.AddCommand( + brewCmd, + execCmd, + promptCmd, + requireCmd, + ) + brewCmd.Flags().BoolVar(&brewCmdFlagDry, "dry", false, "don't execute scripts") + } +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + code := 0 + + // handle interrupt + osInterrupt := make(chan os.Signal, 1) + signal.Notify(osInterrupt, os.Interrupt) + ctx, cancel := context.WithCancel(context.Background()) + + // handle defer + defer func() { + signal.Stop(osInterrupt) + cancel() + os.Exit(code) + }() + + go func() { + <-osInterrupt + l.Debug("received interrupt") + cancel() + }() + + if err := rootCmd.ExecuteContext(ctx); errors.Is(err, context.Canceled) { + l.Warn(err.Error()) + } else if err != nil { + l.Error(err.Error()) + code = 1 + } +} diff --git a/cmd/config.go b/cmd/config.go index 6e322f4..f97f7fa 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -23,7 +23,3 @@ var configCmd = &cobra.Command{ return nil }, } - -func init() { - rootCmd.AddCommand(configCmd) -} diff --git a/cmd/execute.go b/cmd/execute.go index ce5b6fe..76beee9 100644 --- a/cmd/execute.go +++ b/cmd/execute.go @@ -2,7 +2,6 @@ package cmd import ( intconfig "github.com/foomo/posh/internal/config" - intplugin "github.com/foomo/posh/internal/plugin" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -25,7 +24,7 @@ var execCmd = &cobra.Command{ return errors.New("missing [cmd] argument") } - plg, err := intplugin.Load(cmd.Context(), l) + plg, err := pluginProvider(l) if err != nil { return err } @@ -33,17 +32,3 @@ var execCmd = &cobra.Command{ return plg.Execute(cmd.Context(), args) }, } - -func init() { - rootCmd.AddCommand(execCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // execCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // execCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} diff --git a/cmd/prompt.go b/cmd/prompt.go index 5c0870b..1036dbb 100644 --- a/cmd/prompt.go +++ b/cmd/prompt.go @@ -2,7 +2,6 @@ package cmd import ( intconfig "github.com/foomo/posh/internal/config" - intplugin "github.com/foomo/posh/internal/plugin" "github.com/foomo/posh/pkg/config" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -26,7 +25,7 @@ var promptCmd = &cobra.Command{ return err } - plg, err := intplugin.Load(cmd.Context(), l) + plg, err := pluginProvider(l) if err != nil { return err } @@ -34,7 +33,3 @@ var promptCmd = &cobra.Command{ return plg.Prompt(cmd.Context(), cfg) }, } - -func init() { - rootCmd.AddCommand(promptCmd) -} diff --git a/cmd/require.go b/cmd/require.go index d7f1ea1..b67b48a 100644 --- a/cmd/require.go +++ b/cmd/require.go @@ -2,7 +2,6 @@ package cmd import ( intconfig "github.com/foomo/posh/internal/config" - intplugin "github.com/foomo/posh/internal/plugin" "github.com/foomo/posh/pkg/config" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -26,7 +25,7 @@ var requireCmd = &cobra.Command{ return err } - plg, err := intplugin.Load(cmd.Context(), l) + plg, err := pluginProvider(l) if err != nil { return err } @@ -34,7 +33,3 @@ var requireCmd = &cobra.Command{ return plg.Require(cmd.Context(), cfg) }, } - -func init() { - rootCmd.AddCommand(requireCmd) -} diff --git a/cmd/root.go b/cmd/root.go index fc79788..fae5d12 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,21 +1,16 @@ package cmd import ( - "context" - "os" - "os/signal" - - "github.com/foomo/posh/internal/env" - intlog "github.com/foomo/posh/internal/log" "github.com/foomo/posh/pkg/log" - "github.com/pkg/errors" + "github.com/foomo/posh/pkg/plugin" "github.com/spf13/cobra" ) var ( - l log.Logger - flagLevel string - flagNoColor bool + l log.Logger + flagLevel string + flagNoColor bool + pluginProvider plugin.Provider ) // rootCmd represents the base command when called without any subcommands @@ -23,52 +18,3 @@ var rootCmd = &cobra.Command{ Use: "posh", Short: "Project Oriented Shell (posh)", } - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - code := 0 - - // handle interrupt - osInterrupt := make(chan os.Signal, 1) - signal.Notify(osInterrupt, os.Interrupt) - ctx, cancel := context.WithCancel(context.Background()) - - // handle defer - defer func() { - signal.Stop(osInterrupt) - cancel() - os.Exit(code) - }() - - go func() { - <-osInterrupt - l.Debug("received interrupt") - cancel() - }() - - if err := rootCmd.ExecuteContext(ctx); errors.Is(err, context.Canceled) { - l.Warn(err.Error()) - } else if err != nil { - l.Error(err.Error()) - code = 1 - } -} - -func init() { - cobra.OnInitialize(initialize) - rootCmd.PersistentFlags().BoolVar(&flagNoColor, "no-color", false, "disabled colors (default is false)") - rootCmd.PersistentFlags().StringVar(&flagLevel, "level", "info", "set log level (default is warn)") -} - -// initialize reads in config file and ENV variables if set. -func initialize() { - var err error - - // init logger - l, err = intlog.Init(flagLevel, flagNoColor) - cobra.CheckErr(err) - - // init env - l.Must(env.Init()) -} diff --git a/cmd/version.go b/cmd/version.go index 1bd235a..4c0ca7c 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -20,13 +20,9 @@ var versionCmd = &cobra.Command{ buildTime = time.Unix(value, 0).String() } if l.IsLevel(log.LevelDebug) { - l.Printf("v%s, Commit: %s, BuildTime: %s", intversion.Version, intversion.CommitHash, buildTime) + l.Printf("Version: %s\nCommit: %s\nBuildTime: %s", intversion.Version, intversion.CommitHash, buildTime) } else { - l.Printf("v%s", intversion.Version) + l.Printf("%s", intversion.Version) } }, } - -func init() { - rootCmd.AddCommand(versionCmd) -} diff --git a/embed/scaffold/init/$.posh/main.go.gotext b/embed/scaffold/init/$.posh/main.go.gotext new file mode 100644 index 0000000..51741d7 --- /dev/null +++ b/embed/scaffold/init/$.posh/main.go.gotext @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/foomo/posh-sandbox/posh/pkg" + "github.com/foomo/posh/cmd" + _ "github.com/foomo/posh/pkg/plugin" +) + +func init() { + cmd.Init(pkg.New) +} + +func main() { + cmd.Execute() +} diff --git a/embed/scaffold/init/$.posh/plugin.go.gotext b/embed/scaffold/init/$.posh/pkg/plugin.go.gotext similarity index 97% rename from embed/scaffold/init/$.posh/plugin.go.gotext rename to embed/scaffold/init/$.posh/pkg/plugin.go.gotext index ec84805..5b7f50d 100644 --- a/embed/scaffold/init/$.posh/plugin.go.gotext +++ b/embed/scaffold/init/$.posh/pkg/plugin.go.gotext @@ -1,4 +1,4 @@ -package main +package pkg import ( "context" @@ -29,7 +29,7 @@ type Plugin struct { // ~ Constructor // ------------------------------------------------------------------------------------------------ -func New(l log.Logger) (plugin.Plugin, error) { //nolint:unparam +func New(l log.Logger) (plugin.Plugin, error) { inst := &Plugin{ l: l, commands: command.Commands{}, diff --git a/embed/scaffold/init/Makefile b/embed/scaffold/init/Makefile index d2638cb..7536c48 100644 --- a/embed/scaffold/init/Makefile +++ b/embed/scaffold/init/Makefile @@ -1,24 +1,65 @@ +-include .makerc .DEFAULT_GOAL:=help -## === Tasks === +# --- .makerc ----------------------------------------------------------------- +# +# level=debug + +# --- Config ----------------------------------------------------------------- + +level?=info + +# --- Helpers ----------------------------------------------------------------- + +.PHONY: bin/posh +# Builds posh and takes the git hash to detect changes +bin/posh: current=$(shell bin/posh version) +bin/posh: version=$(shell git ls-files -s .posh/pkg | git hash-object --stdin) +bin/posh: commitHash=$(shell git rev-parse HEAD) +bin/posh: buildTimestamp=$(shell date +%s) +bin/posh: ldflags=\ + -X github.com/foomo/posh/internal/version.Version=${version} \ + -X github.com/foomo/posh/internal/version.CommitHash=${commitHash} \ + -X github.com/foomo/posh/internal/version.BuildTimestamp=${buildTimestamp} +bin/posh: + @if [ "${current}" != "${version}" ]; then \ + cd .posh && go build -trimpath -ldflags="${ldflags}" -o ../bin/posh main.go; \ + fi + +# --- Targets ----------------------------------------------------------------- + +.PHONY: clean +## Remove built targets +clean: + @rm bin/* + +.PHONY: config +## Print posh config +config: bin/posh + @bin/posh config --level ${level} .PHONY: brew -## Install packages -brew: - @posh brew +## Install project specific packages +brew: bin/posh + @bin/posh brew --level ${level} .PHONY: require ## Validate dependencies -require: - @posh require +require: bin/posh + @bin/posh require --level ${level} .PHONY: shell -## Start the interactive shell -shell: require brew - @posh prompt +## Start the interactive +shell: bin/posh require brew + @bin/posh prompt --level ${level} + +.PHONY: shell.rebuild +## Rebuild and start the interactive +shell.rebuild: clean shell ## === Utils === +.PHONY: help ## Show help text help: @awk '{ \ diff --git a/internal/log/init.go b/internal/log/init.go index 5090d15..531facd 100644 --- a/internal/log/init.go +++ b/internal/log/init.go @@ -4,13 +4,9 @@ import ( "github.com/foomo/posh/pkg/log" ) -func Init(level string, noColor bool) (log.Logger, error) { - if value, err := log.NewPTerm( +func Init(level string, noColor bool) log.Logger { + return log.NewPTerm( log.PTermWithDisableColor(noColor), log.PTermWithLevel(log.GetLevel(level)), - ); err != nil { - return nil, err - } else { - return value, nil - } + ) } diff --git a/internal/plugin/load.go b/internal/plugin/load.go deleted file mode 100644 index 382013b..0000000 --- a/internal/plugin/load.go +++ /dev/null @@ -1,23 +0,0 @@ -package plugin - -import ( - "context" - - "github.com/foomo/posh/pkg/config" - "github.com/foomo/posh/pkg/log" - "github.com/foomo/posh/pkg/plugin" - "github.com/spf13/viper" -) - -func Load(ctx context.Context, l log.Logger) (plugin.Plugin, error) { - m, err := manager(l) - if err != nil { - return nil, err - } - - var cfg config.Plugin - if err := viper.UnmarshalKey("plugin", &cfg); err != nil { - return nil, err - } - return m.BuildAndLoadPlugin(ctx, cfg.Source, cfg.Provider) -} diff --git a/internal/plugin/manager.go b/internal/plugin/manager.go deleted file mode 100644 index 31ac810..0000000 --- a/internal/plugin/manager.go +++ /dev/null @@ -1,19 +0,0 @@ -package plugin - -import ( - "github.com/foomo/posh/pkg/log" - "github.com/foomo/posh/pkg/plugin" -) - -var m *plugin.Manager - -func manager(l log.Logger) (*plugin.Manager, error) { - if m == nil { - if value, err := plugin.NewManager(l); err != nil { - return nil, err - } else { - m = value - } - } - return m, nil -} diff --git a/main.go b/main.go index 58984e5..0556af9 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,10 @@ import ( "github.com/foomo/posh/cmd" ) +func init() { + cmd.Init(nil) +} + func main() { cmd.Execute() } diff --git a/pkg/log/pterm.go b/pkg/log/pterm.go index fa1da76..456a122 100644 --- a/pkg/log/pterm.go +++ b/pkg/log/pterm.go @@ -11,7 +11,7 @@ type ( name string level Level } - PTermOption func(*PTerm) error + PTermOption func(*PTerm) ) // ------------------------------------------------------------------------------------------------ @@ -19,16 +19,15 @@ type ( // ------------------------------------------------------------------------------------------------ func PTermWithDisableColor(v bool) PTermOption { - return func(o *PTerm) error { + return func(o *PTerm) { if v { pterm.DisableColor() } - return nil } } func PTermWithLevel(v Level) PTermOption { - return func(o *PTerm) error { + return func(o *PTerm) { o.level = v switch { case v <= LevelTrace: @@ -40,7 +39,6 @@ func PTermWithLevel(v Level) PTermOption { default: pterm.Debug.LineNumberOffset = 1 } - return nil } } @@ -48,18 +46,16 @@ func PTermWithLevel(v Level) PTermOption { // ~ Constructor // ------------------------------------------------------------------------------------------------ -func NewPTerm(opts ...PTermOption) (*PTerm, error) { +func NewPTerm(opts ...PTermOption) *PTerm { inst := &PTerm{ level: LevelError, } for _, opt := range opts { if opt != nil { - if err := opt(inst); err != nil { - return nil, err - } + opt(inst) } } - return inst, nil + return inst } // ------------------------------------------------------------------------------------------------ diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go deleted file mode 100644 index 2feaa21..0000000 --- a/pkg/plugin/manager.go +++ /dev/null @@ -1,102 +0,0 @@ -package plugin - -import ( - "context" - "fmt" - "os/exec" - "path" - "path/filepath" - "plugin" - "strings" - - "github.com/foomo/posh/pkg/log" - "github.com/pkg/errors" -) - -type Manager struct { - l log.Logger - plugins map[string]*plugin.Plugin -} - -// ------------------------------------------------------------------------------------------------ -// ~ Constructor -// ------------------------------------------------------------------------------------------------ - -func NewManager(l log.Logger) (*Manager, error) { - inst := &Manager{ - l: l.Named("plugin"), - plugins: map[string]*plugin.Plugin{}, - } - return inst, nil -} - -// ------------------------------------------------------------------------------------------------ -// ~ Public methods -// ------------------------------------------------------------------------------------------------ - -func (m *Manager) BuildAndLoadPlugin(ctx context.Context, filename, provider string) (Plugin, error) { - if err := m.Tidy(ctx, filename); err != nil { - return nil, err - } - if err := m.Build(ctx, filename); err != nil { - return nil, err - } - return m.LoadPlugin(filename, provider) -} - -func (m *Manager) Tidy(ctx context.Context, filename string) error { - m.l.Debug("tidying:", filename) - cmd := exec.CommandContext(ctx, "go", "mod", "tidy") - cmd.Dir = filepath.Dir(filename) - if output, err := cmd.CombinedOutput(); err != nil { - return errors.Wrap(err, string(output)) - } - return nil -} - -func (m *Manager) Build(ctx context.Context, filename string) error { - m.l.Debug("building:", filename) - base := path.Base(filename) - cmd := exec.CommandContext(ctx, "go", "build", - "-buildmode=plugin", - "-a", - "-o", strings.ReplaceAll(base, ".go", ".so"), - base, - ) - cmd.Dir = filepath.Dir(filename) - if output, err := cmd.CombinedOutput(); err != nil { - return errors.Wrap(err, string(output)) - } - return nil -} - -func (m *Manager) LoadPlugin(filename, provider string) (Plugin, error) { - m.l.Debug("loading plugin:", filename, provider) - filename = strings.ReplaceAll(filename, ".go", ".so") - if plg, err := m.Load(filename); err != nil { - return nil, err - } else if sym, err := plg.Lookup(provider); err != nil { - return nil, errors.Wrapf(err, "failed to lookup provider (%s)", provider) - } else if fn, ok := sym.(func(l log.Logger) (Plugin, error)); !ok { - return nil, fmt.Errorf("invalid provider type (%T) ", sym) - } else if inst, err := fn(m.l.Named("")); err != nil { - return nil, errors.Wrap(err, "failed to create plugin instance") - } else if inst == nil { - return nil, errors.New("plugin can not be nil") - } else { - return inst, nil - } -} - -func (m *Manager) Load(filename string) (*plugin.Plugin, error) { - if value, ok := m.plugins[filename]; ok { - return value, nil - } - m.l.Debug("loading:", filename) - if plg, err := plugin.Open(filename); err != nil { - return nil, errors.Wrapf(err, "failed to load plugin (%s)", filename) - } else { - m.plugins[filename] = plg - return plg, nil - } -}