github.com/swaros/contxt/module/runner@v0.0.0-20240305083542-3dbd4436ac40/cobrainit.go (about)

     1  // Copyright (c) 2023 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved.
     2  //
     3  // # Licensed under the MIT License
     4  //
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  //
    12  // The above copyright notice and this permission notice shall be included in all
    13  // copies or substantial portions of the Software.
    14  //
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    21  // SOFTWARE.
    22  package runner
    23  
    24  import (
    25  	"errors"
    26  	"fmt"
    27  	"os"
    28  	"path/filepath"
    29  
    30  	"github.com/spf13/cobra"
    31  	"github.com/swaros/contxt/module/configure"
    32  	"github.com/swaros/contxt/module/ctxout"
    33  	"github.com/swaros/contxt/module/dirhandle"
    34  	"github.com/swaros/contxt/module/mimiclog"
    35  	"github.com/swaros/contxt/module/systools"
    36  )
    37  
    38  type SessionCobra struct {
    39  	RootCmd         *cobra.Command // the root command
    40  	ExternalCmdHndl CmdExecutor    // the command executor that is used to execute the commands logic
    41  	Options         CobraOptions   // all flags for the root command
    42  }
    43  
    44  type CobraOptions struct {
    45  	ShowColors           bool // flag for show colors in output
    46  	DisableTable         bool // flag for disable table output
    47  	RunOnceTimeOutMillis int  // timeout for run once mode
    48  	ShowHints            bool
    49  	LogLevel             string
    50  	DirAll               bool // dir flag for show all dirs in any workspace
    51  	ShowFullTargets      bool
    52  	ShowBuild            bool
    53  	LastDirIndex         bool
    54  	UseContext           string            // flag to switch to a workspace in the context of another workspace
    55  	InContext            bool              // flag to use the current workspace as context
    56  	PreVars              map[string]string // preset variables they will set variables from commandline. they will be overwritten by the template
    57  }
    58  
    59  // this is the main entry point for the cobra command
    60  func NewCobraCmds() *SessionCobra {
    61  	return &SessionCobra{
    62  		RootCmd: &cobra.Command{
    63  			Use:   configure.GetBinaryName(),
    64  			Short: "organize workspaces in the shell",
    65  			Long: `contxt is a tool to manage your projects.
    66  it setups your shell environment to fast switch between projects
    67  without the need to search for the right directory.
    68  also it includes a task runner to execute commands in the right context.
    69  `,
    70  			Run: func(cmd *cobra.Command, args []string) {
    71  				cmd.Help()
    72  			},
    73  		},
    74  	}
    75  }
    76  
    77  // init the cobra command tree
    78  func (c *SessionCobra) Init(cmd CmdExecutor) error {
    79  	c.ExternalCmdHndl = cmd
    80  	if c.ExternalCmdHndl == nil {
    81  		return fmt.Errorf("no command executor defined")
    82  	}
    83  	c.RootCmd.PersistentFlags().BoolVarP(&c.Options.ShowColors, "coloroff", "c", false, "disable usage of colors in output")
    84  	c.RootCmd.PersistentFlags().BoolVarP(&c.Options.ShowHints, "nohints", "n", false, "disable printing hints")
    85  	c.RootCmd.PersistentFlags().StringVar(&c.Options.LogLevel, "loglevel", "FATAL", "set loglevel")
    86  	c.RootCmd.PersistentFlags().BoolVar(&c.Options.DisableTable, "notable", false, "disable table format output")
    87  	c.RootCmd.PersistentFlags().StringVar(&c.Options.UseContext, "usecontext", "", "use a different workspace as context")
    88  	c.RootCmd.PersistentFlags().BoolVarP(&c.Options.InContext, "incontext", "I", false, "use the current workspace as context")
    89  	c.RootCmd.PersistentFlags().StringToStringVarP(&c.Options.PreVars, "var", "v", nil, "set variables by keyname and value.")
    90  
    91  	c.RootCmd.AddCommand(
    92  		c.GetWorkspaceCmd(),
    93  		c.getCompletion(),
    94  		c.GetGotoCmd(),
    95  		c.GetDirCmd(),
    96  		c.GetInteractiveCmd(),
    97  		c.GetRunCmd(),
    98  		c.GetLintCmd(),
    99  		c.GetInstallCmd(),
   100  		c.GetVersionCmd(),
   101  		c.getSharedCmd(),
   102  	)
   103  
   104  	return nil
   105  }
   106  
   107  func (c *SessionCobra) getSharedCmd() *cobra.Command {
   108  	shared := &cobra.Command{
   109  		Use:   "shared",
   110  		Short: "manage shared libraries",
   111  		Long:  "manage shared libraries",
   112  		Run: func(cmd *cobra.Command, args []string) {
   113  			if len(args) == 0 {
   114  				cmd.Help()
   115  			}
   116  		},
   117  	}
   118  	shared.AddCommand(
   119  		c.sharedCmdList(),
   120  		c.getSharedUpdateCmd(),
   121  	)
   122  	return shared
   123  }
   124  
   125  func (c *SessionCobra) sharedCmdList() *cobra.Command {
   126  	return &cobra.Command{
   127  		Use:   "list",
   128  		Short: "show all shared libraries",
   129  		Long:  "show all shared libraries",
   130  		Run: func(cmd *cobra.Command, args []string) {
   131  			c.checkDefaultFlags(cmd, args)
   132  			c.ExternalCmdHndl.PrintShared()
   133  		},
   134  	}
   135  }
   136  
   137  func (c *SessionCobra) getSharedUpdateCmd() *cobra.Command {
   138  	return &cobra.Command{
   139  		Use:   "update",
   140  		Short: "update shared libraries",
   141  		Long:  "update shared libraries",
   142  		Run: func(cmd *cobra.Command, args []string) {
   143  			c.checkDefaultFlags(cmd, args)
   144  			shared := NewSharedHelper()
   145  			useCases, err := shared.ListUseCases(true)
   146  			if err == nil {
   147  				for _, path := range useCases {
   148  					c.println("check usage ", ctxout.ForeBlue, path, ctxout.CleanTag)
   149  					shared.UpdateUseCase(path)
   150  				}
   151  			}
   152  		},
   153  	}
   154  }
   155  
   156  // til here we define the commands
   157  // they needs to be added to the root command
   158  
   159  // getCompletion returns the completion command for the root command.
   160  func (c *SessionCobra) getCompletion() *cobra.Command {
   161  	return &cobra.Command{
   162  		Use:   "completion [bash|zsh|fish|powershell]",
   163  		Short: "Generate completion script",
   164  		Long: `To load completions:
   165  
   166  Bash:
   167  
   168    $ source <(contxt completion bash)
   169  
   170    # To load completions for each session, execute once:
   171    # Linux:
   172    $ contxt completion bash > /etc/bash_completion.d/contxt
   173    # macOS:
   174    $ contxt completion bash > /usr/local/etc/bash_completion.d/contxt
   175  
   176  Zsh:
   177  
   178    # If shell completion is not already enabled in your environment,
   179    # you will need to enable it.  You can execute the following once:
   180  
   181    $ echo "autoload -U compinit; compinit" >> ~/.zshrc
   182  
   183    # To load completions for each session, execute once:
   184    $ contxt completion zsh > "${fpath[1]}/_contxt"
   185  
   186    # You will need to start a new shell for this setup to take effect.
   187  
   188  fish:
   189  
   190    $ contxt completion fish | source
   191  
   192    # To load completions for each session, execute once:
   193    $ contxt completion fish > ~/.config/fish/completions/contxt.fish
   194  PowerShell:
   195  
   196    PS> contxt completion powershell | Out-String | Invoke-Expression
   197  
   198    # To load completions for every new session, run:
   199    PS> contxt completion powershell > contxt.ps1
   200    # and source this file from your PowerShell profile.
   201  
   202    `,
   203  		DisableFlagsInUseLine: true,
   204  		ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
   205  		Args:                  cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
   206  		Run: func(cmd *cobra.Command, args []string) {
   207  			switch args[0] {
   208  			case "bash":
   209  				cmd.Root().GenBashCompletion(os.Stdout)
   210  			case "zsh":
   211  				cmd.Root().GenZshCompletion(os.Stdout)
   212  			case "fish":
   213  				cmd.Root().GenFishCompletion(os.Stdout, true)
   214  			case "powershell":
   215  				cmd.Root().GenPowerShellCompletion(os.Stdout)
   216  			}
   217  		},
   218  	}
   219  
   220  }
   221  
   222  func (c *SessionCobra) GetGotoCmd() *cobra.Command {
   223  	return &cobra.Command{
   224  		Use:   "switch",
   225  		Short: "switch workspace",
   226  		Long: `switch the workspace to a existing ones.
   227  all defined onEnter and onLeave task will be executed
   228  if these task are defined
   229  `,
   230  		RunE: func(_ *cobra.Command, args []string) error {
   231  			current := dirhandle.Pushd()
   232  			current.Popd()
   233  			var cmderr error
   234  			found := false
   235  			if len(args) > 0 {
   236  				configure.GetGlobalConfig().ExecOnWorkSpaces(func(index string, cfg configure.ConfigurationV2) {
   237  					if args[0] == index {
   238  						found = true
   239  						if err := configure.GetGlobalConfig().ChangeWorkspace(index, c.ExternalCmdHndl.CallBackOldWs, c.ExternalCmdHndl.CallBackNewWs); err != nil {
   240  							cmderr = err
   241  						}
   242  					}
   243  				})
   244  			}
   245  			if !found {
   246  				cmderr = fmt.Errorf("workspace %s not found", args[0])
   247  			}
   248  			return cmderr
   249  		},
   250  		ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
   251  			if len(args) != 0 {
   252  				return nil, cobra.ShellCompDirectiveNoFileComp
   253  			}
   254  			targets := configure.GetGlobalConfig().ListWorkSpaces()
   255  			return targets, cobra.ShellCompDirectiveNoFileComp
   256  		},
   257  	}
   258  }
   259  
   260  func (c *SessionCobra) GetPrintWsCmd() *cobra.Command {
   261  	return &cobra.Command{
   262  		Use:   "show",
   263  		Short: "show all workspaces",
   264  		Long:  `show all workspaces and mark the current one`,
   265  		Run: func(cmd *cobra.Command, args []string) {
   266  			c.checkDefaultFlags(cmd, args)
   267  			c.ExternalCmdHndl.PrintWorkspaces()
   268  		},
   269  	}
   270  }
   271  
   272  // prints the current workspace
   273  func (c *SessionCobra) PrintCurrentWs() *cobra.Command {
   274  	return &cobra.Command{
   275  		Use:   "current",
   276  		Short: "prints the current workspace",
   277  		Long:  `prints the current active workspace. This is the workspace which is used for the current session`,
   278  		Run: func(cmd *cobra.Command, args []string) {
   279  			c.checkDefaultFlags(cmd, args)
   280  			c.println(c.ExternalCmdHndl.GetCurrentWorkSpace())
   281  		},
   282  	}
   283  }
   284  
   285  func (c *SessionCobra) GetListWsCmd() *cobra.Command {
   286  	return &cobra.Command{
   287  		Use:   "ls",
   288  		Short: "list all workspaces",
   289  		Long:  `list all workspaces`,
   290  		Run: func(cmd *cobra.Command, args []string) {
   291  			c.checkDefaultFlags(cmd, args)
   292  			ws := c.ExternalCmdHndl.GetWorkspaces()
   293  			for _, w := range ws {
   294  				out, printer := c.ExternalCmdHndl.GetOuputHandler()
   295  				ctxout.CtxOut(out, printer, w)
   296  			}
   297  		},
   298  	}
   299  }
   300  
   301  func (c *SessionCobra) GetNewWsCmd() *cobra.Command {
   302  	return &cobra.Command{
   303  		Use:   "new",
   304  		Short: "create a new workspace",
   305  		Long: `
   306  create a new workspace.
   307  this will trigger any onLeave task defined in the workspace
   308  and also onEnter task defined in the new workspace
   309  `,
   310  		RunE: func(cmd *cobra.Command, args []string) error {
   311  			//checkDefaultFlags(cmd, args)
   312  			if len(args) == 1 {
   313  				if err := configure.GetGlobalConfig().AddWorkSpace(args[0], c.ExternalCmdHndl.CallBackOldWs, c.ExternalCmdHndl.CallBackNewWs); err != nil {
   314  					return err
   315  				} else {
   316  					c.print("workspace created ", args[0])
   317  				}
   318  
   319  			} else {
   320  				if len(args) == 0 {
   321  					return errors.New("no workspace name given")
   322  				} else {
   323  					return errors.New("to many arguments")
   324  				}
   325  			}
   326  			return nil
   327  		},
   328  	}
   329  }
   330  
   331  func (c *SessionCobra) GetRmWsCmd() *cobra.Command {
   332  	return &cobra.Command{
   333  		Use:   "rm",
   334  		Short: "remove a workspace by given name",
   335  		Long: `
   336  remove a workspace.
   337  this will trigger any onLeave task defined in the workspace
   338  and also onEnter task defined in the new workspace
   339  `,
   340  		RunE: func(cmd *cobra.Command, args []string) error {
   341  			//checkDefaultFlags(cmd, args)
   342  			if len(args) > 0 {
   343  				if err := configure.GetGlobalConfig().RemoveWorkspace(args[0]); err != nil {
   344  					c.log().Error("error while trying to remove workspace", err)
   345  					return err
   346  				} else {
   347  					if err := configure.GetGlobalConfig().SaveConfiguration(); err != nil {
   348  						c.log().Error("error while trying to save configuration", err)
   349  						return err
   350  					}
   351  					c.print("workspace removed ", args[0])
   352  				}
   353  			} else {
   354  				return errors.New("no workspace name given")
   355  			}
   356  			return nil
   357  		},
   358  		ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
   359  			if len(args) != 0 {
   360  				return nil, cobra.ShellCompDirectiveNoFileComp
   361  			}
   362  			targets := configure.GetGlobalConfig().ListWorkSpaces()
   363  			return targets, cobra.ShellCompDirectiveNoFileComp
   364  		},
   365  	}
   366  }
   367  
   368  func (c *SessionCobra) GetScanCmd() *cobra.Command {
   369  	return &cobra.Command{
   370  		Use:   "scan",
   371  		Short: "scan all projects in the workspace",
   372  		Long:  "looking for project information, like names and roles, in all the workspaces, and update the global project list",
   373  		Run: func(cmd *cobra.Command, args []string) {
   374  			c.log().Debug("scan for new projects")
   375  			c.checkDefaultFlags(cmd, args)
   376  			c.println(ctxout.BoldTag, "Scanning for new projects", ctxout.CleanTag, " ... ")
   377  			c.println("checking any known contxt project if there are information to update")
   378  			c.print("\n")
   379  			c.print("<table>")
   380  			c.print("<row>", ctxout.BoldTag, "<tab size='15'> project</tab><tab size='70'>path</tab><tab size='15' origin='2'>status</tab>", ctxout.CleanTag, "</row>")
   381  			current := dirhandle.Pushd()
   382  			all, updated := c.ExternalCmdHndl.FindWorkspaceInfoByTemplate(func(ws string, cnt int, update bool, info configure.WorkspaceInfoV2) {
   383  				color := ctxout.ForeYellow
   384  				msg := "found"
   385  				if !update {
   386  					msg = "nothing new"
   387  					color = ctxout.ForeLightGreen
   388  				}
   389  				c.print("<row>", ctxout.ForeBlue, "<tab size='15'> ", ws, "</tab>", ctxout.ForeLightBlue, "<tab size='70'>", info.Path, color, "</tab><tab size='15' origin='2'>", msg, "</tab></row>")
   390  			})
   391  			current.Popd()
   392  			c.print("</table>")
   393  			c.println(ctxout.CleanTag, "")
   394  			c.println("found ", ctxout.ForeLightBlue, all, ctxout.CleanTag, " projects and updated ", ctxout.ForeLightBlue, updated, ctxout.CleanTag, " projects")
   395  
   396  		},
   397  	}
   398  }
   399  
   400  func (c *SessionCobra) GetWorkspaceCmd() *cobra.Command {
   401  	wsCmd := &cobra.Command{
   402  		Use:   "workspace",
   403  		Short: "manage workspaces",
   404  		Long: `create a new workspace 'ctx workspace new <name>'. 
   405  Remove a workspace 'ctx workspace rm <name>'.
   406  list all workspaces 'ctx workspace list'.
   407  scan for new projects in the workspace 'ctx workspace scan'`,
   408  	}
   409  	wsCmd.AddCommand(
   410  		c.GetNewWsCmd(),
   411  		c.GetRmWsCmd(),
   412  		c.GetScanCmd(),
   413  		c.GetPrintWsCmd(),
   414  		c.GetListWsCmd(),
   415  		c.PrintCurrentWs(),
   416  	)
   417  	return wsCmd
   418  }
   419  
   420  // -- lint cmd
   421  
   422  func (c *SessionCobra) GetLintCmd() *cobra.Command {
   423  	var showAll bool
   424  	lCmd := &cobra.Command{
   425  		Use:   "lint",
   426  		Short: "lint the .contxt.yaml file",
   427  		Long:  "lint the .contxt.yaml and shows unexpected fields",
   428  		RunE: func(cmd *cobra.Command, args []string) error {
   429  			c.checkDefaultFlags(cmd, args)
   430  
   431  			return c.ExternalCmdHndl.Lint(showAll)
   432  		},
   433  	}
   434  
   435  	lCmd.Flags().BoolVarP(&showAll, "show-issues", "i", false, "show all issues")
   436  	lCmd.AddCommand(c.GetLintTemplateCmd())
   437  
   438  	return lCmd
   439  }
   440  
   441  // a lint sub command to display the current loaded template
   442  // as yaml
   443  func (c *SessionCobra) GetLintTemplateCmd() *cobra.Command {
   444  	return &cobra.Command{
   445  		Use:   "yaml",
   446  		Short: "show the current loaded template",
   447  		Long:  "show the current loaded template as yaml",
   448  		Run: func(cmd *cobra.Command, args []string) {
   449  			c.checkDefaultFlags(cmd, args)
   450  			c.ExternalCmdHndl.PrintTemplate()
   451  		},
   452  	}
   453  }
   454  
   455  // -- Run cmd
   456  
   457  func (c *SessionCobra) GetRunCmd() *cobra.Command {
   458  	rCmd := &cobra.Command{
   459  		Use:   "run",
   460  		Short: "run a command in the context of a project",
   461  		Long:  "run a command in the context of a project",
   462  		RunE: func(cmd *cobra.Command, args []string) error {
   463  			c.checkDefaultFlags(cmd, args)
   464  			if len(args) > 0 {
   465  				c.log().Debug("run command in context of project", args)
   466  				for _, p := range args {
   467  					if err := c.ExternalCmdHndl.RunTargets(p, true); err != nil {
   468  						return err
   469  					}
   470  				}
   471  			} else {
   472  				targets := c.ExternalCmdHndl.GetTargets(false)
   473  				for _, p := range targets {
   474  					c.println(p)
   475  				}
   476  				return nil
   477  			}
   478  			return nil
   479  		},
   480  		ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
   481  			if len(args) != 0 {
   482  				return nil, cobra.ShellCompDirectiveNoFileComp
   483  			}
   484  			targets := c.ExternalCmdHndl.GetTargets(false)
   485  			return targets, cobra.ShellCompDirectiveNoFileComp
   486  		},
   487  	}
   488  	return rCmd
   489  }
   490  
   491  // -- Dir Command
   492  
   493  func (c *SessionCobra) GetDirCmd() *cobra.Command {
   494  	dCmd := &cobra.Command{
   495  		Use:   "dir",
   496  		Short: "handle workspaces and assigned paths",
   497  		Long:  "manage workspaces and paths they are assigned",
   498  		Run: func(cmd *cobra.Command, args []string) {
   499  			c.checkDefaultFlags(cmd, args)
   500  
   501  			// if we do not have any workspace, we do not need to do anything.
   502  			// without an workspace we also have no paths to show
   503  			if len(configure.GetGlobalConfig().ListWorkSpaces()) == 0 {
   504  				c.log().Debug("no workspace found, nothing to do")
   505  				c.println("no workspace found, nothing to do. create a new workspace with 'ctx workspace new <name>'")
   506  				return
   507  			}
   508  
   509  			if c.Options.LastDirIndex {
   510  				pathStr := configure.GetGlobalConfig().GetActivePath(".")
   511  				fmt.Println(pathStr)
   512  				c.Options.LastDirIndex = false // one time usage
   513  				return
   514  			}
   515  
   516  			if len(args) == 0 {
   517  				c.log().Debug("show all paths in any workspace", c.Options.DirAll)
   518  				current := configure.GetGlobalConfig().UsedV2Config.CurrentSet
   519  				c.print("<table>")
   520  				if c.Options.DirAll {
   521  					configure.GetGlobalConfig().ExecOnWorkSpaces(func(index string, cfg configure.ConfigurationV2) {
   522  						configure.GetGlobalConfig().UsedV2Config.CurrentSet = index
   523  						// header for each workspace
   524  						c.print("<row>", ctxout.BoldTag, "<tab size='100' fill=' '>", index, ctxout.CleanTag, ctxout.ForeDarkGrey, ": index (", cfg.CurrentIndex, ")</tab></row>")
   525  						c.ExternalCmdHndl.PrintPaths(true, c.Options.ShowFullTargets)
   526  						c.print("<row>", ctxout.ForeDarkGrey, "<tab size='100' fill='─'> </tab>", ctxout.CleanTag, "</row>")
   527  
   528  					})
   529  				} else {
   530  					c.ExternalCmdHndl.PrintPaths(false, c.Options.ShowFullTargets)
   531  				}
   532  				c.println("</table>")
   533  				configure.GetGlobalConfig().UsedV2Config.CurrentSet = current
   534  			}
   535  		},
   536  	}
   537  	dCmd.Flags().BoolVarP(&c.Options.DirAll, "all", "a", false, "show all paths in any workspace")
   538  	dCmd.Flags().BoolVarP(&c.Options.ShowFullTargets, "full", "f", false, "show full amount of targets")
   539  	dCmd.Flags().BoolVarP(&c.Options.LastDirIndex, "last", "l", false, "show last used path")
   540  	dCmd.AddCommand(c.GetDirFindCmd(), c.GetDirAddCmd(), c.GetDirRmCmd(), c.GetDirLsCmd())
   541  	return dCmd
   542  }
   543  
   544  func (c *SessionCobra) GetDirFindCmd() *cobra.Command {
   545  	fCmd := &cobra.Command{
   546  		Use:   "find",
   547  		Short: "find a path in the current workspace",
   548  		Long:  "find a path in the current workspace by the given argument combination",
   549  		Run: func(cmd *cobra.Command, args []string) {
   550  			if len(args) < 1 {
   551  				pathStr := configure.GetGlobalConfig().GetActivePath(".")
   552  				// we use plain output here. so we can use it in the shell and is not affected by the output handler
   553  				fmt.Println(pathStr)
   554  			} else {
   555  				path, _ := c.ExternalCmdHndl.DirFindApplyAndSave(args)
   556  				fmt.Println(path) // path only as output. so cn can handle it. and again plain fmt usage
   557  			}
   558  		},
   559  	}
   560  	return fCmd
   561  }
   562  
   563  // GetDirLsCmd returns the command to list all paths in the current workspace
   564  func (c *SessionCobra) GetDirLsCmd() *cobra.Command {
   565  	fCmd := &cobra.Command{
   566  		Use:     "list",
   567  		Aliases: []string{"ls"},
   568  		Short:   "list all paths in the current workspace",
   569  		Long:    "list all paths in the current workspace in a simple list",
   570  		Run: func(cmd *cobra.Command, args []string) {
   571  			paths := configure.GetGlobalConfig().ListPaths()
   572  			for _, path := range paths {
   573  				c.println(path)
   574  			}
   575  
   576  		},
   577  	}
   578  	return fCmd
   579  }
   580  
   581  func (c *SessionCobra) GetDirAddCmd() *cobra.Command {
   582  	aCmd := &cobra.Command{
   583  		Use:   "add",
   584  		Short: "add path(s) to the workspace",
   585  		Long: `add current path (pwd) if no argument is set.
   586  		else add the given paths to the workspace
   587  		like 'ctx dir add /path/to/dir /path/to/other/dir'
   588  		paths need to be absolute paths`,
   589  		RunE: func(cmd *cobra.Command, args []string) error {
   590  			c.checkDefaultFlags(cmd, args)
   591  			c.print("add path(s) to workspace: ", ctxout.ForeGreen, configure.GetGlobalConfig().UsedV2Config.CurrentSet, ctxout.CleanTag)
   592  			if len(args) == 0 {
   593  				dir, err := os.Getwd()
   594  				if err != nil {
   595  					c.log().Error(err)
   596  					return err
   597  				}
   598  				args = append(args, dir)
   599  			}
   600  			c.println(" (", ctxout.ForeDarkGrey, len(args), ctxout.CleanTag, " paths)")
   601  			for _, arg := range args {
   602  				if arg == "" {
   603  					return errors.New("empty path is not allowed")
   604  				}
   605  				c.println("try ... ", ctxout.ForeLightBlue, arg, ctxout.CleanTag)
   606  				// we need to check if the path is absolute
   607  				if !filepath.IsAbs(arg) {
   608  					c.println("error: ", ctxout.ForeRed, "path is not absolute", ctxout.CleanTag)
   609  					currentDir, _ := filepath.Abs(".")
   610  					return errors.New("given path for adding is not absolute [" + arg + "], current path:" + currentDir)
   611  				}
   612  
   613  				if ok, err := dirhandle.Exists(arg); !ok || err != nil {
   614  					if err != nil {
   615  						c.println("error: ", ctxout.ForeRed, err, ctxout.CleanTag)
   616  						return err
   617  					}
   618  					c.println("error: ", ctxout.ForeRed, "path does not exist", ctxout.CleanTag)
   619  					return errors.New("path does not exist")
   620  				}
   621  				if err := configure.GetGlobalConfig().AddPath(arg); err == nil {
   622  					c.println("add ", ctxout.ForeBlue, arg, ctxout.CleanTag)
   623  					configure.GetGlobalConfig().SaveConfiguration()
   624  					cmd := c.GetScanCmd() // we use the scan command to update the project infos
   625  					cmd.Run(cmd, nil)     // this is parsing all templates in all workspaces and updates the project Infos
   626  				} else {
   627  					c.println("error: ", ctxout.ForeRed, err, ctxout.CleanTag)
   628  					return err
   629  				}
   630  			}
   631  			return nil
   632  		},
   633  	}
   634  	return aCmd
   635  }
   636  
   637  func (c *SessionCobra) GetDirRmCmd() *cobra.Command {
   638  	rCmd := &cobra.Command{
   639  		Use:   "rm",
   640  		Short: "remove path(s) from the workspace",
   641  		Long: `remove the given paths from the workspace
   642  		like 'ctx dir rm /path/to/dir /path/to/other/dir'`,
   643  		RunE: func(cmd *cobra.Command, args []string) error {
   644  			c.checkDefaultFlags(cmd, args)
   645  			c.print("remove path(s) from workspace: ", ctxout.ForeGreen, configure.GetGlobalConfig().UsedV2Config.CurrentSet, ctxout.CleanTag)
   646  			if len(args) == 0 {
   647  				dir, err := os.Getwd()
   648  				if err != nil {
   649  					c.log().Error(err)
   650  					return err
   651  				}
   652  				args = append(args, dir)
   653  			}
   654  			c.println(" (", ctxout.ForeDarkGrey, len(args), ctxout.CleanTag, " paths)")
   655  			for _, arg := range args {
   656  				c.println("try ... ", ctxout.ForeLightBlue, arg, ctxout.CleanTag)
   657  				// we need to check if the path is absolute
   658  				if !filepath.IsAbs(arg) {
   659  					c.println("error: ", ctxout.ForeRed, "path is not absolute", ctxout.CleanTag)
   660  					return errors.New("path is not absolute for dir cmd")
   661  				}
   662  
   663  				// we don not check if the path exists. we just remove it
   664  				// it is possible that the path is not existing anymore
   665  				if ok := configure.GetGlobalConfig().RemovePath(arg); ok {
   666  					c.println("remove ", ctxout.ForeBlue, arg, ctxout.CleanTag)
   667  					configure.GetGlobalConfig().SaveConfiguration()
   668  					cmd := c.GetScanCmd() // we use the scan command to update the project infos
   669  					cmd.Run(cmd, nil)     // this is parsing all templates in all workspaces and updates the project Infos
   670  				} else {
   671  					c.println("error: ", ctxout.ForeRed, "could not remove path", ctxout.CleanTag)
   672  					return errors.New("could not remove path")
   673  				}
   674  			}
   675  			return nil
   676  		},
   677  	}
   678  	return rCmd
   679  }
   680  
   681  func (c *SessionCobra) GetInstallCmd() *cobra.Command {
   682  	iCmd := &cobra.Command{
   683  		Use:   "install",
   684  		Short: "install shell support",
   685  		Long: `install shell support for different shells.
   686  		supported shells are: bash, zsh, fish, powershell`,
   687  		RunE: func(cmd *cobra.Command, args []string) error {
   688  			c.checkDefaultFlags(cmd, args)
   689  			c.print("install shell support")
   690  			return nil
   691  		},
   692  	}
   693  	iCmd.AddCommand(c.GetInstallBashCmd())
   694  	iCmd.AddCommand(c.GetInstallZshCmd())
   695  	iCmd.AddCommand(c.GetInstallFishCmd())
   696  	iCmd.AddCommand(c.GetInstallPowershellCmd())
   697  	return iCmd
   698  }
   699  
   700  func (c *SessionCobra) GetInstallBashCmd() *cobra.Command {
   701  	iCmd := &cobra.Command{
   702  		Use:   "bash",
   703  		Short: "install shell support for bash",
   704  		Long: `install shell support for bash.
   705  		this is done by adding some functions and a source command to the bashrc file.
   706  		Afterwards you can use the ctx command in your bash shell instead of contxt.
   707  		So after an switch command, you will automatically change the dir to the new workspace.
   708  		You can also use the cn command to change one of the assigned paths to the current workspace.`,
   709  
   710  		RunE: func(cmd *cobra.Command, args []string) error {
   711  			c.ExternalCmdHndl.MainInit()
   712  			installer := NewShellInstall(c.log())
   713  			if err := installer.BashUserInstall(); err != nil {
   714  				c.log().Error(err)
   715  				return err
   716  			}
   717  			return nil
   718  		},
   719  	}
   720  	return iCmd
   721  }
   722  
   723  func (c *SessionCobra) GetInstallZshBaseFunc() *cobra.Command {
   724  	iCmd := &cobra.Command{
   725  		Use:   "base",
   726  		Short: "get the zsh base functions for completion",
   727  		Long: `get the zsh base functions for completion.
   728  		this is sometimes helpfull to get completion in zsh running, because of some
   729  		restrictions in zsh.
   730  		`,
   731  
   732  		Run: func(cmd *cobra.Command, args []string) {
   733  			c.ExternalCmdHndl.MainInit()
   734  			// check the shortcut flag
   735  			shortcut, _ := cmd.Flags().GetBool("shortcut")
   736  			installer := NewShellInstall(c.log())
   737  			fmt.Println(installer.GetBinFunc(c.RootCmd, shortcut))
   738  
   739  		},
   740  	}
   741  	iCmd.Flags().BoolP("shortcut", "s", false, "show the shortcut functions")
   742  	return iCmd
   743  }
   744  
   745  func (c *SessionCobra) GetInstallZshShellScript() *cobra.Command {
   746  	iCmd := &cobra.Command{
   747  		Use:   "compscript",
   748  		Short: "creates a script for zsh completion",
   749  		Long: `creates a script for zsh completion.
   750  		this script try to create the completions for zsh locally.
   751  		and uses then sudo to copy the file to the zsh completion dir.
   752  		`,
   753  
   754  		Run: func(cmd *cobra.Command, args []string) {
   755  			c.ExternalCmdHndl.MainInit()
   756  
   757  			installer := NewShellInstall(c.log())
   758  			fmt.Println(installer.GetScript(c.RootCmd))
   759  
   760  		},
   761  	}
   762  	return iCmd
   763  }
   764  
   765  func (c *SessionCobra) GetInstallZshCmd() *cobra.Command {
   766  	iCmd := &cobra.Command{
   767  		Use:   "zsh",
   768  		Short: "install shell support for zsh",
   769  		Long: `install shell support for zsh.
   770  		this is done by adding some functions and a source command to the zshrc file.
   771  		Afterwards you can use the ctx command in your zsh shell instead of contxt.
   772  		So after an switch command, you will automatically change the dir to the new workspace.
   773  		You can also use the cn command to change one of the assigned paths to the current workspace.`,
   774  
   775  		RunE: func(cmd *cobra.Command, args []string) error {
   776  			c.ExternalCmdHndl.MainInit()
   777  			installer := NewShellInstall(c.log())
   778  			if err := installer.ZshUpdate(c.RootCmd); err != nil {
   779  				return err
   780  			}
   781  			return nil
   782  		},
   783  	}
   784  	iCmd.AddCommand(c.GetInstallZshBaseFunc())
   785  	iCmd.AddCommand(c.GetInstallZshShellScript())
   786  	return iCmd
   787  }
   788  
   789  func (c *SessionCobra) GetInstallFishCmd() *cobra.Command {
   790  	iCmd := &cobra.Command{
   791  		Use:   "fish",
   792  		Short: "install shell support for fish",
   793  		Long: `install shell support for fish.
   794  		this is done by adding some functions and a source command to the fish config file.
   795  		Afterwards you can use the ctx command in your fish shell instead of contxt.
   796  		So after an switch command, you will automatically change the dir to the new workspace.
   797  		You can also use the cn command to change one of the assigned paths to the current workspace.`,
   798  
   799  		RunE: func(cmd *cobra.Command, args []string) error {
   800  			c.ExternalCmdHndl.MainInit()
   801  			installer := NewShellInstall(c.log())
   802  			if err := installer.FishUpdate(c.RootCmd); err != nil {
   803  				c.log().Error(err)
   804  				return err
   805  			}
   806  			return nil
   807  		},
   808  	}
   809  	return iCmd
   810  }
   811  
   812  func (c *SessionCobra) GetInstallPowershellCmd() *cobra.Command {
   813  	iCmd := &cobra.Command{
   814  		Use:   "powershell",
   815  		Short: "install shell support for powershell",
   816  		Long: `install shell support for powershell.
   817  		this is done by adding some functions and a source command to the powershell profile file.
   818  		Afterwards you can use the ctx command in your powershell shell instead of contxt.		
   819  		You can also use the cn command to change one of the assigned paths to the current workspace.`,
   820  		RunE: func(cmd *cobra.Command, args []string) error {
   821  			c.ExternalCmdHndl.MainInit()
   822  			installer := NewShellInstall(c.log())
   823  			if err := installer.PwrShellUpdate(c.RootCmd); err != nil {
   824  				c.log().Error(err)
   825  				return err
   826  			}
   827  			return nil
   828  		},
   829  	}
   830  	return iCmd
   831  }
   832  
   833  func (c *SessionCobra) GetVersionCmd() *cobra.Command {
   834  	vCmd := &cobra.Command{
   835  		Use:   "version",
   836  		Short: "print the version number of contxt",
   837  		Long:  `All software has versions. This is contxt's`,
   838  		RunE: func(cmd *cobra.Command, args []string) error {
   839  			c.checkDefaultFlags(cmd, args)
   840  			if c.Options.ShowBuild {
   841  				c.println("contxt version: ", configure.GetVersion(), " build ", configure.GetBuild())
   842  			} else {
   843  				c.println("contxt version: ", configure.GetVersion())
   844  			}
   845  			return nil
   846  		},
   847  	}
   848  	vCmd.Flags().BoolVarP(&c.Options.ShowBuild, "build", "b", false, "show build information")
   849  	return vCmd
   850  }
   851  
   852  // log returns the logger
   853  func (c *SessionCobra) log() mimiclog.Logger {
   854  	return c.ExternalCmdHndl.GetLogger()
   855  }
   856  
   857  // print prints the given message to the output handler
   858  func (c *SessionCobra) print(msg ...interface{}) {
   859  	c.ExternalCmdHndl.Print(msg...)
   860  }
   861  
   862  // println prints the given message to the output handler with a new line
   863  func (c *SessionCobra) println(msg ...interface{}) {
   864  	c.ExternalCmdHndl.Println(msg...)
   865  }
   866  
   867  func (c *SessionCobra) checkDefaultFlags(cmd *cobra.Command, _ []string) {
   868  	envColorOff := os.Getenv("CTX_COLOROFF")
   869  	// TODO: why this way? is the global flag not working?
   870  	color, err := cmd.Flags().GetBool("coloroff")
   871  	if err == nil && (color || envColorOff == "true") {
   872  		behave := ctxout.GetBehavior()
   873  		behave.NoColored = true
   874  		ctxout.SetBehavior(behave)
   875  	} else if err != nil {
   876  		c.log().Error(err)
   877  		systools.Exit(systools.ErrorInitApp)
   878  	}
   879  
   880  	c.Options.LogLevel, _ = cmd.Flags().GetString("loglevel")
   881  	if err := c.ExternalCmdHndl.SetLogLevel(c.Options.LogLevel); err != nil {
   882  		c.log().Error(err)
   883  		systools.Exit(systools.ErrorInitApp)
   884  	}
   885  
   886  	// force the log level by env var
   887  	envLogLevel := os.Getenv("CTX_LOGLEVEL")
   888  	if envLogLevel != "" {
   889  		c.Options.LogLevel = envLogLevel
   890  	}
   891  
   892  	// force the disable table flag by env var
   893  	envDisableTable := os.Getenv("CTX_DISABLE_TABLE")
   894  	if envDisableTable == "true" {
   895  		c.Options.DisableTable = true
   896  	}
   897  	if c.Options.DisableTable {
   898  		// overwrite the table plugin with a disabled one
   899  		ctxout.UpdateFilterByRef(ctxout.NewTabOut(), ctxout.PostFilterInfo{Disabled: true})
   900  	}
   901  
   902  	// preset all variables from command line
   903  	// set variables by argument.
   904  	for preKey, preValue := range c.Options.PreVars {
   905  		c.log().Debug("preset Value", preKey, preValue)
   906  		c.ExternalCmdHndl.SetPreValue(preKey, preValue)
   907  	}
   908  
   909  	// forces to change to the current used workspace and the assosiated path
   910  	if c.Options.InContext {
   911  		if c.Options.UseContext != "" {
   912  			c.log().Critical("use of --incontext and --usecontext is not allowed")
   913  			systools.Exit(systools.ErrorInitApp)
   914  		}
   915  		c.log().Info("force to change to the current workspace and path", configure.GetGlobalConfig().CurrentWorkspace())
   916  		path := configure.GetGlobalConfig().GetActivePath("")
   917  		if path != "" {
   918  			c.log().Info("change to path", path)
   919  			os.Chdir(path)
   920  			// important to set the default variables in the context of the used workspace
   921  			c.ExternalCmdHndl.MainInit()
   922  		}
   923  	}
   924  
   925  	// if this flag is set, we do change into the workspace and using the current directory there.
   926  	// so any command will be executed in the context of the workspace
   927  	if c.Options.UseContext != "" {
   928  		c.log().Info("working in context of ", c.Options.UseContext)
   929  		configure.GetGlobalConfig().ExecOnWorkSpaces(func(index string, cfg configure.ConfigurationV2) {
   930  			if c.Options.UseContext == index {
   931  
   932  				if err := configure.GetGlobalConfig().ChangeWorkspaceNotSaved(index); err != nil {
   933  					c.log().Error("error while trying to change workspace", err)
   934  					systools.Exit(systools.ErrorWhileLoadCfg)
   935  				}
   936  
   937  				path := configure.GetGlobalConfig().GetActivePath("")
   938  				if path != "" {
   939  					c.println("going to path: ", ctxout.ForeLightBlue, path, ctxout.CleanTag)
   940  					os.Chdir(path)
   941  					// important to set the default variables in the context of the used workspace
   942  					c.ExternalCmdHndl.MainInit()
   943  				}
   944  			}
   945  		})
   946  
   947  	}
   948  }
   949  
   950  func (c *SessionCobra) GetInteractiveCmd() *cobra.Command {
   951  	iCmd := &cobra.Command{
   952  		Use:   "interactive",
   953  		Short: "start the interactive mode",
   954  		Long:  `start the interactive mode`,
   955  		Run: func(cmd *cobra.Command, args []string) {
   956  			c.checkDefaultFlags(cmd, args)
   957  			c.println("start interactive mode")
   958  			c.ExternalCmdHndl.InteractiveScreen()
   959  		},
   960  	}
   961  	iCmd.AddCommand(c.GetInteractiveOnceCmd())
   962  	return iCmd
   963  }
   964  
   965  func (c *SessionCobra) GetInteractiveOnceCmd() *cobra.Command {
   966  	iCmd := &cobra.Command{
   967  		Use:   "once",
   968  		Short: "run a command in the context ot the contxt shell",
   969  		Long: `run a command in the contxt shell. so you have access to all variables and functions 
   970  		they are only available in the contxt shell`,
   971  		Run: func(cmd *cobra.Command, args []string) {
   972  			c.checkDefaultFlags(cmd, args)
   973  			c.println("...using shell to execute commands:")
   974  			for _, arg := range args {
   975  				c.println("... ", ctxout.ForeLightBlue, arg, ctxout.CleanTag)
   976  			}
   977  			c.println("... timeout is ", ctxout.ForeLightBlue, c.Options.RunOnceTimeOutMillis, ctxout.CleanTag, " milliseconds")
   978  			c.ExternalCmdHndl.ShellWithComands(args, c.Options.RunOnceTimeOutMillis)
   979  		},
   980  	}
   981  	iCmd.PersistentFlags().IntVarP(&c.Options.RunOnceTimeOutMillis, "timeout", "t", 10000, "timeout for the interactive once commands in milliseconds")
   982  	return iCmd
   983  }